@@ -31,16 +31,20 @@ type Client struct {
3131
3232// SupermodelIR is the raw response from the Supermodel API /v1/graphs/supermodel endpoint.
3333type SupermodelIR struct {
34- Repo string `json:"repo"`
35- Summary irSummary `json:"summary"`
36- Metadata irMetadata `json:"metadata"`
37- Domains []irDomain `json:"domains"`
34+ Repo string `json:"repo"`
35+ Summary map [string ]any `json:"summary"`
36+ Metadata irMetadata `json:"metadata"`
37+ Domains []irDomain `json:"domains"`
38+ Graph irGraph `json:"graph"`
3839}
3940
40- type irSummary struct {
41- FilesProcessed int `json:"filesProcessed"`
42- Functions int `json:"functions"`
43- PrimaryLanguage * string `json:"primaryLanguage"`
41+ type irGraph struct {
42+ Nodes []irNode `json:"nodes"`
43+ }
44+
45+ type irNode struct {
46+ Type string `json:"type"`
47+ Name string `json:"name"`
4448}
4549
4650type irMetadata struct {
@@ -49,11 +53,11 @@ type irMetadata struct {
4953}
5054
5155type irDomain struct {
52- Name string `json:"name"`
53- DescriptionSummary string `json:"descriptionSummary"`
54- KeyFiles []string `json:"keyFiles"`
55- Responsibilities []string `json:"responsibilities"`
56- Subdomains []irSubdomain `json:"subdomains"`
56+ Name string `json:"name"`
57+ DescriptionSummary string `json:"descriptionSummary"`
58+ KeyFiles []string `json:"keyFiles"`
59+ Responsibilities []string `json:"responsibilities"`
60+ Subdomains []irSubdomain `json:"subdomains"`
5761}
5862
5963type irSubdomain struct {
@@ -67,71 +71,96 @@ func (ir *SupermodelIR) toProjectGraph(projectName string) *ProjectGraph {
6771 if len (ir .Metadata .Languages ) > 0 {
6872 lang = ir .Metadata .Languages [0 ]
6973 }
70- if ir .Summary .PrimaryLanguage != nil && * ir .Summary .PrimaryLanguage != "" {
71- lang = * ir .Summary .PrimaryLanguage
74+ if v , ok := ir .Summary ["primaryLanguage" ]; ok && v != nil {
75+ if s , ok := v .(string ); ok && s != "" {
76+ lang = s
77+ }
7278 }
7379
74- langMap := make (map [string ]int , len (ir .Metadata .Languages ))
75- for _ , l := range ir .Metadata .Languages {
76- langMap [l ] = 0 // count not available from API
80+ // Extract integer fields from the free-form summary map.
81+ // JSON numbers unmarshal as float64 in map[string]any.
82+ summaryInt := func (key string ) int {
83+ if v , ok := ir .Summary [key ]; ok {
84+ if n , ok := v .(float64 ); ok {
85+ return int (n )
86+ }
87+ }
88+ return 0
7789 }
7890
7991 domains := make ([]Domain , 0 , len (ir .Domains ))
8092 for _ , d := range ir .Domains {
81- subdomainNames := make ([]string , 0 , len (d .Subdomains ))
93+ subdomains := make ([]Subdomain , 0 , len (d .Subdomains ))
8294 for _ , s := range d .Subdomains {
83- subdomainNames = append (subdomainNames , s .Name )
95+ subdomains = append (subdomains , Subdomain {
96+ Name : s .Name ,
97+ Description : s .DescriptionSummary ,
98+ })
8499 }
85100 domains = append (domains , Domain {
86101 Name : d .Name ,
87102 Description : d .DescriptionSummary ,
88103 KeyFiles : d .KeyFiles ,
89104 Responsibilities : d .Responsibilities ,
90- Subdomains : subdomainNames ,
105+ Subdomains : subdomains ,
91106 })
92107 }
93108
109+ var externalDeps []string
110+ for _ , node := range ir .Graph .Nodes {
111+ if node .Type == "ExternalDependency" && node .Name != "" {
112+ externalDeps = append (externalDeps , node .Name )
113+ }
114+ }
115+
94116 return & ProjectGraph {
95- Name : projectName ,
96- Language : lang ,
97- Domains : domains ,
117+ Name : projectName ,
118+ Language : lang ,
119+ Domains : domains ,
120+ ExternalDeps : externalDeps ,
98121 Stats : Stats {
99- TotalFiles : ir . Summary . FilesProcessed ,
100- TotalFunctions : ir . Summary . Functions ,
101- Languages : langMap ,
122+ TotalFiles : summaryInt ( "filesProcessed" ) ,
123+ TotalFunctions : summaryInt ( "functions" ) ,
124+ Languages : ir . Metadata . Languages ,
102125 },
103126 UpdatedAt : time .Now (),
104127 }
105128}
106129
107130// ProjectGraph is the internal model used by the cache and template.
108131type ProjectGraph struct {
109- Name string `json:"name"`
110- Language string `json:"language"`
111- Framework string `json:"framework,omitempty"`
112- Description string `json:"description,omitempty"`
113- Domains []Domain `json:"domains"`
114- Stats Stats `json:"stats"`
115- UpdatedAt time.Time `json:"updated_at"`
132+ Name string `json:"name"`
133+ Language string `json:"language"`
134+ Framework string `json:"framework,omitempty"`
135+ Description string `json:"description,omitempty"`
136+ Domains []Domain `json:"domains"`
137+ ExternalDeps []string `json:"external_deps,omitempty"`
138+ Stats Stats `json:"stats"`
139+ UpdatedAt time.Time `json:"updated_at"`
140+ }
141+
142+ // Subdomain represents a named sub-area within a domain.
143+ type Subdomain struct {
144+ Name string `json:"name"`
145+ Description string `json:"description,omitempty"`
116146}
117147
118148// Domain represents a semantic domain within the project.
119149type Domain struct {
120- Name string `json:"name"`
121- Description string `json:"description"`
122- KeyFiles []string `json:"key_files"`
123- Responsibilities []string `json:"responsibilities"`
124- Subdomains []string `json:"subdomains,omitempty"`
125- DependsOn []string `json:"depends_on,omitempty"`
150+ Name string `json:"name"`
151+ Description string `json:"description"`
152+ KeyFiles []string `json:"key_files"`
153+ Responsibilities []string `json:"responsibilities"`
154+ Subdomains []Subdomain `json:"subdomains,omitempty"`
155+ DependsOn []string `json:"depends_on,omitempty"`
126156}
127157
128158// Stats holds codebase statistics.
129159type Stats struct {
130- TotalFiles int `json:"total_files"`
131- TotalFunctions int `json:"total_functions"`
132- TotalLines int `json:"total_lines"`
133- Languages map [string ]int `json:"languages,omitempty"`
134- CircularDependencyCycles int `json:"circular_dependency_cycles,omitempty"`
160+ TotalFiles int `json:"total_files"`
161+ TotalFunctions int `json:"total_functions"`
162+ Languages []string `json:"languages,omitempty"`
163+ CircularDependencyCycles int `json:"circular_dependency_cycles,omitempty"`
135164}
136165
137166// CircularDependencyCycle represents a single circular import chain.
@@ -292,7 +321,7 @@ func (c *Client) GetGraph(ctx context.Context, projectName string, repoZip []byt
292321
293322// GetCircularDependencies submits the repo zip to the circular dependency endpoint
294323// and returns the list of detected import cycles. Returns nil, nil if the endpoint
295- // is unavailable or returns no cycles — callers should treat this as "no data" .
324+ // is unavailable. If available but no cycles are found, returns an empty response .
296325func (c * Client ) GetCircularDependencies (ctx context.Context , projectName string , repoZip []byte ) (* CircularDependencyResponse , error ) {
297326 c .logFn ("[debug] checking circular dependencies (%d bytes)" , len (repoZip ))
298327
@@ -359,7 +388,19 @@ func (c *Client) GetCircularDependencies(ctx context.Context, projectName string
359388 case http .StatusOK , http .StatusAccepted :
360389 // Continue to parse
361390 default :
362- return nil , fmt .Errorf ("API error %d" , resp .StatusCode )
391+ var errResp struct {
392+ Message string `json:"message"`
393+ Error string `json:"error"`
394+ }
395+ _ = json .Unmarshal (respBody , & errResp )
396+ msg := errResp .Message
397+ if msg == "" {
398+ msg = errResp .Error
399+ }
400+ if msg == "" {
401+ msg = string (respBody )
402+ }
403+ return nil , fmt .Errorf ("API error %d: %s" , resp .StatusCode , msg )
363404 }
364405
365406 var jobResp JobStatus
0 commit comments