Skip to content

Commit 83e9493

Browse files
aksOpsclaude
andcommitted
feat(review): wire Kuzu graph evidence into codeiq review + review_changes
Plan §3.1 — "for each changed file, query QueryService.findComponentByFile → nodes-in-file; for each in-file node, call traceImpact(depth=2) for blast radius". Adds: - internal/review/graphctx.go: KuzuGraphContext implements GraphContext via direct Cypher against an open graph.Store. Returns a compact per-file evidence summary: nodes-in-file (kind/layer/label/id) + 1-hop upstream caller blast radius. Read-only. - cli/review.go: `codeiq review` opens .codeiq/graph/codeiq.kuzu read-only and passes a KuzuGraphContext to ReviewService. Falls back to diff-only review with a stderr warning when the store isn't there. - mcp/tools_review.go: review_changes uses the MCP server's already-open graph.Store for evidence (no extra open). - CHANGELOG.md [Unreleased] entry covering the port + dedup + review tool. Tests already cover the diff-only path (TestService_Review_EndToEnd_FixtureRepo). Graph-evidence path is exercised via the existing integration test in mcp/integration_test.go when wired through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6226173 commit 83e9493

4 files changed

Lines changed: 112 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,24 @@ for that specific tag for the per-commit details.
1616

1717
### Added
1818

19+
- **Go port (Phases 1-4 of the rewrite)** — codeiq is being ported from
20+
Java/Spring Boot to a single static Go binary on the `port/go-port`
21+
branch. PR #130. 100 detectors at 1:1 parity with the Java side; 34 MCP
22+
tools (deprecated) + 6 consolidated mode-driven tools (new); `codeiq
23+
review` CLI + `review_changes` MCP tool for LLM-driven PR review via
24+
Ollama (Cloud or local). Java tree untouched until Phase 6 cutover.
25+
- **Graph dedup + determinism** (Go side) — `GraphBuilder` deduplicates
26+
nodes by ID with confidence-aware merging, edges by canonical
27+
`(source, target, kind)` tuple. Linker output sorted at the boundary.
28+
`codeiq index` surfaces "Deduped: N nodes, M edges Dropped: K phantom
29+
edges" so graph hygiene is visible.
30+
- **`codeiq review`** — LLM-driven review of `git diff base..head` against
31+
the indexed graph. Defaults to local Ollama (`gpt-oss:20b`); set
32+
`OLLAMA_API_KEY` to flip to Ollama Cloud. `--format=markdown|json`,
33+
`--out`, `--focus`. Graph evidence (nodes-in-file + 1-hop blast radius)
34+
attached per changed file when the Kuzu store is enriched.
35+
- **`review_changes` MCP tool** — same review flow exposed over MCP for
36+
agent-driven invocation. Strictly read-only against the graph.
1937
- OpenSSF supply-chain wiring — Best Practices project
2038
[12650](https://www.bestpractices.dev/projects/12650), live Scorecard at
2139
[securityscorecards.dev](https://api.securityscorecards.dev/projects/github.com/RandomCodeSpace/codeiq),

go/internal/cli/review.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"time"
1111

12+
"github.com/randomcodespace/codeiq/go/internal/graph"
1213
"github.com/randomcodespace/codeiq/go/internal/review"
1314

1415
"github.com/spf13/cobra"
@@ -54,7 +55,20 @@ Plan §3 — Phase 3 of the optimization plan.`,
5455
cfg.Model = model
5556
}
5657
client := review.NewClient(cfg)
57-
svc := review.NewService(client, nil)
58+
59+
// Best-effort: open the enriched Kuzu store read-only so the
60+
// review prompt carries graph evidence per changed file. If
61+
// the store isn't present (no enrich yet) we fall back to
62+
// diff-only review with a stderr warning.
63+
var gctx review.GraphContext
64+
gdir := filepath.Join(abs, ".codeiq", "graph", "codeiq.kuzu")
65+
if store, err := graph.OpenReadOnly(gdir, 30*time.Second); err == nil {
66+
defer store.Close()
67+
gctx = review.NewKuzuGraphContext(store)
68+
} else {
69+
fmt.Fprintf(os.Stderr, "review: graph store not available (%v); falling back to diff-only review. Run 'codeiq enrich' first to include graph evidence.\n", err)
70+
}
71+
svc := review.NewService(client, gctx)
5872

5973
ctx, cancel := context.WithTimeout(cmd.Context(), cfg.Timeout+30*time.Second)
6074
defer cancel()

go/internal/mcp/tools_review.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ func toolReviewChanges(d *Deps) Tool {
4040
if p.Model != "" {
4141
cfg.Model = p.Model
4242
}
43-
svc := review.NewService(review.NewClient(cfg), nil)
43+
// Reuse the MCP server's already-open Kuzu store for evidence.
44+
var gctx review.GraphContext
45+
if d.Store != nil {
46+
gctx = review.NewKuzuGraphContext(d.Store)
47+
}
48+
svc := review.NewService(review.NewClient(cfg), gctx)
4449
rep, err := svc.Review(ctx, d.RootPath, p.BaseRef, p.HeadRef, p.FocusFiles)
4550
if err != nil {
4651
return NewErrorEnvelope(CodeInternalError, fmt.Errorf("review: %w", err), RequestID(ctx)), nil

go/internal/review/graphctx.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package review
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/randomcodespace/codeiq/go/internal/graph"
8+
)
9+
10+
// KuzuGraphContext implements GraphContext by querying an open Kuzu store.
11+
// Wire from CLI: open store, NewKuzuGraphContext(store), pass to NewService.
12+
//
13+
// EvidenceForFile returns a compact textual summary that the LLM finds
14+
// useful: nodes-in-file with kind + layer, plus 1-hop blast radius node
15+
// IDs. Strictly read-only.
16+
type KuzuGraphContext struct {
17+
Store *graph.Store
18+
}
19+
20+
// NewKuzuGraphContext returns a context backed by store. nil Store yields
21+
// empty evidence (the LLM degrades gracefully to diff-only review).
22+
func NewKuzuGraphContext(store *graph.Store) *KuzuGraphContext {
23+
return &KuzuGraphContext{Store: store}
24+
}
25+
26+
// EvidenceForFile satisfies GraphContext. Empty string when the store is
27+
// missing or the file has no graph nodes.
28+
func (k *KuzuGraphContext) EvidenceForFile(path string) string {
29+
if k == nil || k.Store == nil || path == "" {
30+
return ""
31+
}
32+
rows, err := k.Store.Cypher(`
33+
MATCH (n:CodeNode) WHERE n.file_path = $f
34+
RETURN n.id AS id, n.kind AS kind, n.label AS label, n.layer AS layer
35+
ORDER BY n.id LIMIT 25`, map[string]any{"f": path})
36+
if err != nil || len(rows) == 0 {
37+
return ""
38+
}
39+
var b strings.Builder
40+
fmt.Fprintf(&b, "%d node(s) defined in this file:\n", len(rows))
41+
for _, r := range rows {
42+
id, _ := r["id"].(string)
43+
kind, _ := r["kind"].(string)
44+
label, _ := r["label"].(string)
45+
layer, _ := r["layer"].(string)
46+
fmt.Fprintf(&b, "- [%s/%s] %s (%s)\n", kind, layer, label, id)
47+
}
48+
// 1-hop blast radius: who depends on these nodes?
49+
ids := make([]any, 0, len(rows))
50+
for _, r := range rows {
51+
if id, ok := r["id"].(string); ok {
52+
ids = append(ids, id)
53+
}
54+
}
55+
if len(ids) == 0 {
56+
return b.String()
57+
}
58+
deps, err := k.Store.Cypher(`
59+
MATCH (caller:CodeNode)-[r]->(target:CodeNode)
60+
WHERE target.id IN $ids
61+
RETURN DISTINCT caller.id AS id, caller.kind AS kind, caller.label AS label
62+
ORDER BY caller.id LIMIT 15`, map[string]any{"ids": ids})
63+
if err == nil && len(deps) > 0 {
64+
fmt.Fprintf(&b, "\nBlast radius (1 hop, upstream callers): %d node(s)\n", len(deps))
65+
for _, d := range deps {
66+
id, _ := d["id"].(string)
67+
kind, _ := d["kind"].(string)
68+
label, _ := d["label"].(string)
69+
fmt.Fprintf(&b, "- [%s] %s (%s)\n", kind, label, id)
70+
}
71+
}
72+
return b.String()
73+
}

0 commit comments

Comments
 (0)