Ken Muse

Why Your Copilot Instructions Don't Need a Folder Map


If you’ve spent time writing custom instructions for GitHub Copilot in VS Code, you’ve probably been tempted to include a detailed map of your project’s folder structure. Something like “the src/ directory contains the application code, tests/ has the test suite, docs/ holds the documentation…” It feels helpful. You’re giving the AI context about your project layout so it can navigate more effectively. It knows where to find your files.

But what if that entire section of your instructions is redundant?

How Copilot sees your workspace

Every time you start a conversation with Copilot in VS Code, the extension automatically generates a visual file tree of your workspace and injects it into the context. You’ve probably noticed it in the conversation – the <workspace_info> block that appears showing your project structure. That’s not something you provided. The Copilot Chat extension builds it fresh for every chat session. For example:

 1<environment_info>
 2The user's current OS is: Linux
 3</environment_info>
 4<workspace_info>
 5I am working in a workspace with the following folders:
 6- /workspaces/awesome-app
 7I am working in a workspace that has the following structure:
 8```
 9LICENSE
10README.md
11config/
12    settings.json
13src/
14    index.ts
15```
16This is the state of the context at this point in the conversation. The view of the workspace structure may be truncated. You can use tools to collect more context if needed.
17</workspace_info>

The extension reads your workspace root, sorts the entries (files first, then directories, each group alphabetical), and formats them into an indented tree. It filters out noise you don’t care about – things like node_modules, dist, .git, binary files, and anything covered by your .gitignore or Copilot content exclusion policies. The result is a clean, relevant snapshot of your project layout that the large language model (LLM) receives before it processes your first message.

Folder expansion

The workspace tree doesn’t get unlimited space; it has a 2,000-character budget. That might sound small for a large project, but the algorithm is clever about how it spends those characters. It prioritizes showing you the full breadth of your project at each level before diving deeper into any single directory. The root-level files and folders are listed first. Then, if budget remains, it expands the first level of subdirectories, then continues to the next level.

The moment the budget runs out at any point, the algorithm inserts a ... placeholder at that indentation level to signal there’s more content available. It then stops going any deeper into other folders. For example:

1config/
2    settings.json
3src/
4    components/
5        index.ts
6        ...
7    index.ts

There’s no hard-coded depth limit. The depth is naturally bounded by the cost of indentation – deeper items require more characters, consuming more of the budget. This means shallow, broad projects get more complete coverage, while deeply nested structures may end up summarized. That means that the process will naturally be more optimized with smaller, more focused repositories.

In a VS Code multi-root workspace – one where you’ve added multiple folders to VS Code as separate roots – the 2,000-character budget is divided evenly among those roots. With four root folders, each one gets only 500 characters to describe its structure. That’s enough to show a handful of top-level entries before the ... placeholder appears, so each root will look even more truncated than it would in a single-root workspace.

Challenges with monorepos

Like many parts of development, using a large monorepo can lead to friction. This approach will provide breadth but little depth about each of the folders (projects) in the repository. It will also include this tree of details even if you are only working on a single project within the monorepo. Depending on how the repo is organized, this could mean that the AI is constantly aware a subset of files and folders. In extreme cases, it may only see a small portion of the top-level folders. This could bias how it analyzes the repository. Since most of the folders will be summarized, it also means that Copilot will rely more on searches to find appropriate content. This can lead to increased token usage.

Because this is set up by the extension and added to the prompt automatically, you cannot alter it. In some cases, it may conflict with your prompting since it occurs early in the messages. You do have some ways to mitigate the issue. You can perform a sparse checkout with a select set of folders, create a workspace that contains only a selected subset of folders, or use files.exclude in your settings to remove a set of folders from the explorer view and search results. By making the folders unavailable in the workspace, they won’t be included in the workspace_info.

Be aware that using workspaces alone does not provide a hard guarantee that files and folders won’t be searched. The LLMs are surprisingly effective in knowing how to use other tools. Some tools – like listDirectory – can ignore .gitignore and some exclusion rules. When command line scripts are invoked, they see everything that the user could see. As a result, you can get conflicting or confusing results. This is where a focused repository – or a sparse checkout – is especially helpful. It gives all of the tools a consistent view of the filesystem.

Finding the missing files

If the structural view of your repository is incomplete, how will the LLM know how to get a more complete view when it needs it? Here’s where it gets interesting. You may have noticed in the snippet above that Copilot adds explicit instructions after the file tree: “The view of the workspace structure may be truncated. You can use tools to collect more context if needed.”

The core exploration tools – listDirectory (list_dir), fileSearch (file_search), textSearch (grep_search), and codebase (semantic_search) – are never deferred or hidden behind discovery mechanisms, so they are always available to every conversation by default. When the LLM sees ..., it infers that there are missing files and folders. To explore further, it can call listDirectory to see the full contents. When it needs to find a specific file pattern, fileSearch handles glob matching. When it needs to locate specific code or text, textSearch and codebase cover exact and fuzzy matching respectively.

The workspace tree only renders if the listDirectory tool is available; the system assumes the LLM can and will explore further when needed. If the tools aren’t available, the structure view is not included in the generated message:

1<environment_info>
2The user's current OS is: Linux
3</environment_info>
4<workspace_info>
5I am working in a workspace with the following folders:
6- /workspaces/awesome-app
7</workspace_info>

When extra context helps

This doesn’t mean you should never mention your project structure. There are cases where a brief note genuinely helps:

  • Folders with non-obvious names that the LLM can’t reason about (e.g., toffee/ is a theme name, not a candy-related module)
  • Important relationships between directories that aren’t apparent from the tree (e.g., “code in bootstrap/ compiles assets that themes/toffee/ consumes at build time”)
  • Conventions that override what names suggest (e.g., “src/ contains only generated code – never edit it directly”)
  • Directories that are filtered from the workspace but contain relevant content (since being outside of the workspace prevents many of the tools from seeing the repository)
  • Important directory names are truncated or have unclear names, forcing most interactions to require multiple searches

The key question is: can the LLM figure out where to look for code from the provided file or folder names alone? If the answer is yes, adding description is redundant. If you’re describing something the LLM would discover with a single listDirectory or fileSearch call, you’re not making the best use of your available instruction space.

Let’s take the non-obvious name “toffee” in the example above. If you asked Copilot to “update the theme to make the background black”, it will likely search for “theme” or “background”. If it’s a web project, it might even try to search for CSS or SCSS files. All of these would ultimately discover themes/toffee/. Since that’s in the folder themes, the LLM will reason that “toffee” is a theme. In this case, a description would have limited value since most work in that folder would likely begin with a search for the appropriate content to update, and both the path and the contents make it clear what the folder provides.

Now, think about a monorepo that has 15 projects, each containing CSS. There is a shared folder that contains SCSS files which are transformed and copied to the various projects. The search results could point in a lot of different directions. It might see the files within a project and not realize that some are generated content. In this case, a description would clarify this behavior for the LLM.

That means the highest-value instructions are the ones that capture knowledge the LLM cannot derive from your codebase alone such as your conventions and less obvious relationships. You want it to know about the things it can’t see. For example:

1- `base.css` must not be directly edited. Make changes `universal/scss` and run `universal/scss/build.sh` to update the file.

Where to put instructions

Remember that unless an instruction is always needed for every task, you should not put it in the root instructions file (such as .github/copilot-instructions.md). If the instruction only applies when working with specific directories, consider creating path-specific instructions in .github/instructions. You can then use applyTo to ensure that the instruction is only loaded when Copilot is working with specific files or specific folders. For example, applyTo: "**/*.css,**/*.scss,**/themes/*,universal/scss/**" might be used to target instructions for our theme processing example based on specific paths.

Alternatively, if you need it to dynamically decide when to load the instructions, make it a skill. Copilot will use the description (for example, Guidelines for updating themes. Use this when querying or modifying the appearance of the application) to determine when to load the additional details into the session. It relies more on the LLM to judge when to load the skill. It’s less deterministic, but it better adapts to inconsistent project structures, such as embedding design code in a React project or using .sass instead of .scss.

Trust the built-in machinery

VS Code’s Copilot extension was designed with a layered discovery strategy. The initial workspace tree gives the LLM orientation. The exploration tools give it ways to gain more depth. Your instructions fill gaps that none of these mechanisms can address – the context that live in your head, not in your file system.

Next time you’re editing your copilot-instructions.md, resist the urge to map out every folder. Ask yourself: “Is this something the LLM can figure out on its own?” If it is, save those tokens for something only you can provide.