Skip to content

Commit 5589051

Browse files
committed
feat: Add analyze command #43
1 parent 3cca831 commit 5589051

3 files changed

Lines changed: 185 additions & 1 deletion

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ go.work.sum
2525
# env file
2626
.env
2727

28-
/tmp
28+
/tmp
29+
30+
# db
31+
analyze.db
32+
agent.db

cmd/cli/analyze.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"flag"
8+
"fmt"
9+
"os"
10+
11+
"github.com/bit8bytes/gogantic/agents"
12+
"github.com/bit8bytes/gogantic/llms/ollama"
13+
"github.com/bit8bytes/gogantic/runner"
14+
"github.com/bit8bytes/gogantic/stores/moderncsqlite"
15+
"github.com/bit8bytes/gogantic/tools"
16+
)
17+
18+
func analyze(ctx context.Context, db *sql.DB, wd string) error {
19+
fs := flag.NewFlagSet("analyze", flag.ExitOnError)
20+
model := fs.String("model", "gemma3:12b", "Ollama model to use")
21+
numCtx := fs.Int("ctx", 16384, "Context window size")
22+
verbose := fs.Bool("v", false, "Print thought messages as the agent reasons")
23+
fs.Parse(os.Args[2:])
24+
25+
targetPath := fs.Arg(0)
26+
if targetPath == "" {
27+
return fmt.Errorf("usage: beago analyze [flags] <path>")
28+
}
29+
30+
storage, err := moderncsqlite.New(ctx, db)
31+
if err != nil {
32+
return fmt.Errorf("storage init: %w", err)
33+
}
34+
defer storage.Close()
35+
36+
llm := ollama.New(ollama.Model{
37+
Model: *model,
38+
Options: ollama.Options{NumCtx: *numCtx},
39+
Stream: false,
40+
Format: ollama.JSON,
41+
})
42+
43+
// These goTools are specifically designed for Golang.
44+
goTools := []agents.Tool{
45+
tools.GitDiff{},
46+
tools.ListDeclarations{},
47+
tools.FindCalls{},
48+
tools.FindUsages{},
49+
tools.GetFunctionSignature{},
50+
tools.GetStructFields{},
51+
tools.RunGoBuild{},
52+
tools.RunGoVet{},
53+
}
54+
55+
agent, err := agents.NewReAct(ctx, llm, goTools, storage)
56+
if err != nil {
57+
return fmt.Errorf("agent init: %w", err)
58+
}
59+
60+
task := fmt.Sprintf(`You are a Go code reviewer. Your job is to check if newly written code follows the same style and best practices as the rest of this codebase.
61+
62+
Project root: %s
63+
Target file: %s
64+
65+
Follow these steps:
66+
1. Call GitDiff with the target file path to get the exact changes.
67+
2. Call ListDeclarations with the project root to find comparable existing code.
68+
3. Call GetFunctionSignature on functions from the diff and on similar functions found in step 2.
69+
4. Compare them. Report ONLY concrete deviations in: error handling style, naming conventions, doc comments, receiver naming, function signature shape.
70+
71+
Format your final answer exactly like this:
72+
73+
REVIEW <target file>
74+
75+
No issues found.
76+
77+
Or if there are deviations:
78+
79+
REVIEW <target file>
80+
81+
ISSUE: <short title>
82+
File: <file>:<line>
83+
Expected: <what the codebase does>
84+
Found: <what the new code does>
85+
86+
Report only concrete deviations. No prose outside this format.`, wd, targetPath)
87+
88+
if err := agent.Task(ctx, task); err != nil {
89+
return fmt.Errorf("task: %w", err)
90+
}
91+
92+
r := runner.New(agent, *verbose)
93+
if err := r.Run(ctx); err != nil {
94+
return fmt.Errorf("run: %w", err)
95+
}
96+
97+
finalAnswer, err := agent.Answer()
98+
if errors.Is(err, agents.ErrNoFinalAnswer) {
99+
fmt.Println("No final answer found")
100+
return nil
101+
}
102+
if err != nil {
103+
return fmt.Errorf("answer: %w", err)
104+
}
105+
106+
fmt.Println(finalAnswer)
107+
return nil
108+
}

cmd/cli/main.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"os"
8+
"time"
9+
)
10+
11+
func main() {
12+
if len(os.Args) < 2 {
13+
fmt.Fprintln(os.Stderr, "usage: beago <command> [args]")
14+
os.Exit(1)
15+
}
16+
17+
wd, err := os.Getwd()
18+
if err != nil {
19+
fmt.Fprintf(os.Stderr, "error: %v\n", err)
20+
os.Exit(1)
21+
}
22+
23+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*240)
24+
defer cancel()
25+
26+
db, err := setupDatabase(ctx, "analyze.db")
27+
if err != nil {
28+
fmt.Fprintf(os.Stderr, "database setup: %v\n", err)
29+
os.Exit(1)
30+
}
31+
defer db.Close()
32+
33+
if err := migrateDatabase(ctx, db); err != nil {
34+
fmt.Fprintf(os.Stderr, "database migration: %v\n", err)
35+
os.Exit(1)
36+
}
37+
38+
switch os.Args[1] {
39+
case "analyze":
40+
if err := analyze(ctx, db, wd); err != nil {
41+
fmt.Fprintf(os.Stderr, "error: %v\n", err)
42+
os.Exit(1)
43+
}
44+
default:
45+
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
46+
os.Exit(1)
47+
}
48+
}
49+
50+
func setupDatabase(ctx context.Context, path string) (*sql.DB, error) {
51+
db, err := sql.Open("sqlite", path)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
if err := db.PingContext(ctx); err != nil {
57+
return nil, err
58+
}
59+
60+
return db, nil
61+
}
62+
63+
func migrateDatabase(ctx context.Context, db *sql.DB) error {
64+
_, err := db.ExecContext(ctx, `
65+
CREATE TABLE IF NOT EXISTS messages (
66+
id INTEGER PRIMARY KEY AUTOINCREMENT,
67+
role TEXT NOT NULL,
68+
content TEXT NOT NULL,
69+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
70+
);`)
71+
return err
72+
}

0 commit comments

Comments
 (0)