feat: argus 改名 opencodereview

This commit is contained in:
kite 2026-04-27 16:16:43 +08:00
parent 9e449d5a24
commit 3d2029dbfa
32 changed files with 507 additions and 160 deletions

2
.gitignore vendored
View file

@ -10,7 +10,7 @@ temp/
bin/
/argus
/opencodereview
# Mac OS X
.DS_Store

View file

@ -2,7 +2,7 @@
build-all dist sha256sum version-info \
build-linux-amd64 build-linux-arm64 build-darwin-amd64 build-darwin-arm64
BINARY_NAME := argus
BINARY_NAME := opencodereview
GO := go
DIST_DIR := ./dist
@ -21,12 +21,12 @@ LD_FLAGS := -s -w \
define BUILD_PLATFORM
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 $(GO) build -ldflags "$(LD_FLAGS)" \
-o $(DIST_DIR)/$(BINARY_NAME)-$(VERSION)-$(1)-$(2) \
./cmd/argus
./cmd/opencodereview
endef
# ── Development targets ──────────────────────────────────────────────────────
build:
$(GO) build -ldflags "$(LD_FLAGS)" -o $(DIST_DIR)/$(BINARY_NAME) ./cmd/argus
$(GO) build -ldflags "$(LD_FLAGS)" -o $(DIST_DIR)/$(BINARY_NAME) ./cmd/opencodereview
test:
$(GO) test -v -race -count=1 ./...

View file

@ -1,6 +1,6 @@
# Argus
# OpenCodeReview
AI-powered code review CLI tool built in Go. Argus reads git diffs, sends changed files to a configurable LLM (via OpenAI-compatible API), and generates structured code review comments — acting as an autonomous AI reviewer that can read your full project context, not just the diff.
AI-powered code review CLI tool built in Go. OpenCodeReview reads git diffs, sends changed files to a configurable LLM (via OpenAI-compatible API), and generates structured code review comments — acting as an autonomous AI reviewer that can read your full project context, not just the diff.
## Features
@ -20,15 +20,15 @@ AI-powered code review CLI tool built in Go. Argus reads git diffs, sends change
Requires [Go](https://go.dev/) 1.24+.
```bash
git clone https://github.com/argus-review/argus.git
cd argus
make # builds binary at ./bin/argus
git clone https://github.com/open-code-review/open-code-review.git
cd open-code-review
make # builds binary at ./bin/opencodereview
```
Or build directly:
```bash
go build -o bin/argus ./cmd/argus
go build -o bin/opencodereview ./cmd/opencodereview
```
Add `bin/` to your `$PATH` or copy the binary somewhere accessible.
@ -37,20 +37,20 @@ Add `bin/` to your `$PATH` or copy the binary somewhere accessible.
### 1. Configure your LLM
Argus connects to any OpenAI-compatible API endpoint. Set up your credentials:
OpenCodeReview connects to any OpenAI-compatible API endpoint. Set up your credentials:
```bash
argus config set llm.url https://your-api-endpoint/v1/chat/completions
argus config set llm.auth_token your-api-key-here
argus config set llm.model claude-opus-4-6
ocr config set llm.url https://your-api-endpoint/v1/chat/completions
ocr config set llm.auth_token your-api-key-here
ocr config set llm.model claude-opus-4-6
```
Configuration is stored at `~/.argus/config.json`.
Configuration is stored at `~/.open-code-review/config.json`.
### 2. Test connectivity
```bash
argus llm test
ocr llm test
```
### 3. Run a review
@ -59,13 +59,13 @@ Navigate to any git repository and run:
```bash
# Review all staged, unstaged, and untracked changes
argus review
ocr review
# Review differences between two branches
argus review --from main --to feature-branch
ocr review --from main --to feature-branch
# Review a specific commit
argus review --commit abc123
ocr review --commit abc123
```
## Usage
@ -74,10 +74,10 @@ argus review --commit abc123
| Command | Description |
|---------|-------------|
| `argus review` / `argus r` | Start a code review session |
| `argus config set <key> <value>` | Manage user configuration |
| `argus llm test` | Test LLM connectivity |
| `argus version` | Show version information |
| `ocr review` / `ocr r` | Start a code review session |
| `ocr config set <key> <value>` | Manage user configuration |
| `ocr llm test` | Test LLM connectivity |
| `ocr version` | Show version information |
### Review Flags
@ -98,24 +98,24 @@ argus review --commit abc123
**Workspace mode** (no flags): reviews all staged, unstaged, and untracked changes in the current working directory.
```bash
argus review
ocr review
```
**Branch range mode**: reviews changes between two git refs using merge-base.
```bash
argus review --from main --to dev
ocr review --from main --to dev
```
**Single commit mode**: reviews a specific commit against its parent.
```bash
argus review --commit abc123
ocr review --commit abc123
```
## Configuration
User config lives at `~/.argus/config.json`. Supported keys:
User config lives at `~/.open-code-review/config.json`. Supported keys:
| Key | Description | Example |
|-----|-------------|---------|
@ -166,7 +166,7 @@ Example config:
└─────────────┘
```
Each changed file is reviewed as an independent subtask running concurrently (configurable semaphore). Full session history (requests, responses, tool calls, durations, token estimates) is saved to `<repo>/temp/argus-session-*.json` for debugging.
Each changed file is reviewed as an independent subtask running concurrently (configurable semaphore). Full session history (requests, responses, tool calls, durations, token estimates) is saved to `<repo>/temp/ocr-session-*.json` for debugging.
### LLM Agent Tools
@ -184,7 +184,7 @@ During a review, the AI agent has access to these tools:
## Project Structure
```
├── cmd/argus/ # CLI entry point and command dispatch
├── cmd/opencodereview/ # CLI entry point and command dispatch
│ ├── main.go # Application entry point
│ ├── review_cmd.go # Core review orchestration
│ ├── flags.go # Flag parsing

View file

@ -8,13 +8,13 @@ import (
"strconv"
)
// Default config file location: ~/.argus/config.json
// Default config file location: ~/.open-code-review/config.json
func defaultConfigPath() string {
home, err := os.UserHomeDir()
if err != nil {
return "argus.json"
return "open-code-review.json"
}
return filepath.Join(home, ".argus", "config.json")
return filepath.Join(home, ".open-code-review", "config.json")
}
func runConfig(args []string) error {
@ -66,7 +66,7 @@ func runConfigSet(key, value string) error {
return nil
}
// Config represents the user-level configuration file (~/.argus/config.json).
// Config represents the user-level configuration file (~/.open-code-review/config.json).
type Config struct {
Llm LlmConfig `json:"llm,omitempty"`
Language string `json:"language,omitempty"` // Output language, defaults to Chinese when empty

View file

@ -8,21 +8,21 @@ import (
// --- custom flag set that supports short flags (-c, -f etc.) ---
type argusFlagSet struct {
type ocrFlagSet struct {
fs *flag.FlagSet
shortMap map[string]string // maps short key "c" -> full name "commit"
showHelp bool
}
func newArgusFlagSet(name string) *argusFlagSet {
return &argusFlagSet{
func newOcrFlagSet(name string) *ocrFlagSet {
return &ocrFlagSet{
fs: flag.NewFlagSet(name, flag.ContinueOnError),
shortMap: make(map[string]string),
}
}
// StringVarP registers --name with optional short form -s.
func (a *argusFlagSet) StringVarP(p *string, name, shorthand string, value, usage string) {
func (a *ocrFlagSet) StringVarP(p *string, name, shorthand string, value, usage string) {
suffix := ""
if shorthand != "" {
a.shortMap[shorthand] = name
@ -32,7 +32,7 @@ func (a *argusFlagSet) StringVarP(p *string, name, shorthand string, value, usag
}
// BoolVarP registers --name with optional short form -s.
func (a *argusFlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
func (a *ocrFlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
suffix := ""
if shorthand != "" {
a.shortMap[shorthand] = name
@ -41,27 +41,27 @@ func (a *argusFlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usa
a.fs.BoolVar(p, name, value, usage+suffix)
}
func (a *argusFlagSet) StringVar(p *string, name string, value string, usage string) {
func (a *ocrFlagSet) StringVar(p *string, name string, value string, usage string) {
a.fs.StringVar(p, name, value, usage)
}
func (a *argusFlagSet) BoolVar(p *bool, name string, value bool, usage string) {
func (a *ocrFlagSet) BoolVar(p *bool, name string, value bool, usage string) {
a.fs.BoolVar(p, name, value, usage)
}
func (a *argusFlagSet) IntVar(p *int, name string, value int, usage string) {
func (a *ocrFlagSet) IntVar(p *int, name string, value int, usage string) {
a.fs.IntVar(p, name, value, usage)
}
func (a *argusFlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
func (a *ocrFlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
a.fs.DurationVar(p, name, value, usage)
}
func (a *argusFlagSet) PrintDefaults() {
func (a *ocrFlagSet) PrintDefaults() {
a.fs.PrintDefaults()
}
func (a *argusFlagSet) Parse(arguments []string) error {
func (a *ocrFlagSet) Parse(arguments []string) error {
expanded := expandShortFlags(arguments, a.shortMap)
for _, arg := range expanded {
@ -107,7 +107,7 @@ type reviewOptions struct {
}
func parseReviewFlags(args []string) (reviewOptions, error) {
a := newArgusFlagSet("argus review")
a := newOcrFlagSet("ocr review")
opts := reviewOptions{}
@ -149,26 +149,26 @@ func parseReviewFlags(args []string) (reviewOptions, error) {
}
func printReviewUsage() {
fmt.Println(`Argus - Code Review Agent CLI
fmt.Println(`OpenCodeReview - AI-Powered Code Review CLI
Usage:
argus review [flags]
argus r [flags] (alias)
ocr review [flags]
ocr r [flags] (alias)
Examples:
# Review staged + unstaged + untracked changes in current workspace
argus review
ocr review
# Review a branch against its base (merge-base mode)
argus review --from master --to dev-ref
ocr review --from master --to dev-ref
# Review a specific commit
argus review --commit abc123
argus review -c abc123
ocr review --commit abc123
ocr review -c abc123
# Output JSON format
argus review --format json
argus review -f json
ocr review --format json
ocr review -f json
Flags:`)
fs := flag.NewFlagSet("print", flag.ContinueOnError)
@ -193,14 +193,14 @@ type configAction struct {
func parseConfigArgs(args []string) (configAction, error) {
if len(args) == 0 {
return configAction{}, fmt.Errorf("usage: argus config set <key> <value>\ne.g., argus config set llm.provider idealab")
return configAction{}, fmt.Errorf("usage: ocr config set <key> <value>\ne.g., ocr config set llm.provider idealab")
}
subCmd := args[0]
switch subCmd {
case "set":
if len(args) < 3 {
return configAction{}, fmt.Errorf("usage: argus config set <key> <value>\ne.g., argus config set llm.model claude-opus-4-6")
return configAction{}, fmt.Errorf("usage: ocr config set <key> <value>\ne.g., ocr config set llm.model claude-opus-4-6")
}
return configAction{
subCmd: "set",
@ -216,13 +216,13 @@ func printConfigUsage() {
fmt.Println(`Configuration management.
Usage:
argus config set <key> <value>
ocr config set <key> <value>
Examples:
argus config set llm.provider idealab
argus config set llm.url https://xx/v1/openai/chat/completions
argus config set llm.auth_token xxxxxxxxxx
argus config set llm.model claude-opus-4-6
ocr config set llm.provider idealab
ocr config set llm.url https://xx/v1/openai/chat/completions
ocr config set llm.auth_token xxxxxxxxxx
ocr config set llm.model claude-opus-4-6
Supported keys: llm.provider, llm.url, llm.auth_token, llm.model`)
}

View file

@ -4,8 +4,8 @@ import (
"fmt"
"time"
"github.com/argus-review/argus/internal/config/testconnection"
"github.com/argus-review/argus/internal/llm"
"github.com/open-code-review/open-code-review/internal/config/testconnection"
"github.com/open-code-review/open-code-review/internal/llm"
)
func runLLM(args []string) error {
@ -18,7 +18,7 @@ func runLLM(args []string) error {
case "test":
return runLLMTest()
default:
return fmt.Errorf("unknown llm sub-command: %s\nRun 'argus llm' for usage", args[0])
return fmt.Errorf("unknown llm sub-command: %s\nRun 'ocr llm' for usage", args[0])
}
}
@ -72,11 +72,11 @@ func printLLMUsage() {
fmt.Println(`LLM utility commands.
Usage:
argus llm <sub-command>
ocr llm <sub-command>
Sub-commands:
test Send a test conversation to the configured LLM model
Examples:
argus llm test Verify LLM connectivity and configuration`)
ocr llm test Verify LLM connectivity and configuration`)
}

View file

@ -1,4 +1,4 @@
// Argus is an AI-powered code review CLI tool.
// OpenCodeReview is an AI-powered code review CLI tool.
// It reads git diffs, sends them to a configurable LLM service, and generates review comments.
package main
@ -8,7 +8,7 @@ import (
"os"
"time"
"github.com/argus-review/argus/internal/telemetry"
"github.com/open-code-review/open-code-review/internal/telemetry"
)
func main() {
@ -50,15 +50,15 @@ func dispatch() error {
printTopLevelUsage()
return nil
default:
return fmt.Errorf("unknown command: %s\nRun 'argus' for usage", args[0])
return fmt.Errorf("unknown command: %s\nRun 'ocr' for usage", args[0])
}
}
func printTopLevelUsage() {
fmt.Println(`Argus - Code Review Agent CLI
fmt.Println(`OpenCodeReview - AI-Powered Code Review CLI
Usage:
argus [command]
ocr [command]
Commands:
review, r Start a code review
@ -67,13 +67,13 @@ Commands:
version Show version information
Examples:
argus review --from dev --to master Review diff range
argus review --commit abc123 Review a single commit
argus config set llm.model opus-4-6 Set a config value
argus llm test Test LLM connectivity
argus version Show version info
ocr review --from dev --to master Review diff range
ocr review --commit abc123 Review a single commit
ocr config set llm.model opus-4-6 Set a config value
ocr llm test Test LLM connectivity
ocr version Show version info
Use "argus review -h" for more information about review.
Use "argus config" for more information about config.
Use "argus llm" for more information about LLM utilities.`)
Use "ocr review -h" for more information about review.
Use "ocr config" for more information about config.
Use "ocr llm" for more information about LLM utilities.`)
}

View file

@ -5,7 +5,7 @@ import (
"fmt"
"os"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
func outputText(comments []model.LlmComment) {

View file

@ -7,13 +7,13 @@ import (
"path/filepath"
"time"
"github.com/argus-review/argus/internal/agent"
"github.com/argus-review/argus/internal/config/rules"
"github.com/argus-review/argus/internal/config/template"
"github.com/argus-review/argus/internal/config/toolsconfig"
"github.com/argus-review/argus/internal/llm"
"github.com/argus-review/argus/internal/telemetry"
"github.com/argus-review/argus/internal/tool"
"github.com/open-code-review/open-code-review/internal/agent"
"github.com/open-code-review/open-code-review/internal/config/rules"
"github.com/open-code-review/open-code-review/internal/config/template"
"github.com/open-code-review/open-code-review/internal/config/toolsconfig"
"github.com/open-code-review/open-code-review/internal/llm"
"github.com/open-code-review/open-code-review/internal/telemetry"
"github.com/open-code-review/open-code-review/internal/tool"
)
func runReview(args []string) error {
@ -62,7 +62,7 @@ func runReview(args []string) error {
return fmt.Errorf("load app config: %w", err)
}
if cfg == nil || cfg.Llm.URL == "" || cfg.Llm.AuthToken == "" {
return fmt.Errorf("llm.url and llm.auth_token are required in $HOME/.argus/config.json")
return fmt.Errorf("llm.url and llm.auth_token are required in $HOME/.open-code-review/config.json")
}
model := cfg.Llm.Model
tpl.ApplyLanguage(cfg.Language)

View file

@ -13,7 +13,7 @@ var (
)
func printVersion() {
fmt.Printf("argus %s", Version)
fmt.Printf("opencodereview %s", Version)
if GitCommit != "" {
fmt.Printf(" (%s)", GitCommit)
}

View file

@ -10,8 +10,8 @@ import (
"path/filepath"
"strings"
"github.com/argus-review/argus/internal/diff"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/diff"
"github.com/open-code-review/open-code-review/internal/model"
)
func main() {

1
dist/VERSION vendored Normal file
View file

@ -0,0 +1 @@
v0.0.1

BIN
dist/opencodereview vendored Executable file

Binary file not shown.

2
go.mod
View file

@ -1,4 +1,4 @@
module github.com/argus-review/argus
module github.com/open-code-review/open-code-review
go 1.25.0

View file

@ -9,15 +9,15 @@ import (
"sync/atomic"
"time"
"github.com/argus-review/argus/internal/config/rules"
"github.com/argus-review/argus/internal/config/template"
"github.com/argus-review/argus/internal/config/toolsconfig"
"github.com/argus-review/argus/internal/diff"
"github.com/argus-review/argus/internal/llm"
"github.com/argus-review/argus/internal/model"
"github.com/argus-review/argus/internal/session"
"github.com/argus-review/argus/internal/telemetry"
"github.com/argus-review/argus/internal/tool"
"github.com/open-code-review/open-code-review/internal/config/rules"
"github.com/open-code-review/open-code-review/internal/config/template"
"github.com/open-code-review/open-code-review/internal/config/toolsconfig"
"github.com/open-code-review/open-code-review/internal/diff"
"github.com/open-code-review/open-code-review/internal/llm"
"github.com/open-code-review/open-code-review/internal/model"
"github.com/open-code-review/open-code-review/internal/session"
"github.com/open-code-review/open-code-review/internal/telemetry"
"github.com/open-code-review/open-code-review/internal/tool"
)
// Args holds all dependencies and configuration needed to run a review session.
@ -175,7 +175,7 @@ func (a *Agent) Run(ctx context.Context) ([]model.LlmComment, error) {
diffSpan.End()
if len(a.diffs) == 0 {
fmt.Println("[argus] No files changed. Skipping review.")
fmt.Println("[ocr] No files changed. Skipping review.")
telemetry.Event(ctx, "no.files.changed")
a.session.Finalize()
return nil, nil
@ -183,7 +183,7 @@ func (a *Agent) Run(ctx context.Context) ([]model.LlmComment, error) {
a.currentDate = time.Now().Format("2006-01-02 15:04")
fmt.Printf("[argus] Reviewing %d file(s) in %s\n", len(a.diffs), a.args.RepoDir)
fmt.Printf("[ocr] Reviewing %d file(s) in %s\n", len(a.diffs), a.args.RepoDir)
telemetry.Event(ctx, "review.started",
telemetry.AnyToAttr("file.count", len(a.diffs)),
telemetry.AnyToAttr("repo.dir", a.args.RepoDir))
@ -295,7 +295,7 @@ func (a *Agent) dispatchSubtasks(ctx context.Context) ([]model.LlmComment, error
fileComments, err := a.executeSubtask(fileCtx, d)
if err != nil {
fmt.Printf("[argus] Subtask error for %s: %v\n", d.NewPath, err)
fmt.Printf("[ocr] Subtask error for %s: %v\n", d.NewPath, err)
telemetry.ErrorEvent(fileCtx, "subtask.error", err,
telemetry.AnyToAttr("file.path", d.NewPath))
}
@ -335,7 +335,7 @@ func (a *Agent) executeSubtask(ctx context.Context, d model.Diff) ([]model.LlmCo
// Phase 1: Plan (skip when changes are below threshold)
var planResult string
if a.args.Template.PlanTask != nil && len(a.args.Template.PlanTask.Messages) > 0 && threshold > 0 && changeLines < int64(threshold) {
fmt.Printf("[argus] Skipping plan phase for %s (%d lines < threshold %d)\n", newPath, changeLines, threshold)
fmt.Printf("[ocr] Skipping plan phase for %s (%d lines < threshold %d)\n", newPath, changeLines, threshold)
telemetry.Event(ctx, "plan.skipped",
telemetry.AnyToAttr("file.path", newPath),
telemetry.AnyToAttr("lines.changed", changeLines),
@ -344,7 +344,7 @@ func (a *Agent) executeSubtask(ctx context.Context, d model.Diff) ([]model.LlmCo
var err error
planResult, err = a.executePlanPhase(ctx, newPath, d.Diff, changeFilesExcludingCurrent, rule)
if err != nil {
fmt.Printf("[argus] Plan phase failed for %s: %v (continuing without plan)\n", newPath, err)
fmt.Printf("[ocr] Plan phase failed for %s: %v (continuing without plan)\n", newPath, err)
telemetry.Eventf(ctx, "plan.failed", err.Error(),
telemetry.AnyToAttr("file.path", newPath))
planResult = ""
@ -375,7 +375,7 @@ func (a *Agent) executeSubtask(ctx context.Context, d model.Diff) ([]model.LlmCo
tokenCount := countMessagesTokens(messages)
if tokenCount > a.args.Template.TokenWarningThreshold {
fmt.Printf("[argus] WARNING: prompt tokens (%d) exceed threshold (%d) for %s\n",
fmt.Printf("[ocr] WARNING: prompt tokens (%d) exceed threshold (%d) for %s\n",
tokenCount, a.args.Template.TokenWarningThreshold, newPath)
telemetry.Event(ctx, "token.threshold.exceeded",
telemetry.AnyToAttr("file.path", newPath),
@ -454,7 +454,7 @@ func (a *Agent) executePlanPhase(_ context.Context, newPath, rawDiff, changeFile
if resp.Usage != nil {
atomic.AddInt64(&a.totalTokensUsed, int64(resp.Usage.TotalTokens))
}
fmt.Printf("[argus] Plan completed for %s\n", newPath)
fmt.Printf("[ocr] Plan completed for %s\n", newPath)
return resp.Content(), nil
}
@ -540,7 +540,7 @@ func (a *Agent) performLlmCodeReview(ctx context.Context, messages []llm.Message
if len(calls) == 0 {
// No tool calls - remind the model
fmt.Printf("[argus] No tool calls parsed for %s, retrying...\n", newPath)
fmt.Printf("[ocr] No tool calls parsed for %s, retrying...\n", newPath)
messages = append(messages, llm.NewTextMessage("user", "You did not successfully call any tools. Please try again or use task_done if finished."))
if content != "" {
messages = append(messages[:len(messages)-1], llm.NewTextMessage("assistant", content), messages[len(messages)-1])
@ -581,19 +581,19 @@ func (a *Agent) performLlmCodeReview(ctx context.Context, messages []llm.Message
break
}
if !hasValidResult {
fmt.Printf("[argus] No valid tool results for %s, stopping.\n", newPath)
fmt.Printf("[ocr] No valid tool results for %s, stopping.\n", newPath)
break
}
succeed := a.addNextMessage(content, calls, results, &messages, newPath)
if !succeed {
fmt.Printf("[argus] Context compression exceeded threshold for %s, stopping.\n", newPath)
fmt.Printf("[ocr] Context compression exceeded threshold for %s, stopping.\n", newPath)
break
}
}
if toolReqCount <= 0 {
fmt.Printf("[argus] Max tool requests reached for %s.\n", newPath)
fmt.Printf("[ocr] Max tool requests reached for %s.\n", newPath)
}
comments := a.args.CommentCollector.Comments()
@ -720,7 +720,7 @@ func BuildToolDefs(entries []toolsconfig.ToolConfigEntry, planOnly bool) []llm.T
}
var fn llm.FunctionDef
if err := json.Unmarshal(defRaw, &fn); err != nil {
fmt.Printf("[argus] WARNING: failed to parse tool definition %q: %v\n", e.Name, err)
fmt.Printf("[ocr] WARNING: failed to parse tool definition %q: %v\n", e.Name, err)
continue
}
defs = append(defs, llm.ToolDef{
@ -752,7 +752,7 @@ func (a *Agent) compressAndRecord(msgs []llm.Message, filePath string) []llm.Mes
rec := fs.AppendTaskRecord(session.MemoryCompressionTask, compressionMsgs)
if err != nil {
rec.SetError(err, duration)
fmt.Printf("[argus] Memory compression failed: %v\n", err)
fmt.Printf("[ocr] Memory compression failed: %v\n", err)
return msgs[:2]
}
rec.SetResponse(resp, duration)
@ -788,7 +788,7 @@ func compressMessages(msgs []llm.Message, compTask template.LlmConversation, cli
resp, err := client.GeneralRequest(compressionMsgs, model, nil)
if err != nil {
fmt.Printf("[argus] Memory compression failed: %v\n", err)
fmt.Printf("[ocr] Memory compression failed: %v\n", err)
return msgs[:2]
}

View file

@ -8,7 +8,7 @@ import (
"path/filepath"
"strings"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
// DiffContextLines defines the number of context lines around each changed hunk.

View file

@ -7,7 +7,7 @@ import (
"regexp"
"strings"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
var (

View file

@ -3,7 +3,7 @@ package diff
import (
"strings"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
// ResolveLineNumbers populates StartLine/EndLine on each comment by matching

View file

@ -3,7 +3,7 @@ package diff
import (
"testing"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
const testDiff = `diff --git a/pkg/example/handler.go b/pkg/example/handler.go

View file

@ -1,5 +1,5 @@
// Package llm provides an OpenAI-compatible LLM client interface.
// Argus supports any service that implements the OpenAI Chat Completion API schema,
// OpenCodeReview supports any service that implements the OpenAI Chat Completion API schema,
// including OpenAI, Claude (via Anthropic's OpenAI-compatible endpoint), local models, etc.
package llm

View file

@ -11,7 +11,7 @@ import (
"sync"
"time"
"github.com/argus-review/argus/internal/llm"
"github.com/open-code-review/open-code-review/internal/llm"
)
// TaskType identifies the kind of LLM request within a file subtask.
@ -176,20 +176,20 @@ func (sh *SessionHistory) writeDebugDump() {
data, err := json.MarshalIndent(snap, "", " ")
if err != nil {
fmt.Printf("[argus debug] Failed to marshal session history: %v\n", err)
fmt.Printf("[ocr debug] Failed to marshal session history: %v\n", err)
return
}
debugDir := filepath.Join(sh.RepoDir, "temp")
if err := os.MkdirAll(debugDir, 0755); err != nil {
fmt.Printf("[argus debug] Failed to create debug dir %s: %v\n", debugDir, err)
fmt.Printf("[ocr debug] Failed to create debug dir %s: %v\n", debugDir, err)
return
}
filename := filepath.Join(debugDir, fmt.Sprintf("argus-session-%s.json", sessionName))
filename := filepath.Join(debugDir, fmt.Sprintf("ocr-session-%s.json", sessionName))
if err := os.WriteFile(filename, data, 0644); err != nil {
fmt.Printf("[argus debug] Failed to write session dump to %s: %v\n", filename, err)
fmt.Printf("[ocr debug] Failed to write session dump to %s: %v\n", filename, err)
} else {
fmt.Printf("[argus debug] Session history written to %s\n", filename)
fmt.Printf("[ocr debug] Session history written to %s\n", filename)
}
}

View file

@ -1,4 +1,4 @@
// Package telemetry provides OpenTelemetry-based observability for Argus CLI.
// Package telemetry provides OpenTelemetry-based observability for OpenCodeReview CLI.
// It supports console output (for personal use) and OTLP export (for system integration).
package telemetry
@ -9,7 +9,7 @@ import (
)
const (
defaultServiceName = "argus"
defaultServiceName = "open-code-review"
defaultOTLPEndpoint = ""
defaultExporter = "console"
defaultContentLogging = false
@ -17,7 +17,7 @@ const (
// Config holds resolved telemetry configuration.
type Config struct {
Enabled bool // Master switch; false when ARGUS_ENABLE_TELEMETRY is unset
Enabled bool // Master switch; false when OCR_ENABLE_TELEMETRY is unset
ServiceName string // Service name in traces/metrics
Exporter string // "console" or "otlp"
OTLPEndpoint string // OTLP collector address (grpc/http)
@ -40,7 +40,7 @@ func DefaultConfig() Config {
// resolveEnv reads environment variables to override defaults.
// Environment takes highest priority.
func resolveEnv(cfg *Config) {
if os.Getenv("ARGUS_ENABLE_TELEMETRY") == "1" {
if os.Getenv("OCR_ENABLE_TELEMETRY") == "1" {
cfg.Enabled = true
}
if v := os.Getenv("OTEL_SERVICE_NAME"); v != "" {
@ -53,12 +53,12 @@ func resolveEnv(cfg *Config) {
if v := os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL"); v != "" {
cfg.OTLPProtocol = v
}
if os.Getenv("ARGUS_CONTENT_LOGGING") == "1" {
if os.Getenv("OCR_CONTENT_LOGGING") == "1" {
cfg.ContentLog = true
}
}
// telemetrySection matches the telemetry key in ~/.argus/config.json.
// telemetrySection matches the telemetry key in ~/.open-code-review/config.json.
type telemetrySection struct {
Enabled *bool `json:"enabled,omitempty"`
Exporter *string `json:"exporter,omitempty"`
@ -121,11 +121,11 @@ func ResolveConfig(configPath string) Config {
return cfg
}
// HomeConfigPath returns the default path to ~/.argus/config.json.
// HomeConfigPath returns the default path to ~/.open-code-review/config.json.
func HomeConfigPath() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".argus", "config.json")
return filepath.Join(home, ".open-code-review", "config.json")
}

View file

@ -61,32 +61,32 @@ func FormatDuration(dur time.Duration) string {
// PrintTraceSummary prints a one-line summary of the review to stdout.
func PrintTraceSummary(filesReviewed, commentsGenerated int64, totalTokens int64, duration time.Duration) {
fmt.Printf("[argus] Summary: %d file(s) reviewed, %d comment(s), ~%d token(s) used, %s elapsed\n",
fmt.Printf("[ocr] Summary: %d file(s) reviewed, %d comment(s), ~%d token(s) used, %s elapsed\n",
filesReviewed, commentsGenerated, totalTokens, FormatDuration(duration))
}
// PrintToolCallStarted prints a line when a tool begins execution.
// Args are summarized as key-value pairs (path, search terms, etc.).
// Example: [argus] ▶ file_read "internal/config/rules/loader.go"
// Example: [ocr] ▶ file_read "internal/config/rules/loader.go"
func PrintToolCallStarted(toolName string, args map[string]any) {
summary := summarizeArgs(args)
if summary != "" {
fmt.Printf("[argus] ▶ %s %s\n", toolName, summary)
fmt.Printf("[ocr] ▶ %s %s\n", toolName, summary)
} else {
fmt.Printf("[argus] ▶ %s\n", toolName)
fmt.Printf("[ocr] ▶ %s\n", toolName)
}
}
// PrintToolCallFinished prints a line when a tool finishes successfully.
// Example: [argus] ✔ file_read "internal/config/rules/loader.go" (12ms)
// Example: [ocr] ✔ file_read "internal/config/rules/loader.go" (12ms)
func PrintToolCallFinished(toolName string, dur time.Duration) {
fmt.Printf("[argus] ✔ %s (%s)\n", toolName, FormatDuration(dur))
fmt.Printf("[ocr] ✔ %s (%s)\n", toolName, FormatDuration(dur))
}
// PrintToolCallError prints a line when a tool fails.
// Example: [argus] ✘ file_read "internal/config/rules/loader.go" failed: permission denied
// Example: [ocr] ✘ file_read "internal/config/rules/loader.go" failed: permission denied
func PrintToolCallError(toolName string, err error) {
fmt.Fprintf(os.Stderr, "[argus] ✘ %s failed: %v\n", toolName, err)
fmt.Fprintf(os.Stderr, "[ocr] ✘ %s failed: %v\n", toolName, err)
}
// summarizeArgs extracts a concise key=value summary from tool arguments for console display.

View file

@ -31,7 +31,7 @@ func initOTLPProviders(ctx context.Context, res *resource.Resource, cfg Config)
otlptracegrpc.WithEndpoint(cfg.OTLPEndpoint),
)
if err != nil {
fmt.Fprintf(os.Stderr, "[argus] WARNING: failed to create OTLP trace exporter: %v\n", err)
fmt.Fprintf(os.Stderr, "[ocr] WARNING: failed to create OTLP trace exporter: %v\n", err)
return
}
@ -46,7 +46,7 @@ func initOTLPProviders(ctx context.Context, res *resource.Resource, cfg Config)
otlpmetricgrpc.WithEndpoint(cfg.OTLPEndpoint),
)
if err != nil {
fmt.Fprintf(os.Stderr, "[argus] WARNING: failed to create OTLP metric exporter: %v\n", err)
fmt.Fprintf(os.Stderr, "[ocr] WARNING: failed to create OTLP metric exporter: %v\n", err)
return
}
@ -62,7 +62,7 @@ func initOTLPProviders(ctx context.Context, res *resource.Resource, cfg Config)
func initConsoleProviders(res *resource.Resource) {
traceExp, err := newStdoutTraceExporter()
if err != nil {
fmt.Fprintf(os.Stderr, "[argus] WARNING: failed to create console trace exporter: %v\n", err)
fmt.Fprintf(os.Stderr, "[ocr] WARNING: failed to create console trace exporter: %v\n", err)
return
}
@ -75,7 +75,7 @@ func initConsoleProviders(res *resource.Resource) {
metricExp, err := newStdoutMetricExporter()
if err != nil {
fmt.Fprintf(os.Stderr, "[argus] WARNING: failed to create console metric exporter: %v\n", err)
fmt.Fprintf(os.Stderr, "[ocr] WARNING: failed to create console metric exporter: %v\n", err)
return
}

View file

@ -34,35 +34,35 @@ func ensureMetrics() {
m := getMeter()
var err error
mReviewDuration, err = m.Int64Histogram("argus.review.duration_seconds",
mReviewDuration, err = m.Int64Histogram("ocr.review.duration_seconds",
metric.WithUnit("s"), metric.WithDescription("Total duration of a code review run"))
checkMetricErr(err)
mFilesReviewed, err = m.Int64Counter("argus.files_reviewed_total",
mFilesReviewed, err = m.Int64Counter("ocr.files_reviewed_total",
metric.WithDescription("Number of files reviewed in this session"))
checkMetricErr(err)
mCommentsGenerated, err = m.Int64Counter("argus.comments_generated_total",
mCommentsGenerated, err = m.Int64Counter("ocr.comments_generated_total",
metric.WithDescription("Number of review comments generated"))
checkMetricErr(err)
mLLMRequests, err = m.Int64Counter("argus.llm.requests_total",
mLLMRequests, err = m.Int64Counter("ocr.llm.requests_total",
metric.WithDescription("Total LLM API requests made"))
checkMetricErr(err)
mLLMTokens, err = m.Int64Counter("argus.llm.tokens_used",
mLLMTokens, err = m.Int64Counter("ocr.llm.tokens_used",
metric.WithDescription("Tokens consumed by LLM requests"))
checkMetricErr(err)
mLLMDuration, err = m.Float64Histogram("argus.llm.request_duration_seconds",
mLLMDuration, err = m.Float64Histogram("ocr.llm.request_duration_seconds",
metric.WithUnit("s"), metric.WithDescription("Duration of individual LLM API requests"))
checkMetricErr(err)
mToolCalls, err = m.Int64Counter("argus.tool.calls_total",
mToolCalls, err = m.Int64Counter("ocr.tool.calls_total",
metric.WithDescription("Total tool calls made"))
checkMetricErr(err)
mToolExecutionTime, err = m.Float64Histogram("argus.tool.execution_duration_seconds",
mToolExecutionTime, err = m.Float64Histogram("ocr.tool.execution_duration_seconds",
metric.WithUnit("s"), metric.WithDescription("Duration of tool executions"))
checkMetricErr(err)
}

View file

@ -20,7 +20,7 @@ var (
)
// serviceName holds the name set during Init.
var serviceName = "argus"
var serviceName = "open-code-review"
// Init initializes global TracerProvider and MeterProvider based on
// environment variables and optional config file. Returns true when enabled.
@ -44,7 +44,7 @@ func Init(ctx context.Context) bool {
resource.WithHost(),
)
if err != nil {
fmt.Fprintf(os.Stderr, "[argus] WARNING: failed to create OTel resource: %v\n", err)
fmt.Fprintf(os.Stderr, "[ocr] WARNING: failed to create OTel resource: %v\n", err)
res = resource.Default()
}
@ -66,7 +66,7 @@ func IsEnabled() bool {
return initialized && len(shutdownFuncs) > 0
}
// ContentLogging returns true when content logging is enabled via ARGUS_CONTENT_LOGGING.
// ContentLogging returns true when content logging is enabled via OCR_CONTENT_LOGGING.
func ContentLogging() bool {
if !IsEnabled() {
return false

View file

@ -34,6 +34,6 @@ func ShutdownWithTimeout(ctx context.Context, timeout time.Duration) {
cctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
if err := Shutdown(cctx); err != nil {
fmt.Fprintf(os.Stderr, "[argus] WARNING: telemetry shutdown error: %v\n", err)
fmt.Fprintf(os.Stderr, "[ocr] WARNING: telemetry shutdown error: %v\n", err)
}
}

View file

@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
// CodeCommentProvider submits review comments to the per-Agent CommentCollector.

View file

@ -3,7 +3,7 @@ package tool
import (
"sync"
"github.com/argus-review/argus/internal/model"
"github.com/open-code-review/open-code-review/internal/model"
)
// CommentCollector is a thread-safe, per-Agent comment store.

198
scripts/install.sh Executable file
View file

@ -0,0 +1,198 @@
#!/usr/bin/env sh
# install.sh — Install or upgrade argus CLI
#
# Usage (pipelined):
# curl -fsSL https://git.cn-hangzhou.oss-cdn.aliyun-inc.com/opencodereview-cli/install.sh | sh
# OCR_VERSION=v0.1.0 curl -fsSL ... | sh
#
# Usage (standalone):
# sh install.sh
# OCR_VERSION=v0.1.0 sh install.sh
#
# Environment variables:
# OCR_VERSION Pin a specific version (e.g., v0.1.0). Defaults to "latest".
# INSTALL_DIR Override install destination (default: /usr/local/bin).
set -eu
# ── Configuration ────────────────────────────────────────────────────────────
BASE_URL="https://code.alibaba-inc.com/lizhengfeng.lzf/opencodereview-cli/raw/master/dist"
BINARY_NAME="opencodereview"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
# ── Logging ──────────────────────────────────────────────────────────────────
info() { printf "[INFO] %s\n" "$*" >&2; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
error() { printf "[ERROR] %s\n" "$*" >&2; }
die() { error "$@"; exit 1; }
# ── Cleanup trap ─────────────────────────────────────────────────────────────
TMPDIR_WORK=""
cleanup() {
if [ -n "$TMPDIR_WORK" ] && [ -d "$TMPDIR_WORK" ]; then
rm -rf "$TMPDIR_WORK"
fi
}
trap cleanup EXIT INT TERM
# ── Detect OS & Arch ─────────────────────────────────────────────────────────
detect_platform() {
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64|amd64|x64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
*) die "Unsupported architecture: $ARCH (need amd64 or arm64)" ;;
esac
case "$OS" in
linux|darwin) ;;
*) die "Unsupported operating system: $OS (need linux or darwin)" ;;
esac
info "Detected platform: ${OS}/${ARCH}"
}
# ── Resolve version ──────────────────────────────────────────────────────────
resolve_version() {
if [ -n "${OCR_VERSION:-}" ]; then
VERSION="$OCR_VERSION"
VERSION="v$(echo "$VERSION" | sed 's/^v//')"
info "Using pinned version: ${VERSION}"
return
fi
# Try fetching latest version from remote
info "Fetching latest version..."
VERSION=$(curl -fsSL --retry 3 --retry-delay 2 "${BASE_URL}/VERSION" 2>/dev/null || true)
if [ -n "$VERSION" ]; then
info "Latest version: ${VERSION}"
return
fi
# Fallback: detect locally built binary in dist/
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." 2>/dev/null && pwd || true)"
if [ -n "$PROJECT_ROOT" ] && [ -d "${PROJECT_ROOT}/dist" ]; then
LOCAL_BIN=""
# Prefer platform-specific binary, fall back to generic one
for pattern in \
"${BINARY_NAME}-${VERSION:-*}-${OS}-${ARCH}" \
"${BINARY_NAME}-${VERSION:-*}-${OS}-*" \
"${BINARY_NAME}"; do
LOCAL_BIN=$(ls "${PROJECT_ROOT}/dist/${pattern}" 2>/dev/null | head -1 || true)
if [ -n "$LOCAL_BIN" ] && [ -f "$LOCAL_BIN" ]; then
break
fi
done
if [ -n "$LOCAL_BIN" ] && [ -f "$LOCAL_BIN" ]; then
VERSION=$("$LOCAL_BIN" version 2>/dev/null | awk '{print $NF}' || echo "local")
VERSION="v$(echo "$VERSION" | sed 's/^v//')"
LOCAL_BINARY="$LOCAL_BIN"
warn "Remote unavailable, using local build: ${VERSION}"
warn " Source: ${LOCAL_BINARY}"
return
fi
fi
die "Cannot determine version. Set OCR_VERSION explicitly, or build locally with 'make build'."
}
# ── Download / locate binary ─────────────────────────────────────────────────
locate_or_download_binary() {
# If a local binary was already resolved during version detection, use it directly
if [ -n "${LOCAL_BINARY:-}" ]; then
DEST="$LOCAL_BINARY"
info "Using local binary: ${DEST}"
return
fi
BINARY_FILE="${BINARY_NAME}-${VERSION}-${OS}-${ARCH}"
DOWNLOAD_URL="${BASE_URL}/${BINARY_FILE}"
TMPDIR_WORK=$(mktemp -d)
DEST="${TMPDIR_WORK}/${BINARY_NAME}"
info "Downloading ${DOWNLOAD_URL} ..."
if ! curl -fsSL --retry 3 --retry-delay 2 -o "$DEST" "$DOWNLOAD_URL"; then
die "Download failed. Check that version '${VERSION}' exists for ${OS}/${ARCH}."
fi
chmod +x "$DEST"
# Verify checksum if available
CHECKSUM_URL="${BASE_URL}/sha256sum-${VERSION}.txt"
EXPECTED_CHECKSUM=$(curl -fsSL --retry 2 "$CHECKSUM_URL" 2>/dev/null | grep "$BINARY_FILE" | awk '{print $1}' || true)
if [ -n "$EXPECTED_CHECKSUM" ]; then
ACTUAL_CHECKSUM=$(shasum -a 256 "$DEST" | awk '{print $1}')
if [ "$ACTUAL_CHECKSUM" != "$EXPECTED_CHECKSUM" ]; then
die "Checksum mismatch! Expected: ${EXPECTED_CHECKSUM}, Got: ${ACTUAL_CHECKSUM}"
fi
info "Checksum verified."
else
warn "Checksum file not found at ${CHECKSUM_URL}; skipping integrity check."
fi
}
# ── Install ──────────────────────────────────────────────────────────────────
install_binary() {
TARGET="${INSTALL_DIR}/${BINARY_NAME}"
# Ensure install directory exists
if [ ! -d "$INSTALL_DIR" ]; then
if command -v sudo >/dev/null 2>&1; then
info "Creating install directory: ${INSTALL_DIR}"
sudo mkdir -p "$INSTALL_DIR"
else
die "Install directory does not exist and sudo is unavailable: ${INSTALL_DIR}"
fi
fi
if [ -f "$TARGET" ]; then
CURRENT_VER=$("$TARGET" version 2>/dev/null | head -1 || echo "unknown")
warn "Existing installation found (${CURRENT_VER}), replacing with ${VERSION}."
fi
if [ ! -w "$INSTALL_DIR" ] && ! [ "$(id -u)" = "0" ]; then
info "Installing to ${INSTALL_DIR} (sudo required)..."
sudo cp "$DEST" "$TARGET"
else
cp "$DEST" "$TARGET"
fi
info "Installed: ${TARGET}"
}
# ── Verify installation ──────────────────────────────────────────────────────
verify_install() {
if command -v "${INSTALL_DIR}/${BINARY_NAME}" >/dev/null 2>&1; then
INSTALLED_VER=$("${INSTALL_DIR}/${BINARY_NAME}" version 2>/dev/null || echo "?")
info ""
info "OpenCodeReview ${INSTALLED_VER} is ready!"
info ""
info "Quick start:"
info " ocr version Show version info"
info " ocr config set Configure your LLM provider"
info " ocr review Start a code review"
else
warn ""
warn "Installation completed but '${BINARY_NAME}' is not on PATH."
warn "Add ${INSTALL_DIR} to your PATH, or run directly:"
warn " ${TARGET} version"
fi
}
# ── Main ─────────────────────────────────────────────────────────────────────
main() {
info "OpenCodeReview CLI Installer"
info "==================="
detect_platform
resolve_version
locate_or_download_binary
install_binary
verify_install
}
main

148
scripts/release.sh Executable file
View file

@ -0,0 +1,148 @@
#!/usr/bin/env bash
#
# release.sh — Build all platform binaries and push to Alibaba Cloud OSS
#
# Usage:
# ./scripts/release.sh # Builds using latest git tag as version
# ./scripts/release.sh v0.1.0 # Explicit version tag
#
# Prerequisites:
# - ossutil configured (run: ossutil config)
# - Git tag exists for the version (or pass version explicitly)
# - Go 1.25+ installed
#
# Environment variables:
# OSS_ENDPOINT OSS endpoint URL (default: oss-cn-hangzhou.aliyuncs.com)
# OSS_BUCKET Bucket name override
# OSS_PREFIX Path prefix within bucket (default: opencodereview-cli)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ENDPOINT="${OSS_ENDPOINT:-oss-cn-hangzhou.aliyuncs.com}"
BUCKET="${OSS_BUCKET:-git.cn-hangzhou}"
PREFIX="${OSS_PREFIX:-opencodereview-cli}"
OSS_PATH="oss://${BUCKET}/${PREFIX}"
info() { echo "[INFO] $*"; }
warn() { echo "[WARN] $*"; }
error() { echo "[ERROR] $*"; }
die() { error "$*"; exit 1; }
VERSION="${1:-}"
if [ -z "$VERSION" ]; then
VERSION=$(git -C "$PROJECT_ROOT" describe --tags --abbrev=0 2>/dev/null || true)
if [ -z "$VERSION" ]; then
die "No git tag found. Pass a version explicitly or create a tag first."
fi
info "Using latest git tag: ${VERSION}"
else
[[ "$VERSION" != v* ]] && VERSION="v${VERSION}"
fi
info "=== OpenCodeReview Release: ${VERSION} ==="
# ── Pre-flight checks ────────────────────────────────────────────────────────
check_prereq() {
command -v "$1" >/dev/null 2>&1 || die "'$1' is required but not installed."
}
check_prereq go
check_prereq ossutil
cd "$PROJECT_ROOT"
if [ -n "$(git status --porcelain)" ]; then
warn "Working tree has uncommitted changes. Proceeding anyway..."
fi
# ── Build all platforms ──────────────────────────────────────────────────────
DIST_DIR="${PROJECT_ROOT}/dist"
mkdir -p "$DIST_DIR"
rm -f "${DIST_DIR}/opencodereview-${VERSION}-"*
GIT_COMMIT="$(git rev-parse --short HEAD)"
BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
LD_FLAGS="-s -w -X main.Version=${VERSION} -X main.GitCommit=${GIT_COMMIT} -X main.BuildDate=${BUILD_DATE}"
TARGETS=(
"linux/amd64"
"linux/arm64"
"darwin/amd64"
"darwin/arm64"
)
for pair in "${TARGETS[@]}"; do
GOOS="${pair%/*}"
GOARCH="${pair#*/}"
OS_ARCH="${GOOS}-${GOARCH}"
OUTPUT_NAME="opencodereview-${VERSION}-${OS_ARCH}"
info "Building ${GOOS}/${GOARCH}${OUTPUT_NAME}"
CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" \
go build -ldflags "${LD_FLAGS}" \
-o "${DIST_DIR}/${OUTPUT_NAME}" \
./cmd/opencodereview
done
# ── Generate per-version checksum file ───────────────────────────────────────
CHECKSUM_FILE="${DIST_DIR}/sha256sum-${VERSION}.txt"
info "Generating checksums: sha256sum-${VERSION}.txt"
(cd "$DIST_DIR" && shasum -a 256 opencodereview-"${VERSION}"-* | sort > "sha256sum-${VERSION}.txt")
info "Checksum contents:"
cat "$CHECKSUM_FILE" | while read -r line; do echo " $line"; done
# ── Upload to OSS ────────────────────────────────────────────────────────────
info ""
info "Uploading to ${OSS_PATH} ..."
for f in "${DIST_DIR}"/opencodereview-"${VERSION}"-*; do
BASENAME="$(basename "$f")"
[[ "$BASENAME" == sha256sum-* ]] && continue
info " Uploading ${BASENAME} ..."
ossutil cp "$f" "${OSS_PATH}/${BASENAME}" \
--endpoint "$ENDPOINT" \
--acl public-read \
--meta "Cache-Control:max-age=31536000"
done
info " Uploading sha256sum-${VERSION}.txt ..."
ossutil cp "$CHECKSUM_FILE" "${OSS_PATH}/sha256sum-${VERSION}.txt" \
--endpoint "$ENDPOINT" \
--acl public-read
info " Uploading install.sh ..."
ossutil cp "${SCRIPT_DIR}/install.sh" "${OSS_PATH}/install.sh" \
--endpoint "$ENDPOINT" \
--acl public-read \
--meta "Content-Type:text/x-sh"
echo -n "$VERSION" > "${DIST_DIR}/VERSION"
info " Uploading VERSION sentinel ..."
ossutil cp "${DIST_DIR}/VERSION" "${OSS_PATH}/VERSION" \
--endpoint "$ENDPOINT" \
--acl public-read \
--meta "Cache-Control:no-cache"
# ── Summary ──────────────────────────────────────────────────────────────────
CDN_URL="https://${BUCKET}.oss-cdn.aliyuncs.com/${PREFIX}"
info ""
info "=== Release ${VERSION} published ==="
info ""
info "Install (latest):"
info " curl -fsSL ${CDN_URL}/install.sh | sh"
info ""
info "Pinned install:"
info " OCR_VERSION=${VERSION} curl -fsSL ${CDN_URL}/install.sh | sh"
info ""
info "Direct downloads:"
for pair in "${TARGETS[@]}"; do
GOOS="${pair%/*}"
GOARCH="${pair#*/}"
info " ${CDN_URL}/opencodereview-${VERSION}-${GOOS}-${GOARCH}"
done