explainthisrepo 0.1.2 → 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 +3 -1
- package/dist/github.d.ts +2 -0
- package/dist/github.js +25 -0
- package/dist/prompt.d.ts +1 -1
- package/dist/prompt.js +7 -1
- package/dist/repo_reader.d.ts +6 -0
- package/dist/repo_reader.js +121 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ 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");
|
|
@@ -115,7 +116,8 @@ async function main() {
|
|
|
115
116
|
try {
|
|
116
117
|
const repoData = await fetchRepo(owner, repo);
|
|
117
118
|
const readme = await fetchReadme(owner, repo);
|
|
118
|
-
const
|
|
119
|
+
const readResult = await readRepoSignalFiles(owner, repo);
|
|
120
|
+
const prompt = buildPrompt(repoData.full_name, repoData.description, readme, detailed, quick, readResult.treeText, readResult.filesText);
|
|
119
121
|
console.log("Generating explanation...");
|
|
120
122
|
const output = await generateExplanation(prompt);
|
|
121
123
|
if (quick) {
|
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, quick?: 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,4 @@
|
|
|
1
|
-
export function buildPrompt(repoName, description, readme, detailed = false, quick = false) {
|
|
1
|
+
export function buildPrompt(repoName, description, readme, detailed = false, quick = false, treeText = null, filesText = null) {
|
|
2
2
|
if (quick) {
|
|
3
3
|
const readmeSnippet = (readme || "").slice(0, 2000);
|
|
4
4
|
return `
|
|
@@ -35,6 +35,12 @@ Repository:
|
|
|
35
35
|
README content:
|
|
36
36
|
${readme || "No README provided"}
|
|
37
37
|
|
|
38
|
+
Repository structure:
|
|
39
|
+
${treeText || "No tree rovided"}
|
|
40
|
+
|
|
41
|
+
Key files (snippets):
|
|
42
|
+
${filesText || "No code files provided"}
|
|
43
|
+
|
|
38
44
|
Instructions:
|
|
39
45
|
- Explain what this project does.
|
|
40
46
|
- Say who it is for.
|
|
@@ -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
|
+
}
|