Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: ./test.sh
- uses: actions/setup-go@v6
with:
go-version-file: autosolve/go.mod
- name: Run shell tests
run: ./test.sh
- name: Run Go tests
run: cd autosolve && go test ./... -count=1
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Breaking changes are prefixed with "Breaking Change: ".

### Added

- `autosolve/assess` action: evaluate tasks for automated resolution suitability
using Claude in read-only mode.
- `autosolve/implement` action: autonomously implement solutions, validate
security, push to fork, and create PRs using Claude. Includes AI security
review, token usage tracking, and per-file batched diff analysis.
- `get-workflow-ref` action: resolve the ref a caller used to invoke a reusable
workflow by parsing the caller's workflow file — no API calls or extra
permissions needed.
Expand Down
11 changes: 11 additions & 0 deletions autosolve/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PHONY: build test clean

# Local dev binary
build:
go build -o autosolve ./cmd/autosolve

test:
go test ./... -count=1

clean:
rm -f autosolve
87 changes: 87 additions & 0 deletions autosolve/assess/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Autosolve Assess
description: Run Claude in read-only mode to assess whether a task is suitable for automated resolution.

inputs:
claude_cli_version:
description: "Claude CLI version to install (e.g. '2.1.79' or 'latest')."
required: false
default: "2.1.79"
prompt:
description: The task to assess. Plain text instructions describing what needs to be done.
required: false
default: ""
skill:
description: Path to a skill/prompt file relative to the repo root.
required: false
default: ""
additional_instructions:
description: Extra context appended after the task prompt but before the assessment footer.
required: false
default: ""
assessment_criteria:
description: Custom criteria for the assessment. If not provided, uses default criteria.
required: false
default: ""
model:
description: Claude model ID.
required: false
default: "claude-opus-4-6"
blocked_paths:
description: Comma-separated path prefixes that cannot be modified (injected into security preamble).
required: false
default: ".github/workflows/"
working_directory:
description: Directory to run in (relative to workspace root). Defaults to workspace root.
required: false
default: "."

outputs:
assessment:
description: PROCEED or SKIP
value: ${{ steps.assess.outputs.assessment }}
summary:
description: Human-readable assessment reasoning.
value: ${{ steps.assess.outputs.summary }}
result:
description: Full Claude result text.
value: ${{ steps.assess.outputs.result }}

runs:
using: "composite"
steps:
- name: Set up Claude CLI
shell: bash
run: |
if command -v roachdev >/dev/null; then
printf '#!/bin/sh\nexec roachdev claude -- "$@"\n' > /usr/local/bin/claude
chmod +x /usr/local/bin/claude
echo "Claude CLI: using roachdev wrapper"
else
curl --fail --silent --show-error --location https://claude.ai/install.sh | bash -s -- "$CLAUDE_CLI_VERSION"
echo "Claude CLI installed: $(claude --version)"
fi
env:
CLAUDE_CLI_VERSION: ${{ inputs.claude_cli_version }}

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: ${{ github.action_path }}/../go.mod

- name: Build autosolve
shell: bash
run: go build -trimpath -o "$RUNNER_TEMP/autosolve" ./cmd/autosolve
working-directory: ${{ github.action_path }}/..

- name: Run assessment
id: assess
shell: bash
working-directory: ${{ inputs.working_directory }}
run: "$RUNNER_TEMP/autosolve" assess
env:
INPUT_PROMPT: ${{ inputs.prompt }}
INPUT_SKILL: ${{ inputs.skill }}
INPUT_ADDITIONAL_INSTRUCTIONS: ${{ inputs.additional_instructions }}
INPUT_ASSESSMENT_CRITERIA: ${{ inputs.assessment_criteria }}
INPUT_MODEL: ${{ inputs.model }}
INPUT_BLOCKED_PATHS: ${{ inputs.blocked_paths }}
107 changes: 107 additions & 0 deletions autosolve/cmd/autosolve/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"context"
"fmt"
"os"
"os/signal"

"github.com/cockroachdb/actions/autosolve/internal/action"
"github.com/cockroachdb/actions/autosolve/internal/assess"
"github.com/cockroachdb/actions/autosolve/internal/claude"
"github.com/cockroachdb/actions/autosolve/internal/config"
"github.com/cockroachdb/actions/autosolve/internal/git"
"github.com/cockroachdb/actions/autosolve/internal/github"
"github.com/cockroachdb/actions/autosolve/internal/implement"
)

// BuildSHA is set at build time via -ldflags.
var BuildSHA = "dev"

const usage = `Usage: autosolve <command>

Commands:
assess Run assessment phase
implement Run implementation phase
version Print the git SHA this binary was built from
`

func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

if len(os.Args) < 2 {
fatalf(usage)
}

var err error
switch os.Args[1] {
case "assess":
err = runAssess(ctx)
case "implement":
err = runImplement(ctx)
case "version":
fmt.Println(BuildSHA)
return
default:
fatalf("unknown command: %s\n\n%s", os.Args[1], usage)
}

if err != nil {
action.LogError(err.Error())
os.Exit(1)
}
}

func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}

func runAssess(ctx context.Context) error {
cfg, err := config.LoadAssessConfig()
if err != nil {
return err
}
if err := config.ValidateAuth(); err != nil {
return err
}
tmpDir, err := ensureTmpDir()
if err != nil {
return err
}
return assess.Run(ctx, cfg, &claude.CLIRunner{}, tmpDir)
}

func runImplement(ctx context.Context) error {
cfg, err := config.LoadImplementConfig()
if err != nil {
return err
}
if err := config.ValidateAuth(); err != nil {
return err
}
tmpDir, err := ensureTmpDir()
if err != nil {
return err
}

gitClient := &git.CLIClient{}
defer implement.Cleanup(gitClient)

ghClient := &github.GithubClient{Token: cfg.PRCreateToken}
return implement.Run(ctx, cfg, &claude.CLIRunner{}, ghClient, gitClient, tmpDir)
}

func ensureTmpDir() (string, error) {
dir := os.Getenv("AUTOSOLVE_TMPDIR")
if dir != "" {
return dir, nil
}
dir, err := os.MkdirTemp("", "autosolve_*")
if err != nil {
return "", fmt.Errorf("creating temp dir: %w", err)
}
os.Setenv("AUTOSOLVE_TMPDIR", dir)
return dir, nil
}
3 changes: 3 additions & 0 deletions autosolve/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/cockroachdb/actions/autosolve

go 1.23.8
Empty file added autosolve/go.sum
Empty file.
Loading
Loading