Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Download dependencies
run: go mod download

- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...

- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: coverage.out
continue-on-error: true

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest

Comment on lines +42 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find any existing golangci-lint version pins or references.
fd -a "\.golangci.*"
echo "---"
rg -n "golangci-lint|GOLANGCI_LINT_VERSION|GOLANGCI_LINT" -S

Repository: tutunak/jcli

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Check for .golangci config files including those that might be in .gitignore
git ls-files -o -i --exclude-standard | grep -i golangci
echo "---"
# Check all files including potentially ignored ones
find . -name ".golangci*" -o -name "golangci-lint*" 2>/dev/null | head -20
echo "---"
# Check for version constraints in go.mod
if [ -f go.mod ]; then
  rg "golangci-lint" go.mod
fi
echo "---"
# Read the ci.yaml file to see full context
cat -n .github/workflows/ci.yaml | head -50

Repository: tutunak/jcli

Length of output: 1355


Pin golangci-lint to a specific version instead of latest.

Using version: latest undermines CI reproducibility; other action versions (checkout, setup-go) and the Go version itself are pinned, but golangci-lint is left unpinned and can introduce breaking changes without notice. Specify a concrete version (e.g., v1.61.0 or newer) aligned with your project's requirements.

♻️ Suggested change
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v6
         with:
-          version: latest
+          version: v1.61.0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.61.0
🤖 Prompt for AI Agents
In @.github/workflows/ci.yaml around lines 42 - 46, The CI step named
"golangci-lint" currently uses `version: latest`; replace that with a pinned tag
(e.g., `v1.61.0` or your chosen tested release) to ensure reproducible builds —
update the `golangci-lint` action's `version` input to a concrete version string
and commit the change so the workflow no longer tracks `latest`.

build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, darwin]
goarch: [amd64, arm64]
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
go build -ldflags "-X main.version=${{ github.sha }}" -o jcli-${{ matrix.goos }}-${{ matrix.goarch }} .

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: jcli-${{ matrix.goos }}-${{ matrix.goarch }}
path: jcli-${{ matrix.goos }}-${{ matrix.goarch }}

integration:
runs-on: ubuntu-latest
needs: [test, build]
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Run integration tests
run: go test -v -tags=integration ./tests/integration/...
101 changes: 101 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Release

on:
push:
branches: [master]

permissions:
contents: write

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Run tests
run: go test -v -race ./...

- name: Run integration tests
run: go test -v -tags=integration ./tests/integration/...

release:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Calculate next version
id: version
run: |
# Get the latest tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Latest tag: $LATEST_TAG"

# Extract version numbers
VERSION=${LATEST_TAG#v}
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"

# Get commit messages since last tag
if [ "$LATEST_TAG" = "v0.0.0" ]; then
COMMITS=$(git log --pretty=format:"%s" HEAD)
else
COMMITS=$(git log --pretty=format:"%s" ${LATEST_TAG}..HEAD)
fi

# Determine version bump based on conventional commits
if echo "$COMMITS" | grep -qE "^BREAKING CHANGE:|^[a-z]+!:"; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif echo "$COMMITS" | grep -qE "^feat(\(.+\))?:"; then
MINOR=$((MINOR + 1))
PATCH=0
else
PATCH=$((PATCH + 1))
fi

NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
echo "New version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT

# Check if this version already exists
if git tag | grep -q "^${NEW_VERSION}$"; then
echo "Tag $NEW_VERSION already exists, skipping release"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi

- name: Run GoReleaser
if: steps.version.outputs.skip != 'true'
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ steps.version.outputs.version }}

- name: Create and push tag
if: steps.version.outputs.skip != 'true'
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git tag ${{ steps.version.outputs.version }}
git push origin ${{ steps.version.outputs.version }}
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Binaries
jcli
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary
*.test

# Output of go coverage tool
*.out
coverage.html

# Dependency directories
vendor/

# Go workspace file
go.work

# IDE
.idea/
.vscode/
*.swp
*.swo

# Build output
dist/

# OS files
.DS_Store
Thumbs.db
50 changes: 50 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
version: 2

before:
hooks:
- go mod tidy

builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w -X main.version={{.Version}}

archives:
- format: tar.gz
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- .Os }}_
{{- .Arch }}
format_overrides:
- goos: windows
format: zip

checksum:
name_template: 'checksums.txt'

snapshot:
version_template: "{{ incpatch .Version }}-next"

changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^chore:'

release:
github:
owner: tutunak
name: jcli
draft: false
prerelease: auto
name_template: "{{.Tag}}"
38 changes: 38 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

```bash
make build # Build binary with version from git tags
make test # Run tests with race detection and coverage
make lint # Run golangci-lint
make test-integration # Run integration tests (requires Jira credentials)
go test ./internal/branch/... # Run tests for a specific package
```

## Architecture

jcli is a CLI tool for Jira workflow management. It uses manual argument parsing (no CLI framework).

### Package Structure

- **cmd/** - Command handlers. `root.go` routes to subcommands via switch statement on `os.Args[1]`. Each command file (e.g., `issue_select.go`) contains one handler function.

- **internal/jira/** - Jira API client using REST API v3. `Client` interface allows mocking. `HTTPClient` implements actual API calls with Basic Auth. Note: descriptions use `json.RawMessage` because Jira v3 returns ADF format, not strings.

- **internal/config/** - YAML config at `~/.config/jcli/config.yaml`. Supports `JIRA_API_TOKEN` env var override.

- **internal/state/** - JSON state at `~/.local/state/jcli/state.json`. Tracks currently selected issue.

- **internal/branch/** - Branch name generator. Format: `<ISSUE-KEY>-<normalized-summary>-<random>`. Issue key preserves original case; summary is lowercased.

- **internal/tui/** - Interactive issue selector using `github.com/charmbracelet/huh`.

### Key Design Decisions

- No CLI framework (cobra, urfave/cli) - uses manual `os.Args` parsing
- `Client` interface in jira package enables `MockClient` for testing
- XDG Base Directory paths for config and state
- JQL query includes `assignee = currentUser()` to show only user's assigned issues
34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.PHONY: build test lint clean install all

VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS := -ldflags "-X main.version=$(VERSION)"

all: lint test build

build:
go build $(LDFLAGS) -o jcli .

build-all:
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o dist/jcli-linux-amd64 .
GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o dist/jcli-linux-arm64 .
GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o dist/jcli-darwin-amd64 .
GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o dist/jcli-darwin-arm64 .

test:
go test -v -race -coverprofile=coverage.out ./...

test-integration:
go test -v -tags=integration ./tests/integration/...

lint:
golangci-lint run ./...

clean:
rm -f jcli coverage.out
rm -rf dist/

install: build
cp jcli $(GOPATH)/bin/

coverage: test
go tool cover -html=coverage.out -o coverage.html
Loading