Author SHA1 Message Date
Jeffery 2f54271c38 fix(Dockerfile): 修正 RUN 指令換行與清理路徑導致 build 失敗
CI / Release Tag Version (pull_request) Successful in 6s
CI / AI Code Review (pull_request) Failing after 54s
2026-07-01 09:51:20 +08:00
Jeffery b3878b9860 feat(Dockerfile): 固定版本號
CI / Release Tag Version (pull_request) Successful in 7s
CI / AI Code Review (pull_request) Failing after 6s
2026-07-01 09:44:53 +08:00
Jeffery 5a07f03abb feat(Dockerfile): 修改映像檔來源
CI / Release Tag Version (pull_request) Successful in 2m23s
CI / AI Code Review (pull_request) Failing after 34s
2026-06-30 13:50:58 +08:00
jiantw83 2703840018 chore(ai-review 狀態): 清空已處理 findings 並登記排除項目
CI / Release Tag Version (pull_request) Successful in 6s
CI / AI Code Review (pull_request) Failing after 12s
2026-06-29 10:49:22 +00:00
jiantw83 45b6abacc8 chore(Docker action): 整理 Docker action 執行設定 2026-06-29 10:49:22 +00:00
jiantw83 0b59ca6023 refactor(LLM CLI): 明確化 CLI 參數與驗證輸出 2026-06-29 10:49:22 +00:00
jiantw83 68ce92b0e5 fix(TLS 驗證): 移除 git 與 OpenRouter 的不安全憑證略過設定 2026-06-29 10:49:22 +00:00
13 changed files with 51 additions and 174 deletions
+6
View File
@@ -16,5 +16,11 @@
"role": "Leo", "role": "Leo",
"original_finding": "extractJSONText is returning an unexpected result; suggest returning an empty array or throwing when no JSON is found.", "original_finding": "extractJSONText is returning an unexpected result; suggest returning an empty array or throwing when no JSON is found.",
"reason": "誤報:extractJSONText 的合約刻意是「盡力擷取,找不到合法 JSON 時回傳去 fence 後的原文」,呼叫端(chatJSON 等)依賴此行為再做後續解析與 AI 修復。改成回 [] 或丟例外會破壞既有呼叫鏈,且型別不一致(回傳值為字串非陣列),故不採納。" "reason": "誤報:extractJSONText 的合約刻意是「盡力擷取,找不到合法 JSON 時回傳去 fence 後的原文」,呼叫端(chatJSON 等)依賴此行為再做後續解析與 AI 修復。改成回 [] 或丟例外會破壞既有呼叫鏈,且型別不一致(回傳值為字串非陣列),故不採納。"
},
{
"location": "app/config.js:25",
"role": "Bard",
"original_finding": "`getInsecureHttpsAgent` 與 `getOpenCodeHttpsAgent` 同時存在,卻指向同一個物件來源,等於替同一段旋律寫了兩個名字,容易讓後續維護者搞不清楚哪個才是正規稱呼。",
"reason": "相容性保留:`getOpenCodeHttpsAgent` 是既有公開匯出名稱,README 與測試仍引用此語意名稱;本次已補上過渡別名註解,說明新程式碼應優先使用 `getInsecureHttpsAgent`,避免破壞既有呼叫端。"
} }
] ]
+1 -66
View File
@@ -1,66 +1 @@
[ []
{
"level": "warning",
"role": "Assassin",
"location": "app/git.js:67",
"problem": "這裡對所有 git 網路操作強制關閉 SSL 驗證。攻擊者若能攔截遠端回應,就能偽造 `ls-remote`、污染抓到的內容,甚至在有認證的情況下竊取 GITEA_TOKEN,讓後續審查建立在被污染的資料上。",
"suggestion": "移除 `GIT_SSL_NO_VERIFY=true`。若是內部憑證問題,改用正確的 CA bundle 或指定受信任憑證檔,不要整體關閉 git 的憑證驗證。",
"is_new": true
},
{
"level": "warning",
"role": "Assassin",
"location": "app/usage.js:180",
"problem": "這裡對 OpenRouter 額度查詢也明確套用不驗證憑證的 agent。攻擊者只要攔截這條 HTTPS 請求,就能讀到 `Authorization: Bearer` 的 API key,還能回傳假 quota 資料誤導流程判斷。",
"suggestion": "改回預設的 HTTPS 憑證驗證。若必須支援自簽憑證,僅對特定內部主機單獨配置受信任 CA,且不要對外部 API 一律關閉驗證。",
"is_new": true
},
{
"level": "warning",
"role": "Bard",
"location": "action.yaml:8",
"problem": "這個新加入的 `model` 輸入沒有任何描述,讓 action 的公開介面像是少了說明書的樂譜,讀者只能靠猜來理解它要吃什麼值。",
"suggestion": "補上一句簡短但明確的描述,例如說明這個欄位是「要使用的模型名稱」或「AI 助理 CLI 的模型識別字串」;讓使用者一眼看懂用途。",
"is_new": true
},
{
"level": "warning",
"role": "Bard",
"location": "app/config.js:4",
"problem": "`process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'` 放在模組載入時就直接生效,這種全域副作用太早出聲,會讓讀者在還沒看到任何呼叫前,就先被迫接受一個隱晦的環境改寫。",
"suggestion": "把這種全域設定移到明確的啟動流程或初始化函式中,至少搭配更醒目的註解,讓副作用出現的位置和目的都更清楚。",
"is_new": true
},
{
"level": "warning",
"role": "Bard",
"location": "app/config.js:25",
"problem": "`getInsecureHttpsAgent` 與 `getOpenCodeHttpsAgent` 同時存在,卻指向同一個物件來源,等於替同一段旋律寫了兩個名字,容易讓後續維護者搞不清楚哪個才是正規稱呼。",
"suggestion": "保留一個主名稱即可;如果一定要兼容舊名稱,請加上明確的遷移註解,說明哪個是新名字、哪個只是過渡別名。",
"is_new": true
},
{
"level": "warning",
"role": "Bard",
"location": "app/llm.js:27",
"problem": "`cliArgs(provider, model, promptFile = null, prompt = null)` 這個簽名把不同 provider 的參數意義混在一起,光看位置很難分辨哪個值在某條分支才有用,讀起來像把三種節拍硬塞進同一小節。",
"suggestion": "改成物件參數或拆成各 provider 專用的組裝函式,讓每個欄位的用途能被名字直接說明,而不是靠呼叫順序猜測。",
"is_new": true
},
{
"level": "warning",
"role": "Bard",
"location": "app/preflight.js:103",
"problem": "`verifyLLM` 的 JSDoc 仍然只描述 `provider` 與 `error`,但實際回傳已經多了 `command` 和 `model`。註解和程式碼不同步,會讓人以為這段函式比實際更單薄。",
"suggestion": "把 `@returns` 的型別與欄位補完整,讓文件精準反映目前回傳結構,避免讀者依舊沿用舊版心智模型。",
"is_new": true
},
{
"level": "info",
"role": "Bard",
"location": "app/preflight.js:170",
"problem": "成功訊息仍寫成 `LLM provider=... CLI 可用`,語氣還停在 provider 中心,但整個流程已改成 CLI 中心,術語有些半新半舊,節奏不夠一致。",
"suggestion": "把成功訊息統一成 CLI 觀點,例如直接報 `command=...` 或改成更中性的「LLM CLI 可用」,讓文字系統保持同一把調性。",
"is_new": true
}
]
+9 -30
View File
@@ -26,7 +26,7 @@ jobs:
id: release-tag-version id: release-tag-version
uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }} uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }}
with: with:
is_beta: 'true' is_beta: true
# job 識別碼:ai-code-review # job 識別碼:ai-code-review
ai-code-review: ai-code-review:
# job 顯示名稱 # job 顯示名稱
@@ -34,43 +34,22 @@ jobs:
needs: release-tag-version needs: release-tag-version
# 指定執行環境的 runner 標籤:ubuntu # 指定執行環境的 runner 標籤:ubuntu
runs-on: ubuntu runs-on: ubuntu
# 此 job 所需的權限設定
permissions:
# 對 repository 內容的寫入權限(讀寫程式碼/檔案)
contents: write
# 對 pull request 的寫入權限(讓 AI 可在 PR 上留言/審查)
pull-requests: write
# 對 issues 的寫入權限(建立/更新 issue 留言所需)
issues: write
# job 的執行步驟 # job 的執行步驟
steps: steps:
- name: 取得程式碼 - name: 取得程式碼
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }} uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
- name: 安裝 AI 工具
uses: https://gitea.jsc.idv.tw/composite-actions/codex@${{ vars.ACTION_CODEX_VERSION }}
with:
oauth: ${{ secrets.CODEX_OAUTH }}
- name: 檢查 AI 助理 CLI
shell: bash
run: |
set -e
tools="$(node --input-type=module -e "import { getLLMCLICommands } from './app/config.js'; console.log(getLLMCLICommands().join(' '));")"
found=""
for tool in $tools; do
if command -v "$tool" >/dev/null 2>&1; then
found="${found} ${tool}"
"$tool" --version || true
fi
done
if [ -z "$found" ]; then
echo "找不到可用的 AI 助理 CLI(需要 ${tools} 其中之一)" >&2
exit 1
fi
echo "可用 AI 助理 CLI:${found}"
- name: AI 程式碼審查 - name: AI 程式碼審查
# 使用外部 composite action 執行審查邏輯。 # 使用外部 composite action 執行審查邏輯。
# 版本由 release-tag-version 計算,便於在 PR 中測試目前 action 版本。 # 版本由 release-tag-version 計算,便於在 PR 中測試目前 action 版本。
uses: https://gitea.jsc.idv.tw/docker-actions/ai-code-review@v${{ needs.release-tag-version.outputs.version }} uses: https://gitea.jsc.idv.tw/docker-actions/ai-code-review@v${{ needs.release-tag-version.outputs.version }}
# 此 job 所需的權限設定
permissions:
# 對 repository 內容的寫入權限(讀寫程式碼/檔案)
contents: write
# 對 pull request 的寫入權限(讓 AI 可在 PR 上留言/審查)
pull-requests: write
# 對 issues 的寫入權限(建立/更新 issue 留言所需)
issues: write
# 傳遞給 composite action 的輸入參數 # 傳遞給 composite action 的輸入參數
with: with:
# 留言用 token:取自 secret COMMENT_TOKEN,供 action 在 PR 上發布審查留言 # 留言用 token:取自 secret COMMENT_TOKEN,供 action 在 PR 上發布審查留言
+6 -26
View File
@@ -1,34 +1,14 @@
# ============================================================================= FROM gitea.jsc.idv.tw/images/codex:0.1.1
# 用途:建置「AI 程式碼審查」Docker action 映像檔。
# 以 Alpine Linux 為基底,安裝 bash / git / Node.js / npm 等執行環境,
# 將 app/ 程式碼與相依套件打包進映像,並透過 entrypoint.sh 作為容器進入點,
# 供 CIGitea Actions)以 Docker action 形式執行 AI code review 流程。
# 更新日期:2026/06/26 11:34:46
# =============================================================================
# 指定支援中的 Alpine Linux 固定版本;Alpine 體積小,可縮小最終映像大小並加快拉取速度。 RUN apt update \
# 釘選明確版本可避免 latest tag 隨時間變動,提升建置可重現性。 && apt install -y --no-install-recommends git \
FROM alpine:3.24.1 && apt clean \
&& rm -rf /var/lib/apt/lists/*
# 安裝必要的工具
# 安裝執行 code review 所需的工具:bash(執行 entrypoint 腳本)、git(前置遠端驗證/取得 diff)、
# nodejs 與 npm(執行 app 內的 Node.js 程式)。
# --no-cache:不保留 apk 套件索引快取,避免殘留在映像層中以減少映像大小。
RUN apk add --no-cache bash git nodejs npm
# 將專案的 app/ 目錄複製到映像內的 /app;包含 Node.js 程式碼與 package.json 等相依宣告。
COPY ./app /app COPY ./app /app
# 進入 /app 安裝 npm 相依套件,使 Node.js 程式可在容器內正常執行。
# 副作用:會在 /app/node_modules 產生套件檔案,並依 package-lock.json(若存在)解析版本。
RUN cd /app && npm install RUN cd /app && npm install
# 將容器進入點腳本 entrypoint.sh 複製到映像根目錄 /entrypoint.sh COPY ./entrypoint.sh /entrypoint.sh
COPY entrypoint.sh /entrypoint.sh
# 賦予 entrypoint.sh 可執行權限,確保容器啟動時能直接執行該腳本。
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
# 設定容器進入點為 /entrypoint.shexec 形式,不經過 shell 解析);
# 容器啟動時即執行此腳本,作為 Docker action 的實際入口。
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
+14 -25
View File
@@ -4,34 +4,23 @@ author: 'Jeffery'
inputs: inputs:
token: token:
description: '' description: ''
required: true required: false
comment_token: comment_token:
description: '' description: ''
required: false required: false
model: model:
description: '' description: '要使用的 AI 助理 CLI 模型識別字串'
required: true required: true
runs: runs:
using: 'composite' using: 'docker'
steps: image: 'Dockerfile'
- name: 安裝 Node.js 相依套件 env:
shell: bash GITEA_SERVER_URL: ${{ gitea.server_url }}
run: | GITEA_REPOSITORY: ${{ gitea.repository }}
cd "${GITHUB_ACTION_PATH:-${GITEA_ACTION_PATH:-.}}/app" GITEA_TOKEN: ${{ inputs.token || gitea.token }}
npm ci GITEA_COMMENT_TOKEN: ${{ inputs.comment_token || inputs.token || gitea.token }}
- name: 執行 AI 程式碼審查 PR_NUMBER: ${{ gitea.event.pull_request.number }}
shell: bash PR_HEAD_SHA: ${{ gitea.event.pull_request.head.sha }}
env: PR_HEAD_BRANCH: ${{ gitea.event.pull_request.head.ref }}
GITEA_SERVER_URL: ${{ gitea.server_url }} PR_BASE_BRANCH: ${{ gitea.event.pull_request.base.ref }}
GITEA_REPOSITORY: ${{ gitea.repository }} MODEL: ${{ inputs.model }}
GITEA_TOKEN: ${{ inputs.token || gitea.token }}
GITEA_COMMENT_TOKEN: ${{ inputs.comment_token || inputs.token || gitea.token }}
PR_NUMBER: ${{ gitea.event.pull_request.number }}
PR_HEAD_SHA: ${{ gitea.event.pull_request.head.sha }}
PR_HEAD_BRANCH: ${{ gitea.event.pull_request.head.ref }}
PR_BASE_BRANCH: ${{ gitea.event.pull_request.base.ref }}
MODEL: ${{ inputs.model }}
run: |
echo "🚀 AI Code Review Action 啟動"
cd "${GITHUB_ACTION_PATH:-${GITEA_ACTION_PATH:-.}}/app"
node main.js
+3
View File
@@ -1,6 +1,8 @@
import https from 'https'; import https from 'https';
import { execFileSync } from 'child_process'; import { execFileSync } from 'child_process';
// 本 action 會連接自架 Gitea / OpenCode,部署環境可能使用內部 CA 或自簽憑證。
// 對外部服務請優先使用預設 TLS 驗證;需要內部服務相容時才使用 getInsecureHttpsAgent()。
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
export const GITEA_TOKEN = process.env.GITEA_TOKEN || ''; export const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
@@ -29,6 +31,7 @@ export function getInsecureHttpsAgent() {
return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false })); return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false }));
} }
// 過渡別名:既有呼叫端仍可用 OpenCode 語意名稱;新程式碼請直接使用 getInsecureHttpsAgent。
export const getOpenCodeHttpsAgent = getInsecureHttpsAgent; export const getOpenCodeHttpsAgent = getInsecureHttpsAgent;
const CLI_CANDIDATES = [ const CLI_CANDIDATES = [
-1
View File
@@ -64,7 +64,6 @@ function withAskpass(workspace, fn, token = GITEA_TOKEN) {
const credEnv = { const credEnv = {
...process.env, ...process.env,
GIT_ASKPASS: askpassScript, GIT_ASKPASS: askpassScript,
GIT_SSL_NO_VERIFY: 'true',
GIT_USERNAME: 'x-token', GIT_USERNAME: 'x-token',
GIT_TOKEN: token, GIT_TOKEN: token,
}; };
+2 -2
View File
@@ -23,7 +23,7 @@ function buildPrompt(systemPrompt, userContent) {
].join('\n'); ].join('\n');
} }
function cliArgs(provider, model, promptFile = null, prompt = null) { function cliArgs({ provider, model, promptFile = null, prompt = null }) {
if (provider === 'codex') { if (provider === 'codex') {
return ['exec', '--model', model, '--sandbox', 'read-only', '--skip-git-repo-check', '-']; return ['exec', '--model', model, '--sandbox', 'read-only', '--skip-git-repo-check', '-'];
} }
@@ -53,7 +53,7 @@ async function runAssistantCLI({ provider, command, model }, prompt) {
promptFile = join(tempDir, 'prompt.md'); promptFile = join(tempDir, 'prompt.md');
await writeFile(promptFile, prompt); await writeFile(promptFile, prompt);
} }
const args = cliArgs(provider, model, promptFile, prompt); const args = cliArgs({ provider, model, promptFile, prompt });
const maxBuffer = Number(process.env.AI_ASSISTANT_MAX_BUFFER || 20 * 1024 * 1024); const maxBuffer = Number(process.env.AI_ASSISTANT_MAX_BUFFER || 20 * 1024 * 1024);
const timeout = Number(process.env.AI_ASSISTANT_TIMEOUT_MS || 15 * 60 * 1000); const timeout = Number(process.env.AI_ASSISTANT_TIMEOUT_MS || 15 * 60 * 1000);
try { try {
+6 -3
View File
@@ -100,8 +100,11 @@ export async function verifyCommentToken(token = GITEA_COMMENT_TOKEN) {
* *
* 確認目前環境可偵測到支援的 CLI,且已解析出 model。實際模型可用性由 CLI * 確認目前環境可偵測到支援的 CLI,且已解析出 model。實際模型可用性由 CLI
* 在正式呼叫時回報;preflight 不主動送 prompt,避免額外消耗額度。 * 在正式呼叫時回報;preflight 不主動送 prompt,避免額外消耗額度。
* @returns {Promise<{ok: true, provider: string}|{ok: false, provider?: string, error: string}>} * @returns {Promise<
* 通過時含 provider;未設定 provider 的失敗分支不含 provider 欄位。 * {ok: true, provider: string, command: string, model: string} |
* {ok: false, provider?: string, error: string}
* >}
* 通過時含 provider、command 與 model;未設定 provider 的失敗分支不含 provider 欄位。
* @remarks 設定來源為 config.js 的 getLLMConfig()。 * @remarks 設定來源為 config.js 的 getLLMConfig()。
*/ */
export async function verifyLLM() { export async function verifyLLM() {
@@ -170,7 +173,7 @@ export async function runPreflight(workspace = process.env.GITHUB_WORKSPACE || '
error(`LLM 驗證失敗: ${llm.error}`); error(`LLM 驗證失敗: ${llm.error}`);
return false; return false;
} }
ok(`LLM provider=${llm.provider} CLI 可用`); ok(`LLM CLI 可用(command=${llm.command}, provider=${llm.provider}, model=${llm.model}`);
result(true, '前置驗證通過'); result(true, '前置驗證通過');
return true; return true;
+3 -3
View File
@@ -96,7 +96,7 @@ describe('commitAndPush', () => {
for (const { args, opts } of networkCalls) { for (const { args, opts } of networkCalls) {
assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`); assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`);
assert.equal(opts.env.GIT_SSL_NO_VERIFY, 'true', `GIT_SSL_NO_VERIFY missing for git ${args[0]}`); assert.equal(opts.env.GIT_SSL_NO_VERIFY, undefined, `GIT_SSL_NO_VERIFY must not be forced for git ${args[0]}`);
} }
}); });
@@ -267,7 +267,7 @@ describe('cloneRepo', () => {
assert.ok(networkCalls.length > 0, 'expected at least one network git call'); assert.ok(networkCalls.length > 0, 'expected at least one network git call');
for (const { args, opts } of networkCalls) { for (const { args, opts } of networkCalls) {
assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`); assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`);
assert.equal(opts.env.GIT_SSL_NO_VERIFY, 'true', `GIT_SSL_NO_VERIFY missing for git ${args[0]}`); assert.equal(opts.env.GIT_SSL_NO_VERIFY, undefined, `GIT_SSL_NO_VERIFY must not be forced for git ${args[0]}`);
} }
}); });
@@ -310,7 +310,7 @@ describe('verifyRemoteAccess', () => {
const lsRemote = calls.find(c => c.args[0] === 'ls-remote'); const lsRemote = calls.find(c => c.args[0] === 'ls-remote');
assert.ok(lsRemote, 'expected git ls-remote to run'); assert.ok(lsRemote, 'expected git ls-remote to run');
assert.ok(lsRemote.opts?.env?.GIT_ASKPASS, 'expected GIT_ASKPASS env for ls-remote'); assert.ok(lsRemote.opts?.env?.GIT_ASKPASS, 'expected GIT_ASKPASS env for ls-remote');
assert.equal(lsRemote.opts.env.GIT_SSL_NO_VERIFY, 'true'); assert.equal(lsRemote.opts.env.GIT_SSL_NO_VERIFY, undefined);
}); });
it('does not leak the token in ls-remote args', () => { it('does not leak the token in ls-remote args', () => {
+1 -1
View File
@@ -82,7 +82,7 @@ describe('fetchAccountQuota', () => {
const get = async (url, opts) => { const get = async (url, opts) => {
assert.match(url, /openrouter\.ai\/api\/v1\/auth\/key$/); assert.match(url, /openrouter\.ai\/api\/v1\/auth\/key$/);
assert.equal(opts.headers.Authorization, 'Bearer sk-or-xxx'); assert.equal(opts.headers.Authorization, 'Bearer sk-or-xxx');
assert.equal(opts.httpsAgent.options.rejectUnauthorized, false); assert.equal(opts.httpsAgent, undefined);
return { data: { data: { usage: 12.4, limit: 100, limit_remaining: 87.6 } } }; return { data: { data: { usage: 12.4, limit: 100, limit_remaining: 87.6 } } };
}; };
const q = await fetchAccountQuota('openai', { apiKeys: ['sk-or-xxx'], baseURL: 'https://openrouter.ai/api/v1' }, { get }); const q = await fetchAccountQuota('openai', { apiKeys: ['sk-or-xxx'], baseURL: 'https://openrouter.ai/api/v1' }, { get });
-2
View File
@@ -1,5 +1,4 @@
import axios from 'axios'; import axios from 'axios';
import { getInsecureHttpsAgent } from './config.js';
import { warn } from './log.js'; import { warn } from './log.js';
/** 本次執行的 token 累計(跨所有 LLM 呼叫)。 */ /** 本次執行的 token 累計(跨所有 LLM 呼叫)。 */
@@ -177,7 +176,6 @@ async function fetchOpenRouterQuota({ apiKey, baseURL }, get) {
const resp = await get(`${stripSlash(baseURL)}/auth/key`, { const resp = await get(`${stripSlash(baseURL)}/auth/key`, {
headers: { Authorization: `Bearer ${apiKey}` }, headers: { Authorization: `Bearer ${apiKey}` },
timeout: 30000, timeout: 30000,
httpsAgent: getInsecureHttpsAgent(),
}); });
const d = resp.data?.data || {}; const d = resp.data?.data || {};
const used = num(d.usage); const used = num(d.usage);
-15
View File
@@ -1,15 +0,0 @@
{
"name": "ai-code-review",
"version": "1.0.0",
"description": "AI code review Gitea action",
"private": true,
"scripts": {
"test": "npm --prefix app test"
},
"repository": {
"type": "git",
"url": "https://gitea.jsc.idv.tw/docker-actions/ai-code-review.git"
},
"author": "Jeffery",
"license": "ISC"
}