Skip to content

Commit 92c46ce

Browse files
committed
chore: add Makefile with test targets (unit, integration, coverage, cross-compile)
1 parent 509d4bb commit 92c46ce

5 files changed

Lines changed: 130 additions & 12 deletions

File tree

Makefile

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# kode — the fastest, minimalistic autonomous ReAct agent CLI in Go.
2+
# https://github.com/BackendStack21/kode
3+
4+
GO := go
5+
GOLINT := $(shell command -v golangci-lint 2>/dev/null)
6+
GIT_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0-dev")
7+
LDFLAGS := -s -w -X main.version=$(GIT_TAG)
8+
COVER := coverage.out
9+
10+
.PHONY: all
11+
all: test build
12+
13+
# ── Build ──────────────────────────────────────────────────────────────
14+
15+
.PHONY: build
16+
build: ## Build the kode binary
17+
$(GO) build -ldflags "$(LDFLAGS)" -o bin/kode ./cmd/kode
18+
19+
.PHONY: install
20+
install: ## Install kode to $GOPATH/bin
21+
$(GO) install -ldflags "$(LDFLAGS)" ./cmd/kode
22+
23+
.PHONY: build-all
24+
build-all: ## Cross-compile for linux, darwin (amd64 + arm64)
25+
GOOS=linux GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o bin/kode-linux-amd64 ./cmd/kode
26+
GOOS=linux GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o bin/kode-linux-arm64 ./cmd/kode
27+
GOOS=darwin GOARCH=amd64 $(GO) build -ldflags "$(LDFLAGS)" -o bin/kode-darwin-amd64 ./cmd/kode
28+
GOOS=darwin GOARCH=arm64 $(GO) build -ldflags "$(LDFLAGS)" -o bin/kode-darwin-arm64 ./cmd/kode
29+
30+
# ── Test (no LLM) ──────────────────────────────────────────────────────
31+
32+
.PHONY: test
33+
test: ## Run unit tests (fast, no LLM API calls)
34+
$(GO) test -short -count=1 ./...
35+
36+
.PHONY: test-race
37+
test-race: ## Run unit tests with race detector
38+
$(GO) test -short -race -count=1 ./...
39+
40+
.PHONY: test-verbose
41+
test-verbose: ## Run unit tests with full output
42+
$(GO) test -short -v -count=1 ./...
43+
44+
# ── Coverage ───────────────────────────────────────────────────────────
45+
46+
.PHONY: coverage
47+
coverage: ## Generate HTML coverage report (unit tests)
48+
$(GO) test -short -coverprofile=$(COVER) -covermode=atomic ./...
49+
$(GO) tool cover -html=$(COVER) -o coverage.html
50+
@echo "→ coverage.html"
51+
52+
.PHONY: coverage-func
53+
coverage-func: ## Print per-function coverage summary
54+
$(GO) test -short -coverprofile=$(COVER) -covermode=atomic ./...
55+
$(GO) tool cover -func=$(COVER)
56+
57+
.PHONY: coverage-total
58+
coverage-total: ## Print total coverage percentage
59+
$(GO) test -short -coverprofile=$(COVER) -covermode=atomic ./...
60+
@$(GO) tool cover -func=$(COVER) | tail -1 | awk '{print "total " $$3}'
61+
62+
# ── Test (with LLM) ────────────────────────────────────────────────────
63+
64+
.PHONY: test-integration
65+
test-integration: ## Run integration tests (requires DEEPSEEK_API_KEY)
66+
@test -n "$$DEEPSEEK_API_KEY" || { \
67+
echo "ERROR: DEEPSEEK_API_KEY not set"; \
68+
echo " export DEEPSEEK_API_KEY=sk-..."; \
69+
exit 1; \
70+
}
71+
$(GO) test -tags=integration -count=1 -timeout 300s -run Integration ./...
72+
73+
.PHONY: test-all
74+
test-all: test test-integration ## Run all tests (unit + integration)
75+
76+
# ── Lint & Format ──────────────────────────────────────────────────────
77+
78+
.PHONY: fmt
79+
fmt: ## Format all Go source files
80+
$(GO) fmt ./...
81+
82+
.PHONY: vet
83+
vet: ## Run go vet
84+
$(GO) vet ./...
85+
86+
.PHONY: lint
87+
lint: ## Run golangci-lint (if installed)
88+
ifdef GOLINT
89+
$(GOLINT) run ./...
90+
else
91+
@echo "golangci-lint not installed — run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"
92+
@exit 1
93+
endif
94+
95+
.PHONY: check
96+
check: fmt vet test ## Format, vet, and unit test (CI fast gate)
97+
98+
# ── Docker / Sandbox ───────────────────────────────────────────────────
99+
100+
.PHONY: docker-test
101+
docker-test: build ## Run tests inside Docker sandbox (mimics --sandbox)
102+
docker run --rm -v "$(PWD):/workspace:ro" -w /workspace golang:1.24-alpine sh -c \
103+
'go test -short -count=1 ./...'
104+
105+
# ── Clean ──────────────────────────────────────────────────────────────
106+
107+
.PHONY: clean
108+
clean: ## Remove build artifacts and coverage files
109+
rm -rf bin/ $(COVER) coverage.html
110+
111+
# ── Help ───────────────────────────────────────────────────────────────
112+
113+
.PHONY: help
114+
help: ## Show this help
115+
@grep -E '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) \
116+
| sort \
117+
| awk 'BEGIN {FS = ":.*## "}; {printf "\033[36m%-22s\033[0m %s\n", $$1, $$2}'

cmd/kode/main.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,6 @@ func builtinTools() []kode.Tool {
208208
&shellTool{},
209209
}
210210
}
211-
212211
// getVersion returns the version string. Resolution order:
213212
// 1. ldflags override (-X main.version=v0.2.1)
214213
// 2. VCS tag from debug.ReadBuildInfo (when built with go install)
@@ -240,4 +239,4 @@ func getVersion() string {
240239
return revision
241240
}
242241
return "dev"
243-
}
242+
}

cmd/kode/shell.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ type shellTool struct {
1515
containerName string // empty = host, non-empty = docker exec into this container
1616
}
1717

18-
func (t *shellTool) Name() string { return "shell" }
19-
func (t *shellTool) Description() string { return "Run a shell command and return its output. Use for: reading files, listing directories, running tests, building code, git operations. The command runs in the current working directory." }
18+
func (t *shellTool) Name() string { return "shell" }
19+
func (t *shellTool) Description() string {
20+
return "Run a shell command and return its output. Use for: reading files, listing directories, running tests, building code, git operations. The command runs in the current working directory."
21+
}
2022
func (t *shellTool) Schema() any {
2123
return map[string]any{
2224
"type": "object",

internal/llm/client.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,18 @@ func New(baseURL, apiKey, model, thinking string) *Client {
3434

3535
// Message represents a chat message.
3636
type Message struct {
37-
Role string `json:"role"` // "system", "user", "assistant", "tool"
38-
Content string `json:"content"` // text content
39-
Name string `json:"name,omitempty"` // tool name (for tool role)
40-
ToolCallID string `json:"tool_call_id,omitempty"` // required for tool role
41-
ToolCalls []ToolCall `json:"tool_calls,omitempty"` // required for assistant role with tool calls
37+
Role string `json:"role"` // "system", "user", "assistant", "tool"
38+
Content string `json:"content"` // text content
39+
Name string `json:"name,omitempty"` // tool name (for tool role)
40+
ToolCallID string `json:"tool_call_id,omitempty"` // required for tool role
41+
ToolCalls []ToolCall `json:"tool_calls,omitempty"` // required for assistant role with tool calls
4242
}
4343

4444
// ToolCall represents a single tool invocation requested by the model.
4545
// Matches the OpenAI API format exactly.
4646
type ToolCall struct {
47-
ID string `json:"id"`
48-
Type string `json:"type"` // always "function"
47+
ID string `json:"id"`
48+
Type string `json:"type"` // always "function"
4949
Function struct {
5050
Name string `json:"name"`
5151
Arguments string `json:"arguments"`

kode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
type Tool interface {
3434
Name() string
3535
Description() string
36-
Schema() any // JSON Schema for the tool's parameters
36+
Schema() any // JSON Schema for the tool's parameters
3737
Call(args string) (string, error)
3838
}
3939

0 commit comments

Comments
 (0)