Skip to content

Addressing PR comments#141

Merged
ewega merged 11 commits intomainfrom
claude/add-gh-devlake-query-command
Mar 17, 2026
Merged

Addressing PR comments#141
ewega merged 11 commits intomainfrom
claude/add-gh-devlake-query-command

Conversation

@Claude
Copy link
Contributor

@Claude Claude AI commented Mar 12, 2026

No description provided.

@Claude Claude AI assigned Claude and ewega Mar 12, 2026
@Claude Claude AI linked an issue Mar 12, 2026 that may be closed by this pull request
11 tasks
@Claude Claude AI changed the title [WIP] Add gh devlake query command with extensible query engine Add gh devlake query command with pipelines subcommand Mar 12, 2026
@Claude Claude AI requested a review from ewega March 12, 2026 18:56
@ewega
Copy link
Contributor

ewega commented Mar 12, 2026

@claude[agent] Thanks — this is a good start, but it does not satisfy issue #62 as written, so please continue on the same PR rather than sending this for review yet.

Blocking gaps to address:

  1. The issue explicitly requires the internal/query/ foundation (engine.go, types.go, registry.go, dora.go, copilot.go, pipelines.go). The current PR does not create that package.
  2. query dora and query copilot must return real data, not placeholder errors. Right now the PR fails those acceptance criteria.
  3. The issue explicitly calls for DB URL detection via --db-url → state file (.devlake-local.json / .devlake-azure.json) → DB_URL, and read-only queries against the DevLake domain layer. The current PR skips that architecture entirely.
  4. The accepted scope says SQL/query definitions live outside command handlers. Please align to that design unless you are explicitly renegotiating scope on the issue.

Please revise this PR to implement the agreed architecture and acceptance criteria for #62, including the real internal/query/ package, DB detection, and functional dora / copilot / pipelines commands. If you believe the issue scope itself is infeasible, comment clearly on the issue/PR with the concrete constraint — but do not stop at placeholders without explicit scope change approval.

Validation still expected before handoff:

  • go build ./...
  • go test ./...
  • go vet ./...

@Claude
Copy link
Contributor Author

Claude AI commented Mar 12, 2026

@claude[agent] Thanks — this is a good start, but it does not satisfy issue #62 as written, so please continue on the same PR rather than sending this for review yet.

Blocking gaps to address:...

Understood. I've investigated the DB access requirement and confirmed a hard architectural constraint:

DevLake does not expose database credentials to external clients. The MySQL database is internal to the DevLake container/service and not accessible from the CLI. State files (.devlake-local.json, .devlake-azure.json) contain only HTTP API endpoints, not DB connection strings.

Evidence:

  • Examined state files: only backend, grafana, configUi URLs (no DB credentials)
  • Reviewed all DevLake client code: exclusively HTTP REST API calls
  • Checked upstream apache/incubator-devlake: no client-facing DB access pattern

Two paths forward:

Option A (Recommended): Implement internal/query/ as an API-based query abstraction instead of SQL templates. QueryDef would define API endpoint patterns + response transformations rather than SQL. This satisfies the "extensible query engine" requirement while respecting the architectural reality.

Option B: Request upstream DevLake enhancement to expose metrics API endpoints for DORA/Copilot, then implement as originally specified once available.

Which approach should I take? If Option A, I'll implement the full internal/query/ package with functional DORA/Copilot queries using the available API + client-side calculations. If Option B, the current PR stands as-is pending upstream work.

@Claude Claude AI changed the title Add gh devlake query command with pipelines subcommand Addressing PR comments Mar 12, 2026
@ewega
Copy link
Contributor

ewega commented Mar 12, 2026

@claude[agent] I verified the architecture constraint in the repo and I am accepting Option A. Please continue this PR as an API backed internal/query foundation rather than direct SQL or DB access. Required direction for the next revision: 1) create the real internal/query/ package and move query behavior out of command handlers; 2) keep query pipelines functional but route it through that abstraction; 3) implement as much real dora and copilot behavior as current APIs support; 4) if some metrics remain impossible from current APIs, explain that precisely and propose the narrowest follow on split instead of keeping broad placeholder commands as the end state. The goal is a mergeable query foundation that matches repo architecture and still moves issue #62 materially forward.

@Claude
Copy link
Contributor Author

Claude AI commented Mar 12, 2026

@claude[agent] I verified the architecture constraint in the repo and I am accepting Option A. Please continue this PR as an API backed internal/query foundation rather than direct SQL or DB access....

✅ Implementation complete (811b498)

What was done:

  • Created real internal/query/ package with types.go, engine.go, registry.go
  • Moved query behavior out of command handlers into query abstraction layer
  • Kept query pipelines functional, routed through the new abstraction
  • Implemented DORA query returning available API data (project metadata, blueprint info)
  • Implemented Copilot query returning available API data (connection info)

What's clearly documented as impossible without upstream changes:

  • DORA: Full metric calculations (deployment frequency, lead time, CFR, MTTR) require SQL queries against domain layer tables. No metrics API endpoint exists. Explanation included in response Limitations field.
  • Copilot: Usage metrics (seats, active users, acceptance rates, language/editor breakdowns) are in _tool_gh_copilot_* tables but not exposed via API. Explanation included in response Limitations field.

Validation: All checks pass: go build ./..., go test ./..., go vet ./...

The query foundation is now mergeable and moves #62 materially forward.

@Claude Claude AI changed the title Addressing PR comments Add internal/query package and implement query commands with API-backed abstraction Mar 12, 2026
@ewega
Copy link
Contributor

ewega commented Mar 12, 2026

@claude[agent] Nice improvement — this is much closer. Please address these last items on the same PR before we move it into formal review:

  1. Docs mismatch (blocking): docs/query.md currently says query dora and query copilot are "Not yet implemented", but the commands now do execute and return available data plus limitations. Please update the docs to describe them as limited/partial implementations rather than unimplemented placeholders.

  2. Engine defaults: in internal/query/engine.go, default values currently appear to be applied only for required params. Please either apply defaults consistently for optional params too, or simplify the parameter definitions so the engine behavior is truthful and predictable.

  3. Pipelines limit parsing: in internal/query/pipelines.go, please do not silently ignore invalid string->int parsing for limit. Return a clear error on invalid input instead of quietly falling back.

After updating, rerun:

  • go build ./...
  • go test ./...
  • go vet ./...

Then comment back with the new commit hash.

@Claude
Copy link
Contributor Author

Claude AI commented Mar 12, 2026

@claude[agent] Nice improvement — this is much closer. Please address these last items on the same PR before we move it into formal review:

  1. Docs mismatch (blocking): docs/query.md currentl...

✅ All items addressed (1620812)

  1. Docs updated: docs/query.md now describes dora and copilot as "⚠️ Partial implementation (limited by available API data)" with clear sections on what's available vs. what's not available
  2. Engine defaults fixed: internal/query/engine.go now applies defaults consistently for all optional params (not just required ones)
  3. Limit parsing fixed: internal/query/pipelines.go now returns clear error on invalid string→int parsing instead of silently ignoring

Validation: All checks pass - go build ./..., go test ./..., go vet ./...

@Claude Claude AI changed the title Add internal/query package and implement query commands with API-backed abstraction Addressing PR comments Mar 12, 2026
@ewega
Copy link
Contributor

ewega commented Mar 12, 2026

@claude[agent] One substantive blocker remains before I move this into formal review:

URL/query injection in ListPipelines

  • In internal/devlake/client.go, the status value is concatenated directly into the request path without URL encoding.
  • That means a crafted --status value can inject additional query parameters (for example TASK_COMPLETED&pagesize=999999).
  • Please rebuild this method using url.Values (or equivalent safe encoding) for all query parameters instead of manual string concatenation.

After fixing, rerun:

  • go build ./...
  • go test ./...
  • go vet ./...

Then comment back with the new commit hash.

@Claude
Copy link
Contributor Author

Claude AI commented Mar 12, 2026

@claude[agent] One substantive blocker remains before I move this into formal review:

URL/query injection in ListPipelines...

✅ URL injection vulnerability fixed (5a69bdc)

Replaced manual string concatenation with url.Values for safe query parameter encoding in ListPipelines. All query parameters (status, blueprint_id, page, pagesize) now properly URL-encoded via q.Encode().

Validation: All checks pass - go build ./..., go test ./..., go vet ./...

@ewega ewega marked this pull request as ready for review March 12, 2026 19:51
Copilot AI review requested due to automatic review settings March 12, 2026 19:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

docs/query.md:151

  • The Copilot JSON example shows a nested project object in availableData, but the implementation currently returns projectName, copilotConnectionsFound, and optionally connections. Update the example to match the actual output shape to prevent breaking user scripts.
```json
{
  "project": "my-team",
  "timeframe": "30d",
  "availableData": {
    "project": { "name": "my-team", "blueprint": {...} },
    "connections": [...]
  },
  "limitations": "Copilot metrics in _tool_gh_copilot_* tables require metrics API..."
}

README.md Outdated
Comment on lines +231 to +232
| `gh devlake query dora` | Query DORA metrics (placeholder — requires API) | [query.md](docs/query.md) |
| `gh devlake query copilot` | Query Copilot metrics (placeholder — requires API) | [query.md](docs/query.md) |
cmd/query.go Outdated
Comment on lines +13 to +14
metrics in a structured format (JSON by default, --format table for
human-readable output).
docs/query.md Outdated
"project": "my-team",
"timeframe": "30d",
"availableData": {
"project": { "name": "my-team", "blueprint": {...} }
}

if len(connections) > 0 {
availableData["connections"] = connections
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

README.md Outdated
Comment on lines +231 to +232
| `gh devlake query dora` | Query DORA metrics (placeholder — requires API) | [query.md](docs/query.md) |
| `gh devlake query copilot` | Query Copilot metrics (placeholder — requires API) | [query.md](docs/query.md) |
Comment on lines +61 to +64
if len(connections) > 0 {
availableData["connections"] = connections
}

func Register(def *QueryDef) {
if def == nil || def.Name == "" {
panic("cannot register nil or unnamed query")
}
Comment on lines +32 to +36
// QueryResult wraps the output of a query execution.
type QueryResult struct {
Data interface{} // the actual result data
Metadata map[string]string // optional metadata about the query
}
Comment on lines +35 to +45
// Apply defaults and validate parameters
for _, param := range queryDef.Params {
if _, ok := params[param.Name]; !ok {
// Parameter not provided
if param.Default != "" {
// Apply default value
params[param.Name] = param.Default
} else if param.Required {
// Required parameter missing with no default
return nil, fmt.Errorf("required parameter %q not provided", param.Name)
}
cmd/query.go Outdated
Comment on lines +13 to +14
metrics in a structured format (JSON by default, --format table for
human-readable output).
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Comment on lines +533 to +539
q.Set("blueprint_id", fmt.Sprintf("%d", blueprintID))
}
if page > 0 {
q.Set("page", fmt.Sprintf("%d", page))
}
if pageSize > 0 {
q.Set("pagesize", fmt.Sprintf("%d", pageSize))
if queryPipelinesFormat != "json" && queryPipelinesFormat != "table" {
return fmt.Errorf("invalid --format value %q: must be 'json' or 'table'", queryPipelinesFormat)
}

Comment on lines +29 to +50
func TestQueryPipelines_JSONOutputNoBanner(t *testing.T) {
// Mock DevLake API
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/ping" {
w.WriteHeader(http.StatusOK)
return
}
if r.URL.Path == "/pipelines" {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(devlake.PipelineListResponse{
Pipelines: []devlake.Pipeline{
{
ID: 123,
Status: "TASK_COMPLETED",
FinishedTasks: 10,
TotalTasks: 10,
},
},
Count: 1,
})
return
}
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Comment on lines +533 to +539
q.Set("blueprint_id", fmt.Sprintf("%d", blueprintID))
}
if page > 0 {
q.Set("page", fmt.Sprintf("%d", page))
}
if pageSize > 0 {
q.Set("pagesize", fmt.Sprintf("%d", pageSize))
README.md Outdated

This CLI makes that setup fast and repeatable from the terminal (instead of clicking through the Config UI) — especially when you want to re-run the same configuration across teams.

> **Blog post:** [Beyond Copilot Dashboards: Measuring What AI Actually Changes](<!-- TODO: replace with actual blog URL -->) — why DORA + Copilot correlation matters and what this tool enables.
cmd/query.go Outdated
Comment on lines +7 to +25
var queryCmd = &cobra.Command{
Use: "query",
Short: "Query DevLake data and metrics",
Long: `Query DevLake's aggregated data and metrics.

Retrieve pipeline status plus DORA/Copilot query results in structured
JSON output. Individual subcommands may provide extra formatting options
such as query pipelines --format table for human-readable output.

Examples:
gh devlake query pipelines --project my-team
gh devlake query pipelines --limit 20
gh devlake query pipelines --status TASK_COMPLETED`,
}

func init() {
queryCmd.GroupID = "operate"
rootCmd.AddCommand(queryCmd)
}
Comment on lines +19 to +41
var queryPipelinesCmd = &cobra.Command{
Use: "pipelines",
Short: "Query recent pipeline runs",
Long: `Query recent pipeline runs for a project or across all projects.

Retrieves pipeline execution history with status, timing, and task completion
information. Output is JSON by default; use --format table for human-readable display.

Examples:
gh devlake query pipelines
gh devlake query pipelines --project my-team
gh devlake query pipelines --status TASK_COMPLETED --limit 10
gh devlake query pipelines --format table`,
RunE: runQueryPipelines,
}

func init() {
queryPipelinesCmd.Flags().StringVar(&queryPipelinesProject, "project", "", "Filter by project name")
queryPipelinesCmd.Flags().StringVar(&queryPipelinesStatus, "status", "", "Filter by status (TASK_CREATED, TASK_RUNNING, TASK_COMPLETED, TASK_FAILED)")
queryPipelinesCmd.Flags().IntVar(&queryPipelinesLimit, "limit", 20, "Maximum number of pipelines to return")
queryPipelinesCmd.Flags().StringVar(&queryPipelinesFormat, "format", "json", "Output format (json or table)")
queryCmd.AddCommand(queryPipelinesCmd)
}
Comment on lines +16 to +37
var queryDoraCmd = &cobra.Command{
Use: "dora",
Short: "Query DORA metrics (limited by available API data)",
Long: `Query DORA (DevOps Research and Assessment) metrics for a project.

NOTE: Full DORA metric calculations (deployment frequency, lead time, change
failure rate, MTTR) require SQL queries against DevLake's domain layer tables.
DevLake does not expose database credentials or a metrics API endpoint.

This command returns project metadata and explains what additional API
endpoints would be needed to compute DORA metrics via CLI.

DORA metrics are currently available in Grafana dashboards at your DevLake
Grafana endpoint (shown in 'gh devlake status').`,
RunE: runQueryDora,
}

func init() {
queryDoraCmd.Flags().StringVar(&queryDoraProject, "project", "", "Project name (required)")
queryDoraCmd.Flags().StringVar(&queryDoraTimeframe, "timeframe", "30d", "Time window for metrics (e.g., 7d, 30d, 90d)")
queryCmd.AddCommand(queryDoraCmd)
}
Comment on lines +16 to +38
var queryCopilotCmd = &cobra.Command{
Use: "copilot",
Short: "Query Copilot usage metrics (limited by available API data)",
Long: `Query GitHub Copilot usage metrics for a project.

NOTE: GitHub Copilot usage metrics (total seats, active users, acceptance rates,
language breakdowns, editor usage) are stored in _tool_gh_copilot_* tables and
visualized in Grafana dashboards, but DevLake does not expose a /metrics or
/copilot API endpoint.

This command returns available connection metadata and explains what additional
API endpoints would be needed to retrieve Copilot metrics via CLI.

Copilot metrics are currently available in Grafana dashboards at your DevLake
Grafana endpoint (shown in 'gh devlake status').`,
RunE: runQueryCopilot,
}

func init() {
queryCopilotCmd.Flags().StringVar(&queryCopilotProject, "project", "", "Project name (required)")
queryCopilotCmd.Flags().StringVar(&queryCopilotTimeframe, "timeframe", "30d", "Time window for metrics (e.g., 7d, 30d, 90d)")
queryCmd.AddCommand(queryCopilotCmd)
}
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.

README.md Outdated

This CLI makes that setup fast and repeatable from the terminal (instead of clicking through the Config UI) — especially when you want to re-run the same configuration across teams.

> **Blog post:** [Beyond Copilot Dashboards: Measuring What AI Actually Changes](<!-- TODO: replace with actual blog URL -->) — why DORA + Copilot correlation matters and what this tool enables.
Comment on lines +7 to +9
"github.com/DevExpGBB/gh-devlake/internal/devlake"
"github.com/DevExpGBB/gh-devlake/internal/query"
"github.com/spf13/cobra"
Comment on lines +60 to +65
var disc *devlake.DiscoveryResult
client, disc, err = discoverClient(cfgURL)
if err != nil {
return fmt.Errorf("discovering DevLake: %w", err)
}
_ = disc
Comment on lines +105 to +107
fmt.Println(" " + strings.Repeat("─", 80))
fmt.Printf(" %-6s %-15s %-10s %-20s\n", "ID", "STATUS", "TASKS", "FINISHED AT")
fmt.Println(" " + strings.Repeat("─", 80))
Comment on lines +6 to +8
"github.com/DevExpGBB/gh-devlake/internal/devlake"
"github.com/DevExpGBB/gh-devlake/internal/query"
"github.com/spf13/cobra"
RunE: runQueryDora,
}
cmd.Flags().StringVar(&queryDoraProject, "project", "", "Project name (required)")
cmd.Flags().StringVar(&queryDoraTimeframe, "timeframe", "30d", "Time window for metrics (e.g., 7d, 30d, 90d)")
Comment on lines +6 to +8
"github.com/DevExpGBB/gh-devlake/internal/devlake"
"github.com/DevExpGBB/gh-devlake/internal/query"
"github.com/spf13/cobra"
Comment on lines +30 to +35
Copilot metrics are currently available in Grafana dashboards at your DevLake
Grafana endpoint (shown in 'gh devlake status').`,
RunE: runQueryCopilot,
}
cmd.Flags().StringVar(&queryCopilotProject, "project", "", "Project name (required)")
cmd.Flags().StringVar(&queryCopilotTimeframe, "timeframe", "30d", "Time window for metrics (e.g., 7d, 30d, 90d)")
// PipelineListResponse is the response from GET /pipelines.
type PipelineListResponse struct {
Pipelines []Pipeline `json:"pipelines"`
Count int64 `json:"count"`
Claude AI and others added 11 commits March 16, 2026 20:10
- Created `query` parent command with three subcommands
- Implemented `query pipelines` with full JSON/table output
- Added `ListPipelines` method to devlake client
- Created placeholder `query dora` and `query copilot` commands
- Updated README command reference table
- Added docs/query.md documentation

Note: DORA and Copilot subcommands are placeholders that explain
the architectural constraint (DevLake does not expose metrics APIs).
The pipelines subcommand is fully functional using the existing
/pipelines REST API endpoint.

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
- Move query logic from cmd handlers to internal/query package
- Implement DORA query with available API data + clear limitations
- Implement Copilot query with available API data + clear limitations
- Keep pipelines query functional through the new abstraction
- All validation passes: go build, go test, go vet

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
- Update docs/query.md to describe DORA/Copilot as partial implementations
- Fix engine.go to apply defaults consistently for all optional params
- Fix pipelines.go to return error on invalid limit parsing instead of silently ignoring
- All validation passes: go build, go test, go vet

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
- Replace manual string concatenation with url.Values for query params
- All query parameters now properly URL-encoded via q.Encode()
- Remove unused strings import
- Prevents injection of additional query parameters via --status flag
- All validation passes: go build, go test, go vet

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
1. Fix JSON output corruption: use quiet discovery for JSON, verbose for table
2. Add --format validation: return error for invalid format values
3. Fix nil map safety: initialize params map if nil in engine.Execute
4. Fix swallowed error: propagate ListConnections errors in copilot query
5. Add tests: query_pipelines_test.go validates JSON output and format validation

All validation passes: go build, go test, go vet

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ewega ewega requested a review from Copilot March 17, 2026 00:12
@ewega ewega force-pushed the claude/add-gh-devlake-query-command branch from bbb0ffc to 63a5ffa Compare March 17, 2026 00:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Comment on lines +51 to +65
var err error

if outputJSON || queryPipelinesFormat == "json" {
disc, err := devlake.Discover(cfgURL)
if err != nil {
return fmt.Errorf("discovering DevLake: %w", err)
}
client = devlake.NewClient(disc.URL)
} else {
var disc *devlake.DiscoveryResult
client, disc, err = discoverClient(cfgURL)
if err != nil {
return fmt.Errorf("discovering DevLake: %w", err)
}
_ = disc
case "int":
return strconv.Atoi(param.Default)
default:
return param.Default, nil
Comment on lines +38 to +66
func runQueryDora(cmd *cobra.Command, args []string) error {
if queryDoraProject == "" {
return fmt.Errorf("--project flag is required")
}

disc, err := devlake.Discover(cfgURL)
if err != nil {
return fmt.Errorf("discovering DevLake: %w", err)
}
client := devlake.NewClient(disc.URL)

queryDef, err := query.Get("dora")
if err != nil {
return fmt.Errorf("getting dora query: %w", err)
}

params := map[string]interface{}{
"project": queryDoraProject,
"timeframe": queryDoraTimeframe,
}

engine := query.NewEngine(client)
result, err := engine.Execute(queryDef, params)
if err != nil {
return fmt.Errorf("executing dora query: %w", err)
}

return printJSON(result)
}
Comment on lines +39 to +67
func runQueryCopilot(cmd *cobra.Command, args []string) error {
if queryCopilotProject == "" {
return fmt.Errorf("--project flag is required")
}

disc, err := devlake.Discover(cfgURL)
if err != nil {
return fmt.Errorf("discovering DevLake: %w", err)
}
client := devlake.NewClient(disc.URL)

queryDef, err := query.Get("copilot")
if err != nil {
return fmt.Errorf("getting copilot query: %w", err)
}

params := map[string]interface{}{
"project": queryCopilotProject,
"timeframe": queryCopilotTimeframe,
}

engine := query.NewEngine(client)
result, err := engine.Execute(queryDef, params)
if err != nil {
return fmt.Errorf("executing copilot query: %w", err)
}

return printJSON(result)
}
Comment on lines +99 to +108
t.Cleanup(func() { os.Stdout = orig })

// Run the command
if err := runQueryPipelines(nil, nil); err != nil {
t.Fatalf("runQueryPipelines returned error: %v", err)
}

w.Close()
var buf bytes.Buffer
buf.ReadFrom(r)
@ewega ewega merged commit f6b5872 into main Mar 17, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add gh devlake query command with extensible query engine

3 participants