aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/aflow/flow/patching
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2026-01-30 20:25:26 +0100
committerDmitry Vyukov <dvyukov@google.com>2026-01-31 16:07:13 +0000
commit3576455960ee88cefa43cad0bdfd1458549569b9 (patch)
treeb0943ccce2feb664e2a30dd2462d99cf13fc4bf7 /pkg/aflow/flow/patching
parentafcca7fa917427568d76a8295ff9f1e88824c1fe (diff)
pkg/aflow/flow/patching: use recent commit subjects
Give LLM the recent commit subjects when it generates description, so that it can use the same style. Add infrastrcuture to write end-to-end action tests to test it.
Diffstat (limited to 'pkg/aflow/flow/patching')
-rw-r--r--pkg/aflow/flow/patching/actions.go32
-rw-r--r--pkg/aflow/flow/patching/actions_test.go59
-rw-r--r--pkg/aflow/flow/patching/patching.go7
3 files changed, 98 insertions, 0 deletions
diff --git a/pkg/aflow/flow/patching/actions.go b/pkg/aflow/flow/patching/actions.go
index 2c98e9306..d42c24fa1 100644
--- a/pkg/aflow/flow/patching/actions.go
+++ b/pkg/aflow/flow/patching/actions.go
@@ -4,6 +4,8 @@
package patching
import (
+ "errors"
+ "fmt"
"os/exec"
"path/filepath"
"strings"
@@ -108,3 +110,33 @@ func maintainers(ctx *aflow.Context, args maintainersArgs) (maintainersResult, e
}
return res, nil
}
+
+var getRecentCommits = aflow.NewFuncAction("get-recent-commits", recentCommits)
+
+type recentCommitsArgs struct {
+ KernelSrc string
+ KernelCommit string
+ PatchDiff string
+}
+
+type recentCommitsResult struct {
+ RecentCommits string
+}
+
+func recentCommits(ctx *aflow.Context, args recentCommitsArgs) (recentCommitsResult, error) {
+ var res recentCommitsResult
+ var files []string
+ for _, file := range vcs.ParseGitDiff([]byte(args.PatchDiff)) {
+ files = append(files, file.Name)
+ }
+ if len(files) == 0 {
+ return res, aflow.FlowError(errors.New("patch diff does not contain any modified files"))
+ }
+ gitArgs := append([]string{"log", "--format=%s", "--no-merges", "-n", "20", args.KernelCommit}, files...)
+ output, err := osutil.RunCmd(10*time.Minute, args.KernelSrc, "git", gitArgs...)
+ if err != nil {
+ return res, aflow.FlowError(fmt.Errorf("%w\n%s", err, output))
+ }
+ res.RecentCommits = string(output)
+ return res, nil
+}
diff --git a/pkg/aflow/flow/patching/actions_test.go b/pkg/aflow/flow/patching/actions_test.go
new file mode 100644
index 000000000..c6f3011e7
--- /dev/null
+++ b/pkg/aflow/flow/patching/actions_test.go
@@ -0,0 +1,59 @@
+// Copyright 2026 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package patching
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/google/syzkaller/pkg/aflow"
+)
+
+func TestRecentCommits(t *testing.T) {
+ // To avoid creating a fake git repo, we use the syzkaller repo itself.
+ // On CI we have a shallow git checkout that does not have the commit.
+ if os.Getenv("CI") != "" {
+ t.Skip("skipping on CI because of shallow git checkout")
+ }
+ aflow.TestAction(t, getRecentCommits, recentCommitsArgs{
+ KernelSrc: filepath.FromSlash("../../../.."),
+ KernelCommit: "e01a0ca6c12c9851ea7090f13879255ef82291e7",
+ PatchDiff: `
+diff --git a/dashboard/app/ai.go b/dashboard/app/ai.go
+index d4539113c..1d7401e61 100644
+--- a/dashboard/app/ai.go
++++ b/dashboard/app/ai.go
+@@ -1,2 +1,2 @@
+-// Copyright 2025 syzkaller project authors. All rights reserved.
++// Copyright 2026 syzkaller project authors. All rights reserved.
+ // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+diff --git a/syz-cluster/pkg/fuzzconfig/generate.go b/syz-cluster/pkg/fuzzconfig/generate.go
+index fa7d082e6..74ec57b49 100644
+--- a/syz-cluster/pkg/fuzzconfig/generate.go
++++ b/syz-cluster/pkg/fuzzconfig/generate.go
+@@ -1,2 +1,2 @@
+-// Copyright 2025 syzkaller project authors. All rights reserved.
++// Copyright 2026 syzkaller project authors. All rights reserved.
+ // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+`,
+ }, recentCommitsResult{RecentCommits: `dashboard: run patching ai jobs on custom base commits
+dashboard/app: upload AI-generated patches to gerrit
+dashboard: journal user actions on the ai dashboard
+pkg/aflow/trajectory: add token usage
+dashboard/app: add AI job running status
+dashboard: filter AI jobs by workflows
+syz-cluster: disable some trace calls for non-bpf targets
+pkg/aflow: make LLM model per-agent rather than per-flow
+dashboard/app: show crash report on AI job page
+dashboard/app: improve AI UI
+pkg/aflow: allow to specify model per-flow
+dashboard/app: add race harmfullness label
+dashboard/app: add manual AI job triage
+pkg/aflow/flow/assessment: add UAF moderation workflow
+dashboard/app: add support for AI workflows
+syz-cluster: rewrite fuzz config generation
+`,
+ }, "")
+}
diff --git a/pkg/aflow/flow/patching/patching.go b/pkg/aflow/flow/patching/patching.go
index 1906204ec..f8ee75a31 100644
--- a/pkg/aflow/flow/patching/patching.go
+++ b/pkg/aflow/flow/patching/patching.go
@@ -75,6 +75,7 @@ func createPatchingFlow(name string, summaryWindow int) *aflow.Flow {
MaxIterations: 10,
},
getMaintainers,
+ getRecentCommits,
&aflow.LLMAgent{
Name: "description-generator",
Model: aflow.BestExpensiveModel,
@@ -222,6 +223,12 @@ Additional description of the patch:
{{.PatchExplanation}}
+Here are summaries of recent commits that touched the same files.
+Format the summary line consistently with these, look how prefixes
+are specified, letter capitalization, style, etc.
+
+{{.RecentCommits}}
+
{{if titleIsWarning .BugTitle}}
If the patch removes the WARN_ON macro, refer to the fact that WARN_ON
must not be used for conditions that can legitimately happen, and that pr_err