Skip to content

Conversation

@simonpcouch
Copy link
Contributor

@simonpcouch simonpcouch commented Dec 1, 2025

Addresses #10721, reworking the changes from #10517. Removes getDirectoryStructure in favor of a directoriesOnly argument to getProjectTree to save on input tokens and reduce the total number of tools.

The same examples from the PR that originally introduced the tool, this time using getProjectTree with directoriesOnly. Efficiently exploring the root:

Screenshot 2025-12-01 at 4 18 31 PM

Navigating a folder that's excluded by default by noticing that results were excluded and calling the tool again:

Screenshot 2025-12-01 at 4 24 26 PM

Seems like models can get just as much mileage out of this interface as with the dedicated tool.

Release Notes

New Features

Bug Fixes

  • N/A

QA Notes

@github-actions
Copy link

github-actions bot commented Dec 1, 2025

E2E Tests 🚀
This PR will run tests tagged with: @:critical

readme  valid tags

Comment on lines 26 to 27
maxFiles?: number;
directoriesOnly?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would we still want something like maxDepth when directoriesOnly is used? Or maybe we'd want to switch maxFiles --> maxItems and use it for both file mode and directory mode?

throw new Error(`The 'include' parameter is required. Specify glob patterns to target specific files (e.g., ["src/**/*.py"], ["*.ts", "tests/**"]).`);
}

const filePatterns = include;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do maxFiles --> maxItems, should we rename these variables as well? E.g. filePatterns --> globPatterns; filesLimit --> itemLimit, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thank you! c80f2f7

Comment on lines 128 to 145
if (!skipExcludes && totalFiles < sparseThreshold) {
log.debug(`[${PositronAssistantToolName.ProjectTree}] Default exclusions were applied and results were very sparse. Searching files again to determine how many files were excluded...`);
const allMatchesUris = await vscode.workspace.findFiles2(
filePatterns,
{
exclude: excludePatterns.length > 0 ? excludePatterns : undefined,
useIgnoreFiles: {
local: false,
parent: false,
global: false,
},
useExcludeSettings: vscode.ExcludeSettingOptions.None,
},
token
);
const totalFilesBeforeExclusion = allMatchesUris.length;
excludedCount = totalFilesBeforeExclusion - totalFiles;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When directoriesOnly is specified, should we still use this logic? As it will use findFiles instead of collecting directories

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ought not to have missed that. Thank you! 24f378b

simonpcouch and others added 4 commits December 2, 2025 11:40
Co-authored-by: sharon <[email protected]>
Signed-off-by: Simon P. Couch <[email protected]>
I noticed that the second, unrestricted search in case of sparse result could take a while if there were many results, so I opted to stop after _any_ additional results were found in the second-search case and then just note that _something_ was found. This cuts down on the latency quite a bit, and the model seems to still take the hint that it should keep looking.
@simonpcouch
Copy link
Contributor Author

The last commit doesn't address any review comment specifically. I noticed that the second, unrestricted search in case of sparse result could take several seconds if there were many results:

  2025-12-02 16:43:07.894 [debug] [getProjectTree] Constructing
  project tree for 1 workspace folders...
  2025-12-02 16:43:07.894 [trace] [getProjectTree] Invoked with
  options: ...clip...
  2025-12-02 16:43:09.532 [debug] [getProjectTree] Default
  exclusions were applied and results were very sparse. Searching
  again to determine how many items were excluded...
  2025-12-02 16:43:16.813 [debug] [getProjectTree] Project tree
  constructed with 0 items across 1 workspace folders.
  2025-12-02 16:43:16.813 [debug] [getProjectTree] 212 results
  were excluded. Set `skipDefaultExcludes` to `true` to include
  them.
  2025-12-02 16:43:16.816 [debug] [tool] Tool getProjectTree
  returned result: [

I opted to instead return after any additional results were found in the second-search case and then just note that something was found. This cuts down on the latency quite a bit, and the model seems to still take the hint that it should keep looking.

Screenshot 2025-12-02 at 5 01 53 PM

Copy link
Member

@sharon-wang sharon-wang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 📁 📁 📁

}

if (matchesInclude(relativePath, includePatterns)) {
directories.push(relativePath + '/');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered if the / might be an issue for Windows, but it seems that asRelativePath (which calls getRelativePath) ultimately uses posix forward slashes /, so we should be good here!

getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string {
let resource: URI | undefined;
let path: string = '';
if (typeof pathOrUri === 'string') {
resource = URI.file(pathOrUri);
path = pathOrUri;
} else if (typeof pathOrUri !== 'undefined') {
resource = pathOrUri;
path = pathOrUri.fsPath;
}
if (!resource) {
return path;
}
const folder = this.getWorkspaceFolder(
resource,
true
);
if (!folder) {
return path;
}
if (typeof includeWorkspace === 'undefined' && this._actualWorkspace) {
includeWorkspace = this._actualWorkspace.folders.length > 1;
}
let result = relativePath(folder.uri, resource);
if (includeWorkspace && folder.name) {
result = `${folder.name}/${result}`;
}
return result!;
}

relativePath(from: URI, to: URI): string | undefined {
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
return undefined;
}
if (from.scheme === Schemas.file) {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/';
const toPath = to.path || '/';
if (this._ignorePathCasing(from)) {
// make casing of fromPath match toPath
let i = 0;
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
break;
}
}
}
fromPath = toPath.substr(0, i) + fromPath.substr(i);
}
return paths.posix.relative(fromPath, toPath);
}

@simonpcouch simonpcouch merged commit 19cefa1 into main Dec 3, 2025
13 checks passed
@simonpcouch simonpcouch deleted the directory-10721 branch December 3, 2025 15:50
@github-actions github-actions bot locked and limited conversation to collaborators Dec 3, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants