1
0
mirror of https://github.com/hustcer/deepseek-review.git synced 2026-05-13 05:16:05 +08:00

9 Commits
v1.8 ... v1.9

Author SHA1 Message Date
hustcer
9b1a390493 Bump to v1.9 2025-02-12 12:04:57 +08:00
Justin Ma
4249b38d5b Test DeepSeek R1 model (#108)
* Test DeepSeek R1 model

* ci skip

* ci skip
2025-02-12 12:01:48 +08:00
hustcer
12e1537975 Try using Infinigence's DeepSeek Model 2025-02-12 11:52:53 +08:00
hustcer
fd3a97c3b6 Test DeepSeek-R1 model 2025-02-12 09:33:23 +08:00
Justin Ma
d84f126292 feat: Add DeepSeek R1 model support (#107)
* Try to add R1 support

* fix: Fix reasoning model output
2025-02-12 09:29:53 +08:00
Justin Ma
8dde3bd95f feat: Add support for custom patch commands in local code review (#106)
* feat: Add `is_safe_git` to validate git commands for safety in shell

* feat: Add support for custom patch commands in local code review

* Update docs
2025-02-11 17:06:49 +08:00
Justin Ma
2288595e98 feat: Add streaming output support for local code review (#103) 2025-02-11 14:11:23 +08:00
Justin Ma
d064f64443 fix: Fix no repo column error for local code review (#102) 2025-02-11 13:10:04 +08:00
hustcer
c3d18ccb03 doc: Update README 2025-02-10 18:19:27 +08:00
8 changed files with 165 additions and 17 deletions

View File

@@ -25,9 +25,12 @@ jobs:
uses: hustcer/deepseek-review@develop
with:
max-length: 15000
# SiliconFlow's DeepSeek model
model: 'deepseek-ai/DeepSeek-V3'
base-url: 'https://api.siliconflow.cn/v1'
# model: 'deepseek-v3' # Infinigence's DeepSeek V3 model
model: 'deepseek-r1' # Infinigence's DeepSeek R1 model
base-url: 'https://cloud.infini-ai.com/maas/v1' # Infinigence's API base URL
# model: 'deepseek-ai/DeepSeek-V3' # SiliconFlow's DeepSeek V3 model
# model: 'deepseek-ai/DeepSeek-R1' # SiliconFlow's DeepSeek R1 model
# base-url: 'https://api.siliconflow.cn/v1' # SiliconFlow's API base URL
# Store the chat token in GitHub Secrets, don't expose it in the workflow file
chat-token: ${{ secrets.CHAT_TOKEN }}
sys-prompt: >

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.env
.env.dev
.env.local
.last-reply.json
prompts.yaml

View File

@@ -1,6 +1,18 @@
# Changelog
All notable changes to this project will be documented in this file.
## [1.9.0] - 2025-02-12
### Bug Fixes
- Fix no repo column error for local code review (#102)
### Features
- Add streaming output support for local code review (#103)
- Add support for custom patch commands by `-c, --patch-cmd` flag in local code review (#106)
- Add DeepSeek R1 model support (#107)
## [1.8.0] - 2025-02-10
### Bug Fixes

View File

@@ -9,7 +9,9 @@
- Automate PR Reviews with DeepSeek via GitHub Action
- Review Remote GitHub PRs Directly from Your Local CLI
- Analyze Commit Changes with DeepSeek for Any Local Repository with CLI
- Streaming Output Support for Local Code Review
- Fully Customizable: Choose Models, Base URLs, and Prompts
- Support Both DeepSeek's V3 & R1 Models
- Supports Self-Hosted DeepSeek Models for Enhanced Flexibility
- Perform Code Reviews for Changes That either Include or Exclude Specific Files
- Add `skip cr` or `skip review` to PR title or body to disable code review in GitHub Actions
@@ -153,7 +155,7 @@ To perform code reviews locally(should works for `macOS`, `Ubuntu`, and `Windows
Use DeepSeek AI to review code changes locally or in GitHub Actions
Usage:
> cr {flags} (token)
> nu cr {flags} (token)
Flags:
-d, --debug: Debug mode
@@ -162,13 +164,15 @@ Flags:
-k, --gh-token <string>: Your GitHub token, fallback to GITHUB_TOKEN env var
-t, --diff-to <string>: Diff to git REF
-f, --diff-from <string>: Diff from git REF
-c, --patch-cmd <string>: The `git show` or `git diff` command to get the diff content, for local CR only
-l, --max-length <int>: Maximum length of the content for review, 0 means no limit.
-m, --model <string>: Model name, deepseek-chat by default (default: 'deepseek-chat')
-b, --base-url <string> (default: 'https://api.deepseek.com')
-m, --model <string>: Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
-s, --sys-prompt <string>: Default to $DEFAULT_OPTIONS.SYS_PROMPT,
-u, --user-prompt <string>: Default to $DEFAULT_OPTIONS.USER_PROMPT,
-i, --include <string>: Comma separated file patterns to include in the code review
-x, --exclude <string>: Comma separated file patterns to exclude in the code review
-T, --temperature <float>: Temperature for the model, between `0` and `2`, default value `1.0`
-h, --help: Display the help message for this command
Parameters:
@@ -195,6 +199,13 @@ nu cr
nu cr --diff-from f536acc
# Perform code review on the `git diff f536acc 0dd0eb5` changes in the local DEFAULT_LOCAL_REPO repo
nu cr --diff-from f536acc --diff-to 0dd0eb5
# Review the changes in the local `DEFAULT_LOCAL_REPO` repo using the `--patch-cmd` flag
nu cr --patch-cmd 'git diff head~3'
nu cr -c 'git show head~3'
nu cr -c 'git diff 2393375 71f5a31'
nu cr -c 'git diff 2393375 71f5a31 nu/*'
nu cr -c 'git diff 2393375 71f5a31 :!nu/*'
# Dangerous commands like `nu cr -c 'git show head~3; rm ./*'` will not be allowed
# Perform code review on PR #31 in the remote DEFAULT_GITHUB_REPO repo
nu cr --pr-number 31
# Perform code review on PR #31 in the remote hustcer/deepseek-review repo

View File

@@ -7,7 +7,9 @@
- 通过 GitHub Action 使用 DeepSeek 进行自动化 PR 审查
- 通过本地 CLI 直接审查远程 GitHub PR
- 通过本地 CLI 使用 DeepSeek 分析任何本地仓库的提交变更
- 本地代码审查的时候支持流式输出
- 完全可定制:选择模型、基础 URL 和提示词
- 支持 DeepSeek V3 和 R1 模型
- 支持自托管 DeepSeek 模型,提供更强的灵活性
- 在 PR 的标题或描述中添加 `skip cr` or `skip review` 可跳过 GitHub Actions 里的代码审查
- 对指定文件变更进行包含/排除式代码审查
@@ -150,7 +152,7 @@ DeepSeek 接口调用入参:
Use DeepSeek AI to review code changes locally or in GitHub Actions
Usage:
> cr {flags} (token)
> nu cr {flags} (token)
Flags:
-d, --debug: Debug mode
@@ -159,13 +161,15 @@ Flags:
-k, --gh-token <string>: Your GitHub token, fallback to GITHUB_TOKEN env var
-t, --diff-to <string>: Diff to git REF
-f, --diff-from <string>: Diff from git REF
-c, --patch-cmd <string>: The `git show` or `git diff` command to get the diff content, for local CR only
-l, --max-length <int>: Maximum length of the content for review, 0 means no limit.
-m, --model <string>: Model name, deepseek-chat by default (default: 'deepseek-chat')
-b, --base-url <string> (default: 'https://api.deepseek.com')
-m, --model <string>: Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
-s, --sys-prompt <string>: Default to $DEFAULT_OPTIONS.SYS_PROMPT,
-u, --user-prompt <string>: Default to $DEFAULT_OPTIONS.USER_PROMPT,
-i, --include <string>: Comma separated file patterns to include in the code review
-x, --exclude <string>: Comma separated file patterns to exclude in the code review
-T, --temperature <float>: Temperature for the model, between `0` and `2`, default value `1.0`
-h, --help: Display the help message for this command
Parameters:
@@ -191,6 +195,13 @@ nu cr
nu cr --diff-from f536acc
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff f536acc 0dd0eb5` 修改内容进行代码审查
nu cr --diff-from f536acc --diff-to 0dd0eb5
# 通过 --patch-cmd 参数对本地 DEFAULT_LOCAL_REPO 仓库变更内容进行审查
nu cr --patch-cmd 'git diff head~3'
nu cr -c 'git show head~3'
nu cr -c 'git diff 2393375 71f5a31'
nu cr -c 'git diff 2393375 71f5a31 nu/*'
nu cr -c 'git diff 2393375 71f5a31 :!nu/*'
# 像 `nu cr -c 'git show head~3; rm ./*'` 这样危险的命令将会被禁止
# 对远程 DEFAULT_GITHUB_REPO 仓库编号为 31 的 PR 进行代码审查
nu cr --pr-number 31
# 对远程 hustcer/deepseek-review 仓库编号为 31 的 PR 进行代码审查

View File

@@ -16,4 +16,5 @@ words:
- linewise
- Subshell
- subshells
- Infinigence
ignorePaths:

View File

@@ -1,7 +1,7 @@
{
"name": "deepseek-review",
"version": "1.8.0",
"actionVer": "v1.8",
"version": "1.9.0",
"actionVer": "v1.9",
"author": "hustcer",
"license": "MIT",
"github": "https://github.com/hustcer/deepseek-review",

View File

@@ -7,6 +7,8 @@
# [√] Debug mode
# [√] Output token usage info
# [√] Perform CR for changes that either include or exclude specific files
# [√] Support streaming output for local code review
# [√] Support using custom patch command to get diff content
# [ ] Add more action outputs
# Description: A script to do code review by DeepSeek
# REF:
@@ -35,6 +37,9 @@ export const ECODE = {
CONDITION_NOT_SATISFIED: 8,
}
const RESPONSE_END = 'data: [DONE]'
const LAST_REPLY_TMP = '.last-reply.json'
const GITHUB_API_BASE = 'https://api.github.com'
# It takes longer to respond to requests made with unknown/rare user agents.
@@ -61,6 +66,7 @@ export def --env deepseek-review [
--gh-token(-k): string, # Your GitHub token, fallback to GITHUB_TOKEN env var
--diff-to(-t): string, # Diff to git REF
--diff-from(-f): string, # Diff from git REF
--patch-cmd(-c): string, # The `git show` or `git diff` command to get the diff content, for local CR only
--max-length(-l): int, # Maximum length of the content for review, 0 means no limit.
--model(-m): string, # Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
@@ -68,11 +74,12 @@ export def --env deepseek-review [
--user-prompt(-u): string # Default to $DEFAULT_OPTIONS.USER_PROMPT,
--include(-i): string, # Comma separated file patterns to include in the code review
--exclude(-x): string, # Comma separated file patterns to exclude in the code review
--temperature: float, # Temperature for the model
--temperature(-T): float, # Temperature for the model, between `0` and `2`, default value `1.0`
]: nothing -> nothing {
$env.config.table.mode = 'psql'
let is_action = ($env.GITHUB_ACTIONS? == 'true')
let stream = if $is_action { false } else { true }
let token = $token | default $env.CHAT_TOKEN?
let repo = $repo | default $env.DEFAULT_GITHUB_REPO?
let CHAT_HEADER = [Authorization $'Bearer ($token)']
@@ -94,6 +101,7 @@ export def --env deepseek-review [
exclude: $exclude,
diff_to: $diff_to,
diff_from: $diff_from,
patch_cmd: $patch_cmd,
pr_number: $pr_number,
max_length: $max_length,
local_repo: $local_repo,
@@ -111,11 +119,11 @@ export def --env deepseek-review [
$'🚀 Initiate the code review by DeepSeek AI for PR (ansi g)#($pr_number)(ansi reset) in (ansi g)($repo)(ansi reset) ...'
}
print $hint; print -n (char nl)
if ($pr_number | is-empty) { $setting | compact-record | reject repo | print }
if ($pr_number | is-empty) { $setting | compact-record | reject -i repo | print }
let content = (
get-diff --pr-number $pr_number --repo $repo --diff-to $diff_to
--diff-from $diff_from --include $include --exclude $exclude)
--diff-from $diff_from --include $include --exclude $exclude --patch-cmd $patch_cmd)
let length = $content | str stats | get unicode-width
if ($max_length != 0) and ($length > $max_length) {
print $'(char nl)(ansi r)The content length ($length) exceeds the maximum limit ($max_length), review skipped.(ansi reset)'
@@ -126,7 +134,7 @@ export def --env deepseek-review [
let user_prompt = $user_prompt | default (load-prompt-from-env USER_PROMPT) | default $DEFAULT_OPTIONS.USER_PROMPT
let payload = {
model: $model,
stream: false,
stream: $stream,
temperature: $temperature,
messages: [
{ role: 'system', content: $sys_prompt },
@@ -135,6 +143,8 @@ export def --env deepseek-review [
}
if $debug { print $'Code Changes:'; hr-line; print $content }
print $'(char nl)Waiting for response from (ansi g)($url)(ansi reset) ...'
if $stream { streaming-output $url $payload --headers $CHAT_HEADER --debug=$debug; return }
let response = http post -e -H $CHAT_HEADER -t application/json $url $payload
if ($response | is-empty) {
print $'(ansi r)Oops, No response returned from DeepSeek API.(ansi reset)'
@@ -145,22 +155,54 @@ export def --env deepseek-review [
print $'❌ Code review failedError: '; hr-line; print $response
exit $ECODE.SERVER_ERROR
}
let reason = $response | get -i choices.0.message.reasoning_content
let review = $response | get -i choices.0.message.content
let result = [$reason $review] | str join "\n"
if ($review | is-empty) {
print $'❌ Code review failedNo review result returned from DeepSeek API.'
exit $ECODE.SERVER_ERROR
}
if not $is_action {
print $'Code Review Result:'; hr-line; print $review
print $'Code Review Result:'; hr-line; print $result
} else {
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json ...$HTTP_HEADERS]
http post -t application/json -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/issues/($pr_number)/comments' { body: $review }
http post -t application/json -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/issues/($pr_number)/comments' { body: $result }
print $'✅ Code review finishedPR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
}
print $'(char nl)Token Usage Info:'; hr-line
$response.usage | table -e | print
}
# Output the streaming response of review result from DeepSeek API
def streaming-output [
url: string, # The Full DeepSeek API URL
payload: record, # The payload to send to DeepSeek API
--debug, # Debug mode
--headers: list, # The headers to send to DeepSeek API
] {
print -n (char nl)
http post -e -H $headers -t application/json $url $payload
| tee { let res = $in; if ($res | describe) =~ 'record' { $res | table -e | print; exit $ECODE.SERVER_ERROR } }
| lines
| each {|line|
if $line == $RESPONSE_END { return }
if ($line | is-empty) { return }
let $last = $line | str substring 6.. | from json
if $last == '-alive' { print $last; return }
if $debug { $last | to json | save -rf $LAST_REPLY_TMP }
$last | get -i choices.0.delta | if ($in | is-not-empty) {
let delta = $in
print -n ($delta.reasoning_content | default $delta.content)
}
}
if $debug and ($LAST_REPLY_TMP | path exists) {
print $'(char nl)(char nl)DeepSeek Token Usage:'; hr-line
open $LAST_REPLY_TMP | select -i model usage | table -e | print
rm -f $LAST_REPLY_TMP
}
}
# Load the prompt content from the specified env var
export def load-prompt-from-env [
prompt_key: string,
@@ -188,6 +230,7 @@ export def get-diff [
--diff-from: string, # Diff from git ref
--include: string, # Comma separated file patterns to include in the code review
--exclude: string, # Comma separated file patterns to exclude in the code review
--patch-cmd: string, # The `git show` or `git diff` command to get the diff content
] {
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json]
let DIFF_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3.diff]
@@ -223,6 +266,10 @@ export def get-diff [
} else if not (git-check $local_repo --check-repo=1) {
print $'Current directory ($local_repo) is (ansi r)NOT(ansi reset) a git repo, bye...(char nl)'
exit $ECODE.CONDITION_NOT_SATISFIED
} else if ($patch_cmd | is-not-empty) {
let valid = is-safe-git $patch_cmd
if not $valid { exit $ECODE.INVALID_PARAMETER }
nu -c $patch_cmd
} else { git diff }
if ($content | is-empty) {
@@ -398,4 +445,66 @@ export def compare-ver [v1: string, v2: string] {
0
}
# Check if the git command is safe to run in the shell
# Validate command examples:
# - git show
# - git diff
# - git show head~1
# - git diff --since=2025-02-09 HEAD
# - git diff 2393375 71f5a31
# - git diff 2393375 71f5a31 nu/*
# - git diff 2393375 71f5a31 :!nu/*
export def is-safe-git [cmd: string] {
# Normalize the command string by trimming and converting to lowercase
let normalized_cmd = ($cmd | str trim | str downcase)
# More strict regex for git commands, allow:
# 1. --since parameter with ISO date format
# 2. File path patterns with or without colon (e.g. :!nu/*, nu/*)
let allowed_regex = '^git\s+(show|diff)(?:\s+(?:--since=\d{4}-\d{2}-\d{2}|[a-zA-Z0-9_\-\.~/]+))*(?:\s+(?::[!]?)?[a-zA-Z0-9_\-\.\*\/]+)?$'
# Dangerous patterns to check (expanded list)
let dangerous_patterns = [
# Command chaining/injection
';', '&&', '||', '|',
# Shell expansion
'?', '[', ']', '{', '}',
# Command substitution
'`', '$(',
# IO redirection
'>', '>>', '<', '<<',
# Special characters
'\n', '\r', '\t',
# Path traversal
'..',
# Environment variables
'$', '%',
# Quotes that might be used for injection
'"', "'"
]
# First check: Command must match the allowed pattern
if ($normalized_cmd | find -r $allowed_regex | is-empty) {
print $'ERROR: Invalid git command format. (ansi r)Only simple `git show` or `git diff` commands are allowed(ansi reset).'
return false
}
# Second check: No dangerous patterns allowed
for pattern in $dangerous_patterns {
if ($cmd | str contains $pattern) {
print $'(ansi r)ERROR: Dangerous pattern detected: `($pattern)`(ansi reset)'
return false
}
}
# Third check: Command parts validation (increased limit to accommodate path patterns)
let cmd_parts = $normalized_cmd | split row ' '
if ($cmd_parts | length) > 6 {
print $'ERROR: Command too complex. (ansi r)Only simple `git show` or `git diff` commands are allowed(ansi reset).'
return false
}
true
}
alias main = deepseek-review