Allow manual context management (#376)

This commit is contained in:
Will Chen
2025-06-10 13:52:20 -07:00
committed by GitHub
parent e7941bc6f7
commit 534cbad909
55 changed files with 3296 additions and 114 deletions

View File

@@ -4,6 +4,9 @@ import path from "node:path";
import { isIgnored } from "isomorphic-git";
import log from "electron-log";
import { IS_TEST_BUILD } from "../ipc/utils/test_utils";
import { glob } from "glob";
import { AppChatContext } from "../lib/schemas";
import { readSettings } from "@/main/settings";
const logger = log.scope("utils/codebase");
@@ -315,15 +318,31 @@ ${content}
}
}
export type CodebaseFile = {
path: string;
content: string;
force?: boolean;
};
/**
* Extract and format codebase files as a string to be included in prompts
* @param appPath - Path to the codebase to extract
* @returns Object containing formatted output and individual files
*/
export async function extractCodebase(appPath: string): Promise<{
export async function extractCodebase({
appPath,
chatContext,
}: {
appPath: string;
chatContext: AppChatContext;
}): Promise<{
formattedOutput: string;
files: { path: string; content: string }[];
files: CodebaseFile[];
}> {
const settings = readSettings();
const isSmartContextEnabled =
settings?.enableDyadPro && settings?.enableProSmartFilesContextMode;
try {
await fsAsync.access(appPath);
} catch {
@@ -335,14 +354,67 @@ export async function extractCodebase(appPath: string): Promise<{
const startTime = Date.now();
// Collect all relevant files
const files = await collectFiles(appPath, appPath);
let files = await collectFiles(appPath, appPath);
// Collect files from contextPaths and smartContextAutoIncludes
const { contextPaths, smartContextAutoIncludes } = chatContext;
const includedFiles = new Set<string>();
const autoIncludedFiles = new Set<string>();
// Add files from contextPaths
if (contextPaths && contextPaths.length > 0) {
for (const p of contextPaths) {
const pattern = createFullGlobPath({
appPath,
globPath: p.globPath,
});
const matches = await glob(pattern, {
nodir: true,
absolute: true,
ignore: "**/node_modules/**",
});
matches.forEach((file) => {
const normalizedFile = path.normalize(file);
includedFiles.add(normalizedFile);
});
}
}
// Add files from smartContextAutoIncludes
if (
isSmartContextEnabled &&
smartContextAutoIncludes &&
smartContextAutoIncludes.length > 0
) {
for (const p of smartContextAutoIncludes) {
const pattern = createFullGlobPath({
appPath,
globPath: p.globPath,
});
const matches = await glob(pattern, {
nodir: true,
absolute: true,
});
matches.forEach((file) => {
const normalizedFile = path.normalize(file);
autoIncludedFiles.add(normalizedFile);
includedFiles.add(normalizedFile); // Also add to included files
});
}
}
// Only filter files if contextPaths are provided
// If only smartContextAutoIncludes are provided, keep all files and just mark auto-includes as forced
if (contextPaths && contextPaths.length > 0) {
files = files.filter((file) => includedFiles.has(path.normalize(file)));
}
// Sort files by modification time (oldest first)
// This is important for cache-ability.
const sortedFiles = await sortFilesByModificationTime(files);
const sortedFiles = await sortFilesByModificationTime([...new Set(files)]);
// Format files and collect individual file contents
const filesArray: { path: string; content: string }[] = [];
const filesArray: CodebaseFile[] = [];
const formatPromises = sortedFiles.map(async (file) => {
const formattedContent = await formatFile(file, appPath);
@@ -352,6 +424,9 @@ export async function extractCodebase(appPath: string): Promise<{
// Why? Normalize Windows-style paths which causes lots of weird issues (e.g. Git commit)
.split(path.sep)
.join("/");
const isForced = autoIncludedFiles.has(path.normalize(file));
const fileContent = isOmittedFile(relativePath)
? OMITTED_FILE_CONTENT
: await readFileWithCache(file);
@@ -359,6 +434,7 @@ export async function extractCodebase(appPath: string): Promise<{
filesArray.push({
path: relativePath,
content: fileContent,
force: isForced,
});
}
@@ -413,3 +489,15 @@ async function sortFilesByModificationTime(files: string[]): Promise<string[]> {
// Sort by modification time (oldest first)
return fileStats.sort((a, b) => a.mtime - b.mtime).map((item) => item.file);
}
function createFullGlobPath({
appPath,
globPath,
}: {
appPath: string;
globPath: string;
}): string {
// By default the glob package treats "\" as an escape character.
// We want the path to use forward slash for all platforms.
return `${appPath.replace(/\\/g, "/")}/${globPath}`;
}