-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
13e475e
commit 1890ed4
Showing
8 changed files
with
507 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
name: CI | ||
|
||
on: | ||
pull_request: | ||
workflow_dispatch: | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
test: | ||
timeout-minutes: 15 | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- uses: pnpm/action-setup@v2 | ||
with: | ||
version: 8 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version: 20 | ||
cache: "pnpm" | ||
|
||
- name: prebuild | ||
run: pnpm install --frozen-lockfile | ||
|
||
- name: Build | ||
run: cd packages/ai-craftsman && pnpm build | ||
|
||
- name: AI Review | ||
run: node packages/ai-craftsman/dist/ci.js | ||
env: | ||
BASE_REF: ${{ github.base_ref }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { getDiff } from "./git.js"; | ||
import { chunk } from "./diff.js"; | ||
import { getTokenCount, chat } from "./openai.js"; | ||
|
||
const main = async () => { | ||
const baseref = process.env["BASE_REF"]; | ||
if (baseref == null || baseref === "") { | ||
throw new Error("BASE_REF is not set"); | ||
} | ||
|
||
for (const { diff, file } of getDiff(baseref)) { | ||
const chunked = chunk({ | ||
source: diff, | ||
counter: getTokenCount, | ||
maxCount: 500, | ||
duplicateLines: 2, | ||
}); | ||
for (const { source, startRow, endRow } of chunked) { | ||
await chat([ | ||
{ | ||
content: "次のコードをレビューしてください", | ||
role: "system", | ||
}, | ||
{ | ||
content: source, | ||
role: "user", | ||
}, | ||
]); | ||
} | ||
} | ||
}; | ||
|
||
void main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { describe, test, expect } from "vitest"; | ||
import { parse, chunk } from "./diff"; | ||
|
||
describe("source", () => { | ||
const source = `\ | ||
const PORT = 8080; | ||
const HOME_PATH = '/home'; | ||
const ABOUT_PATH = '/about'; | ||
const SUBMIT_PATH = '/submit'; | ||
function isPortAvailable(port) { | ||
return port == PORT; | ||
} | ||
function listen(port, callback) { | ||
callback({ type: 'GET', path: HOME_PATH }); | ||
} | ||
function routeToController(method, path, callback) { | ||
callback(); | ||
} | ||
function renderTemplate(template, callback) { | ||
callback(); | ||
} | ||
function fetchDataFromDB(key, callback) { | ||
callback({ name: 'John', age: 30 }); | ||
} | ||
function validateData(data) { | ||
return data != null; | ||
} | ||
function saveToDB(data) { | ||
print("Data saved."); | ||
} | ||
function log(message) { | ||
print("Log: " + message); | ||
} | ||
function respondWithError(message) { | ||
print("Error: " + message); | ||
} | ||
function isNotEmpty(data) { | ||
return data != null; | ||
} | ||
function insertIntoTemplate(data) { | ||
print("Inserting data into template."); | ||
} | ||
function print(message) { | ||
// Simulate printing a message to the console | ||
return "Printed: " + message; | ||
} | ||
function onRequest(req) { | ||
routeToController(req.type, req.path, () => { | ||
if (req.path == HOME_PATH) { | ||
renderTemplate('home.html', () => { | ||
populateData(() => { | ||
fetchDataFromDB('home_data', (data) => { | ||
if (isNotEmpty(data)) { | ||
insertIntoTemplate(data); | ||
} else { | ||
log('No data to display'); | ||
} | ||
}); | ||
}); | ||
}); | ||
} else if (req.path == ABOUT_PATH) { | ||
renderTemplate('about.html', () => {}); | ||
} else { | ||
renderTemplate('404.html', () => {}); | ||
} | ||
}); | ||
} | ||
function main() { | ||
if (isPortAvailable(PORT)) { | ||
listen(PORT, onRequest); | ||
} else { | ||
log(\`Port \${PORT} is not available\`); | ||
} | ||
} | ||
main();`; | ||
|
||
test("parse", async () => { | ||
expect(parse(source).length).toEqual(85); | ||
}); | ||
|
||
test("chunk", async () => { | ||
const result = chunk({ | ||
source, | ||
counter: (str: string) => str.length, | ||
maxCount: 200, | ||
duplicateLines: 2, | ||
}); | ||
|
||
expect(result.length).toEqual(11); | ||
expect(result[0].startRow).toEqual(0); | ||
expect(result[0].endRow).toEqual(9); | ||
expect(result[1].startRow).toEqual(8); | ||
expect(result[1].endRow).toEqual(18); | ||
expect(result[10].startRow).toEqual(78); | ||
expect(result[10].endRow).toEqual(84); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
interface ChunkedSource { | ||
source: string; | ||
startRow: number; | ||
endRow: number; | ||
} | ||
|
||
export interface SourceRow { | ||
source: string; | ||
level: number; | ||
} | ||
|
||
export const parse = (source: string): SourceRow[] => { | ||
const rows: SourceRow[] = []; | ||
const lines = source.split("\n"); | ||
for (const line of lines) { | ||
const level = line.match(/^[\s\t]*/)?.[0]?.length ?? 0; | ||
rows.push({ source: line, level }); | ||
} | ||
return rows; | ||
}; | ||
|
||
export const chunk = ({ | ||
source, | ||
counter, | ||
maxCount, | ||
duplicateLines = 0, | ||
}: { | ||
source: string; | ||
counter: (str: string) => number; | ||
maxCount: number; | ||
duplicateLines?: number; | ||
}): ChunkedSource[] => { | ||
const getChunkedSource = (currentIndex: number): ChunkedSource => { | ||
const currentLine = parsed[currentIndex]?.source; | ||
const prevLine = parsed[currentIndex - 1]?.source ?? ""; | ||
const prevPrevLine = parsed[currentIndex - 2]?.source ?? ""; | ||
let source = prevPrevLine; | ||
if (source.length > 0) source += "\n"; | ||
source += prevLine; | ||
if (source.length > 0) source += "\n"; | ||
source += currentLine; | ||
const row = Math.max(0, currentIndex - duplicateLines); | ||
return { source, startRow: row, endRow: row }; | ||
}; | ||
|
||
const chunks: ChunkedSource[] = []; | ||
let cur: ChunkedSource = { source: "", startRow: 0, endRow: 0 }; | ||
const parsed = parse(source); | ||
parsed.forEach((line, index) => { | ||
if (counter(`${cur.source}${line.source.length}`) > maxCount) { | ||
chunks.push(cur); | ||
cur = getChunkedSource(index); | ||
} else { | ||
cur.source += line.source + "\n"; | ||
cur.endRow = index; | ||
} | ||
}); | ||
if (cur.source.length > 0) { | ||
chunks.push(cur); | ||
} | ||
|
||
return chunks; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** | ||
* reference: https://gist.github.com/drwpow/86b11688babd6d1251b90e22ef7354ba | ||
*/ | ||
|
||
import { readFileSync } from "node:fs"; | ||
import { execSync } from "node:child_process"; | ||
|
||
export const getDiff = (targetBranch: string) => { | ||
const currentBranch = execSync("git rev-parse --abbrev-ref HEAD") | ||
.toString() | ||
.trim(); | ||
|
||
const branch = | ||
currentBranch === targetBranch | ||
? `HEAD~1..HEAD` | ||
: `origin/${targetBranch}..${currentBranch}`; | ||
|
||
const diffFiles = execSync( | ||
`git --no-pager diff --minimal --name-only ${branch}` | ||
) | ||
.toString() | ||
.split("\n") | ||
.map((ln) => ln.trim()) | ||
.filter((ln) => !!ln); | ||
|
||
return diffFiles.map((diffFile) => { | ||
const file = readFileSync(diffFile, "utf-8"); | ||
const diff = execSync(`git --no-pager diff --minimal ${branch} ${diffFile}`) | ||
.toString() | ||
.trim(); | ||
return { file, diff }; | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { getEncoding } from "tiktoken-node"; | ||
import OpenAI from "openai"; | ||
|
||
const openai = new OpenAI({ | ||
apiKey: String(process.env["OPENAI_API_KEY"]), | ||
}); | ||
|
||
const encoding = getEncoding("cl100k_base"); | ||
|
||
export const getTokenCount = (text: string): number => { | ||
return encoding.encode(text).length; | ||
}; | ||
|
||
export const chat = async (messages: OpenAI.ChatCompletionMessageParam[]) => { | ||
const tokenCount = messages.reduce((acc, message) => { | ||
return acc + getTokenCount(message.content ?? ""); | ||
}, 0); | ||
|
||
const model = | ||
tokenCount * 1.1 + 1024 < 4 * 1024 ? "gpt-3.5-turbo" : "gpt-3.5-turbo-16k"; | ||
const response = await openai.chat.completions.create({ | ||
model, | ||
messages, | ||
temperature: 0, | ||
max_tokens: Math.min(1024, (16 * 1024 - tokenCount) * 0.9), | ||
}); | ||
|
||
console.log({ messages, response }); | ||
}; |
Oops, something went wrong.