// Package context - instructions.go 实现 FLYTO.md 项目指令加载. // // FLYTO.md 是项目级指令文件,让用户可以为特定项目定义 // 自定义的行为规则,代码风格,工具偏好等. // // 加载优先级(按顺序搜索,所有找到的文件合并): // 1. ~/.flyto/FLYTO.md - 用户全局指令 // 2. /FLYTO.md - 项目根目录指令 // 3. /.flyto/FLYTO.md - 项目 .flyto 目录指令 // 4. /FLYTO.md - 当前工作目录指令(如果不同于项目根) // // 自定义指令加载机制. package context import ( "os" "os/exec" "path/filepath" "strings" ) // LoadInstructions 加载所有 FLYTO.md 项目指令文件并合并. // // 按优先级搜索以下路径的 FLYTO.md 文件: // 1. ~/.flyto/FLYTO.md - 用户全局配置 // 2. /FLYTO.md - 项目根目录(Git 仓库根或 cwd) // 3. /.flyto/FLYTO.md - 项目 .flyto 子目录 // 4. /FLYTO.md - 当前工作目录(仅当不同于项目根时) // // 所有找到的文件内容用 "\n\n" 合并. // 如果没有找到任何 FLYTO.md 文件,返回空字符串(不报错). func LoadInstructions(cwd string) string { // 确定项目根目录(优先用 Git 仓库根,否则用 cwd) projectRoot := detectProjectRoot(cwd) // 确定用户 home 目录 homeDir, err := os.UserHomeDir() if err != nil { homeDir = "" } // 构建搜索路径列表(按优先级) var searchPaths []string // 1. ~/.flyto/FLYTO.md - 用户全局指令 if homeDir != "" { searchPaths = append(searchPaths, filepath.Join(homeDir, ".flyto", "FLYTO.md")) } // 2. /FLYTO.md - 项目根目录 if projectRoot != "" { searchPaths = append(searchPaths, filepath.Join(projectRoot, "FLYTO.md")) } // 3. /.flyto/FLYTO.md - 项目 .flyto 目录 if projectRoot != "" { searchPaths = append(searchPaths, filepath.Join(projectRoot, ".flyto", "FLYTO.md")) } // 4. /FLYTO.md - 当前工作目录(仅当不同于项目根时) if cwd != projectRoot { searchPaths = append(searchPaths, filepath.Join(cwd, "FLYTO.md")) } // 去重(如果 projectRoot == homeDir/.flyto 之类的边界情况) searchPaths = deduplicatePaths(searchPaths) // 逐个读取文件,收集内容 var contents []string for _, p := range searchPaths { content, err := os.ReadFile(p) if err != nil { // 文件不存在或无法读取,跳过 continue } trimmed := strings.TrimSpace(string(content)) if trimmed != "" { contents = append(contents, trimmed) } } if len(contents) == 0 { return "" } return strings.Join(contents, "\n\n") } // detectProjectRoot 检测项目根目录. // 优先使用 Git 仓库根目录;如果不在 Git 仓库中,返回 cwd 本身. // 精妙之处(CLEVER): 用 git rev-parse --show-toplevel 检测项目根,而非向上遍历找标记文件-- // 这和 Git 自身的逻辑完全一致,天然支持嵌套仓库,submodule,worktree 等复杂场景. // 如果不在 Git 仓库中则回退到 cwd,保证非 Git 项目也能正常工作. func detectProjectRoot(cwd string) string { cmd := exec.Command("git", "rev-parse", "--show-toplevel") cmd.Dir = cwd out, err := cmd.Output() if err == nil { root := strings.TrimSpace(string(out)) if root != "" { return root } } // 不在 Git 仓库中,用 cwd 作为项目根 return cwd } // deduplicatePaths 去除路径列表中的重复项,保持顺序. func deduplicatePaths(paths []string) []string { seen := make(map[string]struct{}) var result []string for _, p := range paths { // 尝试获取绝对路径以处理相对路径和符号链接 abs, err := filepath.Abs(p) if err != nil { abs = p } if _, ok := seen[abs]; !ok { seen[abs] = struct{}{} result = append(result, p) } } return result } // formatInstructionsSection 将 FLYTO.md 内容格式化为系统提示词段落. // 如果内容为空,返回空字符串. func formatInstructionsSection(instructions string) string { if instructions == "" { return "" } return "# Custom Instructions (from FLYTO.md)\n\n" + instructions }