codebread 1.0.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.
- codebread-1.0.0/LICENSE +21 -0
- codebread-1.0.0/PKG-INFO +254 -0
- codebread-1.0.0/README.md +240 -0
- codebread-1.0.0/codebread/__init__.py +3 -0
- codebread-1.0.0/codebread/analyzer.py +84 -0
- codebread-1.0.0/codebread/classifier.py +102 -0
- codebread-1.0.0/codebread/cli.py +106 -0
- codebread-1.0.0/codebread/connections.py +407 -0
- codebread-1.0.0/codebread/diff.py +123 -0
- codebread-1.0.0/codebread/export.py +45 -0
- codebread-1.0.0/codebread/languages.py +110 -0
- codebread-1.0.0/codebread/models.py +119 -0
- codebread-1.0.0/codebread/parsers/__init__.py +53 -0
- codebread-1.0.0/codebread/parsers/generic_parser.py +288 -0
- codebread-1.0.0/codebread/parsers/javascript_parser.py +371 -0
- codebread-1.0.0/codebread/parsers/python_parser.py +291 -0
- codebread-1.0.0/codebread/scanner.py +206 -0
- codebread-1.0.0/codebread/server.py +86 -0
- codebread-1.0.0/codebread/web/app.js +1716 -0
- codebread-1.0.0/codebread/web/index.html +188 -0
- codebread-1.0.0/codebread/web/style.css +601 -0
- codebread-1.0.0/codebread.egg-info/PKG-INFO +254 -0
- codebread-1.0.0/codebread.egg-info/SOURCES.txt +27 -0
- codebread-1.0.0/codebread.egg-info/dependency_links.txt +1 -0
- codebread-1.0.0/codebread.egg-info/entry_points.txt +2 -0
- codebread-1.0.0/codebread.egg-info/requires.txt +3 -0
- codebread-1.0.0/codebread.egg-info/top_level.txt +1 -0
- codebread-1.0.0/pyproject.toml +26 -0
- codebread-1.0.0/setup.cfg +4 -0
codebread-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danial Irsyad
|
|
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.
|
codebread-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codebread
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Interactive codebase analyzer & visualizer — slice open a project and see how it's wired together.
|
|
5
|
+
Author: Danial Irsyad
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: codebase,analyzer,visualizer,call-graph,architecture
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Provides-Extra: full
|
|
12
|
+
Requires-Dist: pathspec>=0.11; extra == "full"
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="assets/logo-wordmark.png" alt="CodeBread" width="360">
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<b>Slice open any codebase and see how it's actually wired together.</b><br>
|
|
21
|
+
An interactive, zero-dependency map of files → functions → API routes → database tables.
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<img alt="version" src="https://img.shields.io/badge/version-1.0.0-2dd4bf">
|
|
26
|
+
<img alt="python" src="https://img.shields.io/badge/python-3.9%2B-0284c7">
|
|
27
|
+
<img alt="deps" src="https://img.shields.io/badge/required%20dependencies-none-2dd4bf">
|
|
28
|
+
<img alt="license" src="https://img.shields.io/badge/license-MIT-8b5cf6">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## What is this
|
|
34
|
+
|
|
35
|
+
CodeBread scans a project, extracts every function and class, maps how
|
|
36
|
+
everything connects — **frontend → API → backend → database** — and renders
|
|
37
|
+
it as an interactive node-graph in your browser. Click a file to slice it
|
|
38
|
+
open into its functions; click a function to see everything it calls and
|
|
39
|
+
everything that calls it, with the full chain lit up end to end.
|
|
40
|
+
|
|
41
|
+
**The problem it solves:** dropping into a codebase you didn't write — or one
|
|
42
|
+
an AI generated for you — and not knowing where anything is. Reading files
|
|
43
|
+
one at a time doesn't build a mental model fast. CodeBread builds the model
|
|
44
|
+
for you: it finds the routes, follows the `fetch()` calls to the handlers
|
|
45
|
+
that answer them, follows the handlers to the tables they touch, and draws
|
|
46
|
+
the whole path.
|
|
47
|
+
|
|
48
|
+
## Why I built this
|
|
49
|
+
|
|
50
|
+
This started as a personal tool. I kept generating projects with AI and then
|
|
51
|
+
staring at the result with no idea how the pieces fit together — which file
|
|
52
|
+
called which, where the API boundary actually was, what touched the
|
|
53
|
+
database. I wanted something that would just *show* me, instead of me
|
|
54
|
+
grepping through the tree file by file. CodeBread is that tool.
|
|
55
|
+
|
|
56
|
+
It's a v1.0 — actively used by me on my own projects, and I'll keep adding
|
|
57
|
+
to it. If it's useful to you too, that's a bonus. Issues and PRs are welcome.
|
|
58
|
+
|
|
59
|
+
## What it's built with
|
|
60
|
+
|
|
61
|
+
- **Backend/analysis engine:** pure Python **standard library** — no
|
|
62
|
+
required third-party packages, nothing to `pip install` beyond Python
|
|
63
|
+
itself. Parsing is stdlib `ast` for Python and structural regex for
|
|
64
|
+
everything else (see language table below).
|
|
65
|
+
- **Frontend:** vanilla **JavaScript + SVG**, no framework, no build step,
|
|
66
|
+
no `npm install`. The entire UI is three files (`app.js`, `index.html`,
|
|
67
|
+
`style.css`).
|
|
68
|
+
- **Serving:** a tiny local server built on `http.server` from the stdlib.
|
|
69
|
+
|
|
70
|
+
That's it. Clone it, run it, nothing to compile.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# option A: install the CLI
|
|
78
|
+
pip install .
|
|
79
|
+
|
|
80
|
+
# option B: don't install anything, just run it
|
|
81
|
+
python codebread.py --path /path/to/project
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Optional extra (better `.gitignore` matching while scanning):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install .[full] # adds pathspec
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Requires **Python 3.9+**. Nothing else.
|
|
91
|
+
|
|
92
|
+
## Use
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
codebread --path /path/to/project # scan + open the UI in your browser
|
|
96
|
+
codebread # prompts for the path
|
|
97
|
+
python codebread.py --path . # uninstalled, same thing
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That's the whole quick start: point it at a folder, a browser tab opens with
|
|
101
|
+
the map already scanned.
|
|
102
|
+
|
|
103
|
+
### CLI reference
|
|
104
|
+
|
|
105
|
+
| Flag | What it does |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `--path, -p PATH` | root folder to scan (prompted if omitted) |
|
|
108
|
+
| `--port 8137` | local server port (auto-picks a free one nearby if taken) |
|
|
109
|
+
| `--json out.json` | export the full graph as JSON |
|
|
110
|
+
| `--load out.json` | re-open a saved scan without re-scanning |
|
|
111
|
+
| `--html out.html` | export a **self-contained static HTML** file you can share |
|
|
112
|
+
| `--diff old.json new.json` | compare two saved scans and print what changed (files/functions/tables added, removed, changed) |
|
|
113
|
+
| `--no-open` | don't auto-open the browser |
|
|
114
|
+
| `--no-serve` | scan + export only, don't start the server |
|
|
115
|
+
| `--version` | print the installed version |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## What you get
|
|
120
|
+
|
|
121
|
+
- **Explorer sidebar** — the full folder tree, expandable like a file
|
|
122
|
+
explorer; every file tagged with its layer color.
|
|
123
|
+
- **Orbit layout (default)** — files float freely in space, spread apart so
|
|
124
|
+
connections stay untangled. Expand a file and its functions arrange in a
|
|
125
|
+
clean ring around it, spoked by straight lines back to the center. A
|
|
126
|
+
**Free** force-directed layout is also available (toggle bottom-right).
|
|
127
|
+
- **Focus mode** — clears the canvas so you inspect one file at a time:
|
|
128
|
+
pick a file in the Explorer and only it, its functions, and its direct
|
|
129
|
+
connections show up. Toggle it off to go back to the full overview.
|
|
130
|
+
- **Progressive reveal (OSINT-style)** — click a file to slice it open into
|
|
131
|
+
its numbered functions; click a function to draw in what it calls and
|
|
132
|
+
what calls it. Dense codebases stay readable.
|
|
133
|
+
- **Full-chain highlight** — select any node and its complete end-to-end
|
|
134
|
+
chain lights up (frontend `fetch()` → `GET /api/users` route → handler →
|
|
135
|
+
`users` table), everything unrelated dims.
|
|
136
|
+
- **Right-click for actions** — a context menu on every node, edge, and
|
|
137
|
+
the empty canvas: reveal connections, focus a file, open it in the IDE
|
|
138
|
+
view, jump to a route's source/target, copy a name or path, and more.
|
|
139
|
+
- **Insights panel** — flags orphaned functions (no detected callers) and
|
|
140
|
+
circular call chains, both clickable to jump straight to them.
|
|
141
|
+
- **Diff mode** — compare two saved scans (`--diff old.json new.json`) to
|
|
142
|
+
see exactly what changed between them.
|
|
143
|
+
- **Minimap + breadcrumb** — a live minimap of the whole graph with a
|
|
144
|
+
draggable viewport, and a breadcrumb showing the real folder path of
|
|
145
|
+
whatever's currently selected.
|
|
146
|
+
- **Keyboard navigation** — `←`/`→` step through a selected node's
|
|
147
|
+
neighbors, `↑`/`↓` walk back/forward through your selection history,
|
|
148
|
+
`/` to search.
|
|
149
|
+
- **Detail panel** — params, return type, auto-generated description
|
|
150
|
+
("Function 3 · handles GET /api/users · queries users"), every caller and
|
|
151
|
+
callee, clickable.
|
|
152
|
+
- **View the actual code** — every function has an expandable
|
|
153
|
+
**▸ View code** section: syntax-highlighted source with real line
|
|
154
|
+
numbers. A full **IDE view** (⌗) opens any file whole, with an outline
|
|
155
|
+
sidebar.
|
|
156
|
+
- **Search** (`/`) and **layer filter** (Frontend / Backend / Database /
|
|
157
|
+
Config) in the header.
|
|
158
|
+
- **Warnings, never silence** — unreadable files, unsupported languages and
|
|
159
|
+
permission problems show up as ⚠ badges and in the warnings list, never
|
|
160
|
+
hidden. Secrets found in config files are always masked.
|
|
161
|
+
|
|
162
|
+
## Language support
|
|
163
|
+
|
|
164
|
+
| Language | Parser | Extracted |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| Python | stdlib `ast` (precise) | functions, classes, params/annotations, docstrings, Flask/FastAPI/Django routes, SQLAlchemy/Django models, raw SQL, `requests`/`httpx` calls |
|
|
167
|
+
| JavaScript / TypeScript / JSX / TSX / Vue / Svelte | structural regex + brace matching | functions, arrow fns, classes, Express/Nest/Fastify routes, `fetch`/`axios` calls, Mongoose/Prisma/Sequelize/Knex/TypeORM, raw SQL |
|
|
168
|
+
| Java, C#, Go, PHP, Ruby | structural regex | functions/methods, classes, Spring/ASP.NET/Laravel/Rails/Gin routes, raw SQL |
|
|
169
|
+
| SQL | regex | `CREATE TABLE` schemas with columns |
|
|
170
|
+
| Config (`.env`, json/yaml/toml/ini, settings.py…) | key scan | DB connection settings — **credentials always masked** |
|
|
171
|
+
| Anything else (Rust, Kotlin, Swift, C/C++…) | — | shown in the UI as "⚠ Unsupported — parsing skipped", never silently dropped |
|
|
172
|
+
|
|
173
|
+
## How it classifies layers
|
|
174
|
+
|
|
175
|
+
Score-based heuristics combining framework import signatures
|
|
176
|
+
(React/Vue → frontend, Flask/Express → backend, SQLAlchemy/Prisma →
|
|
177
|
+
database), folder conventions (`/client`, `/server`, `/models`…), file
|
|
178
|
+
extensions, and extracted facts (defines routes → backend, defines models →
|
|
179
|
+
database). Ambiguous files are marked **Unclassified** instead of guessed.
|
|
180
|
+
|
|
181
|
+
## How orphan + cycle detection works
|
|
182
|
+
|
|
183
|
+
- **Orphaned functions**: any function/method with zero detected incoming
|
|
184
|
+
calls, that isn't a route handler and isn't a common framework entry
|
|
185
|
+
point (`__init__`, `main`, `setUp`, test functions, …). Flagged with a
|
|
186
|
+
badge on the node and listed in the Insights panel — a good starting
|
|
187
|
+
point for finding dead code (heuristic, not exhaustive: it can miss calls
|
|
188
|
+
the static parser doesn't recognize).
|
|
189
|
+
- **Circular call chains**: an iterative cycle-detection pass over the
|
|
190
|
+
call graph (no recursion, so it's safe on large codebases) flags
|
|
191
|
+
functions that call each other in a loop, both on the node (↻ badge) and
|
|
192
|
+
as a listed chain in the Insights panel.
|
|
193
|
+
|
|
194
|
+
## Project layout
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
codebread.py ← runnable entry point (no install needed)
|
|
198
|
+
codebread/
|
|
199
|
+
cli.py ← argparse CLI
|
|
200
|
+
scanner.py ← .gitignore-aware recursive walker
|
|
201
|
+
languages.py ← language detection / binary sniffing
|
|
202
|
+
parsers/
|
|
203
|
+
python_parser.py ← ast-based Python extractor
|
|
204
|
+
javascript_parser.py ← JS/TS/Vue/Svelte extractor
|
|
205
|
+
generic_parser.py ← Java/C#/Go/PHP/Ruby + SQL + config
|
|
206
|
+
classifier.py ← frontend/backend/database/config scoring
|
|
207
|
+
connections.py ← call graph + API↔route + fn↔table matching +
|
|
208
|
+
orphan/cycle detection
|
|
209
|
+
analyzer.py ← pipeline orchestration
|
|
210
|
+
diff.py ← compares two saved scans
|
|
211
|
+
server.py ← stdlib local web server
|
|
212
|
+
export.py ← JSON + single-file HTML export
|
|
213
|
+
web/ ← the UI (vanilla JS + SVG, zero dependencies)
|
|
214
|
+
assets/ ← logo (.svg source + .png for the README —
|
|
215
|
+
GitHub blocks same-repo SVG <img> embeds)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Roadmap
|
|
221
|
+
|
|
222
|
+
Already done as of v1.0:
|
|
223
|
+
|
|
224
|
+
- [x] Orphaned-function and circular-dependency detection
|
|
225
|
+
- [x] Diff mode between two scans
|
|
226
|
+
- [x] Focus mode, orbit layout, right-click actions, minimap, breadcrumb
|
|
227
|
+
|
|
228
|
+
Not yet, but planned:
|
|
229
|
+
|
|
230
|
+
- [ ] "Explain this function" — a plain-language AI summary button
|
|
231
|
+
- [ ] More precise multi-language parsing (currently structural regex for
|
|
232
|
+
everything but Python)
|
|
233
|
+
- [ ] An automated test suite
|
|
234
|
+
|
|
235
|
+
Have an idea? Open an issue.
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
This is a young, actively-changing personal project, so keep that in mind —
|
|
240
|
+
but contributions are genuinely welcome:
|
|
241
|
+
|
|
242
|
+
- **Bugs / ideas** → open an issue with a repro or a description.
|
|
243
|
+
- **Pull requests** → welcome, especially for language parser improvements
|
|
244
|
+
or UI polish. Keep the zero-dependency philosophy: the core tool should
|
|
245
|
+
keep running on nothing but the Python standard library, and the UI
|
|
246
|
+
should keep running on vanilla JS with no build step.
|
|
247
|
+
- No formal test suite yet, so please describe how you manually verified a
|
|
248
|
+
change in your PR.
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
[MIT](LICENSE) — use it, modify it, ship it, sell it. No attribution
|
|
253
|
+
required beyond keeping the license file. See [LICENSE](LICENSE) for the
|
|
254
|
+
full text.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo-wordmark.png" alt="CodeBread" width="360">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<b>Slice open any codebase and see how it's actually wired together.</b><br>
|
|
7
|
+
An interactive, zero-dependency map of files → functions → API routes → database tables.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img alt="version" src="https://img.shields.io/badge/version-1.0.0-2dd4bf">
|
|
12
|
+
<img alt="python" src="https://img.shields.io/badge/python-3.9%2B-0284c7">
|
|
13
|
+
<img alt="deps" src="https://img.shields.io/badge/required%20dependencies-none-2dd4bf">
|
|
14
|
+
<img alt="license" src="https://img.shields.io/badge/license-MIT-8b5cf6">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What is this
|
|
20
|
+
|
|
21
|
+
CodeBread scans a project, extracts every function and class, maps how
|
|
22
|
+
everything connects — **frontend → API → backend → database** — and renders
|
|
23
|
+
it as an interactive node-graph in your browser. Click a file to slice it
|
|
24
|
+
open into its functions; click a function to see everything it calls and
|
|
25
|
+
everything that calls it, with the full chain lit up end to end.
|
|
26
|
+
|
|
27
|
+
**The problem it solves:** dropping into a codebase you didn't write — or one
|
|
28
|
+
an AI generated for you — and not knowing where anything is. Reading files
|
|
29
|
+
one at a time doesn't build a mental model fast. CodeBread builds the model
|
|
30
|
+
for you: it finds the routes, follows the `fetch()` calls to the handlers
|
|
31
|
+
that answer them, follows the handlers to the tables they touch, and draws
|
|
32
|
+
the whole path.
|
|
33
|
+
|
|
34
|
+
## Why I built this
|
|
35
|
+
|
|
36
|
+
This started as a personal tool. I kept generating projects with AI and then
|
|
37
|
+
staring at the result with no idea how the pieces fit together — which file
|
|
38
|
+
called which, where the API boundary actually was, what touched the
|
|
39
|
+
database. I wanted something that would just *show* me, instead of me
|
|
40
|
+
grepping through the tree file by file. CodeBread is that tool.
|
|
41
|
+
|
|
42
|
+
It's a v1.0 — actively used by me on my own projects, and I'll keep adding
|
|
43
|
+
to it. If it's useful to you too, that's a bonus. Issues and PRs are welcome.
|
|
44
|
+
|
|
45
|
+
## What it's built with
|
|
46
|
+
|
|
47
|
+
- **Backend/analysis engine:** pure Python **standard library** — no
|
|
48
|
+
required third-party packages, nothing to `pip install` beyond Python
|
|
49
|
+
itself. Parsing is stdlib `ast` for Python and structural regex for
|
|
50
|
+
everything else (see language table below).
|
|
51
|
+
- **Frontend:** vanilla **JavaScript + SVG**, no framework, no build step,
|
|
52
|
+
no `npm install`. The entire UI is three files (`app.js`, `index.html`,
|
|
53
|
+
`style.css`).
|
|
54
|
+
- **Serving:** a tiny local server built on `http.server` from the stdlib.
|
|
55
|
+
|
|
56
|
+
That's it. Clone it, run it, nothing to compile.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# option A: install the CLI
|
|
64
|
+
pip install .
|
|
65
|
+
|
|
66
|
+
# option B: don't install anything, just run it
|
|
67
|
+
python codebread.py --path /path/to/project
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Optional extra (better `.gitignore` matching while scanning):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install .[full] # adds pathspec
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Requires **Python 3.9+**. Nothing else.
|
|
77
|
+
|
|
78
|
+
## Use
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
codebread --path /path/to/project # scan + open the UI in your browser
|
|
82
|
+
codebread # prompts for the path
|
|
83
|
+
python codebread.py --path . # uninstalled, same thing
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
That's the whole quick start: point it at a folder, a browser tab opens with
|
|
87
|
+
the map already scanned.
|
|
88
|
+
|
|
89
|
+
### CLI reference
|
|
90
|
+
|
|
91
|
+
| Flag | What it does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `--path, -p PATH` | root folder to scan (prompted if omitted) |
|
|
94
|
+
| `--port 8137` | local server port (auto-picks a free one nearby if taken) |
|
|
95
|
+
| `--json out.json` | export the full graph as JSON |
|
|
96
|
+
| `--load out.json` | re-open a saved scan without re-scanning |
|
|
97
|
+
| `--html out.html` | export a **self-contained static HTML** file you can share |
|
|
98
|
+
| `--diff old.json new.json` | compare two saved scans and print what changed (files/functions/tables added, removed, changed) |
|
|
99
|
+
| `--no-open` | don't auto-open the browser |
|
|
100
|
+
| `--no-serve` | scan + export only, don't start the server |
|
|
101
|
+
| `--version` | print the installed version |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## What you get
|
|
106
|
+
|
|
107
|
+
- **Explorer sidebar** — the full folder tree, expandable like a file
|
|
108
|
+
explorer; every file tagged with its layer color.
|
|
109
|
+
- **Orbit layout (default)** — files float freely in space, spread apart so
|
|
110
|
+
connections stay untangled. Expand a file and its functions arrange in a
|
|
111
|
+
clean ring around it, spoked by straight lines back to the center. A
|
|
112
|
+
**Free** force-directed layout is also available (toggle bottom-right).
|
|
113
|
+
- **Focus mode** — clears the canvas so you inspect one file at a time:
|
|
114
|
+
pick a file in the Explorer and only it, its functions, and its direct
|
|
115
|
+
connections show up. Toggle it off to go back to the full overview.
|
|
116
|
+
- **Progressive reveal (OSINT-style)** — click a file to slice it open into
|
|
117
|
+
its numbered functions; click a function to draw in what it calls and
|
|
118
|
+
what calls it. Dense codebases stay readable.
|
|
119
|
+
- **Full-chain highlight** — select any node and its complete end-to-end
|
|
120
|
+
chain lights up (frontend `fetch()` → `GET /api/users` route → handler →
|
|
121
|
+
`users` table), everything unrelated dims.
|
|
122
|
+
- **Right-click for actions** — a context menu on every node, edge, and
|
|
123
|
+
the empty canvas: reveal connections, focus a file, open it in the IDE
|
|
124
|
+
view, jump to a route's source/target, copy a name or path, and more.
|
|
125
|
+
- **Insights panel** — flags orphaned functions (no detected callers) and
|
|
126
|
+
circular call chains, both clickable to jump straight to them.
|
|
127
|
+
- **Diff mode** — compare two saved scans (`--diff old.json new.json`) to
|
|
128
|
+
see exactly what changed between them.
|
|
129
|
+
- **Minimap + breadcrumb** — a live minimap of the whole graph with a
|
|
130
|
+
draggable viewport, and a breadcrumb showing the real folder path of
|
|
131
|
+
whatever's currently selected.
|
|
132
|
+
- **Keyboard navigation** — `←`/`→` step through a selected node's
|
|
133
|
+
neighbors, `↑`/`↓` walk back/forward through your selection history,
|
|
134
|
+
`/` to search.
|
|
135
|
+
- **Detail panel** — params, return type, auto-generated description
|
|
136
|
+
("Function 3 · handles GET /api/users · queries users"), every caller and
|
|
137
|
+
callee, clickable.
|
|
138
|
+
- **View the actual code** — every function has an expandable
|
|
139
|
+
**▸ View code** section: syntax-highlighted source with real line
|
|
140
|
+
numbers. A full **IDE view** (⌗) opens any file whole, with an outline
|
|
141
|
+
sidebar.
|
|
142
|
+
- **Search** (`/`) and **layer filter** (Frontend / Backend / Database /
|
|
143
|
+
Config) in the header.
|
|
144
|
+
- **Warnings, never silence** — unreadable files, unsupported languages and
|
|
145
|
+
permission problems show up as ⚠ badges and in the warnings list, never
|
|
146
|
+
hidden. Secrets found in config files are always masked.
|
|
147
|
+
|
|
148
|
+
## Language support
|
|
149
|
+
|
|
150
|
+
| Language | Parser | Extracted |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| Python | stdlib `ast` (precise) | functions, classes, params/annotations, docstrings, Flask/FastAPI/Django routes, SQLAlchemy/Django models, raw SQL, `requests`/`httpx` calls |
|
|
153
|
+
| JavaScript / TypeScript / JSX / TSX / Vue / Svelte | structural regex + brace matching | functions, arrow fns, classes, Express/Nest/Fastify routes, `fetch`/`axios` calls, Mongoose/Prisma/Sequelize/Knex/TypeORM, raw SQL |
|
|
154
|
+
| Java, C#, Go, PHP, Ruby | structural regex | functions/methods, classes, Spring/ASP.NET/Laravel/Rails/Gin routes, raw SQL |
|
|
155
|
+
| SQL | regex | `CREATE TABLE` schemas with columns |
|
|
156
|
+
| Config (`.env`, json/yaml/toml/ini, settings.py…) | key scan | DB connection settings — **credentials always masked** |
|
|
157
|
+
| Anything else (Rust, Kotlin, Swift, C/C++…) | — | shown in the UI as "⚠ Unsupported — parsing skipped", never silently dropped |
|
|
158
|
+
|
|
159
|
+
## How it classifies layers
|
|
160
|
+
|
|
161
|
+
Score-based heuristics combining framework import signatures
|
|
162
|
+
(React/Vue → frontend, Flask/Express → backend, SQLAlchemy/Prisma →
|
|
163
|
+
database), folder conventions (`/client`, `/server`, `/models`…), file
|
|
164
|
+
extensions, and extracted facts (defines routes → backend, defines models →
|
|
165
|
+
database). Ambiguous files are marked **Unclassified** instead of guessed.
|
|
166
|
+
|
|
167
|
+
## How orphan + cycle detection works
|
|
168
|
+
|
|
169
|
+
- **Orphaned functions**: any function/method with zero detected incoming
|
|
170
|
+
calls, that isn't a route handler and isn't a common framework entry
|
|
171
|
+
point (`__init__`, `main`, `setUp`, test functions, …). Flagged with a
|
|
172
|
+
badge on the node and listed in the Insights panel — a good starting
|
|
173
|
+
point for finding dead code (heuristic, not exhaustive: it can miss calls
|
|
174
|
+
the static parser doesn't recognize).
|
|
175
|
+
- **Circular call chains**: an iterative cycle-detection pass over the
|
|
176
|
+
call graph (no recursion, so it's safe on large codebases) flags
|
|
177
|
+
functions that call each other in a loop, both on the node (↻ badge) and
|
|
178
|
+
as a listed chain in the Insights panel.
|
|
179
|
+
|
|
180
|
+
## Project layout
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
codebread.py ← runnable entry point (no install needed)
|
|
184
|
+
codebread/
|
|
185
|
+
cli.py ← argparse CLI
|
|
186
|
+
scanner.py ← .gitignore-aware recursive walker
|
|
187
|
+
languages.py ← language detection / binary sniffing
|
|
188
|
+
parsers/
|
|
189
|
+
python_parser.py ← ast-based Python extractor
|
|
190
|
+
javascript_parser.py ← JS/TS/Vue/Svelte extractor
|
|
191
|
+
generic_parser.py ← Java/C#/Go/PHP/Ruby + SQL + config
|
|
192
|
+
classifier.py ← frontend/backend/database/config scoring
|
|
193
|
+
connections.py ← call graph + API↔route + fn↔table matching +
|
|
194
|
+
orphan/cycle detection
|
|
195
|
+
analyzer.py ← pipeline orchestration
|
|
196
|
+
diff.py ← compares two saved scans
|
|
197
|
+
server.py ← stdlib local web server
|
|
198
|
+
export.py ← JSON + single-file HTML export
|
|
199
|
+
web/ ← the UI (vanilla JS + SVG, zero dependencies)
|
|
200
|
+
assets/ ← logo (.svg source + .png for the README —
|
|
201
|
+
GitHub blocks same-repo SVG <img> embeds)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Roadmap
|
|
207
|
+
|
|
208
|
+
Already done as of v1.0:
|
|
209
|
+
|
|
210
|
+
- [x] Orphaned-function and circular-dependency detection
|
|
211
|
+
- [x] Diff mode between two scans
|
|
212
|
+
- [x] Focus mode, orbit layout, right-click actions, minimap, breadcrumb
|
|
213
|
+
|
|
214
|
+
Not yet, but planned:
|
|
215
|
+
|
|
216
|
+
- [ ] "Explain this function" — a plain-language AI summary button
|
|
217
|
+
- [ ] More precise multi-language parsing (currently structural regex for
|
|
218
|
+
everything but Python)
|
|
219
|
+
- [ ] An automated test suite
|
|
220
|
+
|
|
221
|
+
Have an idea? Open an issue.
|
|
222
|
+
|
|
223
|
+
## Contributing
|
|
224
|
+
|
|
225
|
+
This is a young, actively-changing personal project, so keep that in mind —
|
|
226
|
+
but contributions are genuinely welcome:
|
|
227
|
+
|
|
228
|
+
- **Bugs / ideas** → open an issue with a repro or a description.
|
|
229
|
+
- **Pull requests** → welcome, especially for language parser improvements
|
|
230
|
+
or UI polish. Keep the zero-dependency philosophy: the core tool should
|
|
231
|
+
keep running on nothing but the Python standard library, and the UI
|
|
232
|
+
should keep running on vanilla JS with no build step.
|
|
233
|
+
- No formal test suite yet, so please describe how you manually verified a
|
|
234
|
+
change in your PR.
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
[MIT](LICENSE) — use it, modify it, ship it, sell it. No attribution
|
|
239
|
+
required beyond keeping the license file. See [LICENSE](LICENSE) for the
|
|
240
|
+
full text.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Orchestrates: scan -> parse -> classify -> connect -> graph dict."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Callable, Dict, Optional
|
|
8
|
+
|
|
9
|
+
from . import __version__
|
|
10
|
+
from .classifier import classify
|
|
11
|
+
from .connections import build_graph
|
|
12
|
+
from .parsers import parse_file
|
|
13
|
+
from .scanner import read_text, scan
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def analyze(root: str,
|
|
17
|
+
progress: Optional[Callable[[str], None]] = None) -> Dict:
|
|
18
|
+
"""Run the full pipeline on `root` and return the graph dict."""
|
|
19
|
+
say = progress or (lambda msg: None)
|
|
20
|
+
root = os.path.abspath(root)
|
|
21
|
+
|
|
22
|
+
say(f"Scanning {root} ...")
|
|
23
|
+
result = scan(root)
|
|
24
|
+
files_meta = result["files"]
|
|
25
|
+
warnings = result["warnings"]
|
|
26
|
+
say(f" found {len(files_meta)} scannable files")
|
|
27
|
+
|
|
28
|
+
parsed = []
|
|
29
|
+
unsupported = {}
|
|
30
|
+
for i, meta in enumerate(files_meta, 1):
|
|
31
|
+
text, err = read_text(meta["abspath"])
|
|
32
|
+
info = parse_file(meta["path"], text, meta["language"])
|
|
33
|
+
if err:
|
|
34
|
+
info.warnings.append(err)
|
|
35
|
+
info.layer = classify(info, text)
|
|
36
|
+
if info.parsed or info.language in ("html", "css"):
|
|
37
|
+
info.source = text if len(text) <= 300_000 else \
|
|
38
|
+
text[:300_000] + "\n… (truncated)"
|
|
39
|
+
parsed.append(info)
|
|
40
|
+
for w in info.warnings:
|
|
41
|
+
if w.startswith("Unsupported:"):
|
|
42
|
+
unsupported.setdefault(meta["language"], 0)
|
|
43
|
+
unsupported[meta["language"]] += 1
|
|
44
|
+
warnings.append({"path": meta["path"], "message": w})
|
|
45
|
+
if i % 50 == 0:
|
|
46
|
+
say(f" parsed {i}/{len(files_meta)} files")
|
|
47
|
+
|
|
48
|
+
for lang, count in sorted(unsupported.items()):
|
|
49
|
+
say(f" note: {count} file(s) in {lang} - no parser available, "
|
|
50
|
+
f"shown as unsupported in the UI")
|
|
51
|
+
|
|
52
|
+
say("Mapping connections ...")
|
|
53
|
+
_annotate_tree(result["tree"], {f.path: f for f in parsed})
|
|
54
|
+
graph = build_graph(parsed, result["tree"], warnings)
|
|
55
|
+
graph["meta"] = {
|
|
56
|
+
"root": root,
|
|
57
|
+
"name": os.path.basename(root) or root,
|
|
58
|
+
"scannedAt": datetime.datetime.now().isoformat(timespec="seconds"),
|
|
59
|
+
"version": __version__,
|
|
60
|
+
}
|
|
61
|
+
s = graph["stats"]
|
|
62
|
+
say(f"Done: {s['files']} files, {s['functions']} functions, "
|
|
63
|
+
f"{s['tables']} tables, {s['connections']} connections, "
|
|
64
|
+
f"{s['warnings']} warnings")
|
|
65
|
+
return graph
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _annotate_tree(node: Dict, by_path: Dict) -> None:
|
|
69
|
+
"""Copy layer/warning info onto tree nodes for the sidebar."""
|
|
70
|
+
if node.get("type") == "file":
|
|
71
|
+
info = by_path.get(node.get("path"))
|
|
72
|
+
if info is not None:
|
|
73
|
+
node["layer"] = info.layer
|
|
74
|
+
node["nFunctions"] = len(info.functions)
|
|
75
|
+
if info.warnings and "warning" not in node:
|
|
76
|
+
node["warning"] = "parse"
|
|
77
|
+
return
|
|
78
|
+
layers = set()
|
|
79
|
+
for child in node.get("children", []):
|
|
80
|
+
_annotate_tree(child, by_path)
|
|
81
|
+
if child.get("layer") and child["layer"] not in ("unknown", None):
|
|
82
|
+
layers.add(child["layer"])
|
|
83
|
+
if len(layers) == 1:
|
|
84
|
+
node["layer"] = layers.pop()
|