Skip to content

Commit 123fc63

Browse files
committed
Update outgoing sound, reorg code
1 parent 6989430 commit 123fc63

22 files changed

+159
-95
lines changed

.claude/prompt.txt

Lines changed: 0 additions & 24 deletions
This file was deleted.

.github/SECURITY.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Security Policy
2+
3+
## Reporting a Vulnerability
4+
5+
Please follow our security reporting guidelines at:
6+
https://github.com/codeGROOVE-dev/vulnerability-reports/blob/main/SECURITY.md
7+
8+
This document contains all the specifics for how to submit a security report, including contact information, expected response times, and disclosure policies.
9+
10+
## Security Practices
11+
12+
- GitHub tokens are never logged or stored
13+
- All inputs are validated
14+
- File permissions are restricted (0600/0700)
15+
- Only HTTPS URLs to github.com are allowed
16+
- No shell interpolation of user data

.github/dependabot.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "gomod"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
open-pull-requests-limit: 5
8+
9+
- package-ecosystem: "github-actions"
10+
directory: "/"
11+
schedule:
12+
interval: "weekly"
13+
open-pull-requests-limit: 5

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
6+
push:
7+
branches: [ main ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
name: Test
15+
runs-on: ${{ matrix.os }}
16+
strategy:
17+
matrix:
18+
os: [ubuntu-latest, macos-latest, windows-latest]
19+
go-version: ['1.21']
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- uses: actions/setup-go@v5
25+
with:
26+
go-version: ${{ matrix.go-version }}
27+
cache: true
28+
29+
- name: Install dependencies (Linux)
30+
if: matrix.os == 'ubuntu-latest'
31+
run: sudo apt-get update && sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
32+
33+
- name: Lint
34+
run: make lint
35+
36+
- name: Build
37+
run: make build
38+
39+
- name: Test
40+
run: go test -v -race ./...

Makefile

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
1010
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
1111
LDFLAGS := -X main.version=$(GIT_VERSION) -X main.commit=$(GIT_COMMIT) -X main.date=$(BUILD_DATE)
1212

13-
.PHONY: build clean deps run app-bundle install install-darwin install-unix install-windows
13+
.PHONY: all build clean deps run app-bundle install install-darwin install-unix install-windows
14+
15+
# Default target
16+
all: build
1417

1518
# Install dependencies
1619
deps:
@@ -24,39 +27,38 @@ ifeq ($(shell uname),Darwin)
2427
@echo "Running $(BUNDLE_NAME) from /Applications..."
2528
@open "/Applications/$(BUNDLE_NAME).app"
2629
else
27-
go run .
30+
go run ./cmd/goose
2831
endif
2932

3033
# Build for current platform
31-
build:
34+
build: out
3235
ifeq ($(OS),Windows_NT)
33-
CGO_ENABLED=1 go build -ldflags "-H=windowsgui $(LDFLAGS)" -o $(APP_NAME).exe .
36+
CGO_ENABLED=1 go build -ldflags "-H=windowsgui $(LDFLAGS)" -o out/$(APP_NAME).exe ./cmd/goose
3437
else
35-
CGO_ENABLED=1 go build -ldflags "$(LDFLAGS)" -o $(APP_NAME) .
38+
CGO_ENABLED=1 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME) ./cmd/goose
3639
endif
3740

3841
# Build for all platforms
3942
build-all: build-darwin build-linux build-windows
4043

4144
# Build for macOS
4245
build-darwin:
43-
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-darwin-amd64 .
44-
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-darwin-arm64 .
46+
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-darwin-amd64 ./cmd/goose
47+
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-darwin-arm64 ./cmd/goose
4548

4649
# Build for Linux
4750
build-linux:
48-
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-linux-amd64 .
49-
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-linux-arm64 .
51+
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-linux-amd64 ./cmd/goose
52+
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o out/$(APP_NAME)-linux-arm64 ./cmd/goose
5053

5154
# Build for Windows
5255
build-windows:
53-
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui $(LDFLAGS)" -o out/$(APP_NAME)-windows-amd64.exe .
56+
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -ldflags "-H=windowsgui $(LDFLAGS)" -o out/$(APP_NAME)-windows-amd64.exe ./cmd/goose
5457
CGO_ENABLED=1 GOOS=windows GOARCH=arm64 go build -ldflags "-H=windowsgui $(LDFLAGS)" -o out/$(APP_NAME)-windows-arm64.exe .
5558

5659
# Clean build artifacts
5760
clean:
5861
rm -rf out/
59-
rm -f $(APP_NAME)
6062

6163
# Create out directory
6264
out:
@@ -163,7 +165,7 @@ install-darwin: app-bundle
163165
install-unix: build
164166
@echo "Installing on $(shell uname)..."
165167
@echo "Installing binary to /usr/local/bin..."
166-
@sudo install -m 755 $(APP_NAME) /usr/local/bin/
168+
@sudo install -m 755 out/$(APP_NAME) /usr/local/bin/
167169
@echo "Installation complete! $(APP_NAME) has been installed to /usr/local/bin"
168170

169171
# Install on Windows
@@ -172,7 +174,7 @@ install-windows: build
172174
@echo "Creating program directory..."
173175
@if not exist "%LOCALAPPDATA%\Programs\$(APP_NAME)" mkdir "%LOCALAPPDATA%\Programs\$(APP_NAME)"
174176
@echo "Copying executable..."
175-
@copy /Y "$(APP_NAME).exe" "%LOCALAPPDATA%\Programs\$(APP_NAME)\"
177+
@copy /Y "out\$(APP_NAME).exe" "%LOCALAPPDATA%\Programs\$(APP_NAME)\"
176178
@echo "Installation complete! $(APP_NAME) has been installed to %LOCALAPPDATA%\Programs\$(APP_NAME)"
177179
@echo "You may want to add %LOCALAPPDATA%\Programs\$(APP_NAME) to your PATH environment variable."
178180
# BEGIN: lint-install .
File renamed without changes.

github.go renamed to cmd/goose/github.go

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"regexp"
1213
"runtime"
1314
"strings"
1415
"sync"
@@ -20,9 +21,23 @@ import (
2021
"golang.org/x/oauth2"
2122
)
2223

24+
// githubTokenRegex matches valid GitHub token formats.
25+
var githubTokenRegex = regexp.MustCompile(`^(ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})$`)
26+
27+
// validateGitHubToken validates a GitHub token format.
28+
func validateGitHubToken(token string) error {
29+
if token == "" {
30+
return errors.New("empty token")
31+
}
32+
if !githubTokenRegex.MatchString(token) {
33+
return errors.New("invalid GitHub token format")
34+
}
35+
return nil
36+
}
37+
2338
// initClients initializes GitHub and Turn API clients.
2439
func (app *App) initClients(ctx context.Context) error {
25-
token, err := app.githubToken(ctx)
40+
token, err := app.token(ctx)
2641
if err != nil {
2742
return fmt.Errorf("get github token: %w", err)
2843
}
@@ -44,8 +59,18 @@ func (app *App) initClients(ctx context.Context) error {
4459
return nil
4560
}
4661

47-
// githubToken retrieves the GitHub token using gh CLI.
48-
func (*App) githubToken(ctx context.Context) (string, error) {
62+
// token retrieves the GitHub token from GITHUB_TOKEN env var or gh CLI.
63+
func (*App) token(ctx context.Context) (string, error) {
64+
// Check GITHUB_TOKEN environment variable first
65+
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
66+
token = strings.TrimSpace(token)
67+
if err := validateGitHubToken(token); err != nil {
68+
return "", fmt.Errorf("invalid GITHUB_TOKEN: %w", err)
69+
}
70+
log.Println("Using GitHub token from GITHUB_TOKEN environment variable")
71+
return token, nil
72+
}
73+
4974
// Only check absolute paths for security - never use PATH
5075
var trustedPaths []string
5176
switch runtime.GOOS {
@@ -179,10 +204,19 @@ func (app *App) executeGitHubQuery(ctx context.Context, query string, opts *gith
179204
return result, nil
180205
}
181206

207+
// prResult holds the result of a Turn API query for a PR.
208+
type prResult struct {
209+
err error
210+
turnData *turn.CheckResponse
211+
url string
212+
isOwner bool
213+
wasFromCache bool
214+
}
215+
182216
// fetchPRsInternal is the implementation for PR fetching.
183217
// It returns GitHub data immediately and starts Turn API queries in the background (when waitForTurn=false),
184218
// or waits for Turn data to complete (when waitForTurn=true).
185-
func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incoming []PR, outgoing []PR, err error) {
219+
func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incoming []PR, outgoing []PR, _ error) {
186220
// Use targetUser if specified, otherwise use authenticated user
187221
user := app.currentUser.GetLogin()
188222
if app.targetUser != "" {
@@ -200,9 +234,9 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
200234

201235
// Run both queries in parallel
202236
type queryResult struct {
237+
err error
203238
query string
204239
issues []*github.Issue
205-
err error
206240
}
207241

208242
queryResults := make(chan queryResult, 2)
@@ -236,11 +270,13 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
236270
// Collect results from both queries
237271
var allIssues []*github.Issue
238272
seenURLs := make(map[string]bool)
273+
var queryErrors []error
239274

240275
for range 2 {
241276
result := <-queryResults
242277
if result.err != nil {
243278
log.Printf("[GITHUB] Query failed: %s - %v", result.query, result.err)
279+
queryErrors = append(queryErrors, result.err)
244280
// Continue processing other query results even if one fails
245281
continue
246282
}
@@ -257,6 +293,11 @@ func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incomin
257293
}
258294
log.Printf("[GITHUB] Both searches completed in %v, found %d unique PRs", time.Since(searchStart), len(allIssues))
259295

296+
// If both queries failed, return an error
297+
if len(queryErrors) == 2 {
298+
return nil, nil, fmt.Errorf("all GitHub queries failed: %v", queryErrors)
299+
}
300+
260301
// Limit PRs for performance
261302
if len(allIssues) > maxPRsToProcess {
262303
log.Printf("Limiting to %d PRs for performance (total: %d)", maxPRsToProcess, len(allIssues))
@@ -359,13 +400,6 @@ func (app *App) updatePRData(url string, needsReview bool, isOwner bool, actionR
359400
// fetchTurnDataSync fetches Turn API data synchronously and updates PRs directly.
360401
func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, user string, incoming *[]PR, outgoing *[]PR) {
361402
turnStart := time.Now()
362-
type prResult struct {
363-
err error
364-
turnData *turn.CheckResponse
365-
url string
366-
isOwner bool
367-
wasFromCache bool
368-
}
369403

370404
// Create a channel for results
371405
results := make(chan prResult, len(issues))
@@ -455,17 +489,7 @@ func (app *App) fetchTurnDataSync(ctx context.Context, issues []*github.Issue, u
455489

456490
// fetchTurnDataAsync fetches Turn API data in the background and updates PRs as results arrive.
457491
func (app *App) fetchTurnDataAsync(ctx context.Context, issues []*github.Issue, user string) {
458-
// Log start of Turn API queries
459-
// Start Turn API queries in background
460-
461492
turnStart := time.Now()
462-
type prResult struct {
463-
err error
464-
turnData *turn.CheckResponse
465-
url string
466-
isOwner bool
467-
wasFromCache bool
468-
}
469493

470494
// Create a channel for results
471495
results := make(chan prResult, len(issues))
File renamed without changes.
File renamed without changes.

main.go renamed to cmd/goose/main.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -378,23 +378,24 @@ func (app *App) updatePRs(ctx context.Context) {
378378
// buildCurrentMenuState creates a MenuState representing the current menu items.
379379
func (app *App) buildCurrentMenuState() *MenuState {
380380
// Apply the same filtering as the menu display (stale PR filtering)
381-
var filteredIncoming, filteredOutgoing []PR
381+
staleThreshold := time.Now().Add(-stalePRThreshold)
382382

383-
now := time.Now()
384-
staleThreshold := now.Add(-stalePRThreshold)
385-
386-
for i := range app.incoming {
387-
if !app.hideStaleIncoming || app.incoming[i].UpdatedAt.After(staleThreshold) {
388-
filteredIncoming = append(filteredIncoming, app.incoming[i])
383+
filterStale := func(prs []PR) []PR {
384+
if !app.hideStaleIncoming {
385+
return prs
389386
}
390-
}
391-
392-
for i := range app.outgoing {
393-
if !app.hideStaleIncoming || app.outgoing[i].UpdatedAt.After(staleThreshold) {
394-
filteredOutgoing = append(filteredOutgoing, app.outgoing[i])
387+
var filtered []PR
388+
for i := range prs {
389+
if prs[i].UpdatedAt.After(staleThreshold) {
390+
filtered = append(filtered, prs[i])
391+
}
395392
}
393+
return filtered
396394
}
397395

396+
filteredIncoming := filterStale(app.incoming)
397+
filteredOutgoing := filterStale(app.outgoing)
398+
398399
// Sort PRs the same way the menu does
399400
incomingSorted := sortPRsBlockedFirst(filteredIncoming)
400401
outgoingSorted := sortPRsBlockedFirst(filteredOutgoing)

0 commit comments

Comments
 (0)