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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: src/go.mod
cache-dependency-path: src/go.sum

- name: Build
run: go build ./...
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ steps.bump.outputs.version }}"
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q "$TAG"; then
echo "Tag $TAG already exists — skipping release"
exit 0
fi
git tag "$TAG"
git push origin "$TAG"
gh release create "$TAG" --title "$TAG" --notes "${{ steps.notes.outputs.notes }}"
56 changes: 48 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Works with just Claude. Optionally adds Codex and Gemini for multi-perspective a
| `/devkit:refactor` | Full refactor lifecycle — analyze, plan, restructure, verify, compare |
| `/devkit:research` | Deep research — clarify, parallel search, analyze sources, synthesize |
| `/devkit:decompose` | Goal decomposition — break into task DAG, assign agents, execute in dependency order |
| `/devkit:scrape` | URL-to-Markdown conversion via Jina Reader / Firecrawl / WebFetch |
| `/devkit:status` | Health check — installed CLIs, available agents, ready commands |

### Self-Improvement Loops (Claude-only)
Expand Down Expand Up @@ -268,8 +269,17 @@ devkit/
│ └── tri-security.yml # Three-tier security audit
├── presets/ # Reusable prompt templates (planned)
│ └── .gitkeep
└── src/
└── TODO.md # Go harness roadmap
├── .github/workflows/ # CI/CD
│ ├── ci.yml # Build + test + vet on push/PR
│ └── release.yml # Auto-tag + release on version bump
└── src/ # Go CLI harness
├── go.mod
├── main.go
├── Makefile
├── cmd/ # Cobra commands
├── lib/ # DB, git, metric, state, report
├── loops/ # Improve, feature, bugfix, refactor, testgen, review, dispatch
└── runners/ # Claude, Codex, Gemini runner interfaces
```

---
Expand All @@ -288,23 +298,31 @@ Set automatically in each multi-agent command:

## Go CLI Harness

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

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

Or install directly:

```bash
cd src && make install
```

### Commands

All loop commands support `--agent` to choose the AI agent (default: `claude`).

| Command | Description |
|---|---|
| `devkit improve` | Metric-gated iteration loop — one Claude invocation per iteration |
| `devkit improve` | Metric-gated iteration loop — one agent 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 review` | Parallel multi-agent code review |
| `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 |
Expand All @@ -316,13 +334,17 @@ cd src && make build
- **Hard budget caps** — stops spawning at your dollar limit
- **CI/CD integration** — runs headless, no conversation needed
- **True parallel dispatch** — goroutines, not sequential prompts
- **Multi-agent support** — `--agent claude`, `--agent codex`, or `--agent gemini`

### Examples

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

# Same thing with Codex instead of Claude
devkit improve --metric "npm test" --iterations 50 --agent codex

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

Expand All @@ -332,13 +354,29 @@ devkit bugfix "login 500 on plus sign emails" --test "go test ./..."
# Generate tests for a module
devkit test-gen src/parser/ --test "go test ./..."

# Multi-agent review with all available agents
devkit review

# Resume a crashed session
devkit resume abc123def456

# Check what happened
devkit status
```

### Testing

```bash
cd src && go test ./... -v
```

76+ tests across 4 packages (lib, runners, loops, cmd). Loop tests use mock runners — no API calls needed.

### CI/CD

- **CI pipeline** (`.github/workflows/ci.yml`): build + vet + test (with `-race`) + gofmt check on every push/PR to main
- **Auto-release** (`.github/workflows/release.yml`): auto-bumps version, tags, and creates GitHub release on merged PRs

---

## Prerequisites
Expand Down Expand Up @@ -404,7 +442,9 @@ Devkit's `tri:*` commands use `/gemini:rescue --background` for multi-agent disp

See [ROADMAP.md](ROADMAP.md) for full details.

- [ ] Go CLI harness for deterministic loop control, process management, and unattended runs (see `src/TODO.md`)
- [x] Go CLI harness — 9 commands, SQLite state, crash recovery, budget enforcement, multi-agent support
- [x] CI/CD pipeline — build, vet, test, auto-release on version bump
- [x] Branch protection — PRs required for main
- [ ] Cost event hooks — budget threshold events with auto-downgrade actions
- [ ] Execution registry — centralized step tracking with timing and token usage
- [ ] Preset library — curated prompt templates for common review/improvement scenarios
Expand Down
11 changes: 5 additions & 6 deletions src/cmd/bugfix.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import (

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

var bugfixCmd = &cobra.Command{
Use: "bugfix [description]",
Short: "Full bugfix lifecycle: diagnose, fix, verify",
Long: "Spawns Claude for each step: diagnose root cause → apply fix → run tests to verify.",
Long: "Spawns an AI agent for each step: diagnose root cause → apply fix → run tests to verify.",
Example: ` devkit bugfix "login returns 500 when email has a plus sign"
devkit bugfix "race condition in cache invalidation" --test "go test ./..."`,
Args: cobra.MinimumNArgs(1),
Expand All @@ -29,10 +28,10 @@ var bugfixCmd = &cobra.Command{
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")
agentName, _ := cmd.Flags().GetString("agent")
runner, err := resolveRunner(agentName)
if err != nil {
return err
}

result, err := loops.RunBugfix(cmd.Context(), db, runner, &lib.Git{Dir: repoRoot}, loops.BugfixConfig{
Expand Down
11 changes: 5 additions & 6 deletions src/cmd/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import (

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

var featureCmd = &cobra.Command{
Use: "feature [description]",
Short: "Full feature lifecycle: plan, implement, test, lint",
Long: "Spawns Claude for each step: plan → implement → test (loop until green) → lint.",
Long: "Spawns an AI agent for each step: plan → implement → test (loop until green) → lint.",
Example: ` devkit feature "add JWT authentication" --target src/auth/
devkit feature "add search endpoint" --test "npm test" --lint "npm run lint"`,
Args: cobra.MinimumNArgs(1),
Expand All @@ -31,10 +30,10 @@ var featureCmd = &cobra.Command{
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")
agentName, _ := cmd.Flags().GetString("agent")
runner, err := resolveRunner(agentName)
if err != nil {
return err
}

result, err := loops.RunFeature(cmd.Context(), db, runner, &lib.Git{Dir: repoRoot}, loops.FeatureConfig{
Expand Down
11 changes: 5 additions & 6 deletions src/cmd/improve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import (

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

var improveCmd = &cobra.Command{
Use: "improve",
Short: "Run a metric-gated improvement loop",
Long: "Spawns Claude per iteration. Each iteration: propose change, run metric, keep if pass, revert if fail.",
Long: "Spawns an AI agent per iteration. Each iteration: propose change, run metric, keep if pass, revert if fail.",
Example: ` devkit improve --target src/ --metric "npm test" --objective "0 failing tests" --iterations 20
devkit improve --metric "go test ./..." --iterations 10 --budget 5.00`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -43,10 +42,10 @@ var improveCmd = &cobra.Command{
return fmt.Errorf("working tree has uncommitted changes — commit or stash before running devkit improve")
}

available := runners.DetectRunners()
runner := runners.FindRunner("claude", available)
if runner == nil {
return fmt.Errorf("claude CLI not found in PATH — install from https://docs.anthropic.com/en/docs/claude-code")
agentName, _ := cmd.Flags().GetString("agent")
runner, err := resolveRunner(agentName)
if err != nil {
return err
}

git := &lib.Git{Dir: repoRoot}
Expand Down
11 changes: 5 additions & 6 deletions src/cmd/refactor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import (

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

var refactorCmd = &cobra.Command{
Use: "refactor [description]",
Short: "Full refactor lifecycle: analyze, transform, verify",
Long: "Spawns Claude for each step: analyze code smells → apply transformations → verify tests still pass.",
Long: "Spawns an AI agent for each step: analyze code smells → apply transformations → verify tests still pass.",
Example: ` devkit refactor "extract auth middleware into shared package" --target src/
devkit refactor "flatten nested callbacks to async/await" --test "npm test"`,
Args: cobra.MinimumNArgs(1),
Expand All @@ -30,10 +29,10 @@ var refactorCmd = &cobra.Command{
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")
agentName, _ := cmd.Flags().GetString("agent")
runner, err := resolveRunner(agentName)
if err != nil {
return err
}

result, err := loops.RunRefactor(cmd.Context(), db, runner, &lib.Git{Dir: repoRoot}, loops.RefactorConfig{
Expand Down
9 changes: 4 additions & 5 deletions src/cmd/resume.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

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

Expand Down Expand Up @@ -40,10 +39,10 @@ var resumeCmd = &cobra.Command{
return fmt.Errorf("resume only supports improve sessions — session %s is %q", sessionID, session.Workflow)
}

available := runners.DetectRunners()
runner := runners.FindRunner("claude", available)
if runner == nil {
return fmt.Errorf("claude CLI not found in PATH")
agentName, _ := cmd.Flags().GetString("agent")
runner, err := resolveRunner(agentName)
if err != nil {
return err
}

// Require clean worktree before resuming
Expand Down
26 changes: 26 additions & 0 deletions src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"

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

Expand Down Expand Up @@ -44,6 +46,10 @@ var rootCmd = &cobra.Command{
},
}

func init() {
rootCmd.PersistentFlags().String("agent", "claude", "AI agent to use (claude, codex, gemini)")
}

func Execute() error {
rootCmd.Version = Version
ctx, cancel := context.WithCancel(context.Background())
Expand All @@ -60,6 +66,26 @@ func Execute() error {
return rootCmd.ExecuteContext(ctx)
}

func resolveRunner(name string) (runners.Runner, error) {
return resolveRunnerFrom(name, runners.DetectRunners())
}

func resolveRunnerFrom(name string, available []runners.Runner) (runners.Runner, error) {
name = strings.ToLower(strings.TrimSpace(name))
r := runners.FindRunner(name, available)
if r != nil {
return r, nil
}
var names []string
for _, a := range available {
names = append(names, a.Name())
}
if len(names) == 0 {
return nil, fmt.Errorf("no AI agents found in PATH — install claude, codex, or gemini")
}
return nil, fmt.Errorf("agent %q not found — available: %s", name, strings.Join(names, ", "))
}

func findRepoRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
Expand Down
Loading
Loading