Prompt file diagnostics feedback (#291450)

This commit is contained in:
Paul
2026-01-29 11:40:56 +11:00
committed by GitHub
parent d3d2601e4a
commit c84bdba0a4
2 changed files with 76 additions and 26 deletions
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { Schemas } from '../../../../../base/common/network.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../../nls.js';
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
@@ -38,15 +39,20 @@ function encodePathForMarkdown(path: string): string {
* The returned path is URL encoded for use in markdown link targets.
*/
function getRelativePath(uri: URI, workspaceFolders: readonly IWorkspaceFolder[]): string {
// On desktop, vscode-userdata scheme maps 1:1 to file scheme paths via FileUserDataProvider.
// Convert to file scheme so relativePath() can compute paths correctly.
// On web, vscode-userdata uses IndexedDB so this conversion has no effect (different schemes won't match workspace folders).
const normalizedUri = uri.scheme === Schemas.vscodeUserData ? uri.with({ scheme: Schemas.file }) : uri;
for (const folder of workspaceFolders) {
const relative = relativePath(folder.uri, uri);
const relative = relativePath(folder.uri, normalizedUri);
if (relative) {
return encodePathForMarkdown(relative);
}
}
// Fall back to fsPath if not under any workspace folder
// Use forward slashes for consistency in markdown links
return encodePathForMarkdown(uri.fsPath.replace(/\\/g, '/'));
return encodePathForMarkdown(normalizedUri.fsPath.replace(/\\/g, '/'));
}
// Tree prefixes
@@ -117,8 +123,13 @@ export function registerChatCustomizationDiagnosticsAction() {
}, {
id: CHAT_CONFIG_MENU_ID,
when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),
order: 20,
order: 14,
group: '3_configure'
}, {
id: MenuId.ChatWelcomeContext,
group: '2_settings',
order: 0,
when: ChatContextKeys.inChatEditor.negate()
}]
});
}
@@ -423,12 +434,14 @@ export function formatStatusOutput(
// Count loaded and skipped files (overwritten counts as skipped)
let loadedCount = info.files.filter(f => f.status === 'loaded').length;
const skippedCount = info.files.filter(f => f.status === 'skipped' || f.status === 'overwritten').length;
// Include special files in the loaded count
if (info.type === PromptsType.agent && specialFiles.agentsMd.enabled) {
loadedCount += specialFiles.agentsMd.files.length;
}
if (info.type === PromptsType.instructions && specialFiles.copilotInstructions.enabled) {
loadedCount += specialFiles.copilotInstructions.files.length;
// Include special files in the loaded count for instructions
if (info.type === PromptsType.instructions) {
if (specialFiles.agentsMd.enabled) {
loadedCount += specialFiles.agentsMd.files.length;
}
if (specialFiles.copilotInstructions.enabled) {
loadedCount += specialFiles.copilotInstructions.files.length;
}
}
lines.push(`**${typeName}**${enabledStatus}<br>`);
@@ -558,8 +571,9 @@ export function formatStatusOutput(
hasContent = true;
}
// Add special files for agents (AGENTS.md)
if (info.type === PromptsType.agent) {
// Add special files for instructions (AGENTS.md and copilot-instructions.md)
if (info.type === PromptsType.instructions) {
// AGENTS.md
if (specialFiles.agentsMd.enabled && specialFiles.agentsMd.files.length > 0) {
lines.push(`AGENTS.md<br>`);
for (let i = 0; i < specialFiles.agentsMd.files.length; i++) {
@@ -575,10 +589,8 @@ export function formatStatusOutput(
lines.push(`AGENTS.md -<br>`);
hasContent = true;
}
}
// Add special files for instructions (copilot-instructions.md)
if (info.type === PromptsType.instructions) {
// copilot-instructions.md
if (specialFiles.copilotInstructions.enabled && specialFiles.copilotInstructions.files.length > 0) {
lines.push(`${COPILOT_CUSTOM_INSTRUCTIONS_FILENAME}<br>`);
for (let i = 0; i < specialFiles.copilotInstructions.files.length; i++) {
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { Schemas } from '../../../../../../base/common/network.js';
import { URI } from '../../../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { formatStatusOutput, IFileStatusInfo, IPathInfo, ITypeStatusInfo } from '../../../browser/actions/chatCustomizationDiagnosticsAction.js';
@@ -91,7 +92,6 @@ suite('formatStatusOutput', () => {
'.github/agents<br>',
`${TREE_BRANCH} [\`code-reviewer.agent.md\`](${filePath('.github/agents/code-reviewer.agent.md')})<br>`,
`${TREE_END} [\`test-helper.agent.md\`](${filePath('.github/agents/test-helper.agent.md')})<br>`,
'AGENTS.md -<br>',
''
));
});
@@ -119,7 +119,6 @@ suite('formatStatusOutput', () => {
'.github/agents<br>',
`${TREE_BRANCH} [\`good-agent.agent.md\`](${filePath('.github/agents/good-agent.agent.md')})<br>`,
`${TREE_END} ${ICON_ERROR} [\`broken-agent.agent.md\`](${filePath('.github/agents/broken-agent.agent.md')}) - *Missing name attribute*<br>`,
'AGENTS.md -<br>',
''
));
});
@@ -151,7 +150,6 @@ suite('formatStatusOutput', () => {
`${TREE_END} [\`my-agent.agent.md\`](${filePath('.github/agents/my-agent.agent.md')})<br>`,
'~/.copilot/agents<br>',
`${TREE_END} ${ICON_WARN} [\`my-agent.agent.md\`](${filePath('home/.copilot/agents/my-agent.agent.md')}) - *Overwritten by higher priority file*<br>`,
'AGENTS.md -<br>',
''
));
});
@@ -229,16 +227,17 @@ suite('formatStatusOutput', () => {
'',
'.github/instructions<br>',
`${TREE_END} [\`testing.instructions.md\`](${filePath('.github/instructions/testing.instructions.md')})<br>`,
'AGENTS.md -<br>',
'copilot-instructions.md<br>',
`${TREE_END} [\`copilot-instructions.md\`](${filePath('.github/copilot-instructions.md')})<br>`,
''
));
});
test('agents with AGENTS.md enabled', () => {
test('instructions with AGENTS.md enabled', () => {
const statusInfos: ITypeStatusInfo[] = [{
type: PromptsType.agent,
paths: [createPath('.github/agents', true)],
type: PromptsType.instructions,
paths: [createPath('.github/instructions', true)],
files: [],
enabled: true
}];
@@ -254,13 +253,14 @@ suite('formatStatusOutput', () => {
'## Chat Customization Diagnostics',
'*WARNING: This file may contain sensitive information.*',
'',
'**Custom Agents**<br>',
'**Instructions**<br>',
'*2 files loaded*',
'',
'.github/agents<br>',
'.github/instructions<br>',
'AGENTS.md<br>',
`${TREE_BRANCH} [\`AGENTS.md\`](${filePath('AGENTS.md')})<br>`,
`${TREE_END} [\`AGENTS.md\`](${filePath('docs/AGENTS.md')})<br>`,
'copilot-instructions.md -<br>',
''
));
});
@@ -286,7 +286,6 @@ suite('formatStatusOutput', () => {
'',
'.github/agents<br>',
`${ICON_ERROR} custom/agents - *Folder does not exist*<br>`,
'AGENTS.md -<br>',
''
));
});
@@ -310,7 +309,6 @@ suite('formatStatusOutput', () => {
'**Custom Agents**<br>',
'',
'.github/agents<br>',
'AGENTS.md -<br>',
''
));
});
@@ -341,7 +339,6 @@ suite('formatStatusOutput', () => {
`${TREE_END} [\`local-agent.agent.md\`](${filePath('.github/agents/local-agent.agent.md')})<br>`,
'Extension: my-publisher.my-extension<br>',
`${TREE_END} [\`ext-agent.agent.md\`](${filePath('extensions/my-publisher.my-extension/agents/ext-agent.agent.md')})<br>`,
'AGENTS.md -<br>',
''
));
});
@@ -406,13 +403,13 @@ suite('formatStatusOutput', () => {
'',
'.github/agents<br>',
`${TREE_END} [\`helper.agent.md\`](${filePath('.github/agents/helper.agent.md')})<br>`,
'AGENTS.md -<br>',
'',
'**Instructions**<br>',
'*1 file loaded*',
'',
'.github/instructions<br>',
`${TREE_END} [\`code-style.instructions.md\`](${filePath('.github/instructions/code-style.instructions.md')})<br>`,
'AGENTS.md -<br>',
'copilot-instructions.md -<br>',
'',
'**Prompt Files**<br>',
@@ -483,4 +480,45 @@ suite('formatStatusOutput', () => {
assert.ok(output.includes('docs%20%26%20notes'), 'Ampersand should be URL-encoded');
assert.ok(output.includes('test%5B1%5D.prompt.md'), 'Brackets should be URL-encoded');
});
test('vscode-userdata scheme URIs are converted to file scheme for relative paths', () => {
// Create a workspace folder
const workspaceFolderUri = URI.file('/Users/test/workspace');
const workspaceFolder = {
uri: workspaceFolderUri,
name: 'workspace',
index: 0,
toResource: (relativePath: string) => URI.joinPath(workspaceFolderUri, relativePath)
};
// Create a vscode-userdata URI that maps to a path under the workspace
const userDataUri = URI.file('/Users/test/workspace/.github/agents/my-agent.agent.md').with({ scheme: Schemas.vscodeUserData });
const statusInfos: ITypeStatusInfo[] = [{
type: PromptsType.agent,
paths: [{
uri: URI.file('/Users/test/workspace/.github/agents'),
exists: true,
storage: PromptsStorage.local,
scanOrder: 1,
displayPath: '.github/agents',
isDefault: true
}],
files: [{
uri: userDataUri,
status: 'loaded',
name: 'my-agent.agent.md',
storage: PromptsStorage.local
}],
enabled: true
}];
const output = formatStatusOutput(statusInfos, emptySpecialFiles, [workspaceFolder]);
// The vscode-userdata URI should be converted to file scheme internally,
// allowing relative path computation against workspace folders
assert.ok(output.includes('.github/agents/my-agent.agent.md'), 'Should use relative path from workspace folder');
// Should not contain the full absolute path
assert.ok(!output.includes('/Users/test/workspace/.github'), 'Should not contain absolute path when relative path is available');
});
});