This commit is contained in:
2026-06-23 05:02:15 +08:00
commit e6f1776d4f
264 changed files with 54215 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
package cc
import (
"bufio"
"bytes"
"context"
"encoding/json"
"log/slog"
"os/exec"
"regexp"
"time"
)
const reviewDefaultTimeoutMs = 300_000
// ReviewOpts configures a RunReview call.
type ReviewOpts struct {
WorkspaceRoot string
Prompt string
TimeoutMs int // 0 → reviewDefaultTimeoutMs
OnThreadID func(string)
}
// RunCodex is the injectable runner. Tests replace this to avoid needing a real codex binary.
var RunCodex func(ctx context.Context, cwd string, args []string) (stdout, stderr []byte, err error) = defaultRunCodex
func defaultRunCodex(ctx context.Context, cwd string, args []string) ([]byte, []byte, error) {
cmd := exec.CommandContext(ctx, "codex", args...)
if cwd != "" {
cmd.Dir = cwd
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return stdout.Bytes(), stderr.Bytes(), err
}
// RunReview runs `codex exec review --json` and calls opts.OnThreadID with the
// first thread_id/session_id found in the NDJSON output.
// Errors are logged, not returned (fire-and-forget semantics).
func RunReview(ctx context.Context, opts ReviewOpts) error {
timeoutMs := opts.TimeoutMs
if timeoutMs <= 0 {
timeoutMs = reviewDefaultTimeoutMs
}
tCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutMs)*time.Millisecond)
defer cancel()
args := []string{
"exec",
"-c", "model_reasoning_effort=xhigh",
"review",
"--dangerously-bypass-approvals-and-sandbox",
"--json",
opts.Prompt,
}
stdout, stderr, err := RunCodex(tCtx, opts.WorkspaceRoot, args)
if err != nil {
slog.Warn("codex exec review failed", "err", err, "stderr", string(stderr))
return nil // fire-and-forget: never propagate
}
if opts.OnThreadID != nil {
scanForThreadID(stdout, opts.OnThreadID)
}
return nil
}
type threadEvent struct {
ThreadID string `json:"thread_id"`
SessionID string `json:"session_id"`
ID string `json:"id"`
}
var uuidRe = regexp.MustCompile(`(?i)^[0-9a-f-]{16,}$`)
func scanForThreadID(ndjson []byte, onThreadID func(string)) {
scanner := bufio.NewScanner(bytes.NewReader(ndjson))
for scanner.Scan() {
line := scanner.Bytes()
if len(bytes.TrimSpace(line)) == 0 {
continue
}
var ev threadEvent
if err := json.Unmarshal(line, &ev); err != nil {
continue
}
id := ""
switch {
case ev.ThreadID != "":
id = ev.ThreadID
case ev.SessionID != "":
id = ev.SessionID
case uuidRe.MatchString(ev.ID):
id = ev.ID
}
if id != "" {
onThreadID(id)
return
}
}
}