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
61 changes: 5 additions & 56 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,6 @@
# Agent Context for Drover Code
# Agent Context

Welcome, AI Agent. This file is intended to help AI coding assistants understand the structure, context, and conventions of the `drover-code` repository.

## Ecosystem Role

> **Part of the Drover Ecosystem**: `drover-code` serves as the **Core Agent Engine**. It is the fast, static Go binary that actually runs the agentic loop, calls the Anthropic API (via `drover-gateway`), and executes tools. It is orchestrated by `drover` and runs headlessly inside `drover-cloud` unikernels.

## What this repo is

## Layout

| Path | Role |
|------|------|
| `cmd/drover-code` | CLI entry: TUI, headless, `webhook`, flags |
| `cmd/ukc-agent` | HTTP agent for Unikraft Cloud instances (workspace sync & exec) |
| `internal/agent` | Agent loop, events |
| `internal/api` | HTTP client, SSE stream |
| `internal/bridge` | IDE bridge (JSON-RPC framing over stdio) |
| `internal/config` | Settings merge, `CLAUDE.md` / markdown injection |
| `internal/integrations/sqlforge` | Detect `sqlforge.yml`; inject SQLForge CLI guidance |
| `internal/convo` | Conversation state, compaction heuristics |
| `internal/coordinator` | Multi-worker coordinator mode |
| `internal/github` | Webhook server, parser, runner |
| `internal/tools` | Tool registry and implementations |
| `internal/tui` | Bubble Tea model and views |
| `internal/dream` | Session memory (JSON / SQLite) |
| `design/` | Design specs and roadmap (numbered `01-…`, test plan `13`, UX `14`) |
| `docs/` | User-facing docs (Tutorials, How-Tos, Reference, Explanation) |

## Build and test

```bash
CGO_ENABLED=0 go build -o drover-code ./cmd/drover-code
CGO_ENABLED=0 go build -o ukc-agent ./cmd/ukc-agent
CGO_ENABLED=0 go test ./...
```

CI uses Go 1.22 with `CGO_ENABLED=0`. Local `go.mod` may list a newer `go` directive; keep changes compatible with CI unless you bump the workflow.

Fuzz targets are listed in `.github/workflows/ci.yml` (`fuzz` job).

## Conventions

- Prefer focused changes: match existing style, imports, and error wrapping in touched packages.
- Property / fuzz tests: see `design/12-property-fuzz-testing.md` and doc `13-test-coverage-plan.md`.
- Do not assume Node: this is Go-only for the main binary.

## Product behavior pointers

- End-user overview, env vars, and modes: `README.md`.
- `internal/config` walks upward from `workDir` and merges `CLAUDE.md` files into the system prompt. If this repository is the working directory, **this** `CLAUDE.md` is included like any other project instructions file.
- When `sqlforge.yml` is found at a project root, SQLForge CLI guidance is appended automatically. How-to: `docs/how-to/sqlforge-from-drover-code.md`.

## Optional evals

Live Anthropic eval tests are opt-in (`RUN_AGENT_EVALS=1` and API key); see `evals/` and `README.md`.
> **Note to AI Agents:**
> The comprehensive context, repository structure, and operational guidelines for `drover-code` have been migrated to adhere to the organization's strict Diátaxis documentation structure.
>
> Please read the authoritative agent context here: **[`docs/reference/agent-context.md`](docs/reference/agent-context.md)**
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ Docs under `docs/` follow [Diátaxis](https://diataxis.fr/) and require YAML fro
- [Integration Checklist](docs/how-to/integration-checklist.md)

**Reference (Information-oriented):**
- [Agent Personas & Context](AGENTS.md)
- [Agent Personas & Context](docs/reference/agent-context.md)
- [Available Tools](docs/reference/available-tools.md)
- [Environment Variables](docs/reference/environment-variables.md)
- [Implementation Requirements](docs/reference/implementation-reqs.md)
- [Requirements Index](docs/reference/requirements-index.md)
- [Requirements Summary](docs/reference/requirements-summary.txt)
Expand All @@ -28,26 +29,26 @@ Docs under `docs/` follow [Diátaxis](https://diataxis.fr/) and require YAML fro
- [Architecture Overview](docs/explanation/architecture-overview.md)
- [Unikraft Cloud Architecture](docs/explanation/unikraft-cloud.md)

**Design Specifications (`design/`):**
- [01 Foundation](design/01-foundation.md)
- [02 Agent Loop](design/02-agent-loop.md)
- [03 Tools Overview](design/03-tools-overview.md)
- [04 FS Tools](design/04-fs-tools.md)
- [05 Shell Search Tools](design/05-shell-search-tools.md)
- [06 Git Web Tools](design/06-git-web-tools.md)
- [07 TUI](design/07-tui.md)
- [08 Config & Permissions](design/08-config-permissions-undercover.md)
- [09 Advanced Systems](design/09-advanced-systems.md)
- [10 Integrations](design/10-integrations.md)
- [11 Headless Orchestration](design/11-headless-orchestration.md)
- [12 Property Fuzz Testing](design/12-property-fuzz-testing.md)
- [13 Test Coverage Plan](design/13-test-coverage-plan.md)
- [14 UX Memory Improvements](design/14-ux-memory-improvements.md)
- [15 UKC Tools](design/15-ukc-tools.md)
- [16 UKC Workspace Sync](design/16-ukc-workspace-sync.md)
- [17 Custom Commands](design/17-custom-commands.md)
- [Claude Go Design Spec](design/claude-go-design-spec.md)
- [Claude Go Test Spec](design/claude-go-test-spec.md)
**Architecture Decision Records (`docs/adr/`):**
- [01 Foundation](docs/adr/01-foundation.md)
- [02 Agent Loop](docs/adr/02-agent-loop.md)
- [03 Tools Overview](docs/adr/03-tools-overview.md)
- [04 FS Tools](docs/adr/04-fs-tools.md)
- [05 Shell Search Tools](docs/adr/05-shell-search-tools.md)
- [06 Git Web Tools](docs/adr/06-git-web-tools.md)
- [07 TUI](docs/adr/07-tui.md)
- [08 Config & Permissions](docs/adr/08-config-permissions-undercover.md)
- [09 Advanced Systems](docs/adr/09-advanced-systems.md)
- [10 Integrations](docs/adr/10-integrations.md)
- [11 Headless Orchestration](docs/adr/11-headless-orchestration.md)
- [12 Property Fuzz Testing](docs/adr/12-property-fuzz-testing.md)
- [13 Test Coverage Plan](docs/adr/13-test-coverage-plan.md)
- [14 UX Memory Improvements](docs/adr/14-ux-memory-improvements.md)
- [15 UKC Tools](docs/adr/15-ukc-tools.md)
- [16 UKC Workspace Sync](docs/adr/16-ukc-workspace-sync.md)
- [17 Custom Commands](docs/adr/17-custom-commands.md)
- [Claude Go Design Spec](docs/adr/claude-go-design-spec.md)
- [Claude Go Test Spec](docs/adr/claude-go-test-spec.md)

## Requirements

Expand Down Expand Up @@ -115,7 +116,7 @@ export CLAUDE_CODE_COORDINATOR_MODE=1
export ANTHROPIC_MODEL=claude-haiku-4-5-20251001
```

or `"model"` in `~/.claude/settings.json` or `.claude/settings.json`.
or `"model"` in `~/.drover/settings.json` or `.drover/settings.json`.

## Other LLM providers (Moonshot, GLM, …)

Expand All @@ -125,11 +126,11 @@ drover-code targets the **Anthropic Messages API** wire format. Gateways that im

## Configuration

Settings merge in order:
Settings merge in order (legacy `~/.claude` / `.claude` paths at each tier are still read when present; drover wins at the same tier):

1. `~/.claude/settings.json`
2. `<workdir>/.claude/settings.json`
3. `<workdir>/.claude/settings.local.json`
1. `~/.drover/settings.json`
2. `<workdir>/.drover/settings.json`
3. `<workdir>/.drover/settings.local.json`

`CLAUDE.md` files under the working tree are concatenated and injected into the system prompt.

Expand Down
20 changes: 14 additions & 6 deletions cmd/drover-code/envutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,28 @@ func envIntPositive(key string) int {
return n
}

// anthropicAPIKey returns the first non-empty of ANTHROPIC_API_KEY or
// ANTHROPIC_AUTH_TOKEN (Moonshot/Kimi and some other shims use the latter).
// anthropicAPIKey returns the first non-empty API key from the environment.
// It checks Anthropic, Gemini, and OpenAI keys to support API gateways.
func anthropicAPIKey() string {
if v := strings.TrimSpace(os.Getenv("ANTHROPIC_API_KEY")); v != "" {
return v
keys := []string{
"ANTHROPIC_API_KEY",
"ANTHROPIC_AUTH_TOKEN",
"GEMINI_API_KEY",
"OPENAI_API_KEY",
}
return strings.TrimSpace(os.Getenv("ANTHROPIC_AUTH_TOKEN"))
for _, k := range keys {
if v := strings.TrimSpace(os.Getenv(k)); v != "" {
return v
}
}
return ""
}

func requireAnthropicAPIKey() string {
if k := anthropicAPIKey(); k != "" {
return k
}
fmt.Fprintln(os.Stderr, "error: set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN")
fmt.Fprintln(os.Stderr, "error: set ANTHROPIC_API_KEY, GEMINI_API_KEY, or OPENAI_API_KEY")
os.Exit(2)
return ""
}
7 changes: 6 additions & 1 deletion cmd/ukc-agent/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"sync"
"time"
Expand Down Expand Up @@ -65,7 +66,11 @@ func (jr *jobRunner) runCommand(parent context.Context, j *job, command string)
defer cancel()

cmd := exec.CommandContext(ctx, "/bin/sh", "-c", command)
cmd.Dir = "/workspace"
if dir := os.Getenv("UKC_AGENT_WORKSPACE"); dir != "" {
cmd.Dir = dir
} else {
cmd.Dir = "/workspace"
}
stdout, err := cmd.StdoutPipe()
if err != nil {
j.emitThenClose(err.Error(), 1)
Expand Down
112 changes: 112 additions & 0 deletions cmd/ukc-agent/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)

func TestStreamReplay_LastEventID(t *testing.T) {
jr := newJobRunner("")
id := randomID()

// Create a dummy job with history
j := &job{
id: id,
history: make([]map[string]any, 0, 64),
listeners: make([]chan struct{}, 0),
}
jr.jobs[id] = j

j.trySend(map[string]any{"line": "hello"})
j.trySend(map[string]any{"line": "world"})
j.trySend(map[string]any{"done": true, "exit_code": 0})

// Test requesting from event 1 (skipping "hello", fetching "world" and "done")
req := httptest.NewRequest(http.MethodGet, "/stream", nil)
req.Header.Set("Last-Event-ID", "0")
rec := httptest.NewRecorder()

err := jr.streamSSE(rec, req, id)
if err != nil {
t.Fatalf("streamSSE error: %v", err)
}

out := rec.Body.String()
if strings.Contains(out, "hello") {
t.Fatalf("expected hello to be skipped, got: %s", out)
}
if !strings.Contains(out, "world") {
t.Fatalf("expected world, got: %s", out)
}
}

func TestStreamReplay_GapWarning(t *testing.T) {
jr := newJobRunner("")
id := randomID()

j := &job{
id: id,
history: make([]map[string]any, 0, 64),
listeners: make([]chan struct{}, 0),
baseIndex: 50, // simulate that events 0-49 were evicted
}
jr.jobs[id] = j

j.trySend(map[string]any{"line": "late event"})
j.trySend(map[string]any{"done": true, "exit_code": 0})

// Requesting from event 0, but base is 50, so gap warning should be emitted
req := httptest.NewRequest(http.MethodGet, "/stream", nil)
req.Header.Set("Last-Event-ID", "0")
rec := httptest.NewRecorder()

err := jr.streamSSE(rec, req, id)
if err != nil {
t.Fatalf("streamSSE error: %v", err)
}

out := rec.Body.String()
if !strings.Contains(out, "stream replay gap") {
t.Fatalf("expected gap warning, got: %s", out)
}
if !strings.Contains(out, "late event") {
t.Fatalf("expected late event, got: %s", out)
}
}

func TestStreamReplay_LiveReconnection(t *testing.T) {
jr := newJobRunner("")
tmpDir := t.TempDir()
t.Setenv("UKC_AGENT_WORKSPACE", tmpDir)
id := jr.start(context.Background(), "echo 'first'; sleep 0.1; echo 'second'")

// First connection: read "first", then disconnect
ctx, cancel1 := context.WithCancel(context.Background())
req1, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/stream", nil)
rec1 := httptest.NewRecorder()

// We want to stream until we see "first", then cancel the context to simulate disconnect
go func() {
// Read body manually instead of streamSSE to interleave properly
time.Sleep(50 * time.Millisecond)
cancel1()
}()

_ = jr.streamSSE(rec1, req1, id) // this will return context.Canceled

// Second connection: read the rest
req2 := httptest.NewRequest(http.MethodGet, "/stream", nil)
rec2 := httptest.NewRecorder()
_ = jr.streamSSE(rec2, req2, id)

out := rec2.Body.String()

// We might get "first" again if we didn't use Last-Event-ID, but we should definitely get "second"
if !strings.Contains(out, "second") {
t.Fatalf("expected second to arrive in the stream: %s", out)
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 1 addition & 6 deletions docs/how-to/configure-custom-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ Official Anthropic needs only `ANTHROPIC_API_KEY` (default base URL is `https://

## Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `ANTHROPIC_API_KEY` | One of key vars | Primary API key (Anthropic `sk-ant-…` or a provider key). |
| `ANTHROPIC_AUTH_TOKEN` | One of key vars | Alternative key name; **if `ANTHROPIC_API_KEY` is empty, this is used** (e.g. Moonshot/Kimi docs). |
| `ANTHROPIC_BASE_URL` | For gateways | Overrides the API host. Requests go to `{BASE_URL}/v1/messages` (no trailing slash required). |
| `ANTHROPIC_MODEL` | Optional | Model id for the provider (also configurable via `.claude/settings.json` `"model"`). |
To configure custom endpoints, you will need to set the appropriate environment variables. For a complete list of accepted variables (such as `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY`), please refer to the [Environment Variables Reference](../reference/environment-variables.md).

At least one of `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` must be non-empty.

Expand Down
66 changes: 66 additions & 0 deletions docs/reference/agent-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Agent Context
description: Reference for AI coding assistants to understand the structure, context, and conventions of the drover-code repository.
product: drover-code
audience: agent
doc_type: reference
surface: repo-docs
---

# Agent Context for Drover Code

Welcome, AI Agent. This file is intended to help AI coding assistants understand the structure, context, and conventions of the `drover-code` repository.

## Ecosystem Role

> **Part of the Drover Ecosystem**: `drover-code` serves as the **Core Agent Engine**. It is the fast, static Go binary that actually runs the agentic loop, calls the Anthropic API (via `drover-gateway`), and executes tools. It is orchestrated by `drover` and runs headlessly inside `drover-cloud` unikernels.

## What this repo is

## Layout

| Path | Role |
|------|------|
| `cmd/drover-code` | CLI entry: TUI, headless, `webhook`, flags |
| `cmd/ukc-agent` | HTTP agent for Unikraft Cloud instances (workspace sync & exec) |
| `internal/agent` | Agent loop, events |
| `internal/api` | HTTP client, SSE stream |
| `internal/bridge` | IDE bridge (JSON-RPC framing over stdio) |
| `internal/config` | Settings merge, `CLAUDE.md` / markdown injection |
| `internal/integrations/sqlforge` | Detect `sqlforge.yml`; inject SQLForge CLI guidance |
| `internal/convo` | Conversation state, compaction heuristics |
| `internal/coordinator` | Multi-worker coordinator mode |
| `internal/github` | Webhook server, parser, runner |
| `internal/tools` | Tool registry and implementations |
| `internal/tui` | Bubble Tea model and views |
| `internal/dream` | Session memory (JSON / SQLite) |
| `design/` | Design specs and roadmap (numbered `01-…`, test plan `13`, UX `14`) |
| `docs/` | User-facing docs (Tutorials, How-Tos, Reference, Explanation) |

## Build and test

```bash
CGO_ENABLED=0 go build -o drover-code ./cmd/drover-code
CGO_ENABLED=0 go build -o ukc-agent ./cmd/ukc-agent
CGO_ENABLED=0 go test ./...
```

CI uses Go 1.22 with `CGO_ENABLED=0`. Local `go.mod` may list a newer `go` directive; keep changes compatible with CI unless you bump the workflow.

Fuzz targets are listed in `.github/workflows/ci.yml` (`fuzz` job).

## Conventions

- Prefer focused changes: match existing style, imports, and error wrapping in touched packages.
- Property / fuzz tests: see `design/12-property-fuzz-testing.md` and doc `13-test-coverage-plan.md`.
- Do not assume Node: this is Go-only for the main binary.

## Product behavior pointers

- End-user overview, env vars, and modes: `README.md`.
- `internal/config` walks upward from `workDir` and merges `CLAUDE.md` files into the system prompt. If this repository is the working directory, **this** `CLAUDE.md` is included like any other project instructions file.
- When `sqlforge.yml` is found at a project root, SQLForge CLI guidance is appended automatically. How-to: `docs/how-to/sqlforge-from-drover-code.md`.

## Optional evals

Live Anthropic eval tests are opt-in (`RUN_AGENT_EVALS=1` and API key); see `evals/` and `README.md`.
Loading
Loading