Unverified 提交 5db0b044 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Support exclude paths in manual context management (#774)

上级 74ada705
...@@ -74,3 +74,64 @@ test("manage context - smart context - auto-includes only", async ({ po }) => { ...@@ -74,3 +74,64 @@ test("manage context - smart context - auto-includes only", async ({ po }) => {
await po.snapshotServerDump("request"); await po.snapshotServerDump("request");
}); });
test("manage context - exclude paths", async ({ po }) => {
await po.setUp();
await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
// Add some include paths first
await dialog.addManualContextFile("src/**/*.ts");
await dialog.addManualContextFile("manual/**");
// Add exclude paths
await dialog.addExcludeContextFile("src/components/**");
await dialog.addExcludeContextFile("manual/exclude/**");
await po.snapshotDialog();
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages", { name: "exclude-paths-basic" });
// Test that exclude paths take precedence over include paths
const dialog2 = await po.openContextFilesPicker();
await dialog2.removeExcludeContextFile(); // Remove src/components/**
await dialog2.addExcludeContextFile("src/**"); // This should exclude everything from src
await po.snapshotDialog();
await dialog2.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages", {
name: "exclude-paths-precedence",
});
});
test("manage context - exclude paths with smart context", async ({ po }) => {
await po.setUpDyadPro();
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" });
await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
// Add manual context files
await dialog.addManualContextFile("src/**/*.ts");
await dialog.addManualContextFile("manual/**");
// Add smart context auto-includes
await dialog.addAutoIncludeContextFile("a.ts");
await dialog.addAutoIncludeContextFile("exclude/**");
// Add exclude paths that should filter out some of the above
await dialog.addExcludeContextFile("src/components/**");
await dialog.addExcludeContextFile("exclude/exclude.ts");
await po.snapshotDialog();
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages", {
name: "exclude-paths-with-smart-context",
});
});
...@@ -47,6 +47,18 @@ export class ContextFilesPickerDialog { ...@@ -47,6 +47,18 @@ export class ContextFilesPickerDialog {
.first() .first()
.click(); .click();
} }
async addExcludeContextFile(path: string) {
await this.page.getByTestId("exclude-context-files-input").fill(path);
await this.page.getByTestId("exclude-context-files-add-button").click();
}
async removeExcludeContextFile() {
await this.page
.getByTestId("exclude-context-files-remove-button")
.first()
.click();
}
} }
class ProModesDialog { class ProModesDialog {
......
- dialog:
- heading "Codebase Context" [level=3]
- paragraph:
- text: Select the files to use as context.
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use the entire codebase as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
\ No newline at end of file
- dialog:
- heading "Codebase Context" [level=3]
- paragraph:
- text: Select the files to use as context.
- img
- textbox "src/**/*.tsx"
- button "Add"
- text: /src\/\*\*\/\*\.ts 4 files, ~\d+ tokens/
- button:
- img
- text: /manual\/\*\* 3 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- text: /src\/components\/\*\* 2 files, ~\d+ tokens/
- button:
- img
- text: manual/exclude/** 0 files, ~0 tokens
- button:
- img
\ No newline at end of file
- dialog:
- heading "Codebase Context" [level=3]
- paragraph:
- text: Select the files to use as context.
- img
- textbox "src/**/*.tsx"
- button "Add"
- text: /src\/\*\*\/\*\.ts 4 files, ~\d+ tokens/
- button:
- img
- text: /manual\/\*\* 3 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- text: manual/exclude/** 0 files, ~0 tokens
- button:
- img
- text: /src\/\*\* 7 files, ~\d+ tokens/
- button:
- img
\ No newline at end of file
- dialog:
- heading "Codebase Context" [level=3]
- paragraph:
- text: Select the files to use as context.
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- img
- textbox "src/**/*.config.ts"
- button "Add"
\ No newline at end of file
- dialog:
- heading "Codebase Context" [level=3]
- paragraph:
- text: Select the files to use as context.
- img
- textbox "src/**/*.tsx"
- button "Add"
- text: /src\/\*\*\/\*\.ts 4 files, ~\d+ tokens/
- button:
- img
- text: /manual\/\*\* 3 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- img
- textbox "node_modules/**/*"
- button "Add"
- text: /src\/components\/\*\* 2 files, ~\d+ tokens/
- button:
- img
- text: /exclude\/exclude\.ts 1 files, ~\d+ tokens/
- button:
- img
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- img
- textbox "src/**/*.config.ts"
- button "Add"
- text: /a\.ts 1 files, ~\d+ tokens/
- button:
- img
- text: /exclude\/\*\* 2 files, ~\d+ tokens/
- button:
- img
\ No newline at end of file
...@@ -16,29 +16,33 @@ import { ...@@ -16,29 +16,33 @@ import {
} from "./ui/tooltip"; } from "./ui/tooltip";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { useContextPaths } from "@/hooks/useContextPaths"; import { useContextPaths } from "@/hooks/useContextPaths";
import type { ContextPathResult } from "@/lib/schemas";
export function ContextFilesPicker() { export function ContextFilesPicker() {
const { settings } = useSettings(); const { settings } = useSettings();
const { const {
contextPaths, contextPaths,
smartContextAutoIncludes, smartContextAutoIncludes,
excludePaths,
updateContextPaths, updateContextPaths,
updateSmartContextAutoIncludes, updateSmartContextAutoIncludes,
updateExcludePaths,
} = useContextPaths(); } = useContextPaths();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [newPath, setNewPath] = useState(""); const [newPath, setNewPath] = useState("");
const [newAutoIncludePath, setNewAutoIncludePath] = useState(""); const [newAutoIncludePath, setNewAutoIncludePath] = useState("");
const [newExcludePath, setNewExcludePath] = useState("");
const addPath = () => { const addPath = () => {
if ( if (
newPath.trim() === "" || newPath.trim() === "" ||
contextPaths.find((p) => p.globPath === newPath) contextPaths.find((p: ContextPathResult) => p.globPath === newPath)
) { ) {
setNewPath(""); setNewPath("");
return; return;
} }
const newPaths = [ const newPaths = [
...contextPaths.map(({ globPath }) => ({ globPath })), ...contextPaths.map(({ globPath }: ContextPathResult) => ({ globPath })),
{ {
globPath: newPath, globPath: newPath,
}, },
...@@ -49,21 +53,25 @@ export function ContextFilesPicker() { ...@@ -49,21 +53,25 @@ export function ContextFilesPicker() {
const removePath = (pathToRemove: string) => { const removePath = (pathToRemove: string) => {
const newPaths = contextPaths const newPaths = contextPaths
.filter((p) => p.globPath !== pathToRemove) .filter((p: ContextPathResult) => p.globPath !== pathToRemove)
.map(({ globPath }) => ({ globPath })); .map(({ globPath }: ContextPathResult) => ({ globPath }));
updateContextPaths(newPaths); updateContextPaths(newPaths);
}; };
const addAutoIncludePath = () => { const addAutoIncludePath = () => {
if ( if (
newAutoIncludePath.trim() === "" || newAutoIncludePath.trim() === "" ||
smartContextAutoIncludes.find((p) => p.globPath === newAutoIncludePath) smartContextAutoIncludes.find(
(p: ContextPathResult) => p.globPath === newAutoIncludePath,
)
) { ) {
setNewAutoIncludePath(""); setNewAutoIncludePath("");
return; return;
} }
const newPaths = [ const newPaths = [
...smartContextAutoIncludes.map(({ globPath }) => ({ globPath })), ...smartContextAutoIncludes.map(({ globPath }: ContextPathResult) => ({
globPath,
})),
{ {
globPath: newAutoIncludePath, globPath: newAutoIncludePath,
}, },
...@@ -74,11 +82,36 @@ export function ContextFilesPicker() { ...@@ -74,11 +82,36 @@ export function ContextFilesPicker() {
const removeAutoIncludePath = (pathToRemove: string) => { const removeAutoIncludePath = (pathToRemove: string) => {
const newPaths = smartContextAutoIncludes const newPaths = smartContextAutoIncludes
.filter((p) => p.globPath !== pathToRemove) .filter((p: ContextPathResult) => p.globPath !== pathToRemove)
.map(({ globPath }) => ({ globPath })); .map(({ globPath }: ContextPathResult) => ({ globPath }));
updateSmartContextAutoIncludes(newPaths); updateSmartContextAutoIncludes(newPaths);
}; };
const addExcludePath = () => {
if (
newExcludePath.trim() === "" ||
excludePaths.find((p: ContextPathResult) => p.globPath === newExcludePath)
) {
setNewExcludePath("");
return;
}
const newPaths = [
...excludePaths.map(({ globPath }: ContextPathResult) => ({ globPath })),
{
globPath: newExcludePath,
},
];
updateExcludePaths(newPaths);
setNewExcludePath("");
};
const removeExcludePath = (pathToRemove: string) => {
const newPaths = excludePaths
.filter((p: ContextPathResult) => p.globPath !== pathToRemove)
.map(({ globPath }: ContextPathResult) => ({ globPath }));
updateExcludePaths(newPaths);
};
const isSmartContextEnabled = const isSmartContextEnabled =
settings?.enableDyadPro && settings?.enableProSmartFilesContextMode; settings?.enableDyadPro && settings?.enableProSmartFilesContextMode;
...@@ -100,7 +133,10 @@ export function ContextFilesPicker() { ...@@ -100,7 +133,10 @@ export function ContextFilesPicker() {
<TooltipContent>Codebase Context</TooltipContent> <TooltipContent>Codebase Context</TooltipContent>
</Tooltip> </Tooltip>
<PopoverContent className="w-96" align="start"> <PopoverContent
className="w-96 max-h-[80vh] overflow-y-auto"
align="start"
>
<div className="relative space-y-4"> <div className="relative space-y-4">
<div> <div>
<h3 className="font-medium">Codebase Context</h3> <h3 className="font-medium">Codebase Context</h3>
...@@ -153,7 +189,7 @@ export function ContextFilesPicker() { ...@@ -153,7 +189,7 @@ export function ContextFilesPicker() {
<TooltipProvider> <TooltipProvider>
{contextPaths.length > 0 ? ( {contextPaths.length > 0 ? (
<div className="space-y-2"> <div className="space-y-2">
{contextPaths.map((p) => ( {contextPaths.map((p: ContextPathResult) => (
<div <div
key={p.globPath} key={p.globPath}
className="flex items-center justify-between gap-2 rounded-md border p-2" className="flex items-center justify-between gap-2 rounded-md border p-2"
...@@ -197,6 +233,91 @@ export function ContextFilesPicker() { ...@@ -197,6 +233,91 @@ export function ContextFilesPicker() {
)} )}
</TooltipProvider> </TooltipProvider>
<div className="pt-2">
<div>
<h3 className="font-medium">Exclude Paths</h3>
<p className="text-sm text-muted-foreground">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="flex items-center gap-1 cursor-help">
These files will be excluded from the context.{" "}
<InfoIcon className="ml-2 size-4" />
</span>
</TooltipTrigger>
<TooltipContent className="max-w-[300px]">
<p>
Exclude paths take precedence - files that match both
include and exclude patterns will be excluded.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</p>
</div>
<div className="flex w-full max-w-sm items-center space-x-2 mt-4">
<Input
data-testid="exclude-context-files-input"
type="text"
placeholder="node_modules/**/*"
value={newExcludePath}
onChange={(e) => setNewExcludePath(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
addExcludePath();
}
}}
/>
<Button
type="submit"
onClick={addExcludePath}
data-testid="exclude-context-files-add-button"
>
Add
</Button>
</div>
<TooltipProvider>
{excludePaths.length > 0 && (
<div className="space-y-2 mt-4">
{excludePaths.map((p: ContextPathResult) => (
<div
key={p.globPath}
className="flex items-center justify-between gap-2 rounded-md border p-2 border-red-200"
>
<div className="flex flex-1 flex-col overflow-hidden">
<Tooltip>
<TooltipTrigger asChild>
<span className="truncate font-mono text-sm text-red-600">
{p.globPath}
</span>
</TooltipTrigger>
<TooltipContent>
<p>{p.globPath}</p>
</TooltipContent>
</Tooltip>
<span className="text-xs text-muted-foreground">
{p.files} files, ~{p.tokens} tokens
</span>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => removeExcludePath(p.globPath)}
data-testid="exclude-context-files-remove-button"
>
<Trash2 className="size-4" />
</Button>
</div>
</div>
))}
</div>
)}
</TooltipProvider>
</div>
{isSmartContextEnabled && ( {isSmartContextEnabled && (
<div className="pt-2"> <div className="pt-2">
<div> <div>
...@@ -247,7 +368,7 @@ export function ContextFilesPicker() { ...@@ -247,7 +368,7 @@ export function ContextFilesPicker() {
<TooltipProvider> <TooltipProvider>
{smartContextAutoIncludes.length > 0 && ( {smartContextAutoIncludes.length > 0 && (
<div className="space-y-2 mt-4"> <div className="space-y-2 mt-4">
{smartContextAutoIncludes.map((p) => ( {smartContextAutoIncludes.map((p: ContextPathResult) => (
<div <div
key={p.globPath} key={p.globPath}
className="flex items-center justify-between gap-2 rounded-md border p-2" className="flex items-center justify-between gap-2 rounded-md border p-2"
......
...@@ -15,7 +15,12 @@ export function useContextPaths() { ...@@ -15,7 +15,12 @@ export function useContextPaths() {
} = useQuery<ContextPathResults, Error>({ } = useQuery<ContextPathResults, Error>({
queryKey: ["context-paths", appId], queryKey: ["context-paths", appId],
queryFn: async () => { queryFn: async () => {
if (!appId) return { contextPaths: [], smartContextAutoIncludes: [] }; if (!appId)
return {
contextPaths: [],
smartContextAutoIncludes: [],
excludePaths: [],
};
const ipcClient = IpcClient.getInstance(); const ipcClient = IpcClient.getInstance();
return ipcClient.getChatContextResults({ appId }); return ipcClient.getChatContextResults({ appId });
}, },
...@@ -25,9 +30,17 @@ export function useContextPaths() { ...@@ -25,9 +30,17 @@ export function useContextPaths() {
const updateContextPathsMutation = useMutation< const updateContextPathsMutation = useMutation<
unknown, unknown,
Error, Error,
{ contextPaths: GlobPath[]; smartContextAutoIncludes?: GlobPath[] } {
contextPaths: GlobPath[];
smartContextAutoIncludes?: GlobPath[];
excludePaths?: GlobPath[];
}
>({ >({
mutationFn: async ({ contextPaths, smartContextAutoIncludes }) => { mutationFn: async ({
contextPaths,
smartContextAutoIncludes,
excludePaths,
}) => {
if (!appId) throw new Error("No app selected"); if (!appId) throw new Error("No app selected");
const ipcClient = IpcClient.getInstance(); const ipcClient = IpcClient.getInstance();
return ipcClient.setChatContext({ return ipcClient.setChatContext({
...@@ -35,6 +48,7 @@ export function useContextPaths() { ...@@ -35,6 +48,7 @@ export function useContextPaths() {
chatContext: { chatContext: {
contextPaths, contextPaths,
smartContextAutoIncludes: smartContextAutoIncludes || [], smartContextAutoIncludes: smartContextAutoIncludes || [],
excludePaths: excludePaths || [],
}, },
}); });
}, },
...@@ -46,28 +60,63 @@ export function useContextPaths() { ...@@ -46,28 +60,63 @@ export function useContextPaths() {
const updateContextPaths = async (paths: GlobPath[]) => { const updateContextPaths = async (paths: GlobPath[]) => {
const currentAutoIncludes = const currentAutoIncludes =
contextPathsData?.smartContextAutoIncludes || []; contextPathsData?.smartContextAutoIncludes || [];
const currentExcludePaths = contextPathsData?.excludePaths || [];
return updateContextPathsMutation.mutateAsync({ return updateContextPathsMutation.mutateAsync({
contextPaths: paths, contextPaths: paths,
smartContextAutoIncludes: currentAutoIncludes.map(({ globPath }) => ({ smartContextAutoIncludes: currentAutoIncludes.map(
globPath, ({ globPath }: { globPath: string }) => ({
})), globPath,
}),
),
excludePaths: currentExcludePaths.map(
({ globPath }: { globPath: string }) => ({
globPath,
}),
),
}); });
}; };
const updateSmartContextAutoIncludes = async (paths: GlobPath[]) => { const updateSmartContextAutoIncludes = async (paths: GlobPath[]) => {
const currentContextPaths = contextPathsData?.contextPaths || []; const currentContextPaths = contextPathsData?.contextPaths || [];
const currentExcludePaths = contextPathsData?.excludePaths || [];
return updateContextPathsMutation.mutateAsync({ return updateContextPathsMutation.mutateAsync({
contextPaths: currentContextPaths.map(({ globPath }) => ({ globPath })), contextPaths: currentContextPaths.map(
({ globPath }: { globPath: string }) => ({ globPath }),
),
smartContextAutoIncludes: paths, smartContextAutoIncludes: paths,
excludePaths: currentExcludePaths.map(
({ globPath }: { globPath: string }) => ({
globPath,
}),
),
});
};
const updateExcludePaths = async (paths: GlobPath[]) => {
const currentContextPaths = contextPathsData?.contextPaths || [];
const currentAutoIncludes =
contextPathsData?.smartContextAutoIncludes || [];
return updateContextPathsMutation.mutateAsync({
contextPaths: currentContextPaths.map(
({ globPath }: { globPath: string }) => ({ globPath }),
),
smartContextAutoIncludes: currentAutoIncludes.map(
({ globPath }: { globPath: string }) => ({
globPath,
}),
),
excludePaths: paths,
}); });
}; };
return { return {
contextPaths: contextPathsData?.contextPaths || [], contextPaths: contextPathsData?.contextPaths || [],
smartContextAutoIncludes: contextPathsData?.smartContextAutoIncludes || [], smartContextAutoIncludes: contextPathsData?.smartContextAutoIncludes || [],
excludePaths: contextPathsData?.excludePaths || [],
isLoading, isLoading,
error, error,
updateContextPaths, updateContextPaths,
updateSmartContextAutoIncludes, updateSmartContextAutoIncludes,
updateExcludePaths,
}; };
} }
...@@ -39,10 +39,10 @@ export function registerContextPathsHandlers() { ...@@ -39,10 +39,10 @@ export function registerContextPathsHandlers() {
const results: ContextPathResults = { const results: ContextPathResults = {
contextPaths: [], contextPaths: [],
smartContextAutoIncludes: [], smartContextAutoIncludes: [],
excludePaths: [],
}; };
const { contextPaths, smartContextAutoIncludes } = validateChatContext( const { contextPaths, smartContextAutoIncludes, excludePaths } =
app.chatContext, validateChatContext(app.chatContext);
);
for (const contextPath of contextPaths) { for (const contextPath of contextPaths) {
const { formattedOutput, files } = await extractCodebase({ const { formattedOutput, files } = await extractCodebase({
appPath, appPath,
...@@ -76,6 +76,23 @@ export function registerContextPathsHandlers() { ...@@ -76,6 +76,23 @@ export function registerContextPathsHandlers() {
tokens: totalTokens, tokens: totalTokens,
}); });
} }
for (const excludePath of excludePaths || []) {
const { formattedOutput, files } = await extractCodebase({
appPath,
chatContext: {
contextPaths: [excludePath],
smartContextAutoIncludes: [],
},
});
const totalTokens = estimateTokens(formattedOutput);
results.excludePaths.push({
...excludePath,
files: files.length,
tokens: totalTokens,
});
}
return results; return results;
}, },
); );
......
...@@ -8,6 +8,7 @@ export function validateChatContext(chatContext: unknown): AppChatContext { ...@@ -8,6 +8,7 @@ export function validateChatContext(chatContext: unknown): AppChatContext {
return { return {
contextPaths: [], contextPaths: [],
smartContextAutoIncludes: [], smartContextAutoIncludes: [],
excludePaths: [],
}; };
} }
...@@ -20,6 +21,7 @@ export function validateChatContext(chatContext: unknown): AppChatContext { ...@@ -20,6 +21,7 @@ export function validateChatContext(chatContext: unknown): AppChatContext {
return { return {
contextPaths: [], contextPaths: [],
smartContextAutoIncludes: [], smartContextAutoIncludes: [],
excludePaths: [],
}; };
} }
} }
...@@ -120,6 +120,7 @@ export type GlobPath = z.infer<typeof GlobPathSchema>; ...@@ -120,6 +120,7 @@ export type GlobPath = z.infer<typeof GlobPathSchema>;
export const AppChatContextSchema = z.object({ export const AppChatContextSchema = z.object({
contextPaths: z.array(GlobPathSchema), contextPaths: z.array(GlobPathSchema),
smartContextAutoIncludes: z.array(GlobPathSchema), smartContextAutoIncludes: z.array(GlobPathSchema),
excludePaths: z.array(GlobPathSchema).optional(),
}); });
export type AppChatContext = z.infer<typeof AppChatContextSchema>; export type AppChatContext = z.infer<typeof AppChatContextSchema>;
...@@ -131,6 +132,7 @@ export type ContextPathResult = GlobPath & { ...@@ -131,6 +132,7 @@ export type ContextPathResult = GlobPath & {
export type ContextPathResults = { export type ContextPathResults = {
contextPaths: ContextPathResult[]; contextPaths: ContextPathResult[];
smartContextAutoIncludes: ContextPathResult[]; smartContextAutoIncludes: ContextPathResult[];
excludePaths: ContextPathResult[];
}; };
export const ReleaseChannelSchema = z.enum(["stable", "beta"]); export const ReleaseChannelSchema = z.enum(["stable", "beta"]);
......
...@@ -472,9 +472,10 @@ export async function extractCodebase({ ...@@ -472,9 +472,10 @@ export async function extractCodebase({
} }
// Collect files from contextPaths and smartContextAutoIncludes // Collect files from contextPaths and smartContextAutoIncludes
const { contextPaths, smartContextAutoIncludes } = chatContext; const { contextPaths, smartContextAutoIncludes, excludePaths } = chatContext;
const includedFiles = new Set<string>(); const includedFiles = new Set<string>();
const autoIncludedFiles = new Set<string>(); const autoIncludedFiles = new Set<string>();
const excludedFiles = new Set<string>();
// Add files from contextPaths // Add files from contextPaths
if (contextPaths && contextPaths.length > 0) { if (contextPaths && contextPaths.length > 0) {
...@@ -509,6 +510,7 @@ export async function extractCodebase({ ...@@ -509,6 +510,7 @@ export async function extractCodebase({
const matches = await glob(pattern, { const matches = await glob(pattern, {
nodir: true, nodir: true,
absolute: true, absolute: true,
ignore: "**/node_modules/**",
}); });
matches.forEach((file) => { matches.forEach((file) => {
const normalizedFile = path.normalize(file); const normalizedFile = path.normalize(file);
...@@ -518,12 +520,36 @@ export async function extractCodebase({ ...@@ -518,12 +520,36 @@ export async function extractCodebase({
} }
} }
// Add files from excludePaths
if (excludePaths && excludePaths.length > 0) {
for (const p of excludePaths) {
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);
excludedFiles.add(normalizedFile);
});
}
}
// Only filter files if contextPaths are provided // Only filter files if contextPaths are provided
// If only smartContextAutoIncludes are provided, keep all files and just mark auto-includes as forced // If only smartContextAutoIncludes are provided, keep all files and just mark auto-includes as forced
if (contextPaths && contextPaths.length > 0) { if (contextPaths && contextPaths.length > 0) {
files = files.filter((file) => includedFiles.has(path.normalize(file))); files = files.filter((file) => includedFiles.has(path.normalize(file)));
} }
// Filter out excluded files (this takes precedence over include paths)
if (excludedFiles.size > 0) {
files = files.filter((file) => !excludedFiles.has(path.normalize(file)));
}
// Sort files by modification time (oldest first) // Sort files by modification time (oldest first)
// This is important for cache-ability. // This is important for cache-ability.
const sortedFiles = await sortFilesByModificationTime([...new Set(files)]); const sortedFiles = await sortFilesByModificationTime([...new Set(files)]);
...@@ -543,7 +569,9 @@ export async function extractCodebase({ ...@@ -543,7 +569,9 @@ export async function extractCodebase({
virtualFileSystem, virtualFileSystem,
}); });
const isForced = autoIncludedFiles.has(path.normalize(file)); const isForced =
autoIncludedFiles.has(path.normalize(file)) &&
!excludedFiles.has(path.normalize(file));
// Determine file content based on whether we should read it // Determine file content based on whether we should read it
let fileContent: string; let fileContent: string;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论