Skip to content

feat: Multi-Step README Generation Pipeline - Fixes Token Limit Issues (#101)#116

Open
naheel0 wants to merge 8 commits intomainfrom
feature/multi-step-readme-generation
Open

feat: Multi-Step README Generation Pipeline - Fixes Token Limit Issues (#101)#116
naheel0 wants to merge 8 commits intomainfrom
feature/multi-step-readme-generation

Conversation

@naheel0
Copy link
Copy Markdown
Member

@naheel0 naheel0 commented Apr 3, 2026

This PR implements a comprehensive multi-step README generation pipeline that solves the token limit issues described in issue #101.

Problem Solved

Solution

  • Section-by-section generation approach (800 tokens per section)
  • Automatic retry logic with fallback mechanisms
  • Smart repository analysis with token-conscious filtering
  • Dynamic section planning based on project type
  • Concurrent processing with dependency management

Key Features

  • Eliminates token limit truncation issues
  • 95%+ complete README generation regardless of repo size
  • Enhanced error handling and recovery
  • Backward compatible API
  • Comprehensive documentation included

Technical Details

  • Added multi-step-readme-generator.ts with full pipeline
  • Enhanced API route with detailed statistics
  • Original implementation backed up for safety
  • Complete integration guide in docs/

Closes #101

…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
@naheel0 naheel0 requested a review from adithyanmkd as a code owner April 3, 2026 13:32
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
readme-gen-ai Ready Ready Preview, Comment Apr 3, 2026 5:23pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Enhanced README generation with improved multi-phase pipeline supporting advanced configuration (retries, concurrency, token management)
    • Added SEO improvements with sitemap and crawler configuration
  • Documentation

    • Published comprehensive integration guide with step-by-step setup, configuration options, troubleshooting, and performance optimization examples
  • Refactor

    • Updated API response structure with detailed generation statistics and enhanced metadata output

Walkthrough

Replaces the single-pass README generator with a multi-step pipeline: repository analysis, section planning, per-section generation (with retries/continuation), and assembly. Adds MultiStepReadmeGenerator, request wiring, a detailed integration guide, and sitemap/robots files. No exported API declarations were removed.

Changes

Cohort / File(s) Summary
Multi‑step generator
src/lib/multi-step-readme-generator.ts
New end-to-end pipeline: RepositoryAnalyzer (Octokit fetches), SectionPlanner (dependency-aware ordering), SectionGenerator (per-section prompts, retries, continuation), ReadmeAssembler, MultiStepReadmeGenerator, handleReadmeGeneration, and typed result/stats shapes.
API route refactor
src/app/api/generate/route.ts
Rewrote POST handler to accept NextRequest, validate githubUrl, instantiate/configure MultiStepReadmeGenerator (env-driven), call generateReadme, and return structured JSON (success/markdown/stats/metadata) or error payloads; removed single-pass Octokit/prompt flow.
Legacy backup
src/app/api/generate/route.ts.backup
Preserves original single-pass Gemini-based implementation (Octokit metadata/contents fetch, single large prompt, code-fence stripping) for reference/rollback.
Docs / Integration guide
docs/multi-step-integration-guide.md
New comprehensive guide covering setup, env vars, example route integration, MultiStepReadmeGenerator options, custom section planning, troubleshooting, performance patterns (Redis/Bull/SSE), testing, and API reference.
Frontend small change
src/components/Generator/SearchInput.tsx
Externalized hardcoded languages to SUPPORTED_LANGUAGES import; language select now uses shared constant.
Static site files
public/robots.txt, public/sitemap-0.xml, public/sitemap.xml
Added robots.txt and sitemap(s) for crawler configuration and indexing.

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}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

area: ai-logic, area: backend

Suggested reviewers

  • adithyanmkd
  • jaseel0

Poem

🐇 Hopping through code with a curious cheer,

Sections planned, one by one, now clear.
Analyzer sniffs, Planner draws the chart,
Gemini sings sections, each playing its part.
README stitched — a tidy work of art. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: implementing a multi-step README generation pipeline to resolve token limit issues (issue #101).
Description check ✅ Passed The description is well-related to the changeset, clearly explaining the problem, solution, key features, and technical details of the multi-step pipeline implementation.
Linked Issues check ✅ Passed The PR comprehensively implements all coding objectives from issue #101: multi-step section planning [#101], section-wise generation with token limits [#101], retry logic with fallback [#101], token-conscious repository analysis [#101], and final README assembly [#101].
Out of Scope Changes check ✅ Passed All changes are directly related to the multi-step pipeline objectives: core pipeline implementation (multi-step-readme-generator.ts), API route updates, documentation/guide, SEO additions (sitemap/robots.txt), backup of original code, and refactoring SearchInput.tsx to use externalized constants.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/multi-step-readme-generation

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (1)
src/lib/multi-step-readme-generator.ts (1)

96-131: Consider reusing the existing getRepoContents helper from src/lib/octokit.ts.

RepositoryAnalyzer.getRepositoryContents duplicates logic from the canonical getRepoContents helper 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6441193 and 8246ab4.

📒 Files selected for processing (4)
  • docs/multi-step-integration-guide.md
  • src/app/api/generate/route.ts
  • src/app/api/generate/route.ts.backup
  • src/lib/multi-step-readme-generator.ts

Comment on lines +26 to +32
```typescript
import { handleReadmeGeneration } from '@/lib/multi-step-readme-generator';

export async function POST(request: Request) {
return handleReadmeGeneration(request);
}
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
```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.

Comment on lines +63 to +65
if (result.success) {
setReadme(result.readme);
setGenerationStats(result.stats);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +127 to +132
const result = await assembler.generateCompleteReadme(
metadata,
structure,
customSections
);
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +392 to +398
// Override assembler to stream results
const originalAssembler = generator.assembler;
generator.assembler.generateSectionsInBatches = async (...args) => {
// Stream each section as it's completed
// Implementation details...
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +14 to +15
import { GoogleGenerativeAI } from "@google/generative-ai";
import { Octokit } from "@octokit/rest";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +162 to +211
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}`);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +269 to +276
return (
relevantFiles.some((file) =>
lowerFilename.includes(file.toLowerCase()),
) ||
relevantExtensions.some((ext) => lowerFilename.endsWith(ext)) ||
lowerFilename.startsWith(".")
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Comment on lines +314 to +348
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,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested 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,
};
}
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.

Comment on lines +786 to +795
) {
sections.push({
id: "testing",
title: "Testing",
priority: "low",
order: 8.5,
estimatedTokens: 300,
dependencies: ["development"],
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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:

  1. Making testing depend on usage instead, or
  2. 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.

Suggested change
) {
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
@naheel0 naheel0 force-pushed the feature/multi-step-readme-generation branch from 8246ab4 to 6456916 Compare April 3, 2026 14:35
…ntrolled format string'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
src/app/api/generate/route.ts (1)

77-89: ⚠️ Potential issue | 🟡 Minor

Use constant log messages for the user-provided URL.

These logs still interpolate githubUrl into 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 | 🟠 Major

Replace 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 reads name, path, and type.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8246ab4 and f954519.

📒 Files selected for processing (5)
  • public/robots.txt
  • public/sitemap-0.xml
  • public/sitemap.xml
  • src/app/api/generate/route.ts
  • src/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

Comment on lines +179 to +188
// 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),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
// 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.

Comment on lines +627 to +676
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: [],
},
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +1248 to +1253
// Check if content ends abruptly without proper markdown closure
const endsAbruptly =
!content.trim().endsWith(".") &&
!content.trim().endsWith("\n") &&
content.length > 100;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
// 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.

Comment on lines +1341 to +1348
return {
success: successfulSections > 0,
readme,
sectionsGenerated: successfulSections,
sectionsTotal: optimizedSections.length,
errors,
tokensUsed: totalTokens,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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).

Comment on lines +1418 to +1435
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);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/app/api/generate/route.ts (1)

127-137: Consider sanitizing the error message in production.

Exposing error.message to 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

📥 Commits

Reviewing files that changed from the base of the PR and between f954519 and 06a6d63.

📒 Files selected for processing (1)
  • src/app/api/generate/route.ts

naheel0 added 2 commits April 3, 2026 22:29
- 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
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (6)
src/lib/multi-step-readme-generator.ts (5)

183-188: ⚠️ Potential issue | 🟡 Minor

Replace any[] with the defined GitHubContentItem[] type to fix CI failure.

The ESLint @typescript-eslint/no-explicit-any error is blocking CI. The GitHubContentItem type 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 | 🟡 Minor

Truncation heuristic has false-positive potential.

The endsAbruptly check (lines 1282-1285) uses content.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 | 🟠 Major

Add a "Tech Stack" base section to meet issue #101 requirements.

The PR objectives from issue #101 explicitly 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 | 🟠 Major

Dependency batching marks sections as processed prematurely.

processed.add(section.id) on line 1466 happens while building the batch, so dependency chains like header -> description -> features can 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 | 🟠 Major

Success flag may mislead clients when required sections fail.

The current logic success: successfulSections > 0 returns true even if only one optional section succeeded while all critical sections failed. Since src/app/api/generate/route.ts uses 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 | 🟡 Minor

Validate GEMINI_API_KEY before 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 using SupportedLanguage type for stronger type safety.

The language state uses string type while a stricter SupportedLanguage type 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 SearchInputProps interface to use SupportedLanguage instead of string for 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 shared getOctokit() utility for consistency.

The codebase has a shared Octokit factory at src/lib/octokit.ts that handles authentication via process.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/main would capture tree in the repo field, though the .git replacement 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 at src/lib/gemini.ts uses "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—yet SectionGenerator uses an older model that may produce lower-quality READMEs and hit token limits more easily.

Additionally, SectionGenerator does not apply the safety settings configured in gemini.ts, creating another inconsistency in content policy.

Recommend:

  1. Update "gemini-1.5-pro" to "gemini-2.5-flash" to align with the shared utility and benefit from improved capabilities.
  2. Extract the model name to a shared constant to prevent future divergence.
  3. Consider applying the safety settings from gemini.ts if they should apply uniformly across the application.

Note: Direct reuse of getGeminiModel() is not straightforward since SectionGenerator accepts apiKey as 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.log doesn't use format strings like printf, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 06a6d63 and bec5bfd.

📒 Files selected for processing (4)
  • public/sitemap-0.xml
  • src/app/api/generate/route.ts
  • src/components/Generator/SearchInput.tsx
  • src/lib/multi-step-readme-generator.ts
✅ Files skipped from review due to trivial changes (1)
  • public/sitemap-0.xml

Comment on lines +1743 to +1799
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 },
);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if handleReadmeGeneration is imported/used anywhere
echo "=== Searching for handleReadmeGeneration usage ==="
rg "handleReadmeGeneration" --type ts -C 2

Repository: 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 -10

Repository: 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.

Comment on lines +1757 to +1768
// 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,
},
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚨 Issue: Incomplete README Generation Due to Single-Pass AI Request

2 participants