Build reads discovered files and constructs the AI agent config payload. basePath is the base directory, discovered contains files relative to basePath with their kinds. agentName identifies the AI agent (e.g. "claude", "cursor"). gitCtx may be nil if not in a git repository.
(basePath string, discovered []DiscoveredFile, agentName string, gitCtx *GitContext)
| 33 | // agentName identifies the AI agent (e.g. "claude", "cursor"). |
| 34 | // gitCtx may be nil if not in a git repository. |
| 35 | func Build(basePath string, discovered []DiscoveredFile, agentName string, gitCtx *GitContext) (*Data, error) { |
| 36 | // Resolve basePath to its real path so symlink comparisons are reliable |
| 37 | realRoot, err := filepath.EvalSymlinks(basePath) |
| 38 | if err != nil { |
| 39 | return nil, fmt.Errorf("resolving root dir: %w", err) |
| 40 | } |
| 41 | |
| 42 | configFiles := make([]ConfigFile, 0, len(discovered)) |
| 43 | hashes := make([]string, 0, len(discovered)) |
| 44 | // Collect raw content from settings.json files to avoid base64 round-trip during MCP extraction. |
| 45 | var rawSettingsFiles []rawConfigContent |
| 46 | |
| 47 | for _, df := range discovered { |
| 48 | relPath := df.Path |
| 49 | absPath := filepath.Join(basePath, relPath) |
| 50 | |
| 51 | content, _, err := safeReadFile(absPath, realRoot) |
| 52 | if err != nil { |
| 53 | return nil, fmt.Errorf("%s: %w", relPath, err) |
| 54 | } |
| 55 | |
| 56 | hash := sha256.Sum256(content) |
| 57 | hexHash := hex.EncodeToString(hash[:]) |
| 58 | hashes = append(hashes, fmt.Sprintf("%s:%s", relPath, hexHash)) |
| 59 | |
| 60 | configFiles = append(configFiles, ConfigFile{ |
| 61 | Path: relPath, |
| 62 | Kind: df.Kind, |
| 63 | SHA256: hexHash, |
| 64 | Size: int64(len(content)), |
| 65 | Content: base64.StdEncoding.EncodeToString(content), |
| 66 | }) |
| 67 | |
| 68 | // Keep raw bytes for settings.json files to avoid base64 round-trip |
| 69 | if df.Kind == ConfigFileKindConfiguration && filepath.Base(relPath) == "settings.json" { |
| 70 | rawSettingsFiles = append(rawSettingsFiles, rawConfigContent{path: relPath, content: content}) |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | mcpServers := extractMCPServers(realRoot, rawSettingsFiles) |
| 75 | |
| 76 | data := Data{ |
| 77 | Agent: Agent{Name: agentName}, |
| 78 | ConfigHash: computeCombinedHash(hashes), |
| 79 | GitContext: gitCtx, |
| 80 | ConfigFiles: configFiles, |
| 81 | MCPServers: mcpServers, |
| 82 | } |
| 83 | |
| 84 | return &data, nil |
| 85 | } |
| 86 | |
| 87 | // rawConfigContent holds a file's raw bytes alongside its relative path, |
| 88 | // used to pass already-read content to MCP extraction without re-decoding. |