mirror of
https://github.com/hustcer/deepseek-review.git
synced 2026-05-13 05:16:05 +08:00
Compare commits
98 Commits
v1.0
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adf7e63560 | ||
|
|
55958cd4a9 | ||
|
|
88ea2aed5e | ||
|
|
683ed811c6 | ||
|
|
c3ec0285c3 | ||
|
|
b0ae06217d | ||
|
|
9551fc2dec | ||
|
|
9272a75907 | ||
|
|
55a5d76958 | ||
|
|
1260c388b9 | ||
|
|
0088fe059a | ||
|
|
3ff7a32e6d | ||
|
|
0088530d17 | ||
|
|
93a5d817e7 | ||
|
|
17ab7ae3fa | ||
|
|
2b2cb85fd8 | ||
|
|
00806171f7 | ||
|
|
f3ea289921 | ||
|
|
4af2f6036b | ||
|
|
3dd6b23201 | ||
|
|
c2b26fdc30 | ||
|
|
06d80785b1 | ||
|
|
84dbd263c9 | ||
|
|
c0d78b92b9 | ||
|
|
5652f31856 | ||
|
|
68e582fb95 | ||
|
|
534d6230b7 | ||
|
|
6af37c1d98 | ||
|
|
9b1a390493 | ||
|
|
4249b38d5b | ||
|
|
12e1537975 | ||
|
|
fd3a97c3b6 | ||
|
|
d84f126292 | ||
|
|
8dde3bd95f | ||
|
|
2288595e98 | ||
|
|
d064f64443 | ||
|
|
c3d18ccb03 | ||
|
|
2393375400 | ||
|
|
22e7b71776 | ||
|
|
798baee3d8 | ||
|
|
b19e2097cc | ||
|
|
699ebd30f0 | ||
|
|
a12b9c6fdd | ||
|
|
44b44de9b1 | ||
|
|
c17a3836e8 | ||
|
|
5c6cbc3c92 | ||
|
|
88206a92f0 | ||
|
|
2ab221a624 | ||
|
|
cc5bb6bb4c | ||
|
|
f0ec9a5789 | ||
|
|
9fce8f6d22 | ||
|
|
bb17f50cc2 | ||
|
|
27722e9e3a | ||
|
|
865b132815 | ||
|
|
03903b7a36 | ||
|
|
00000006fb | ||
|
|
1c83969b7b | ||
|
|
609f212d2b | ||
|
|
0000000089 | ||
|
|
f9f66ccc4c | ||
|
|
eba892d969 | ||
|
|
91bfd9c2d1 | ||
|
|
f781a7d0a9 | ||
|
|
cdba3ce088 | ||
|
|
f7ced8080f | ||
|
|
d9a84f47da | ||
|
|
7ccfa23bd5 | ||
|
|
3cb42fca6a | ||
|
|
e60fc915f8 | ||
|
|
522a0174e3 | ||
|
|
00000001ec | ||
|
|
025694ce43 | ||
|
|
f847c1a154 | ||
|
|
7b21216ef9 | ||
|
|
c984584c3a | ||
|
|
e9361125bc | ||
|
|
000000086e | ||
|
|
5225cc66ea | ||
|
|
e94ff03208 | ||
|
|
a7fd374fc1 | ||
|
|
a7699eee9c | ||
|
|
000000090a | ||
|
|
052182c5de | ||
|
|
e8e5bc99ff | ||
|
|
272fb2244e | ||
|
|
6dc9fc6f1d | ||
|
|
35a8ded25b | ||
|
|
00000006e0 | ||
|
|
88e0011fbf | ||
|
|
18450d75e8 | ||
|
|
8e67d7be7e | ||
|
|
28505c4767 | ||
|
|
4c4defaaca | ||
|
|
f536accea4 | ||
|
|
b527650ce1 | ||
|
|
40b98c9c16 | ||
|
|
8c99ac926e | ||
|
|
f962bbb88d |
25
.github/workflows/basic.yml
vendored
25
.github/workflows/basic.yml
vendored
@@ -1,25 +0,0 @@
|
||||
# Description:
|
||||
# - Deepseek code review with GitHub Actions
|
||||
# REF:
|
||||
# - https://github.com/marketplace/actions/checkout
|
||||
|
||||
name: Code Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
# fix: GraphQL: Resource not accessible by integration (addComment) error
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
setup-deepseek-review:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
name: Code Review
|
||||
steps:
|
||||
- name: Deepseek Code Review
|
||||
uses: hustcer/deepseek-review@develop
|
||||
with:
|
||||
deepseek-token: ${{ secrets.DEEPSEEK_TOKEN }}
|
||||
105
.github/workflows/cr.yml
vendored
Normal file
105
.github/workflows/cr.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
# Description:
|
||||
# - DeepSeek code review with GitHub Actions
|
||||
|
||||
name: Code Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened # Triggers when a PR is opened
|
||||
- reopened # Triggers when a PR is reopened
|
||||
- synchronize # Triggers when a commit is pushed to the PR
|
||||
# - labeled # Triggers when a label is added to the PR
|
||||
|
||||
# fix: GraphQL: Resource not accessible by integration (addComment) error
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
setup-deepseek-review:
|
||||
runs-on: macos-latest
|
||||
name: Code Review
|
||||
# Make sure the code review happens only when the PR has the label 'ai review'
|
||||
# if: contains(github.event.pull_request.labels.*.name, 'ai review')
|
||||
steps:
|
||||
- name: DeepSeek Code Review
|
||||
uses: hustcer/deepseek-review@develop
|
||||
with:
|
||||
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:
|
||||
|
||||
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}}
|
||||
```
|
||||
98
.github/workflows/tests.yml
vendored
Normal file
98
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# Description: This workflow runs tests for hustcer/deepseek-review.
|
||||
# REF:
|
||||
# - https://github.com/vyadh/nutest/blob/main/.github/workflows/tests.yaml
|
||||
|
||||
name: Run Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
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@v4
|
||||
|
||||
- name: Checkout Nutest Repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: v1.0.1
|
||||
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/review.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
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
||||
.env
|
||||
.env.dev
|
||||
.env.local
|
||||
config.yml
|
||||
prompts.yaml
|
||||
|
||||
195
CHANGELOG.md
195
CHANGELOG.md
@@ -1,13 +1,206 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [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
|
||||
|
||||
- Add check for empty DeepSeek review response with error handling (#90)
|
||||
- Add `awk` binary missing check (#92)
|
||||
|
||||
### Features
|
||||
|
||||
- Add version validation for `awk`/`gawk` and implement robust semantic version comparison for compatibility checks (#91)
|
||||
- Add support for configurable `temperature` parameter in DeepSeek model setup (#93)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Update README add `awk` or `gawk` as required tools
|
||||
|
||||
### Refactor
|
||||
|
||||
- Streamline main wrapper and simplify argument handling for `nu/review.nu` integration (#88)
|
||||
|
||||
## [1.7.0] - 2025-02-08
|
||||
|
||||
### Features
|
||||
|
||||
- Remove the dependency on `just` for local code review (#84)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix possible GitHub comment posting errors
|
||||
|
||||
### Refactor
|
||||
|
||||
- Improve prompts loading helper (#82)
|
||||
|
||||
## [1.6.0] - 2025-02-07
|
||||
|
||||
### Features
|
||||
|
||||
- Read `CHAT_MODEL` and `BASE_URL` from `.env` for local code review (#80)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Use SiliconFlow's DeepSeek model
|
||||
- Remove the dependency on `gh` (#78)
|
||||
|
||||
### Deps
|
||||
|
||||
- Upgrade `Nushell` to v0.102 (#76)
|
||||
|
||||
## [1.5.1] - 2025-02-01
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix `awk` error on `macOS` runner (#71)
|
||||
|
||||
## [1.5.0] - 2025-02-01
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update README (#61)
|
||||
|
||||
### Features
|
||||
|
||||
- Add example of triggering code review by adding `ai review` label (#60)
|
||||
- Load multi-line prompts from yaml config for local code reviewing (#67)
|
||||
- Add `include` and `exclude` for file pattern filtering support (#68)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Update prompts for current repo's workflow (#63)
|
||||
|
||||
### Refactor
|
||||
|
||||
- Extracted git repo check into `is-repo` custom command (#64)
|
||||
|
||||
## [1.3.0] - 2025-01-31
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update CLI help output (#53)
|
||||
- Polish documents (#57)
|
||||
|
||||
### Features
|
||||
|
||||
- Add `github-token` input (#55)
|
||||
- Add `skip cr` or `skip review` to PR title or body to disable code review in GitHub Actions (#56)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Increase `max-length` in review workflow (#54)
|
||||
|
||||
## [1.2.0] - 2025-01-31
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Change `DEEPSEEK_TOKEN` to `CHAT_TOKEN` (#50)
|
||||
|
||||
### Features
|
||||
|
||||
- Add `max-length` input (#52)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Update action name, description and icon (#49)
|
||||
|
||||
## [1.1.0] - 2025-01-30
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Do not override `GITHUB_TOKEN` env var (#30)
|
||||
- Check `gh` installation status in GitHub Action (#31)
|
||||
- Add git repo and git ref checking (#32)
|
||||
- Add repo checking for GitHub PR review (#34)
|
||||
- Polish CLI output for local code review (#44)
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add features description to README (#29)
|
||||
- Add CLI help doc (#36)
|
||||
- Add planed features to doc (#39)
|
||||
- Add local code review guide (#41)
|
||||
|
||||
### Features
|
||||
|
||||
- Add dot env conf for local code review (#33)
|
||||
- Add more CLI short flags (#35)
|
||||
- Add `DEFAULT_GITHUB_REPO` & `DEFAULT_LOCAL_REPO` config for local code review (#42)
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Use `v1` in README docs (#17)
|
||||
|
||||
## [1.0.0] - 2025-01-29
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix gh token error
|
||||
- Fix add comment error
|
||||
- Make action fail if no response returned from deepseek (#3)
|
||||
- Make action fail if no response returned from DeepSeek (#3)
|
||||
|
||||
### Documentation
|
||||
|
||||
|
||||
198
README.md
198
README.md
@@ -1,14 +1,59 @@
|
||||
# Deepseek Code Review
|
||||
<p align="center">
|
||||
<img src="./CR.jpg" width="90" height="60" alt="DeepSeek Code Review Logo" />
|
||||
</p>
|
||||
|
||||
# DeepSeek Code Review
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
[中文说明](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
|
||||
- Add `skip cr` or `skip review` to 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 by CLI
|
||||
- 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` & `R1` Models
|
||||
- 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
|
||||
|
||||
## 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
|
||||
|
||||
Add a GitHub workflow with the following contents:
|
||||
|
||||
```yaml
|
||||
name: Code Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
types:
|
||||
- opened # Triggers when a PR is opened
|
||||
- reopened # Triggers when a PR is reopened
|
||||
- synchronize # Triggers when a commit is pushed to the PR
|
||||
|
||||
# fix: GraphQL: Resource not accessible by integration (addComment) error
|
||||
permissions:
|
||||
@@ -19,29 +64,83 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Code Review
|
||||
steps:
|
||||
- name: Deepseek Code Review
|
||||
uses: hustcer/deepseek-review@develop
|
||||
- name: DeepSeek Code Review
|
||||
uses: hustcer/deepseek-review@v1
|
||||
with:
|
||||
deepseek-token: ${{ secrets.DEEPSEEK_TOKEN }}
|
||||
chat-token: ${{ secrets.CHAT_TOKEN }}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>CHAT_TOKEN Config</summary>
|
||||
|
||||
Follow these steps to config your `CHAT_TOKEN`:
|
||||
|
||||
- Click on the "Settings" tab in your repository navigation bar.
|
||||
- In the left sidebar, click on "Secrets and variables" under "Security".
|
||||
- Click on "Actions" -> "New repository secret" button.
|
||||
- Enter `CHAT_TOKEN` in the "Name" field.
|
||||
- Enter the value of your `CHAT_TOKEN` in the "Secret" field.
|
||||
- Finally, click the "Add secret" button to save the secret.
|
||||
|
||||
</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:
|
||||
- [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
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
name: Code Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled # Triggers when a label is added to the PR
|
||||
|
||||
# fix: GraphQL: Resource not accessible by integration (addComment) error
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
setup-deepseek-review:
|
||||
runs-on: ubuntu-latest
|
||||
name: Code Review
|
||||
# Make sure the code review happens only when the PR has the label 'ai review'
|
||||
if: contains(github.event.pull_request.labels.*.name, 'ai review')
|
||||
steps:
|
||||
- name: DeepSeek Code Review
|
||||
uses: hustcer/deepseek-review@v1
|
||||
with:
|
||||
chat-token: ${{ secrets.CHAT_TOKEN }}
|
||||
```
|
||||
|
||||
With this setup, DeepSeek code review will not run automatically upon PR creation. Instead, it will only be triggered when you manually add the `ai review` label.
|
||||
|
||||
## Input Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | ------ | ----------------------------------------------------------------------- |
|
||||
| deepseek-token | String | Required, Deepseek API Token |
|
||||
| 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` |
|
||||
| sys-prompt | String | Optional, system prompt corresponding to `$sys_prompt` in the input, default value see note below |
|
||||
| user-prompt | String | Optional, system prompt corresponding to `$user_prompt` in the input, default value see note below |
|
||||
| chat-token | String | Required, DeepSeek API Token |
|
||||
| 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 |
|
||||
| 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 }}`. |
|
||||
|
||||
**API Call Input**:
|
||||
**DeepSeek API Call Payload**:
|
||||
|
||||
```js
|
||||
{
|
||||
// `$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
|
||||
@@ -55,6 +154,83 @@ jobs:
|
||||
}
|
||||
```
|
||||
|
||||
> [!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.
|
||||
|
||||
## Local Code Review
|
||||
|
||||
### Required Tools
|
||||
|
||||
To perform code reviews locally(should works for `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.
|
||||
- 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:
|
||||
> nu cr {flags} (token)
|
||||
|
||||
Flags:
|
||||
-d, --debug: Debug mode
|
||||
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
|
||||
-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
|
||||
-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, or read from CHAT_MODEL env var, `deepseek-chat` by default
|
||||
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
|
||||
-s, --sys-prompt <string>: Default to $DEFAULT_OPTIONS.SYS_PROMPT,
|
||||
-u, --user-prompt <string>: Default to $DEFAULT_OPTIONS.USER_PROMPT,
|
||||
-i, --include <string>: Comma separated file patterns to include in the code review
|
||||
-x, --exclude <string>: Comma separated file patterns to exclude in the code review
|
||||
-T, --temperature <float>: Temperature for the model, between `0` and `2`, default value `1.0`
|
||||
-h, --help: Display the help message for this command
|
||||
|
||||
Parameters:
|
||||
token <string>: Your DeepSeek API token, fallback to CHAT_TOKEN env var (optional)
|
||||
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
To perform code reviews locally, you need to modify the configuration file. A sample configuration file [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml) is already provided in the repository. Copy it to `config.yml` and adjust it according to your actual setup.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> The `config.yml` 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.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```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
|
||||
# Review the changes in the local `DEFAULT_LOCAL_REPO` repo using the `--patch-cmd` flag
|
||||
nu cr --patch-cmd 'git diff head~3'
|
||||
nu cr -c 'git show head~3'
|
||||
nu cr -c 'git diff 2393375 71f5a31'
|
||||
nu cr -c 'git diff 2393375 71f5a31 nu/*'
|
||||
nu cr -c 'git diff 2393375 71f5a31 :!nu/*'
|
||||
# Dangerous commands like `nu cr -c 'git show head~3; rm ./*'` will not be allowed
|
||||
# Perform code review on PR #31 in the remote DEFAULT_GITHUB_REPO repo
|
||||
nu cr --pr-number 31
|
||||
# Perform code review on PR #31 in the remote hustcer/deepseek-review repo
|
||||
nu cr --pr-number 31 --repo hustcer/deepseek-review
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under:
|
||||
|
||||
194
README.zh-CN.md
194
README.zh-CN.md
@@ -1,12 +1,53 @@
|
||||
# Deepseek Code Review
|
||||
# DeepSeek 代码审查
|
||||
|
||||
## 通过 GitHub Action 进行代码审核
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
本工具也支持使用 SiliconCloud 上的 DeepSeek 模型,[注册](https://cloud.siliconflow.cn/i/rqCdIxzS) 就**免费赠送 2000 万 Token**,赶紧试试吧!
|
||||
|
||||
## 特性
|
||||
|
||||
### GitHub Action
|
||||
|
||||
- 通过 GitHub Action 使用 DeepSeek 进行自动化 PR 审查
|
||||
- 在 PR 的标题或描述中添加 `skip cr` or `skip review` 可跳过 GitHub Actions 里的代码审查
|
||||
- 跨平台:支持 GitHub `macOS`, `Ubuntu` & `Windows` Runners
|
||||
|
||||
### 本地代码审查
|
||||
|
||||
- 本地代码审查的时候支持流式输出
|
||||
- 通过本地 CLI 直接审查远程 GitHub PR
|
||||
- 通过本地 CLI 使用 DeepSeek 分析任何本地仓库的提交变更
|
||||
- 跨平台:理论上只要能运行 [Nushell](https://github.com/nushell/nushell) 即可使用本工具
|
||||
|
||||
### 本地或 GH Action
|
||||
|
||||
- 支持 DeepSeek `V3` 和 `R1` 模型
|
||||
- 完全可定制:选择模型、基础 URL 和提示词
|
||||
- 支持自托管 DeepSeek 模型,提供更强的灵活性
|
||||
- 对指定文件变更进行包含/排除式代码审查
|
||||
|
||||
## 计划支持特性
|
||||
|
||||
- [ ] **通过提及触发代码审查**:当 PR 评论中提及 `github-actions bot` 时,自动触发代码审查
|
||||
- [ ] **本地生成提交信息**:为本地仓库的代码变更生成 Commit Message
|
||||
|
||||
## 通过 GitHub Action 进行代码审查
|
||||
|
||||
### 创建 PR 时自动触发代码审查
|
||||
|
||||
创建一个 GitHub workflow 内容如下:
|
||||
|
||||
```yaml
|
||||
name: Code Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
types:
|
||||
- opened # Triggers when a PR is opened
|
||||
- reopened # Triggers when a PR is reopened
|
||||
- synchronize # Triggers when a commit is pushed to the PR
|
||||
|
||||
# fix: GraphQL: Resource not accessible by integration (addComment) error
|
||||
permissions:
|
||||
@@ -17,29 +58,83 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Code Review
|
||||
steps:
|
||||
- name: Deepseek Code Review
|
||||
uses: hustcer/deepseek-review@develop
|
||||
- name: DeepSeek Code Review
|
||||
uses: hustcer/deepseek-review@v1
|
||||
with:
|
||||
deepseek-token: ${{ secrets.DEEPSEEK_TOKEN }}
|
||||
chat-token: ${{ secrets.CHAT_TOKEN }}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>CHAT_TOKEN 配置</summary>
|
||||
|
||||
按照以下步骤配置你的 `CHAT_TOKEN`:
|
||||
|
||||
1. 点击仓库导航栏中的 "Settings" 选项卡
|
||||
2. 在左侧边栏中,点击 "Security" 下的 "Secrets and variables"
|
||||
3. 点击 "Actions" -> "New repository secret" 按钮
|
||||
4. 在 "Name" 字段中输入 `CHAT_TOKEN`
|
||||
5. 在 "Secret" 字段中输入你的 `CHAT_TOKEN` 值
|
||||
6. 最后,点击 "Add secret"按钮保存密钥
|
||||
|
||||
</details>
|
||||
|
||||
当 PR 创建的时候会自动触发 DeepSeek 代码审查,并将审查结果(依赖于提示词)以评论的方式发布到对应的 PR 上。比如:
|
||||
- [示例 1](https://github.com/hustcer/deepseek-review/pull/30) 基于[默认提示词](https://github.com/hustcer/deepseek-review/blob/main/action.yaml#L35) & [运行日志](https://github.com/hustcer/deepseek-review/actions/runs/13043609677/job/36390331791#step:2:53).
|
||||
- [示例 2](https://github.com/hustcer/deepseek-review/pull/68) 基于[这个提示词](https://github.com/hustcer/deepseek-review/blob/eba892d969049caff00b51a31e5c093aeeb536e3/.github/workflows/cr.yml#L32)
|
||||
|
||||
### 当 PR 添加指定 Label 时触发审查
|
||||
|
||||
如果你不希望创建 PR 时自动审查可以选择通过添加标签时触发代码审查,比如创建如下 Workflow:
|
||||
|
||||
```yaml
|
||||
name: Code Review
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled # Triggers when a label is added to the PR
|
||||
|
||||
# fix: GraphQL: Resource not accessible by integration (addComment) error
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
setup-deepseek-review:
|
||||
runs-on: ubuntu-latest
|
||||
name: Code Review
|
||||
# Make sure the code review happens only when the PR has the label 'ai review'
|
||||
if: contains(github.event.pull_request.labels.*.name, 'ai review')
|
||||
steps:
|
||||
- name: DeepSeek Code Review
|
||||
uses: hustcer/deepseek-review@v1
|
||||
with:
|
||||
chat-token: ${{ secrets.CHAT_TOKEN }}
|
||||
```
|
||||
|
||||
如此以来当 PR 创建的时候不会自动触发 DeepSeek 代码审查,只有你手工添加 `ai review` 标签的时候才会触发审查。
|
||||
|
||||
## 输入参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------------- | ------ | -------------------------------------------------------------- |
|
||||
| deepseek-token | String | 必填,Deepseek API Token |
|
||||
| model | String | 可选,配置代码审核选用的模型,默认为 `deepseek-chat` |
|
||||
| base-url | String | 可选,Deepseek API Base URL, 默认为 `https://api.deepseek.com` |
|
||||
| sys-prompt | String | 可选,系统 Prompt 对应入参中的 `$sys_prompt`, 默认值见后文注释 |
|
||||
| user-prompt | String | 可选,用户 Prompt 对应入参中的 `$user_prompt`, 默认值见后文注释 |
|
||||
| chat-token | String | 必填,DeepSeek API Token |
|
||||
| model | String | 可选,配置代码审查选用的模型,默认为 `deepseek-chat` |
|
||||
| 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` |
|
||||
| include-patterns | String | 可选,代码审查中要包含的以逗号分隔的文件模式,无默认值 |
|
||||
| exclude-patterns | String | 可选,代码审查中要排除的以逗号分隔的文件模式,默认值为 `pnpm-lock.yaml,package-lock.json,*.lock` |
|
||||
| github-token | String | 可选,用于访问 API 进行 PR 管理的 GitHub Token,默认为 `${{ github.token }}` |
|
||||
|
||||
接口调用入参:
|
||||
DeepSeek 接口调用入参:
|
||||
|
||||
```js
|
||||
{
|
||||
// `$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
|
||||
@@ -53,6 +148,81 @@ jobs:
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 可以通过提示词的语言来控制代码审查结果的语言,当前默认的提示词语言是英文的,
|
||||
> 当你使用中文提示词的时候生成的代码审查结果就是中文的
|
||||
|
||||
## 本地代码审查
|
||||
|
||||
### 依赖工具
|
||||
|
||||
在本地进行代码审查,支持 `macOS`, `Ubuntu` & `Windows` 不过需要安装以下工具:
|
||||
|
||||
- [`Nushell`](https://www.nushell.sh/book/installation.html), 建议安装最新版本
|
||||
- [`awk`](https://github.com/onetrueawk/awk) 或者 [`gawk`](https://www.gnu.org/software/gawk/) 的最新版版本,优先推荐 `gawk`
|
||||
- 接下来只需要把本仓库代码克隆到本地,然后进入仓库目录执行 `nu cr -h` 即可看到类似如下输出:
|
||||
|
||||
```console
|
||||
Use DeepSeek AI to review code changes locally or in GitHub Actions
|
||||
|
||||
Usage:
|
||||
> nu cr {flags} (token)
|
||||
|
||||
Flags:
|
||||
-d, --debug: Debug mode
|
||||
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
|
||||
-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
|
||||
-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, or read from CHAT_MODEL env var, `deepseek-chat` by default
|
||||
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
|
||||
-s, --sys-prompt <string>: Default to $DEFAULT_OPTIONS.SYS_PROMPT,
|
||||
-u, --user-prompt <string>: Default to $DEFAULT_OPTIONS.USER_PROMPT,
|
||||
-i, --include <string>: Comma separated file patterns to include in the code review
|
||||
-x, --exclude <string>: Comma separated file patterns to exclude in the code review
|
||||
-T, --temperature <float>: Temperature for the model, between `0` and `2`, default value `1.0`
|
||||
-h, --help: Display the help message for this command
|
||||
|
||||
Parameters:
|
||||
token <string>: Your DeepSeek API token, fallback to CHAT_TOKEN env var (optional)
|
||||
|
||||
```
|
||||
|
||||
### 环境配置
|
||||
|
||||
在本地进行代码审查需要先修改配置文件,仓库里已经有了 [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml) 配置文件示例,将其拷贝到 `config.yml` 然后根据自己的实际情况进行修改即可。
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> `config.yml` 配置文件仅在本地使用,在 GitHub Workflow 里面不会使用,里面的敏感信息请
|
||||
> 妥善保存,不要提交到代码仓库里面
|
||||
|
||||
### 使用举例
|
||||
|
||||
```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
|
||||
# 通过 --patch-cmd 参数对本地 DEFAULT_LOCAL_REPO 仓库变更内容进行审查
|
||||
nu cr --patch-cmd 'git diff head~3'
|
||||
nu cr -c 'git show head~3'
|
||||
nu cr -c 'git diff 2393375 71f5a31'
|
||||
nu cr -c 'git diff 2393375 71f5a31 nu/*'
|
||||
nu cr -c 'git diff 2393375 71f5a31 :!nu/*'
|
||||
# 像 `nu cr -c 'git show head~3; rm ./*'` 这样危险的命令将会被禁止
|
||||
# 对远程 DEFAULT_GITHUB_REPO 仓库编号为 31 的 PR 进行代码审查
|
||||
nu cr --pr-number 31
|
||||
# 对远程 hustcer/deepseek-review 仓库编号为 31 的 PR 进行代码审查
|
||||
nu cr --pr-number 31 --repo hustcer/deepseek-review
|
||||
```
|
||||
|
||||
## 许可
|
||||
|
||||
Licensed under:
|
||||
|
||||
63
action.yaml
63
action.yaml
@@ -1,4 +1,4 @@
|
||||
# Deepseek Code Review Action
|
||||
# DeepSeek Code Review Action
|
||||
# @author: hustcer
|
||||
# @created: 2025/01/29 13:05:20
|
||||
# REF:
|
||||
@@ -6,34 +6,53 @@
|
||||
# - https://docs.github.com/cn/actions/creating-actions/metadata-syntax-for-github-actions
|
||||
# - https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||
|
||||
name: 'Deepseek Code Review'
|
||||
name: 'DeepSeek CR'
|
||||
author: 'hustcer'
|
||||
description: 'A github action to do code review by Deepseek for PRs.'
|
||||
description: '🚀 Sharpen Your Code, Ship with Confidence – Elevate Your Workflow with DeepSeek Code Review 🚀'
|
||||
|
||||
branding:
|
||||
icon: 'code'
|
||||
icon: 'eye'
|
||||
color: 'purple'
|
||||
|
||||
inputs:
|
||||
deepseek-token:
|
||||
chat-token:
|
||||
required: true
|
||||
description: 'Your deepseek API token.'
|
||||
description: 'Your DeepSeek API token.'
|
||||
max-length:
|
||||
default: 0
|
||||
required: false
|
||||
description: 'The maximum length of the content for review, 0 means no limit.'
|
||||
model:
|
||||
required: false
|
||||
default: 'deepseek-chat'
|
||||
description: 'The deepseek model to choose for code review.'
|
||||
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'
|
||||
description: 'The base url of deepseek API.'
|
||||
description: 'The base url of DeepSeek API.'
|
||||
sys-prompt:
|
||||
required: false
|
||||
default: '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.'
|
||||
description: 'The system prompt for deepseek API.'
|
||||
description: 'The system prompt for DeepSeek API.'
|
||||
user-prompt:
|
||||
required: false
|
||||
default: 'Please review the following code changes'
|
||||
description: 'The user prompt for deepseek API.'
|
||||
description: 'The user prompt for DeepSeek API.'
|
||||
include-patterns:
|
||||
required: false
|
||||
description: 'The comma separated file patterns to include in the code review.'
|
||||
exclude-patterns:
|
||||
required: false
|
||||
default: 'pnpm-lock.yaml,package-lock.json,*.lock'
|
||||
description: 'The comma separated file patterns to exclude in the code review.'
|
||||
github-token:
|
||||
required: false
|
||||
default: '${{ github.token }}'
|
||||
description: 'The GITHUB_TOKEN secret or personal access token to authenticate. Defaults to `github.token`.'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
@@ -41,28 +60,36 @@ runs:
|
||||
- name: Setup Nu
|
||||
uses: hustcer/setup-nu@v3
|
||||
with:
|
||||
version: 0.101.0
|
||||
version: 0.102.0
|
||||
|
||||
- name: Deepseek Code Review
|
||||
- name: DeepSeek Code Review
|
||||
shell: nu {0}
|
||||
run: |
|
||||
const NU_LIB_DIRS = [ ${{ github.action_path }}/nu ]
|
||||
use review.nu *
|
||||
let model = '${{inputs.model}}'
|
||||
let baseUrl = '${{inputs.base-url}}'
|
||||
let token = '${{inputs.deepseek-token}}'
|
||||
let sysPrompt = '${{inputs.sys-prompt}}'
|
||||
let userPrompt = '${{inputs.user-prompt}}'
|
||||
let ghToken = '${{ github.token }}'
|
||||
let model = '${{ inputs.model }}'
|
||||
let baseUrl = '${{ inputs.base-url }}'
|
||||
let repo = '${{ github.repository }}'
|
||||
let token = '${{ inputs.chat-token }}'
|
||||
let ghToken = '${{ inputs.github-token }}'
|
||||
let sysPrompt = '${{ inputs.sys-prompt }}'
|
||||
let userPrompt = '${{ inputs.user-prompt }}'
|
||||
let pr = '${{ github.event.pull_request.number }}'
|
||||
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
|
||||
--pr-number $pr
|
||||
--gh-token $ghToken
|
||||
--base-url $baseUrl
|
||||
--max-length $maxLength
|
||||
--sys-prompt $sysPrompt
|
||||
--user-prompt $userPrompt
|
||||
--temperature $temperature
|
||||
--include $includePatterns
|
||||
--exclude $excludePatterns
|
||||
)
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ commit_parsers = [
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^breaking", group = "Breaking Changes"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
|
||||
222
config.example.yml
Normal file
222
config.example.yml
Normal file
@@ -0,0 +1,222 @@
|
||||
# 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 1.0
|
||||
temperature: 1.0
|
||||
# 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 local repository to review, could be overrode by '-r' or '--repo'
|
||||
default-local-repo: 'review'
|
||||
# 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: 'DeepSeek'
|
||||
token: 'YOUR_DEEPSEEK_TOKEN' # Required, The API token for the provider
|
||||
base-url: 'https://api.deepseek.com'
|
||||
models:
|
||||
- name: 'deepseek-chat' # 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'
|
||||
|
||||
|
||||
# Multiple local repositories could be defined, select the one by name in 'settings.default-local-repo'
|
||||
# You can also use `-r` or `--repo` to specify the local repository to review by name to override the default
|
||||
local-repos:
|
||||
- name: 'review'
|
||||
path: '/Users/hustcer/deepseek-review'
|
||||
- name: 'milestone'
|
||||
path: '/Users/hustcer/milestone-action'
|
||||
- name: 'setup-nu'
|
||||
path: '/Users/hustcer/setup-nu'
|
||||
|
||||
# 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: 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}}
|
||||
```
|
||||
50
cr
Normal file
50
cr
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env nu
|
||||
# Author: hustcer
|
||||
# Created: 2025/02/08 19:02:15
|
||||
# Description: A wrapper for nu/review.nu as the main entry point of the project.
|
||||
|
||||
use nu/config.nu *
|
||||
use nu/common.nu [hr-line, ECODE]
|
||||
use nu/review.nu [deepseek-review]
|
||||
|
||||
# Use DeepSeek AI to review code changes locally or in GitHub Actions
|
||||
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, or local repo path / alias
|
||||
--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
|
||||
--patch-cmd(-c): string, # The `git show` or `git diff` command to get the diff content, for local CR only
|
||||
--max-length(-l): int, # Maximum length of the content for review, 0 means no limit.
|
||||
--model(-m): string, # Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
|
||||
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
|
||||
--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 `1.0`, Only for V3
|
||||
] {
|
||||
|
||||
config-check
|
||||
config-load --debug=$debug --repo=$repo --model=$model
|
||||
(
|
||||
deepseek-review $token
|
||||
--repo=$repo
|
||||
--debug=$debug
|
||||
--include=$include
|
||||
--exclude=$exclude
|
||||
--model=$env.CHAT_MODEL
|
||||
--base-url=$base_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
|
||||
)
|
||||
}
|
||||
21
cspell.yml
21
cspell.yml
@@ -4,11 +4,26 @@
|
||||
# npx cspell --no-progress .
|
||||
|
||||
words:
|
||||
- psql
|
||||
- MPFR
|
||||
- nuon
|
||||
- pwsh
|
||||
- nuons
|
||||
- ECODE
|
||||
- vyadh
|
||||
- nutest
|
||||
- endfor
|
||||
- dotenv
|
||||
- hustcer
|
||||
- Nushell
|
||||
- creatio
|
||||
- justfile
|
||||
- lefthook
|
||||
- deepseek
|
||||
- hustcer
|
||||
- endfor
|
||||
- dotenv
|
||||
- linewise
|
||||
- Subshell
|
||||
- subshells
|
||||
- Infinigence
|
||||
- SILICONFLOW
|
||||
- USERPROFILE
|
||||
ignorePaths:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "deepseek-review",
|
||||
"version": "1.0.0",
|
||||
"actionVer": "v1.0",
|
||||
"version": "1.12.0",
|
||||
"actionVer": "v1.12",
|
||||
"author": "hustcer",
|
||||
"license": "MIT",
|
||||
"github": "https://github.com/hustcer/deepseek-review",
|
||||
"home": "https://github.com/marketplace/actions/deepseek-review",
|
||||
"description": "A github action to do code review for pull requests."
|
||||
"home": "https://github.com/marketplace/actions/deepseek-cr",
|
||||
"description": "🚀 Sharpen Your Code, Ship with Confidence – Elevate Your Workflow with DeepSeek Code Review 🚀"
|
||||
}
|
||||
|
||||
145
nu/common.nu
Normal file
145
nu/common.nu
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env nu
|
||||
# Author: hustcer
|
||||
# Created: 2025/02/12 19:05:20
|
||||
# Description: Common helpers for DeepSeek-Review
|
||||
#
|
||||
|
||||
# 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,
|
||||
}
|
||||
|
||||
# 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 -i $i | default 0
|
||||
let y = $b | get -i $i | default 0
|
||||
if $x > $y { return 1 }
|
||||
if $x < $y { return (-1) }
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
| transpose -r -d
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
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 {
|
||||
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 }
|
||||
}
|
||||
166
nu/config.nu
Normal file
166
nu/config.nu
Normal file
@@ -0,0 +1,166 @@
|
||||
#!/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 -i $'($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 -i $type
|
||||
| default []
|
||||
| where name == $prompt_key
|
||||
| get -i 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 {|it|
|
||||
let empties = [name token models] | filter { |field| $it | get -i $field | is-empty }
|
||||
if ($empties | is-not-empty) {
|
||||
print $'Field (ansi r)`($empties | str join ,)`(ansi reset) should not be empty for provider:'
|
||||
$it | 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 [] {
|
||||
file-exists $SETTING_FILE
|
||||
let options = open $SETTING_FILE
|
||||
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 -i 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 -i 0.name
|
||||
| default $model
|
||||
|
||||
{ CHAT_TOKEN: $provider.token?, BASE_URL: $provider.base-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
|
||||
--repo(-r): string, # Load the specified local repository by name
|
||||
--model(-m): string, # Load the specified model by name
|
||||
] {
|
||||
let all_settings = open $SETTING_FILE
|
||||
let settings = $all_settings | get settings? | default {}
|
||||
let local_repo = $all_settings.local-repos
|
||||
| default []
|
||||
| where name == ($repo | default $settings.default-local-repo? | default '')
|
||||
| get -i 0.path
|
||||
| default $repo
|
||||
|
||||
let user_prompt = $all_settings.prompts?.user?
|
||||
| default []
|
||||
| where name == ($settings.user-prompt? | default '')
|
||||
| get -i 0.prompt
|
||||
|
||||
let system_prompt = $all_settings.prompts?.system?
|
||||
| default []
|
||||
| where name == ($settings.system-prompt? | default '')
|
||||
| get -i 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_LOCAL_REPO: $local_repo,
|
||||
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
|
||||
}
|
||||
}
|
||||
187
nu/kv.nu
Normal file
187
nu/kv.nu
Normal file
@@ -0,0 +1,187 @@
|
||||
# NOTE: This file was copied from https://github.com/nushell/nushell/tree/main/crates/nu-std/std-rfc/kv
|
||||
# And will be removed after the Nu v0.103 release
|
||||
# More examples could be found here: https://github.com/nushell/nushell/discussions/14965
|
||||
#
|
||||
# kv module
|
||||
#
|
||||
# use std-rfc/kv *
|
||||
#
|
||||
# Easily store and retrieve key-value pairs
|
||||
# in a pipeline.
|
||||
#
|
||||
# A common request is to be able to assign a
|
||||
# pipeline result to a variable. While it's
|
||||
# not currently possible to use a "let" statement
|
||||
# within a pipeline, this module provides an
|
||||
# alternative. Think of each key as a variable
|
||||
# that can be set and retrieved.
|
||||
|
||||
# Stores the pipeline value for later use
|
||||
#
|
||||
# If the key already exists, it is updated to the new value provided.
|
||||
export def "kv set" [
|
||||
key: string
|
||||
value_or_closure?: any
|
||||
--return (-r): string # Whether and what to return to the pipeline output
|
||||
--universal (-u)
|
||||
] {
|
||||
# Pipeline input is preferred, but prioritize
|
||||
# parameter if present. This allows $in to be
|
||||
# used in the parameter if needed.
|
||||
let input = $in
|
||||
|
||||
# If passed a closure, execute it
|
||||
let arg_type = ($value_or_closure | describe)
|
||||
let value = match $arg_type {
|
||||
closure => { $input | do $value_or_closure }
|
||||
_ => ($value_or_closure | default $input)
|
||||
}
|
||||
|
||||
# Store values as nuons for type-integrity
|
||||
let kv_pair = {
|
||||
session: '' # Placeholder
|
||||
key: $key
|
||||
value: ($value | to nuon)
|
||||
}
|
||||
|
||||
let db_open = (db_setup --universal=$universal)
|
||||
try {
|
||||
# Delete the existing key if it does exist
|
||||
do $db_open | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key }
|
||||
}
|
||||
|
||||
match $universal {
|
||||
true => { $kv_pair | into sqlite (universal_db_path) -t std_kv_store }
|
||||
false => { $kv_pair | stor insert -t std_kv_store }
|
||||
}
|
||||
|
||||
# The value that should be returned from `kv set`
|
||||
# By default, this is the input to `kv set`, even if
|
||||
# overridden by a positional parameter.
|
||||
# This can also be:
|
||||
# input: (Default) The pipeline input to `kv set`, even if
|
||||
# overridden by a positional parameter. `null` if no
|
||||
# pipeline input was used.
|
||||
# ---
|
||||
# value: If a positional parameter was used for the value, then
|
||||
# return it, otherwise return the input (whatever was set).
|
||||
# If the positional was a closure, return the result of the
|
||||
# closure on the pipeline input.
|
||||
# ---
|
||||
# all: The entire contents of the existing kv table are returned
|
||||
match ($return | default 'input') {
|
||||
'all' => (kv list --universal=$universal)
|
||||
'a' => (kv list --universal=$universal)
|
||||
'value' => $value
|
||||
'v' => $value
|
||||
'input' => $input
|
||||
'in' => $input
|
||||
'i' => $input
|
||||
_ => {
|
||||
error make {
|
||||
msg: "Invalid --return option"
|
||||
label: {
|
||||
text: "Must be 'all'/'a', 'value'/'v', or 'input'/'in'/'i'"
|
||||
span: (metadata $return).span
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Retrieves a stored value by key
|
||||
#
|
||||
# Counterpart of "kv set". Returns null if the key is not found.
|
||||
export def "kv get" [
|
||||
key: string # Key of the kv-pair to retrieve
|
||||
--universal (-u)
|
||||
] {
|
||||
let db_open = (db_setup --universal=$universal)
|
||||
do $db_open
|
||||
# Hack to turn a SQLiteDatabase into a table
|
||||
| $in.std_kv_store | wrap temp | get temp
|
||||
| where key == $key
|
||||
# Should only be one occurrence of each key in the stor
|
||||
| get -i value.0
|
||||
| match $in {
|
||||
# Key not found
|
||||
null => null
|
||||
# Key found
|
||||
_ => { from nuon }
|
||||
}
|
||||
}
|
||||
|
||||
# List the currently stored key-value pairs
|
||||
#
|
||||
# Returns results as the Nushell value rather than the stored nuon.
|
||||
export def "kv list" [
|
||||
--universal (-u)
|
||||
] {
|
||||
let db_open = (db_setup --universal=$universal)
|
||||
|
||||
do $db_open | $in.std_kv_store? | each {|kv_pair|
|
||||
{
|
||||
key: $kv_pair.key
|
||||
value: ($kv_pair.value | from nuon )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Returns and removes a key-value pair
|
||||
export def --env "kv drop" [
|
||||
key: string # Key of the kv-pair to drop
|
||||
--universal (-u)
|
||||
] {
|
||||
let db_open = (db_setup --universal=$universal)
|
||||
|
||||
let value = (kv get --universal=$universal $key)
|
||||
|
||||
try {
|
||||
do $db_open
|
||||
# Hack to turn a SQLiteDatabase into a table
|
||||
| query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key }
|
||||
}
|
||||
|
||||
if $universal and ($env.NU_KV_UNIVERSALS? | default false) {
|
||||
hide-env $key
|
||||
}
|
||||
|
||||
$value
|
||||
}
|
||||
|
||||
def universal_db_path [] {
|
||||
$env.NU_UNIVERSAL_KV_PATH?
|
||||
| default (
|
||||
$nu.data-dir | path join "std_kv_variables.sqlite3"
|
||||
)
|
||||
}
|
||||
|
||||
def db_setup [
|
||||
--universal
|
||||
] : nothing -> closure {
|
||||
try {
|
||||
match $universal {
|
||||
true => {
|
||||
# Ensure universal sqlite db and table exists
|
||||
let uuid = (random uuid)
|
||||
let dummy_record = {
|
||||
session: ''
|
||||
key: $uuid
|
||||
value: ''
|
||||
}
|
||||
$dummy_record | into sqlite (universal_db_path) -t std_kv_store
|
||||
open (universal_db_path) | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $uuid }
|
||||
}
|
||||
false => {
|
||||
# Create the stor table if it doesn't exist
|
||||
stor create -t std_kv_store -c {session: str, key: str, value: str} | ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return the correct closure for opening on-disk vs. in-memory
|
||||
match $universal {
|
||||
true => {{|| open (universal_db_path)}}
|
||||
false => {{|| stor open}}
|
||||
}
|
||||
}
|
||||
467
nu/review.nu
467
nu/review.nu
@@ -2,126 +2,431 @@
|
||||
# Author: hustcer
|
||||
# Created: 2025/01/29 13:02:15
|
||||
# TODO:
|
||||
# [√] Deepseek code review for GitHub PRs
|
||||
# [√] Deepseek code review for local commit changes
|
||||
# [√] Debug mode
|
||||
# [√] Output token usage info
|
||||
# [ ] Add more action outputs
|
||||
# Description: A script to do code review by deepseek
|
||||
# [√] DeepSeek code review for GitHub PRs
|
||||
# [√] DeepSeek code review for local commit changes
|
||||
# [√] 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:
|
||||
# - https://docs.github.com/en/rest/issues/comments
|
||||
# - https://docs.github.com/en/rest/pulls/pulls
|
||||
# Env vars:
|
||||
# GITHUB_TOKEN: Your GitHub API token
|
||||
# DEEPSEEK_TOKEN: Your Deepseek API token
|
||||
# CHAT_TOKEN: Your DeepSeek API token
|
||||
# BASE_URL: DeepSeek API base URL
|
||||
# SYSTEM_PROMPT: System prompt message
|
||||
# USER_PROMPT: User prompt message
|
||||
# Usage:
|
||||
# 1. Local: just cr
|
||||
# 2. Local: just cr -f HEAD~1 --debug
|
||||
#
|
||||
# - Local Repo Review: just cr
|
||||
# - Local Repo Review: just cr -f HEAD~1 --debug
|
||||
# - Local PR Review: just cr -r hustcer/deepseek-review -n 32
|
||||
|
||||
use kv.nu *
|
||||
use common.nu [
|
||||
ECODE, hr-line, is-installed, windows?, mac?,
|
||||
compare-ver, compact-record, git-check, has-ref,
|
||||
]
|
||||
|
||||
const RESPONSE_END = 'data: [DONE]'
|
||||
|
||||
const GITHUB_API_BASE = 'https://api.github.com'
|
||||
|
||||
# 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,
|
||||
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.',
|
||||
}
|
||||
|
||||
# Use Deepseek AI to review code changes
|
||||
export def deepseek-review [
|
||||
token?: string, # Your Deepseek API token, fallback to DEEPSEEK_TOKEN
|
||||
--debug(-d), # Debug mode
|
||||
--repo: string, # GitHub repository name, e.g. hustcer/deepseek-review
|
||||
--pr-number: string, # GitHub PR number
|
||||
--gh-token: string, # Your GitHub token, GITHUB_TOKEN by default
|
||||
--diff-to(-t): string, # Diff to git ref
|
||||
--diff-from(-f): string, # Diff from git ref
|
||||
--model: string = $DEFAULT_OPTIONS.MODEL, # Model name, deepseek-chat by default
|
||||
--base-url: string = $DEFAULT_OPTIONS.BASE_URL,
|
||||
--sys-prompt: string = $DEFAULT_OPTIONS.SYS_PROMPT,
|
||||
--user-prompt: string = $DEFAULT_OPTIONS.USER_PROMPT,
|
||||
] {
|
||||
# If the PR title or body contains any of these keywords, skip the review
|
||||
const IGNORE_REVIEW_KEYWORDS = ['skip review' 'skip cr']
|
||||
|
||||
let token = $token | default $env.DEEPSEEK_TOKEN?
|
||||
$env.GH_TOKEN = $gh_token | default $env.GITHUB_TOKEN?
|
||||
if ($token | is-empty) {
|
||||
print $'(ansi r)Please provide your Deepseek API token by setting `DEEPSEEK_TOKEN` or passing it as an argument.(ansi reset)'
|
||||
return
|
||||
# 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 repo name, e.g. hustcer/deepseek-review, or local repo path / alias
|
||||
--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
|
||||
--patch-cmd(-c): string, # The `git show` or `git diff` command to get the diff content, for local CR only
|
||||
--max-length(-l): int, # Maximum length of the content for review, 0 means no limit.
|
||||
--model(-m): string, # Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
|
||||
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
|
||||
--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 `1.0`
|
||||
]: nothing -> nothing {
|
||||
|
||||
$env.config.table.mode = 'psql'
|
||||
let is_action = ($env.GITHUB_ACTIONS? == 'true')
|
||||
let stream = if $is_action { false } else { true }
|
||||
let token = $token | default $env.CHAT_TOKEN?
|
||||
let repo = $repo | default $env.DEFAULT_GITHUB_REPO?
|
||||
let CHAT_HEADER = [Authorization $'Bearer ($token)']
|
||||
let local_repo = $env.DEFAULT_LOCAL_REPO? | default (pwd)
|
||||
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 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 hint = if ($env.GITHUB_ACTIONS? != 'true') {
|
||||
$'🚀 Initiate the code review by Deepseek AI for local changes ...'
|
||||
let url = $'($base_url)/chat/completions'
|
||||
let setting = {
|
||||
repo: $repo,
|
||||
model: $model,
|
||||
chat_url: $url,
|
||||
include: $include,
|
||||
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,
|
||||
temperature: $temperature,
|
||||
}
|
||||
$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
|
||||
}
|
||||
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) ...'
|
||||
$'🚀 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)
|
||||
$env.GITHUB_TOKEN = $gh_token | default $env.GITHUB_TOKEN?
|
||||
let diff_content = if ($pr_number | is-not-empty) {
|
||||
gh pr diff $pr_number --repo $repo | str trim
|
||||
} else if ($diff_from | is-not-empty) {
|
||||
git diff $diff_from ($diff_to | default HEAD)
|
||||
} else { git diff }
|
||||
if ($diff_content | is-empty) {
|
||||
print $'(ansi r)Please provide the diff content by passing `--pr-number`.(ansi reset)'
|
||||
return
|
||||
if ($pr_number | is-empty) {
|
||||
print 'Current Settings:'; hr-line
|
||||
$setting | compact-record | reject -i 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 --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 $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($diff_content)" }
|
||||
{ role: 'user', content: $"($user_prompt):\n($content)" }
|
||||
]
|
||||
}
|
||||
if $debug {
|
||||
print $'Code Changes:'; hr-line; print $diff_content
|
||||
}
|
||||
let header = [Authorization $'Bearer ($token)']
|
||||
let url = $'($base_url)/chat/completions'
|
||||
print $'(char nl)(ansi g)Waiting for response from Deepseek ...(ansi reset)'
|
||||
let response = http post -e -H $header -t application/json $url $payload
|
||||
if $debug { print $'(char nl)Code Changes:'; hr-line; print $content }
|
||||
print $'(char nl)Waiting for response from (ansi g)($base_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)'
|
||||
exit 1
|
||||
return
|
||||
}
|
||||
if $debug {
|
||||
print $'Deepseek Response:'; hr-line
|
||||
$response | table -e | print
|
||||
print $'(ansi r)Oops, No response returned from ($base_url) ...(ansi reset)'
|
||||
exit $ECODE.SERVER_ERROR
|
||||
}
|
||||
if $debug { print $'DeepSeek Model Response:'; hr-line; $response | table -e | print }
|
||||
if ($response | describe) == 'string' {
|
||||
print $'❌ Code review failed!Error: '; hr-line; print $response
|
||||
exit 1
|
||||
return
|
||||
print $'✖️ Code review failed!Error: '; hr-line; print $response
|
||||
exit $ECODE.SERVER_ERROR
|
||||
}
|
||||
let reason = $response | get -i choices.0.message.reasoning_content
|
||||
let review = $response | get -i choices.0.message.content
|
||||
if ($env.GITHUB_ACTIONS? != 'true') {
|
||||
print $'Code Review Result:'; hr-line
|
||||
print $review
|
||||
let result = ['<details>' '<summary> Reasoning Details</summary>' $reason "</details>\n" $review] | str join "\n"
|
||||
if ($review | is-empty) {
|
||||
print $'✖️ Code review failed!No review result returned from ($base_url) ...'
|
||||
exit $ECODE.SERVER_ERROR
|
||||
}
|
||||
let result = if ($reason | is-empty) { $review } else { $result }
|
||||
if not $is_action {
|
||||
print $'Code Review Result:'; hr-line; print $result
|
||||
} else {
|
||||
gh pr comment $pr_number --body $review --repo $repo
|
||||
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: $result }
|
||||
print $'✅ Code review finished!PR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
|
||||
}
|
||||
print $'(char nl)Token Usage Info:'; hr-line
|
||||
print $'(char nl)Token Usage:'; hr-line
|
||||
$response.usage | table -e | print
|
||||
}
|
||||
|
||||
# Check if some command available in current shell
|
||||
export def is-installed [ app: string ] {
|
||||
(which $app | length) > 0
|
||||
# Output the streaming response of review result from DeepSeek API
|
||||
def streaming-output [
|
||||
url: string, # The Full DeepSeek API URL
|
||||
payload: record, # The payload to send to DeepSeek API
|
||||
--debug, # Debug mode
|
||||
--headers: list, # The headers to send to DeepSeek API
|
||||
] {
|
||||
print -n (char nl)
|
||||
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: '
|
||||
if $record_error or $other_error {
|
||||
$res | table -e | print
|
||||
exit $ECODE.SERVER_ERROR
|
||||
}
|
||||
}
|
||||
| try { lines } catch { print $'(ansi r)Error Happened ...(ansi reset)'; exit $ECODE.SERVER_ERROR }
|
||||
| each {|line|
|
||||
if $line == $RESPONSE_END { return }
|
||||
if ($line | is-empty) { return }
|
||||
let $last = $line | str substring 6.. | from json
|
||||
if $last == '-alive' { print $last; return }
|
||||
if $debug { $last | to json | kv set last-reply }
|
||||
$last | get -i choices.0.delta | if ($in | is-not-empty) {
|
||||
let delta = $in
|
||||
if ($delta.reasoning_content? | 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.reasoning_content? | 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 -i model usage | table -e | print
|
||||
}
|
||||
}
|
||||
|
||||
export def hr-line [
|
||||
width?: int = 90,
|
||||
--color(-c): string = 'g',
|
||||
--blank-line(-b),
|
||||
--with-arrow(-a),
|
||||
# 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
|
||||
--patch-cmd: string, # The `git show` or `git diff` command to get the diff content
|
||||
] {
|
||||
# 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 }
|
||||
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 ($pr_number | is-empty) {
|
||||
if not ($local_repo | path exists) {
|
||||
print $'(ansi r)The directory ($local_repo) does not exist.(ansi reset)'
|
||||
exit $ECODE.CONDITION_NOT_SATISFIED
|
||||
}
|
||||
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 if ($patch_cmd | is-not-empty) {
|
||||
let valid = is-safe-git $patch_cmd
|
||||
if not $valid { exit $ECODE.INVALID_PARAMETER }
|
||||
nu -c $patch_cmd
|
||||
} else { git diff }
|
||||
|
||||
if ($content | is-empty) {
|
||||
print $'(ansi g)Nothing to review.(ansi reset)'; exit $ECODE.SUCCESS
|
||||
}
|
||||
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 ','
|
||||
$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
|
||||
}
|
||||
|
||||
# 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>] {
|
||||
$patterns
|
||||
| each { |pat|
|
||||
$pat | str replace "*" ".*" | str replace "?" "." | str replace "/" "\\/"
|
||||
}
|
||||
| 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 --since=2025-02-09 HEAD
|
||||
# - git diff 2393375 71f5a31
|
||||
# - git diff 2393375 71f5a31 nu/*
|
||||
# - git diff 2393375 71f5a31 :!nu/*
|
||||
export def is-safe-git [cmd: string] {
|
||||
# Normalize the command string by trimming and converting to lowercase
|
||||
let normalized_cmd = ($cmd | str trim | str downcase)
|
||||
|
||||
# More strict regex for git commands, allow:
|
||||
# 1. --since parameter with ISO date format
|
||||
# 2. File path patterns with or without colon (e.g. :!nu/*, nu/*)
|
||||
let allowed_regex = '^git\s+(show|diff)(?:\s+(?:--since=\d{4}-\d{2}-\d{2}|[a-zA-Z0-9_\-\.~/]+))*(?:\s+(?::[!]?)?[a-zA-Z0-9_\-\.\*\/]+)?$'
|
||||
|
||||
# Dangerous patterns to check (expanded list)
|
||||
let dangerous_patterns = [
|
||||
# Command chaining/injection
|
||||
';', '&&', '||', '|',
|
||||
# Shell expansion
|
||||
'?', '[', ']', '{', '}',
|
||||
# Command substitution
|
||||
'`', '$(',
|
||||
# IO redirection
|
||||
'>', '>>', '<', '<<',
|
||||
# Special characters
|
||||
'\n', '\r', '\t',
|
||||
# Path traversal
|
||||
'..',
|
||||
# Environment variables
|
||||
'$', '%',
|
||||
# Quotes that might be used for injection
|
||||
'"', "'"
|
||||
]
|
||||
|
||||
# First check: Command must match the allowed pattern
|
||||
if ($normalized_cmd | find -r $allowed_regex | is-empty) {
|
||||
print $'ERROR: Invalid git command format. (ansi r)Only simple `git show` or `git diff` commands are allowed(ansi reset).'
|
||||
return false
|
||||
}
|
||||
|
||||
print $'(ansi $color)(build-line $width)(if $with_arrow {'>'})(ansi reset)'
|
||||
if $blank_line { char nl }
|
||||
# Second check: No dangerous patterns allowed
|
||||
for pattern in $dangerous_patterns {
|
||||
if ($cmd | str contains $pattern) {
|
||||
print $'(ansi r)ERROR: Dangerous pattern detected: `($pattern)`(ansi reset)'
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
# Third check: Command parts validation (increased limit to accommodate path patterns)
|
||||
let cmd_parts = $normalized_cmd | split row ' '
|
||||
if ($cmd_parts | length) > 6 {
|
||||
print $'ERROR: Command too complex. (ansi r)Only simple `git show` or `git diff` commands are allowed(ansi reset).'
|
||||
return false
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
# 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-path)/scoop/shims/gawk.exe'
|
||||
let version = get-awk-ver $awk_bin
|
||||
{ awk_bin: $awk_bin, version: $version }
|
||||
}
|
||||
|
||||
alias main = deepseek-review
|
||||
|
||||
13
tests/resources/.env.test
Normal file
13
tests/resources/.env.test
Normal 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
153
tests/resources/diff.patch
Normal 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)" }
|
||||
64
tests/test-common.nu
Normal file
64
tests/test-common.nu
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
use std/assert
|
||||
|
||||
use ../nu/common.nu [compare-ver, 'from env', is-installed, has-ref, git-check, compact-record]
|
||||
|
||||
#[test]
|
||||
def 'compare-ver:v1.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-ver:v1.0.1 is equal to v1.0.1' [] {
|
||||
assert equal (compare-ver 1.0.1 1.0.1) 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'compare-ver:v1.0.0 is equal to v1' [] {
|
||||
assert equal (compare-ver v1.0.0 v1) 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'compare-ver:v1.0.1 is greater than v1' [] {
|
||||
assert equal (compare-ver v1.0.1 v1) 1
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'compare-ver:v1.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-installed:binary install check should work' [] {
|
||||
assert equal (is-installed git) true
|
||||
assert equal (is-installed abc) false
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'has-ref:git repo should has HEAD ref' [] {
|
||||
assert equal (has-ref HEAD) true
|
||||
assert equal (has-ref 0000) false
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'git-check:current dir is a git repo' [] {
|
||||
assert equal (git-check (pwd) --check-repo=1) true
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'compact-record:should 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' } }
|
||||
}
|
||||
118
tests/test-review.nu
Normal file
118
tests/test-review.nu
Normal file
@@ -0,0 +1,118 @@
|
||||
|
||||
use std/assert
|
||||
|
||||
use ../nu/review.nu [
|
||||
get-diff, 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-git:should 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 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 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
|
||||
}
|
||||
|
||||
#[test]
|
||||
def 'generate-include-regex:should 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-regex:should 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-diff:get 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-diff:get 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-diff:get 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-diff:get 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
|
||||
}
|
||||
Reference in New Issue
Block a user