From 2487073600c426f01b17db747831e5fcd740a00d Mon Sep 17 00:00:00 2001 From: Gunju Kim Date: Wed, 3 Jun 2026 03:59:33 +0000 Subject: [PATCH] Wire Kanon api-review alias --- internal/examples/self_development_test.go | 112 ++++++++++++++++++++- kanon-development/README.md | 19 ++-- kanon-development/kanon-reviewer.yaml | 4 +- 3 files changed, 124 insertions(+), 11 deletions(-) diff --git a/internal/examples/self_development_test.go b/internal/examples/self_development_test.go index 436a4e701..8d07dc56f 100644 --- a/internal/examples/self_development_test.go +++ b/internal/examples/self_development_test.go @@ -80,13 +80,115 @@ func TestKelosAPIReviewerTriggerableByBot(t *testing.T) { assertReviewerTriggerableByBot(t, "kelos-api-reviewer.yaml", "/kelos api-review") } +func TestKanonReviewerAcceptsAPIReviewAlias(t *testing.T) { + t.Parallel() + + ts := readDevelopmentTaskSpawner(t, "kanon-development", "kanon-reviewer.yaml") + spawner := ts.Spec.When.GitHubWebhook + if spawner == nil { + t.Fatalf("expected kanon-reviewer.yaml to use githubWebhook") + } + + tests := []struct { + name string + eventType string + payload string + want bool + }{ + { + name: "issue comment api-review alias", + eventType: "issue_comment", + payload: `{ + "action": "created", + "sender": {"login": "gjkim42"}, + "issue": { + "number": 1, + "state": "open", + "pull_request": {"url": "https://api.github.com/repos/kelos-dev/kanon/pulls/1"} + }, + "comment": {"body": "/kelos api-review"}, + "repository": {"full_name": "kelos-dev/kanon", "owner": {"login": "kelos-dev"}, "name": "kanon"} + }`, + want: true, + }, + { + name: "issue comment original review command", + eventType: "issue_comment", + payload: `{ + "action": "created", + "sender": {"login": "gjkim42"}, + "issue": { + "number": 1, + "state": "open", + "pull_request": {"url": "https://api.github.com/repos/kelos-dev/kanon/pulls/1"} + }, + "comment": {"body": "/kelos review"}, + "repository": {"full_name": "kelos-dev/kanon", "owner": {"login": "kelos-dev"}, "name": "kanon"} + }`, + want: true, + }, + { + name: "pull request review api-review alias", + eventType: "pull_request_review", + payload: `{ + "action": "submitted", + "sender": {"login": "gjkim42"}, + "pull_request": { + "number": 1, + "state": "open", + "html_url": "https://github.com/kelos-dev/kanon/pull/1", + "head": {"ref": "feature-branch"} + }, + "review": {"body": "/kelos api-review"}, + "repository": {"full_name": "kelos-dev/kanon", "owner": {"login": "kelos-dev"}, "name": "kanon"} + }`, + want: true, + }, + { + name: "kelos bot remains excluded", + eventType: "issue_comment", + payload: `{ + "action": "created", + "sender": {"login": "kelos-bot[bot]"}, + "issue": { + "number": 1, + "state": "open", + "pull_request": {"url": "https://api.github.com/repos/kelos-dev/kanon/pulls/1"} + }, + "comment": {"body": "/kelos api-review"}, + "repository": {"full_name": "kelos-dev/kanon", "owner": {"login": "kelos-dev"}, "name": "kanon"} + }`, + want: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + eventData, err := webhook.ParseGitHubWebhook(tt.eventType, []byte(tt.payload)) + if err != nil { + t.Fatalf("ParseGitHubWebhook() error = %v", err) + } + got, err := webhook.MatchesGitHubEvent(spawner, tt.eventType, eventData) + if err != nil { + t.Fatalf("MatchesGitHubEvent() error = %v", err) + } + if got != tt.want { + t.Fatalf("MatchesGitHubEvent() = %v, want %v", got, tt.want) + } + }) + } +} + // assertReviewerTriggerableByBot checks that the given reviewer spawner triggers // for both the Kelos bot and a maintainer posting bodyPattern on an open PR, but // not for an unauthorized user. func assertReviewerTriggerableByBot(t *testing.T, file, bodyPattern string) { t.Helper() - ts := readSelfDevelopmentTaskSpawner(t, file) + ts := readDevelopmentTaskSpawner(t, "self-development", file) spawner := ts.Spec.When.GitHubWebhook if spawner == nil { t.Fatalf("expected %s to use githubWebhook", file) @@ -139,7 +241,13 @@ func assertReviewerTriggerableByBot(t *testing.T, file, bodyPattern string) { func readSelfDevelopmentTaskSpawner(t *testing.T, file string) *kelosv1alpha1.TaskSpawner { t.Helper() - path := filepath.Join("..", "..", "self-development", file) + return readDevelopmentTaskSpawner(t, "self-development", file) +} + +func readDevelopmentTaskSpawner(t *testing.T, dir, file string) *kelosv1alpha1.TaskSpawner { + t.Helper() + + path := filepath.Join("..", "..", dir, file) data, err := os.ReadFile(path) if err != nil { t.Fatalf("reading %s: %v", path, err) diff --git a/kanon-development/README.md b/kanon-development/README.md index ee9e60cf5..2737cfc26 100644 --- a/kanon-development/README.md +++ b/kanon-development/README.md @@ -32,7 +32,7 @@ maintain (`kanon-development/*`) live in *this* repository, so they use the |---|---|---|---| | **kanon-workers** | Webhook: issue comment `/kelos pick-up` | Codex | Picks up issues, creates or updates PRs, self-reviews, and ensures CI passes | | **kanon-planner** | Webhook: issue comment `/kelos plan` | Codex | Investigates an issue and posts a structured implementation plan — advisory only, no code changes | -| **kanon-reviewer** | Webhook: PR comment `/kelos review` | Codex | Reviews PRs on demand — analyzes code, checks conventions, and submits structured reviews | +| **kanon-reviewer** | Webhook: PR comment/review with `/kelos review` or `/kelos api-review` | Codex | Reviews PRs on demand — analyzes code, checks conventions, and submits structured reviews | | **kanon-pr-responder** | Webhook: PR review/comment with `/kelos pick-up` | Codex | Re-engages on PR review feedback and updates the existing branch incrementally | | **kanon-triage** | Webhook: issue opened/reopened (untriaged) | Codex | Classifies issues by kind/priority, detects duplicates, and recommends an actor | | **kanon-fake-user** | Cron (daily 09:00 UTC) | Codex | Tests DX as a new user — follows docs, tries CLI workflows, files issues for problems found | @@ -41,8 +41,13 @@ maintain (`kanon-development/*`) live in *this* repository, so they use the | **kanon-self-update** | Cron (daily 06:00 UTC) | Codex | Reviews and tunes the `kanon-development/` prompts, configs, and README — the pipeline improves itself | | **kanon-squash-commits** | Webhook: PR comment `/kelos squash-commits` | Codex | Rebases and squashes PR branch commits into a single clean commit | -> **Not ported from `self-development/`:** `kelos-api-reviewer` (Kanon has no -> Kubernetes CRDs/API surface to review) and `kelos-image-update` (Kanon has no +> **Not ported from `self-development/`:** `kelos-api-reviewer` — Kanon has no +> Kubernetes CRDs, so there is no separate API-review spawner. Kanon's +> user-facing API surface (CLI commands/flags and the `kanon.yaml` schema) is +> reviewed by `kanon-reviewer` directly (see its "CLI and config surfaces are +> special" section), and triaged via the `kind/api` label. `/kelos api-review` +> is accepted as an alias for `/kelos review` on Kanon PRs; both commands run +> the same reviewer. `kelos-image-update` is also not ported (Kanon has no > coding-agent Dockerfiles to bump). Apply the whole directory at once — this includes `agentconfig.yaml`, which @@ -99,11 +104,11 @@ kubectl apply -f kanon-development/kanon-planner.yaml ### kanon-reviewer.yaml -Reviews open pull requests on demand when a maintainer posts `/kelos review`. +Reviews open pull requests on demand when a maintainer posts `/kelos review` or `/kelos api-review`. | | | |---|---| -| **Trigger** | GitHub PR comment webhook with `/kelos review` | +| **Trigger** | GitHub PR comment/review webhook with `/kelos review` or `/kelos api-review` | | **Agent** | Codex | | **Concurrency** | 3 | @@ -116,8 +121,8 @@ Reviews open pull requests on demand when a maintainer posts `/kelos review`. - Read-only agent — does not push code, modify files, or run local validation **Handoff flow:** -1. `/kelos review` — requests a code review on the PR -2. `/kelos review` — maintainer can retrigger review after changes are pushed +1. `/kelos review` or `/kelos api-review` — requests a code review on the PR +2. `/kelos review` or `/kelos api-review` — maintainer can retrigger review after changes are pushed **Deploy:** ```bash diff --git a/kanon-development/kanon-reviewer.yaml b/kanon-development/kanon-reviewer.yaml index 9f1bf434d..24c144fe4 100644 --- a/kanon-development/kanon-reviewer.yaml +++ b/kanon-development/kanon-reviewer.yaml @@ -45,13 +45,13 @@ spec: filters: - event: issue_comment action: created - bodyPattern: /kelos review + bodyPattern: /kelos (api-)?review commentOn: PullRequest state: open author: gjkim42 - event: pull_request_review action: submitted - bodyPattern: /kelos review + bodyPattern: /kelos (api-)?review state: open author: gjkim42 reporting: