Skip to content

Commit bc29bcd

Browse files
authored
Merge pull request #219 from calchiwo/feat/repo-map
feat(cli, prompt): add repo map mode
2 parents 167e69f + 32233db commit bc29bcd

8 files changed

Lines changed: 209 additions & 25 deletions

File tree

README.md

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ExplainThisRepo
22

3-
_The fastest way to understand any codebase in plain English using real project signals. Not blind AI summarization._
3+
_The fastest way to understand any codebase in plain English using real project signals. Not blind AI guessing._
44

55
ExplainThisRepo analyzes real project signals; configs, entrypoints, manifests, dependencies graph, file structure and high-signal files producing a clear, structured `EXPLAIN.md` that explains what the codebase actually does and how it is organized in plain English.
66

@@ -18,16 +18,19 @@ ExplainThisRepo analyzes real project signals; configs, entrypoints, manifests,
1818

1919
- Understand any GitHub repository in seconds
2020
- Derives architectural summaries from repository structure and code signals.
21-
Not blind AI summarization.
22-
- Deterministic repo signal extractor that feeds LLMs correctly
21+
Not blind AI guessing
22+
- Deterministic repo signal extractor that feeds LLMs correctly. Signals first. LLM second
2323
- Translates complex code structures into plain English
2424
- Speeds up understanding of unfamiliar codebases
2525
- Solves the "**garbage in, garbage out**" problem specifically for codebases
2626
- Extract architecture signals from configs, entrypoints, and manifests
27-
- Works with GitHub repositories, local directories, private repositories, individual files and monorepos
27+
- System map that shows you where to start, what matters and what to ignore
28+
- Works with GitHub repositories, Local repositories, GitHub directories, local directories, GitHub files and local files
29+
- Supports private repositories and monorepos
2830
- Zero-cloning and remote analysis
29-
- Outputs the explanation to an `EXPLAIN.md` file in your current directory, prints it directly in the terminal, or a specified output file (`.txt`, `.pdf`, `.docx`)
31+
- Outputs the explanation to an `EXPLAIN.md` file in your current directory, prints it directly in the terminal, or a specified output file (`.txt`, `.pdf`, `.docx`, `.md`)
3032
- Multiple explanation modes (quick, simple, detailed)
33+
- Additional tools: stack detection, repo map
3134

3235
## Installation
3336

@@ -115,8 +118,8 @@ npx explainthisrepo owner/repo
115118

116119
ExplainThisRepo uses a hybrid architecture:
117120

118-
- Python → core implementaion (analysis, prompts, providers, output)
119-
- npm → ships prebuilt native binaries (no Python required)
121+
- Python → core implementation (analysis, prompts, providers, output)
122+
- npm → launches prebuilt native binaries (no Python install required)
120123
- pip → installs the full Python package
121124

122125
> The npm and pip versions run the same core engine.
@@ -185,7 +188,7 @@ Run:
185188
explainthisrepo init
186189
```
187190

188-
For step-by-step instructions, see [docs/GITHUB_TOKEN.md](docs/GITHUB_TOKEN.md)
191+
For step-by-step instructions, see [docs/GITHUB_TOKEN.md](https://github.com/calchiwo/ExplainThisRepo/blob/main/docs/GITHUB_TOKEN.md)
189192

190193
## Flag options
191194

@@ -199,6 +202,8 @@ For step-by-step instructions, see [docs/GITHUB_TOKEN.md](docs/GITHUB_TOKEN.md)
199202

200203
- `--stack` → Tech stack breakdown from repo signals
201204

205+
- `--map` → System map for understanding a codebase before changing it
206+
202207
- `--version` → Check installed CLI version
203208

204209
- `--help` → Show usage guide
@@ -209,7 +214,7 @@ For step-by-step instructions, see [docs/GITHUB_TOKEN.md](docs/GITHUB_TOKEN.md)
209214

210215
- `--output` / `-o` → Specify output file or directory (default: `EXPLAIN.md`)
211216

212-
## Flexible Repository and Local Directory Input
217+
## Flexible Input Types
213218

214219
Accepts various formats for repository input, full GitHub URLs (with or without https), `owner/repo` format, issue links, query strings, and SSH clone links
215220

@@ -226,7 +231,7 @@ explainthisrepo ./path/to/directory
226231
explainthisrepo ./path/to/file.py
227232
```
228233

229-
All inputs are normalized internally to `owner/repo`.
234+
GitHub inputs are normalized internally to `owner/repo`.
230235

231236
## CLI aliases
232237

@@ -322,6 +327,26 @@ explainthisrepo owner/repo --stack
322327
```
323328
![Stack detector Output](assets/stack-command-output.png)
324329

330+
### Repo map
331+
332+
Navigation system map for understanding unfamiliar codebases that shows you where to start, what matters and what to ignore before touching it:
333+
334+
```bash
335+
explainthisrepo owner/repo --map
336+
explainthisrepo . --map
337+
```
338+
339+
By default, repo map mode writes to `REPO_MAP.md`.
340+
341+
The map focuses on:
342+
343+
- where to start reading
344+
- the likely main flow through the project
345+
- important files and why they matter
346+
- visible architecture boundaries
347+
- files or folders to ignore first
348+
- open questions that cannot be determined from repo signals
349+
325350
## Local Directory Analysis
326351

327352
ExplainThisRepo can analyze local directories directly in the terminal, using the same modes and output formats as GitHub repositories
@@ -338,6 +363,7 @@ explainthisrepo . --quick
338363
explainthisrepo . --simple
339364
explainthisrepo . --detailed
340365
explainthisrepo . --stack
366+
explainthisrepo . --map
341367
```
342368

343369
When analyzing a local directory:
@@ -351,7 +377,7 @@ This allows analysis of projects directly from the local filesystem, without req
351377

352378
## File Analysis
353379

354-
ExplanThisRepo analyzes individual files directly
380+
ExplainThisRepo analyzes individual files directly
355381

356382
```bash
357383
explainthisrepo ./path/to/file.py
@@ -388,9 +414,11 @@ explainthisrepo owner/repo/path/to/file.py --detailed
388414

389415
When analyzing a GitHub file:
390416
- The file is fetched directly via the GitHub API
391-
- Only valid text files are processed (binary files are rejected)
392-
- File size is capped to prevent unsafe or truncated analysis
417+
- Raw bytes are passed into a unified ingestion pipeline
418+
- Binary detection, decoding, and size limits are handled in one place
419+
- File ingestion is identical to local file analysis
393420
- The explanation focuses on the file’s purpose, logic, and behavior
421+
- This removes divergence between local and GitHub file handling
394422

395423
Input format must be:
396424

@@ -421,14 +449,10 @@ explainthisrepo owner/repo/path/to/directory --detailed
421449
When analyzing a GitHub directory:
422450

423451
- Directory contents are fetched via the GitHub API
424-
425452
- Only structure and metadata are used (no full repo fetch)
426-
427453
- Signals include files, subdirectories, and extension distribution
428-
429454
- The explanation focuses on the directory’s role and structure
430455

431-
432456
`--stack` is not supported for directory targets.
433457

434458
### Custom output

_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "0.25.3"
1+
VERSION = "0.26.0"

explain_this_repo/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "0.25.3"
1+
VERSION = "0.26.0"

explain_this_repo/cli.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
build_file_simple_prompt,
3232
build_prompt,
3333
build_quick_prompt,
34+
build_repo_map_prompt,
3435
build_simple_prompt,
3536
)
3637
from explain_this_repo.repo_reader import read_repo_signal_files
@@ -367,10 +368,19 @@ def _extract_directory_signals(contents: list[dict]) -> dict:
367368
}
368369

369370

371+
def _resolve_mode_output(args, default_output: str) -> str:
372+
if args.output == "EXPLAIN.md":
373+
return default_output
374+
return args.output
375+
376+
370377
def _handle_file_mode(args, llm: str | None) -> None:
371378
if args.stack:
372379
print("error: --stack is not supported for file targets")
373380
raise SystemExit(1)
381+
if args.map:
382+
print("error: --map is not supported for file targets")
383+
raise SystemExit(1)
374384

375385
file_path = os.path.abspath(args.repository)
376386

@@ -452,6 +462,9 @@ def _handle_github_file_mode(
452462
if args.stack:
453463
print("error: --stack is not supported for GitHub file targets")
454464
raise SystemExit(1)
465+
if args.map:
466+
print("error: --map is not supported for GitHub file targets")
467+
raise SystemExit(1)
455468

456469
if owner is None or repo is None or file_path is None:
457470
try:
@@ -533,6 +546,9 @@ def _handle_github_directory_mode(
533546
if args.stack:
534547
print("error: --stack is not supported for GitHub directory targets")
535548
raise SystemExit(1)
549+
if args.map:
550+
print("error: --map is not supported for GitHub directory targets")
551+
raise SystemExit(1)
536552

537553
if owner is None or repo is None or directory_path is None:
538554
try:
@@ -629,6 +645,40 @@ def _handle_directory_mode(args, llm: str | None) -> None:
629645
print_stack(report, args.repository, "")
630646
return
631647

648+
if args.map:
649+
with console.status("Reading repository files...", spinner="dots"):
650+
read_result = read_local_repo_signal_files(local_path)
651+
652+
readme_content = read_result.key_files.get(
653+
next(
654+
(k for k in read_result.key_files if k.lower().startswith("readme")),
655+
"",
656+
),
657+
None,
658+
)
659+
660+
prompt = build_repo_map_prompt(
661+
repo_name=local_path,
662+
description=None,
663+
readme=readme_content,
664+
tree_text=read_result.tree_text,
665+
files_text=read_result.files_text,
666+
)
667+
668+
with console.status("Generating repo map...", spinner="dots"):
669+
output = generate_with_exit(prompt, llm=llm)
670+
671+
output_path = _resolve_mode_output(args, "REPO_MAP.md")
672+
print(f"Writing {output_path}...")
673+
write_output(output, output_path)
674+
675+
word_count = len(output.split())
676+
print(f"{output_path} generated successfully 🎉")
677+
print(f"Words: {word_count}")
678+
print(f"Location: {os.path.abspath(output_path)}")
679+
print(f"Open {output_path} to navigate the codebase.")
680+
return
681+
632682
if args.quick:
633683
with console.status("Reading repository files...", spinner="dots"):
634684
read_result = read_local_repo_signal_files(local_path)
@@ -722,6 +772,38 @@ def _handle_github_mode(args, llm: str | None) -> None:
722772
print_stack(report, f"{owner}/{repo}", "")
723773
return
724774

775+
if args.map:
776+
try:
777+
with console.status(f"Fetching {owner}/{repo}...", spinner="dots"):
778+
repo_data = fetch_repo(owner, repo)
779+
readme = fetch_readme(owner, repo)
780+
read_result = read_repo_signal_files(owner, repo)
781+
except Exception as e:
782+
print(f"error: {e}")
783+
raise SystemExit(1)
784+
785+
prompt = build_repo_map_prompt(
786+
repo_name=repo_data.get("full_name"),
787+
description=repo_data.get("description"),
788+
readme=readme,
789+
tree_text=read_result.tree_text,
790+
files_text=read_result.files_text,
791+
)
792+
793+
with console.status("Generating repo map...", spinner="dots"):
794+
output = generate_with_exit(prompt, llm=llm)
795+
796+
output_path = _resolve_mode_output(args, "REPO_MAP.md")
797+
print(f"Writing {output_path}...")
798+
write_output(output, output_path)
799+
800+
word_count = len(output.split())
801+
print(f"{output_path} generated successfully 🎉")
802+
print(f"Words: {word_count}")
803+
print(f"Location: {os.path.abspath(output_path)}")
804+
print(f"Open {output_path} to navigate the codebase.")
805+
return
806+
725807
try:
726808
with console.status(f"Fetching {owner}/{repo}...", spinner="dots"):
727809
repo_data = fetch_repo(owner, repo)
@@ -800,6 +882,7 @@ def main():
800882
" explainthisrepo owner/repo --quick\n"
801883
" explainthisrepo owner/repo --simple\n"
802884
" explainthisrepo owner/repo --stack\n"
885+
" explainthisrepo owner/repo --map\n"
803886
" explainthisrepo owner/repo/packages/react-dom\n"
804887
" explainthisrepo owner/repo/packages/react-dom --quick\n"
805888
" explainthisrepo owner/repo/packages/react-dom --simple\n"
@@ -814,6 +897,7 @@ def main():
814897
" explainthisrepo . --quick\n"
815898
" explainthisrepo . --simple\n"
816899
" explainthisrepo . --stack\n"
900+
" explainthisrepo . --map\n"
817901
" explainthisrepo ./path/to/file.py\n"
818902
" explainthisrepo ./path/to/file.py --quick\n"
819903
" explainthisrepo ./path/to/file.py --simple\n"
@@ -901,6 +985,11 @@ def main():
901985
action="store_true",
902986
help="Stack detection mode",
903987
)
988+
mode_group.add_argument(
989+
"--map",
990+
action="store_true",
991+
help="Map the system before changing it",
992+
)
904993

905994
args = parser.parse_args()
906995

@@ -958,4 +1047,4 @@ def _run():
9581047

9591048

9601049
if __name__ == "__main__":
961-
_run()
1050+
_run()

0 commit comments

Comments
 (0)