From 94214782a958eb457b4d993eb72b8896750a59ea Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 5 Nov 2023 11:04:05 +0900 Subject: [PATCH] improve --- .github/workflows/ai-review.yaml | 6 +- ExampleOfCodigRules.md | 59 +++++++ GUIDE.md | 176 -------------------- README.md | 89 +++++++++- action.yml | 6 +- packages/ai-craftsman/src/bin.ts | 25 ++- packages/ai-craftsman/src/ci/codingGuide.ts | 18 +- packages/ai-craftsman/src/config.ts | 9 +- 8 files changed, 188 insertions(+), 200 deletions(-) create mode 100644 ExampleOfCodigRules.md delete mode 100644 GUIDE.md diff --git a/.github/workflows/ai-review.yaml b/.github/workflows/ai-review.yaml index 2b1c072..9a40c0d 100644 --- a/.github/workflows/ai-review.yaml +++ b/.github/workflows/ai-review.yaml @@ -38,8 +38,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_EVENT_PATH: ${{ github.event_path }} LANGUAGE: Japanese - DEBUG: true - CODING_GUIDE_PATH: GUIDE.md - CODING_GUIDE_LEVEL: 2 - CODING_GUIDE_ENABLE_PATTERN: "AI Review.*ON" + CODING_GUIDE_PATH: ExampleOfCodigRules.md # CODING_GUIDE_READER: packages/ai-craftsman/dist/ci/codingGuide.js + DEBUG: true diff --git a/ExampleOfCodigRules.md b/ExampleOfCodigRules.md new file mode 100644 index 0000000..5b73351 --- /dev/null +++ b/ExampleOfCodigRules.md @@ -0,0 +1,59 @@ +# Sample Coding Rules for AI Review Flex + +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. + +- File Pattern: \.(js|ts|jsx|tsx)$ +- AI Review: ON + +## SQL Naming Conventions + +Use snake*case for all SQL identifiers, including table names and column names. Prefix tables with `tbl*`and views with`view\_`. + +- File Pattern: `\.(sql)$` +- AI Review: ON + +## Commit Messages + +All commit messages should follow the conventional commit format for consistency and clarity. + +- File Pattern: `\.(md|txt|docx)$` +- AI Review: OFF diff --git a/GUIDE.md b/GUIDE.md deleted file mode 100644 index 7a4d57e..0000000 --- a/GUIDE.md +++ /dev/null @@ -1,176 +0,0 @@ -## コンポーネントファイル構造 - -| Meta | Content | -| --------- | ----------------------------------------------- | -| Level | 必須 | -| Link | https://angular.io/guide/styleguide#style-04-06 | -| AI Review | OFF | - -### 説明 - -各コンポーネントは独自のディレクトリを持ち、そのディレクトリ内にはコンポーネントのクラスファイル、テンプレート、スタイル、テストが含まれるべきです。 - -### 理由 - -コンポーネントの関連ファイルを一箇所に集めることで、コンポーネントの再利用性を高め、管理を容易にします。また、新しい開発者がプロジェクトに参加した際の学習コストを削減します。 - -### 例 - -``` -// 良いコードの例 -/src - /app - /button - button.component.ts - button.component.html - button.component.css - button.component.spec.ts - -// 悪いコードの例 -/src - /components - button.ts - /styles - button.css - /views - button.html -``` - -### 例外ケース - -- ユーティリティ関数やサービスなど、特定のコンポーネントに紐付かない共有ファイルは共通のディレクトリに配置する。 - -### リファクタリングガイドライン - -既存プロジェクトでは、大幅なディレクトリ構造の変更を避け、新規コンポーネントからこの規則に従うこと。時間が許せば、徐々に既存のコンポーネントも新しいファイル構造に移行させる。 - ---- - -## 非同期操作の取り扱い - -| Meta | Content | -| --------- | ------------------------------------------------------------------------------------------- | -| Level | 必須 | -| Link | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function | -| AI Review | ON | - -### 説明 - -非同期操作は`async/await`パターンを使用して実装すること。コールバックや`.then()`、`.catch()`メソッドを用いた Promise チェーンは避ける。 - -### 理由 - -`async/await`は非同期コードをより読みやすく、同期的な流れで記述できるためです。コードの可読性とメンテナンス性が向上します。 - -### 例 - -```typescript -// 良いコードの例 -async function getUser(id: string): Promise { - try { - const response = await fetch(`/api/users/${id}`); - const user = await response.json(); - return user; - } catch (error) { - throw new Error(error); - } -} - -// 悪いコードの例 -function getUser(id: string): Promise { - return fetch(`/api/users/${id}`) - .then((response) => response.json()) - .then((user) => user) - .catch((error) => { - throw new Error(error); - }); -} -``` - -### 例外ケース - -- 既存のコードベースで`Promise`チェーンが広範に使用されている場合は、段階的に`async/await`にリファクタリングする。 - -### リファクタリングガイドライン - -新規プロジェクトや新規機能では`async/await`を用いること。既存のコードは、重要なバグ修正や機能追加の際に、この新しいパターンに徐々に置き換えていく。 - -## 型定義の適用 - -| Meta | Content | -| --------- | ------------------------------------------------------------------ | -| Level | 必須 | -| Link | https://www.typescriptlang.org/docs/handbook/2/everyday-types.html | -| AI Review | ON | - -### 説明 - -全ての変数、関数の引数、および戻り値には明示的な型またはインターフェースを指定すること。 - -### 理由 - -型を明示的に宣言することで、コンパイル時の型チェックの恩恵を受けられ、ランタイムエラーのリスクを減らします。また、コードの意図が明確になり、他の開発者がコードを理解しやすくなります。 - -### 例 - -```typescript -// 良いコードの例 -function add(x: number, y: number): number { - return x + y; -} - -// 悪いコードの例 -function add(x, y) { - return x + y; -} -``` - -### 例外ケース - -- ライブラリが提供する関数や変数の型が any である場合は、適宜型アサーションを使用する。 - -### リファクタリングガイドライン - -既存のコードでは、任意の型が使われていた場合、それを具体的な型に置き換えていく。新規コードでは初めから型を適用する。 - ---- - -## ユニットテストの実施 - -| Meta | Content | -| --------- | -------------------------------------- | -| Level | 推奨 | -| Link | https://jestjs.io/docs/getting-started | -| AI Review | OFF | - -### 説明 - -新しく追加する全ての関数に対してユニットテストを書き、既存の関数についても段階的にテストを追加する。 - -### 理由 - -ユニットテストにより、コードの品質を保ち、将来的な機能追加やリファクタリングが安全に行われるようにします。バグの発見を早期に行い、修正コストを削減する効果もあります。 - -### 例 - -```typescript -// 良いコードの例(テストの例) -describe("add function", () => { - it("adds two numbers correctly", () => { - expect(add(1, 2)).toBe(3); - }); -}); - -// 悪いコードの例(テストがない) -function add(x: number, y: number): number { - return x + y; -} -``` - -### 例外ケース - -- 外部 API の結果に依存する関数など、テストが困難なケースでは、モックを使用して依存関係を切り離す。 - -### リファクタリングガイドライン - -テストがない既存の関数に対しては、新しいバグ報告や機能追加があるたびにユニットテストを追加していく。新規関数では最初からテストを書くことをルール化する。 diff --git a/README.md b/README.md index 5db0eb7..58fbb1f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,88 @@ -# AI Craftman +# AI Review Flex -AI reviewer configurable for you. +`AI Review Flex` is a versatile GitHub Action that employs AI to review code against project-specific guidelines. By interpreting markdown coding guides, it delivers tailored feedback for each pull request to uphold your coding standards. -## What is this +## Prerequisites -Fully configurable AI reviewers that review according to project-specific coding conventions. +To use `AI Review Flex`, make sure you have: ---- +- A GitHub repository. +- An OpenAI API Key. -The rest of the README is WIP +## Setup + +Incorporate this action in your workflow (`.github/workflows/main.yml`) with the required settings: + +```yaml +- 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 + CODING_GUIDE_ENABLE_PATTERN: "AI Review.*ON" + CODING_GUIDE_FILE_PATTERN: "File Pattern:\\s*`?(.+)`?$" + CODING_GUIDE_READER: "path/to/custom_reader.js" + DEBUG: false +``` + +## Customizing Coding Rules + +Configure these parameters in the workflow for tailored coding standard reviews: + +| Parameter | Description | Required | Default | +| ----------------------------- | -------------------------------------------- | -------- | --------------------------- | +| `CODING_GUIDE_PATH` | Path to markdown coding rules. | No | - | +| `CODING_GUIDE_LEVEL` | Markdown level to interpret as rules. | No | `2` | +| `CODING_GUIDE_ENABLE_PATTERN` | Regex pattern to match reviewed rules. | No | `AI Review.*ON` | +| `CODING_GUIDE_FILE_PATTERN` | Regex for file paths to apply rules. | No | `File Pattern:\s*/?(.+)/?$` | +| `CODING_GUIDE_READER` | Path to a `.js` custom rule reader function. | No | - | +| `LANGUAGE` | Language preference for reviews. | No | "English" | +| `DEBUG` | Enable debug mode for detailed logs. | No | `false` | + +Example custom rule reader function: + +```javascript +import * as fs from "node:fs"; + +export default async (): Promise<{ rule: string, filePattern: RegExp }[]> => { + const markdown = await fs.promises.readFile("GUIDE.md", "utf-8"); + return markdown + .split("## ") + .map((rule) => `## ${rule}`) + .filter(() => { + /** some filter process */ + return true; + }) + .map((rule) => { + const [, filePattern = ".*"] = rule.match(/File Pattern: (\S+)$/m) ?? []; + return { rule, filePattern: RegExp(filePattern) }; + }); +}; +``` + +Set the custom reader in your workflow as: + +```yaml +CODING_GUIDE_READER: "path/to/your/rules-reader.js" +``` + +## Example of Coding Rules + +Please see [ExampleOfCodigRules.md](./ExampleOfCodigRules.md) to check example of coding rules. + +## Usage + +Configure the action, and it will automatically review PRs based on your rules. + +## Contributing + +Suggestions and contributions are appreciated! Feel free to submit issues or pull requests. + +## License + +Distributed under the MIT License. diff --git a/action.yml b/action.yml index 14076f7..8f4554e 100644 --- a/action.yml +++ b/action.yml @@ -27,7 +27,11 @@ inputs: description: "Hierarchy of markdowns representing each rule." required: false CODING_GUIDE_ENABLE_PATTERN: - description: "Regular expression patterns for rules that enable review by AI." + description: "Regex patterns for rules that enable review by AI." + required: false + default: "AI Review.*ON" + CODING_GUIDE_FILE_PATTERN: + description: "Regex pattern to match file path that the rules should apply to." required: false default: ".*" CODING_GUIDE_READER: diff --git a/packages/ai-craftsman/src/bin.ts b/packages/ai-craftsman/src/bin.ts index f02e368..6c8b22c 100644 --- a/packages/ai-craftsman/src/bin.ts +++ b/packages/ai-craftsman/src/bin.ts @@ -22,17 +22,28 @@ const excludePatterns = [ /.*\.md$/, ]; -const readCodingRules = async (): Promise => { +interface ConfigRule { + rule: string; + filePattern: RegExp; +} + +const readCodingRules = async (): Promise => { const { codingGuide } = env; if (codingGuide.reader != null) { const module = await import(path.resolve(codingGuide.reader)); - return (await module.default()) as string[]; + return (await module.default()) as ConfigRule[]; } - if (codingGuide.path != null && codingGuide.level != null) { + if (codingGuide.path != null) { const markdown = await fs.promises.readFile(codingGuide.path, "utf-8"); const parsed = parseMarkdown(markdown); const chunked = chunkMarkdownByLevel(parsed, codingGuide.level); - return chunked.filter((chunk) => codingGuide.enablePattern.test(chunk)); + return chunked + .filter((chunk) => codingGuide.enablePattern.test(chunk)) + .map((chunk) => { + const [, filePattern = "^$"] = + chunk.match(codingGuide.filePattern) ?? []; + return { rule: chunk, filePattern: RegExp(filePattern) }; + }); } throw new Error( @@ -62,6 +73,10 @@ const main = async () => { for (const df of splitForEachDiff(diff)) { if (df.type === "delete") continue; for (const rule of rules) { + if (!rule.filePattern.test(path)) { + console.log(`SKIP REVIEW: ${path} by ${rule.rule.split("\n")[0]}`); + continue; + } const randomId = Math.random().toString(32).substring(2); promises.push(async () => { if (env.debug) { @@ -71,7 +86,7 @@ const main = async () => { diff: df.diff, }); } - const reviewComments = await review(rule, df.diff, env.language); + const reviewComments = await review(rule.rule, df.diff, env.language); for (const comment of reviewComments) { if (env.debug) { console.debug(`Receive REVIEW (${randomId})`, comment); diff --git a/packages/ai-craftsman/src/ci/codingGuide.ts b/packages/ai-craftsman/src/ci/codingGuide.ts index 082ee4d..53730c5 100644 --- a/packages/ai-craftsman/src/ci/codingGuide.ts +++ b/packages/ai-craftsman/src/ci/codingGuide.ts @@ -1,10 +1,16 @@ import * as fs from "node:fs"; -import { parseMarkdown, chunkMarkdownByLevel } from "../utils/markdown.js"; -export default async (): Promise => { +export default async (): Promise<{ rule: string; filePattern: RegExp }[]> => { const markdown = await fs.promises.readFile("GUIDE.md", "utf-8"); - const parsed = parseMarkdown(markdown); - return chunkMarkdownByLevel(parsed, 2).filter((chunk) => { - return chunk.match(/AI Review.*ON/g); - }); + return markdown + .split("## ") + .map((rule) => `## ${rule}`) + .filter(() => { + /** some filter process */ + return true; + }) + .map((rule) => { + const [, filePattern = ".*"] = rule.match(/File Pattern: (.+)$/m) ?? []; + return { rule, filePattern: RegExp(filePattern) }; + }); }; diff --git a/packages/ai-craftsman/src/config.ts b/packages/ai-craftsman/src/config.ts index 064f606..94870d1 100644 --- a/packages/ai-craftsman/src/config.ts +++ b/packages/ai-craftsman/src/config.ts @@ -28,9 +28,14 @@ export const env = { codingGuide: { reader: getEnv("CODING_GUIDE_READER") || undefined, path: getEnv("CODING_GUIDE_PATH") || undefined, - level: Number(getEnv("CODING_GUIDE_LEVEL")) || undefined, + level: Number(getEnv("CODING_GUIDE_LEVEL", { defaultValue: "2" })), enablePattern: RegExp( - getEnv("CODING_GUIDE_ENABLE_PATTERN", { defaultValue: ".*" }) + getEnv("CODING_GUIDE_ENABLE_PATTERN", { defaultValue: "AI Review.*ON" }) + ), + filePattern: RegExp( + getEnv("CODING_GUIDE_FILE_PATTERN", { + defaultValue: "File Pattern:\\s*`?(.+)`?$", + }) ), }, };