微软开源 GitHub Copilot VS code plugin 源码分析 (二) copilot-instructions.md 文件的应用逻辑

GitHub Copilot Chat 扩展通过以下机制来应用 copilot-instructions.md 文件:

1. 配置设置控制

关键设置: github.copilot.chat.codeGeneration.useInstructionFiles

  • 默认值: true
  • 作用: 控制是否自动加载 copilot-instructions.md 文件中的指令
  • 位置: package.nls.json 第106行

2. 服务架构

主要服务: CustomInstructionsService

这个服务负责:

  • 从设置和文件中获取自定义指令
  • 处理不同类型的指令(文件引用和文本指令)
  • 管理指令的生命周期

3. 指令文件类型

系统支持两种类型的指令文件:

A. 标准指令文件 (.instructions.md)

  • 扩展名: .instructions.md
  • 位置: 可配置,通过 chat.instructionsFilesLocations 设置控制
  • 特性: 支持 applyTo 前端元数据,可以根据 glob 模式自动应用

B. Copilot 专用指令文件 copilot-instructions.md

4. 应用逻辑流程

步骤1:提示构建时

当构建 AI 提示时,CustomInstructions 组件会:

  1. 检查是否启用代码生成指令
js 复制代码
if (includeCodeGenerationInstructions !== false) {
    customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.CodeGenerationInstructions));
}
  1. 获取配置的指令

    • 从工作区文件夹级别配置
    • 从工作区级别配置
    • 从全局配置

步骤2:指令收集

CustomInstructionsService.fetchInstructionsFromSetting() 方法:

  1. 检查配置设置
js 复制代码
const inspect = this.configurationService.inspectConfig(configKey);
  1. 收集指令

    • 处理文件引用类型的指令 ({ file: "filename" })
    • 处理文本类型的指令 ({ text: "instruction text" })
  2. 文件读取 当遇到文件引用时,通过 _collectInstructionsFromFile() 方法:

js 复制代码
const promises = this.workspaceService.getWorkspaceFolders().map(async folderUri => {
    const fileUri = Uri.joinPath(folderUri, customInstructionsFile);
    const instruction = await this.readInstructionsFromFile(fileUri);
    if (instruction) {
        result.push(instruction);
    }
});

步骤3:文件加载

对于 copilot-instructions.md 文件:

  1. 自动检测:useInstructionFiles 设置为 true
  2. 文件路径构建: copilot-instructions.md
  3. 内容读取: 使用 readInstructionsFromFile() 方法
  4. 内容解析: 将文件内容作为指令文本处理

5. 指令应用时机

指令在以下情况下被应用:

  1. 代码生成请求(默认启用)
  2. 测试生成请求(需要显式启用)
  3. 代码反馈请求(需要显式启用)
  4. 提交消息生成(需要显式启用)
  5. PR 描述生成(需要显式启用)

6. 配置方式

通过设置文件配置:

json 复制代码
{
  "github.copilot.chat.codeGeneration.instructions": [
    {
      "file": ".github/copilot-instructions.md"
    }
  ]
}

通过 useInstructionFiles 设置:

json 复制代码
{
  "github.copilot.chat.codeGeneration.useInstructionFiles": true
}

7. 新功能:按需加载

从 0.29 版本开始,系统支持按需加载指令文件

  • AI 模型可以在需要时主动请求加载相关的指令文件
  • 支持 glob 模式匹配来确定适用的指令
  • 提供指令文件列表和描述给 AI 模型选择

相关代码分析:

js 复制代码
export class CustomInstructionsService implements ICustomInstructionsService {
    // 从文件加载指令
    public async fetchInstructionsFromFile(fileUri: Uri): Promise<ICustomInstructions | undefined> {
        return await this.readInstructionsFromFile(fileUri);
    }

    // 从设置加载指令
    public async fetchInstructionsFromSetting(configKey: Config<CodeGenerationInstruction[]>): Promise<ICustomInstructions[]> {
        const result: ICustomInstructions[] = [];
        const instructions: IInstruction[] = [];
        const seenFiles: Set<string> = new Set();

        const inspect = this.configurationService.inspectConfig(configKey);
        if (inspect) {
            await this.collectInstructionsFromSettings(
                [inspect.workspaceFolderValue, inspect.workspaceValue, inspect.globalValue], 
                seenFiles, instructions, result
            );
        }

        const reference = Uri.from({ 
            scheme: this.envService.uriScheme, 
            authority: 'settings', 
            path: `/${configKey.fullyQualifiedId}` 
        });
        
        if (instructions.length > 0) {
            result.push({
                kind: CustomInstructionsKind.Setting,
                content: instructions,
                reference,
            });
        }
        return result;
    }

    // 读取指令文件内容
    private async readInstructionsFromFile(fileUri: Uri, languageId?: string): Promise<ICustomInstructions | undefined> {
        try {
            const fileContents = await this.fileSystemService.readFile(fileUri);
            const content = new TextDecoder().decode(fileContents);
            const instruction = content.trim();
            
            if (!instruction) {
                this.logService.debug(`Instructions file is empty: ${fileUri.toString()}`);
                return;
            }
            
            return {
                kind: CustomInstructionsKind.File,
                content: [{ instruction, languageId }],
                reference: fileUri
            };
        } catch (e) {
            this.logService.debug(`Instructions file not found: ${fileUri.toString()}`);
            return undefined;
        }
    }
}

CustomInstructions 渲染组件:

js 复制代码
export class CustomInstructions extends PromptElement<CustomInstructionsProps> {
    override async render(state: void, sizing: PromptSizing) {
        const chunks = [];

        // 处理聊天变量中的指令(按需加载)
        if (includeCodeGenerationInstructions !== false && this.props.chatVariables) {
            for (const variable of this.props.chatVariables) {
                if (isPromptInstruction(variable)) {
                    if (isString(variable.value)) {
                        chunks.unshift(<TextChunk>{variable.value}</TextChunk>);
                    } else if (isUri(variable.value)) {
                        // 按需加载指令文件
                        const instructions = await this.customInstructionsService.fetchInstructionsFromFile(variable.value);
                        if (instructions) {
                            chunks.push(
                                <Tag name='attachment' attrs={{ 
                                    filePath: this.promptPathRepresentationService.getFilePath(variable.value) 
                                }}>
                                    <references value={[new CustomInstructionPromptReference(instructions, instructions.content.map(instruction => instruction.instruction))]} />
                                    {instructions.content.map(instruction => <TextChunk>{instruction.instruction}</TextChunk>)}
                                </Tag>
                            );
                        }
                    }
                }
            }
        }

        // 从设置加载指令
        const customInstructions: ICustomInstructions[] = [];
        if (includeCodeGenerationInstructions !== false) {
            customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.CodeGenerationInstructions));
        }
        if (includeTestGenerationInstructions) {
            customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.TestGenerationInstructions));
        }

        return (<>
            {introduction}{systemMessageConflictWarning}<br />
            <Tag name='instructions'>
                {chunks}
            </Tag>
        </>);
    }

    // 创建指令元素,支持语言过滤
    private createInstructionElement(instructions: ICustomInstructions) {
        const lines = [];
        for (const entry of instructions.content) {
            if (entry.languageId) {
                if (entry.languageId === this.props.languageId) {
                    lines.push(`For ${entry.languageId} code: ${entry.instruction}`);
                }
            } else {
                lines.push(entry.instruction);
            }
        }
        
        if (lines.length === 0) {
            return undefined;
        }

        return (<>
            <references value={[new CustomInstructionPromptReference(instructions, lines)]} />
            <>
                {lines.map(line => <TextChunk>{line}</TextChunk>)}
            </>
        </>);
    }
}

实际工作流程:

js 复制代码
// 指令文件格式示例
/*
---
applyTo: "**/*.ts"
description: "TypeScript coding style rules"
---

Use camelCase for variable names.
Always use strict type annotations.
Prefer const over let when possible.
*/

// 按需加载的触发机制
const availableInstructions = await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.CodeGenerationInstructions);

// 语言模型工具可以访问指令列表
for (const instruction of availableInstructions) {
    if (matchesCurrentContext(instruction.reference, currentFiles)) {
        chunks.push(this.createInstructionElement(instruction));
    }
}
  • 🎯 智能选择:根据当前编程语言和文件类型选择合适的指令
  • 性能优化:只加载相关的指令,避免上下文膨胀
  • 🔄 动态适应:根据任务需求自动调整指令集合
  • 📁 条件匹配:通过 glob 模式精确匹配适用场景

8. 指令文件生成

扩展提供了 Chat: Generate Instructions 命令来:

  • 分析代码库结构和模式
  • 自动生成 copilot-instructions.md 文件
  • 放置在 .github 文件夹中

总结

copilot-instructions.md 文件的应用是一个 多层次、可配置的系统

  1. 自动化: 通过 useInstructionFiles 设置自动加载
  2. 可配置: 通过详细的指令配置控制具体行为
  3. 智能化: 支持按需加载和模式匹配
  4. 集成化: 与提示构建系统深度集成

这个系统让开发者可以为项目定制 AI 行为,而无需在每次交互中手动指定规则。

相关推荐
vibecoding775 小时前
一文搞懂企业级 API 网关选型:12 个维度、4 类企业、7 步落地
人工智能·大模型·ai编程
Dawson Zhu8 小时前
AI 辅助调试陷入「反复修改」循环?用证据驱动的四步法破局
人工智能·语言模型·架构·aigc·agi
Behaviour9 小时前
iPhone Duo 来了,苹果 Siri AI 接入定制 Gemini
人工智能·ios·语言模型·aigc·iphone
旋生万物10 小时前
量子纠错容错率87%?螺旋拓扑码用“相位保护“给量子比特上保险(附Python)
python·ai编程·量子计算·量子纠错·螺旋计算
kyriewen10 小时前
我用 AI 写完一个需求后才发现,最难的不是 prompt,而是验收
前端·程序员·ai编程
程序员老刘11 小时前
Dart Skills CLI 1.0发布,老刘年初的预言兑现了
flutter·ai编程·dart
旋生万物11 小时前
素数都排在等角螺线上?用螺旋数论给黎曼猜想画一张“几何画像“(附Python)
python·ai编程·数论·黎曼猜想·素数分布
浩风祭月12 小时前
GPT Image 2.5 Sunburst还是Flare?模型选型、Python接入与成本避坑
python·aigc·openai·图像生成·gpt image 2.5
手写码匠12 小时前
DeepSeek 函数调用实战:从零搭建一个会“动手“的 AI 助手
人工智能·深度学习·算法·aigc