feat: Multi-Step README Generation Pipeline - Fixes Token Limit Issues (#101)#116
feat: Multi-Step README Generation Pipeline - Fixes Token Limit Issues (#101)#116
Conversation
…mit issues - Add comprehensive multi-step README generator with section-by-section approach - Solve token limit problems from issue #101 by generating sections individually - Implement smart repository analysis with token-conscious filtering - Add dynamic section planning based on project type detection - Include retry logic and fallback mechanisms for failed sections - Support continuation for truncated content - Add enhanced API route with detailed statistics and error handling - Backup original implementation for safe migration - Include comprehensive integration guide and documentation Closes #101
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughReplaces the single-pass README generator with a multi-step pipeline: repository analysis, section planning, per-section generation (with retries/continuation), and assembly. Adds Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant API as API Route
participant Generator as MultiStepReadmeGenerator
participant Analyzer as RepositoryAnalyzer
participant Planner as SectionPlanner
participant Model as Gemini/Model
participant GitHub as GitHub API
Client->>API: POST /api/generate (githubUrl)
API->>API: Validate URL, extract owner/repo
API->>Generator: generateReadme(githubUrl)
Generator->>Analyzer: analyzeRepository(owner, repo)
Analyzer->>GitHub: Fetch metadata & contents (parallel)
Analyzer-->>Generator: {metadata, structure}
Generator->>Planner: planSections(metadata, structure)
Planner-->>Generator: ReadmeSection[]
loop For each section (batched by concurrency)
Generator->>Model: generateContent(section_prompt)
Model-->>Generator: Section markdown (or error/truncated)
Generator->>Generator: retry/continue as configured
end
Generator->>Generator: assemble README, collect stats
Generator-->>API: {readme, stats, metadata, warnings}
API-->>Client: HTTP 200 {success, markdown, stats, metadata}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (1)
src/lib/multi-step-readme-generator.ts (1)
96-131: Consider reusing the existinggetRepoContentshelper fromsrc/lib/octokit.ts.
RepositoryAnalyzer.getRepositoryContentsduplicates logic from the canonicalgetRepoContentshelper with different error handling and instance management. This creates two independent code paths that may diverge over time. Consider composing or extending the existing helper to reduce maintenance burden.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 96 - 131, The analyzeRepository flow in RepositoryAnalyzer duplicates repository content fetching by calling getRepositoryContents instead of reusing the existing getRepoContents helper; refactor RepositoryAnalyzer to call or wrap the canonical getRepoContents helper (from octokit.ts) so there's a single source of truth for content fetching and error handling — update RepositoryAnalyzer constructor/instance usage if needed to share the same Octokit instance or adapt getRepoContents to accept an Octokit instance, replace calls to getRepositoryContents with getRepoContents, and remove or delegate duplicate logic in the getRepositoryContents implementation to avoid divergent code paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/multi-step-integration-guide.md`:
- Around line 392-398: The example wrongly mutates a non-public property
(generator.assembler) — instead, either call the public streaming API (use
generator.generateSectionsInBatches or generator.generateSectionsInBatchesStream
if available) to stream sections, or add a small public hook on the Generator
class (e.g., generator.withAssembler or generator.overrideAssembler) that
accepts a custom assembler implementation and wires it into the generator
internals; update the example to use generator.generateSectionsInBatches (or the
new public withAssembler/overrideAssembler) and remove direct access to
generator.assembler.
- Around line 127-132: The snippet references a non-existent method
assembler.generateCompleteReadme; remove or replace this example with actual API
calls. Locate the example using the symbols assembler, generateCompleteReadme,
metadata, structure, and customSections and either delete the block or update it
to call the real function(s) in the codebase (e.g., the actual Assembler/README
builder method names) and pass equivalent parameters (metadata, structure,
customSections) matching the real method signature so the docs reflect current
API usage.
- Around line 63-65: The frontend reads the wrong response field—change usages
of result.readme to result.markdown so the component matches the API's response
shape; update the block where setReadme(result.readme) and
setGenerationStats(result.stats) are called (e.g., the code invoking setReadme
and setGenerationStats) to call setReadme(result.markdown) instead, leaving
setGenerationStats(result.stats) as-is to keep compatibility with the API.
- Around line 26-32: The docs import a non-existent symbol
handleReadmeGeneration from '@/lib/multi-step-readme-generator'; locate the
actual export(s) in that module and either export handleReadmeGeneration from
'@/lib/multi-step-readme-generator' or update the example to import and call the
real exported function (e.g., the module's actual handler like generateReadme or
handleReadmeRequest) so the example matches the module API; ensure the POST
example calls the exact exported function name from that module.
In `@src/app/api/generate/route.ts`:
- Line 20: The handler currently destructures "language" from body but never
uses it; either remove the unused "language" binding or thread it into the
README creation call—modify the destructure in the request handler (where "const
{ url: githubUrl, language = \"English\" } = body;") and, if keeping
localization, pass the "language" value into generateReadme(githubUrl, ...) (or
the appropriate generateReadme signature) so generateReadme receives and
respects the language; otherwise delete the "language" variable and default to
the existing behavior to eliminate the unused variable.
- Around line 62-73: Check and validate process.env.GEMINI_API_KEY before
passing it into MultiStepReadmeGenerator: replace the non-null assertion usage
with an explicit guard that throws a clear, descriptive error if GEMINI_API_KEY
is missing (e.g., "Missing GEMINI_API_KEY: set your API key in environment"),
then pass the validated key into the MultiStepReadmeGenerator constructor;
reference the symbol MultiStepReadmeGenerator and the environment variable
GEMINI_API_KEY when implementing this check.
- Around line 79-90: The code instantiates
MultiStepReadmeGenerator.generateReadme but that class is missing; add a
MultiStepReadmeGenerator class in src/lib/multi-step-readme-generator.ts that
composes the existing RepositoryAnalyzer and SectionPlanner (and any other
exporter/renderer used by the route) and exposes an async
generateReadme(githubUrl: string) method which: (1) uses RepositoryAnalyzer
(e.g., analyzeRepository or similar) to fetch and analyze the repo, (2) passes
analysis to SectionPlanner (e.g., planSections) to produce planned sections, (3)
renders or assembles those sections into the final README text, and (4) returns
the shape expected by the route (an object with success, stats:
{sectionsGenerated, sectionsTotal, tokensUsed}, and errors array). Ensure the
constructor accepts any dependencies required by the route and that the method
surfaces errors into the returned errors array rather than throwing.
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 14-15: Remove the unused import symbol GoogleGenerativeAI from the
top of the file to satisfy ESLint; locate the import statement that reads
"import { GoogleGenerativeAI } ..." and delete only the GoogleGenerativeAI
portion (or the entire import line if nothing else is imported there), leaving
the Octokit import intact.
- Around line 786-795: The "testing" section currently declares a dependency on
"development" which may not exist for private repos or repos with zero forks;
update the section object created in generateSections (the block that pushes id:
"testing") to either remove the dependencies field or replace dependencies:
["development"] with dependencies: ["usage"] so the dependency always resolves
(optimizeSectionOrder will then handle ordering); make the change where the
sections.push({ id: "testing", ... }) is defined to ensure testing stands alone
or depends on "usage" instead of "development".
- Around line 269-276: The current broad dotfile check
(lowerFilename.startsWith(".")) incorrectly includes irrelevant files; replace
it with an explicit allowlist check: remove the startsWith(".") branch in the
predicate and add an allowlist array (e.g., dotfileAllowlist and
dotdirAllowlist) that you check against lowerFilename using exact equality for
files and startsWith for allowed directories; update the final return to OR
against relevantFiles.some(...), relevantExtensions.some(...), or
dotfileAllowlist.includes(lowerFilename) or dotdirAllowlist.some(dir =>
lowerFilename.startsWith(dir)); reference the existing lowerFilename,
relevantFiles, and relevantExtensions symbols when making the change.
- Around line 314-348: The analyzeStructure function's contents parameter is
typed as any[]; change it to use the proper RepoContentItem[] type (update the
signature private analyzeStructure(contents: RepoContentItem[]):
RepositoryStructure) and ensure RepoContentItem is imported or available in the
same module; update any downstream uses in detectTechStack/detectProjectType
calls if their signatures expect the stronger type.
- Around line 162-211: Change the explicit any[] return type in
getRepositoryContents to the proper Octokit response type: replace
Promise<any[]> with
Promise<RestEndpointMethodTypes['repos']['getContent']['response']['data'][]>
and add import type { RestEndpointMethodTypes } from "@octokit/rest"; this
matches the union returned by this.octokit.rest.repos.getContent (you already
wrap single-item responses as an array), so update the function signature and
ensure any local variables typed as data/subContents use that
RestEndpointMethodTypes['repos']['getContent']['response']['data'] type to
satisfy the linter.
---
Nitpick comments:
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 96-131: The analyzeRepository flow in RepositoryAnalyzer
duplicates repository content fetching by calling getRepositoryContents instead
of reusing the existing getRepoContents helper; refactor RepositoryAnalyzer to
call or wrap the canonical getRepoContents helper (from octokit.ts) so there's a
single source of truth for content fetching and error handling — update
RepositoryAnalyzer constructor/instance usage if needed to share the same
Octokit instance or adapt getRepoContents to accept an Octokit instance, replace
calls to getRepositoryContents with getRepoContents, and remove or delegate
duplicate logic in the getRepositoryContents implementation to avoid divergent
code paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5b9b68df-222e-4d35-95f3-493bfb24faf2
📒 Files selected for processing (4)
docs/multi-step-integration-guide.mdsrc/app/api/generate/route.tssrc/app/api/generate/route.ts.backupsrc/lib/multi-step-readme-generator.ts
| ```typescript | ||
| import { handleReadmeGeneration } from '@/lib/multi-step-readme-generator'; | ||
|
|
||
| export async function POST(request: Request) { | ||
| return handleReadmeGeneration(request); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
handleReadmeGeneration function doesn't exist in the implementation.
The quick integration example imports handleReadmeGeneration from @/lib/multi-step-readme-generator, but this function is not exported from the module. Update the documentation to match the actual API.
Proposed fix
-import { handleReadmeGeneration } from '@/lib/multi-step-readme-generator';
+import { MultiStepReadmeGenerator } from '@/lib/multi-step-readme-generator';
export async function POST(request: Request) {
- return handleReadmeGeneration(request);
+ const body = await request.json();
+ const generator = new MultiStepReadmeGenerator(
+ process.env.GEMINI_API_KEY!,
+ process.env.GITHUB_TOKEN
+ );
+ const result = await generator.generateReadme(body.url);
+ return Response.json(result);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ```typescript | |
| import { handleReadmeGeneration } from '@/lib/multi-step-readme-generator'; | |
| export async function POST(request: Request) { | |
| return handleReadmeGeneration(request); | |
| } | |
| ``` |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/multi-step-integration-guide.md` around lines 26 - 32, The docs import a
non-existent symbol handleReadmeGeneration from
'@/lib/multi-step-readme-generator'; locate the actual export(s) in that module
and either export handleReadmeGeneration from
'@/lib/multi-step-readme-generator' or update the example to import and call the
real exported function (e.g., the module's actual handler like generateReadme or
handleReadmeRequest) so the example matches the module API; ensure the POST
example calls the exact exported function name from that module.
| if (result.success) { | ||
| setReadme(result.readme); | ||
| setGenerationStats(result.stats); |
There was a problem hiding this comment.
Response field mismatch: result.readme vs result.markdown.
The frontend example accesses result.readme, but the API route returns the content under the markdown key for backward compatibility (see route.ts line 107).
Proposed fix
if (result.success) {
- setReadme(result.readme);
+ setReadme(result.markdown);
setGenerationStats(result.stats);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (result.success) { | |
| setReadme(result.readme); | |
| setGenerationStats(result.stats); | |
| if (result.success) { | |
| setReadme(result.markdown); | |
| setGenerationStats(result.stats); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/multi-step-integration-guide.md` around lines 63 - 65, The frontend
reads the wrong response field—change usages of result.readme to result.markdown
so the component matches the API's response shape; update the block where
setReadme(result.readme) and setGenerationStats(result.stats) are called (e.g.,
the code invoking setReadme and setGenerationStats) to call
setReadme(result.markdown) instead, leaving setGenerationStats(result.stats)
as-is to keep compatibility with the API.
| const result = await assembler.generateCompleteReadme( | ||
| metadata, | ||
| structure, | ||
| customSections | ||
| ); | ||
| ``` |
There was a problem hiding this comment.
assembler.generateCompleteReadme doesn't exist.
This example references an assembler object and generateCompleteReadme method that aren't part of the current implementation. Remove or update this section to reflect actual API usage.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/multi-step-integration-guide.md` around lines 127 - 132, The snippet
references a non-existent method assembler.generateCompleteReadme; remove or
replace this example with actual API calls. Locate the example using the symbols
assembler, generateCompleteReadme, metadata, structure, and customSections and
either delete the block or update it to call the real function(s) in the
codebase (e.g., the actual Assembler/README builder method names) and pass
equivalent parameters (metadata, structure, customSections) matching the real
method signature so the docs reflect current API usage.
| // Override assembler to stream results | ||
| const originalAssembler = generator.assembler; | ||
| generator.assembler.generateSectionsInBatches = async (...args) => { | ||
| // Stream each section as it's completed | ||
| // Implementation details... | ||
| }; | ||
|
|
There was a problem hiding this comment.
Streaming example accesses internal generator.assembler which isn't exposed.
The streaming example attempts to override generator.assembler.generateSectionsInBatches, but assembler is not a public property. This code won't work as written.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/multi-step-integration-guide.md` around lines 392 - 398, The example
wrongly mutates a non-public property (generator.assembler) — instead, either
call the public streaming API (use generator.generateSectionsInBatches or
generator.generateSectionsInBatchesStream if available) to stream sections, or
add a small public hook on the Generator class (e.g., generator.withAssembler or
generator.overrideAssembler) that accepts a custom assembler implementation and
wires it into the generator internals; update the example to use
generator.generateSectionsInBatches (or the new public
withAssembler/overrideAssembler) and remove direct access to
generator.assembler.
| import { GoogleGenerativeAI } from "@google/generative-ai"; | ||
| import { Octokit } from "@octokit/rest"; |
There was a problem hiding this comment.
Remove unused import GoogleGenerativeAI.
The import is flagged by ESLint as unused. This appears to be scaffolding for future implementation—remove it until it's actually needed to satisfy the linter.
Proposed fix
-import { GoogleGenerativeAI } from "@google/generative-ai";
import { Octokit } from "@octokit/rest";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { GoogleGenerativeAI } from "@google/generative-ai"; | |
| import { Octokit } from "@octokit/rest"; | |
| import { Octokit } from "@octokit/rest"; |
🧰 Tools
🪛 GitHub Actions: autofix.ci
[warning] 14-14: ESLint warning: 'GoogleGenerativeAI' is defined but never used. (@typescript-eslint/no-unused-vars)
🪛 GitHub Actions: CI
[warning] 14-14: ESLint (@typescript-eslint/no-unused-vars): 'GoogleGenerativeAI' is defined but never used.
🪛 GitHub Check: autofix
[warning] 14-14:
'GoogleGenerativeAI' is defined but never used
🪛 GitHub Check: build-and-lint
[warning] 14-14:
'GoogleGenerativeAI' is defined but never used
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 14 - 15, Remove the
unused import symbol GoogleGenerativeAI from the top of the file to satisfy
ESLint; locate the import statement that reads "import { GoogleGenerativeAI }
..." and delete only the GoogleGenerativeAI portion (or the entire import line
if nothing else is imported there), leaving the Octokit import intact.
| private async getRepositoryContents( | ||
| owner: string, | ||
| repo: string, | ||
| path = "", | ||
| maxDepth = 2, | ||
| ): Promise<any[]> { | ||
| try { | ||
| const { data } = await this.octokit.rest.repos.getContent({ | ||
| owner, | ||
| repo, | ||
| path, | ||
| }); | ||
|
|
||
| if (!Array.isArray(data)) { | ||
| return [data]; | ||
| } | ||
|
|
||
| // Filter out unimportant files and limit results | ||
| const filteredContents = data | ||
| .filter((item) => this.isRelevantFile(item.name)) | ||
| .slice(0, 100); // Prevent token overflow | ||
|
|
||
| if (maxDepth > 0) { | ||
| // Recursively get important subdirectories | ||
| const subdirectories = filteredContents.filter( | ||
| (item) => item.type === "dir" && this.isImportantDirectory(item.name), | ||
| ); | ||
|
|
||
| for (const dir of subdirectories.slice(0, 5)) { | ||
| // Limit subdirectory exploration | ||
| try { | ||
| const subContents = await this.getRepositoryContents( | ||
| owner, | ||
| repo, | ||
| dir.path, | ||
| maxDepth - 1, | ||
| ); | ||
| filteredContents.push(...subContents); | ||
| } catch (error) { | ||
| // Continue if subdirectory is inaccessible | ||
| console.warn(`Could not access directory ${dir.path}: ${error}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return filteredContents; | ||
| } catch (error) { | ||
| throw new Error(`Failed to fetch repository contents: ${error}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
Replace any[] with proper Octokit types to satisfy the linter.
The pipeline fails due to @typescript-eslint/no-explicit-any. Use the Octokit response type for repository contents.
Proposed fix
+// Add type for repository content items
+type RepoContentItem = {
+ name: string;
+ path: string;
+ type: "file" | "dir" | "submodule" | "symlink";
+};
private async getRepositoryContents(
owner: string,
repo: string,
path = "",
maxDepth = 2,
- ): Promise<any[]> {
+ ): Promise<RepoContentItem[]> {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private async getRepositoryContents( | |
| owner: string, | |
| repo: string, | |
| path = "", | |
| maxDepth = 2, | |
| ): Promise<any[]> { | |
| try { | |
| const { data } = await this.octokit.rest.repos.getContent({ | |
| owner, | |
| repo, | |
| path, | |
| }); | |
| if (!Array.isArray(data)) { | |
| return [data]; | |
| } | |
| // Filter out unimportant files and limit results | |
| const filteredContents = data | |
| .filter((item) => this.isRelevantFile(item.name)) | |
| .slice(0, 100); // Prevent token overflow | |
| if (maxDepth > 0) { | |
| // Recursively get important subdirectories | |
| const subdirectories = filteredContents.filter( | |
| (item) => item.type === "dir" && this.isImportantDirectory(item.name), | |
| ); | |
| for (const dir of subdirectories.slice(0, 5)) { | |
| // Limit subdirectory exploration | |
| try { | |
| const subContents = await this.getRepositoryContents( | |
| owner, | |
| repo, | |
| dir.path, | |
| maxDepth - 1, | |
| ); | |
| filteredContents.push(...subContents); | |
| } catch (error) { | |
| // Continue if subdirectory is inaccessible | |
| console.warn(`Could not access directory ${dir.path}: ${error}`); | |
| } | |
| } | |
| } | |
| return filteredContents; | |
| } catch (error) { | |
| throw new Error(`Failed to fetch repository contents: ${error}`); | |
| } | |
| } | |
| // Add type for repository content items | |
| type RepoContentItem = { | |
| name: string; | |
| path: string; | |
| type: "file" | "dir" | "submodule" | "symlink"; | |
| }; | |
| private async getRepositoryContents( | |
| owner: string, | |
| repo: string, | |
| path = "", | |
| maxDepth = 2, | |
| ): Promise<RepoContentItem[]> { | |
| try { | |
| const { data } = await this.octokit.rest.repos.getContent({ | |
| owner, | |
| repo, | |
| path, | |
| }); | |
| if (!Array.isArray(data)) { | |
| return [data]; | |
| } | |
| // Filter out unimportant files and limit results | |
| const filteredContents = data | |
| .filter((item) => this.isRelevantFile(item.name)) | |
| .slice(0, 100); // Prevent token overflow | |
| if (maxDepth > 0) { | |
| // Recursively get important subdirectories | |
| const subdirectories = filteredContents.filter( | |
| (item) => item.type === "dir" && this.isImportantDirectory(item.name), | |
| ); | |
| for (const dir of subdirectories.slice(0, 5)) { | |
| // Limit subdirectory exploration | |
| try { | |
| const subContents = await this.getRepositoryContents( | |
| owner, | |
| repo, | |
| dir.path, | |
| maxDepth - 1, | |
| ); | |
| filteredContents.push(...subContents); | |
| } catch (error) { | |
| // Continue if subdirectory is inaccessible | |
| console.warn(`Could not access directory ${dir.path}: ${error}`); | |
| } | |
| } | |
| } | |
| return filteredContents; | |
| } catch (error) { | |
| throw new Error(`Failed to fetch repository contents: ${error}`); | |
| } | |
| } |
🧰 Tools
🪛 GitHub Actions: autofix.ci
[error] 167-167: ESLint error: Unexpected any. Specify a different type. (@typescript-eslint/no-explicit-any)
🪛 GitHub Actions: CI
[error] 167-167: ESLint (@typescript-eslint/no-explicit-any): Unexpected any. Specify a different type.
🪛 GitHub Check: autofix
[failure] 167-167:
Unexpected any. Specify a different type
🪛 GitHub Check: build-and-lint
[failure] 167-167:
Unexpected any. Specify a different type
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 162 - 211, Change the
explicit any[] return type in getRepositoryContents to the proper Octokit
response type: replace Promise<any[]> with
Promise<RestEndpointMethodTypes['repos']['getContent']['response']['data'][]>
and add import type { RestEndpointMethodTypes } from "@octokit/rest"; this
matches the union returned by this.octokit.rest.repos.getContent (you already
wrap single-item responses as an array), so update the function signature and
ensure any local variables typed as data/subContents use that
RestEndpointMethodTypes['repos']['getContent']['response']['data'] type to
satisfy the linter.
| return ( | ||
| relevantFiles.some((file) => | ||
| lowerFilename.includes(file.toLowerCase()), | ||
| ) || | ||
| relevantExtensions.some((ext) => lowerFilename.endsWith(ext)) || | ||
| lowerFilename.startsWith(".") | ||
| ); | ||
| } |
There was a problem hiding this comment.
Overly broad dotfile matching includes unwanted files.
lowerFilename.startsWith(".") matches .git, .DS_Store, .vscode, and other files that aren't useful for README generation. Consider an explicit allowlist instead.
Proposed fix
+ const relevantDotfiles = [
+ ".env.example",
+ ".gitignore",
+ ".dockerignore",
+ ".editorconfig",
+ ".nvmrc",
+ ];
+
return (
relevantFiles.some((file) =>
lowerFilename.includes(file.toLowerCase()),
) ||
- relevantExtensions.some((ext) => lowerFilename.endsWith(ext)) ||
- lowerFilename.startsWith(".")
+ relevantExtensions.some((ext) => lowerFilename.endsWith(ext)) ||
+ relevantDotfiles.some((df) => lowerFilename === df)
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ( | |
| relevantFiles.some((file) => | |
| lowerFilename.includes(file.toLowerCase()), | |
| ) || | |
| relevantExtensions.some((ext) => lowerFilename.endsWith(ext)) || | |
| lowerFilename.startsWith(".") | |
| ); | |
| } | |
| const relevantDotfiles = [ | |
| ".env.example", | |
| ".gitignore", | |
| ".dockerignore", | |
| ".editorconfig", | |
| ".nvmrc", | |
| ]; | |
| return ( | |
| relevantFiles.some((file) => | |
| lowerFilename.includes(file.toLowerCase()), | |
| ) || | |
| relevantExtensions.some((ext) => lowerFilename.endsWith(ext)) || | |
| relevantDotfiles.some((df) => lowerFilename === df) | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 269 - 276, The current
broad dotfile check (lowerFilename.startsWith(".")) incorrectly includes
irrelevant files; replace it with an explicit allowlist check: remove the
startsWith(".") branch in the predicate and add an allowlist array (e.g.,
dotfileAllowlist and dotdirAllowlist) that you check against lowerFilename using
exact equality for files and startsWith for allowed directories; update the
final return to OR against relevantFiles.some(...),
relevantExtensions.some(...), or dotfileAllowlist.includes(lowerFilename) or
dotdirAllowlist.some(dir => lowerFilename.startsWith(dir)); reference the
existing lowerFilename, relevantFiles, and relevantExtensions symbols when
making the change.
| private analyzeStructure(contents: any[]): RepositoryStructure { | ||
| const files = contents | ||
| .map((item) => item.name || item.path) | ||
| .filter(Boolean); | ||
|
|
||
| const rootFiles = files.filter((file) => !file.includes("/")); | ||
| const directories = [ | ||
| ...new Set( | ||
| files | ||
| .filter((file) => file.includes("/")) | ||
| .map((file) => file.split("/")[0]), | ||
| ), | ||
| ]; | ||
|
|
||
| // Categorize files | ||
| const packageFiles = files.filter((file) => this.isPackageFile(file)); | ||
| const configFiles = files.filter((file) => this.isConfigFile(file)); | ||
| const documentationFiles = files.filter((file) => | ||
| this.isDocumentationFile(file), | ||
| ); | ||
|
|
||
| // Detect tech stack and project type | ||
| const techStack = this.detectTechStack(files); | ||
| const projectType = this.detectProjectType(files, directories, techStack); | ||
|
|
||
| return { | ||
| rootFiles, | ||
| directories, | ||
| packageFiles, | ||
| configFiles, | ||
| documentationFiles, | ||
| techStack, | ||
| projectType, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Replace any[] parameter with proper type.
Same linter issue as above—use the RepoContentItem type for the contents parameter.
Proposed fix
- private analyzeStructure(contents: any[]): RepositoryStructure {
+ private analyzeStructure(contents: RepoContentItem[]): RepositoryStructure {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private analyzeStructure(contents: any[]): RepositoryStructure { | |
| const files = contents | |
| .map((item) => item.name || item.path) | |
| .filter(Boolean); | |
| const rootFiles = files.filter((file) => !file.includes("/")); | |
| const directories = [ | |
| ...new Set( | |
| files | |
| .filter((file) => file.includes("/")) | |
| .map((file) => file.split("/")[0]), | |
| ), | |
| ]; | |
| // Categorize files | |
| const packageFiles = files.filter((file) => this.isPackageFile(file)); | |
| const configFiles = files.filter((file) => this.isConfigFile(file)); | |
| const documentationFiles = files.filter((file) => | |
| this.isDocumentationFile(file), | |
| ); | |
| // Detect tech stack and project type | |
| const techStack = this.detectTechStack(files); | |
| const projectType = this.detectProjectType(files, directories, techStack); | |
| return { | |
| rootFiles, | |
| directories, | |
| packageFiles, | |
| configFiles, | |
| documentationFiles, | |
| techStack, | |
| projectType, | |
| }; | |
| } | |
| private analyzeStructure(contents: RepoContentItem[]): RepositoryStructure { | |
| const files = contents | |
| .map((item) => item.name || item.path) | |
| .filter(Boolean); | |
| const rootFiles = files.filter((file) => !file.includes("/")); | |
| const directories = [ | |
| ...new Set( | |
| files | |
| .filter((file) => file.includes("/")) | |
| .map((file) => file.split("/")[0]), | |
| ), | |
| ]; | |
| // Categorize files | |
| const packageFiles = files.filter((file) => this.isPackageFile(file)); | |
| const configFiles = files.filter((file) => this.isConfigFile(file)); | |
| const documentationFiles = files.filter((file) => | |
| this.isDocumentationFile(file), | |
| ); | |
| // Detect tech stack and project type | |
| const techStack = this.detectTechStack(files); | |
| const projectType = this.detectProjectType(files, directories, techStack); | |
| return { | |
| rootFiles, | |
| directories, | |
| packageFiles, | |
| configFiles, | |
| documentationFiles, | |
| techStack, | |
| projectType, | |
| }; | |
| } |
🧰 Tools
🪛 GitHub Check: autofix
[failure] 314-314:
Unexpected any. Specify a different type
🪛 GitHub Check: build-and-lint
[failure] 314-314:
Unexpected any. Specify a different type
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 314 - 348, The
analyzeStructure function's contents parameter is typed as any[]; change it to
use the proper RepoContentItem[] type (update the signature private
analyzeStructure(contents: RepoContentItem[]): RepositoryStructure) and ensure
RepoContentItem is imported or available in the same module; update any
downstream uses in detectTechStack/detectProjectType calls if their signatures
expect the stronger type.
| ) { | ||
| sections.push({ | ||
| id: "testing", | ||
| title: "Testing", | ||
| priority: "low", | ||
| order: 8.5, | ||
| estimatedTokens: 300, | ||
| dependencies: ["development"], | ||
| }); | ||
| } |
There was a problem hiding this comment.
Testing section depends on "development" which may not exist.
The testing section declares a dependency on development (line 793), but development is only added when !metadata.isPrivate && metadata.forks > 0 (line 727). For private repos or repos with zero forks, this dependency won't resolve. While optimizeSectionOrder silently skips missing dependencies, consider either:
- Making
testingdepend onusageinstead, or - Removing the dependency since testing can stand alone.
Proposed fix
sections.push({
id: "testing",
title: "Testing",
priority: "low",
order: 8.5,
estimatedTokens: 300,
- dependencies: ["development"],
+ dependencies: ["usage"],
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ) { | |
| sections.push({ | |
| id: "testing", | |
| title: "Testing", | |
| priority: "low", | |
| order: 8.5, | |
| estimatedTokens: 300, | |
| dependencies: ["development"], | |
| }); | |
| } | |
| ) { | |
| sections.push({ | |
| id: "testing", | |
| title: "Testing", | |
| priority: "low", | |
| order: 8.5, | |
| estimatedTokens: 300, | |
| dependencies: ["usage"], | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 786 - 795, The "testing"
section currently declares a dependency on "development" which may not exist for
private repos or repos with zero forks; update the section object created in
generateSections (the block that pushes id: "testing") to either remove the
dependencies field or replace dependencies: ["development"] with dependencies:
["usage"] so the dependency always resolves (optimizeSectionOrder will then
handle ordering); make the change where the sections.push({ id: "testing", ...
}) is defined to ensure testing stands alone or depends on "usage" instead of
"development".
- Add missing SectionGenerator, ReadmeAssembler, and MultiStepReadmeGenerator classes - Fix Octokit import to use 'octokit' package instead of '@octokit/rest' - Verify all TypeScript types and interfaces are correctly defined - Confirm successful build with no compilation errors - Include all required methods for section generation, retry logic, and assembly The implementation now includes: - Complete section-by-section generation pipeline - Automatic retry and fallback mechanisms - Token-conscious repository analysis - Dynamic section planning based on project type - Concurrent processing with dependency management - Enhanced error handling and logging
8246ab4 to
6456916
Compare
…ntrolled format string' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (2)
src/app/api/generate/route.ts (1)
77-89:⚠️ Potential issue | 🟡 MinorUse constant log messages for the user-provided URL.
These logs still interpolate
githubUrlinto the format string. In Node,%sequences in user input are treated as formatter tokens, which is the CodeQL finding on this endpoint.Suggested change
- console.log(`Starting multi-step README generation for ${githubUrl}`); + console.log("Starting multi-step README generation", { githubUrl }); ... - console.log(`README generation completed for ${githubUrl}:`, { + console.log("README generation completed", { + githubUrl, success: result.success, sectionsGenerated: result.stats.sectionsGenerated, sectionsTotal: result.stats.sectionsTotal, tokensUsed: result.stats.tokensUsed, timeElapsed: endTime - startTime, errors: result.errors.length, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/generate/route.ts` around lines 77 - 89, Logs interpolate the user-provided githubUrl via template literals which allows percent-format tokens to be interpreted; update the console.log calls around generator.generateReadme (the "Starting multi-step README generation for ${githubUrl}" and "README generation completed for ${githubUrl}:" lines) to avoid formatting by passing the URL as a separate argument or as a property on the object (e.g., console.log('Starting multi-step README generation for', githubUrl) and console.log('README generation completed', { githubUrl, success: result.success, ... })) so the user string is never interpreted as a format string; keep the same surrounding variables (generator.generateReadme, result, startTime, endTime) and preserve the logged stats object.src/lib/multi-step-readme-generator.ts (1)
162-167:⚠️ Potential issue | 🟠 MajorReplace the
any[]repository-content shape.This is still the same merge-blocker from earlier reviews: CI is failing on both of these
any[]signatures. A narrow shared content type is enough here because this file only readsname,path, andtype.Suggested change
+type RepoContentItem = { + name: string; + path: string; + type: "file" | "dir" | "symlink" | "submodule"; +}; private async getRepositoryContents( owner: string, repo: string, path = "", maxDepth = 2, - ): Promise<any[]> { + ): Promise<RepoContentItem[]> { - private analyzeStructure(contents: any[]): RepositoryStructure { + private analyzeStructure(contents: RepoContentItem[]): RepositoryStructure {Also applies to: 314-317
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 162 - 167, Define a narrow interface (e.g., RepositoryItem with fields name: string; path: string; type: string | 'file' | 'dir') and replace the any[] return type on getRepositoryContents with RepositoryItem[]; then update the other any[] occurrences in this file to use the same RepositoryItem[] so callers only rely on name, path, and type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 179-188: The current filtering applies this.isRelevantFile to all
items and assigns to filteredContents, which removes plain directories before
the recursion; change the logic so directories are not pre-filtered by
isRelevantFile: keep two separate arrays (or filters) — one for files (filter
items where item.type !== "dir" and this.isRelevantFile(item.name), e.g.
filteredFiles) and one for directories (filter items where item.type === "dir"
and this.isImportantDirectory(item.name), e.g. subdirectories) — then use
filteredFiles for limiting/processing and subdirectories for the recursive
branch inside the method in multi-step-readme-generator.ts where
filteredContents, this.isRelevantFile, and this.isImportantDirectory are
referenced.
- Around line 1248-1253: The current truncation heuristic in
multi-step-readme-generator.ts uses content.trim() which removes trailing
newlines (making the newline check ineffective) and falsely flags valid Markdown
as truncated; update the endsAbruptly logic (the const endsAbruptly using
content) to avoid calling trim(), check raw content.endsWith('\n') instead of
trimming, and add a check for unclosed fenced code blocks using a regex like
(/```/g)?.length % 2 !== 0 so sections with open code fences aren't treated as
truncated; keep the length > 100 and final-period check but ensure you use the
untrimmed content and the code-fence parity test to suppress false positives.
- Around line 1418-1435: The batching loop incorrectly marks sections as
processed while still building the same concurrent batch
(processed.add(section.id)), allowing dependent sections to be included in the
same batch; fix by introducing a separate "scheduled" (or "inBatch") Set to mark
items already queued during batch construction and only add to processed after
the batch completes (or move processed.add into the batch-execution/completion
code); update references around remaining/currentBatch/toRemove to check
scheduled instead of processed when deciding dependencies (keep
section.dependencies.every(...processed...) but also ensure you don’t treat
scheduled as processed), and remove the premature processed.add(section.id)
inside the loop.
- Around line 627-676: The baseSections array is missing the required Tech Stack
section; add a new ReadmeSection object to baseSections with id "tech-stack"
(e.g., title "Tech Stack"), appropriate priority (critical/high per Issue `#101`),
an order value between "usage" and "license" (or wherever the plan requires,
e.g., order: 6), estimatedTokens (e.g., 200) and sensible dependencies (e.g.,
["description"] or ["usage"] as needed) so the pipeline always includes the tech
stack deliverable; update the baseSections constant to include this object.
- Around line 1341-1348: The return value currently sets success:
successfulSections > 0 which marks runs with partial/placeholder sections as
success; change it so success is true only when all expected sections were
generated successfully—e.g., set success to successfulSections ===
optimizedSections.length (or alternatively success = errors.length === 0) in the
object returned by the function that builds the final result (references:
successfulSections, optimizedSections, errors, tokensUsed, readme).
---
Duplicate comments:
In `@src/app/api/generate/route.ts`:
- Around line 77-89: Logs interpolate the user-provided githubUrl via template
literals which allows percent-format tokens to be interpreted; update the
console.log calls around generator.generateReadme (the "Starting multi-step
README generation for ${githubUrl}" and "README generation completed for
${githubUrl}:" lines) to avoid formatting by passing the URL as a separate
argument or as a property on the object (e.g., console.log('Starting multi-step
README generation for', githubUrl) and console.log('README generation
completed', { githubUrl, success: result.success, ... })) so the user string is
never interpreted as a format string; keep the same surrounding variables
(generator.generateReadme, result, startTime, endTime) and preserve the logged
stats object.
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 162-167: Define a narrow interface (e.g., RepositoryItem with
fields name: string; path: string; type: string | 'file' | 'dir') and replace
the any[] return type on getRepositoryContents with RepositoryItem[]; then
update the other any[] occurrences in this file to use the same RepositoryItem[]
so callers only rely on name, path, and type.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: aabd4840-cecd-4ef2-a489-e405362d3ea1
📒 Files selected for processing (5)
public/robots.txtpublic/sitemap-0.xmlpublic/sitemap.xmlsrc/app/api/generate/route.tssrc/lib/multi-step-readme-generator.ts
✅ Files skipped from review due to trivial changes (3)
- public/robots.txt
- public/sitemap.xml
- public/sitemap-0.xml
| // Filter out unimportant files and limit results | ||
| const filteredContents = data | ||
| .filter((item) => this.isRelevantFile(item.name)) | ||
| .slice(0, 100); // Prevent token overflow | ||
|
|
||
| if (maxDepth > 0) { | ||
| // Recursively get important subdirectories | ||
| const subdirectories = filteredContents.filter( | ||
| (item) => item.type === "dir" && this.isImportantDirectory(item.name), | ||
| ); |
There was a problem hiding this comment.
Don't filter out directories before recursion.
isRelevantFile(item.name) drops plain directories like src, app, docs, and tests before the item.type === "dir" branch ever runs. In practice this keeps the analyzer at the repo root, so tech-stack and project-type detection will miss most real repositories.
Suggested change
- const filteredContents = data
- .filter((item) => this.isRelevantFile(item.name))
- .slice(0, 100); // Prevent token overflow
+ const filteredContents = data
+ .filter((item) =>
+ item.type === "dir"
+ ? this.isImportantDirectory(item.name)
+ : this.isRelevantFile(item.name),
+ )
+ .slice(0, 100); // Prevent token overflow📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Filter out unimportant files and limit results | |
| const filteredContents = data | |
| .filter((item) => this.isRelevantFile(item.name)) | |
| .slice(0, 100); // Prevent token overflow | |
| if (maxDepth > 0) { | |
| // Recursively get important subdirectories | |
| const subdirectories = filteredContents.filter( | |
| (item) => item.type === "dir" && this.isImportantDirectory(item.name), | |
| ); | |
| // Filter out unimportant files and limit results | |
| const filteredContents = data | |
| .filter((item) => | |
| item.type === "dir" | |
| ? this.isImportantDirectory(item.name) | |
| : this.isRelevantFile(item.name), | |
| ) | |
| .slice(0, 100); // Prevent token overflow | |
| if (maxDepth > 0) { | |
| // Recursively get important subdirectories | |
| const subdirectories = filteredContents.filter( | |
| (item) => item.type === "dir" && this.isImportantDirectory(item.name), | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 179 - 188, The current
filtering applies this.isRelevantFile to all items and assigns to
filteredContents, which removes plain directories before the recursion; change
the logic so directories are not pre-filtered by isRelevantFile: keep two
separate arrays (or filters) — one for files (filter items where item.type !==
"dir" and this.isRelevantFile(item.name), e.g. filteredFiles) and one for
directories (filter items where item.type === "dir" and
this.isImportantDirectory(item.name), e.g. subdirectories) — then use
filteredFiles for limiting/processing and subdirectories for the recursive
branch inside the method in multi-step-readme-generator.ts where
filteredContents, this.isRelevantFile, and this.isImportantDirectory are
referenced.
| const baseSections: ReadmeSection[] = [ | ||
| { | ||
| id: "header", | ||
| title: "Project Header", | ||
| priority: "critical", | ||
| order: 1, | ||
| estimatedTokens: 200, | ||
| dependencies: [], | ||
| }, | ||
| { | ||
| id: "description", | ||
| title: "Description", | ||
| priority: "critical", | ||
| order: 2, | ||
| estimatedTokens: 300, | ||
| dependencies: ["header"], | ||
| }, | ||
| { | ||
| id: "features", | ||
| title: "Features", | ||
| priority: "high", | ||
| order: 3, | ||
| estimatedTokens: 400, | ||
| dependencies: ["description"], | ||
| }, | ||
| { | ||
| id: "installation", | ||
| title: "Installation", | ||
| priority: "critical", | ||
| order: 4, | ||
| estimatedTokens: 500, | ||
| dependencies: ["features"], | ||
| }, | ||
| { | ||
| id: "usage", | ||
| title: "Usage", | ||
| priority: "high", | ||
| order: 5, | ||
| estimatedTokens: 600, | ||
| dependencies: ["installation"], | ||
| }, | ||
| { | ||
| id: "license", | ||
| title: "License", | ||
| priority: "medium", | ||
| order: 10, | ||
| estimatedTokens: 100, | ||
| dependencies: [], | ||
| }, | ||
| ]; |
There was a problem hiding this comment.
Add the required Tech Stack section to the base plan.
Issue #101 explicitly requires a Tech Stack section, but the base section list never creates one and no conditional path adds it later. The pipeline can't currently guarantee one of the required deliverables.
Suggested change
{
id: "features",
title: "Features",
priority: "high",
order: 3,
estimatedTokens: 400,
dependencies: ["description"],
},
+ {
+ id: "tech-stack",
+ title: "Tech Stack",
+ priority: "high",
+ order: 3.5,
+ estimatedTokens: 300,
+ dependencies: ["description"],
+ },
{
id: "installation",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 627 - 676, The
baseSections array is missing the required Tech Stack section; add a new
ReadmeSection object to baseSections with id "tech-stack" (e.g., title "Tech
Stack"), appropriate priority (critical/high per Issue `#101`), an order value
between "usage" and "license" (or wherever the plan requires, e.g., order: 6),
estimatedTokens (e.g., 200) and sensible dependencies (e.g., ["description"] or
["usage"] as needed) so the pipeline always includes the tech stack deliverable;
update the baseSections constant to include this object.
| // Check if content ends abruptly without proper markdown closure | ||
| const endsAbruptly = | ||
| !content.trim().endsWith(".") && | ||
| !content.trim().endsWith("\n") && | ||
| content.length > 100; | ||
|
|
There was a problem hiding this comment.
The truncation heuristic false-positives on valid Markdown.
Because trim() removes trailing newlines, the newline branch is dead, and any section over 100 characters that doesn't end with . is treated as truncated. Code fences, bullet lists, tables, and badge lines will all trigger unnecessary continuation/retry work here.
Suggested change
- const endsAbruptly =
- !content.trim().endsWith(".") &&
- !content.trim().endsWith("\n") &&
- content.length > 100;
+ const trimmed = content.trimEnd();
+ const endsAbruptly =
+ trimmed.length > 100 &&
+ (trimmed.match(/```/g) ?? []).length % 2 !== 0;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Check if content ends abruptly without proper markdown closure | |
| const endsAbruptly = | |
| !content.trim().endsWith(".") && | |
| !content.trim().endsWith("\n") && | |
| content.length > 100; | |
| const trimmed = content.trimEnd(); | |
| const endsAbruptly = | |
| trimmed.length > 100 && | |
| (trimmed.match(/ |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 1248 - 1253, The current
truncation heuristic in multi-step-readme-generator.ts uses content.trim() which
removes trailing newlines (making the newline check ineffective) and falsely
flags valid Markdown as truncated; update the endsAbruptly logic (the const
endsAbruptly using content) to avoid calling trim(), check raw
content.endsWith('\n') instead of trimming, and add a check for unclosed fenced
code blocks using a regex like (/```/g)?.length % 2 !== 0 so sections with open
code fences aren't treated as truncated; keep the length > 100 and final-period
check but ensure you use the untrimmed content and the code-fence parity test to
suppress false positives.
| return { | ||
| success: successfulSections > 0, | ||
| readme, | ||
| sectionsGenerated: successfulSections, | ||
| sectionsTotal: optimizedSections.length, | ||
| errors, | ||
| tokensUsed: totalTokens, | ||
| }; |
There was a problem hiding this comment.
Don't mark the whole run as successful when required sections failed.
success: successfulSections > 0 returns true even if only one section succeeded and the rest fell back to placeholders. Since src/app/api/generate/route.ts uses this flag to choose HTTP 200 vs 500, clients will treat heavily degraded READMEs as successful.
Suggested change
+ const requiredSectionIds = new Set([
+ "header",
+ "description",
+ "features",
+ "installation",
+ "license",
+ ]);
+ const requiredSectionsSucceeded = optimizedSections
+ .filter((section) => requiredSectionIds.has(section.id))
+ .every((section) => results[section.id]?.success);
+
return {
- success: successfulSections > 0,
+ success: requiredSectionsSucceeded,
readme,
sectionsGenerated: successfulSections,
sectionsTotal: optimizedSections.length,
errors,
tokensUsed: totalTokens,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 1341 - 1348, The return
value currently sets success: successfulSections > 0 which marks runs with
partial/placeholder sections as success; change it so success is true only when
all expected sections were generated successfully—e.g., set success to
successfulSections === optimizedSections.length (or alternatively success =
errors.length === 0) in the object returned by the function that builds the
final result (references: successfulSections, optimizedSections, errors,
tokensUsed, readme).
| for ( | ||
| let i = 0; | ||
| i < remaining.length && | ||
| currentBatch.length < this.config.concurrentSections; | ||
| i++ | ||
| ) { | ||
| const section = remaining[i]; | ||
|
|
||
| // Check if dependencies are satisfied | ||
| const dependenciesSatisfied = section.dependencies.every((depId) => | ||
| processed.has(depId), | ||
| ); | ||
|
|
||
| if (dependenciesSatisfied) { | ||
| currentBatch.push(section); | ||
| processed.add(section.id); | ||
| toRemove.push(i); | ||
| } |
There was a problem hiding this comment.
Dependency batching is not actually dependency-safe.
processed.add(section.id) happens while the batch is still being built, so chains like header -> description -> features can land in the same concurrent batch. That makes downstream dependency-context lookups run before the prerequisite sections have finished.
Suggested change
if (dependenciesSatisfied) {
currentBatch.push(section);
- processed.add(section.id);
toRemove.push(i);
}
}
+
+ for (const section of currentBatch) {
+ processed.add(section.id);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for ( | |
| let i = 0; | |
| i < remaining.length && | |
| currentBatch.length < this.config.concurrentSections; | |
| i++ | |
| ) { | |
| const section = remaining[i]; | |
| // Check if dependencies are satisfied | |
| const dependenciesSatisfied = section.dependencies.every((depId) => | |
| processed.has(depId), | |
| ); | |
| if (dependenciesSatisfied) { | |
| currentBatch.push(section); | |
| processed.add(section.id); | |
| toRemove.push(i); | |
| } | |
| for ( | |
| let i = 0; | |
| i < remaining.length && | |
| currentBatch.length < this.config.concurrentSections; | |
| i++ | |
| ) { | |
| const section = remaining[i]; | |
| // Check if dependencies are satisfied | |
| const dependenciesSatisfied = section.dependencies.every((depId) => | |
| processed.has(depId), | |
| ); | |
| if (dependenciesSatisfied) { | |
| currentBatch.push(section); | |
| toRemove.push(i); | |
| } | |
| } | |
| for (const section of currentBatch) { | |
| processed.add(section.id); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 1418 - 1435, The
batching loop incorrectly marks sections as processed while still building the
same concurrent batch (processed.add(section.id)), allowing dependent sections
to be included in the same batch; fix by introducing a separate "scheduled" (or
"inBatch") Set to mark items already queued during batch construction and only
add to processed after the batch completes (or move processed.add into the
batch-execution/completion code); update references around
remaining/currentBatch/toRemove to check scheduled instead of processed when
deciding dependencies (keep section.dependencies.every(...processed...) but also
ensure you don’t treat scheduled as processed), and remove the premature
processed.add(section.id) inside the loop.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/app/api/generate/route.ts (1)
127-137: Consider sanitizing the error message in production.Exposing
error.messageto clients could leak sensitive implementation details (internal paths, API error messages, etc.). Consider returning a generic message in production while preserving detailed logging.Optional: Environment-aware error response
} catch (error) { console.error("Multi-step README generation API error:", error); + const isDev = process.env.NODE_ENV === "development"; return NextResponse.json( { error: "Internal server error in multi-step README generation", - message: error instanceof Error ? error.message : "Unknown error", + ...(isDev && { message: error instanceof Error ? error.message : "Unknown error" }), }, { status: 500 }, ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/generate/route.ts` around lines 127 - 137, The catch block in route.ts currently returns error.message to the client; change it to avoid leaking details in production by checking NODE_ENV (or an existing isProduction helper) and returning a generic client-facing message like "Internal server error" when in production while still logging the full error server-side using console.error or your logger; update the NextResponse.json call to use the sanitized message for production but include error instanceof Error ? error.message : undefined in server logs only, and ensure the symbols referenced (the catch error variable, NextResponse.json) are the ones adjusted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/app/api/generate/route.ts`:
- Around line 127-137: The catch block in route.ts currently returns
error.message to the client; change it to avoid leaking details in production by
checking NODE_ENV (or an existing isProduction helper) and returning a generic
client-facing message like "Internal server error" when in production while
still logging the full error server-side using console.error or your logger;
update the NextResponse.json call to use the sanitized message for production
but include error instanceof Error ? error.message : undefined in server logs
only, and ensure the symbols referenced (the catch error variable,
NextResponse.json) are the ones adjusted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: acda0275-b5a0-406a-8c9a-30e416048f68
📒 Files selected for processing (1)
src/app/api/generate/route.ts
- Fix TypeScript 'any' type definitions with proper GitHub API interfaces - Add proper context usage in buildSectionPrompt function - Include structure parameter in continueGeneration function - Remove unused language variable in route.ts API endpoint - Fix unused SUPPORTED_LANGUAGES by using it instead of hardcoded array - Add ESLint ignore comments for external API response types - All tests, builds, and lints now pass successfully Resolves failing CI/CD checks in PR #116
- Resolved merge conflicts between local lint fixes and remote autofix changes - Maintained ESLint ignore comments for external API types - Kept improved error handling using individual push instead of spread operator - Preserved remote formatting improvements for function parameters - Removed unused language variable (not implemented in generator) All CI/CD checks should now pass
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (6)
src/lib/multi-step-readme-generator.ts (5)
183-188:⚠️ Potential issue | 🟡 MinorReplace
any[]with the definedGitHubContentItem[]type to fix CI failure.The ESLint
@typescript-eslint/no-explicit-anyerror is blocking CI. TheGitHubContentItemtype is already defined at lines 67-83 and should be used here.Proposed fix
private async getRepositoryContents( owner: string, repo: string, path = "", maxDepth = 2, - ): Promise<any[]> { + ): Promise<GitHubContentItem[]> {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 183 - 188, The getRepositoryContents method currently returns Promise<any[]> which triggers the no-explicit-any ESLint error; change its signature to return Promise<GitHubContentItem[]> and ensure you use the existing GitHubContentItem type defined in this file (referenced by its name) so the function signature becomes Promise<GitHubContentItem[]>—no other logic changes required.
1268-1295:⚠️ Potential issue | 🟡 MinorTruncation heuristic has false-positive potential.
The
endsAbruptlycheck (lines 1282-1285) usescontent.trim()which removes trailing newlines, making the newline check always false. Additionally, valid Markdown like code fences, bullet lists, and tables often don't end with periods, triggering unnecessary continuation attempts.Proposed improvement
// Check if content ends abruptly without proper markdown closure + const trimmed = content.trimEnd(); + const hasUnclosedCodeFence = (content.match(/```/g) ?? []).length % 2 !== 0; const endsAbruptly = - !content.trim().endsWith(".") && - !content.trim().endsWith("\n") && - content.length > 100; + trimmed.length > 100 && hasUnclosedCodeFence;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 1268 - 1295, The truncation heuristic in isResponseTruncated is producing false positives because endsAbruptly uses content.trim() (removing newlines) and relies on punctuation, which misclassifies valid markdown (code fences, lists, tables); update endsAbruptly logic to operate on the untrimmed content or use a trimmed variable for length checks, detect unclosed code fences by counting ``` occurrences (e.g., hasUnclosedCodeFence = (content.match(/```/g) || []).length % 2 !== 0), and replace the current punctuation/newline checks with a condition like trimmed.length > 100 && hasUnclosedCodeFence; also relax or refine sectionChecks to consider presence of markdown structures (``` , lists, tables) before flagging truncation so isResponseTruncated returns true only for genuine truncation cases.
651-700:⚠️ Potential issue | 🟠 MajorAdd a "Tech Stack" base section to meet issue
#101requirements.The PR objectives from issue
#101explicitly require a Tech Stack section as one of the required deliverables. The current base sections don't include it, and no conditional logic adds one. This means the generated README may not include tech stack information consistently.Proposed fix - add tech-stack to base sections
{ id: "features", title: "Features", priority: "high", order: 3, estimatedTokens: 400, dependencies: ["description"], }, + { + id: "tech-stack", + title: "Tech Stack", + priority: "high", + order: 3.5, + estimatedTokens: 300, + dependencies: ["description"], + }, { id: "installation",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 651 - 700, Add a new ReadmeSection entry to the baseSections array for the required Tech Stack section: create an object with id "tech-stack", title "Tech Stack", priority "high", order 6, estimatedTokens ~200, and dependencies set to ["usage"] so it follows Usage; ensure the new entry is included alongside the existing ReadmeSection objects in the baseSections constant.
1464-1468:⚠️ Potential issue | 🟠 MajorDependency batching marks sections as processed prematurely.
processed.add(section.id)on line 1466 happens while building the batch, so dependency chains likeheader -> description -> featurescan land in the same concurrent batch. When these run in parallel, downstream sections may try to read dependency context before prerequisites have completed.Proposed fix - defer processed marking until batch execution
if (dependenciesSatisfied) { currentBatch.push(section); - processed.add(section.id); toRemove.push(i); } } + + // Mark sections as processed only after adding to batch + for (const section of currentBatch) { + processed.add(section.id); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 1464 - 1468, The code marks sections as processed while building a concurrent batch (processed.add(section.id) inside the currentBatch construction), which allows dependency chains to be scheduled together and run in parallel; remove the processed.add(section.id) call from the batch-building loop and instead mark each section id in processed only after its batch task has been executed (e.g., in the batch execution/completion path that consumes currentBatch/toRemove), so dependenciesSatisfied still gates inclusion but processed reflects completed work; update any logic that relied on processed being set during batch assembly to use a temporary list (e.g., toRemove/currentBatch) and then call processed.add(...) once the section's work finishes.
1374-1381:⚠️ Potential issue | 🟠 MajorSuccess flag may mislead clients when required sections fail.
The current logic
success: successfulSections > 0returnstrueeven if only one optional section succeeded while all critical sections failed. Sincesrc/app/api/generate/route.tsuses this flag to determine HTTP 200 vs 500, clients may receive degraded READMEs (with placeholders) as successful responses.Proposed fix - base success on required sections
+ const requiredSectionIds = new Set([ + "header", + "description", + "features", + "installation", + "license", + ]); + const requiredSectionsSucceeded = optimizedSections + .filter((s) => requiredSectionIds.has(s.id)) + .every((s) => results[s.id]?.success); + return { - success: successfulSections > 0, + success: requiredSectionsSucceeded, readme,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 1374 - 1381, The success boolean should reflect whether all required sections were generated, not just any successful section; update the return to compute requiredSectionsTotal = optimizedSections.filter(s => s.required).length and successfulRequiredSections = count of those required sections that succeeded (use the same success-tracking logic used for successfulSections), then set success: requiredSectionsTotal === 0 ? successfulSections > 0 : successfulRequiredSections === requiredSectionsTotal so that if there are required sections the overall success is true only when all required ones succeeded (use symbols optimizedSections, successfulSections and the success-tracking logic already in the function to find and count required vs optional results).src/app/api/generate/route.ts (1)
62-73:⚠️ Potential issue | 🟡 MinorValidate
GEMINI_API_KEYbefore use to avoid cryptic runtime errors.The non-null assertion
process.env.GEMINI_API_KEY!will throw a confusing error if the environment variable is missing. Add explicit validation with a clear error message.Proposed fix
+ const geminiApiKey = process.env.GEMINI_API_KEY; + if (!geminiApiKey) { + console.error("GEMINI_API_KEY environment variable is not set"); + return NextResponse.json( + { error: "Server configuration error" }, + { status: 500 }, + ); + } + // Initialize the multi-step generator with enhanced configuration const generator = new MultiStepReadmeGenerator( - process.env.GEMINI_API_KEY!, + geminiApiKey, process.env.GITHUB_TOKEN, // Optional GitHub token for higher rate limits🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/generate/route.ts` around lines 62 - 73, Replace the non-null assertion on process.env.GEMINI_API_KEY used when constructing MultiStepReadmeGenerator with an explicit validation: check process.env.GEMINI_API_KEY at the top of the handler (or immediately before new MultiStepReadmeGenerator) and if it's missing, return/throw a clear error (e.g., throw new Error or return a 400/500 response) with a message like "GEMINI_API_KEY environment variable is required" so the failure is obvious; ensure you reference the same symbol names (process.env.GEMINI_API_KEY and MultiStepReadmeGenerator) when adding the check.
🧹 Nitpick comments (6)
src/components/Generator/SearchInput.tsx (1)
29-29: Consider usingSupportedLanguagetype for stronger type safety.The
languagestate usesstringtype while a stricterSupportedLanguagetype is available from the same module. Using the narrower type would provide compile-time guarantees that only valid languages are used.♻️ Suggested improvement
-import { SUPPORTED_LANGUAGES } from "@/constants/languages"; +import { SUPPORTED_LANGUAGES, SupportedLanguage } from "@/constants/languages";- const [language, setLanguage] = useState("English"); + const [language, setLanguage] = useState<SupportedLanguage>("English");Note: This would also require updating the
SearchInputPropsinterface to useSupportedLanguageinstead ofstringfor full type safety throughout the component chain.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Generator/SearchInput.tsx` at line 29, The language state currently uses a plain string; change its type to the existing SupportedLanguage type (import it from the same module) by typing the state hook (language and setLanguage) as SupportedLanguage so only valid languages are allowed, and update the SearchInputProps interface to use SupportedLanguage instead of string so the prop and internal state are consistently typed (look for the useState declaration for language, the setLanguage updater, and the SearchInputProps interface in SearchInput.tsx).src/lib/multi-step-readme-generator.ts (3)
116-123: Consider using the sharedgetOctokit()utility for consistency.The codebase has a shared Octokit factory at
src/lib/octokit.tsthat handles authentication viaprocess.env.GITHUB_TOKEN. Creating a separate instance here could lead to authentication inconsistencies if the shared utility is updated (e.g., token rotation, scoping changes).Proposed refactor to use shared utility
+import { getOctokit } from "@/lib/octokit"; + export class RepositoryAnalyzer { private octokit: Octokit; constructor(githubToken?: string) { - this.octokit = new Octokit({ - auth: githubToken, - }); + // Use shared Octokit instance, or create with custom token if provided + if (githubToken) { + this.octokit = new Octokit({ auth: githubToken }); + } else { + this.octokit = getOctokit() as unknown as Octokit; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 116 - 123, The RepositoryAnalyzer class currently instantiates its own Octokit in the constructor (private octokit: Octokit and constructor) rather than using the shared factory; replace that local creation by importing and calling the shared getOctokit() helper (from src/lib/octokit) and assign its result to this.octokit so authentication and token handling remain consistent across the codebase—update the constructor to call getOctokit() and remove the direct new Octokit({...}) usage.
1725-1736: URL parsing handles common cases but could be more robust.The regex
/github\.com\/([^\/]+)\/([^\/]+)/works for standard URLs but may capture extra path segments (e.g.,github.com/owner/repo/tree/mainwould capturetreein the repo field, though the.gitreplacement wouldn't apply).More robust parsing
private parseGithubUrl(url: string): { owner: string; repo: string } { - const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/); + const match = url.match(/github\.com\/([^\/]+)\/([^\/\?#]+)/); if (!match) { throw new Error("Invalid GitHub URL format"); } return { owner: match[1], - repo: match[2].replace(/\.git$/, ""), // Remove .git suffix if present + repo: match[2].replace(/\.git$/, "").split("/")[0], // Remove .git suffix and any trailing path }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 1725 - 1736, parseGithubUrl currently uses a regex that can capture extra path segments (e.g., "tree/main") into the repo; update parseGithubUrl to robustly extract only the owner and repository name by either parsing with the URL API and splitting pathname or by using a stricter regex that stops the repo capture at an optional ".git" or the next "/" (for example match owner and repo as the first two path segments and ignore everything after). Ensure the returned repo has the ".git" suffix removed and any trailing segments discarded so parseGithubUrl always returns the correct owner and repo values.
880-894: Model version mismatch affects generation capabilities.This class hardcodes
"gemini-1.5-pro"(line 1236) while the shared utility atsrc/lib/gemini.tsuses"gemini-2.5-flash". This creates an inconsistency: Gemini-2.5-Flash offers significantly better performance—64K output tokens (vs 8K), superior reasoning and coding benchmarks, and newer training data—yetSectionGeneratoruses an older model that may produce lower-quality READMEs and hit token limits more easily.Additionally,
SectionGeneratordoes not apply the safety settings configured ingemini.ts, creating another inconsistency in content policy.Recommend:
- Update
"gemini-1.5-pro"to"gemini-2.5-flash"to align with the shared utility and benefit from improved capabilities.- Extract the model name to a shared constant to prevent future divergence.
- Consider applying the safety settings from
gemini.tsif they should apply uniformly across the application.Note: Direct reuse of
getGeminiModel()is not straightforward sinceSectionGeneratoracceptsapiKeyas a constructor parameter, while the shared utility uses environment variables. A constant or parameter-based approach is more feasible.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/multi-step-readme-generator.ts` around lines 880 - 894, SectionGenerator currently hardcodes an older model and omits safety settings; change its GoogleGenerativeAI instantiation to use the shared gemini model constant (e.g., replace "gemini-1.5-pro" with a new exported constant like GEMINI_MODEL in src/lib/gemini.ts set to "gemini-2.5-flash") and ensure the same safety/config settings from gemini.ts are applied (either by importing a shared config object or by adding optional constructor params for modelName and safetySettings and passing them through to GoogleGenerativeAI); update the SectionGenerator constructor and its use of GoogleGenerativeAI accordingly so the model name and safety options are unified across the codebase (refer to SectionGenerator, GoogleGenerativeAI, and the gemini.ts exports).src/app/api/generate/route.ts (2)
127-136: Error handling properly catches and reports failures.The catch block logs the error and returns a structured error response. Consider also logging the stack trace for debugging.
Enhanced error logging
} catch (error) { - console.error("Multi-step README generation API error:", error); + console.error("Multi-step README generation API error:", error instanceof Error ? error.stack : error); return NextResponse.json(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/generate/route.ts` around lines 127 - 136, The catch block in src/app/api/generate/route.ts currently logs the error via console.error and returns a JSON error via NextResponse.json; update the error logging to include the error stack when available (use error.stack or fallback to the error object) so debugging has stack traces available — modify the catch handling around the existing console.error call (and keep the NextResponse.json behavior unchanged) to log both the message and the stack for the caught error.
82-90: Consider sanitizing logged URL to prevent log injection.While
console.logdoesn't use format strings likeprintf, logging user-provided URLs could potentially include control characters or very long strings that might affect log parsing/storage. This is low risk but worth considering for production.Optional sanitization
// Log generation statistics for monitoring - console.log("README generation completed for", githubUrl, { + console.log("README generation completed for", githubUrl.slice(0, 200), { success: result.success,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/api/generate/route.ts` around lines 82 - 90, Sanitize the user-provided githubUrl before logging to avoid control characters or excessively long values: create a small sanitizer (e.g., sanitizeLogUrl) that strips/control-characters (remove \n, \r, other non-printables), collapses whitespace, and truncates to a safe max length (e.g., 200 chars), then use the sanitized value (sanitizedGithubUrl) in the console.log call where githubUrl is currently used; update the logging block that outputs "README generation completed for" to pass sanitizedGithubUrl instead of githubUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 1743-1799: Remove the unused exported function
handleReadmeGeneration: delete the entire function declaration (including its
try/catch and use of MultiStepReadmeGenerator and generateReadme) from the
module and any related dead imports or variables that become unused as a result;
search the repo for any references to handleReadmeGeneration to ensure safe
removal and run tests/linters to confirm no remaining usages or broken exports.
- Around line 1757-1768: The code instantiates MultiStepReadmeGenerator using a
non-null assertion on process.env.GEMINI_API_KEY which can produce an unclear
runtime error; replace the direct non-null assertion with an explicit check
before creating the generator (e.g., verify process.env.GEMINI_API_KEY is
defined and throw a clear Error like "GEMINI_API_KEY is required" if missing),
then pass the validated key into the MultiStepReadmeGenerator constructor (refer
to the generator instantiation where MultiStepReadmeGenerator is called and to
process.env.GEMINI_API_KEY).
---
Duplicate comments:
In `@src/app/api/generate/route.ts`:
- Around line 62-73: Replace the non-null assertion on
process.env.GEMINI_API_KEY used when constructing MultiStepReadmeGenerator with
an explicit validation: check process.env.GEMINI_API_KEY at the top of the
handler (or immediately before new MultiStepReadmeGenerator) and if it's
missing, return/throw a clear error (e.g., throw new Error or return a 400/500
response) with a message like "GEMINI_API_KEY environment variable is required"
so the failure is obvious; ensure you reference the same symbol names
(process.env.GEMINI_API_KEY and MultiStepReadmeGenerator) when adding the check.
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 183-188: The getRepositoryContents method currently returns
Promise<any[]> which triggers the no-explicit-any ESLint error; change its
signature to return Promise<GitHubContentItem[]> and ensure you use the existing
GitHubContentItem type defined in this file (referenced by its name) so the
function signature becomes Promise<GitHubContentItem[]>—no other logic changes
required.
- Around line 1268-1295: The truncation heuristic in isResponseTruncated is
producing false positives because endsAbruptly uses content.trim() (removing
newlines) and relies on punctuation, which misclassifies valid markdown (code
fences, lists, tables); update endsAbruptly logic to operate on the untrimmed
content or use a trimmed variable for length checks, detect unclosed code fences
by counting ``` occurrences (e.g., hasUnclosedCodeFence = (content.match(/```/g)
|| []).length % 2 !== 0), and replace the current punctuation/newline checks
with a condition like trimmed.length > 100 && hasUnclosedCodeFence; also relax
or refine sectionChecks to consider presence of markdown structures (``` ,
lists, tables) before flagging truncation so isResponseTruncated returns true
only for genuine truncation cases.
- Around line 651-700: Add a new ReadmeSection entry to the baseSections array
for the required Tech Stack section: create an object with id "tech-stack",
title "Tech Stack", priority "high", order 6, estimatedTokens ~200, and
dependencies set to ["usage"] so it follows Usage; ensure the new entry is
included alongside the existing ReadmeSection objects in the baseSections
constant.
- Around line 1464-1468: The code marks sections as processed while building a
concurrent batch (processed.add(section.id) inside the currentBatch
construction), which allows dependency chains to be scheduled together and run
in parallel; remove the processed.add(section.id) call from the batch-building
loop and instead mark each section id in processed only after its batch task has
been executed (e.g., in the batch execution/completion path that consumes
currentBatch/toRemove), so dependenciesSatisfied still gates inclusion but
processed reflects completed work; update any logic that relied on processed
being set during batch assembly to use a temporary list (e.g.,
toRemove/currentBatch) and then call processed.add(...) once the section's work
finishes.
- Around line 1374-1381: The success boolean should reflect whether all required
sections were generated, not just any successful section; update the return to
compute requiredSectionsTotal = optimizedSections.filter(s => s.required).length
and successfulRequiredSections = count of those required sections that succeeded
(use the same success-tracking logic used for successfulSections), then set
success: requiredSectionsTotal === 0 ? successfulSections > 0 :
successfulRequiredSections === requiredSectionsTotal so that if there are
required sections the overall success is true only when all required ones
succeeded (use symbols optimizedSections, successfulSections and the
success-tracking logic already in the function to find and count required vs
optional results).
---
Nitpick comments:
In `@src/app/api/generate/route.ts`:
- Around line 127-136: The catch block in src/app/api/generate/route.ts
currently logs the error via console.error and returns a JSON error via
NextResponse.json; update the error logging to include the error stack when
available (use error.stack or fallback to the error object) so debugging has
stack traces available — modify the catch handling around the existing
console.error call (and keep the NextResponse.json behavior unchanged) to log
both the message and the stack for the caught error.
- Around line 82-90: Sanitize the user-provided githubUrl before logging to
avoid control characters or excessively long values: create a small sanitizer
(e.g., sanitizeLogUrl) that strips/control-characters (remove \n, \r, other
non-printables), collapses whitespace, and truncates to a safe max length (e.g.,
200 chars), then use the sanitized value (sanitizedGithubUrl) in the console.log
call where githubUrl is currently used; update the logging block that outputs
"README generation completed for" to pass sanitizedGithubUrl instead of
githubUrl.
In `@src/components/Generator/SearchInput.tsx`:
- Line 29: The language state currently uses a plain string; change its type to
the existing SupportedLanguage type (import it from the same module) by typing
the state hook (language and setLanguage) as SupportedLanguage so only valid
languages are allowed, and update the SearchInputProps interface to use
SupportedLanguage instead of string so the prop and internal state are
consistently typed (look for the useState declaration for language, the
setLanguage updater, and the SearchInputProps interface in SearchInput.tsx).
In `@src/lib/multi-step-readme-generator.ts`:
- Around line 116-123: The RepositoryAnalyzer class currently instantiates its
own Octokit in the constructor (private octokit: Octokit and constructor) rather
than using the shared factory; replace that local creation by importing and
calling the shared getOctokit() helper (from src/lib/octokit) and assign its
result to this.octokit so authentication and token handling remain consistent
across the codebase—update the constructor to call getOctokit() and remove the
direct new Octokit({...}) usage.
- Around line 1725-1736: parseGithubUrl currently uses a regex that can capture
extra path segments (e.g., "tree/main") into the repo; update parseGithubUrl to
robustly extract only the owner and repository name by either parsing with the
URL API and splitting pathname or by using a stricter regex that stops the repo
capture at an optional ".git" or the next "/" (for example match owner and repo
as the first two path segments and ignore everything after). Ensure the returned
repo has the ".git" suffix removed and any trailing segments discarded so
parseGithubUrl always returns the correct owner and repo values.
- Around line 880-894: SectionGenerator currently hardcodes an older model and
omits safety settings; change its GoogleGenerativeAI instantiation to use the
shared gemini model constant (e.g., replace "gemini-1.5-pro" with a new exported
constant like GEMINI_MODEL in src/lib/gemini.ts set to "gemini-2.5-flash") and
ensure the same safety/config settings from gemini.ts are applied (either by
importing a shared config object or by adding optional constructor params for
modelName and safetySettings and passing them through to GoogleGenerativeAI);
update the SectionGenerator constructor and its use of GoogleGenerativeAI
accordingly so the model name and safety options are unified across the codebase
(refer to SectionGenerator, GoogleGenerativeAI, and the gemini.ts exports).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 18738834-b32f-49eb-8b6d-4c8dfd55342d
📒 Files selected for processing (4)
public/sitemap-0.xmlsrc/app/api/generate/route.tssrc/components/Generator/SearchInput.tsxsrc/lib/multi-step-readme-generator.ts
✅ Files skipped from review due to trivial changes (1)
- public/sitemap-0.xml
| export async function handleReadmeGeneration( | ||
| request: Request, | ||
| ): Promise<Response> { | ||
| try { | ||
| const body = await request.json(); | ||
| const { githubUrl } = body; | ||
|
|
||
| if (!githubUrl) { | ||
| return Response.json( | ||
| { error: "GitHub URL is required" }, | ||
| { status: 400 }, | ||
| ); | ||
| } | ||
|
|
||
| // Initialize generator with environment variables | ||
| const generator = new MultiStepReadmeGenerator( | ||
| process.env.GEMINI_API_KEY!, | ||
| process.env.GITHUB_TOKEN, // Optional | ||
| { | ||
| maxRetries: 3, | ||
| maxTokensPerSection: 800, | ||
| temperature: 0.7, | ||
| concurrentSections: 3, | ||
| enableContinuation: true, | ||
| }, | ||
| ); | ||
|
|
||
| const result = await generator.generateReadme(githubUrl); | ||
|
|
||
| if (!result.success) { | ||
| return Response.json( | ||
| { | ||
| error: "Failed to generate README", | ||
| details: result.errors, | ||
| }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
|
|
||
| return Response.json({ | ||
| success: true, | ||
| readme: result.readme, | ||
| stats: result.stats, | ||
| metadata: { | ||
| name: result.metadata?.name, | ||
| description: result.metadata?.description, | ||
| language: result.metadata?.language, | ||
| stars: result.metadata?.stars, | ||
| }, | ||
| }); | ||
| } catch (error) { | ||
| return Response.json( | ||
| { error: `Internal server error: ${error}` }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if handleReadmeGeneration is imported/used anywhere
echo "=== Searching for handleReadmeGeneration usage ==="
rg "handleReadmeGeneration" --type ts -C 2Repository: BeyteFlow/ReadmeGenAI
Length of output: 480
🏁 Script executed:
# Check if handleReadmeGeneration is imported anywhere
echo "=== Checking for imports of handleReadmeGeneration ==="
rg "import.*handleReadmeGeneration|from.*multi-step-readme-generator" --type ts
# Check the route handler file
echo "=== Checking src/app/api/generate/route.ts ==="
cat src/app/api/generate/route.ts
# Check for any index exports
echo "=== Checking for index/export files ==="
find src -name "index.ts" -o -name "index.js" | head -10Repository: BeyteFlow/ReadmeGenAI
Length of output: 4889
Remove the unused handleReadmeGeneration export.
The function is never imported or used anywhere in the codebase. The src/app/api/generate/route.ts endpoint reimplements similar logic independently rather than using this helper, making it dead code that should be removed to avoid maintenance burden.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 1743 - 1799, Remove the
unused exported function handleReadmeGeneration: delete the entire function
declaration (including its try/catch and use of MultiStepReadmeGenerator and
generateReadme) from the module and any related dead imports or variables that
become unused as a result; search the repo for any references to
handleReadmeGeneration to ensure safe removal and run tests/linters to confirm
no remaining usages or broken exports.
| // Initialize generator with environment variables | ||
| const generator = new MultiStepReadmeGenerator( | ||
| process.env.GEMINI_API_KEY!, | ||
| process.env.GITHUB_TOKEN, // Optional | ||
| { | ||
| maxRetries: 3, | ||
| maxTokensPerSection: 800, | ||
| temperature: 0.7, | ||
| concurrentSections: 3, | ||
| enableContinuation: true, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Validate GEMINI_API_KEY before use to provide clear error messages.
The non-null assertion process.env.GEMINI_API_KEY! will throw a confusing runtime error if the environment variable is undefined. Add explicit validation.
Proposed fix
+ const geminiApiKey = process.env.GEMINI_API_KEY;
+ if (!geminiApiKey) {
+ return Response.json(
+ { error: "Server configuration error: Missing API key" },
+ { status: 500 },
+ );
+ }
+
// Initialize generator with environment variables
const generator = new MultiStepReadmeGenerator(
- process.env.GEMINI_API_KEY!,
+ geminiApiKey,
process.env.GITHUB_TOKEN, // Optional🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/multi-step-readme-generator.ts` around lines 1757 - 1768, The code
instantiates MultiStepReadmeGenerator using a non-null assertion on
process.env.GEMINI_API_KEY which can produce an unclear runtime error; replace
the direct non-null assertion with an explicit check before creating the
generator (e.g., verify process.env.GEMINI_API_KEY is defined and throw a clear
Error like "GEMINI_API_KEY is required" if missing), then pass the validated key
into the MultiStepReadmeGenerator constructor (refer to the generator
instantiation where MultiStepReadmeGenerator is called and to
process.env.GEMINI_API_KEY).
This PR implements a comprehensive multi-step README generation pipeline that solves the token limit issues described in issue #101.
Problem Solved
Solution
Key Features
Technical Details
Closes #101