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

99 Commits
v1.8 ... main

Author SHA1 Message Date
Justin Ma
9163ca2434 deps: Upgrade Nu to 0.112.2 & hustcer/setup-nu to v3.23 (#202)
* deps: Upgrade Nu to 0.112.2

* deps: Upgrade hustcer/setup-nu@v3.23
2026-05-07 21:24:17 +08:00
Xinnil
cb78a000a7 [RR] Add Trigger for Skipping CR (#199)
* new constants and check

* Update

* Delete comments
2026-05-07 21:05:53 +08:00
Xinnil
d1dcf1311d Review models Integration (#200)
* Update review.nu

Update model to preavoid depreciation

* Update refs

* Update payload in review

* Update the cr help
2026-05-07 20:57:52 +08:00
Justin Ma
6cbc5ef631 deps: Upgrade Nu to 0.111 (#198) 2026-03-01 15:55:32 +08:00
Justin Ma
92a3c2ae17 fix: Try to fix workflow error for Nu 0.111 (#197)
* fix: Try to fix workflow error for Nu 0.111

* chore: Update readme
2026-02-09 10:41:14 +08:00
hustcer
90c8878923 Bump to v1.20 2026-01-23 19:53:21 +08:00
Justin Ma
231c2d8432 chore: Update Nushell to 0.110.0 (#196) 2026-01-21 07:50:05 +08:00
hustcer
923ee0f999 deps: Upgrade hustcer/setup-nu to v3.22 2025-12-13 22:42:11 +08:00
Justin Ma
32cffba4d1 deps: Upgrade Nu to 0.109.1 (#195) 2025-12-03 22:34:55 +08:00
Justin Ma
0c001b0ee2 refactor: A better from env parser (#194)
* refactor: A better `from env` parser

* refactor: A better `from env` parser
2025-11-29 22:45:55 +08:00
hustcer
edcd90e48b deps: Upgrade actions/checkout@v6 2025-11-26 23:10:18 +08:00
Justin Ma
dfc4c59c1f fix: Fix from env for .env file parsing (#193)
* fix: Fix `from env` for .env file parsing

* fix: Fix `from env` for .env file parsing

* fix: Fix `from env` for .env file parsing
2025-11-26 23:07:41 +08:00
hustcer
92061e6c82 chore: Update README.md 2025-11-22 23:22:03 +08:00
Justin Ma
3c3f0a3c85 deps: Upgrade hustcer/setup-nu to v3.21 (#192) 2025-10-25 11:13:42 +08:00
Justin Ma
7a87f8adc0 chore: Update nutest to main ref (#191) 2025-10-24 23:06:24 +08:00
Justin Ma
d7b2e3a926 deps: Upgrade Nushell version to 0.108.0 (#190) 2025-10-24 22:51:09 +08:00
hustcer
91ae07bb82 deps: Upgrade hustcer/setup-nu to v3.20 2025-08-25 20:03:18 +08:00
hustcer
d830da6937 deps: Upgrade to actions/checkout@v5 2025-08-25 19:48:24 +08:00
hustcer
885b55dbb0 chore: Update minimum required Nushell version to 0.106.1 2025-08-18 21:16:27 +08:00
hustcer
317d3c92da fix: Make config-check use default value for --config flag 2025-08-13 22:14:52 +08:00
hustcer
938790c65b Bump version to v1.19 2025-07-24 20:05:51 +08:00
Justin Ma
a4d125ecba deps: Upgrade Nu to v0.106 (#186) 2025-07-24 20:01:49 +08:00
hustcer
930e0ae68f fix: Fix getting Nu binary path for Nushell 0.106 2025-07-15 20:16:21 +08:00
Justin Ma
d0b2ea125a fix: Fix "variable not found" error (#185) 2025-07-14 15:28:49 +08:00
hustcer
9852113028 Bump version to v1.18 2025-06-11 15:37:14 +08:00
Justin Ma
9ad3373cd5 chore: Upgrade Nu to 0.105 and pin hustcer/setup-nu to v3.19 (#183) 2025-06-11 15:04:04 +08:00
Justin Ma
981ef03409 feat: Set defalut temperature to 0.3 for code review (#181) 2025-04-29 17:09:53 +08:00
Justin Ma
9852118ba7 deps: Upgrade nutest to v1.1.0 (#179)
* deps: Upgrade nutest to v1.1.0

* fix nutest upgrading
2025-04-29 16:36:21 +08:00
Justin Ma
985f205ae5 chore: Refine diff flag descriptions in docs and scripts (#177) 2025-04-18 11:06:46 +08:00
hustcer
29aec71797 Bump to v1.17 2025-04-11 19:40:59 +08:00
hustcer
957db0afb6 Update CHANGELOG.md 2025-04-11 19:39:49 +08:00
Justin Ma
c53edfe925 doc: Update README.md add output file functionality for local code reviews (#175) 2025-04-11 19:28:26 +08:00
Justin Ma
0298773233 feat: Add write code review result to file support (#172)
* feat: Add write code review result to file support

* feat: Add write code review result to file support

* feat: Add write code review result to file support

* feat: Add write code review result to file support
2025-04-11 11:39:47 +08:00
Justin Ma
9d6bb02502 feat: Add code review for git show head:path/to/file patch command support (#171) 2025-04-10 18:16:39 +08:00
Justin Ma
26e38f1543 fix: Read default include and exclude patterns from config for local code review (#170) 2025-04-10 18:07:34 +08:00
hustcer
8b7262a6b4 [ci skip] 2025-04-09 17:29:21 +08:00
hustcer
0679ef2b0e Bump to v1.16 2025-04-05 10:13:41 +08:00
hustcer
ac1bb26376 chore: Add openrouter.ai config example 2025-04-03 18:09:41 +08:00
Justin Ma
443d1d887e feat: Add openrouter deepseek model support (#167)
* feat: Add openrouter deepseek V3 model support

* feat: Add openrouter deepseek R1 model support

* feat: Add openrouter deepseek model support for GitHub action

* feat: Add openrouter deepseek model support for GitHub action

* feat: Add openrouter deepseek model support for GitHub action
2025-04-03 18:05:46 +08:00
Justin Ma
90d7be5ff2 refactor: Replace custom kv.nu module with std-rfc/kv for key-value functionality (#166)
* refactor: Replace custom `kv.nu` module with `std-rfc/kv` for key-value functionality

* refactor: Replace custom `kv.nu` module with `std-rfc/kv` for key-value functionality

* ci skip
2025-04-02 20:11:14 +08:00
Justin Ma
9af6f3d480 refactor: Refactored diff handling by moving logic to separate module (#165)
* refactor: Refactored diff handling by moving logic to separate module

* refactor: Refactored diff handling by moving logic to separate module

* Fix tests

* Fix tests
2025-04-02 19:54:45 +08:00
Justin Ma
abe4d66650 refactor: Refactor get-diff custom command (#164) 2025-04-02 18:20:14 +08:00
Justin Ma
da010fada9 doc: Add powershell alias setup guide (#163) 2025-03-25 15:45:23 +08:00
hustcer
61a7b5f654 chore: Add set fish alias guide 2025-03-24 20:24:08 +08:00
hustcer
308b582471 Bump to v1.15 2025-03-23 18:07:21 +08:00
Justin Ma
22e1082798 Add detailed help documentation of CLI for local code review (#162)
* chore: Update README

* chore: Update README

* ci skip

* ci skip
2025-03-23 18:00:23 +08:00
Justin Ma
98510dd05d feat: Add repo of current directory code review support (#161)
* feat: Add repo of current directory code review support

* ci skip
2025-03-23 11:37:51 +08:00
hustcer
985a75f4b2 deps: Upgrade Nushell to v0.103 2025-03-22 09:08:21 +08:00
hustcer
990b275f72 chore: Add just test task to run tests locally 2025-03-16 20:10:11 +08:00
hustcer
f0521e7f9c Update cspell config 2025-03-07 11:26:14 +08:00
Justin Ma
3ea2007572 feat: Support local DeepSee model running on Ollama (#152)
* feat: Support local DeepSee modle running on Ollama

* feat: Support local DeepSee modle running on Ollama

* feat: Support local DeepSee modle running on Ollama
2025-03-01 15:37:56 +08:00
Justin Ma
fa7541f78e refactor: Enhance the glob pattern handling in glob-to-regex function (#151) 2025-02-28 19:17:52 +08:00
Justin Ma
7f5575257d refactor: Simplify is-safe-git common util (#150)
* refactor: Simplify is-safe-git common util

* ci skip

* ci skip

* ci skip
2025-02-28 18:32:06 +08:00
Justin Ma
d53983d8ee chore: Some code refactor (#149) 2025-02-28 17:59:14 +08:00
hustcer
991d9e8de3 chore: Use SiliconFlow's DeepSeek API 2025-02-28 17:55:28 +08:00
Justin Ma
ff5bb86a59 feat: Add --config option to specify config file path for local code review (#146)
* feat: Add --config option to specify config file path for local code review

* feat: Add --config option to specify config file path for local code review
2025-02-21 18:24:34 +08:00
hustcer
daecd2734c Fix prompts template 2025-02-18 16:38:53 +08:00
hustcer
95ca66de3e Fix tests 2025-02-18 15:32:02 +08:00
Justin Ma
eb198fae3d feat: Add nushell version check and notify for update (#144)
* feat: Add nushell version check and notify for update

* feat: Add nushell version check and notify for update

* ci skip

* ci skip
2025-02-18 15:25:44 +08:00
Justin Ma
5ebbddb801 feat: Post a comment to the PR to notify the user when no CHAT_TOKEN is provided (#143) 2025-02-18 11:21:14 +08:00
Justin Ma
9901bcbeb1 chore: Update code review prompt for current nushell repo (#139) 2025-02-17 18:19:34 +08:00
Justin Ma
4e787e2430 feat: Add example code review prompts for frontend, java and rust to config.example.yml (#138)
* feat: Add sample code review prompts for frontend, java and rust to config.example.yml

* ci skip
2025-02-17 18:13:23 +08:00
Justin Ma
8e91c18ef8 Add two more tests (#135)
* Add two more tests

* Add two more tests

* Add two more tests

* Add two more tests
2025-02-17 12:11:47 +08:00
hustcer
55958cd4a9 chore: Update tests status badge to README 2025-02-16 21:37:50 +08:00
hustcer
88ea2aed5e chore: Publish test summary (#133) 2025-02-16 21:08:20 +08:00
hustcer
683ed811c6 fix: Add get-diff test for applying exclude and include simultaneously 2025-02-16 18:35:54 +08:00
hustcer
c3ec0285c3 Bump to v1.12 2025-02-16 18:22:58 +08:00
Justin Ma
b0ae06217d chore: Fix gawk installation for GitHub Windows runners (#132)
* chore: Fix gawk installation for Windows runners

* chore: Fix gawk installation for Windows runners

* chore: Fix gawk installation for Windows runners

* chore: Fix gawk installation for Windows runners

* chore: Fix gawk installation for Windows runners
2025-02-16 18:07:14 +08:00
Justin Ma
9551fc2dec fix: Fix include and exclude on GitHub Windows runners and related tests (#131)
* fix: Fix include and exclude on GitHub Windows runners and related tests

* ci skip

* ci skip

* ci skip

* ci skip

* ci skip

* chore: Add scoop shims path to front

* chore: Add scoop shims path to front

* chore: Add scoop shims path to front
2025-02-16 16:16:59 +08:00
hustcer
9272a75907 chore: Add ubuntu-22.04-arm to test matrix 2025-02-16 13:30:52 +08:00
hustcer
55a5d76958 chore: Disable posting test results 2025-02-16 13:26:35 +08:00
hustcer
1260c388b9 chore: Add tests for get-diff command in review module 2025-02-16 13:24:34 +08:00
Justin Ma
0088fe059a chore: Add review related tests (#129)
* Update tests

* chore: Add review related tests

* chore: Add review related tests

* chore: Add review related tests

* chore: Add review related tests

* chore: Add review related tests

* chore: Add review related tests
2025-02-15 15:53:39 +08:00
Justin Ma
3ff7a32e6d chore: Add more tests for common helpers (#128)
* Update tests

* Update tests

* Update tests

* Update tests

* Update tests

* Update tests
2025-02-15 14:12:48 +08:00
hustcer
0088530d17 chore: Publish Test Results (#127) 2025-02-15 11:13:59 +08:00
Justin Ma
93a5d817e7 doc: Update README (#126) 2025-02-15 10:41:05 +08:00
hustcer
17ab7ae3fa chore: Try to add tests workflow and some common tests (#125) 2025-02-14 23:50:46 +08:00
Justin Ma
2b2cb85fd8 feat: Use config.yml instead of .env as the local code review config file (#123)
* feat: Use config.yml instead of .env as the local code review config file

* refactor common helpers

* Remove .env.example

* fix model override

* fix: Fix Remote PR review

* Check if the prompt config keys exist

* ci skip

* ci skip

* fix model validation

* fix model validation

* chore: Update readme

* chore: Increase max length for cr.yml

* Update doc
2025-02-14 22:09:15 +08:00
hustcer
00806171f7 chore: Increase max length for cr.yml 2025-02-14 19:31:11 +08:00
hustcer
f3ea289921 Bump to v1.11 2025-02-13 19:48:21 +08:00
Justin Ma
4af2f6036b fix: Fix include & exclude pattern matching error (#122)
* fix: Fix include & exclude pattern matching error

* ci skip

* code refactor
2025-02-13 19:44:43 +08:00
hustcer
3dd6b23201 fix: Fix reasoning_content field may not exists error 2025-02-13 15:45:09 +08:00
Justin Ma
c2b26fdc30 fix: Do not show reasoning parts if reviewed by V3 model (#121) 2025-02-13 15:29:10 +08:00
hustcer
06d80785b1 Bump to v1.10 2025-02-12 23:30:23 +08:00
Justin Ma
84dbd263c9 fix: Improve error handling for API response in streaming-output (#117)
* fix: Improve error handling for API response in streaming-output

* Fix token error

* Fix error
2025-02-12 23:24:43 +08:00
hustcer
c0d78b92b9 feat: Separate the reasoning and the content part in the output of DeepSeek (#115) 2025-02-12 19:30:26 +08:00
hustcer
5652f31856 perf: Save the last reply of the model in memory instead of a file (#113) 2025-02-12 18:26:40 +08:00
hustcer
68e582fb95 Update README 2025-02-12 17:40:54 +08:00
Justin Ma
534d6230b7 chore: Refine DeepSeek review output, clarify debug logs, and adjust error messaging for better context (#111) 2025-02-12 14:26:39 +08:00
Justin Ma
6af37c1d98 chore: Make awk version check for both awk and gawk (#109) 2025-02-12 12:21:03 +08:00
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
23 changed files with 2300 additions and 434 deletions

View File

@@ -1,37 +0,0 @@
# Description: Environment variables for Local Code Review Only
# Usage: Copy this file to .env and replace the values with your own
# WARNING: Do not commit the actual .env file to version control as it may contain sensitive information.
# CHAT_TOKEN: Obtain this token from your DeepSeek account settings
CHAT_TOKEN='Your DeepSeek API token'
# CHAT_MODEL='deepseek-chat' # Official DeepSeek model
# CHAT_MODEL='deepseek-ai/DeepSeek-V3' # SiliconFlow DS model
# GITHUB_TOKEN: Your GitHub API token to query GitHub PR changes
# Generate this token from your GitHub account with the necessary permissions
GITHUB_TOKEN='Your GitHub API token'
# MAX_LENGTH: The maximum length of the content for review, 0 means no limit.
MAX_LENGTH='0'
# The maximum temperature for the model to generate the response. 1.0 by default.
TEMPERATURE='1.0'
# The comma separated file patterns to include in the code review.
INCLUDE_PATTERNS=''
# The comma separated file patterns to exclude in the code review.
EXCLUDE_PATTERNS='pnpm-lock.yaml,package-lock.json,*.lock'
# Default GitHub repository name to fetch PR changes
DEFAULT_GITHUB_REPO='hustcer/deepseek-review'
# Default local repository absolute path to query commit changes
DEFAULT_LOCAL_REPO='/Users/hustcer/deepseek-review'
# BASE_URL: DeepSeek API base URL
# Replace with the actual API base URL if different
BASE_URL='https://api.deepseek.ai'
# BASE_URL='https://api.siliconflow.cn/v1' # SiliconFlow API
# USER_PROMPT: User prompt message, customize as needed
# OR a yaml file path with key name specified to load the prompt message,
# e.g., USER_PROMPT='/User/abc/prompts.yaml:usr-prompt'
# Will load the prompt message from the 'usr-prompt' key in the 'prompts.yaml' file
USER_PROMPT='Please review the following code changes'
# SYSTEM_PROMPT: System prompt message, customize as needed
# OR a yaml file path with key name specified to load the prompt message,
# e.g., SYSTEM_PROMPT='/User/abc/prompts.yaml:sys-prompt'
# Will load the prompt message from the 'sys-prompt' key in the 'prompts.yaml' file
SYSTEM_PROMPT='You are a professional code review assistant responsible for analyzing code changes in GitHub Pull Requests. Identify potential issues such as code style violations, logical errors, security vulnerabilities, and provide improvement suggestions. Clearly list the problems and recommendations in a concise manner.'

View File

@@ -24,41 +24,44 @@ jobs:
- name: DeepSeek Code Review
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'
max-length: 50000
# 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: >
As a senior DevOps engineer, perform comprehensive review of shell scripts with focus on:
As a senior Nushell engineer, perform comprehensive script review with focus on:
1. Core Requirements:
- Validate POSIX compatibility
- Check for proper error handling
- Verify safe variable usage
- Assess resource management
### 1. Core Requirements:
- Validate Nu 0.100+ compatibility
- Check structured data handling
- Verify pipeline efficiency
- Assess module organization
2. Security Analysis:
- Shell injection prevention
- Safe file operations
- Proper permissions handling
- Secure command execution
### 2. Security Analysis:
- Command injection prevention
- Data leakage prevention
- Safe external command usage
- Proper permission validation
3. Performance Optimization:
- Efficient process management
- Proper use of subshells
- Stream handling best practices
- Avoidance of unnecessary forks
### 3. Performance Optimization:
- Pipeline optimization
- Memory usage patterns
- Builtin vs external command usage
- Parallel execution opportunities
Rules:
- Target bash/sh compatibility
- Highlight security vulnerabilities
- Suggest performance improvements
- Keep feedback actionable
- Use technical shell terminology
**Rules:**
- Target Nu 0.100+ features
- Highlight data flow vulnerabilities
- Suggest structured data optimizations
- Keep feedback Nu-specific
- Use modern shell terminology
Required output structure:
**Required output structure:**
#### Script Analysis
- Key observations
@@ -70,19 +73,18 @@ jobs:
**Overall Quality:** Rating (1-5)
Use the following reference data:
```yaml
checklist:
- Compatibility: ["POSIX compliance", "Shell-specific features", "Portability"]
- Security: ["Input validation", "Safe eval usage", "Permission checks"]
- Reliability: ["Error handling", "Exit codes", "Signal trapping"]
- Performance: ["Process management", "I/O operations", "Subshell usage"]
- Compatibility: ["Nu version", "Cross-platform support", "Plugin dependencies"]
- Security: ["Input sanitization", "Temporary file handling", "Env exposure"]
- Reliability: ["Error propagation", "Null handling", "Type validation"]
- Performance: ["Lazy evaluation", "Batch processing", "Stream handling"]
examples:
- issue: "❗ Unquoted variable expansion in line 42 (shell injection risk)"
- issue: "⚠️ Missing error handling for rm operation in line 15"
- suggestion: "Replace backticks with $() for better readability and nesting"
- suggestion: "Use exec for file handling to reduce file descriptors"
- issue: "❗ Unfiltered external command arguments in line 15 (command injection risk)"
- issue: "⚠️ Plaintext credentials in environment variables"
- suggestion: "Replace `each { }` with `par-each` for parallel processing"
- suggestion: "Use builtin `from json` instead of jq for better performance"
response_template: |
#### Script Analysis

100
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,100 @@
# Description: This workflow runs tests for hustcer/deepseek-review.
# REF:
# - https://github.com/vyadh/nutest/blob/main/.github/workflows/tests.yaml
# - https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
name: Run Tests
on:
pull_request:
paths-ignore:
- '**.md'
- 'docs/**'
workflow_dispatch:
push:
branches:
- main
- develop
- feature/test
paths-ignore:
- '**.md'
- 'docs/**'
schedule:
- cron: '0 0 * * *' # Run every morning at 0am UTC
permissions:
contents: read
jobs:
run-tests:
name: Run Tests
permissions:
checks: write
pull-requests: write
strategy:
fail-fast: false
matrix:
version: ['*', nightly] # Earliest supported, latest and nightly
platform: [ubuntu-latest, windows-latest, macos-latest, ubuntu-22.04-arm]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v6
- name: Checkout Nutest Repo
uses: actions/checkout@v6
with:
ref: main
path: nutest
repository: vyadh/nutest
sparse-checkout: nutest/
- name: Setup Nu
uses: hustcer/setup-nu@v3
with:
version: ${{ matrix.version }}
- name: Test DeepSeek Review
shell: nu {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
use ${{ github.workspace }}/nutest/nutest
use ${{ github.workspace }}/nu/util.nu [prepare-awk]
prepare-awk
(
nutest run-tests
--fail
--display terminal
--report { type: junit, path: test-report.xml }
--returns summary | to json | save --force test-summary.json
)
- name: Publish Test Summary
if: runner.os == 'macOS' && matrix.version == '*' && github.ref == 'refs/heads/main'
shell: nu {0}
run: |
let filename = 'test-summary.json'
let gist_id = 'b99391ee59016b17d0befe3331387e89'
let data = {
files: {
"test-summary.json": {
content: (open --raw $filename)
}
}
}
(
$data | http patch
--redirect-mode 'follow'
--content-type 'application/json'
--headers {
'X-GitHub-Api-Version': '2022-11-28'
'Accept': 'application/vnd.github+json'
'Authorization': $'Bearer ${{ secrets.GH_PAT }}'
}
$'https://api.github.com/gists/($gist_id)'
) | ignore

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
.env
.env.dev
.env.local
review.md
config.yml
prompts.yaml

View File

@@ -1,6 +1,185 @@
# Changelog
All notable changes to this project will be documented in this file.
## [1.20.0] - 2026-01-23
### Bug Fixes
- Make config-check use default value for --config flag
- Fix `from env` for .env file parsing (#193)
### Miscellaneous Tasks
- Update minimum required Nushell version to 0.110.0
- Update nutest to main ref (#191)
- Update README.md
### Refactor
- A better `from env` parser (#194)
### Deps
- Upgrade to actions/checkout@v5
- Upgrade `hustcer/setup-nu` to v3.20
- Upgrade Nushell version to 0.108.0 (#190)
- Upgrade `hustcer/setup-nu` to v3.21 (#192)
- Upgrade actions/checkout@v6
- Upgrade Nu to 0.109.1 (#195)
- Update Nushell to 0.110.0 (#196)
- Upgrade `hustcer/setup-nu` to v3.22
## [1.19.0] - 2025-07-23
### Bug Fixes
- Fix "variable not found" error (#185)
- Fix getting Nu binary path for Nushell 0.106
### Deps
- Upgrade Nu to v0.106 (#186)
## [1.18.0] - 2025-06-11
### Features
- Set default `temperature` to **0.3** for code review (#181)
### Miscellaneous Tasks
- Refine diff flag descriptions in docs and scripts (#177)
- Upgrade `Nu` to 0.105 and pin [`hustcer/setup-nu`](https://github.com/hustcer/setup-nu) to v3.19 (#183)
### Deps
- Upgrade `nutest` to v1.1.0 (#179)
## [1.17.0] - 2025-04-11
### Bug Fixes
- Read default `include` and `exclude` patterns from config for local code review (#170)
### Features
All the following changes are for local code review only:
- Add code review for `git show head:path/to/file` patch command support (#171)
- Add write code review result to file by `--output` flag support (#172)
## [1.16.0] - 2025-04-05
### Documentation
- Add alias setup guide for `powershell` (#163)
### Features
- Add OpenRouter deepseek model support (#167)
### Miscellaneous Tasks
- Add alias setup guide for `fish`
- Add openrouter.ai config example
- Set minimum required `nushell` version to v0.103
### Refactor
- Refactor `get-diff` custom command (#164)
- Refactor diff handling by moving logic to separate module (#165)
- Replace custom `kv.nu` module with `std-rfc/kv` for key-value functionality (#166)
## [1.15.0] - 2025-03-23
### Features
- Add example code review prompts for frontend, java and rust to `config.example.yml` (#138)
- Post a comment to the PR to notify the user when no `CHAT_TOKEN` is provided (#143)
- Add nushell version check and notify for update (#144)
- Add `--config` option to specify config file path for local code review (#146)
- Support local DeepSeek model running on Ollama (#152)
- Add repo of current directory code review support (#161)
### Miscellaneous Tasks
- Publish test summary (#133)
- Update tests status badge to README
- Update code review prompt for current nushell repo (#139)
- Use SiliconFlow's DeepSeek API
- Some code refactor (#149)
- Add `just test` task to run tests locally
### Refactor
- Simplify `is-safe-git` common util (#150)
- Enhance the glob pattern handling in `glob-to-regex` function (#151)
### Deps
- Upgrade `Nushell` to `v0.103.0`
## [1.12.0] - 2025-02-16
### Bug Fixes
- Fix `include` and `exclude` on GitHub Windows runners and related tests (#131)
### Documentation
- Update README (#126)
### Features
- Use `config.yml` instead of `.env` as the local code review config file (#123)
### Miscellaneous Tasks
- Try to add tests workflow and some common tests (#125)
- Add review related tests (#129)
- Add tests for `get-diff` command in review module
- Add `ubuntu-22.04-arm` to test matrix
- Fix `gawk` installation for GitHub Windows runners (#132)
## [1.11.0] - 2025-02-13
### Bug Fixes
- Do not show reasoning parts if reviewed by DeepSeek V3 model (#121)
- Fix `reasoning_content` field may not exists error
- Fix `include` & `exclude` pattern matching error (#122)
## [1.10.0] - 2025-02-12
### Features
- Separate the reasoning and the content part of the output (#115)
### Bug Fixes
- Improve error handling of API response for streaming-output (#117)
### Miscellaneous Tasks
- Make `awk` version check works for both `awk` and `gawk` (#109)
- Refine DeepSeek review output, clarify debug logs, and adjust error messaging for better context (#111)
### Performance
- Save the last reply of the model to db instead of a file (#113)
## [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

@@ -21,13 +21,14 @@ set dotenv-load := true
set positional-arguments := true
# Just commands aliases
alias t := test
alias cr := code-review
# Use `just --evaluate` to show env vars
# Used to handle the path separator issue
DEEPSEEK_REVIEW_PATH := parent_directory(justfile())
NU_DIR := parent_directory(`(which nu).path.0`)
NU_DIR := parent_directory(`$nu.current-exe`)
_query_plugin := if os_family() == 'windows' { 'nu_plugin_query.exe' } else { 'nu_plugin_query' }
# To pass arguments to a dependency, put the dependency
@@ -48,6 +49,10 @@ code-review *OPTIONS:
@overlay use {{ join(DEEPSEEK_REVIEW_PATH, 'nu', 'review.nu') }}; \
deepseek-review {{OPTIONS}}
# Run the test cases locally by nutest
test:
@use $'($nu.default-config-dir)/lib/nutest' *; run-tests
# Plugins need to be registered only once after nu v0.61
_setup:
@register -e json {{ join(NU_DIR, _query_plugin) }}

164
README.md
View File

@@ -1,27 +1,44 @@
# DeepSeek Code Review
![Tests](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.total&label=Tests)
![Passed](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.passed&label=Passed&color=%2331c654)
![Failed](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.failed&label=Failed&color=red)
![Skipped](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.skipped&label=Skipped&color=yellow)
[中文说明](README.zh-CN.md)
`deepseek-review` also offers seamless integration with DeepSeek models on SiliconCloud. [Sign Up Now](https://cloud.siliconflow.cn/i/rqCdIxzS) to **Claim Your Free 20 Million Tokens** and start exploring its capabilities!
## Features
### GitHub Action
- 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
- Fully Customizable: Choose Models, Base URLs, and Prompts
- 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
- Add `skip cr` or `skip review` to the PR title or body to disable code review in GitHub Actions
- Cross-platform Support: Compatible with GitHub Runners across `macOS`, `Ubuntu`, and `Windows`.
### Local Code Review
- Streaming output support for local code review
- Review remote GitHub PRs directly from your local CLI
- Review commit changes with DeepSeek for any local repository via CLI
- Support on-demand change generation via `git show`/`git diff` commands for further code review
- Output code review results to a specified file in Markdown format
- Cross-platform compatibility: Designed to function seamlessly across all platforms capable of running [Nushell](https://github.com/nushell/nushell)
### Both GH Action & Local
- Support both DeepSeek's `V3` and `R1` models
- Fully customizable: Choose models, base URLs, and prompts
- Support self-hosted DeepSeek models for enhanced flexibility
- Perform code reviews for changes that either include or exclude specific files
## Planned Features
- [ ] **Trigger Code Review on Mention**: Automatically initiate code review when the `github-actions` bot is mentioned in a PR comment.
- [ ] **Generate Commit Message Locally**: Generate a commit message for the code changes in any local repository.
## Code Review with GitHub Action
### Initiate Code Review When PR was Created
### Initiate Code Review When a PR is Created
Add a GitHub workflow with the following contents:
@@ -63,13 +80,13 @@ jobs:
</details>
When a PR is created, DeepSeek code review will be automatically triggered, and the review results(depend on your prompt) will be posted as comments on the corresponding PR. For example:
When a PR is created, DeepSeek code review will be automatically triggered, and the review results (depending on your prompt) will be posted as comments on the corresponding PR. For example:
- [Example 1](https://github.com/hustcer/deepseek-review/pull/30) with [default prompts](https://github.com/hustcer/deepseek-review/blob/main/action.yaml#L35) & [Run Log](https://github.com/hustcer/deepseek-review/actions/runs/13043609677/job/36390331791#step:2:53).
- [Example 2](https://github.com/hustcer/deepseek-review/pull/68) with [this prompt](https://github.com/hustcer/deepseek-review/blob/eba892d969049caff00b51a31e5c093aeeb536e3/.github/workflows/cr.yml#L32)
### Trigger CR When a Specific Label was Added
### Trigger Code Review When a Specific Label is Added
If you don't want automatic review on PR creation, you can choose to trigger code review by adding a label. For example, create the following workflow:
If you don't want automatic code review on PR creation, you can choose to trigger code review by adding a label. For example, create the following workflow:
```yaml
name: Code Review
@@ -102,21 +119,21 @@ With this setup, DeepSeek code review will not run automatically upon PR creatio
| Name | Type | Description |
| -------------- | ------ | ----------------------------------------------------------------------- |
| chat-token | String | Required, DeepSeek API Token |
| model | String | Optional, The model used for code review, defaults to `deepseek-chat` |
| model | String | Optional, The model used for code review, defaults to `deepseek-v4-flash` |
| base-url | String | Optional, DeepSeek API Base URL, defaults to `https://api.deepseek.com` |
| max-length | Int | Optional, Maximum length(Unicode width) of the content for review, if the content length exceeds this value, the review will be skipped. Default `0` means no limit. |
| max-length | Int | Optional, Maximum length (Unicode width) of the content for review. If the content length exceeds this value, the review will be skipped. Default `0` means no limit. |
| sys-prompt | String | Optional, System prompt corresponding to `$sys_prompt` in the payload, default value see note below |
| user-prompt | String | Optional, User prompt corresponding to `$user_prompt` in the payload, default value see note below |
| temperature | Number | Optional, The temperature for the model to generate the response, between `0` and `2`, default value `1.0` |
| include-patterns | String | Optional, The comma separated file patterns to include in the code review. No default |
| exclude-patterns | String | Optional, The comma separated file patterns to exclude in the code review. Default to `pnpm-lock.yaml,package-lock.json,*.lock` |
| github-token | String | Optional, The `GITHUB_TOKEN` secret or personal access token to authenticate. Defaults to `github.token`. |
| temperature | Number | Optional, The temperature for the model to generate the response, between `0` and `2`. Default value is `0.3` |
| include-patterns | String | Optional, Comma-separated file patterns to include in the code review. No default |
| exclude-patterns | String | Optional, Comma-separated file patterns to exclude from the code review. Defaults to `pnpm-lock.yaml,package-lock.json,*.lock` |
| github-token | String | Optional, The `GITHUB_TOKEN` secret or personal access token to authenticate. Defaults to `${{ github.token }}`. |
**DeepSeek API Call Payload**:
```js
{
// `$model` default value: deepseek-chat
// `$model` default value: deepseek-v4-flash
model: $model,
stream: false,
temperature: $temperature,
@@ -135,40 +152,45 @@ With this setup, DeepSeek code review will not run automatically upon PR creatio
> [!NOTE]
>
> You can control the language of the code review results by the language of the
> Prompt. The default Prompt language is currently English. When you use a Chinese
> Prompt, the generated code review results will be in Chinese.
> You can control the language of the code review results through the language of the
> prompt. The default prompt language is currently English. When you use a Chinese
> prompt, the generated code review results will be in Chinese.
## Local Code Review
### Required Tools
To perform code reviews locally(should works for `macOS`, `Ubuntu`, and `Windows`), you need to install the following tools:
To perform code reviews locally (works on `macOS`, `Ubuntu`, and `Windows`), you need to install the following tools:
- [`Nushell`](https://www.nushell.sh/book/installation.html). It is recommended to install the latest versions.
- The latest version of [`awk`](https://github.com/onetrueawk/awk) or [`gawk`](https://www.gnu.org/software/gawk/) is required, with `gawk` being the preferred choice.
- [`Nushell`](https://www.nushell.sh/book/installation.html). It is recommended to install the latest version (minimum version required: `0.112.2`).
- The latest version of [`awk`](https://github.com/onetrueawk/awk) or [`gawk`](https://www.gnu.org/software/gawk/) is required, with `gawk` being preferred.
- Clone this repository to your local machine, navigate to the repository directory, and run `nu cr -h`. You should see an output similar to the following:
```console
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
-r, --repo <string>: GitHub repository name, e.g. hustcer/deepseek-review
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review
-n, --pr-number <string>: GitHub PR number
-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
-f, --diff-from <string>: Git diff starting commit SHA
-t, --diff-to <string>: Git diff ending commit SHA
-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-v4-flash` by default
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
-U, --chat-url <string>: DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
-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 `0.3`
-C, --config <string>: Config file path, default to `config.yml`
-o, --output <string>: Output file path
-h, --help: Display the help message for this command
Parameters:
@@ -178,27 +200,77 @@ Parameters:
### Environment Configuration
To perform code reviews locally, you need to modify the configuration file. A sample configuration file `.env.example` is already provided in the repository. Copy it to `.env` and adjust it according to your actual setup.
To perform code reviews locally, you need to modify the configuration file. The repository provides a configuration example [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml). Copy it to `config.yml` and modify it according to your needs. **Read the comments in the configuration file carefully**, as they explain the purpose of each configuration item.
> [!WARNING]
>
> The `.env` configuration file is only used locally and will not be utilized in GitHub
> Workflow. Please securely store any sensitive information in it and avoid committing
> it to the code repository.
> The `config.yml` configuration file is **only used locally** and will not be utilized in GitHub Workflows. **Sensitive information** in this file should be properly secured and **never committed** to the code repository.
>
### Usage Examples
**Create a Command Alias**
For convenience when performing code reviews across any local repository, create a command alias. For example:
```sh
# Perform code review on the `git diff` changes in the local DEFAULT_LOCAL_REPO repo
nu cr
# Perform code review on the `git diff f536acc` changes in the local DEFAULT_LOCAL_REPO repo
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
# 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
nu cr --pr-number 31 --repo hustcer/deepseek-review
# For Nushell: Modify config.nu and add:
alias cr = nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml
# Modify ~/.zshrc for zsh, ~/.bashrc for bash, or ~/.config/fish/config.fish for fish and add:
alias cr="nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml"
# After sourcing the modified profile, use `cr` for code reviews
# For Windows PowerShell users, set the cr alias by editing $PROFILE and add:
function cr {
nu D:\absolute\path\to\deepseek-review\cr --config D:\absolute\path\to\deepseek-review\config.yml @args
}
# Then restart the terminal or run `. $PROFILE` in pwsh to make `cr` work
```
### Review Local Repository
To review a local repository:
- Navigate to the Git repository directory.
- Use the `cr` command to review current modifications, provided that `config.yml` is correctly configured.
**Usage Examples**
```sh
# Perform code review on the `git diff` changes in the current directory
cr
# Perform code review on the `git diff f536acc` changes in the current directory
cr --diff-from f536acc
# Perform code review on the `git diff f536acc` changes and output the result to review.md
cr --diff-from f536acc --output review.md
# Perform code review on the `git diff f536acc 0dd0eb5` changes in the current directory
cr --diff-from f536acc --diff-to 0dd0eb5
# Review the changes in the current directory using the `--patch-cmd` flag
cr --patch-cmd 'git diff head~3'
cr -c 'git show head~3'
cr -c 'git diff 2393375 71f5a31'
cr -c 'git diff 2393375 71f5a31 nu/*'
cr -c 'git diff 2393375 71f5a31 :!nu/*'
# Dangerous commands like `cr -c 'git show head~3; rm ./*'` will not be allowed
```
### Review Remote GitHub PR Locally
When reviewing a remote GitHub PR locally:
- Always specify the PR number via `--pr-number`.
- Use `--repo` to indicate the target repository (e.g., `hustcer/deepseek-review`). If `--repo` is omitted, the tool reads `settings.default-github-repo` from `config.yml`.
**Usage Examples**
```sh
# Perform code review on PR #31 in the remote DEFAULT_GITHUB_REPO repository
cr --pr-number 31
# Perform code review on PR #31 in the remote hustcer/deepseek-review repository
cr --pr-number 31 --repo hustcer/deepseek-review
# Perform code review on PR #31 and exclude changes in pnpm-lock.yaml
cr --pr-number 31 --exclude pnpm-lock.yaml
```
## License

View File

@@ -1,21 +1,38 @@
# DeepSeek 代码审查
本工具也支持使用 SiliconCloud 上的 DeepSeek 模型,[注册](https://cloud.siliconflow.cn/i/rqCdIxzS) 就**免费赠送 2000 万 Token**,赶紧试试吧!
![Tests](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.total&label=Tests)
![Passed](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.passed&label=Passed&color=%2331c654)
![Failed](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.failed&label=Failed&color=red)
![Skipped](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.skipped&label=Skipped&color=yellow)
## 特性
### GitHub Action
- 通过 GitHub Action 使用 DeepSeek 进行自动化 PR 审查
- 在 PR 的标题或描述中添加 `skip cr` or `skip review` 可跳过 GitHub Actions 里的代码审查
- 跨平台:支持 GitHub `macOS`, `Ubuntu` & `Windows` Runners
### 本地代码审查
- 本地代码审查的时候支持流式输出
- 通过本地 CLI 直接审查远程 GitHub PR
- 通过本地 CLI 使用 DeepSeek 分析任何本地仓库的提交变更
- 通过本地 CLI 使用 DeepSeek 审查任何本地仓库的提交变更
- 允许通过自定义 `git show`/`git diff` 命令生成变更记录并进行审查
- 允许将代码审查结果以 Markdown 格式输出到指定文件
- 跨平台:理论上只要能运行 [Nushell](https://github.com/nushell/nushell) 即可使用本工具
### 本地或 GH Action
- 支持 DeepSeek `V3``R1` 模型
- 完全可定制:选择模型、基础 URL 和提示词
- 支持自托管 DeepSeek 模型,提供更强的灵活性
- 在 PR 的标题或描述中添加 `skip cr` or `skip review` 可跳过 GitHub Actions 里的代码审查
- 对指定文件变更进行包含/排除式代码审查
- 跨平台:支持 GitHub `macOS`, `Ubuntu` & `Windows` Runners
## 计划支持特性
- [ ] **通过提及触发代码审查**:当 PR 评论中提及 `github-actions bot` 时,自动触发代码审查
- [ ] **本地生成提交信息**:为本地仓库的代码变更生成 Commit Message
## 通过 GitHub Action 进行代码审查
@@ -100,12 +117,12 @@ jobs:
| 名称 | 类型 | 描述 |
| -------------- | ------ | -------------------------------------------------------------- |
| chat-token | String | 必填DeepSeek API Token |
| model | String | 可选,配置代码审查选用的模型,默认为 `deepseek-chat` |
| model | String | 可选,配置代码审查选用的模型,默认为 `deepseek-v4-flash` |
| base-url | String | 可选DeepSeek API Base URL, 默认为 `https://api.deepseek.com` |
| max-length | Int | 可选,待审查内容的最大 Unicode 长度, 默认 `0` 表示没有限制,超过非零值则跳过审查 |
| sys-prompt | String | 可选,系统提示词对应入参中的 `$sys_prompt`, 默认值见后文注释 |
| user-prompt | String | 可选,用户提示词对应入参中的 `$user_prompt`, 默认值见后文注释 |
| temperature | Number | 可选,采样温度,介于 `0``2` 之间, 默认值 `1.0` |
| temperature | Number | 可选,采样温度,介于 `0``2` 之间, 默认值 `0.3` |
| include-patterns | String | 可选,代码审查中要包含的以逗号分隔的文件模式,无默认值 |
| exclude-patterns | String | 可选,代码审查中要排除的以逗号分隔的文件模式,默认值为 `pnpm-lock.yaml,package-lock.json,*.lock` |
| github-token | String | 可选,用于访问 API 进行 PR 管理的 GitHub Token默认为 `${{ github.token }}` |
@@ -114,7 +131,7 @@ DeepSeek 接口调用入参:
```js
{
// `$model` default value: deepseek-chat
// `$model` default value: deepseek-v4-flash
model: $model,
stream: false,
temperature: $temperature,
@@ -142,7 +159,7 @@ DeepSeek 接口调用入参:
在本地进行代码审查,支持 `macOS`, `Ubuntu` & `Windows` 不过需要安装以下工具:
- [`Nushell`](https://www.nushell.sh/book/installation.html), 建议安装最新版本
- [`Nushell`](https://www.nushell.sh/book/installation.html), 建议安装最新版本(最低版本 `0.112.2`)
- [`awk`](https://github.com/onetrueawk/awk) 或者 [`gawk`](https://www.gnu.org/software/gawk/) 的最新版版本,优先推荐 `gawk`
- 接下来只需要把本仓库代码克隆到本地,然后进入仓库目录执行 `nu cr -h` 即可看到类似如下输出:
@@ -150,22 +167,27 @@ 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
-r, --repo <string>: GitHub repository name, e.g. hustcer/deepseek-review
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review
-n, --pr-number <string>: GitHub PR number
-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
-f, --diff-from <string>: Git diff starting commit SHA
-t, --diff-to <string>: Git diff ending commit SHA
-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-v4-flash` by default
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
-U, --chat-url <string>: DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
-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 `0.3`
-C, --config <string>: Config file path, default to `config.yml`
-o, --output <string>: Output file path
-h, --help: Display the help message for this command
Parameters:
@@ -175,26 +197,73 @@ Parameters:
### 环境配置
在本地进行代码审查需要先修改配置文件,仓库里已经有了 `.env.example` 配置文件示例,将其拷贝到 `.env` 然后根据自己的实际情况进行修改即可。
在本地进行代码审查需要先修改配置文件,仓库里已经有了 [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml) 配置文件示例,将其拷贝到 `config.yml` 然后根据自己的实际情况进行修改即可,在修改配置文件的过程中请仔细阅读其中的注释,注释会说明每个配置项的作用
> [!WARNING]
>
> `.env` 配置文件仅在本地使用,在 GitHub Workflow 里面不会使用,里面的敏感信息请
> `config.yml` 配置文件仅在本地使用,在 GitHub Workflow 里面不会使用,里面的敏感信息请
> 妥善保存,不要提交到代码仓库里面
>
### 使用举例
**创建命令别名**
为了方便您可以在任意本地仓库进行代码审查需要创建一个别名,比如:
```sh
# Nushell: 修改其 config.nu 配置文件,添加:
alias cr = nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml
# Modify ~/.zshrc for zsh or ~/.bashrc for bash or ~/.config/fish/config.fish for fish and add:
alias cr="nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml"
# After sourcing the profile you have edit, you can use `cr` now
# For Windows powershell users please set cr alias by editing $PROFILE and add:
function cr {
nu D:\absolute\path\to\deepseek-review\cr --config D:\absolute\path\to\deepseek-review\config.yml @args
}
# Then restart the terminal or run `. $PROFILE` in pwsh to make `cr` work
```
之后就可以通过 `cr` 命令来进行代码审查了。
### 审查本地仓库
对本地仓库进行代码审查时需要先切换到 Git 仓库所在目录,然后通过 `cr` 命令即可对当前目录的当前修改进行代码审查,前提是您已经对 `config.yml` 进行了正确的配置。
**使用举例**
```sh
# 对本地当前目录所在仓库 `git diff` 修改内容进行代码审查
cr
# 对本地当前目录所在仓库 `git diff f536acc` 修改内容进行代码审查
cr --diff-from f536acc
# 对本地当前目录所在仓库 `git diff f536acc` 修改内容进行代码审查并将审查结果输出到 review.md
cr --diff-from f536acc --output review.md
# 对本地当前目录所在仓库 `git diff f536acc 0dd0eb5` 修改内容进行代码审查
cr --diff-from f536acc --diff-to 0dd0eb5
# 通过 --patch-cmd 参数对本地当前目录所在仓库变更内容进行审查
cr --patch-cmd 'git diff head~3'
cr -c 'git show head~3'
cr -c 'git diff 2393375 71f5a31'
cr -c 'git diff 2393375 71f5a31 nu/*'
cr -c 'git diff 2393375 71f5a31 :!nu/*'
# 像 `cr -c 'git show head~3; rm ./*'` 这样危险的命令将会被禁止
```
### 本地审查远程 GitHub PR
在本地对远程 GitHub 仓库的 PR 进行审查的时候一定要通过 `--pr-number` 传入待审查的 PR 编号,以及 `--repo` 指明待审查的仓库,比如 `hustcer/deepseek-review`, 如果没有指定 `--repo` 参数则从 config.yml 里面的 `settings.default-github-repo` 配置项读取待审查的仓库。
**使用举例**
```sh
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff` 修改内容进行代码审查
nu cr
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff f536acc` 修改内容进行代码审查
nu cr --diff-from f536acc
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff f536acc 0dd0eb5` 修改内容进行代码审查
nu cr --diff-from f536acc --diff-to 0dd0eb5
# 对远程 DEFAULT_GITHUB_REPO 仓库编号为 31 的 PR 进行代码审查
nu cr --pr-number 31
cr --pr-number 31
# 对远程 hustcer/deepseek-review 仓库编号为 31 的 PR 进行代码审查
nu cr --pr-number 31 --repo hustcer/deepseek-review
cr --pr-number 31 --repo hustcer/deepseek-review
# 对 PR 进行审查的时候排除 pnpm-lock.yaml 文件的变更
cr --pr-number 31 --exclude pnpm-lock.yaml
```
## 许可

View File

@@ -24,11 +24,11 @@ inputs:
description: 'The maximum length of the content for review, 0 means no limit.'
model:
required: false
default: 'deepseek-chat'
default: 'deepseek-v4-flash'
description: 'The DeepSeek model to choose for code review.'
temperature:
required: false
default: 1.0
default: 0.3
description: 'The temperature of the model.'
base-url:
required: false
@@ -58,9 +58,9 @@ runs:
using: 'composite'
steps:
- name: Setup Nu
uses: hustcer/setup-nu@v3
uses: hustcer/setup-nu@v3.23
with:
version: 0.102.0
version: 0.112.2
- name: DeepSeek Code Review
shell: nu {0}
@@ -78,7 +78,7 @@ runs:
let includePatterns = '${{ inputs.include-patterns }}'
let excludePatterns = '${{ inputs.exclude-patterns }}'
let maxLength = try { '${{ inputs.max-length }}' | into int } catch { 0 }
let temperature = try { '${{ inputs.temperature }}' | into float } catch { 1.0 }
let temperature = try { '${{ inputs.temperature }}' | into float } catch { 0.3 }
(deepseek-review $token
--model $model
--repo $repo

443
config.example.yml Normal file
View File

@@ -0,0 +1,443 @@
# DeepSeek-Review Config File for Local Code Review Only
# @author : hustcer
# @created: 2025/02/12 19:05:20
# WARN:
# - This file is for local code review only, don't commit it to the git repository
# - This file may contain sensitive information, such as API tokens, don't expose it to others
# RULES:
# - Multiple providers could be defined, but only one could be selected, with its name in 'settings.provider'
# - One and Only one model could be enabled in one model group
# - Model in model groups is disabled by default, even if there is no 'enabled' field
settings:
# The provider to use for code review, with it's name in 'providers'
provider: 'DeepSeek'
# The maximum length of the content for review, 0 means no limit
# The value should be a positive integer, with default value 0
# If the content length exceeds the non-zero limit, the review will be skipped
# Note that it's unicode width not LLM token length
max-length: 0
# The temperature of the model, The value should be between 0 and 2, with default value 0.3
temperature: 0.3
# The user prompt name to use for DeepSeek API select from 'prompts.user'
user-prompt: 'default'
# The system prompt name to use for DeepSeek API select from 'prompts.system'
system-prompt: 'default'
# The GITHUB_TOKEN secret or personal access token(PAT) to authenticate
# This token is used to fetch the PR changes from GitHub API
# Default value will be ${{ github.token }} if used in GitHub Actions
github-token: 'YOUR_GITHUB_TOKEN'
# Default GitHub repository to review, could be overrode by '-r' or '--repo' if used with `-n` or `--pr-number`
default-github-repo: 'hustcer/deepseek-review'
# Include changes in the following file patterns
include-patterns: ''
# Exclude changes in the following file patterns
exclude-patterns: 'pnpm-lock.yaml,package-lock.json,*.lock'
# Multiple providers could be defined, select the one by name in 'settings.provider'
# This way you could switch between different predefined providers easily
providers:
- name: ollama-local
token: 'empty'
chat-url: http://localhost:11555/api/chat
models:
- name: deepseek-r1
alias: r1
enabled: true
description: 'DeepSeek R1 model running on Ollama'
- name: 'DeepSeek'
token: 'YOUR_DEEPSEEK_TOKEN' # Required, The API token for the provider
base-url: 'https://api.deepseek.com'
models:
- name: 'deepseek-v4-flash' # Required, Pass the model name to --model flag to use it
alias: v3 # Optional, Alias name could also be passed to --model flag
enabled: true # One and Only one model could be enabled in one model group
description: 'DeepSeek V3' # Optional, Description of the model, won't be used actually
- name: 'deepseek-reasoner'
alias: r1
enabled: false # Model is disabled by default
description: 'DeepSeek R1'
- name: 'SiliconFlow'
token: 'YOUR_SILICONFLOW_TOKEN'
base-url: 'https://api.siliconflow.cn/v1' # Required if not the official API
models:
- name: 'deepseek-ai/DeepSeek-V3'
alias: v3
enabled: true
description: 'SiliconFlow DeepSeek V3 model'
- name: 'deepseek-ai/DeepSeek-R1'
alias: r1
description: 'SiliconFlow DeepSeek R1 model'
- name: OpenRouter
token: sk-or-v1-*****
base-url: https://openrouter.ai/api/v1
models:
- name: deepseek/deepseek-chat-v3-0324:free
alias: v3
enabled: true
description: 'OpenRouter DeepSeek V3 model'
- name: deepseek/deepseek-r1:free
alias: r1
description: 'OpenRouter DeepSeek R1 model'
# Multiple Prompts could be defined, select the one by name in 'settings.user-prompt' or 'settings.system-prompt'
prompts:
user:
- name: default
prompt: 'Please review the following code changes:'
system:
- name: default
prompt: >
You are a professional code review assistant responsible for analyzing code changes in GitHub Pull Requests.
Identify potential issues such as code style violations, logical errors, security vulnerabilities, and provide
improvement suggestions. Clearly list the problems and recommendations in a concise manner.
- name: frontend
prompt: >
As a senior Frontend engineer, perform comprehensive code review with focus on:
### 1. Core Requirements:
- Validate ES specification compliance
- Check component design patterns
- Verify state management efficiency
- Assess accessibility implementation
### 2. Security Analysis:
- XSS prevention measures
- CSRF protection implementation
- Safe third-party dependency usage
- Sensitive data handling practices
### 3. Performance Optimization:
- Resource loading strategies
- Rendering performance optimization
- Memory leak prevention
- Bundle size management
**Rules:**
- Target React/Vue framework conventions
- Highlight security vulnerabilities
- Suggest performance metrics improvements
- Provide code snippets for fixes
- Use modern frontend terminology
**Required output structure:**
#### Code Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
```yaml
checklist:
- Compatibility: ["Browser support", "Responsive design", "ES specification"]
- Security: ["Input sanitization", "CORS configuration", "Cookie flags"]
- Reliability: ["Error boundaries", "Loading states", "Fallback UI"]
- Performance: ["Lazy loading", "Code splitting", "Rendering optimization"]
examples:
- issue: "❗ Unsanitized HTML insertion in line 28 (XSS risk)"
- issue: "⚠️ Missing `rel='noreferrer'` on external links in line 15"
- suggestion: "Implement virtual scrolling for large datasets in TableComponent"
- suggestion: "Convert CSS-in-JS to CSS Modules for better tree-shaking"
response_template: |
#### Code Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```
- name: java
prompt: >
As a senior Java Backend engineer, perform comprehensive code review with focus on:
### 1. Core Requirements:
- Validate Java coding standards compliance
- Check enterprise design patterns implementation
- Verify resource management efficiency
- Assess concurrency control mechanisms
### 2. Security Analysis:
- SQL/NoSQL injection prevention
- Sensitive data encryption handling
- Proper authentication/authorization
- Secure session management
### 3. Performance Optimization:
- Thread pool configuration analysis
- Database connection management
- Cache strategy evaluation
- GC tuning opportunities
**Rules:**
- Target Spring Boot/Jakarta EE frameworks
- Highlight OWASP TOP 10 vulnerabilities
- Suggest JVM optimization strategies
- Provide code snippets for fixes
- Use enterprise Java terminology
**Required output structure:**
#### Code Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
```yaml
checklist:
- Compatibility: ["JDK version", "Framework version", "API contracts"]
- Security: ["Input validation", "CSRF protection", "Security headers"]
- Reliability: ["Exception handling", "Transaction management", "Circuit breakers"]
- Performance: ["Connection pooling", "Query optimization", "Object reuse"]
examples:
- issue: "❗ Unvalidated user input in DAO layer (SQL injection risk)[2,3](@ref)"
- issue: "⚠️ Plaintext credentials storage in properties file[2](@ref)"
- suggestion: "Replace synchronized blocks with ReentrantLock for better concurrency[3](@ref)"
- suggestion: "Implement prepared statement caching in JDBC configuration[3](@ref)"
response_template: |
#### Code Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```
- name: rust
prompt: >
As a senior Rust engineer, perform comprehensive code review with focus on:
### 1. Core Requirements:
- Validate Rust coding standards compliance
- Check memory safety guarantees
- Verify concurrency model correctness
- Assess error handling patterns
### 2. Security Analysis:
- Unsafe code usage validation
- Data race prevention measures
- Input sanitization practices
- Supply chain security checks
### 3. Performance Optimization:
- Memory allocation patterns
- Iterator vs loop efficiency
- Parallel execution opportunities
- Zero-cost abstraction utilization
**Rules:**
- Target Rust 2021 edition features
- Highlight memory safety violations
- Suggest lifetime optimization strategies
- Provide unsafe code alternatives
- Use Rust ecosystem terminology
**Required output structure:**
#### Code Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
```yaml
checklist:
- Compatibility: ["Edition compliance", "Crate versioning", "FFI safety"]
- Security: ["Unsafe scoping", "Panic safety", "Trait bounds"]
- Reliability: ["Error propagation", "Test coverage", "Documentation"]
- Performance: ["Allocation tracking", "Concurrency patterns", "SIMD usage"]
examples:
- issue: "❗ Unmarked unsafe block in line 75 (memory safety violation risk)[1](@ref)"
- issue: "⚠️ Missing error handling for Result in line 42[1](@ref)"
- suggestion: "Replace Box<dyn Trait> with impl Trait for better monomorphization[1](@ref)"
- suggestion: "Use crossbeam-channel instead of std::sync::mpsc for improved throughput[3](@ref)"
response_template: |
#### Code Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```
- name: strict-dev
prompt: >
Act as a senior engineer performing rigorous code review. Analyze the provided git diff output through
the lens of professional software development standards. Structure findings using these guidelines:
1. Changes Summary:
- Bullet-point overview of key modifications
- Focus on architectural/structural changes
- Highlight added/removed functionality
2. Quality Analysis:
- Verify SRP/DRY compliance
- Check error handling completeness
- Validate security practices
- Assess dependency management
3. Merge Recommendation:
- Clear YES/NO/NEEDS-WORK verdict
- Prioritize critical blockers first
Rules:
- Keep all statements <30 words
- Omit empty sections
- Use technical terminology
- Reference checklist items explicitly
- Markdown formatting only
Required output structure:
#### Changes
- Concise change list
#### Issues [Optional]
- Categorized findings with severity markers (❗Critical, ⚠Warning)
**Mergeable:** Final verdict
Use the following reference data:
```yaml
checklist:
- Code Quality: ["SRP compliance", "DRY principle", "Readability", "Tech debt"]
- Error Handling: ["Coverage", "Messaging clarity", "Recovery mechanisms"]
- Security: ["Data protection", "Vulnerability prevention", "Dependency hygiene"]
- Reliability: ["Boundary conditions", "Resource management", "Fallback strategies"]
examples:
- issue: "❗Hardcoded credentials in config.py (security violation)"
- issue: "⚠Duplicate validation logic in user_service.py (DRY violation)"
- change: "Added JWT authentication middleware"
- change: "Refactored payment processor into standalone module"
response_template: |
#### Changes
- {{bullet_points}}
{{#issues}}
#### Issues
- {{issues_list}}
{{/issues}}
**Mergeable:** {{verdict}}
```
- name: devops
prompt: >
As a senior DevOps engineer, perform comprehensive review of shell scripts with focus on:
1. Core Requirements:
- Validate POSIX compatibility
- Check for proper error handling
- Verify safe variable usage
- Assess resource management
2. Security Analysis:
- Shell injection prevention
- Safe file operations
- Proper permissions handling
- Secure command execution
3. Performance Optimization:
- Efficient process management
- Proper use of subshells
- Stream handling best practices
- Avoidance of unnecessary forks
Rules:
- Target bash/sh compatibility
- Highlight security vulnerabilities
- Suggest performance improvements
- Keep feedback actionable
- Use technical shell terminology
Required output structure:
#### Script Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
Use the following reference data:
```yaml
checklist:
- Compatibility: ["POSIX compliance", "Shell-specific features", "Portability"]
- Security: ["Input validation", "Safe eval usage", "Permission checks"]
- Reliability: ["Error handling", "Exit codes", "Signal trapping"]
- Performance: ["Process management", "I/O operations", "Subshell usage"]
examples:
- issue: "❗ Unquoted variable expansion in line 42 (shell injection risk)"
- issue: "⚠️ Missing error handling for rm operation in line 15"
- suggestion: "Replace backticks with $() for better readability and nesting"
- suggestion: "Use exec for file handling to reduce file descriptors"
response_template: |
#### Script Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```

56
cr
View File

@@ -3,14 +3,54 @@
# Created: 2025/02/08 19:02:15
# Description: A wrapper for nu/review.nu as the main entry point of the project.
use nu/review.nu ['from env', ECODE]
use nu/config.nu *
use nu/review.nu [deepseek-review]
use nu/common.nu [hr-line, check-nushell, ECODE]
# Use DeepSeek AI to review code changes locally or in GitHub Actions
def --wrapped main [...rest] {
if not ('.env' | path exists) {
print $'Please refer to (ansi g)`.env.example`(ansi reset) to create a (ansi r)`.env`(ansi reset) file in the root directory of the project.'
exit $ECODE.MISSING_DEPENDENCY
}
open .env | load-env
nu $'($env.FILE_PWD)/nu/review.nu' ...$rest
def main [
token?: string, # Your DeepSeek API token, fallback to CHAT_TOKEN env var
--debug(-d), # Debug mode
--repo(-r): string, # GitHub repo name, e.g. hustcer/deepseek-review
--pr-number(-n): string, # GitHub PR number
--gh-token(-k): string, # Your GitHub token, fallback to GITHUB_TOKEN env var
--diff-from(-f): string, # Git diff starting commit SHA
--diff-to(-t): string, # Git diff ending commit SHA
--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-v4-flash` by default
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
--chat-url(-U): string, # DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
--sys-prompt(-s): string # Default to $DEFAULT_OPTIONS.SYS_PROMPT,
--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(-T): float, # Temperature for the model, between `0` and `2`, default value `0.3`
--config(-C): string # Config file path, default to `config.yml`
--output(-o): string, # Output file path
] {
check-nushell
config-check --config=$config
config-load --debug=$debug --config=$config --model=$model
(
deepseek-review $token
--repo=$repo
--debug=$debug
--output=$output
--model=$env.CHAT_MODEL
--base-url=$base_url
--chat-url=$chat_url
--gh-token=$gh_token
--diff-to=$diff_to
--diff-from=$diff_from
--patch-cmd=$patch_cmd
--pr-number=$pr_number
--max-length=$max_length
--sys-prompt=$sys_prompt
--user-prompt=$user_prompt
--temperature=$temperature
--include=($include | default $env.INCLUDE_PATTERNS?)
--exclude=($exclude | default $env.EXCLUDE_PATTERNS?)
)
}

View File

@@ -5,15 +5,35 @@
words:
- psql
- MPFR
- nuon
- pwsh
- JDBC
- mpsc
- nuons
- ECODE
- vyadh
- nutest
- endfor
- dotenv
- Ollama
- hustcer
- Nushell
- creatio
- pipefail
- justfile
- lefthook
- deepseek
- linewise
- Subshell
- subshells
- noreferrer
- OPENROUTER
- Infinigence
- SILICONFLOW
- USERPROFILE
- Unsanitized
- Unvalidated
- monomorphization
ignorePaths:
- config.yml

View File

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

218
nu/common.nu Normal file
View File

@@ -0,0 +1,218 @@
#!/usr/bin/env nu
# Author: hustcer
# Created: 2025/02/12 19:05:20
# Description: Common helpers for DeepSeek-Review
#
use std-rfc/kv ['kv set', 'kv get']
# Commonly used exit codes
export const ECODE = {
SUCCESS: 0,
OUTDATED: 1,
AUTH_FAILED: 2,
SERVER_ERROR: 3,
MISSING_BINARY: 5,
INVALID_PARAMETER: 6,
MISSING_DEPENDENCY: 7,
CONDITION_NOT_SATISFIED: 8,
}
export const GITHUB_API_BASE = 'https://api.github.com'
# If current host is Windows
export def windows? [] {
# Windows / Darwin
(sys host | get name) == 'Windows'
}
# If current host is macOS
export def mac? [] {
# Windows / Darwin
(sys host | get name) == 'Darwin'
}
# Compare two version number, return `1` if first one is higher than second one,
# Return `0` if they are equal, otherwise return `-1`
# Format: Expects semantic version strings (major.minor.patch)
# - Optional 'v' prefix
# - Pre-release suffixes (-beta, -rc, etc.) are ignored
# - Missing segments default to 0
export def compare-ver [v1: string, v2: string] {
# Parse the version number: remove pre-release and build information,
# only take the main version part, and convert it to a list of numbers
def parse-ver [v: string] {
$v | str downcase | str trim -c v | str trim
| split row - | first | split row . | each { into int }
}
let a = parse-ver $v1
let b = parse-ver $v2
# Compare the major, minor, and patch parts; fill in the missing parts with 0
# If you want to compare more parts use the following code:
# for i in 0..([2 ($a | length) ($b | length)] | math max)
for i in 0..2 {
let x = $a | get -o $i | default 0
let y = $b | get -o $i | default 0
if $x > $y { return 1 }
if $x < $y { return (-1) }
}
0
}
# Check nushell version and notify user to upgrade it if outdated
# Check version once daily and cache the result
export def check-nushell [--debug] {
const _DATE_FMT = '%Y.%m.%d'
const V_KEY = 'NU-VERSION-CHECK@DEEPSEEK-REVIEW'
let version_cached = kv get -u $V_KEY
let today = date now | format date $_DATE_FMT
let check = if ($version_cached | is-empty) or $version_cached.date? != $today {
let $check = try { ({ ...(version check), date: $today }) } catch { ({ current: true }) }
if $debug { print 'Checking for the latest Nushell version...'; $check | print }
kv set -u $V_KEY $check --return value
} else {
$version_cached
}
# If the current version is the latest after user upgrade, return
if $check.current or (compare-ver $check.latest (version).version) == 0 { return }
print $'(char nl) (ansi yr) WARNING: (ansi reset) Your Nushell is (ansi r)OUTDATED(ansi reset)'
print $' ------------> Please upgrade Nushell to the latest version: (ansi g)($check.latest)(ansi reset) <------------'
print -n (char nl)
}
# Converts a .env file into a record
# May be used like this: open .env | load-env
# Works with quoted and unquoted .env files
export def "from env" []: string -> record {
let input = $in
# Process escape sequences in double-quoted values using str replace chain
# Use NUL char as placeholder to avoid replacement conflicts
let process_escapes = {|content: string|
$content
| str replace -a '\\' (char nul) # Placeholder for \\ to avoid conflicts
| str replace -a '\n' (char nl)
| str replace -a '\r' (char cr)
| str replace -a '\t' (char tab)
| str replace -a '\"' '"'
| str replace -a (char nul) '\' # Restore \\ to single \
}
# Parse double-quoted value with escape sequence support
let parse_double_quoted = {|val: string|
let matched = ($val | parse -r '^"(?P<content>(?:[^"\\]|\\.)*)"')
if ($matched | is-empty) { $val | str trim -c '"' } else { do $process_escapes $matched.0.content }
}
# Parse single-quoted value (no escape processing)
let parse_single_quoted = {|val: string|
let matched = ($val | parse -r "^'(?P<content>[^']*)'")
if ($matched | is-empty) { $val | str trim -c "'" } else { $matched.0.content }
}
# Parse unquoted value: handle escaped hash (\#) and strip inline comments
let parse_unquoted = {|val: string|
$val
| str replace -a '\#' (char nul) # Placeholder for \#
| split row '#' # Split by comment delimiter
| first # Take content before first #
| str replace -a (char nul) '#' # Restore \# to #
| str trim
}
# Parse value based on its format
let parse_value = {|val: string|
match $val {
$v if ($v | str starts-with '"') => { do $parse_double_quoted $v }
$v if ($v | str starts-with "'") => { do $parse_single_quoted $v }
_ => { do $parse_unquoted $val }
}
}
let parsed = $input | lines
| str trim
| compact -e
| where {|line| not ($line | str starts-with '#') }
| parse "{key}={value}"
| update key {|row| $row.key | str trim | str replace -r '^export\s+' '' }
| update value {|row| do $parse_value ($row.value | str trim) }
if ($parsed | is-empty) { {} } else { $parsed | transpose -r -d -l }
}
# Compact the record by removing empty columns
export def compact-record []: record -> record {
let record = $in
let empties = $record | columns | where {|it| $record | get $it | is-empty }
$record | reject ...$empties
}
# Check if some command available in current shell
export def is-installed [ app: string ] {
(which $app | length) > 0
}
export def hr-line [
width?: int = 90,
--blank-line(-b),
--with-arrow(-a),
--color(-c): string = 'g',
] {
# Create a line by repeating the unit with specified times
def build-line [
times: int,
unit: string = '-',
] {
0..<$times | reduce -f '' { |i, acc| $unit + $acc }
}
print $'(ansi $color)(build-line $width)(if $with_arrow {'>'})(ansi reset)'
if $blank_line { char nl | print -n }
}
# Check if git was installed and if current directory is a git repo
export def git-check [
dest: string, # The dest dir to check
--check-repo: int, # Check if current directory is a git repo
] {
cd $dest
if not (is-installed git) {
print $'You should (ansi r)INSTALL git(ansi reset) first to run this command, bye...'
exit $ECODE.MISSING_BINARY
}
# If we don't need repo check just quit now
if ($check_repo != 0) {
if not (is-repo) {
print $'Current directory is (ansi r)NOT(ansi reset) a git repo, bye...(char nl)'
exit $ECODE.CONDITION_NOT_SATISFIED
}
}
true
}
# Check if current directory is a git repo
export def is-repo [] {
let checkRepo = try {
# Put `complete` inside `do` block to avoid pipefail error in Nushell 0.110+
do { git rev-parse --is-inside-work-tree | complete }
} catch {
({ stdout: 'false' })
}
if ($checkRepo.stdout =~ 'true') { true } else { false }
}
# Check if a git repo has the specified ref: could be a branch or tag, etc.
export def has-ref [
ref: string # The git ref to check
] {
if not (is-repo) { return false }
# Put `complete` inside `do` block to avoid pipefail error in Nushell 0.110+
let parse = (do { git rev-parse --verify -q $ref | complete })
if ($parse.stdout | is-empty) { false } else { true }
}
# Notify the user that the `CHAT_TOKEN` hasn't been configured
export const NO_TOKEN_TIP = (
"**Notice:** It looks like you're using [`hustcer/deepseek-review`](https://github.com/hustcer/deepseek-review), but the `CHAT_TOKEN` hasn't " +
"been configured in your repo's **Variables/Secrets**. Please ensure this token is set for proper functionality. For step-by-step guidance, refer " +
"to the **CHAT_TOKEN Config** section of [README](https://github.com/hustcer/deepseek-review/blob/main/README.md#code-review-with-github-action).")

161
nu/config.nu Normal file
View File

@@ -0,0 +1,161 @@
#!/usr/bin/env nu
# Author: hustcer
# Created: 2025/02/13 19:56:56
# Description: Validate and load the config.yml file.
#
# TODO:
# [√] Check if the config.yml file exists.
# [√] Check if the prompt config keys exist.
use common.nu [ECODE, hr-line]
# Config file name
const SETTING_FILE = 'config.yml'
# Check if the config.yml file exists.
def file-exists [file: string] {
if ($file | path exists) { return true }
print $'The config file (ansi r)($file)(ansi reset) does not exist. '
print $'Please copy the (ansi g)config.example.yml(ansi reset) file to create a new one.'
exit $ECODE.MISSING_DEPENDENCY
}
# Check if the prompt keys exist in the config.yml file
def check-prompts [options: record] {
check-prompt $options user
check-prompt $options system
}
# Check if the specified type of prompt key exists in the config.yml file
def check-prompt [options: record, type: string] {
let prompt_key = $options.settings | get -o $'($type)-prompt' | default ''
if ($prompt_key | is-empty) {
print $'(ansi r)The ($type) prompt key is missing in `settings.($type)-prompt` config.yml file.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let prompt = $options.prompts | get -o $type
| default []
| where name == $prompt_key
| get -o 0.prompt
if ($prompt | is-empty) {
print $'The ($type) prompt (ansi r)($prompt_key)(ansi reset) is missing in `prompts.($type)` of config.yml file.'
exit $ECODE.INVALID_PARAMETER
}
}
# Check if the model providers and models are correctly configured in config.yml
def check-providers [options: record] {
# settings.provider correctly configured and related provider exists
let provider_name = $options.settings.provider
if ($provider_name | is-empty) {
print $'(ansi r)The provider name is missing in `settings.provider` of config.yml file.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let provider_exists = $options.providers
| where name == $provider_name
| is-not-empty
if not $provider_exists {
print $'(ansi r)The provider ($provider_name) does not exist in `providers` of config.yml file.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
# Each provider should have name, token and models field
$options.providers | each {|p|
let empties = [name token models] | where { |field| $p | get -o $field | is-empty }
if ($empties | is-not-empty) {
print $'Field (ansi r)`($empties | str join ,)`(ansi reset) should not be empty for provider:'
$p | table -e -t psql | print
exit $ECODE.INVALID_PARAMETER
}
}
}
# Check if the models are correctly configured in config.yml
def check-models [options: record] {
# Each model group should have one and only one enabled model
$options.providers | each {|provider|
let enabled_models = $provider.models | default false enabled | where enabled | length
if ($enabled_models != 1) {
print $'Model group (ansi r)`($provider.name)`(ansi reset) should have one and only one enabled model.'
exit $ECODE.INVALID_PARAMETER
}
}
# All models should have a name field
$options.providers | each {|provider|
$provider.models | enumerate | each {|e|
if ($e.item.name? | is-empty) {
print $'Model name is missing for provider (ansi r)`($provider.name)` model #($e.index)(ansi reset)...'
exit $ECODE.INVALID_PARAMETER
}
}
}
}
# Check if the config.yml file exists and if it's valid
export def config-check [--config: string] {
let config = $config | default $SETTING_FILE
file-exists $config
let options = open $config
check-prompts $options
check-providers $options
check-models $options
}
# Get model config information
def get-model-envs [settings: record, model?: string = ''] {
let name = $settings.settings?.provider? | default ''
let provider = $settings.providers
| default []
| where name == $name
| get -o 0
| default {}
let model_name = $provider.models
| default []
| where {|it| if ($model | is-empty) {
$it.enabled? | default false
} else {
$it.name == $model or $it.alias? == $model }
}
| get -o 0.name
| default $model
{ CHAT_TOKEN: $provider.token?, BASE_URL: $provider.base-url?, CHAT_URL: $provider.chat-url?, CHAT_MODEL: $model_name }
}
# Load the config.yml file to the environment
export def --env config-load [
--debug(-d), # Print the loaded environment variables
--config(-C): string, # The config file path, default to `config.yml`
--model(-m): string, # Load the specified model by name
] {
let all_settings = open ($config | default $SETTING_FILE)
let settings = $all_settings | get settings? | default {}
let user_prompt = $all_settings.prompts?.user?
| default []
| where name == ($settings.user-prompt? | default '')
| get -o 0.prompt
let system_prompt = $all_settings.prompts?.system?
| default []
| where name == ($settings.system-prompt? | default '')
| get -o 0.prompt
let model_envs = get-model-envs $all_settings $model
let env_vars = {
...$model_envs,
USER_PROMPT: $user_prompt,
SYSTEM_PROMPT: $system_prompt,
MAX_LENGTH: $settings.max-length,
TEMPERATURE: $settings.temperature,
GITHUB_TOKEN: $settings.github-token,
EXCLUDE_PATTERNS: $settings.exclude-patterns,
INCLUDE_PATTERNS: $settings.include-patterns,
DEFAULT_GITHUB_REPO: $settings.default-github-repo,
}
load-env $env_vars
if $debug {
print 'Loaded Environment Variables:'; hr-line
$env_vars | table -t psql | print
}
}

157
nu/diff.nu Normal file
View File

@@ -0,0 +1,157 @@
#!/usr/bin/env nu
# Author: hustcer
# Created: 2025/04/02 20:02:15
# Description: Diff command for DeepSeek-Review
use common.nu [GITHUB_API_BASE, ECODE, git-check, has-ref]
use util.nu [generate-include-regex, generate-exclude-regex, prepare-awk, is-safe-git]
# If the PR title or body contains any of these keywords, skip the review
const IGNORE_REVIEW_KEYWORDS = ['skip review' 'skip cr']
# Get the diff content from GitHub PR or local git changes and apply filters
export def get-diff [
--repo: string, # GitHub repository name
--pr-number: string, # GitHub PR number
--diff-to: string, # Diff to git ref
--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 content = (
get-diff-content --repo $repo --pr-number $pr_number --patch-cmd $patch_cmd
--diff-to $diff_to --diff-from $diff_from --include $include --exclude $exclude)
if ($content | is-empty) {
print $'(ansi g)Nothing to review.(ansi reset)'
exit $ECODE.SUCCESS
}
apply-file-filters $content --include $include --exclude $exclude
}
# Get diff content from GitHub PR or local git changes
def get-diff-content [
--repo: string, # GitHub repository name
--pr-number: string, # GitHub PR number
--diff-to: string, # Diff to git ref
--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 local_repo = $env.PWD
if ($pr_number | is-not-empty) {
get-pr-diff --repo $repo $pr_number
} else if ($diff_from | is-not-empty) {
get-ref-diff $diff_from --diff-to $diff_to
} 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) {
get-patch-diff $patch_cmd
} else {
git diff
}
}
# Get the diff content of the specified GitHub PR,
# if the PR description contains the skip keyword, exit
def get-pr-diff [
--repo: string, # GitHub repository name
pr_number: string, # GitHub PR number
] {
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]
if ($repo | is-empty) {
print $'(ansi r)Please provide the GitHub repository name by `--repo` option.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let description = http get -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)'
| select title body | values | str join "\n"
# Check if the PR title or body contains keywords to skip the review
if ($IGNORE_REVIEW_KEYWORDS | any {|it| $description =~ $it }) {
print $'(ansi r)The PR title or body contains keywords to skip the review, bye...(ansi reset)'
exit $ECODE.SUCCESS
}
let commit_msg = http get -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)/commits'
| last | get commit.message
if ($IGNORE_REVIEW_KEYWORDS | any {|it| $commit_msg =~ $it }) {
print $'(ansi r)The latest PR commit message contains keywords to skip the review, bye...(ansi reset)'
exit $ECODE.SUCCESS
}
# Get the diff content of the PR
http get -H $DIFF_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)' | str trim
}
# Get diff content from local git changes
def get-ref-diff [
diff_from: string, # Diff from git REF
--diff-to: string, # Diff to git ref
] {
# Validate the git refs
if not (has-ref $diff_from) {
print $'(ansi r)The specified git ref ($diff_from) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
if ($diff_to | is-not-empty) and not (has-ref $diff_to) {
print $'(ansi r)The specified git ref ($diff_to) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
git diff $diff_from ($diff_to | default HEAD)
}
# Get the diff content from the specified git command
def get-patch-diff [
cmd: string # The `git show` or `git diff` command to get the diff content
] {
let valid = is-safe-git $cmd
if not $valid {
exit $ECODE.INVALID_PARAMETER
}
# Get the diff content from the specified git command
nu -c $cmd
}
# Apply file filters to the diff content to include or exclude specific files
def apply-file-filters [
content: string, # The diff content to filter
--include: string, # Comma separated file patterns to include in the code review
--exclude: string, # Comma separated file patterns to exclude in the code review
] {
mut filtered_content = $content
let awk_bin = (prepare-awk)
let outdated_awk = $'If you are using an (ansi r)outdated awk version(ansi reset), please upgrade to the latest version or use gawk latest instead.'
if ($include | is-not-empty) {
let patterns = $include | split row ','
$filtered_content = $filtered_content | try {
^$awk_bin (generate-include-regex $patterns)
} catch {
print $outdated_awk
exit $ECODE.OUTDATED
}
}
if ($exclude | is-not-empty) {
let patterns = $exclude | split row ','
$filtered_content = $filtered_content | try {
^$awk_bin (generate-exclude-regex $patterns)
} catch {
print $outdated_awk
exit $ECODE.OUTDATED
}
}
$filtered_content
}

View File

@@ -46,9 +46,9 @@ export def 'make-release' [
export def has-ref [
ref: string # The git ref to check
] {
let checkRepo = (do -i { git rev-parse --is-inside-work-tree } | complete)
# Put `complete` inside `do` block to avoid pipefail error in Nushell 0.110+
let checkRepo = (do { git rev-parse --is-inside-work-tree | complete })
if not ($checkRepo.stdout =~ 'true') { return false }
# Brackets were required here, or error will occur
let parse = (do -i { git rev-parse --verify -q $ref } | complete)
let parse = (do { git rev-parse --verify -q $ref | complete })
if ($parse.stdout | is-empty) { false } else { true }
}

450
nu/review.nu Normal file → Executable file
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:
@@ -23,69 +25,70 @@
# - Local Repo Review: just cr -f HEAD~1 --debug
# - Local PR Review: just cr -r hustcer/deepseek-review -n 32
# Commonly used exit codes
export const ECODE = {
SUCCESS: 0,
OUTDATED: 1,
AUTH_FAILED: 2,
SERVER_ERROR: 3,
MISSING_BINARY: 5,
INVALID_PARAMETER: 6,
MISSING_DEPENDENCY: 7,
CONDITION_NOT_SATISFIED: 8,
}
use std-rfc/kv *
use diff.nu [get-diff]
use common.nu [
ECODE, NO_TOKEN_TIP, hr-line, is-installed, windows?, mac?,
compare-ver, compact-record, git-check, has-ref, GITHUB_API_BASE
]
const GITHUB_API_BASE = 'https://api.github.com'
const IGNORED_MESSAGES = {
'-alive': true, # The server is alive
'data: [DONE]': true, # The end of the response
': OPENROUTER PROCESSING': true, # OPENROUTER in PROCESSING message
}
# It takes longer to respond to requests made with unknown/rare user agents.
# When make http post pretend to be curl, it gets a response just as quickly as curl.
const HTTP_HEADERS = [User-Agent curl/8.9]
const DEFAULT_OPTIONS = {
MODEL: 'deepseek-chat',
TEMPERATURE: 1.0,
MODEL: 'deepseek-v4-flash',
TEMPERATURE: 0.3,
BASE_URL: 'https://api.deepseek.com',
USER_PROMPT: 'Please review the following code changes:',
SYS_PROMPT: 'You are a professional code review assistant responsible for analyzing code changes in GitHub Pull Requests. Identify potential issues such as code style violations, logical errors, security vulnerabilities, and provide improvement suggestions. Clearly list the problems and recommendations in a concise manner.',
}
# If the PR title or body contains any of these keywords, skip the review
const IGNORE_REVIEW_KEYWORDS = ['skip review' 'skip cr']
# Use DeepSeek AI to review code changes locally or in GitHub Actions
export def --env deepseek-review [
token?: string, # Your DeepSeek API token, fallback to CHAT_TOKEN env var
--debug(-d), # Debug mode
--repo(-r): string, # GitHub repository name, e.g. hustcer/deepseek-review
--repo(-r): string, # GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
--output(-o): string, # Output file path
--pr-number(-n): string, # GitHub PR number
--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
--diff-to(-t): string, # Git diff ending commit SHA
--diff-from(-f): string, # Git diff starting commit SHA
--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
--model(-m): string, # Model name, or read from CHAT_MODEL env var, `deepseek-v4-flash` by default
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
--chat-url(-U): string, # DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
--sys-prompt(-s): string # Default to $DEFAULT_OPTIONS.SYS_PROMPT,
--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 `0.3`
]: nothing -> nothing {
$env.config.table.mode = 'psql'
let local_repo = $env.PWD
let write_file = ($output | is-not-empty)
let is_action = ($env.GITHUB_ACTIONS? == 'true')
let token = $token | default $env.CHAT_TOKEN?
let repo = $repo | default $env.DEFAULT_GITHUB_REPO?
let CHAT_HEADER = [Authorization $'Bearer ($token)']
let local_repo = $env.DEFAULT_LOCAL_REPO? | default (pwd)
let stream = if $is_action or $write_file { false } else { true }
let model = $model | default $env.CHAT_MODEL? | default $DEFAULT_OPTIONS.MODEL
let base_url = $base_url | default $env.BASE_URL? | default $DEFAULT_OPTIONS.BASE_URL
let url = $chat_url | default $env.CHAT_URL? | default $'($base_url)/chat/completions'
let max_length = try { $max_length | default ($env.MAX_LENGTH? | default 0 | into int) } catch { 0 }
let temperature = try { $temperature | default $env.TEMPERATURE? | default $DEFAULT_OPTIONS.TEMPERATURE | into float } catch { $DEFAULT_OPTIONS.TEMPERATURE }
if ($temperature < 0) or ($temperature > 2) {
print $'(ansi r)Invalid temperature value, should be in the range of 0 to 2.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let url = $'($base_url)/chat/completions'
# Determine output mode
let output_mode = if $is_action { 'action' } else if ($output | is-not-empty) { 'file' } else { 'console' }
validate-temperature $temperature
let setting = {
repo: $repo,
model: $model,
@@ -94,6 +97,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,
@@ -101,301 +105,205 @@ export def --env deepseek-review [
}
$env.GH_TOKEN = $gh_token | default $env.GITHUB_TOKEN?
if ($token | is-empty) {
print $'(ansi r)Please provide your DeepSeek API token by setting `CHAT_TOKEN` or passing it as an argument.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
validate-token $token --pr-number $pr_number --repo $repo
let hint = if not $is_action and ($pr_number | is-empty) {
$'🚀 Initiate the code review by DeepSeek AI for local changes ...'
} else {
$'🚀 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) {
print 'Current Settings:'; hr-line
$setting | compact-record | reject -o repo | print; print -n (char nl)
}
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)'
exit $ECODE.SUCCESS
}
print $'Review content length: (ansi g)($length)(ansi reset), current max length: (ansi g)($max_length)(ansi reset)'
let sys_prompt = $sys_prompt | default (load-prompt-from-env SYSTEM_PROMPT) | default $DEFAULT_OPTIONS.SYS_PROMPT
let user_prompt = $user_prompt | default (load-prompt-from-env USER_PROMPT) | default $DEFAULT_OPTIONS.USER_PROMPT
let sys_prompt = $sys_prompt | default $env.SYSTEM_PROMPT? | default $DEFAULT_OPTIONS.SYS_PROMPT
let user_prompt = $user_prompt | default $env.USER_PROMPT? | default $DEFAULT_OPTIONS.USER_PROMPT
let payload = {
model: $model,
stream: false,
stream: $stream,
temperature: $temperature,
messages: [
{ role: 'system', content: $sys_prompt },
{ role: 'user', content: $"($user_prompt):\n($content)" }
]
],
thinking: { type: 'disabled' }
}
if $debug { print $'Code Changes:'; hr-line; print $content }
if $debug { print $'(char nl)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)'
print $'(ansi r)Oops, No response returned from ($url) ...(ansi reset)'
exit $ECODE.SERVER_ERROR
}
if $debug { print $'DeepSeek Response:'; hr-line; $response | table -e | print }
if $debug { print $'DeepSeek Model Response:'; hr-line; $response | table -e | print }
if ($response | describe) == 'string' {
print $' Code review failedError: '; hr-line; print $response
print $'✖️ Code review failedError: '; hr-line; print $response
exit $ECODE.SERVER_ERROR
}
let review = $response | get -i choices.0.message.content
let message = $response | get -o choices.0.message
let reason = $message | coalesce-reasoning
let review = $message.content? | default ($response | get -o message.content)
let result = ['<details>' '<summary> Reasoning Details</summary>' $reason "</details>\n" $review] | str join "\n"
if ($review | is-empty) {
print $' Code review failedNo review result returned from DeepSeek API.'
print $'✖️ Code review failedNo review result returned from ($base_url) ...'
exit $ECODE.SERVER_ERROR
}
if not $is_action {
print $'Code Review Result:'; hr-line; print $review
} 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 }
print $'✅ Code review finishedPR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
let result = if ($reason | is-empty) { $review } else { $result }
match $output_mode {
'action' => {
post-comments-to-pr $repo $pr_number $result
print $'✅ Code review finishedPR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
}
'file' => { write-review-to-file $output $setting $result $response }
_ => { print $'Code Review Result:'; hr-line; print $result }
}
if ($response.usage? | is-not-empty) {
print $'(char nl)Token Usage:'; hr-line
$response.usage? | table -e | print
}
print $'(char nl)Token Usage Info:'; hr-line
$response.usage | table -e | print
}
# Load the prompt content from the specified env var
export def load-prompt-from-env [
prompt_key: string,
# Write the code review result to a file
def write-review-to-file [
file: string, # Output file path
setting: record, # Review settings
result: string, # Review result
response: record, # DeepSeek API response
] {
let prompt = $env | get -i $prompt_key | default ''
if ($prompt !~ '.ya?ml') { return $prompt }
let parts = $prompt | split row :
if ($parts | length) != 2 {
print $'(ansi r)Invalid prompt format: expected path:key for YAML files.(ansi reset)'
let file = (if not ($file | str ends-with '.md') { $'($file).md' } else { $file })
let token_usage = if ($response.usage? | is-empty) { [] } else {
['## Token Usage', '', ($response.usage? | transpose key val | to md --pretty)]
}
# Generate content sections
let content_sections = [
'# DeepSeek Code Review Result', ''
$"Generated at: (date now | format date '%Y/%m/%d %H:%M:%S')", ''
'## Code Review Settings', ''
($setting | compact-record | reject -o repo | transpose key val | to md --pretty)
'', '## Review Detail', '', $result, '', ...$token_usage
]
try {
$content_sections | str join (char nl) | save --force $file
print $'Code Review Result saved to (ansi g)($file)(ansi reset)'
} catch {|err|
print $'(ansi r)Failed to save review result: (ansi reset)'
$err | table -e | print
}
}
# Validate the DeepSeek API token
def validate-token [token?: string, --pr-number: string, --repo: string] {
if ($token | is-empty) {
print $'(ansi r)Please provide your DeepSeek API token by setting `CHAT_TOKEN` or passing it as an argument.(ansi reset)'
if ($pr_number | is-not-empty) { post-comments-to-pr $repo $pr_number $NO_TOKEN_TIP }
exit $ECODE.INVALID_PARAMETER
}
let key = $parts | last
let path = $parts | first
try { open $path | get -i $key } catch {
print $'(ansi r)Failed to load the prompt content from ($path), please check it again.(ansi reset)'
$token
}
# Validate the temperature value
def validate-temperature [temp: float] {
if ($temp < 0) or ($temp > 2) {
print $'(ansi r)Invalid temperature value, should be in the range of 0 to 2.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
$temp
}
# Get the diff content from GitHub PR or local git changes
export def get-diff [
--repo: string, # GitHub repository name
--pr-number: string, # GitHub PR number
--diff-to: string, # Diff to git ref
--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
# Post review comments to GitHub PR
def post-comments-to-pr [
repo: string, # GitHub repository name, e.g. hustcer/deepseek-review
pr_number: string, # GitHub PR number
comments: string, # Comments content to post
] {
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]
let local_repo = $env.DEFAULT_LOCAL_REPO? | default (pwd)
if not ($local_repo | path exists) {
print $'(ansi r)The directory ($local_repo) does not exist.(ansi reset)'
exit $ECODE.CONDITION_NOT_SATISFIED
let comment_url = $'($GITHUB_API_BASE)/repos/($repo)/issues/($pr_number)/comments'
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json ...$HTTP_HEADERS]
try {
http post -t application/json -H $BASE_HEADER $comment_url { body: $comments }
} catch {|err|
print $'(ansi r)Failed to post comments to PR: (ansi reset)'
$err | table -e | print
exit $ECODE.SERVER_ERROR
}
cd $local_repo
mut content = if ($pr_number | is-not-empty) {
if ($repo | is-empty) {
print $'(ansi r)Please provide the GitHub repository name by `--repo` option.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
# TODO: Ignore keywords checking when triggering by mentioning the bot
let description = http get -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)'
| select title body | values | str join "\n"
if ($IGNORE_REVIEW_KEYWORDS | any {|it| $description =~ $it }) {
print $'(ansi r)The PR title or body contains keywords to skip the review, bye...(ansi reset)'
exit $ECODE.SUCCESS
}
http get -H $DIFF_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)' | str trim
} else if ($diff_from | is-not-empty) {
if not (has-ref $diff_from) {
print $'(ansi r)The specified git ref ($diff_from) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
if ($diff_to | is-not-empty) and not (has-ref $diff_to) {
print $'(ansi r)The specified git ref ($diff_to) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
git diff $diff_from ($diff_to | default HEAD)
} 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 { git diff }
if ($content | is-empty) {
print $'(ansi g)Nothing to review.(ansi reset)'; exit $ECODE.SUCCESS
}
let awk_bin = (prepare-awk)
let outdated_awk = $'You may using an (ansi r)outdated awk version(ansi reset), please upgrade to the latest version or use gawk latest instead.'
if ($include | is-not-empty) {
let patterns = $include | split row ','
$content = $content | try { ^$awk_bin (generate-include-regex $patterns) } catch { print $outdated_awk; exit $ECODE.OUTDATED }
}
if ($exclude | is-not-empty) {
let patterns = $exclude | split row ','
$content = $content | try { ^$awk_bin (generate-exclude-regex $patterns) } catch { print $outdated_awk; exit $ECODE.OUTDATED }
}
$content
}
# Prepare gawk for macOS
export def prepare-awk [] {
const MIN_GAWK_VERSION = '5.3.1'
const MIN_AWK_VERSION = '20250116'
let awk_installed = is-installed awk
let gawk_installed = is-installed gawk
if $awk_installed {
let awk_version = awk --version | lines | first | split row ' ' | last
print $'Current awk version: ($awk_version)'
if (compare-ver $awk_version $MIN_AWK_VERSION) >= 0 { return 'awk' }
}
if $gawk_installed {
let gawk_version = gawk --version | lines | first | split row , | first | split row ' ' | last
print $'Current gawk version: ($gawk_version)'
if (compare-ver $gawk_version $MIN_GAWK_VERSION) >= 0 { return 'gawk' }
}
if (sys host | get name) == 'Darwin' and (is-installed brew) {
brew install gawk
print $'Current gawk version: (gawk --version | lines | first)'
return 'gawk'
}
if (not $awk_installed) and (not $gawk_installed) {
print $'(ansi r)Neither `awk` nor `gawk` is installed, please install the latest version of `gawk`.(ansi reset)'
exit $ECODE.MISSING_BINARY
}
'awk'
}
# Compact the record by removing empty columns
export def compact-record []: record -> record {
let record = $in
let empties = $record | columns | filter {|it| $record | get $it | is-empty }
$record | reject ...$empties
}
# Check if some command available in current shell
export def is-installed [ app: string ] {
(which $app | length) > 0
}
# Check if git was installed and if current directory is a git repo
export def git-check [
dest: string, # The dest dir to check
--check-repo: int, # Check if current directory is a git repo
# 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
] {
cd $dest
if not (is-installed git) {
print $'You should (ansi r)INSTALL git(ansi reset) first to run this command, bye...'
exit $ECODE.MISSING_BINARY
}
# If we don't need repo check just quit now
if ($check_repo != 0) {
if not (is-repo) {
print $'Current directory is (ansi r)NOT(ansi reset) a git repo, bye...(char nl)'
exit $ECODE.CONDITION_NOT_SATISFIED
}
}
true
}
# Check if current directory is a git repo
export def is-repo [] {
let checkRepo = try {
do -i { git rev-parse --is-inside-work-tree } | complete
} catch {
({ stdout: 'false' })
}
if ($checkRepo.stdout =~ 'true') { true } else { false }
}
# Check if a git repo has the specified ref: could be a branch or tag, etc.
export def has-ref [
ref: string # The git ref to check
] {
if not (is-repo) { return false }
# Brackets were required here, or error will occur
let parse = (do -i { git rev-parse --verify -q $ref } | complete)
if ($parse.stdout | is-empty) { false } else { true }
}
export def hr-line [
width?: int = 90,
--blank-line(-b),
--with-arrow(-a),
--color(-c): string = 'g',
] {
# Create a line by repeating the unit with specified times
def build-line [
times: int,
unit: string = '-',
] {
0..<$times | reduce -f '' { |i, acc| $unit + $acc }
}
print $'(ansi $color)(build-line $width)(if $with_arrow {'>'})(ansi reset)'
if $blank_line { char nl }
}
# Generate the awk include regex pattern string for the specified patterns
export def generate-include-regex [patterns: list<string>] {
let pattern = $patterns | each {|pat| $pat | str replace '/' '\/' } | str join '|'
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}p"
}
# Generate the awk exclude regex pattern string for the specified patterns
def generate-exclude-regex [patterns: list<string>] {
let pattern = $patterns | each {|pat| $pat | str replace '/' '\/' } | str join '|'
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}!p"
}
# Converts a .env file into a record
# may be used like this: open .env | load-env
# works with quoted and unquoted .env files
export def 'from env' []: string -> record {
lines
| split column '#' # remove comments
| get column1
| parse '{key}={value}'
| update value {
str trim # Trim whitespace between value and inline comments
| str trim -c '"' # unquote double-quoted values
| str trim -c "'" # unquote single-quoted values
| str replace -a "\\n" "\n" # replace `\n` with newline char
| str replace -a "\\r" "\r" # replace `\r` with carriage return
| str replace -a "\\t" "\t" # replace `\t` with tab
print -n (char nl)
kv set content 0
kv set reasoning 0
http post -e -H $headers -t application/json $url $payload
| tee {
let res = $in
let type = $res | describe
let record_error = $type =~ '^record'
let other_error = $type =~ '^string' and $res !~ 'data: ' and $res !~ 'done'
if $record_error or $other_error {
$res | table -e | print
exit $ECODE.SERVER_ERROR
}
}
| transpose -r -d
| try { lines } catch { print $'(ansi r)Error Happened ...(ansi reset)'; exit $ECODE.SERVER_ERROR }
| each {|line|
if ($line | is-empty) { return }
if ($IGNORED_MESSAGES | get -o $line | default false) { return }
let $last = $line | parse-line
if $debug { $last | to json | kv set last-reply }
$last | get -o choices.0.delta | default ($last | get -o message) | if ($in | is-not-empty) {
let delta = $in
if ($delta | coalesce-reasoning | is-not-empty) { kv set reasoning ((kv get reasoning) + 1) }
if (kv get reasoning) == 1 { print $'(char nl)Reasoning Details:'; hr-line }
if ($delta.content | is-not-empty) { kv set content ((kv get content) + 1) }
if (kv get content) == 1 { print $'(char nl)Review Details:'; hr-line }
print -n ($delta | coalesce-reasoning | default $delta.content)
}
}
if $debug and (kv get last-reply | is-not-empty) {
print $'(char nl)(char nl)Model & Token Usage:'; hr-line
kv get last-reply | from json | select -o model usage | table -e | print
}
}
# Compare two version number, return `1` if first one is higher than second one,
# Return `0` if they are equal, otherwise return `-1`
# Format: Expects semantic version strings (major.minor.patch)
# - Optional 'v' prefix
# - Pre-release suffixes (-beta, -rc, etc.) are ignored
# - Missing segments default to 0
export def compare-ver [v1: string, v2: string] {
# Parse the version number: remove pre-release and build information,
# only take the main version part, and convert it to a list of numbers
def parse-ver [v: string] {
$v | str downcase | str trim -c v | str trim
| split row - | first | split row . | each { into int }
# Parse the line from the streaming response
def parse-line [] {
let $line = $in
# DeepSeek Response vs Local Ollama Response
try {
if $line =~ '^data: ' {
$line | str substring 6.. | from json
} else {
$line | from json
}
} catch {
print -e $'(ansi r)Unrecognized content:(ansi reset) ($line)'
exit $ECODE.SERVER_ERROR
}
let a = parse-ver $v1
let b = parse-ver $v2
# Compare the major, minor, and patch parts; fill in the missing parts with 0
# If you want to compare more parts use the following code:
# for i in 0..([2 ($a | length) ($b | length)] | math max)
for i in 0..2 {
let x = $a | get -i $i | default 0
let y = $b | get -i $i | default 0
if $x > $y { return 1 }
if $x < $y { return (-1) }
}
0
}
# Coalesce the reasoning content
def coalesce-reasoning [] {
let msg = $in
$msg.reasoning_content? | default $msg.reasoning?
}
alias main = deepseek-review

138
nu/util.nu Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env nu
# Author: hustcer
# Created: 2025/04/02 20:02:15
#
use common.nu [ECODE, is-installed, compare-ver, windows?, mac?]
# AWK family version check for both awk and gawk
# awk: awk version 20250116 -> 20250116
# gawk: GNU Awk 5.3.1, API 4.0, (GNU MPFR 4.2.1, GNU MP 6.3.0) -> 5.3.1
def get-awk-ver [awk_bin: string] {
^$awk_bin --version | lines | first | split row , | first | split row ' ' | last
}
# Prepare gawk for macOS
export def prepare-awk [] {
const MIN_GAWK_VERSION = '5.3.1'
const MIN_AWK_VERSION = '20250116'
let awk_installed = is-installed awk
let gawk_installed = is-installed gawk
if $awk_installed {
let awk_version = get-awk-ver awk
if (compare-ver $awk_version $MIN_AWK_VERSION) >= 0 {
print $'Current awk version: ($awk_version)'
return 'awk'
}
}
if $gawk_installed {
let gawk_version = get-awk-ver gawk
if (compare-ver $gawk_version $MIN_GAWK_VERSION) >= 0 {
print $'Current gawk version: ($gawk_version)'
return 'gawk'
} else if (windows?) and ($env.GITHUB_ACTIONS? == 'true') {
let awk_info = (install-gawk-for-actions)
print $'Current gawk version: ($awk_info.version)'
return $awk_info.awk_bin
}
}
if (mac?) and (is-installed brew) {
brew install gawk
print $'Current gawk version: (get-awk-ver gawk)'
return 'gawk'
}
if (not $awk_installed) and (not $gawk_installed) {
print $'(ansi r)Neither `awk` nor `gawk` is installed, please install the latest version of `gawk`.(ansi reset)'
exit $ECODE.MISSING_BINARY
}
print $'Current awk version: (get-awk-ver awk)'
'awk'
}
# Convert glob patterns to regex patterns
# Pass in *.nu directly as a regular expression does not work, because * in
# a regular expression needs to be attached to the previous pattern, the correct
# form should be .* So we should convert each glob pattern to a regex pattern:
# 1. Convert * to .*
# 2. Convert ? to . (optional, as needed)
# 3. Convert / to \/
def glob-to-regex [patterns: list<string>] {
# Handle empty patterns list
if ($patterns | length) == 0 { return '' }
# Define a mapping of characters to escape
let regex_escapes = {
# Escape special regex characters first
"\\.": "\\\\.",
"\\+": "\\\\+",
"\\^": "\\\\^",
"\\$": "\\\\$",
"\\(": "\\\\(",
"\\)": "\\\\)",
"\\[": "\\\\[",
"\\]": "\\\\]",
"\\{": "\\\\{",
"\\}": "\\\\}",
"\\|": "\\\\|",
# Then convert glob patterns to regex patterns
"*": ".*",
"?": ".",
"/": "\\/",
}
$patterns
| each { |pat|
$regex_escapes | columns | reduce -f $pat { |k, acc|
$acc | str replace -a $k ($regex_escapes | get $k)
}
}
| str join '|'
}
# Generate the awk include regex pattern string for the specified patterns
export def generate-include-regex [patterns: list<string>] {
let pattern = glob-to-regex $patterns
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}p"
}
# Generate the awk exclude regex pattern string for the specified patterns
export def generate-exclude-regex [patterns: list<string>] {
let pattern = glob-to-regex $patterns
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}!p"
}
# 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 2393375 71f5a31
# - git diff 2393375 71f5a31 nu/*
# - git diff 2393375 71f5a31 :!nu/*
export def is-safe-git [cmd: string] {
let normalized_cmd = ($cmd | str trim | str downcase)
# Define allowed command patterns with named capture groups for better validation
let git_cmd_pattern = '^git\s+(show|diff)(?:\s+(?:[a-zA-Z0-9_\-\.~/]+(?::[a-zA-Z0-9_\-\.\*\/]+)?)){0,3}(?:\s+(?::[!]?)?[a-zA-Z0-9_\-\.\*\/]+){0,2}$'
if ($normalized_cmd | find -r $git_cmd_pattern | is-empty) {
print $'(ansi r)Invalid git command format. (ansi g)Only simple `git show` or `git diff` commands are allowed.(ansi reset)'
return false
}
true
}
# Setup scoop and install gawk for GitHub Windows runners
# This command is essential for resolving the issue of simultaneously
# applying include and exclude patterns on GitHub's Windows runners.
def install-gawk-for-actions [] {
# Install scoop using PowerShell
pwsh -c r#'
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("https://get.scoop.sh")
$env:Path = "$env:USERPROFILE\scoop\shims;" + $env:Path; scoop update; scoop install gawk
'# | complete | get stdout | print
let awk_bin = $'($nu.home-dir)/scoop/shims/gawk.exe'
let version = get-awk-ver $awk_bin
{ awk_bin: $awk_bin, version: $version }
}

13
tests/resources/.env.test Normal file
View File

@@ -0,0 +1,13 @@
# Description: Environment variables for Local Code Review Only
# Usage: Copy this file to .env and replace the values with your own
CHAT_MODEL="deepseek-chat" # Official DeepSeek model
# BASE_URL: Deepseek API base URL
BASE_URL='https://api.deepseek.ai' # DeepSeek Official
# The maximum temperature for the model to generate the response. 1.0 by default.
TEMPERATURE=1.0
# MAX_LENGTH: The maximum length of the content for review, 0 means no limit.
MAX_LENGTH=0
# USER_PROMPT: User prompt message
USER_PROMPT='Please review the following code changes'

153
tests/resources/diff.patch Normal file
View File

@@ -0,0 +1,153 @@
commit 22e7b71776edf9ee580345368fc4c8269bb05cab
Author: Justin Ma <hustcer@outlook.com>
Date: Mon Feb 10 16:41:22 2025 +0800
feat: Add support for configurable temperature parameter in DeepSeek model setup (#93)
* feat: Add support for configurable temperature parameter in DeepSeek model setup
* chore: Add temperature validation
diff --git a/.env.example b/.env.example
index 8966133..710f532 100644
--- a/.env.example
+++ b/.env.example
@@ -11,6 +11,8 @@ CHAT_TOKEN='Your DeepSeek API token'
GITHUB_TOKEN='Your GitHub API token'
# MAX_LENGTH: The maximum length of the content for review, 0 means no limit.
MAX_LENGTH='0'
+# The maximum temperature for the model to generate the response. 1.0 by default.
+TEMPERATURE='1.0'
# The comma separated file patterns to include in the code review.
INCLUDE_PATTERNS=''
# The comma separated file patterns to exclude in the code review.
diff --git a/README.md b/README.md
index 682f96b..79b5f1b 100644
--- a/README.md
+++ b/README.md
@@ -102,11 +102,12 @@ With this setup, DeepSeek code review will not run automatically upon PR creatio
| Name | Type | Description |
| -------------- | ------ | ----------------------------------------------------------------------- |
| chat-token | String | Required, DeepSeek API Token |
-| model | String | Optional, the model used for code review, defaults to `deepseek-chat` |
+| model | String | Optional, The model used for code review, defaults to `deepseek-chat` |
| base-url | String | Optional, DeepSeek API Base URL, defaults to `https://api.deepseek.com` |
| max-length | Int | Optional, Maximum length(Unicode width) of the content for review, if the content length exceeds this value, the review will be skipped. Default `0` means no limit. |
-| sys-prompt | String | Optional, system prompt corresponding to `$sys_prompt` in the payload, default value see note below |
-| user-prompt | String | Optional, user prompt corresponding to `$user_prompt` in the payload, default value see note below |
+| sys-prompt | String | Optional, System prompt corresponding to `$sys_prompt` in the payload, default value see note below |
+| user-prompt | String | Optional, User prompt corresponding to `$user_prompt` in the payload, default value see note below |
+| temperature | Number | Optional, The temperature for the model to generate the response, between `0` and `2`, default value `1.0` |
| include-patterns | String | Optional, The comma separated file patterns to include in the code review. No default |
| exclude-patterns | String | Optional, The comma separated file patterns to exclude in the code review. Default to `pnpm-lock.yaml,package-lock.json,*.lock` |
| github-token | String | Optional, The `GITHUB_TOKEN` secret or personal access token to authenticate. Defaults to `github.token`. |
@@ -118,6 +119,7 @@ With this setup, DeepSeek code review will not run automatically upon PR creatio
// `$model` default value: deepseek-chat
model: $model,
stream: false,
+ temperature: $temperature,
messages: [
// `$sys_prompt` default value: You are a professional code review assistant responsible for
// analyzing code changes in GitHub Pull Requests. Identify potential issues such as code
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 375a2ed..b22be07 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -105,6 +105,7 @@ jobs:
| max-length | Int | 可选,待审查内容的最大 Unicode 长度, 默认 `0` 表示没有限制,超过非零值则跳过审查 |
| sys-prompt | String | 可选,系统提示词对应入参中的 `$sys_prompt`, 默认值见后文注释 |
| user-prompt | String | 可选,用户提示词对应入参中的 `$user_prompt`, 默认值见后文注释 |
+| temperature | Number | 可选,采样温度,介于 `0` 和 `2` 之间, 默认值 `1.0` |
| include-patterns | String | 可选,代码审查中要包含的以逗号分隔的文件模式,无默认值 |
| exclude-patterns | String | 可选,代码审查中要排除的以逗号分隔的文件模式,默认值为 `pnpm-lock.yaml,package-lock.json,*.lock` |
| github-token | String | 可选,用于访问 API 进行 PR 管理的 GitHub Token默认为 `${{ github.token }}` |
@@ -116,6 +117,7 @@ DeepSeek 接口调用入参:
// `$model` default value: deepseek-chat
model: $model,
stream: false,
+ temperature: $temperature,
messages: [
// `$sys_prompt` default value: You are a professional code review assistant responsible for
// analyzing code changes in GitHub Pull Requests. Identify potential issues such as code
diff --git a/action.yaml b/action.yaml
index 194424a..e258516 100644
--- a/action.yaml
+++ b/action.yaml
@@ -26,6 +26,10 @@ inputs:
required: false
default: 'deepseek-chat'
description: 'The DeepSeek model to choose for code review.'
+ temperature:
+ required: false
+ default: 1.0
+ description: 'The temperature of the model.'
base-url:
required: false
default: 'https://api.deepseek.com'
@@ -74,6 +78,7 @@ runs:
let includePatterns = '${{ inputs.include-patterns }}'
let excludePatterns = '${{ inputs.exclude-patterns }}'
let maxLength = try { '${{ inputs.max-length }}' | into int } catch { 0 }
+ let temperature = try { '${{ inputs.temperature }}' | into float } catch { 1.0 }
(deepseek-review $token
--model $model
--repo $repo
@@ -83,6 +88,7 @@ runs:
--max-length $maxLength
--sys-prompt $sysPrompt
--user-prompt $userPrompt
+ --temperature $temperature
--include $includePatterns
--exclude $excludePatterns
)
diff --git a/nu/review.nu b/nu/review.nu
index 1adcd9f..4c5f4a2 100644
--- a/nu/review.nu
+++ b/nu/review.nu
@@ -43,6 +43,7 @@ const HTTP_HEADERS = [User-Agent curl/8.9]
const DEFAULT_OPTIONS = {
MODEL: 'deepseek-chat',
+ TEMPERATURE: 1.0,
BASE_URL: 'https://api.deepseek.com',
USER_PROMPT: 'Please review the following code changes:',
SYS_PROMPT: 'You are a professional code review assistant responsible for analyzing code changes in GitHub Pull Requests. Identify potential issues such as code style violations, logical errors, security vulnerabilities, and provide improvement suggestions. Clearly list the problems and recommendations in a concise manner.',
@@ -67,6 +68,7 @@ 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
]: nothing -> nothing {
$env.config.table.mode = 'psql'
@@ -76,8 +78,13 @@ export def --env deepseek-review [
let CHAT_HEADER = [Authorization $'Bearer ($token)']
let local_repo = $env.DEFAULT_LOCAL_REPO? | default (pwd)
let model = $model | default $env.CHAT_MODEL? | default $DEFAULT_OPTIONS.MODEL
- let max_length = $max_length | default ($env.MAX_LENGTH? | default 0 | into int)
let base_url = $base_url | default $env.BASE_URL? | default $DEFAULT_OPTIONS.BASE_URL
+ let max_length = try { $max_length | default ($env.MAX_LENGTH? | default 0 | into int) } catch { 0 }
+ let temperature = try { $temperature | default $env.TEMPERATURE? | default $DEFAULT_OPTIONS.TEMPERATURE | into float } catch { $DEFAULT_OPTIONS.TEMPERATURE }
+ if ($temperature < 0) or ($temperature > 2) {
+ print $'(ansi r)Invalid temperature value, should be in the range of 0 to 2.(ansi reset)'
+ exit $ECODE.INVALID_PARAMETER
+ }
let url = $'($base_url)/chat/completions'
let setting = {
repo: $repo,
@@ -90,6 +97,7 @@ export def --env deepseek-review [
pr_number: $pr_number,
max_length: $max_length,
local_repo: $local_repo,
+ temperature: $temperature,
}
$env.GH_TOKEN = $gh_token | default $env.GITHUB_TOKEN?
@@ -119,6 +127,7 @@ export def --env deepseek-review [
let payload = {
model: $model,
stream: false,
+ temperature: $temperature,
messages: [
{ role: 'system', content: $sys_prompt },
{ role: 'user', content: $"($user_prompt):\n($content)" }

101
tests/test-common.nu Normal file
View File

@@ -0,0 +1,101 @@
use std/assert
use std/testing *
use ../nu/common.nu [
compare-ver, 'from env', is-installed, has-ref,
git-check, compact-record, is-repo, windows?, mac?,
]
@test
def 'compare-verv1.0.0 is greater than v0.999.0' [] {
assert equal (compare-ver 1.0.0 0.999.0) 1
assert equal (compare-ver v1.0.0 v0.999.0) 1
}
@test
def 'compare-verv1.0.1 is equal to v1.0.1' [] {
assert equal (compare-ver 1.0.1 1.0.1) 0
}
@test
def 'compare-verv1.0.0 is equal to v1' [] {
assert equal (compare-ver v1.0.0 v1) 0
}
@test
def 'compare-verv1.0.1 is greater than v1' [] {
assert equal (compare-ver v1.0.1 v1) 1
}
@test
def 'compare-verv1.0.1 is lower than v1.1.0' [] {
assert less (compare-ver 1.0.1 v1.1) 0
assert equal (compare-ver 1.0.1 1.1.0) (-1)
}
@test
def 'from-env.env load should work' [] {
open tests/resources/.env.test | from env | load-env
assert equal $env.CHAT_MODEL deepseek-chat
assert equal $env.BASE_URL https://api.deepseek.ai
assert equal $env.TEMPERATURE '1.0'
assert equal $env.MAX_LENGTH '0'
assert equal $env.USER_PROMPT 'Please review the following code changes'
}
@test
def 'is-installedbinary install check should work' [] {
assert equal (is-installed git) true
assert equal (is-installed abc) false
}
@test
def 'has-refgit repo should has HEAD ref' [] {
assert equal (has-ref HEAD) true
assert equal (has-ref 0000) false
}
@test
def 'is-repocurrent dir is a git repo' [] {
assert equal (is-repo) true
}
@test
def 'git-checkcurrent dir is a git repo' [] {
assert equal (git-check (pwd) --check-repo=1) true
}
@test
def 'compact-recordshould work as expected' [] {
assert equal ({a: null, b: '', c: 'abc' } | compact-record) { c: 'abc' }
assert equal ({a: null, b: 0, c: 1, e: { f: 'g' } } | compact-record) { b: 0, c: 1, e: { f: 'g' } }
}
@test
def 'OS check should work as expected' [] {
# `$env.RUNNER_OS` Possible values are Linux, Windows, or macOS in GitHub Actions
match $nu.os-info.name {
'windows' => {
assert equal (mac?) false
assert equal (windows?) true
if ($env.RUNNER_OS? | is-not-empty) {
assert equal $env.RUNNER_OS Windows
}
}
'macos' => {
assert equal (mac?) true
assert equal (windows?) false
if ($env.RUNNER_OS? | is-not-empty) {
assert equal $env.RUNNER_OS macOS
}
}
_ => {
assert equal (mac?) false
assert equal (windows?) false
if ($env.RUNNER_OS? | is-not-empty) {
assert equal $env.RUNNER_OS Linux
}
}
}
}

122
tests/test-review.nu Normal file
View File

@@ -0,0 +1,122 @@
use std/assert
use std/testing *
use ../nu/diff.nu [get-diff]
use ../nu/util.nu [is-safe-git, prepare-awk, generate-include-regex, generate-exclude-regex]
# Get the unicode width of the input string
def get-uw [] { $in | str stats | get unicode-width }
@before-all
def setup [] {
let awk_bin = (prepare-awk)
let patch = open -r tests/resources/diff.patch
print 'Mock patch creation from commit: 22e7b71'
{ patch: $patch, awk: $awk_bin, SHA: 22e7b71 }
}
@test
def 'is-safe-gitshould work as expected' [] {
assert equal (is-safe-git 'git diff') true
assert equal (is-safe-git 'git show') true
assert equal (is-safe-git 'git log') false
assert equal (is-safe-git 'git checkout') false
assert equal (is-safe-git 'git show 0dd0eb5') true
assert equal (is-safe-git 'git show HEAD') true
assert equal (is-safe-git 'git show head~1') true
assert equal (is-safe-git 'git diff HEAD~2') true
assert equal (is-safe-git 'git diff head~3 main') true
assert equal (is-safe-git 'git diff f536acc 0dd0eb5') true
assert equal (is-safe-git 'git show 2393375 | less') false
assert equal (is-safe-git 'git show 2393375>diff.patch') false
assert equal (is-safe-git 'git show 2393375 o+e>diff.patch') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 nu/*') true
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*') true
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*; rm -rf abc') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* && rm -rf abc') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* || rm -rf abc') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*; rm ./*') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*; rm -f ./*') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*; rm -rf ./*') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* > out.txt') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* >> out.txt') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* < in.txt') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* << in.txt') false
assert equal (is-safe-git 'git show head:nu/common.nu') true
assert equal (is-safe-git 'git show HEAD:nu/common.nu') true
}
@test
def 'generate-include-regexshould work as expected' [] {
let patch = $in.patch
let awk_bin = $in.awk
assert equal ($patch | ^$awk_bin (generate-include-regex [*]) | get-uw) (7959 + 5)
assert equal ($patch | ^$awk_bin (generate-include-regex [nu/*]) | get-uw) 2576
assert equal ($patch | ^$awk_bin (generate-include-regex [nu/*, **/*.yaml]) | get-uw) 3669
assert equal ($patch | ^$awk_bin (generate-include-regex [.env*, *.md, nu/*]) | get-uw) 6871
}
@test
def 'generate-exclude-regexshould work as expected' [] {
let patch = $in.patch
let awk_bin = $in.awk
assert equal ($patch | ^$awk_bin (generate-exclude-regex [*]) | get-uw) 356
assert equal ($patch | ^$awk_bin (generate-exclude-regex [.env*, *.md, nu/*]) | get-uw) (1350 + 99)
}
@test
def 'both include and exclude should work as expected' [] {
let patch = $in.patch
let awk_bin = $in.awk
assert equal ($patch
| ^$awk_bin (generate-include-regex [nu/*, **/*.yaml])
| ^$awk_bin (generate-exclude-regex [**/*.yaml])
| get-uw) 2576
}
@test
def 'both exclude and include should work as expected' [] {
let patch = $in.patch
let awk_bin = $in.awk
assert equal ($patch
| ^$awk_bin (generate-exclude-regex [**/*.yaml])
| ^$awk_bin (generate-include-regex [nu/*])
| get-uw) 2576
}
@test
def 'get-diffget patch from remote PR should work' [] {
$env.GH_TOKEN = $env.GITHUB_TOKEN?
const repo = 'hustcer/deepseek-review'
if ($env.GH_TOKEN | is-empty) { print '$env.GH_TOKEN is empty'; return }
let patch = get-diff --pr-number 93 --repo $repo
assert equal ($patch | lines | skip 1
| str join "\n" | get-uw) 7923
}
@test
def 'get-diffget patch from remote PR with include should work' [] {
$env.GH_TOKEN = $env.GITHUB_TOKEN?
const repo = 'hustcer/deepseek-review'
if ($env.GH_TOKEN | is-empty) { print '$env.GH_TOKEN is empty'; return }
let patch = get-diff --pr-number 93 --repo $repo --include nu/*
assert equal ($patch | get-uw) 2576
}
@test
def 'get-diffget patch from remote PR with exclude should work' [] {
$env.GH_TOKEN = $env.GITHUB_TOKEN?
const repo = 'hustcer/deepseek-review'
if ($env.GH_TOKEN | is-empty) { print '$env.GH_TOKEN is empty'; return }
let patch = get-diff --pr-number 93 --repo $repo --exclude **/*.yaml,**/*.nu,*.md
assert equal ($patch | get-uw) 555
}
@test
def 'get-diffget patch from remote PR with exclude & include should work' [] {
$env.GH_TOKEN = $env.GITHUB_TOKEN?
const repo = 'hustcer/deepseek-review'
if ($env.GH_TOKEN | is-empty) { print '$env.GH_TOKEN is empty'; return }
let patch = get-diff --pr-number 93 --repo $repo --exclude **/*.yaml,*.md --include **/*.nu
assert equal ($patch | get-uw) 2576
}