Skip to content

Commit 5f45c10

Browse files
feat: add --fast mode with Sonnet, demo video
- Fast mode inlines file contents, no tool calls needed - Schema-aligned JSON prompt for reliable parsing - 52s analysis time with claude-sonnet-4 (vs 3m+ with opus) - Added demo-sonnet.mp4 showing end-to-end run
1 parent 251b76a commit 5f45c10

6 files changed

Lines changed: 291 additions & 9 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ That's it. You get 12+ interconnected markdown files covering everything a new c
4747

4848
<div align="center">
4949

50-
<!-- Demo GIF: Generate with `vhs media/demo.tape` -->
51-
<!-- ![Repo Bootcamp Demo](https://raw.githubusercontent.com/Arthur742Ramos/repo-bootcamp/main/media/demo.gif) -->
50+
https://github.com/Arthur742Ramos/repo-bootcamp/raw/main/media/demo-sonnet.mp4
5251

5352
*Generate comprehensive onboarding docs in under 60 seconds*
5453

media/demo-sonnet.mp4

942 KB
Binary file not shown.

media/demo-sonnet.tape

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Demo: RepoBootcamp with Sonnet --fast mode
2+
Output media/demo-sonnet.mp4
3+
Set FontSize 14
4+
Set Width 1200
5+
Set Height 700
6+
Set Theme "Catppuccin Mocha"
7+
Set TypingSpeed 40ms
8+
Set Shell zsh
9+
10+
# Start with clean slate
11+
Hide
12+
Type "cd /Users/arthur/Desktop/RepoBootcamp && rm -rf /tmp/demo-output"
13+
Enter
14+
Sleep 500ms
15+
Show
16+
17+
# Title
18+
Type "# RepoBootcamp - Generate onboarding docs in under 1 minute"
19+
Enter
20+
Sleep 1.5s
21+
22+
# Run the command
23+
Type "node dist/index.js https://github.com/sindresorhus/escape-string-regexp -o /tmp/demo-output --fast --model claude-sonnet-4"
24+
Enter
25+
Sleep 65s
26+
27+
# Show generated files
28+
Type "ls -la /tmp/demo-output/"
29+
Enter
30+
Sleep 2s
31+
32+
# Show the main bootcamp doc
33+
Type "head -60 /tmp/demo-output/BOOTCAMP.md"
34+
Enter
35+
Sleep 4s
36+
37+
Type "# Done! Full onboarding kit in under 1 minute 🚀"
38+
Enter
39+
Sleep 2s

src/agent.ts

Lines changed: 248 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import { CopilotClient, SessionEvent } from "@github/copilot-sdk";
77
import chalk from "chalk";
8+
import * as fs from "fs";
9+
import * as path from "path";
810
import type { RepoFacts, ScanResult, RepoInfo, BootcampOptions } from "./types.js";
911
import { getRepoTools, setToolContext, clearToolContext } from "./tools.js";
1012
import { validateRepoFacts, getMissingFieldsSummary, type ValidatedRepoFacts } from "./schema.js";
@@ -161,6 +163,185 @@ Include 2-4 codeExamples showing key patterns/usage (short snippets of 5-15 line
161163
REMEMBER: Limit tool calls. After reading key files, produce output immediately. Don't over-research.`;
162164
}
163165

166+
/**
167+
* Create a fast analysis prompt with inline file contents (no tools needed)
168+
*/
169+
function createFastAnalysisPrompt(
170+
repoPath: string,
171+
repoInfo: RepoInfo,
172+
scanResult: ScanResult,
173+
options: BootcampOptions
174+
): string {
175+
// Read key files inline
176+
const keyFiles = ["README.md", "readme.md", "package.json", "pyproject.toml", "Cargo.toml", "go.mod"];
177+
const inlineContents: string[] = [];
178+
179+
for (const filename of keyFiles) {
180+
const filePath = path.join(repoPath, filename);
181+
if (fs.existsSync(filePath)) {
182+
try {
183+
const content = fs.readFileSync(filePath, "utf-8").substring(0, 5000);
184+
inlineContents.push(`### ${filename}\n\`\`\`\n${content}\n\`\`\``);
185+
} catch (e) {
186+
// Skip unreadable files
187+
}
188+
}
189+
}
190+
191+
// Also try to read main entry point
192+
const entryPoints = ["index.ts", "index.js", "src/index.ts", "src/index.js", "main.py", "lib.rs", "main.go"];
193+
for (const entry of entryPoints) {
194+
const filePath = path.join(repoPath, entry);
195+
if (fs.existsSync(filePath)) {
196+
try {
197+
const content = fs.readFileSync(filePath, "utf-8").substring(0, 3000);
198+
inlineContents.push(`### ${entry}\n\`\`\`\n${content}\n\`\`\``);
199+
break; // Only include first found entry point
200+
} catch (e) {
201+
// Skip
202+
}
203+
}
204+
}
205+
206+
const fileList = scanResult.files
207+
.filter((f) => !f.isDirectory)
208+
.slice(0, 30)
209+
.map((f) => f.path)
210+
.join("\n");
211+
212+
const cmdList = scanResult.commands.map((c) => `- ${c.name}: ${c.command}`).join("\n");
213+
214+
return `Analyze this repository and produce a comprehensive onboarding kit.
215+
216+
## Repository
217+
- Name: ${repoInfo.fullName}
218+
- URL: ${repoInfo.url}
219+
- Branch: ${repoInfo.branch}
220+
221+
## Pre-detected Information
222+
Languages: ${scanResult.stack.languages.join(", ") || "Unknown"}
223+
Frameworks: ${scanResult.stack.frameworks.join(", ") || "None detected"}
224+
Build System: ${scanResult.stack.buildSystem || "Unknown"}
225+
Has CI: ${scanResult.stack.hasCi}
226+
227+
## File Tree (first 30 files)
228+
${fileList}
229+
230+
## Detected Commands
231+
${cmdList || "None detected"}
232+
233+
## Key File Contents (READ THESE - no tools available)
234+
${inlineContents.join("\n\n")}
235+
236+
---
237+
238+
Based on the above information, produce a JSON object. Follow this EXACT structure with these EXACT field names and enum values:
239+
240+
## CRITICAL SCHEMA REQUIREMENTS:
241+
242+
### Enum Values (use EXACTLY these strings):
243+
- confidence: "high", "medium", or "low"
244+
- entrypoints[].type: "main", "binary", "server", "cli", "web", or "library"
245+
- firstTasks[].difficulty: "beginner", "intermediate", or "advanced"
246+
- firstTasks[].category: "bug-fix", "test", "docs", "refactor", or "feature"
247+
248+
### Required Fields:
249+
- repoName, purpose, description (all strings)
250+
- stack.languages, stack.frameworks (arrays of strings)
251+
- stack.buildSystem (string), stack.packageManager (string or null)
252+
- stack.hasDocker, stack.hasCi (booleans)
253+
- quickstart.prerequisites, quickstart.steps (arrays of strings)
254+
- quickstart.commands (array of {name, command, source})
255+
- structure.keyDirs (array of {path, purpose})
256+
- structure.entrypoints (array of {path, type, description})
257+
- structure.testDirs, structure.docsDirs (arrays of strings)
258+
- ci.workflows (array of {name, file, triggers, mainSteps})
259+
- ci.mainChecks (array of strings)
260+
- contrib.howToAddFeature, contrib.howToAddTest (arrays of strings)
261+
- architecture.overview (string)
262+
- architecture.components (array of {name, description, directory})
263+
- firstTasks (array with title, description, difficulty, category, files, why)
264+
265+
\`\`\`json
266+
{
267+
"repoName": "${repoInfo.fullName}",
268+
"purpose": "one-line description of what this repo does",
269+
"description": "2-3 sentence detailed description",
270+
"sources": ["README.md", "package.json"],
271+
"confidence": "high",
272+
"stack": {
273+
"languages": ["TypeScript"],
274+
"frameworks": ["Node.js"],
275+
"buildSystem": "npm",
276+
"packageManager": "npm",
277+
"hasDocker": false,
278+
"hasCi": true
279+
},
280+
"quickstart": {
281+
"prerequisites": ["Node.js >= 18"],
282+
"steps": ["Clone the repository", "Run npm install", "Run npm test"],
283+
"commands": [{"name": "install", "command": "npm install", "source": "package.json"}],
284+
"commonErrors": [{"error": "Missing dependencies", "fix": "Run npm install"}],
285+
"sources": ["README.md"]
286+
},
287+
"structure": {
288+
"keyDirs": [{"path": "src", "purpose": "Source code", "keyFiles": ["index.ts"]}],
289+
"entrypoints": [{"path": "src/index.ts", "type": "library", "description": "Main export"}],
290+
"testDirs": ["test"],
291+
"docsDirs": [],
292+
"sources": ["package.json"]
293+
},
294+
"ci": {
295+
"workflows": [{"name": "CI", "file": ".github/workflows/main.yml", "triggers": ["push", "pull_request"], "mainSteps": ["test", "lint"]}],
296+
"mainChecks": ["Tests must pass"],
297+
"sources": [".github/workflows/main.yml"]
298+
},
299+
"contrib": {
300+
"howToAddFeature": ["Create a new file in src/", "Export from index.ts", "Add tests"],
301+
"howToAddTest": ["Add test file in test/ directory", "Run npm test"],
302+
"codeStyle": "Standard JavaScript style",
303+
"sources": ["README.md"]
304+
},
305+
"architecture": {
306+
"overview": "Simple single-purpose utility library",
307+
"components": [{"name": "Core", "description": "Main functionality", "directory": "src"}],
308+
"dataFlow": "Input -> Process -> Output",
309+
"keyAbstractions": [{"name": "Main function", "description": "Primary export"}],
310+
"codeExamples": [{"title": "Basic usage", "file": "src/index.ts", "code": "import x from 'lib'", "explanation": "Import and use"}],
311+
"sources": ["src/index.ts"]
312+
},
313+
"firstTasks": [
314+
{
315+
"title": "Add a test case",
316+
"description": "Add a new test case for edge cases",
317+
"difficulty": "beginner",
318+
"category": "test",
319+
"files": ["test/test.js"],
320+
"why": "Good first contribution to understand the codebase"
321+
}
322+
],
323+
"runbook": {
324+
"applicable": false,
325+
"deploySteps": [],
326+
"observability": [],
327+
"incidents": [],
328+
"sources": []
329+
}
330+
}
331+
\`\`\`
332+
333+
Focus: ${options.focus}
334+
Target audience: ${options.audience}
335+
336+
INSTRUCTIONS:
337+
1. Replace the example values above with actual data from this repository
338+
2. Provide at least 3-5 firstTasks with varying difficulty levels
339+
3. Set runbook.applicable = false for libraries that aren't deployed as services
340+
4. Use ONLY the exact enum values listed in the CRITICAL SCHEMA REQUIREMENTS section
341+
342+
IMPORTANT: Return ONLY the JSON object, no other text or markdown.`;
343+
}
344+
164345
/**
165346
* Parse the JSON response from Copilot and validate against schema
166347
*/
@@ -248,9 +429,13 @@ const PREFERRED_MODELS = [
248429
async function createSessionWithFallback(
249430
client: CopilotClient,
250431
config: Parameters<CopilotClient["createSession"]>[0],
251-
verbose: boolean = false
432+
verbose: boolean = false,
433+
overrideModel?: string
252434
): Promise<{ session: Awaited<ReturnType<CopilotClient["createSession"]>>; model: string }> {
253-
for (const model of PREFERRED_MODELS) {
435+
// If a specific model is requested, try it first
436+
const modelsToTry = overrideModel ? [overrideModel, ...PREFERRED_MODELS] : PREFERRED_MODELS;
437+
438+
for (const model of modelsToTry) {
254439
try {
255440
if (verbose) {
256441
console.log(chalk.gray(`Trying model: ${model}`));
@@ -304,7 +489,64 @@ export async function analyzeRepo(
304489
startTime: Date.now(),
305490
};
306491

307-
// Set up tool context
492+
const client = new CopilotClient();
493+
494+
// Fast mode: no tools, inline file contents
495+
if (options.fast) {
496+
try {
497+
const { session, model } = await createSessionWithFallback(
498+
client,
499+
{
500+
streaming: true,
501+
systemMessage: { content: "You are an expert software architect. Analyze repositories and produce JSON output." },
502+
// No tools in fast mode
503+
},
504+
options.verbose,
505+
options.model
506+
);
507+
508+
stats.model = model;
509+
console.log(chalk.blue(`\nUsing model: ${model}`));
510+
console.log(chalk.yellow(`⚡ Fast mode: no tools, inline file contents\n`));
511+
512+
const prompt = createFastAnalysisPrompt(repoPath, repoInfo, scanResult, options);
513+
let fullResponse = "";
514+
515+
session.on((event: SessionEvent) => {
516+
stats.totalEvents++;
517+
if (event.type === "assistant.message_delta") {
518+
const delta = event.data.deltaContent;
519+
if (delta) {
520+
fullResponse += delta;
521+
if (options.verbose) {
522+
process.stdout.write(delta);
523+
}
524+
}
525+
}
526+
});
527+
528+
await session.sendAndWait({ prompt }, 300000);
529+
stats.responseLength = fullResponse.length;
530+
stats.endTime = Date.now();
531+
532+
const { facts, errors, warnings } = parseAndValidateRepoFacts(fullResponse, options.verbose);
533+
534+
if (!facts) {
535+
throw new Error(`Analysis failed: ${errors?.join(", ") || "Unknown error"}`);
536+
}
537+
538+
if (warnings?.length) {
539+
console.log(chalk.yellow("\n[Warnings]"));
540+
warnings.forEach(w => console.log(chalk.yellow(` - ${w}`)));
541+
}
542+
543+
return { facts: facts as RepoFacts, stats };
544+
} catch (error: any) {
545+
throw new Error(`Fast analysis failed: ${error.message}`);
546+
}
547+
}
548+
549+
// Standard mode with tools
308550
setToolContext({
309551
repoPath,
310552
verbose: options.verbose,
@@ -324,8 +566,6 @@ export async function analyzeRepo(
324566
},
325567
});
326568

327-
const client = new CopilotClient();
328-
329569
try {
330570
// Get tools for the session
331571
const tools = getRepoTools();
@@ -338,7 +578,8 @@ export async function analyzeRepo(
338578
systemMessage: { content: SYSTEM_PROMPT },
339579
tools,
340580
},
341-
options.verbose
581+
options.verbose,
582+
options.model
342583
);
343584

344585
stats.model = model;
@@ -432,7 +673,7 @@ No markdown, no explanations, just the JSON object starting with { and ending wi
432673
- firstTasks: [{ title, description, difficulty, category, files, why }]`;
433674

434675
fullResponse = "";
435-
await session.sendAndWait({ prompt: retryPrompt }, 120000);
676+
await session.sendAndWait({ prompt: retryPrompt }, 300000);
436677
result = parseAndValidateRepoFacts(fullResponse, options.verbose);
437678
}
438679

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ program
553553
.option("--dry-run", "Preview issues without creating (use with --create-issues)")
554554
.option("-s, --style <style>", "Output style: startup, enterprise, oss, devops", "oss")
555555
.option("--render-diagrams [format]", "Render diagrams.mmd to SVG/PNG (requires mermaid-cli)", "svg")
556+
.option("--fast", "Fast mode: inline key files, skip tools, much faster (~15-30s)")
556557
.action(async (repoUrl: string, opts) => {
557558
const options: BootcampOptions = {
558559
branch: opts.branch,
@@ -566,6 +567,7 @@ program
566567
keepTemp: opts.keepTemp || false,
567568
jsonOnly: opts.jsonOnly || false,
568569
stats: opts.stats || false,
570+
fast: opts.fast || false,
569571
// New options
570572
interactive: opts.interactive || false,
571573
transcript: opts.transcript || false,

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface BootcampOptions {
1515
keepTemp?: boolean;
1616
jsonOnly?: boolean;
1717
stats?: boolean;
18+
fast?: boolean;
1819
// New features
1920
interactive?: boolean;
2021
transcript?: boolean;

0 commit comments

Comments
 (0)