Skip to content

Commit 06010f7

Browse files
feat(#82): Implement reviewer component for validating refactoring integrity (#106)
1 parent 37e30b2 commit 06010f7

File tree

11 files changed

+188
-204
lines changed

11 files changed

+188
-204
lines changed

internal/client/refrax_client.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,22 @@ func refactor(f domain.Facilitator, p domain.Project, size int, ch chan<- refact
173173
for _, c := range all {
174174
before[c.Name()] = c
175175
}
176-
task := domain.NewTask("refactor the project", all, map[string]any{"max-size": fmt.Sprintf("%d", size)})
177-
refactored, err := f.Refactor(task)
176+
job := domain.Job{
177+
Descr: &domain.Description{
178+
Text: "refactor the project",
179+
Meta: map[string]any{
180+
"max-size": fmt.Sprintf("%d", size),
181+
},
182+
},
183+
Classes: all,
184+
}
185+
artifacts, err := f.Refactor(&job)
178186
if err != nil {
179187
ch <- refactoring{err: fmt.Errorf("failed to refactor project %s: %w", p, err)}
180188
close(ch)
181189
return
182190
}
191+
refactored := artifacts.Classes
183192
log.Info("refactored %d classes in project %s", len(refactored), p)
184193
for _, c := range refactored {
185194
log.Debug("rececived refactored class: ", c)

internal/critic/server.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,15 @@ func (c *Critic) ListenAndServe() error {
7676
}
7777

7878
// Review sends the provided Java class to the Critic for analysis and returns suggested improvements.
79-
func (c *Critic) Review(job *domain.Job) ([]domain.Suggestion, error) {
79+
func (c *Critic) Review(job *domain.Job) (*domain.Artifacts, error) {
8080
address := fmt.Sprintf("http://localhost:%d", c.port)
8181
c.log.Info("asking critic (%s) to lint the class...", address)
8282
critic := protocol.NewClient(address)
8383
resp, err := critic.SendMessage(job.Marshal())
8484
if err != nil {
8585
return nil, fmt.Errorf("failed to send message to critic: %w", err)
8686
}
87-
return domain.RespToSuggestions(resp), nil
87+
return domain.UnmarshalArtifacts(resp.Result.(*protocol.Message))
8888
}
8989

9090
// Shutdown gracefully shuts down the Critic server.
@@ -133,21 +133,26 @@ func (c *Critic) thinkLong(m *protocol.Message) (*protocol.Message, error) {
133133
if err != nil {
134134
return nil, fmt.Errorf("failed to get answer from brain: %w", err)
135135
}
136-
res := protocol.NewMessage().WithMessageID(m.MessageID)
137136
suggestions := parseAnswer(answer)
138137
suggestions = associated(suggestions, class.Path())
139138
logSuggestions(c.log, suggestions)
140-
c.log.Info("found %d possible improvements", len(suggestions))
139+
all := make([]domain.Suggestion, 0, len(suggestions))
141140
for _, suggestion := range suggestions {
142-
c.log.Debug("suggestion: %s", suggestion)
143141
if strings.EqualFold(suggestion, notFound) {
144-
c.log.Info("no suggestions found")
145-
} else {
146-
res.AddPart(protocol.NewText(suggestion))
142+
c.log.Info("no suggestions found for class %s", class.Name())
143+
continue
147144
}
145+
s := domain.NewSuggestion(suggestion)
146+
all = append(all, s)
148147
}
149-
c.log.Debug("sending response: %s", res.MessageID)
150-
return res, nil
148+
artifacts := domain.Artifacts{
149+
Descr: &domain.Description{
150+
Text: fmt.Sprintf("Critique for class %s", class.Name()),
151+
},
152+
153+
Suggestions: all,
154+
}
155+
return artifacts.Marshal().Message, nil
151156
}
152157

153158
func logSuggestions(logger log.Logger, suggestions []string) {

internal/critic/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,5 @@ func TestCriticThink_ReturnsMessage(t *testing.T) {
129129
response, err := critic.think(context.Background(), msg)
130130

131131
require.NoError(t, err)
132-
assert.Equal(t, msg.MessageID, response.MessageID)
132+
require.NotNil(t, response)
133133
}

internal/domain/a2a.go

Lines changed: 75 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,55 @@ package domain
22

33
import (
44
"fmt"
5-
"strconv"
65

7-
"github.com/cqfn/refrax/internal/log"
86
"github.com/cqfn/refrax/internal/protocol"
97
"github.com/cqfn/refrax/internal/util"
108
"github.com/google/uuid"
119
)
1210

11+
const (
12+
typeClass = "class"
13+
typeSuggestion = "suggestion"
14+
typeExample = "example"
15+
)
16+
17+
func UnmarshalArtifacts(msg *protocol.Message) (*Artifacts, error) {
18+
if len(msg.Parts) == 0 {
19+
return nil, fmt.Errorf("message has no parts")
20+
}
21+
artifacts := &Artifacts{}
22+
descr, err := UnmarshalDescription(msg.Parts[0])
23+
if err != nil {
24+
return nil, fmt.Errorf("failed to unmarshal description: %w", err)
25+
}
26+
artifacts.Descr = descr
27+
classes := make([]Class, 0)
28+
suggestions := make([]Suggestion, 0)
29+
for _, part := range msg.Parts[1:] {
30+
metas := part.Metadata()
31+
t := metas["type"]
32+
switch t {
33+
case typeClass:
34+
c, err := UnmarshalClass(part)
35+
if err != nil {
36+
return nil, fmt.Errorf("failed to unmarshal class: %w", err)
37+
}
38+
classes = append(classes, c)
39+
case typeSuggestion:
40+
s, err := UnmarshalSuggestion(part)
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to unmarshal suggestion: %w", err)
43+
}
44+
suggestions = append(suggestions, s)
45+
default:
46+
return nil, fmt.Errorf("unknown part type %s", t)
47+
}
48+
}
49+
artifacts.Classes = classes
50+
artifacts.Suggestions = suggestions
51+
return artifacts, nil
52+
}
53+
1354
func UnmarshalJob(msg *protocol.Message) (*Job, error) {
1455
if len(msg.Parts) == 0 {
1556
return nil, fmt.Errorf("message has no parts")
@@ -27,19 +68,19 @@ func UnmarshalJob(msg *protocol.Message) (*Job, error) {
2768
metas := part.Metadata()
2869
t := metas["type"]
2970
switch t {
30-
case "class":
71+
case typeClass:
3172
c, err := UnmarshalClass(part)
3273
if err != nil {
3374
return nil, fmt.Errorf("failed to unmarshal class: %w", err)
3475
}
3576
classes = append(classes, c)
36-
case "example":
77+
case typeExample:
3778
e, err := UnmarshalClass(part)
3879
if err != nil {
3980
return nil, fmt.Errorf("failed to unmarshal example class: %w", err)
4081
}
4182
examples = append(examples, e)
42-
case "suggestion":
83+
case typeSuggestion:
4384
s, err := UnmarshalSuggestion(part)
4485
if err != nil {
4586
return nil, fmt.Errorf("failed to unmarshal suggestion: %w", err)
@@ -87,7 +128,7 @@ func UnmarshalDescription(part protocol.Part) (*Description, error) {
87128
descr := part.(*protocol.TextPart).Text
88129
res := Description{
89130
Text: descr,
90-
meta: part.Metadata(),
131+
Meta: part.Metadata(),
91132
}
92133
return &res, nil
93134
}
@@ -102,7 +143,7 @@ func (j *Job) Marshal() *protocol.MessageSendParams {
102143
if class == nil {
103144
continue
104145
}
105-
msg.AddPart(MarshalClass(class, "class"))
146+
msg.AddPart(MarshalClass(class, typeClass))
106147
}
107148
}
108149
if len(j.Suggestions) > 0 {
@@ -118,15 +159,39 @@ func (j *Job) Marshal() *protocol.MessageSendParams {
118159
if example == nil {
119160
continue
120161
}
121-
msg.AddPart(MarshalClass(example, "example"))
162+
msg.AddPart(MarshalClass(example, typeExample))
163+
}
164+
}
165+
return protocol.NewMessageSendParams().WithMessage(msg)
166+
}
167+
168+
func (a *Artifacts) Marshal() *protocol.MessageSendParams {
169+
msg := protocol.NewMessage().WithMessageID(uuid.NewString())
170+
if a.Descr != nil {
171+
msg.AddPart(a.Descr.Marshal())
172+
}
173+
if len(a.Classes) > 0 {
174+
for _, class := range a.Classes {
175+
if class == nil {
176+
continue
177+
}
178+
msg.AddPart(MarshalClass(class, typeClass))
179+
}
180+
}
181+
if len(a.Suggestions) > 0 {
182+
for _, suggestion := range a.Suggestions {
183+
if suggestion == nil {
184+
continue
185+
}
186+
msg.AddPart(MarshalSuggestion(suggestion))
122187
}
123188
}
124189
return protocol.NewMessageSendParams().WithMessage(msg)
125190
}
126191

127192
func (d *Description) Marshal() protocol.Part {
128193
part := protocol.NewText(d.Text)
129-
for k, v := range d.meta {
194+
for k, v := range d.Meta {
130195
part = part.WithMetadata(k, v)
131196
}
132197
return part
@@ -140,128 +205,5 @@ func MarshalClass(c Class, t string) protocol.Part {
140205
}
141206

142207
func MarshalSuggestion(s Suggestion) protocol.Part {
143-
return protocol.NewText(s.Text()).WithMetadata("type", "suggestion")
144-
}
145-
146-
// TaskToMsg converts a Task object to a protocol.Message.
147-
func TaskToMsg(task Task) *protocol.Message {
148-
param, ok := task.Param("max-size")
149-
if !ok {
150-
param = "200"
151-
}
152-
size, err := strconv.Atoi(param)
153-
if err != nil {
154-
panic(err)
155-
}
156-
msg := protocol.NewMessage().
157-
WithMessageID(uuid.NewString()).
158-
AddPart(protocol.NewText(task.Description()).WithMetadata("max-size", size))
159-
all := task.Classes()
160-
for _, class := range all {
161-
name := class.Name()
162-
path := class.Path()
163-
msg = msg.AddPart(protocol.NewFileBytes([]byte(class.Content())).WithMetadata("class-name", name).WithMetadata("class-path", path))
164-
}
165-
return msg
166-
}
167-
168-
// RespToClasses converts a protocol.JSONRPCResponse to a slice of Class objects.
169-
func RespToClasses(resp *protocol.JSONRPCResponse) ([]Class, error) {
170-
parts := resp.Result.(*protocol.Message).Parts
171-
log.Debug("received %d parts in refactoring response", len(parts))
172-
classes := make([]Class, 0, len(parts))
173-
for _, p := range parts {
174-
kind := p.PartKind()
175-
if kind == protocol.PartKindFile {
176-
log.Debug("received file part %v", p)
177-
classname := p.Metadata()["class-name"]
178-
path := fmt.Sprintf("%v", p.Metadata()["class-path"])
179-
bytes := p.(*protocol.FilePart).File.(protocol.FileWithBytes).Bytes
180-
decoded, err := util.DecodeFile(bytes)
181-
if err != nil {
182-
return nil, fmt.Errorf("failed to decode refactored class %s: %w", classname, err)
183-
}
184-
class := NewClass(fmt.Sprintf("%v", classname), path, decoded)
185-
classes = append(classes, class)
186-
}
187-
}
188-
return classes, nil
189-
}
190-
191-
// ClassesToMsg compiles a protocol message from a list of classes.
192-
func ClassesToMsg(classes []Class) *protocol.Message {
193-
msg := protocol.NewMessage()
194-
for _, v := range classes {
195-
msg.AddPart(protocol.NewFileBytes([]byte(v.Content())).WithMetadata("class-name", v.Name()))
196-
}
197-
return msg
198-
}
199-
200-
// RespToSuggestions converts a protocol.JSONRPCResponse to a slice of Suggestion objects.
201-
func RespToSuggestions(resp *protocol.JSONRPCResponse) []Suggestion {
202-
criticMessage := resp.Result.(*protocol.Message)
203-
suggestions := make([]Suggestion, 0, len(criticMessage.Parts))
204-
for _, part := range criticMessage.Parts {
205-
if part.PartKind() == protocol.PartKindText {
206-
suggestion := NewSuggestion(part.(*protocol.TextPart).Text)
207-
suggestions = append(suggestions, suggestion)
208-
}
209-
}
210-
return suggestions
211-
}
212-
213-
// RespToClass converts a JSONRPCResponse into a Class, decoding file parts as needed.
214-
func RespToClass(resp *protocol.JSONRPCResponse) (Class, error) {
215-
part := resp.Result.(*protocol.Message).Parts[0]
216-
if part.PartKind() == protocol.PartKindFile {
217-
filePart := part.(*protocol.FilePart)
218-
decoded, err := util.DecodeFile(filePart.File.(protocol.FileWithBytes).Bytes)
219-
if err != nil {
220-
return nil, fmt.Errorf("failed to decode file part: %w", err)
221-
}
222-
meta := filePart.Metadata()
223-
name := meta["class-name"]
224-
if name == nil {
225-
return nil, fmt.Errorf("file part has no class name metadata")
226-
}
227-
path := meta["class-path"]
228-
if path == nil {
229-
return nil, fmt.Errorf("file part has no class path metadata")
230-
}
231-
log.Debug("decoded class name: %s, path: ", name, path)
232-
return NewClass(fmt.Sprintf("%v", name), fmt.Sprintf("%v", path), decoded), nil
233-
}
234-
return nil, fmt.Errorf("expected file part, got %s", part.PartKind())
235-
}
236-
237-
// MsgToTask parses a task from a protocol message.
238-
func MsgToTask(msg *protocol.Message) (Task, error) {
239-
if len(msg.Parts) == 0 {
240-
return nil, fmt.Errorf("message has no parts")
241-
}
242-
part := msg.Parts[0]
243-
if part.PartKind() != protocol.PartKindText {
244-
return nil, fmt.Errorf("expected text part, got %s", part.PartKind())
245-
}
246-
descr := part.(*protocol.TextPart).Text
247-
params := part.Metadata()
248-
classes := make([]Class, 0)
249-
for _, v := range msg.Parts[1:] {
250-
if v.PartKind() != protocol.PartKindFile {
251-
continue
252-
}
253-
filePart := v.(*protocol.FilePart)
254-
decoded, err := util.DecodeFile(filePart.File.(protocol.FileWithBytes).Bytes)
255-
if err != nil {
256-
return nil, fmt.Errorf("failed to decode file part: %w", err)
257-
}
258-
meta := v.Metadata()
259-
class := NewClass(fmt.Sprintf("%v", meta["class-name"]), fmt.Sprintf("%v", meta["class-path"]), decoded)
260-
classes = append(classes, class)
261-
}
262-
return &task{
263-
descr: descr,
264-
classes: classes,
265-
parameters: params,
266-
}, nil
208+
return protocol.NewText(s.Text()).WithMetadata("type", typeSuggestion)
267209
}

internal/domain/a2a_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func TestMarshalAndUnmarshalJob(t *testing.T) {
1111
before := &Job{
1212
Descr: &Description{
1313
Text: "Test Job",
14-
meta: map[string]any{"key": "value"},
14+
Meta: map[string]any{"key": "value"},
1515
},
1616
Classes: []Class{
1717
NewClass("TestClass", "test/path/TestClass.java", "public class TestClass {}"),

0 commit comments

Comments
 (0)