lkatzey 1 месяц назад
Родитель
Сommit
4a4d7f2dd6

+ 33 - 12
packages/collect/src/collectors/github-daily.ts

@@ -47,7 +47,7 @@ class GitHubDailyCollector implements DataSource {
         if (!res.ok) continue;
         const raw = await res.json() as any;
         const data = raw.data ?? raw;
-        details.push(formatRepo(data));
+        details.push(formatRepo(owner, name, data));
       } catch {
         // Skip failed repo lookups
       }
@@ -72,21 +72,42 @@ function formatTrendingSummary(repos: any[]): string {
   return lines.join("\n");
 }
 
-function formatRepo(data: any): string {
-  const name = data.fullName || data.full_name || "unknown";
-  const lines: string[] = [`## ${name}`];
+function formatRepo(trendingOwner: string, trendingName: string, data: any): string {
+  // The repo detail API returns the REAL owner (e.g. facebook/react was
+  // transferred to the react org and now reports fullName "react/react").
+  // Prefer the detail's fullName when available, otherwise fall back to the
+  // trending-side owner/name used to query it.
+  const fullName = data.fullName || `${trendingOwner}/${trendingName}`;
+  const [detailOwner, detailName] = fullName.split("/");
+
+  const lines: string[] = [`## ${fullName}`];
 
   if (data.description) lines.push(`\n${data.description}`);
-  const meta: string[] = [];
-  if (data.language) meta.push(`Language: ${data.language}`);
-  if (data.stars != null) meta.push(`Stars: ${formatCount(data.stars)}`);
-  else if (data.stargazers_count != null) meta.push(`Stars: ${formatCount(data.stargazers_count)}`);
-  if (data.forks != null) meta.push(`Forks: ${formatCount(data.forks)}`);
-  if (data.topics?.length) meta.push(`Topics: ${data.topics.join(", ")}`);
-  if (meta.length) lines.push(meta.map((m) => `- ${m}`).join("\n"));
+
+  // Structured metadata block — the LLM is instructed to copy this verbatim
+  // into scene.github.repo. Do NOT edit field names or values here.
+  const meta = {
+    owner: detailOwner || trendingOwner,
+    name: detailName || trendingName,
+    fullName,
+    language: data.language ?? "",
+    languageColor: data.languageColor ?? "",
+    stars: data.stars,
+    forks: data.forks,
+    license: data.license ?? "",
+  };
+  lines.push(`<!-- repo-meta: ${JSON.stringify(meta)} -->`);
+
+  // Image refs — the LLM copies these into scene.images[] verbatim.
+  // socialPreview is resolved by the assets stage (calls crawler, decodes
+  // base64 PNG). starHistory is fetched as a normal URL image.
+  const imageRefs = {
+    socialPreview: `${detailOwner || trendingOwner}/${detailName || trendingName}`,
+    starHistory: `https://api.star-history.com/svg?repos=${fullName}&type=Date`,
+  };
+  lines.push(`<!-- repo-images: ${JSON.stringify(imageRefs)} -->`);
 
   if (data.readme) {
-    // Truncate readme to avoid excessive content
     const readme = data.readme.length > 2000 ? data.readme.slice(0, 2000) + "\n..." : data.readme;
     lines.push(`\n### README\n${readme}`);
   }

+ 49 - 15
packages/collect/src/collectors/github-repo.ts

@@ -33,24 +33,58 @@ class GitHubRepoCollector implements DataSource {
     const raw = (await response.json()) as Record<string, any>;
     const data = (raw.data ?? raw) as Record<string, any>;
 
-    const name = data.fullName || data.full_name || "unknown";
-    const lines: string[] = [`# ${name}\n`];
-    if (data.description) lines.push(`${data.description}\n`);
-    if (data.language) lines.push(`- Language: ${data.language}`);
-    if (data.stars != null) lines.push(`- Stars: ${formatCount(data.stars)}`);
-    if (data.forks != null) lines.push(`- Forks: ${formatCount(data.forks)}`);
-    if (data.openIssues != null) lines.push(`- Open Issues: ${formatCount(data.openIssues)}`);
-    if (data.topics?.length) lines.push(`- Topics: ${data.topics.join(", ")}`);
+    return { type: "text", content: formatRepoDetail(owner, repo, data) };
+  }
+}
+
+function formatRepoDetail(queryOwner: string, queryRepo: string, data: any): string {
+  // The API returns the REAL owner (transferred repos report their current
+  // owner). Prefer data.fullName; fall back to the query params.
+  const fullName = data.fullName || `${queryOwner}/${queryRepo}`;
+  const [detailOwner, detailName] = fullName.split("/");
+
+  const lines: string[] = [`# ${fullName}\n`];
+  if (data.description) lines.push(`${data.description}\n`);
+
+  // Structured metadata block — LLM copies verbatim into scene.github.repo.
+  const meta = {
+    owner: detailOwner || queryOwner,
+    name: detailName || queryRepo,
+    fullName,
+    language: data.language ?? "",
+    languageColor: data.languageColor ?? "",
+    stars: data.stars,
+    forks: data.forks,
+    license: data.license ?? "",
+  };
+  lines.push(`<!-- repo-meta: ${JSON.stringify(meta)} -->`);
+
+  // Image refs — LLM copies verbatim into scene.images[].
+  const imageRefs = {
+    socialPreview: `${detailOwner || queryOwner}/${detailName || queryRepo}`,
+    starHistory: `https://api.star-history.com/svg?repos=${fullName}&type=Date`,
+  };
+  lines.push(`<!-- repo-images: ${JSON.stringify(imageRefs)} -->`);
+
+  const meta2: string[] = [];
+  if (data.language) meta2.push(`Language: ${data.language}`);
+  if (data.stars != null) meta2.push(`Stars: ${formatCount(data.stars)}`);
+  if (data.forks != null) meta2.push(`Forks: ${formatCount(data.forks)}`);
+  if (data.openIssues != null) meta2.push(`Open Issues: ${formatCount(data.openIssues)}`);
+  if (data.topics?.length) meta2.push(`Topics: ${data.topics.join(", ")}`);
+  if (meta2.length) {
     lines.push("");
-    if (data.readme) {
-      const readme = data.readme.length > 3000
-        ? data.readme.slice(0, 3000) + "\n..."
-        : data.readme;
-      lines.push("## README\n", readme);
-    }
+    lines.push(meta2.map((m) => `- ${m}`).join("\n"));
+  }
 
-    return { type: "text", content: lines.join("\n") };
+  if (data.readme) {
+    const readme = data.readme.length > 3000
+      ? data.readme.slice(0, 3000) + "\n..."
+      : data.readme;
+    lines.push("\n## README\n", readme);
   }
+
+  return lines.join("\n");
 }
 
 registerCollector("github-repo", (config) => new GitHubRepoCollector(config));

+ 37 - 6
packages/collect/src/collectors/github-trending.ts

@@ -26,13 +26,44 @@ class GitHubTrendingCollector implements DataSource {
 
     const lines: string[] = ["# GitHub Trending\n"];
     for (const repo of items) {
-      const name = repo.fullName || `${repo.author}/${repo.name}`;
-      lines.push(`## ${name}`);
+      const owner = repo.author ?? "";
+      const name = repo.name ?? "";
+      const fullName = repo.fullName || `${owner}/${name}`;
+      if (!owner || !name) continue;
+
+      lines.push(`## ${fullName}`);
       if (repo.description) lines.push(`${repo.description}`);
-      if (repo.language) lines.push(`- Language: ${repo.language}`);
-      if (repo.stars != null) lines.push(`- Stars: ${formatCount(repo.stars)}`);
-      if (repo.currentPeriodStars != null) lines.push(`- Today: +${formatCount(repo.currentPeriodStars)}`);
-      if (repo.url) lines.push(`- URL: ${repo.url}`);
+
+      // Structured metadata block — LLM copies verbatim into scene.github.repo.
+      // The trending endpoint does not expose license; downstream template
+      // tolerates an empty license (no license tag rendered).
+      const meta = {
+        owner,
+        name,
+        fullName,
+        language: repo.language ?? "",
+        languageColor: repo.languageColor ?? "",
+        stars: repo.stars,
+        forks: repo.forks,
+        license: "",
+      };
+      lines.push(`<!-- repo-meta: ${JSON.stringify(meta)} -->`);
+
+      // Image refs — LLM copies verbatim into scene.images[].
+      const imageRefs = {
+        socialPreview: `${owner}/${name}`,
+        starHistory: `https://api.star-history.com/svg?repos=${fullName}&type=Date`,
+      };
+      lines.push(`<!-- repo-images: ${JSON.stringify(imageRefs)} -->`);
+
+      const meta2: string[] = [];
+      if (repo.language) meta2.push(`Language: ${repo.language}`);
+      if (repo.stars != null) meta2.push(`Stars: ${formatCount(repo.stars)}`);
+      if (repo.currentPeriodStars != null) meta2.push(`Today: +${formatCount(repo.currentPeriodStars)}`);
+      if (repo.url) meta2.push(`URL: ${repo.url}`);
+      if (meta2.length) {
+        lines.push(meta2.map((m) => `- ${m}`).join("\n"));
+      }
       lines.push("");
     }