explainthisrepo 0.1.1 → 0.1.3

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.
package/dist/cli.js CHANGED
@@ -8,10 +8,12 @@ import { fetchRepo, fetchReadme } from "./github.js";
8
8
  import { buildPrompt } from "./prompt.js";
9
9
  import { generateExplanation } from "./generate.js";
10
10
  import { writeOutput } from "./writer.js";
11
+ import { readRepoSignalFiles } from "./repo_reader.js";
11
12
  function usage() {
12
13
  console.log("usage:");
13
14
  console.log(" explainthisrepo owner/repo");
14
15
  console.log(" explainthisrepo owner/repo --detailed");
16
+ console.log(" explainthisrepo owner/repo --quick");
15
17
  console.log(" explainthisrepo --doctor");
16
18
  console.log(" explainthisrepo --version");
17
19
  }
@@ -85,12 +87,16 @@ async function main() {
85
87
  process.exit(code);
86
88
  }
87
89
  let detailed = false;
90
+ let quick = false;
88
91
  if (args.length === 2) {
89
- if (args[1] !== "--detailed") {
92
+ if (args[1] === "--detailed")
93
+ detailed = true;
94
+ else if (args[1] === "--quick")
95
+ quick = true;
96
+ else {
90
97
  usage();
91
98
  process.exit(1);
92
99
  }
93
- detailed = true;
94
100
  }
95
101
  else if (args.length !== 1) {
96
102
  usage();
@@ -110,9 +116,15 @@ async function main() {
110
116
  try {
111
117
  const repoData = await fetchRepo(owner, repo);
112
118
  const readme = await fetchReadme(owner, repo);
113
- const prompt = buildPrompt(repoData.full_name, repoData.description, readme, detailed);
119
+ const readResult = await readRepoSignalFiles(owner, repo);
120
+ const prompt = buildPrompt(repoData.full_name, repoData.description, readme, detailed, quick, readResult.treeText, readResult.filesText);
114
121
  console.log("Generating explanation...");
115
122
  const output = await generateExplanation(prompt);
123
+ if (quick) {
124
+ console.log("Quick summary 🎉");
125
+ console.log(output.trim());
126
+ return;
127
+ }
116
128
  console.log("Writing EXPLAIN.md...");
117
129
  writeOutput(output);
118
130
  const wordCount = output.split(/\s+/).filter(Boolean).length;
package/dist/github.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export declare function fetchRepo(owner: string, repo: string): Promise<any>;
2
2
  export declare function fetchReadme(owner: string, repo: string): Promise<string | null>;
3
+ export declare function fetchTree(owner: string, repo: string): Promise<any[]>;
4
+ export declare function fetchFile(owner: string, repo: string, filePath: string): Promise<string>;
package/dist/github.js CHANGED
@@ -95,3 +95,28 @@ export async function fetchReadme(owner, repo) {
95
95
  throw err;
96
96
  }
97
97
  }
98
+ export async function fetchTree(owner, repo) {
99
+ return requestWithRetry(async () => {
100
+ const repoRes = await github.get(`/repos/${owner}/${repo}`);
101
+ const defaultBranch = repoRes.data?.default_branch;
102
+ const res = await github.get(`/repos/${owner}/${repo}/git/trees/${defaultBranch}`, {
103
+ params: { recursive: 1 },
104
+ });
105
+ return (res.data?.tree || []).map((item) => ({
106
+ path: item.path,
107
+ type: item.type,
108
+ size: item.size,
109
+ }));
110
+ });
111
+ }
112
+ export async function fetchFile(owner, repo, filePath) {
113
+ return requestWithRetry(async () => {
114
+ const res = await github.get(`/repos/${owner}/${repo}/contents/${filePath}`, {
115
+ headers: {
116
+ ...getGithubHeaders(),
117
+ Accept: "application/vnd.github.v3.raw",
118
+ },
119
+ });
120
+ return res.data;
121
+ });
122
+ }
package/dist/prompt.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function buildPrompt(repoName: string, description: string | null, readme: string | null, detailed?: boolean): string;
1
+ export declare function buildPrompt(repoName: string, description: string | null, readme: string | null, detailed?: boolean, quick?: boolean, treeText?: string | null, filesText?: string | null): string;
package/dist/prompt.js CHANGED
@@ -1,4 +1,28 @@
1
- export function buildPrompt(repoName, description, readme, detailed = false) {
1
+ export function buildPrompt(repoName, description, readme, detailed = false, quick = false, treeText = null, filesText = null) {
2
+ if (quick) {
3
+ const readmeSnippet = (readme || "").slice(0, 2000);
4
+ return `
5
+ You are a senior software engineer.
6
+
7
+ Write a ONE-SENTENCE plain-English definition of what this GitHub repository is.
8
+
9
+ Repository:
10
+ - Name: ${repoName}
11
+ - Description: ${description || "No description provided"}
12
+
13
+ README snippet:
14
+ ${readmeSnippet || "No README provided"}
15
+
16
+ Rules:
17
+ - Output MUST be exactly 1 sentence.
18
+ - Plain English.
19
+ - No markdown.
20
+ - No quotes.
21
+ - No bullet points.
22
+ - No extra text.
23
+ - Do not add features not stated in the description/README.
24
+ `.trim();
25
+ }
2
26
  let prompt = `
3
27
  You are a senior software engineer.
4
28
 
@@ -11,6 +35,12 @@ Repository:
11
35
  README content:
12
36
  ${readme || "No README provided"}
13
37
 
38
+ Repository structure:
39
+ ${treeText || "No tree rovided"}
40
+
41
+ Key files (snippets):
42
+ ${filesText || "No code files provided"}
43
+
14
44
  Instructions:
15
45
  - Explain what this project does.
16
46
  - Say who it is for.
@@ -0,0 +1,6 @@
1
+ export type RepoReadResult = {
2
+ treeText: string;
3
+ filesText: string;
4
+ selectedFiles: string[];
5
+ };
6
+ export declare function readRepoSignalFiles(owner: string, repo: string): Promise<RepoReadResult>;
@@ -0,0 +1,121 @@
1
+ import { fetchTree, fetchFile } from "./github.js";
2
+ const MAX_FILES = 20;
3
+ const MAX_TOTAL_BYTES = 150_000;
4
+ const MAX_FILE_CHARS = 6000;
5
+ function isSkippablePath(p) {
6
+ const s = p.toLowerCase();
7
+ return (s.includes("node_modules/") ||
8
+ s.startsWith("node_modules/") ||
9
+ s.includes("dist/") ||
10
+ s.startsWith("dist/") ||
11
+ s.includes(".git/") ||
12
+ s.startsWith(".git/") ||
13
+ s.endsWith(".min.js") ||
14
+ s.endsWith(".min.css") ||
15
+ s.endsWith(".map") ||
16
+ s.endsWith(".lock") ||
17
+ s === "package-lock.json" ||
18
+ s === "pnpm-lock.yaml" ||
19
+ s === "bun.lockb" ||
20
+ s === "yarn.lock");
21
+ }
22
+ function scoreSignalFile(p) {
23
+ const s = p.toLowerCase();
24
+ if (s === "package.json")
25
+ return 100;
26
+ if (s === "pyproject.toml" || s === "requirements.txt")
27
+ return 90;
28
+ if (s === "go.mod" || s === "cargo.toml" || s === "pom.xml" || s === "build.gradle")
29
+ return 85;
30
+ if (s === "tsconfig.json")
31
+ return 75;
32
+ if (s === "next.config.js" || s === "next.config.mjs" || s === "vite.config.ts" || s === "vite.config.js")
33
+ return 70;
34
+ if (s === "svelte.config.js" || s === "nuxt.config.ts" || s === "nuxt.config.js")
35
+ return 70;
36
+ if (s === "dockerfile" || s.endsWith("dockerfile"))
37
+ return 65;
38
+ if (s === "docker-compose.yml" || s === "compose.yml")
39
+ return 60;
40
+ if (s === "vercel.json" || s === "netlify.toml")
41
+ return 55;
42
+ if (s.endsWith("/main.ts") || s.endsWith("/main.js") || s.endsWith("/index.ts") || s.endsWith("/index.js"))
43
+ return 60;
44
+ if (s.endsWith("/app.ts") || s.endsWith("/app.js") || s.endsWith("/server.ts") || s.endsWith("/server.js"))
45
+ return 58;
46
+ if (s.endsWith("/cli.ts") || s.endsWith("/cli.js"))
47
+ return 57;
48
+ if (s.endsWith("main.py") || s.endsWith("__main__.py"))
49
+ return 55;
50
+ if (s.startsWith("apps/") || s.startsWith("packages/"))
51
+ return 50;
52
+ if (s.endsWith(".ts") || s.endsWith(".js") || s.endsWith(".py") || s.endsWith(".go") || s.endsWith(".rs"))
53
+ return 15;
54
+ return 0;
55
+ }
56
+ function buildTreeSummary(tree) {
57
+ const lines = [];
58
+ const top = new Set();
59
+ const second = new Set();
60
+ for (const item of tree) {
61
+ const parts = item.path.split("/");
62
+ if (parts.length >= 1)
63
+ top.add(parts[0]);
64
+ if (parts.length >= 2)
65
+ second.add(`${parts[0]}/${parts[1]}`);
66
+ }
67
+ lines.push("Top-level:");
68
+ lines.push(Array.from(top).sort().join(" "));
69
+ lines.push("\nSecond-level:");
70
+ lines.push(Array.from(second).sort().slice(0, 60).join("\n"));
71
+ return lines.join("\n");
72
+ }
73
+ export async function readRepoSignalFiles(owner, repo) {
74
+ const tree = await fetchTree(owner, repo);
75
+ const blobs = tree
76
+ .filter((x) => x.type === "blob")
77
+ .map((x) => ({ path: x.path, type: "blob", size: x.size }));
78
+ const scored = blobs
79
+ .filter((x) => !isSkippablePath(x.path))
80
+ .map((x) => ({
81
+ path: x.path,
82
+ size: x.size ?? 0,
83
+ score: scoreSignalFile(x.path),
84
+ }))
85
+ .filter((x) => x.score > 0)
86
+ .sort((a, b) => b.score - a.score);
87
+ const selected = [];
88
+ let totalBytes = 0;
89
+ for (const f of scored) {
90
+ if (selected.length >= MAX_FILES)
91
+ break;
92
+ if (f.size > 0 && f.size > 200_000)
93
+ continue;
94
+ if (totalBytes + (f.size || 2000) > MAX_TOTAL_BYTES)
95
+ continue;
96
+ selected.push(f.path);
97
+ totalBytes += f.size || 2000;
98
+ }
99
+ const treeText = buildTreeSummary(tree);
100
+ const fileBlocks = [];
101
+ for (const filePath of selected) {
102
+ try {
103
+ const content = await fetchFile(owner, repo, filePath);
104
+ const trimmed = (content || "").slice(0, MAX_FILE_CHARS);
105
+ fileBlocks.push([
106
+ `FILE: ${filePath}`,
107
+ "```",
108
+ trimmed,
109
+ "```",
110
+ "",
111
+ ].join("\n"));
112
+ }
113
+ catch {
114
+ }
115
+ }
116
+ return {
117
+ treeText,
118
+ filesText: fileBlocks.join("\n"),
119
+ selectedFiles: selected,
120
+ };
121
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "explainthisrepo",
3
- "version": "0.1.1",
4
- "description": "CLI tool to explain a GitHub repository in plain English",
3
+ "version": "0.1.3",
4
+ "description": "ExplainThisRepo is a CLI developer tool to explain any GitHub repository in plain English",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "author": "Caleb Wodi <calebwodi33@gmail.com>",
@@ -20,6 +20,10 @@
20
20
  "explain",
21
21
  "repository",
22
22
  "ai",
23
+ "repo-analysis",
24
+ "code-explanater",
25
+ "documentation",
26
+ "developer-productivity",
23
27
  "developer-tools"
24
28
  ],
25
29
  "bin": {