-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Problem
DevLake aggregates data from multiple DevOps tools into a unified data model, but the only way to gain insights is through pre-built Grafana dashboards. Users cannot ask ad-hoc, cross-tool questions like "Which repos had the highest lead time for changes last month?" or "Is there a correlation between Copilot adoption and deployment frequency?" without writing SQL or building custom dashboards.
Proposed Solution
Integrate the GitHub Copilot SDK (Go) into gh-devlake and add gh devlake insights — a natural language Q&A command that reasons over DevLake's aggregated data.
Command surface
# One-shot question
gh devlake insights "Which repos had the highest lead time last month?"
# Interactive mode (multi-turn conversation)
gh devlake insights
> Which teams deploy most frequently?
> Compare that with their change failure rate
> exitArchitecture
internal/
copilot/
client.go # Copilot SDK client lifecycle (start/stop, graceful error if CLI missing)
tools.go # Custom tool definitions (DefineTool wrappers)
system.go # System message with DevLake context
cmd/
insights.go # gh devlake insights command
SDK client lifecycle (internal/copilot/client.go)
package copilot
import (
copilot "github.com/github/copilot-sdk/go"
)
// NewCopilotClient creates and starts a Copilot SDK client.
// Returns a user-friendly error if Copilot CLI is not installed.
func NewCopilotClient(ctx context.Context) (*copilot.Client, error) {
client := copilot.NewClient(&copilot.ClientOptions{
LogLevel: "error",
})
if err := client.Start(ctx); err != nil {
// Check if it's a "copilot not found" error
return nil, fmt.Errorf("Copilot CLI is required for this command.\n" +
"Install: https://docs.github.com/en/copilot/how-tos/copilot-cli/install-copilot-cli")
}
return client, nil
}Design decision: Copilot CLI must be installed as a prerequisite. No embedded bundling for now — keeps the binary small and avoids version coupling.
Custom tools (internal/copilot/tools.go)
Using the SDK's DefineTool for type-safe tool registration:
// Tool: query_dora_metrics
type DoraParams struct {
Project string `json:"project" jsonschema:"DevLake project name"`
Timeframe string `json:"timeframe,omitempty" jsonschema:"Time range, e.g. 30d, 7d. Default: 30d"`
}
var queryDoraTool = copilot.DefineTool("query_dora_metrics",
"Query DORA metrics (deployment frequency, lead time, change failure rate, MTTR) for a DevLake project",
func(params DoraParams, inv copilot.ToolInvocation) (any, error) {
engine := query.NewEngine(dbURL)
result, err := engine.Execute("dora", map[string]string{
"project": params.Project,
"timeframe": params.Timeframe,
})
return result, err
})
// Tool: query_copilot_usage
type CopilotParams struct {
Project string `json:"project" jsonschema:"DevLake project name"`
Timeframe string `json:"timeframe,omitempty" jsonschema:"Time range. Default: 30d"`
}
var queryCopilotTool = copilot.DefineTool("query_copilot_usage",
"Query GitHub Copilot usage metrics (seats, acceptance rate, languages, editors) for a DevLake project",
func(params CopilotParams, inv copilot.ToolInvocation) (any, error) {
engine := query.NewEngine(dbURL)
return engine.Execute("copilot", map[string]string{
"project": params.Project,
"timeframe": params.Timeframe,
})
})
// Tool: list_connections
var listConnectionsTool = copilot.DefineTool("list_connections",
"List all configured DevLake plugin connections with their health status",
func(params struct{}, inv copilot.ToolInvocation) (any, error) {
client := devlake.NewClient(apiURL)
// ... list connections + test each ...
})
// Tool: check_health
var checkHealthTool = copilot.DefineTool("check_health",
"Check health of all DevLake endpoints and connections",
func(params struct{}, inv copilot.ToolInvocation) (any, error) {
client := devlake.NewClient(apiURL)
return client.Health()
})
// Tool: get_pipeline_status
type PipelineParams struct {
Limit int `json:"limit,omitempty" jsonschema:"Max pipelines to return. Default: 10"`
}
var getPipelinesTool = copilot.DefineTool("get_pipeline_status",
"Get recent DevLake pipeline runs and their status (completed, failed, running)",
func(params PipelineParams, inv copilot.ToolInvocation) (any, error) {
client := devlake.NewClient(apiURL)
// ... list recent pipelines ...
})The tools reuse the query engine from #62 and the existing DevLake API client from internal/devlake/client.go.
Session configuration
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1", // or let user override with --model
Streaming: true,
Tools: []copilot.Tool{
queryDoraTool,
queryCopilotTool,
listConnectionsTool,
checkHealthTool,
getPipelinesTool,
},
SystemMessage: &copilot.SystemMessageConfig{
Content: systemPrompt, // DevLake-specific context
},
OnUserInputRequest: func(req copilot.UserInputRequest, inv copilot.UserInputInvocation) (copilot.UserInputResponse, error) {
// Enable ask_user tool for clarifying questions
fmt.Printf(" %s\n", req.Question)
answer := prompt.Input(" > ")
return copilot.UserInputResponse{Answer: answer, WasFreeform: true}, nil
},
})Streaming output
session.On(func(event copilot.SessionEvent) {
if event.Type == "assistant.message_delta" {
if event.Data.DeltaContent != nil {
fmt.Print(*event.Data.DeltaContent)
}
}
if event.Type == "session.idle" {
fmt.Println() // newline when done
}
})Auth model — separate concerns
| Auth | Purpose | Resolution |
|---|---|---|
| Copilot CLI auth | Model access (GPT-4, Claude, etc.) | Copilot CLI handles this — user must be signed in (copilot auth login) |
| DevLake PAT | Data access (API calls, connections) | Existing token resolution chain (flag → envfile → env → prompt) |
| DB connection | Query execution | State file auto-detection or --db-url flag |
No token sharing between the two auth domains.
Cost transparency
On first use, print a one-time notice:
💡 Insights uses GitHub Copilot to analyze DevLake data.
Each prompt counts against your Copilot premium request quota.
For cost-free queries, use: gh devlake query dora --project my-team
This notice is shown once and suppressed thereafter (tracked via a flag in the state file or a dot file).
Command flags
gh devlake insights [question]
--model Model to use (default: gpt-4.1)
--project Default project for queries (avoids repeated prompting)
--db-url Database connection URL (auto-detected if omitted)
Files to create/modify
| File | Change |
|---|---|
internal/copilot/client.go |
NEW — SDK client lifecycle |
internal/copilot/tools.go |
NEW — custom tool definitions |
internal/copilot/system.go |
NEW — system message with DevLake context |
cmd/insights.go |
NEW — gh devlake insights command |
go.mod |
Add github.com/github/copilot-sdk/go dependency |
Acceptance Criteria
-
gh devlake insights "question"sends a prompt and streams the response -
gh devlake insightsenters interactive multi-turn mode - Custom tools (
query_dora_metrics,query_copilot_usage,list_connections,check_health,get_pipeline_status) are registered and functional - LLM can compose multiple tool calls to answer cross-cutting questions
- Graceful error if Copilot CLI is not installed
- First-use cost transparency notice is displayed
- Auth is separate: Copilot for models, PAT for DevLake, DB URL for queries
- Streaming output for progressive terminal rendering
-
go build ./...andgo test ./...pass - README updated
Target Version
v0.4.x — AI-powered insights over DevLake data.
Dependencies
- Add
gh devlake querycommand with extensible query engine #62 —gh devlake querycommand (provides the data layer that custom tools call) - Add
--jsonoutput flag to read commands #60 —--jsonoutput flag (shared output pattern)
References
- Copilot SDK (Go):
github/copilot-sdk/go— client, session, streaming, custom toolsgo/README.md— full API reference includingDefineTool,SessionConfig, streaming events,OnUserInputRequest, session hooks, transport modesgo/client.go—NewClient,Start,Stop,CreateSessiongo/definetool.go— type-safe tool definition with automatic JSON schema generationgo/session.go—Send,On, event handling- Installation:
go get github.com/github/copilot-sdk/go
- Copilot SDK overview:
github/copilot-sdk— architecture (SDK → JSON-RPC → Copilot CLI server), FAQ, auth methods, BYOK support - Copilot SDK getting started: docs.github.com — prerequisites, installation, first message, streaming
- Copilot SDK blog: Build an agent into any app — architecture rationale, SDK vs CLI relationship
- DevLake domain layer:
apache/incubator-devlake/backend/core/models/domainlayer/— the tables that query tools read from - DevLake API:
apache/incubator-devlake/AGENTS.md— plugin structure, API patterns, three-layer data model internal/devlake/client.go— existing API client methods reused by toolsinternal/query/— query engine from Addgh devlake querycommand with extensible query engine #62, consumed by custom tools