Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions internal/find/doc.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [deps]
// imported-by cmd/find.go
// imported-by internal/find/integration_test.go
// [impact]
// risk MEDIUM
// domains CLIInfrastructure · SupermodelAPI
// direct 2
// transitive 3
// affects cmd/find.go · internal/find/integration_test.go
54 changes: 13 additions & 41 deletions internal/find/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"sort"
"strings"

"github.com/supermodeltools/cli/internal/analyze"
"github.com/supermodeltools/cli/internal/api"
"github.com/supermodeltools/cli/internal/cache"
"github.com/supermodeltools/cli/internal/config"
"github.com/supermodeltools/cli/internal/ui"
)
Expand Down Expand Up @@ -60,21 +60,17 @@ func search(g *api.Graph, symbol, kind string) []Match {

for _, rel := range rels {
switch rel.Type {
case "CALLS", "CONTAINS_CALL":
if n := nodeByID[rel.EndNode]; n != nil {
callerNode := nodeByID[rel.StartNode]
if callerNode != nil {
callers[rel.EndNode] = append(callers[rel.EndNode], callerNode.Prop("name", "qualifiedName"))
}
case "calls", "contains_call":
if callerNode := nodeByID[rel.StartNode]; callerNode != nil {
callers[rel.EndNode] = append(callers[rel.EndNode], callerNode.Prop("name", "qualifiedName"))
}
if n := nodeByID[rel.StartNode]; n != nil {
calleeNode := nodeByID[rel.EndNode]
if calleeNode != nil {
callees[rel.StartNode] = append(callees[rel.StartNode], calleeNode.Prop("name", "qualifiedName"))
}
if calleeNode := nodeByID[rel.EndNode]; calleeNode != nil {
callees[rel.StartNode] = append(callees[rel.StartNode], calleeNode.Prop("name", "qualifiedName"))
}
case "defines_function", "defines", "declares_class":
if fileNode := nodeByID[rel.StartNode]; fileNode != nil {
defFile[rel.EndNode] = fileNode.Prop("filePath", "path", "name")
}
case "DEFINES_FUNCTION", "DEFINES", "DECLARES_CLASS":
defFile[rel.EndNode] = nodeByID[rel.StartNode].Prop("path", "name", "file")
}
}

Expand All @@ -96,7 +92,7 @@ func search(g *api.Graph, symbol, kind string) []Match {
ID: n.ID,
Kind: label,
Name: name,
File: n.Prop("file", "path"),
File: n.Prop("filePath", "file", "path"),
DefinedIn: defFile[n.ID],
}
cs := callers[n.ID]
Expand Down Expand Up @@ -139,31 +135,7 @@ func printMatches(w io.Writer, matches []Match, fmt_ ui.Format) error {
return nil
}

// --- Graph retrieval ---------------------------------------------------------

func getGraph(ctx context.Context, cfg *config.Config, dir string, force bool) (*api.Graph, error) {
zipPath, err := createZip(dir)
if err != nil {
return nil, err
}
defer os.Remove(zipPath)

hash, err := cache.HashFile(zipPath)
if err != nil {
return nil, err
}
if !force {
if g, _ := cache.Get(hash); g != nil {
return g, nil
}
}

spin := ui.Start("Analyzing repository…")
defer spin.Stop()
g, err := api.New(cfg).Analyze(ctx, zipPath, "find-"+hash[:16])
if err != nil {
return nil, err
}
_ = cache.Put(hash, g)
return g, nil
g, _, err := analyze.GetGraph(ctx, cfg, dir, force)
return g, err
}
43 changes: 43 additions & 0 deletions internal/find/handler.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [deps]
// imports internal/api/client.go
// imports internal/api/doc.go
// imports internal/api/types.go
// imports internal/cache/cache.go
// imports internal/cache/doc.go
// imports internal/cache/fingerprint.go
// imports internal/config/config.go
// imports internal/config/doc.go
// imports internal/ui/doc.go
// imports internal/ui/output.go
// imported-by cmd/find.go
// imported-by internal/find/integration_test.go
// [calls]
// Run ← init cmd/find.go:10
// Run ← TestIntegration_Run_Find internal/find/integration_test.go:16
// Run ← TestIntegration_Run_Find_JSON internal/find/integration_test.go:31
// Run ← TestIntegration_Run_Find_NoMatch internal/find/integration_test.go:47
// Run ← TestIntegration_Run_Find_KindFilter internal/find/integration_test.go:62
// Run → getGraph internal/find/handler.go:144
// Run → search internal/find/handler.go:49
// Run → printMatches internal/find/handler.go:120
// Run → ParseFormat internal/ui/output.go:24
// getGraph ← Run internal/find/handler.go:36
// getGraph → Analyze internal/api/client.go:41
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify what getGraph calls in the refactored handler.go

# Look for the getGraph function implementation and its call sites
rg -A 10 'func getGraph' internal/find/handler.go

Repository: supermodeltools/cli

Length of output: 233


Regenerate the call-graph metadata file.

The metadata at line 29 is stale. Your code actually does what the PR says—getGraph now calls analyze.GetGraph() instead of the old deprecated endpoint. I verified the actual code:

func getGraph(ctx context.Context, cfg *config.Config, dir string, force bool) (*api.Graph, error) {
	g, _, err := analyze.GetGraph(ctx, cfg, dir, force)
	return g, err
}

Perfect. But the generated metadata file still shows the old call path (getGraph → Analyze). Since this is an auto-generated documentation file, just regenerate it with whatever tool/command creates it, and it'll reflect the current call graph.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/find/handler.graph.go` at line 29, The call-graph metadata is stale:
update the generated metadata so it reflects that getGraph now calls
analyze.GetGraph(); regenerate the call-graph metadata file (the
handler.graph.go metadata) using the project's call-graph generation
tool/command so the entry becomes "getGraph → analyze.GetGraph()" instead of the
old "getGraph → Analyze". After regenerating, commit the updated metadata file.

// getGraph → HashFile internal/cache/cache.go:59
// getGraph → Get internal/cache/cache.go:28
// getGraph → Start internal/ui/output.go:74
// getGraph → Put internal/cache/cache.go:44
// printMatches ← Run internal/find/handler.go:36
// printMatches → JSON internal/ui/output.go:47
// search ← Run internal/find/handler.go:36
// [impact]
// risk MEDIUM
// domains CLIInfrastructure · SupermodelAPI
// direct 2
// transitive 3
// affects cmd/find.go · internal/find/integration_test.go
6 changes: 3 additions & 3 deletions internal/find/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ func TestSearch_CallersAndCallees(t *testing.T) {
{ID: "callee", Labels: []string{"Function"}, Properties: map[string]any{"name": "callee"}},
},
Relationships: []api.Relationship{
{ID: "r1", Type: "CALLS", StartNode: "caller", EndNode: "target"},
{ID: "r2", Type: "CALLS", StartNode: "target", EndNode: "callee"},
{ID: "r1", Type: "calls", StartNode: "caller", EndNode: "target"},
{ID: "r2", Type: "calls", StartNode: "target", EndNode: "callee"},
},
}
matches := search(g, "target", "")
Expand All @@ -110,7 +110,7 @@ func TestSearch_DefinesFunction(t *testing.T) {
{ID: "fn1", Labels: []string{"Function"}, Properties: map[string]any{"name": "authenticate"}},
},
Relationships: []api.Relationship{
{ID: "r1", Type: "DEFINES_FUNCTION", StartNode: "file1", EndNode: "fn1"},
{ID: "r1", Type: "defines_function", StartNode: "file1", EndNode: "fn1"},
},
}
matches := search(g, "authenticate", "Function")
Expand Down
24 changes: 24 additions & 0 deletions internal/find/handler_test.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [deps]
// imports internal/api/client.go
// imports internal/api/doc.go
// imports internal/api/types.go
// imports internal/ui/doc.go
// imports internal/ui/output.go
// [calls]
// TestSearch_BasicMatch → makeGraph internal/find/handler_test.go:191
// TestSearch_CaseInsensitive → makeGraph internal/find/handler_test.go:191
// TestSearch_KindFilter → makeGraph internal/find/handler_test.go:191
// TestSearch_KindFilterExcludesOtherKinds → makeGraph internal/find/handler_test.go:191
// TestSearch_NoMatch → makeGraph internal/find/handler_test.go:191
// TestSearch_SortedByKindThenName → makeGraph internal/find/handler_test.go:191
// makeGraph ← TestSearch_BasicMatch internal/find/handler_test.go:15
// makeGraph ← TestSearch_CaseInsensitive internal/find/handler_test.go:28
// makeGraph ← TestSearch_KindFilter internal/find/handler_test.go:37
// makeGraph ← TestSearch_KindFilterExcludesOtherKinds internal/find/handler_test.go:48
// makeGraph ← TestSearch_NoMatch internal/find/handler_test.go:59
// makeGraph ← TestSearch_SortedByKindThenName internal/find/handler_test.go:67
23 changes: 23 additions & 0 deletions internal/find/integration_test.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [deps]
// imports internal/find/doc.go
// imports internal/find/handler.go
// imports internal/find/zip.go
// imports internal/testutil/integration.go
// [calls]
// TestIntegration_Run_Find → IntegrationConfig internal/testutil/integration.go:17
// TestIntegration_Run_Find → MinimalGoDir internal/testutil/integration.go:102
// TestIntegration_Run_Find → Run internal/find/handler.go:36
// TestIntegration_Run_Find_JSON → IntegrationConfig internal/testutil/integration.go:17
// TestIntegration_Run_Find_JSON → MinimalGoDir internal/testutil/integration.go:102
// TestIntegration_Run_Find_JSON → Run internal/find/handler.go:36
// TestIntegration_Run_Find_KindFilter → IntegrationConfig internal/testutil/integration.go:17
// TestIntegration_Run_Find_KindFilter → MinimalGoDir internal/testutil/integration.go:102
// TestIntegration_Run_Find_KindFilter → Run internal/find/handler.go:36
// TestIntegration_Run_Find_NoMatch → IntegrationConfig internal/testutil/integration.go:17
// TestIntegration_Run_Find_NoMatch → MinimalGoDir internal/testutil/integration.go:102
// TestIntegration_Run_Find_NoMatch → Run internal/find/handler.go:36
21 changes: 21 additions & 0 deletions internal/find/zip.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [deps]
// imported-by cmd/find.go
// imported-by internal/find/integration_test.go
// [calls]
// createZip → isGitRepo internal/find/zip.go:67
// createZip → gitArchive internal/find/zip.go:74
// createZip → walkZip internal/find/zip.go:82
// gitArchive ← createZip internal/find/zip.go:46
// isGitRepo ← createZip internal/find/zip.go:46
// walkZip ← createZip internal/find/zip.go:46
// [impact]
// risk MEDIUM
// domains CLIInfrastructure · SupermodelAPI
// direct 2
// transitive 3
// affects cmd/find.go · internal/find/integration_test.go
12 changes: 12 additions & 0 deletions internal/find/zip_test.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [calls]
// TestWalkZip_IncludesFiles → readZipEntries internal/find/zip_test.go:92
// TestWalkZip_SkipsHiddenFiles → readZipEntries internal/find/zip_test.go:92
// TestWalkZip_SkipsSkipDirs → readZipEntries internal/find/zip_test.go:92
// readZipEntries ← TestWalkZip_IncludesFiles internal/find/zip_test.go:17
// readZipEntries ← TestWalkZip_SkipsHiddenFiles internal/find/zip_test.go:33
// readZipEntries ← TestWalkZip_SkipsSkipDirs internal/find/zip_test.go:52
13 changes: 13 additions & 0 deletions internal/focus/doc.graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build ignore

package ignore

// @generated supermodel-sidecar — do not edit
// [deps]
// imported-by cmd/focus.go
// [impact]
// risk MEDIUM
// domains CLIInfrastructure · SupermodelAPI
// direct 1
// transitive 2
// affects cmd/focus.go
59 changes: 13 additions & 46 deletions internal/focus/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"sort"
"strings"

"github.com/supermodeltools/cli/internal/analyze"
"github.com/supermodeltools/cli/internal/api"
"github.com/supermodeltools/cli/internal/cache"
"github.com/supermodeltools/cli/internal/config"
"github.com/supermodeltools/cli/internal/ui"
)
Expand Down Expand Up @@ -99,11 +99,11 @@ func extract(g *api.Graph, target string, depth int, includeTypes bool) *Slice {

rels := g.Rels()

// 1. Direct imports (IMPORTS edges from file node, up to depth hops).
// 1. Direct imports (imports edges from file node, up to depth hops).
sl.Imports = reachableImports(g, fileNode.ID, nodeByID, rels, depth)

// 2. Functions defined in this file.
fnIDs := functionNodesForFile(g, fileNode.ID, rels)
fnIDs := functionNodesForFile(fileNode.ID, rels)
calleesOf := buildCalleesOf(rels)

for _, fnID := range fnIDs {
Expand All @@ -124,14 +124,14 @@ func extract(g *api.Graph, target string, depth int, includeTypes bool) *Slice {
return sl.Functions[i].Name < sl.Functions[j].Name
})

// 3. External callers (CALLS edges whose target is one of our functions).
// 3. External callers (calls edges whose target is one of our functions).
fnIDSet := make(map[string]bool, len(fnIDs))
for _, id := range fnIDs {
fnIDSet[id] = true
}
seenCallers := make(map[string]bool)
for _, rel := range rels {
if rel.Type != "CALLS" && rel.Type != "CONTAINS_CALL" {
if rel.Type != "calls" && rel.Type != "contains_call" {
continue
}
if !fnIDSet[rel.EndNode] {
Expand All @@ -141,7 +141,7 @@ func extract(g *api.Graph, target string, depth int, includeTypes bool) *Slice {
if callerNode == nil {
continue
}
callerFile := callerNode.Prop("file", "path")
callerFile := callerNode.Prop("filePath", "file", "path")
if pathMatches(callerFile, target) {
continue // skip self-calls
}
Expand Down Expand Up @@ -179,7 +179,7 @@ func reachableImports(g *api.Graph, seedID string, nodeByID map[string]*api.Node
next := make([]string, 0)
for _, cur := range queue {
for _, rel := range rels {
if rel.Type != "IMPORTS" && rel.Type != "WILDCARD_IMPORTS" {
if rel.Type != "imports" && rel.Type != "wildcard_imports" {
continue
}
if rel.StartNode != cur || visited[rel.EndNode] {
Expand All @@ -202,30 +202,23 @@ func reachableImports(g *api.Graph, seedID string, nodeByID map[string]*api.Node
}

// functionNodesForFile returns the IDs of Function nodes associated with fileID
// via DEFINES_FUNCTION or DEFINES relationships.
func functionNodesForFile(g *api.Graph, fileID string, rels []api.Relationship) []string {
// via defines_function or defines relationships.
func functionNodesForFile(fileID string, rels []api.Relationship) []string {
var ids []string
for _, rel := range rels {
if (rel.Type == "defines_function" || rel.Type == "defines") && rel.StartNode == fileID {
ids = append(ids, rel.EndNode)
}
}
// Fallback: match by file property on Function nodes.
if len(ids) == 0 {
for _, n := range g.NodesByLabel("Function") {
if n.Prop("file", "path") != "" {
ids = append(ids, n.ID)
}
}
}
return ids
}

// buildCalleesOf returns a map from function node ID to the IDs of functions it calls.
func buildCalleesOf(rels []api.Relationship) map[string][]string {
m := make(map[string][]string)
for _, rel := range rels {
if rel.Type == "calls" || rel.Type == "contains_call" {
t := rel.Type
if t == "calls" || t == "contains_call" {
m[rel.StartNode] = append(m[rel.StartNode], rel.EndNode)
}
}
Expand All @@ -235,7 +228,7 @@ func buildCalleesOf(rels []api.Relationship) map[string][]string {
func extractTypes(g *api.Graph, fileID string, nodeByID map[string]*api.Node, rels []api.Relationship) []Type {
var types []Type
for _, rel := range rels {
if rel.Type != "DECLARES_CLASS" && rel.Type != "DEFINES" {
if rel.Type != "declares_class" && rel.Type != "defines" {
continue
}
if rel.StartNode != fileID {
Expand Down Expand Up @@ -337,32 +330,6 @@ func printMarkdown(w io.Writer, sl *Slice) error {
return nil
}

// --- Graph retrieval (not importing analyze slice) ---------------------------

func getGraph(ctx context.Context, cfg *config.Config, dir string, force bool) (*api.Graph, string, error) {
zipPath, err := createZip(dir)
if err != nil {
return nil, "", err
}
defer os.Remove(zipPath)

hash, err := cache.HashFile(zipPath)
if err != nil {
return nil, "", err
}
if !force {
if g, _ := cache.Get(hash); g != nil {
return g, hash, nil
}
}
spin := ui.Start("Analyzing repository…")
defer spin.Stop()

client := newAPIClient(cfg)
g, err := client.Analyze(ctx, zipPath, "focus-"+hash[:16])
if err != nil {
return nil, hash, err
}
_ = cache.Put(hash, g)
return g, hash, nil
return analyze.GetGraph(ctx, cfg, dir, force)
}
Loading
Loading