Skip to content

Go port - phase 1 - base framework#1500

Open
offbyone wants to merge 1 commit intomainfrom
o1/golang-port/1
Open

Go port - phase 1 - base framework#1500
offbyone wants to merge 1 commit intomainfrom
o1/golang-port/1

Conversation

@offbyone
Copy link
Copy Markdown
Contributor

@offbyone offbyone commented Jan 30, 2026

Begin porting this CLI to go

  • Justfile targets: just go-build, go-test, etc... just ci-all to run CI for both.
  • Conventional pkg/ and cmd/ layout
  • depends on the cobra CLI interface
  • linter rules
  • integrated go CI

The eventual result will be a -beta CLI version in parallel with the C# CLI

  • Did you write/update appropriate tests
  • Release notes updated (if appropriate) — not user-facing yet (parallel Go port)
  • Appropriate logging output
  • Issue linked — tracking via PR stack
  • Docs updated (or issue created)
  • New package licenses are added to ThirdPartyNotices.txt (if applicable)

PR Stack (Go port)

@offbyone offbyone changed the title Go port - phase 1 Go port - phase 1 - base framework Jan 30, 2026
Comment thread .github/workflows/go-ci.yml Fixed
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 30, 2026

Unit Test Results

    1 files      1 suites   24s ⏱️
1 032 tests 1 032 ✅ 0 💤 0 ❌
1 033 runs  1 033 ✅ 0 💤 0 ❌

Results for commit 44a0e25.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
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

This PR introduces the initial Go “foundation” alongside the existing C# CLI, establishing core shared packages, three Cobra-based CLI entrypoints, and build/test/lint automation to support an incremental port.

Changes:

  • Added core Go packages for logging, retry policy, filesystem, env access, and an app container (manual DI) with initial unit tests.
  • Added Go module files plus developer docs and golangci-lint configuration.
  • Added just targets and a dedicated GitHub Actions workflow for Go CI.
Show a summary per file
File Description
pkg/retry/retry.go New retry policy wrapper + helpers (HTTP retryable statuses, generic ExecuteWithResult).
pkg/retry/retry_test.go Unit tests for retry policy behavior and helpers.
pkg/logger/logger.go New logger implementation with warning counting and verbose output.
pkg/logger/logger_test.go Unit tests for logger output and warning counting.
pkg/filesystem/filesystem.go Filesystem provider abstraction (read/write/copy/temp helpers).
pkg/env/env.go Environment variable provider for CLI configuration inputs.
pkg/app/app.go Manual DI “App” container for shared dependencies.
pkg/app/app_test.go Unit tests for App initialization and defaults.
cmd/gei/main.go Cobra root command skeleton for gei (logger wiring + placeholders).
cmd/ado2gh/main.go Cobra root command skeleton for ado2gh (logger wiring + placeholders).
cmd/bbs2gh/main.go Cobra root command skeleton for bbs2gh (logger wiring + placeholders).
go.mod Initializes Go module and declares initial dependencies.
go.sum Locks Go dependency checksums.
justfile Adds Go build/test/lint/publish targets and combined CI target.
.github/workflows/go-ci.yml Adds Go CI workflow (build/test/coverage/lint).
.golangci.yml Adds golangci-lint v2 configuration for the Go code.
GO_DEVELOPMENT.md Adds Go port development guide and workflow documentation.
.gitignore Ignores Go coverage artifacts and .out files.
.github/workflows/CI.yml Minor matrix formatting adjustment (whitespace).

Copilot's findings

  • Files reviewed: 16/19 changed files
  • Comments generated: 10

Comment thread go.mod
Comment on lines +5 to +8
require (
github.com/avast/retry-go/v4 v4.7.0
github.com/spf13/cobra v1.10.2
)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

New third-party Go dependencies are introduced here (e.g., github.com/spf13/cobra, github.com/avast/retry-go/v4), but ThirdPartyNotices.txt does not currently mention them. If this repo’s compliance process requires it, add the corresponding license notices.

Copilot uses AI. Check for mistakes.
Comment thread pkg/retry/retry.go
return err
})
if err != nil {
return result, fmt.Errorf("retry failed after %d attempts: %w", p.maxAttempts, lastErr)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

ExecuteWithResult wraps the error using lastErr, but when the retry loop fails due to context cancellation/deadline, lastErr can be unrelated to the actual failure reason. Consider wrapping err from p.Execute (and optionally include lastErr as additional context) so callers can reliably detect context errors.

Suggested change
return result, fmt.Errorf("retry failed after %d attempts: %w", p.maxAttempts, lastErr)
if lastErr != nil && lastErr != err {
return result, fmt.Errorf("retry failed after %d attempts (last operation error: %v): %w", p.maxAttempts, lastErr, err)
}
return result, fmt.Errorf("retry failed after %d attempts: %w", p.maxAttempts, err)

Copilot uses AI. Check for mistakes.
Comment thread pkg/retry/retry_test.go
Comment on lines +125 to +127
for _, tt := range tests {
t.Run(string(rune(tt.statusCode)), func(t *testing.T) {
got := retry.IsRetryableHTTPStatus(tt.statusCode)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The subtest name uses string(rune(tt.statusCode)), which turns status codes into non-obvious single-rune names (e.g., 200 -> "È") and can make test output hard to interpret. Prefer using a decimal string (e.g., via strconv.Itoa or fmt.Sprintf).

Copilot uses AI. Check for mistakes.
Comment thread justfile
Comment on lines +169 to +176
go-publish-linux:
mkdir -p dist/linux-x64
GOOS=linux GOARCH=amd64 go build -o dist/linux-x64/gei-linux-amd64 ./cmd/gei
GOOS=linux GOARCH=amd64 go build -o dist/linux-x64/ado2gh-linux-amd64 ./cmd/ado2gh
GOOS=linux GOARCH=amd64 go build -o dist/linux-x64/bbs2gh-linux-amd64 ./cmd/bbs2gh
GOOS=linux GOARCH=arm64 go build -o dist/linux-arm64/gei-linux-arm64 ./cmd/gei
GOOS=linux GOARCH=arm64 go build -o dist/linux-arm64/ado2gh-linux-arm64 ./cmd/ado2gh
GOOS=linux GOARCH=arm64 go build -o dist/linux-arm64/bbs2gh-linux-arm64 ./cmd/bbs2gh
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

go-publish-linux builds outputs into dist/linux-arm64/... but only creates dist/linux-x64. Without mkdir -p dist/linux-arm64, the arm64 builds will fail when writing the output file.

Copilot uses AI. Check for mistakes.
Comment thread pkg/logger/logger.go
Comment on lines +39 to +41
// Debug logs a debug message
func (l *Logger) Debug(format string, args ...interface{}) {
l.log("[DEBUG]", format, args...)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

Debug logging is currently always emitted (it calls l.log(...) unconditionally). Since the CLIs call log.Debug("Execution started") in PersistentPreRun, this will print debug output even when --verbose is false. Consider gating Debug behind l.verbose (or a dedicated debug flag/env var) so normal runs remain quiet.

Suggested change
// Debug logs a debug message
func (l *Logger) Debug(format string, args ...interface{}) {
l.log("[DEBUG]", format, args...)
// Debug logs a debug message when verbose mode is enabled
func (l *Logger) Debug(format string, args ...interface{}) {
if l.verbose {
l.log("[DEBUG]", format, args...)
}

Copilot uses AI. Check for mistakes.
Comment thread pkg/logger/logger.go
func (l *Logger) LogWarningCount() {
count := l.GetWarningCount()
if count > 0 {
l.Warning("Total warnings: %d", count)
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

LogWarningCount logs via l.Warning(...), which increments warningCount. That mutates the warning count and can over-count warnings (and changes the value returned by GetWarningCount). Consider logging the summary without incrementing the warning counter (e.g., using an internal log method or Info).

Suggested change
l.Warning("Total warnings: %d", count)
l.log("[WARNING]", "Total warnings: %d", count)

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +105
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()

_, err = io.Copy(destination, source)
return err
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

CopyFile uses os.Create(dst), which creates the destination with default permissions (subject to umask) and may produce world-readable files. Since this CLI can handle credentials/tokens, consider creating the file with a restrictive mode (e.g., 0600) or preserving the source mode to avoid accidentally exposing sensitive output.

Copilot uses AI. Check for mistakes.
Comment thread justfile
# ============================================================================

# Build Go binaries
go-build:
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

go-build writes binaries to dist/... but doesn't ensure the dist directory exists. On a clean checkout, go build -o dist/gei ... will fail. Consider adding mkdir -p dist (or platform-appropriate equivalent) before building.

Suggested change
go-build:
go-build:
mkdir -p dist

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +79
./dist/gei --version
./dist/ado2gh --version
./dist/bbs2gh --version
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

On Windows runners, the go build -o dist/gei ./cmd/gei output is typically dist/gei.exe, so invoking ./dist/gei --version will fail. Consider making this step OS-aware (use .exe on Windows) or derive the executable suffix from go env GOEXE.

Suggested change
./dist/gei --version
./dist/ado2gh --version
./dist/bbs2gh --version
goexe="$(go env GOEXE)"
./dist/gei${goexe} --version
./dist/ado2gh${goexe} --version
./dist/bbs2gh${goexe} --version

Copilot uses AI. Check for mistakes.
Comment thread go.mod
Comment on lines +3 to +4
go 1.25.4

Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

The go directive is set to 1.25.4. The Go toolchain typically expects the go directive to be a language version (major.minor, or major.minor.0), with patch versions specified via a separate toolchain directive if needed. Consider adjusting this to avoid go mod / setup-go parsing failures.

Suggested change
go 1.25.4
go 1.25.0
toolchain go1.25.4

Copilot uses AI. Check for mistakes.
Begin porting this CLI to go

- Justfile targets: `just go-build`, `go-test`, etc... `just ci-all` to run CI for both.
- Conventional `pkg/` and `cmd/` layout
- depends on the cobra CLI interface
- linter rules
- integrated go CI

The eventual result will be a -beta CLI version in parallel with the C# CLI
@github-actions
Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
gei 81% 73% 608
bbs2gh 83% 78% 667
Octoshift 83% 73% 1837
ado2gh 71% 69% 741
Summary 81% (7962 / 9881) 73% (1961 / 2681) 3853

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.

3 participants