package cartography import ( "fmt" "os" "path/filepath" "sort" "strings" ) // composeEnrichQuery builds a prompt describing a declaration. func composeEnrichQuery(entry *Entry, source string, related []string) string { var b strings.Builder b.WriteString("Describe this Go declaration. Give a concise answer with these sections:\n\n") b.WriteString("DESCRIPTION: What it does and why (one paragraph)\n") b.WriteString("CONTRACT: Inputs, outputs, error conditions, guarantees (one paragraph)\n") b.WriteString("EDGE CASES: What happens with nil, empty, zero, or invalid inputs (one line)\n") b.WriteString("VALIDATION: How to verify it works correctly (one line)\n\n") fmt.Fprintf(&b, "Package: %s\n", entry.Package) if entry.Signature != "" { fmt.Fprintf(&b, "Signature: %s\n", entry.Signature) } if entry.DocComment != "" { fmt.Fprintf(&b, "Doc: %s\n", entry.DocComment) } if len(related) > 0 { fmt.Fprintf(&b, "Related declarations: %s\n", strings.Join(related, ", ")) } fmt.Fprintf(&b, "\nSource code:\n```go\n%s\n```\n", source) return b.String() } // parseEnrichResponse extracts structured fields from the oracle's response. func parseEnrichResponse(entry *Entry, answer string) { lines := strings.Split(answer, "\n") var section string var desc, contract, edgeCases, validation strings.Builder for _, line := range lines { trimmed := strings.TrimSpace(line) upper := strings.ToUpper(trimmed) switch { case strings.HasPrefix(upper, "DESCRIPTION:"): section = "desc" rest := strings.TrimPrefix(trimmed, trimmed[:12]) rest = strings.TrimSpace(rest) if rest != "" { desc.WriteString(rest) } case strings.HasPrefix(upper, "CONTRACT:"): section = "contract" rest := strings.TrimPrefix(trimmed, trimmed[:9]) rest = strings.TrimSpace(rest) if rest != "" { contract.WriteString(rest) } case strings.HasPrefix(upper, "EDGE CASES:") || strings.HasPrefix(upper, "EDGE_CASES:"): section = "edge" rest := trimmed[strings.IndexByte(trimmed, ':')+1:] rest = strings.TrimSpace(rest) if rest != "" { edgeCases.WriteString(rest) } case strings.HasPrefix(upper, "VALIDATION:"): section = "validation" rest := strings.TrimPrefix(trimmed, trimmed[:11]) rest = strings.TrimSpace(rest) if rest != "" { validation.WriteString(rest) } default: if trimmed == "" { continue } switch section { case "desc": desc.WriteString(" ") desc.WriteString(trimmed) case "contract": contract.WriteString(" ") contract.WriteString(trimmed) case "edge": edgeCases.WriteString(" ") edgeCases.WriteString(trimmed) case "validation": validation.WriteString(" ") validation.WriteString(trimmed) } } } if d := strings.TrimSpace(desc.String()); d != "" { entry.Description = d } if c := strings.TrimSpace(contract.String()); c != "" { entry.Contract = c } if e := strings.TrimSpace(edgeCases.String()); e != "" { entry.EdgeCases = e } if v := strings.TrimSpace(validation.String()); v != "" { entry.Validation = v } } // readDeclSourceFrom reads declaration source from an explicit root directory. // For external packages, sourceRoot is the package directory and entry.FilePath // is importPath/filename.go — we use only the filename part. func readDeclSourceFrom(entry *Entry, sourceRoot string) (string, error) { // entry.FilePath is like "go/token/token.go"; we need just the filename. filename := filepath.Base(entry.FilePath) path := filepath.Join(sourceRoot, filename) return readDeclSourcePath(path, entry.Line) } // readDeclSource reads the source text of a declaration from disk. // Returns up to 150 lines starting from the declaration's line. func readDeclSource(entry *Entry, projectRoot string) (string, error) { path := filepath.Join(projectRoot, entry.FilePath) return readDeclSourcePath(path, entry.Line) } // readDeclSourcePath reads a declaration from a file starting at the given line. func readDeclSourcePath(path string, line int) (string, error) { data, err := os.ReadFile(path) if err != nil { return "", err } lines := strings.Split(string(data), "\n") start := line - 1 // 0-indexed if start < 0 { start = 0 } end := start + 150 if end > len(lines) { end = len(lines) } // Find the end of the declaration (matching braces). depth := 0 for i := start; i < end; i++ { for _, ch := range lines[i] { if ch == '{' { depth++ } if ch == '}' { depth-- if depth == 0 { return strings.Join(lines[start:i+1], "\n"), nil } } } } return strings.Join(lines[start:end], "\n"), nil } // findRelated returns IDs of related entries (same package, referenced in DependsOn). func findRelated(entry *Entry, atlas *Atlas) []string { ids := make([]string, 0, len(atlas.Entries)) for id := range atlas.Entries { ids = append(ids, id) } sort.Strings(ids) var related []string for _, id := range ids { e := atlas.Entries[id] if e.Package == entry.Package && e.ID != entry.ID && e.Exported { related = append(related, e.ID) if len(related) >= 5 { break } } } return related }