Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
build-and-test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: src

steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: src/go.mod

- name: Build
run: go build ./...

- name: Vet
run: go vet ./...

- name: Test
run: go test ./... -v -race -count=1

- name: Check formatting
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "Files not formatted:"
echo "$unformatted"
exit 1
fi
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,61 @@ Set automatically in each multi-agent command:

---

## Go CLI Harness

Deterministic orchestration binary — the machine controls the loop, Claude is the body.

```bash
cd src && make build
./bin/devkit --help
```

### Commands

| Command | Description |
|---|---|
| `devkit improve` | Metric-gated iteration loop — one Claude invocation per iteration |
| `devkit feature` | Plan, implement, test, lint — commits only after tests pass |
| `devkit bugfix` | Diagnose, fix, verify — reverts if tests break |
| `devkit refactor` | Analyze, transform, verify — reverts if behavior changes |
| `devkit test-gen` | Generate tests, run, fix failures — iterates until green |
| `devkit review` | Parallel multi-agent code review (Claude + Codex + Gemini) |
| `devkit dispatch` | Send any task to multiple agents, compare outputs |
| `devkit status` | Show all sessions, costs, iteration history |
| `devkit resume` | Pick up a crashed or paused session |

### What it does that plugins can't

- **Exact iteration counts** — Go binary owns the loop, not the LLM
- **Crash recovery** — SQLite state + handoff files survive crashes
- **Hard budget caps** — stops spawning at your dollar limit
- **CI/CD integration** — runs headless, no conversation needed
- **True parallel dispatch** — goroutines, not sequential prompts

### Examples

```bash
# Run 50 improvement iterations overnight, stop at $20
devkit improve --metric "npm test" --iterations 50 --budget 20.00

# Implement a feature with test verification
devkit feature "add JWT auth" --target src/auth/ --test "npm test"

# Fix a bug with automated verification
devkit bugfix "login 500 on plus sign emails" --test "go test ./..."

# Generate tests for a module
devkit test-gen src/parser/ --test "go test ./..."

# Resume a crashed session
devkit resume abc123def456

# Check what happened
devkit status
```

---

## Prerequisites

**Required:** Claude Code (you're already here)
Expand Down
12 changes: 12 additions & 0 deletions commands/tri-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ description: Multi-agent debugging — send a bug report to available agents (Cl

Send a bug description to all available agents in parallel, get independent root-cause analyses, and consolidate into a recommended fix.

## Step 0: Harness Detection

```bash
if command -v devkit >/dev/null 2>&1; then
echo "Go harness detected — delegating to devkit dispatch for full output capture."
devkit dispatch {prompt with bug context}
exit 0
fi
```

If the `devkit` binary is in PATH, delegate entirely to it. Only fall through to plugin-based steps if the harness is not installed.

## Step 1: Gather Context

Collect from the user:
Expand Down
12 changes: 12 additions & 0 deletions commands/tri-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ description: Dispatch a task to all three agents (Claude, Codex, Gemini) in para

Send the same task to Claude, Codex, and Gemini in parallel. Compare outputs.

## Step 0: Harness Detection

```bash
if command -v devkit >/dev/null 2>&1; then
echo "Go harness detected — delegating to devkit dispatch for full output capture."
devkit dispatch {prompt}
exit 0
fi
```

If the `devkit` binary is in PATH, delegate entirely to it. Only fall through to plugin-based steps if the harness is not installed.

## When to use

- Comparing approaches to a problem
Expand Down
14 changes: 14 additions & 0 deletions commands/tri-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ description: Triple-agent PR/code review. Claude runs as native background agent

Run the same code review across three AI agents in parallel and consolidate results.

## Step 0: Harness Detection

```bash
if command -v devkit >/dev/null 2>&1; then
echo "Go harness detected — delegating to devkit review for full output capture."
devkit review {prompt or default}
# The harness handles parallel dispatch, full stdout capture (no truncation),
# SQLite session tracking, and consolidated output. Skip all steps below.
exit 0
fi
```

If the `devkit` binary is in PATH, delegate entirely to it. The harness avoids output truncation, captures full agent responses, and tracks sessions in SQLite. Only fall through to the plugin-based steps below if the harness is not installed.

## Step 1: Gather Context

```bash
Expand Down
12 changes: 12 additions & 0 deletions commands/tri-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ description: Multi-agent security audit — independent security reviews from av

Independent security reviews from all available agents, consolidated into a severity-ranked report.

## Step 0: Harness Detection

```bash
if command -v devkit >/dev/null 2>&1; then
echo "Go harness detected — delegating to devkit review --security for full output capture."
devkit review --security {prompt or default}
exit 0
fi
```

If the `devkit` binary is in PATH, delegate entirely to it. Only fall through to plugin-based steps if the harness is not installed.

## Step 1: Gather Scope

Determine what to audit:
Expand Down
12 changes: 12 additions & 0 deletions commands/tri-test-gen.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ description: Multi-agent test generation — each available agent generates test

Generate tests from all available agents in parallel, then merge the best tests into a comprehensive suite.

## Step 0: Harness Detection

```bash
if command -v devkit >/dev/null 2>&1; then
echo "Go harness detected — delegating to devkit test-gen for full output capture."
devkit test-gen {target} --test {test_command}
exit 0
fi
```

If the `devkit` binary is in PATH, delegate entirely to it. Only fall through to plugin-based steps if the harness is not installed.

## Step 1: Analyze Target

Read the target files and detect:
Expand Down
8 changes: 7 additions & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev
LDFLAGS := -s -w -X main.version=$(VERSION)
GOFLAGS := -trimpath

.PHONY: build install clean test vet fmt check all
.PHONY: build install link clean test vet fmt check all

all: check build

Expand All @@ -12,6 +12,12 @@ build:

install:
go install $(GOFLAGS) -ldflags '$(LDFLAGS)' .
@echo "Installed to $$(go env GOPATH)/bin/$(BINARY)"
@echo "Ensure $$(go env GOPATH)/bin is in your PATH"

link: build
ln -sf $(CURDIR)/bin/$(BINARY) /usr/local/bin/$(BINARY)
@echo "Linked bin/$(BINARY) → /usr/local/bin/$(BINARY)"

clean:
rm -rf bin/
Expand Down
7 changes: 6 additions & 1 deletion src/cmd/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ var dispatchCmd = &cobra.Command{

var agents []string
if agentList != "" {
agents = strings.Split(agentList, ",")
for _, a := range strings.Split(agentList, ",") {
a = strings.TrimSpace(a)
if a != "" {
agents = append(agents, a)
}
}
}

available := runners.DetectRunners()
Expand Down
9 changes: 8 additions & 1 deletion src/cmd/resume.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@ package cmd

import (
"fmt"
"regexp"
"strings"

"github.com/5uck1ess/devkit/lib"
"github.com/5uck1ess/devkit/loops"
"github.com/5uck1ess/devkit/runners"
"github.com/spf13/cobra"
)

var sessionIDPattern = regexp.MustCompile(`^[a-f0-9]{12}$`)

var resumeCmd = &cobra.Command{
Use: "resume <session-id>",
Short: "Resume a paused or crashed session",
Long: "Picks up an improve session from where it left off, using the SQLite state and handoff file.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
sessionID := args[0]
sessionID := strings.ToLower(args[0])
if !sessionIDPattern.MatchString(sessionID) {
return fmt.Errorf("invalid session ID %q — expected 12 hex characters (e.g., a1b2c3d4e5f6)", sessionID)
}

session, err := db.GetSession(sessionID)
if err != nil {
Expand Down
7 changes: 6 additions & 1 deletion src/cmd/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ var reviewCmd = &cobra.Command{

var agents []string
if agentList != "" {
agents = strings.Split(agentList, ",")
for _, a := range strings.Split(agentList, ",") {
a = strings.TrimSpace(a)
if a != "" {
agents = append(agents, a)
}
}
}

available := runners.DetectRunners()
Expand Down
8 changes: 6 additions & 2 deletions src/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ func showAllSessions() error {
fmt.Printf("%-14s %-10s %-10s %-8s %s\n", "-------", "--------", "------", "----", "-------")

for _, s := range sessions {
cost, _ := db.SessionTotalCost(s.ID)
cost, err := db.SessionTotalCost(s.ID)
costStr := fmt.Sprintf("$%.4f", cost)
if err != nil {
costStr = "unknown"
}
age := formatAge(s.CreatedAt)
fmt.Printf("%-14s %-10s %-10s $%-7.4f %s\n", s.ID, s.Workflow, s.Status, cost, age)
fmt.Printf("%-14s %-10s %-10s %-8s %s\n", s.ID, s.Workflow, s.Status, costStr, age)
}
return nil
}
Expand Down
66 changes: 66 additions & 0 deletions src/cmd/testgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package cmd

import (
"fmt"
"strings"

"github.com/5uck1ess/devkit/lib"
"github.com/5uck1ess/devkit/loops"
"github.com/5uck1ess/devkit/runners"
"github.com/spf13/cobra"
)

var testGenCmd = &cobra.Command{
Use: "test-gen [target]",
Short: "Generate tests for target code, run them, fix failures",
Long: "Analyzes target code, generates comprehensive tests, runs them, and iterates until green.",
Example: ` devkit test-gen src/auth/
devkit test-gen lib/parser.go --test "go test ./..."
devkit test-gen src/ --test "npm test" --budget 5.00`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
testCmd, _ := cmd.Flags().GetString("test")
budget, _ := cmd.Flags().GetFloat64("budget")

dirty, err := (&lib.Git{Dir: repoRoot}).HasUncommittedChanges()
if err != nil {
return fmt.Errorf("check git status: %w", err)
}
if dirty {
return fmt.Errorf("working tree has uncommitted changes — commit or stash first")
}

available := runners.DetectRunners()
runner := runners.FindRunner("claude", available)
if runner == nil {
return fmt.Errorf("claude CLI not found in PATH")
}

result, err := loops.RunTestGen(cmd.Context(), db, runner, &lib.Git{Dir: repoRoot}, loops.TestGenConfig{
Target: strings.Join(args, " "),
TestCmd: testCmd,
RepoRoot: repoRoot,
BudgetUSD: budget,
})
if err != nil {
return err
}

var totalCost float64
for _, s := range result.Steps {
totalCost += s.CostUSD
}
fmt.Printf("\n=== Test Generation Complete ===\n")
fmt.Printf("Session: %s\n", result.Session.ID)
fmt.Printf("Steps: %d\n", len(result.Steps))
fmt.Printf("Cost: $%.4f\n", totalCost)
fmt.Printf("\nRun `devkit status %s` for details.\n", result.Session.ID)
return nil
},
}

func init() {
rootCmd.AddCommand(testGenCmd)
testGenCmd.Flags().String("test", "", "Test command to run generated tests")
testGenCmd.Flags().Float64("budget", 0, "Maximum spend in USD (0 = unlimited)")
}
Loading
Loading