Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ program
.description("Scan project and create docs/scan.md (auto-included in every LLM request)")
.option("-d, --dir <path>", "Project directory", ".")
.option("-f, --force", "Force re-scan even if scan.md exists")
.option("-q, --quick", "Skip AI analysis (fast, no LLM call — same as autoInit)")
.action(scanCommand);

// ── Create Project (Legacy) ───────────────────────────────────
Expand Down
39 changes: 33 additions & 6 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface InitOptions {
dir?: string;
quiet?: boolean;
skipScan?: boolean;
/** Run a quick scan (no AI analysis) — fast, gives agent immediate project knowledge */
quickScan?: boolean;
}

/**
Expand Down Expand Up @@ -151,10 +153,12 @@ export default {
}

// 7. Run project scan (creates docs/scan.md)
// Quick scan = local analysis only (fast, no LLM call) — gives agent immediate project knowledge.
// Full scan (no quickScan flag) = quick scan + AI-powered architecture analysis.
if (!options.skipScan) {
if (!quiet) console.log();
try {
await scanProject(dir, false);
await scanProject(dir, false, options.quickScan);
} catch {
if (!quiet) console.log(` ${colors.warning(icons.warning)} ${colors.dim("Scan skipped (can be run later with 'ua scan')")}`);
}
Expand All @@ -178,12 +182,35 @@ export default {

/**
* Auto-init: Run silently on first chat session if .ultraagent/ doesn't exist.
* Only creates the directory structure — skips the scan (too slow for auto-init).
* Creates the directory structure AND a quick scan (no AI analysis) so the
* agent has immediate project knowledge (tree, deps, scripts, key files, ...).
* Run `ua scan --force` later for the full AI-powered architecture analysis.
*/
export async function autoInit(projectDir: string): Promise<void> {
if (isInitialized(projectDir)) return;
const scanPath = path.join(projectDir, "docs", "scan.md");
const hasStructure = isInitialized(projectDir);
const hasScan = fs.existsSync(scanPath);

console.log(colors.dim(`\n ${icons.info} First time in this project — setting up UltraAgent...`));
await initProject({ dir: projectDir, quiet: true, skipScan: true });
console.log(colors.dim(` ${icons.success} .ultraagent/ created. Use 'ua scan' for full project context.\n`));
if (hasStructure && hasScan) return;

if (!hasStructure) {
console.log(colors.dim(`\n ${icons.info} First time in this project — setting up UltraAgent...`));
}
if (!hasScan) {
console.log(colors.dim(` ${icons.info} Scanning project for immediate context (quick scan, no LLM)...`));
}

await initProject({
dir: projectDir,
quiet: true,
// Only run the quick scan if scan.md doesn't exist yet
skipScan: hasScan,
quickScan: true,
});

if (!hasScan) {
console.log(colors.dim(` ${icons.success} docs/scan.md created — agent has project context. Run 'ua scan --force' for deep AI analysis.\n`));
} else if (!hasStructure) {
console.log(colors.dim(` ${icons.success} .ultraagent/ created.\n`));
}
}
43 changes: 24 additions & 19 deletions src/commands/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { getConfig } from "../utils/config.js";
interface ScanOptions {
dir?: string;
force?: boolean;
/** Skip the AI analysis phase — much faster, used by autoInit */
quick?: boolean;
}

/**
Expand All @@ -30,13 +32,14 @@ export async function scanCommand(options: ScanOptions): Promise<void> {

heading("Project Scan");

await scanProject(dir, options.force);
await scanProject(dir, options.force, options.quick);
}

/**
* Core scan logic — can be called from other commands (e.g., /new).
* Pass `quick: true` to skip the AI analysis phase (much faster, no LLM needed).
*/
export async function scanProject(dir: string, force?: boolean): Promise<string> {
export async function scanProject(dir: string, force?: boolean, quick?: boolean): Promise<string> {
const scanPath = path.join(dir, "docs", "scan.md");

// Check if scan already exists
Expand Down Expand Up @@ -65,25 +68,27 @@ export async function scanProject(dir: string, force?: boolean): Promise<string>
const details = await gatherProjectDetails(dir);
detailSpinner.succeed(colors.success("Details gathered"));

// Phase 3: AI-powered deep analysis
console.log();
statusLine("🤖", "AI analysis", "Generating comprehensive documentation...");
// Phase 3: AI-powered deep analysis (skipped in quick mode)
let aiAnalysis = "";
if (!quick) {
console.log();
statusLine("🤖", "AI analysis", "Generating comprehensive documentation...");

const config = getConfig();
const analysisPrompt = buildScanPrompt(dir, info, tree, details);
const config = getConfig();
const analysisPrompt = buildScanPrompt(dir, info, tree, details);

let aiAnalysis = "";
try {
aiAnalysis = await runAgent(
{ mode: "analyze", config, projectDir: dir, skipScanInjection: true },
analysisPrompt,
{
onToken: (token) => process.stdout.write(colors.dim(token)),
}
);
} catch (err: unknown) {
const detail = err instanceof Error ? err.message : String(err);
console.log(colors.warning(` ${icons.warning} AI analysis skipped: ${detail}`));
try {
aiAnalysis = await runAgent(
{ mode: "analyze", config, projectDir: dir, skipScanInjection: true },
analysisPrompt,
{
onToken: (token) => process.stdout.write(colors.dim(token)),
}
);
} catch (err: unknown) {
const detail = err instanceof Error ? err.message : String(err);
console.log(colors.warning(` ${icons.warning} AI analysis skipped: ${detail}`));
}
}

// Phase 4: Generate scan.md
Expand Down