Skip to content

Commit

Permalink
[feat] Add incremental review (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
baseballyama authored Nov 5, 2023
1 parent cbbde40 commit ec10fff
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-queens-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ai-craftsman": minor
---

feat: Add incremental review feature
7 changes: 5 additions & 2 deletions .github/workflows/ai-review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: AI Review

on:
pull_request:
issue_comment:
types: [created]
workflow_dispatch:

concurrency:
Expand All @@ -12,6 +14,9 @@ jobs:
test:
timeout-minutes: 15
runs-on: ubuntu-latest
if: >
github.event_name == 'pull_request' ||
(github.event.issue.pull_request && startsWith(github.event.comment.body, '/ai-review-flex'))
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -33,10 +38,8 @@ jobs:
- name: AI Review
run: node packages/ai-craftsman/dist/bin.js
env:
BASE_REF: ${{ github.base_ref }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT_PATH: ${{ github.event_path }}
LANGUAGE: Japanese
CODING_GUIDE_PATH: ExampleOfCodigRules.md
# CODING_GUIDE_READER: packages/ai-craftsman/dist/ci/codingGuide.js
Expand Down
28 changes: 0 additions & 28 deletions ExampleOfCodigRules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,13 @@

The following are sample coding rules that `AI Review Flex`. These rules are written in markdown and should be placed in a file (e.g., `coding_guide.md`) within your repository.

## Use camelCase for variable names

Ensure that all your variable names are in camelCase. For example, use `employeeList` instead of `EmployeeList` or `employee_list`.

- File Pattern: \.(js|ts|jsx|tsx)$
- AI Review: ON

## Functions should have descriptive names

Function names should clearly describe what the function does using verb-noun pairs when applicable, such as `calculateTotal` or `getUserInfo`.

- File Pattern: \.(js|ts)$
- AI Review: ON

## Constants should be in uppercase

All constants should be declared using uppercase letters and underscore separators, like `MAX_RETRY_COUNT`.

- File Pattern: \.(js|ts|jsx|tsx)$
- AI Review: ON

## Comment public methods and classes

All public methods and classes should have JSDoc comments describing their purpose, parameters, and return values.

- File Pattern: \.(js|ts)$
- AI Review: ON

## Avoid 'any' type in TypeScript

The use of `any` type should be avoided in TypeScript files. Instead, use specific types or generics for better type safety.

- File Pattern: \.ts$
- AI Review: ON

## No console logs in production code

Remove all `console.log` statements from the production code to avoid leaking information and cluttering the output.
Expand Down
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ Incorporate this action in your workflow (`.github/workflows/main.yml`) with the
- name: AI Review Flex
uses: baseballyama/ai-review-flex@main
with:
BASE_REF: ${{ github.base_ref }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_EVENT_PATH: ${{ github.event_path }}
LANGUAGE: "English"
CODING_GUIDE_PATH: "path/to/guide.md"
CODING_GUIDE_LEVEL: 2
Expand Down Expand Up @@ -75,6 +73,22 @@ CODING_GUIDE_READER: "path/to/your/rules-reader.js"
Please see [ExampleOfCodigRules.md](./ExampleOfCodigRules.md) to check example of coding rules.
### Incremental Reviews
`AI Review Flex` supports not only one-time comprehensive reviews but also incremental reviews. Incremental reviews focus on changes made since the last code analysis, allowing for continuous integration of feedback and improvements. To initiate an incremental review on your pull request, simply add the following comment:

```markdown
/ai-review-flex incremental
```

For a full review that encompasses all changes in the pull request, use the comment:

```markdown
/ai-review-flex
```

This feature ensures that your team can keep code quality high by systematically reviewing incremental changes, thus making code reviews more efficient and focused.

## Usage

Configure the action, and it will automatically review PRs based on your rules.
Expand Down
6 changes: 0 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
name: "AI Craftsman"
description: "Fully customizable AI reviewer"
inputs:
BASE_REF:
description: "Value of github.base_ref"
required: true
OPENAI_API_KEY:
description: "OpenAI API Key"
required: true
GITHUB_TOKEN:
description: "Value of secrets.GITHUB_TOKEN"
required: true
GITHUB_EVENT_PATH:
description: "Value of github.event_path"
required: true
LANGUAGE:
description: "Your preferred language. Example: English"
required: false
Expand Down
19 changes: 18 additions & 1 deletion packages/ai-craftsman/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getDiff,
postComment,
postReviewComment,
getLatestCommitIdByTheApp,
} from "./utils/git.js";
import { splitForEachDiff } from "./utils/diff.js";
import { promiseAllWithConcurrencyLimit } from "./utils/concurrent.js";
Expand Down Expand Up @@ -60,10 +61,26 @@ const main = async () => {
return;
}

let isIncremental = false;
if (env.github.comment) {
if (!env.github.comment.startsWith("/ai-review-flex")) {
console.log(
"Skip AI review because the comment doesn't starts with /ai-review-flex."
);
return;
}
if (env.github.comment.includes("incremental")) {
isIncremental = true;
}
}

const rules = await readCodingRules();
const promises: (() => Promise<void>)[] = [];
let commented = false;
for (const { diff, path } of getDiff(env.github.baseRef)) {
const targetBranch = isIncremental
? await getLatestCommitIdByTheApp()
: env.github.baseRef;
for (const { diff, path } of getDiff(targetBranch)) {
if (excludePatterns.some((pattern) => pattern.test(path))) {
console.log(`SKIP REVIEW: ${path}`);
continue;
Expand Down
19 changes: 17 additions & 2 deletions packages/ai-craftsman/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import github from "@actions/github";

const getEnv = (
key: string,
option: {
Expand All @@ -15,12 +17,25 @@ const getEnv = (
return value;
};

const required = <T>(key: string, value: T | undefined): T => {
if (!value) throw new Error(`${key} should be set.`);
return value;
};

export const env = {
github: {
token: getEnv("GITHUB_TOKEN", { required: true }),
repository: getEnv("GITHUB_REPOSITORY", { required: true }),
repository: required(
"GITHUB_REPOSITORY",
github.context.payload?.repository
),
eventPath: getEnv("GITHUB_EVENT_PATH", { required: true }),
baseRef: getEnv("BASE_REF", { required: true }),
baseRef: required(
"github.base_ref",
String(github.context.payload.pull_request?.["base"]?.ref || "")
),
comment:
String(github.context?.payload?.comment?.["body"] ?? "") || undefined,
},
openaiApiKey: getEnv("OPENAI_API_KEY", { required: true }),
language: getEnv("LANGUAGE", { defaultValue: "English", required: true }),
Expand Down
13 changes: 9 additions & 4 deletions packages/ai-craftsman/src/utils/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ const getOctokit = () => {
};

const getOwnerAndRepo = () => {
const ownerAndRepo = env.github.repository?.split("/") ?? [];
const owner = ownerAndRepo[0] ?? "";
const repo = ownerAndRepo[1] ?? "";
const owner = env.github.repository?.owner?.login ?? "";
const repo = env.github.repository?.name ?? "";
return { owner, repo };
};

Expand Down Expand Up @@ -68,10 +67,16 @@ export const getCommentsOrderByCreatedAtDesc = async () => {
};

export const hasCommentByTheApp = async (): Promise<boolean> => {
const comments = await await getCommentsOrderByCreatedAtDesc();
const comments = await getCommentsOrderByCreatedAtDesc();
return comments.some((c) => c.body.includes(`<!-- COMMIT_ID: `));
};

export const getLatestCommitIdByTheApp = async (): Promise<string> => {
const comments = await getCommentsOrderByCreatedAtDesc();
const comment = comments.find((c) => c.body.includes(`<!-- COMMIT_ID: `));
return comment?.body.match(/<!-- COMMIT_ID:\s*(.+)\s*-->/)?.[1] ?? "";
};

export const postComment = async (body: string) => {
try {
const octokit = getOctokit();
Expand Down

0 comments on commit ec10fff

Please sign in to comment.