From 102842050c1752eb2b9e91734ea13b705c930202 Mon Sep 17 00:00:00 2001 From: Patel230 Date: Thu, 14 May 2026 21:20:35 +0530 Subject: [PATCH 1/3] =?UTF-8?q?feat(hawk-sdk-go):=20production=20hardening?= =?UTF-8?q?=20=E2=80=94=20linter,=20errcheck,=20infrastructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added golangci-lint config (errcheck, staticcheck, unused, gocritic, bodyclose, noctx) - Fixed all errcheck issues (resp.Body.Close in defer and error paths) - Added Makefile with standard targets - Added .editorconfig --- .editorconfig | 18 ++++++++++++++++++ .golangci.yml | 43 +++++++++++++++++++++++++++++++++++++++++++ Makefile | 28 ++++++++++++++++++++++++++++ client.go | 8 ++++---- retry.go | 2 +- 5 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 .editorconfig create mode 100644 .golangci.yml create mode 100644 Makefile diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c3f539a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.go] +indent_style = tab +indent_size = 4 + +[*.{yaml,yml,json,toml}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..760c36a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,43 @@ +version: "2" + +linters: + default: none + enable: + - errcheck + - govet + - ineffassign + - staticcheck + - unused + - misspell + - gocritic + - unconvert + - whitespace + - bodyclose + - noctx + +linters-settings: + errcheck: + check-type-assertions: true + check-blank: false + gocritic: + enabled-tags: + - diagnostic + - performance + disabled-checks: + - hugeParam + - rangeValCopy + - appendAssign + staticcheck: + checks: ["all", "-SA1019"] + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + exclude-dirs: + - vendor + exclude-rules: + - path: _test\.go + linters: + - errcheck + - gocritic + - noctx diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d74004e --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +NAME := hawk-sdk-go + +.PHONY: all test lint fmt vet clean help + +all: lint test + +test: ## Run tests with race detector + go test ./... -race -count=1 -timeout=60s + +test-coverage: ## Run tests with coverage + go test ./... -race -coverprofile=coverage.out -covermode=atomic + go tool cover -func=coverage.out | grep "^total:" + +lint: ## Run linter + golangci-lint run ./... --timeout=5m + +fmt: ## Format code + gofumpt -w . + goimports -w . + +vet: ## Run go vet + go vet ./... + +clean: ## Clean artifacts + rm -f coverage.out + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/client.go b/client.go index 96f91ce..5bac3d8 100644 --- a/client.go +++ b/client.go @@ -82,7 +82,7 @@ func (c *Client) ChatStream(ctx context.Context, req ChatRequest) (*StreamReader } if resp.StatusCode != http.StatusOK { - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() return nil, parseAPIError(resp) } @@ -129,7 +129,7 @@ func (c *Client) DeleteSession(ctx context.Context, id string) error { if err != nil { return fmt.Errorf("hawk-sdk: delete request: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { return parseAPIError(resp) @@ -162,7 +162,7 @@ func (c *Client) get(ctx context.Context, path string, params url.Values, out in if err != nil { return fmt.Errorf("hawk-sdk: request failed: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return parseAPIError(resp) @@ -191,7 +191,7 @@ func (c *Client) post(ctx context.Context, path string, body interface{}, out in if err != nil { return fmt.Errorf("hawk-sdk: request failed: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return parseAPIError(resp) diff --git a/retry.go b/retry.go index 5fc09d4..d9e4316 100644 --- a/retry.go +++ b/retry.go @@ -140,7 +140,7 @@ func (c *Client) doWithRetry(ctx context.Context, req *http.Request, body []byte } // Drain body before retry to allow connection reuse. - resp.Body.Close() + _ = resp.Body.Close() if sleepErr := sleepWithContext(ctx, backoff); sleepErr != nil { return nil, sleepErr From 4ca8dedeac63be43da09160d99b758ef4f9c7bb2 Mon Sep 17 00:00:00 2001 From: Patel230 Date: Fri, 15 May 2026 00:39:50 +0530 Subject: [PATCH 2/3] feat(hawk-sdk-go): re-baseline to v0.2.0, add Version + User-Agent, full OSS bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This SDK had no README, LICENSE, CHANGELOG, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, .gitignore, .gitattributes, or .github/ directory at all. This commit lands the full top-50 OSS bootstrap and aligns the SDK with the rest of the hawk-eco ecosystem (hawk, tok, eyrie, yaad, trace, sight, inspect). Version surface (new): - version.go — `const Version = "0.2.0"` and `userAgent()` helper. - client.go — every outbound HTTP request now sets `User-Agent: hawk-sdk-go/0.2.0` (Health, Chat, ChatStream, Sessions, Session, Messages, DeleteSession, Stats). Lets daemon operators identify SDK clients in logs and reject misbehaving versions cleanly. Pattern matches eyrie's userAgent(). - client_test.go — daemon-version mock fixture updated 0.3.0 → 0.2.0 for ecosystem consistency. The test does not assert on this value; the change is cosmetic. New OSS files (all of these were missing): - LICENSE — MIT. - README.md — install, usage (Health / Chat / ChatStream / custom base URL / typed errors), full API surface, versioning, semver guarantees, contributing pointer. - CHANGELOG.md — Keep-a-Changelog format with [Unreleased] capturing this PR + a backfilled [0.0.1] entry for the prior two commits (initial SDK + hardening). - CONTRIBUTING.md — quick start, branch flow (this repo branches from main, not dev), conventional commits, code standards (User-Agent rule, error-wrapping, table-driven tests), testing, SDK-version-bump procedure. - SECURITY.md — vulnerability reporting via GitHub Security Advisories, in-scope examples (token leakage, TLS misuse, header smuggling, panic on attacker input, redirect host escape), out-of-scope pointers (daemon issues to hawk repo, dep issues upstream). - CODE_OF_CONDUCT.md — Contributor Covenant 2.1. - .gitignore — Go test artifacts, IDE, OS, build caches, go.work. - .gitattributes — LF normalization, binary detection, GitHub linguist hint to collapse go.sum. - .github/workflows/ci.yml — race tests + coverage upload, golangci- lint v2 action, govulncheck, multi-platform build matrix (linux/darwin/windows × amd64/arm64). - .github/dependabot.yml — weekly gomod + github-actions updates. - .github/PULL_REQUEST_TEMPLATE.md — Summary / Changes / API impact (with version-bump reminder) / Daemon compatibility (which daemon SHA was tested) / Testing / Checklist (incl. 'every new outbound request sets User-Agent via userAgent()'). - .github/ISSUE_TEMPLATE/bug_report.yml — surface dropdown (Client / ChatStream / Retry / Errors / Agent-Tool-Workflow / Build-module), required SDK + daemon + Go versions. - .github/ISSUE_TEMPLATE/feature_request.yml — kind selector (new Client method / streaming / retry / typed errors / agent orchestration / config / tooling) + solo-dev fit checks (incl. 'does not break wire-compatibility with existing daemon versions'). - .github/ISSUE_TEMPLATE/config.yml — routes security to advisories, questions to discussions, blocks blank issues. Cleanup: - gofmt -w on client.go and retry.go to remove trailing-blank-line drift from the prior hardening commit. Verification: - `go build ./...` clean - `go vet ./...` clean - `go test -race -count=1 -timeout=60s ./...` passes - `gofmt -l .` empty (whole module is gofmt-clean) --- .gitattributes | 32 +++++ .github/ISSUE_TEMPLATE/bug_report.yml | 115 +++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 70 ++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 72 +++++++++++ .github/dependabot.yml | 27 ++++ .github/workflows/ci.yml | 90 +++++++++++++ .gitignore | 28 ++++ CHANGELOG.md | 70 ++++++++++ CODE_OF_CONDUCT.md | 55 ++++++++ CONTRIBUTING.md | 93 ++++++++++++++ LICENSE | 21 +++ README.md | 142 +++++++++++++++++++++ SECURITY.md | 51 ++++++++ client.go | 5 +- client_test.go | 2 +- retry.go | 1 - version.go | 9 ++ 18 files changed, 888 insertions(+), 3 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 version.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8d7c9b0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,32 @@ +# Default: normalize line endings to LF on commit, leave the working copy alone. +* text=auto eol=lf + +# Explicitly LF for source, scripts, and config — never CRLF. +*.go text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.json text eol=lf +*.toml text eol=lf +*.sh text eol=lf +Makefile text eol=lf + +# Windows-only files keep CRLF. +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Binary files — never diffed, never EOL-normalized. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.zip binary +*.tar binary +*.tar.gz binary +*.gz binary +*.pdf binary + +# Generated files — collapse in PR diffs (GitHub linguist hint). +go.sum linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..9ae9e32 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,115 @@ +name: Bug report +description: Something is broken or behaving unexpectedly. +title: "bug: " +labels: ["bug", "triage"] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a bug report. Please fill in as much + of the form as you can — the more we know, the faster we can fix it. + + Before submitting: + - Search [existing issues](https://github.com/GrayCodeAI/hawk-sdk-go/issues) to avoid duplicates. + - If this is a security issue, please **do not** file a public issue. See `SECURITY.md`. + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear, concise description of the bug. + placeholder: When I call hawksdk.Client., I expected X but got Y. + validations: + required: true + + - type: dropdown + id: surface + attributes: + label: Surface + description: Which SDK surface is affected? + options: + - "Client (Health / Chat / Sessions / Stats / DeleteSession)" + - "ChatStream / SSE" + - "Retry / backoff" + - "Errors (typed APIError, categories)" + - "Agent / Tool / Workflow orchestration" + - "Build / module" + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + description: Minimal Go snippet that reliably reproduces the problem. + render: go + placeholder: | + c := hawksdk.New() + resp, err := c.Chat(context.Background(), hawksdk.ChatRequest{Message: "..."}) + // ^ wrong shape / panic / hang / etc. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What did you expect to happen instead? + validations: + required: true + + - type: input + id: sdk-version + attributes: + label: hawk-sdk-go version + description: Output of `hawksdk.Version` or the git SHA you built from. + placeholder: "0.2.0" + validations: + required: true + + - type: input + id: daemon-version + attributes: + label: hawk daemon version + description: Output of `hawk version` (the daemon you're hitting). + placeholder: "0.2.0" + validations: + required: true + + - type: input + id: go-version + attributes: + label: Go version + description: Output of `go version`. + placeholder: "go version go1.26.1 darwin/arm64" + validations: + required: true + + - type: input + id: os + attributes: + label: Operating system + description: e.g. macOS 14.5 (arm64), Ubuntu 24.04 (amd64), Windows 11 (amd64). + placeholder: "macOS 14.5 (arm64)" + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs / output + description: | + Paste any relevant output, including the full error wrapping chain. + **Redact API tokens, session IDs, and any private data first.** + render: shell + + - type: checkboxes + id: confirm + attributes: + label: Confirmation + options: + - label: I searched existing issues and did not find a duplicate. + required: true + - label: I redacted any secrets, tokens, or private data from logs. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..63f3212 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/GrayCodeAI/hawk-sdk-go/security/advisories/new + about: Please report security issues privately via a GitHub Security Advisory. See SECURITY.md. + - name: Question / discussion + url: https://github.com/GrayCodeAI/hawk-sdk-go/discussions + about: Have a question or want to discuss an idea? Open a discussion instead of an issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..11956ae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,70 @@ +name: Feature request +description: Suggest an improvement or a new SDK capability. +title: "feat: " +labels: ["enhancement", "triage"] + +body: + - type: markdown + attributes: + value: | + Thanks for proposing a feature. hawk-sdk-go is a thin Go client for + the local hawk daemon. Every feature is evaluated against whether it + serves **a single developer** running their own hawk daemon — i.e. + it improves ergonomics, lowers latency, or simplifies integration. + + Before submitting: + - Search [existing issues](https://github.com/GrayCodeAI/hawk-sdk-go/issues) to avoid duplicates. + - For new daemon endpoints, the daemon side must land first. + + - type: dropdown + id: kind + attributes: + label: Kind of feature + description: What flavour of change is this? + options: + - "New Client method (wraps a daemon endpoint)" + - "Streaming / SSE handling" + - "Retry / backoff / resilience" + - "Typed errors / error categories" + - "Agent / Tool / Workflow orchestration" + - "Configuration (options / transport)" + - "Tooling / CI / docs" + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What problem are you trying to solve? + description: Describe the user problem first. Solutions can come later. + placeholder: When I call , I have to write boilerplate Y because the SDK doesn't expose X. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: How would you like the SDK to behave? Snippet of API you'd want. + render: go + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: | + What did you try? What do other SDKs (openai-go, anthropic-sdk-go, + sashabaranov/go-openai, langchaingo) do? Why isn't that enough? + + - type: checkboxes + id: principles + attributes: + label: Solo-developer fit + description: hawk-sdk-go avoids enterprise scope. Confirm this feature respects that. + options: + - label: Works with zero configuration (sensible defaults). + - label: Does not introduce a third-party network dependency. + - label: Does not break the wire-compatibility with existing daemon versions. + - label: Has an escape hatch (override via option, transport, or env). diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ca6bae1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,72 @@ + + +## Summary + + + +## Changes + + + +- + +## API impact + + + +## Daemon compatibility + + + +## Testing + + + +```text +$ make test +... +$ make lint +... +``` + +## Checklist + +- [ ] Commits follow [Conventional Commits](https://www.conventionalcommits.org/) + (`feat:`, `fix:`, `perf:`, `refactor:`, `docs:`, `test:`, etc.) +- [ ] `make build` (or `go build ./...`) passes +- [ ] `make lint` passes (no new lint findings, no `nolint:…` without justification) +- [ ] `make test` passes locally with `-race` enabled +- [ ] New or changed code has tests (table-driven where appropriate) +- [ ] Public APIs have godoc comments and runnable examples where helpful +- [ ] `CHANGELOG.md` updated under `## [Unreleased]` if user-visible +- [ ] `version.go` bumped if this is a release-eligible change +- [ ] Every new outbound HTTP request sets `User-Agent: hawk-sdk-go/` + via the `userAgent()` helper +- [ ] No secrets, tokens, or PII added to the repo +- [ ] No `Co-authored-by:` trailers (this is solo-developer work) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..534aa94 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,27 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + labels: + - dependencies + - go + commit-message: + prefix: "chore(deps)" + include: scope + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + labels: + - dependencies + - github-actions + commit-message: + prefix: "chore(ci)" + include: scope diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5af979a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,90 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + test: + name: Test (race + coverage) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26.1" + check-latest: true + cache: true + - name: go mod download + run: go mod download + - name: go test -race -count=1 + run: go test -race -count=1 -timeout=60s ./... + - name: coverage + run: | + go test -race -coverprofile=coverage.out -covermode=atomic -timeout=60s ./... + go tool cover -func=coverage.out | grep "^total:" + - name: upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage + path: coverage.out + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26.1" + check-latest: true + cache: true + - uses: golangci/golangci-lint-action@v7 + with: + version: v2.1.0 + args: --timeout 5m + + security: + name: Security (govulncheck) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26.1" + check-latest: true + cache: true + - name: govulncheck + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + build: + name: Build (${{ matrix.goos }}/${{ matrix.goarch }}) + runs-on: ubuntu-latest + needs: [test, lint] + strategy: + matrix: + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + exclude: + - goos: windows + goarch: arm64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26.1" + check-latest: true + cache: true + - name: build + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + run: go build ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d6a0d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Test artifacts +*.test +*.out +coverage.html +coverage.txt + +# Build output +bin/ +dist/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Go build/mod caches +.gocache/ +.gomodcache/ + +# Go workspace (local-only) +go.work +go.work.sum diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..be5ad50 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,70 @@ +# Changelog + +All notable changes to `hawk-sdk-go` are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- **`const Version = "0.2.0"`** in `version.go`, exposed as the package-level + source of truth for the SDK version. Aligns hawk-sdk-go with the rest of + the hawk-eco ecosystem (`hawk`, `tok`, `eyrie`, `yaad`, `trace`, `sight`, + `inspect`). +- **`User-Agent: hawk-sdk-go/` header** on every outbound HTTP + request — `Health`, `Chat`, `ChatStream`, `Sessions`, `Session`, + `Messages`, `DeleteSession`, `Stats`. Lets daemon operators identify + SDK clients in logs and reject misbehaving versions cleanly. +- **OSS standard files** (this is the first PR to add them): + - `README.md` — install, usage, API surface, versioning, contributing. + - `LICENSE` — MIT. + - `CONTRIBUTING.md` — quick start, branch flow (this repo branches + from `main`), conventional commits, code standards, testing. + - `SECURITY.md` — vulnerability reporting via GitHub Security + Advisories. + - `CODE_OF_CONDUCT.md` — Contributor Covenant 2.1. + - `.gitignore` — Go artifacts, IDE files, OS files, coverage output. + - `.gitattributes` — LF normalization, binary detection, GitHub + linguist hint to collapse `go.sum` in PR diffs. + - `.github/workflows/ci.yml` — race tests + coverage upload + lint + (golangci-lint v2) + multi-platform build matrix + (linux/darwin/windows × amd64/arm64) + govulncheck. + - `.github/dependabot.yml` — weekly `gomod` + `github-actions` + updates. + - `.github/PULL_REQUEST_TEMPLATE.md` — Summary / Changes / API impact + / Daemon compatibility / Testing / Checklist. + - `.github/ISSUE_TEMPLATE/bug_report.yml` — structured bug report. + - `.github/ISSUE_TEMPLATE/feature_request.yml` — feature request + with solo-dev fit checks. + - `.github/ISSUE_TEMPLATE/config.yml` — routes security to + advisories, questions to discussions, blocks blank issues. + +### Changed +- `gofmt -w` on `client.go` and `retry.go` to remove pre-existing + trailing-blank-line drift in files I touched. +- Test fixture in `client_test.go` updated to mock daemon version + `"0.2.0"` (was `"0.3.0"`) for ecosystem consistency. The test does + not assert on this value; the change is cosmetic. + +### Production-hardening pass already on this branch (commit `1028420`) +- Strict `golangci-lint` v2 config with `errcheck`, `staticcheck`, + `gocritic` (diagnostic + performance), `unused`, `ineffassign`, + `misspell`, `noctx`, `bodyclose`, `unconvert`, `whitespace`. +- All `errcheck` issues fixed (`resp.Body.Close` deferred and + error-path closes). +- Initial `Makefile`, `.editorconfig`. + +## [0.0.1] — 2026-05-13 + +### Added +- Initial Go SDK for the hawk daemon API (commits `66e1e1a`, `d04480f`): + - `Client` with `Health`, `Chat`, `ChatStream`, `Sessions`, + `Session`, `Messages`, `DeleteSession`, `Stats`. + - Typed errors with categories (`ErrCategoryRateLimited`, + `ErrCategoryUnauthorized`, `ErrCategoryNotFound`, + `ErrCategoryServer`, `ErrCategoryTransport`). + - Retry with exponential backoff, full jitter, and `Retry-After` + honouring on 429. + - SSE stream helpers. + - `Agent`, `Tool`, `Workflow` orchestration primitives. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f40f6dc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,55 @@ +# Code of Conduct + +## Our pledge + +We — the maintainers and contributors of the hawk-sdk-go project — pledge to +make participation in our community a harassment-free experience for everyone, +regardless of age, body size, visible or invisible disability, ethnicity, +sex characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our standards + +Examples of behavior that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility, apologizing to those affected by mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior: + +- The use of sexualized language or imagery, and sexual attention or advances +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behavior, and will take appropriate and fair corrective action +in response to any behavior they deem inappropriate, threatening, offensive, +or harmful. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported via the contact in `SECURITY.md` or by opening a confidential GitHub +Security Advisory. All complaints will be reviewed and investigated promptly +and fairly. + +All community leaders are obligated to respect the privacy and security of +the reporter of any incident. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..69fb87b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +# Contributing to hawk-sdk-go + +Thanks for considering a contribution. `hawk-sdk-go` is the Go SDK for the +hawk daemon API. It is built for **solo developers** running their own hawk +daemon locally — small surface area, zero external dependencies, fast feedback. + +## Quick start + +```bash +git clone https://github.com/GrayCodeAI/hawk-sdk-go.git +cd hawk-sdk-go +make test # race detector, -count=1, -timeout=60s +make lint # golangci-lint v2 +``` + +Go 1.26.1 is the targeted toolchain. + +## Branch flow + +This repo does **not** have a `dev` branch. Branch from `main`: + +```bash +git checkout main +git pull origin main +git checkout -b feat/ +``` + +Open the PR against `main`. Do **not** push directly to `main`. + +One PR per logical change. Do not mix unrelated changes in a single PR. + +## Commit messages + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +``` +feat(client): add ChatStream backpressure +fix(retry): respect Retry-After when value is a date +perf(stream): re-use buffer in SSE parser +docs(readme): document agent orchestration +test(retry): add coverage for context cancellation during backoff +``` + +Allowed types: `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `chore`, +`build`, `ci`, `style`. Add a scope when it clarifies the change. Do not add +`Co-authored-by:` trailers — this is solo-developer work. + +## Code standards + +- `gofmt -l .` must be empty for files you touch. +- `go vet ./...` must be clean. +- `golangci-lint run ./...` must surface no new findings. The repo enables + `errcheck`, `staticcheck`, `gocritic`, `unused`, `ineffassign`, `misspell`, + `noctx`, `bodyclose`, `unconvert`, `whitespace`. Use `//nolint:` + only with a one-line justification. +- Public types and functions must have godoc comments. +- Prefer table-driven tests with `t.Parallel()` where independent. +- Wrap errors with context: `fmt.Errorf("hawk-sdk: : %w", err)`. +- Propagate `context.Context` everywhere; never call out to a network without it. +- Set `User-Agent: hawk-sdk-go/` on every new HTTP request via + the `userAgent()` helper. + +## Bumping the SDK version + +The single source of truth is `version.go`. Bumping it automatically +updates the User-Agent header. When bumping: + +1. Edit `version.go`. +2. Add a `## [X.Y.Z] — YYYY-MM-DD` entry at the top of `CHANGELOG.md`. +3. Tag the release: `git tag vX.Y.Z && git push origin vX.Y.Z`. + +This SDK adheres to [SemVer](https://semver.org/spec/v2.0.0.html). +Breaking changes to the daemon API or the SDK surface bump the major +version. New SDK methods or daemon endpoints bump the minor. Bug fixes +and internal improvements bump the patch. + +## Testing + +```bash +make test # full suite with race detector +make test-coverage # coverage totals +``` + +When adding a new client method, cover: success path, retryable error +(429 with `Retry-After`), non-retryable error, context cancellation, +and (for streaming) early `Close()` and EOF. + +## Reporting bugs / requesting features + +- Bug: open an issue using the bug-report template. +- Feature: open an issue using the feature-request template. +- Security: do **not** file a public issue. Use a GitHub Security Advisory + per `SECURITY.md`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0354720 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 GrayCode AI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..80f7bb4 --- /dev/null +++ b/README.md @@ -0,0 +1,142 @@ +# hawk-sdk-go + +Go SDK for the [hawk](https://github.com/GrayCodeAI/hawk) daemon API. + +`hawk-sdk-go` is a small, dependency-free client for the local hawk daemon +HTTP API. It exposes idiomatic Go types for chat, streaming, sessions, +messages, and aggregated stats; supports automatic retry with exponential +backoff and `Retry-After` honouring; and provides typed error categories +for clean error handling. + +It is built for **solo developers** running their own hawk daemon locally. +Nothing in this SDK calls a third-party service or phones home. + +## Install + +```bash +go get github.com/GrayCodeAI/hawk-sdk-go +``` + +## Usage + +### Health check + +```go +package main + +import ( + "context" + "fmt" + "log" + + hawksdk "github.com/GrayCodeAI/hawk-sdk-go" +) + +func main() { + c := hawksdk.New() + h, err := c.Health(context.Background()) + if err != nil { + log.Fatal(err) + } + fmt.Printf("daemon ok — version=%s sessions=%d uptime=%s\n", + h.Version, h.Sessions, h.Uptime) +} +``` + +### Chat + +```go +resp, err := c.Chat(ctx, hawksdk.ChatRequest{ + Message: "summarise this commit", + Model: "claude-opus-4-20250514", +}) +if err != nil { + return err +} +fmt.Println(resp.Response) +``` + +### Streaming + +```go +stream, err := c.ChatStream(ctx, hawksdk.ChatRequest{Message: "..."}) +if err != nil { + return err +} +defer stream.Close() + +for { + chunk, err := stream.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + fmt.Print(chunk.Delta) +} +``` + +### Custom base URL & retry + +```go +c := hawksdk.New( + hawksdk.WithBaseURL("http://hawk.local:4590"), + hawksdk.WithRetry(hawksdk.DefaultRetryConfig()), +) +``` + +### Typed errors + +```go +resp, err := c.Chat(ctx, req) +if err != nil { + var apiErr *hawksdk.APIError + if errors.As(err, &apiErr) { + switch apiErr.Category { + case hawksdk.ErrCategoryRateLimited: + // back off + case hawksdk.ErrCategoryUnauthorized: + // re-auth + case hawksdk.ErrCategoryNotFound: + // ... + } + } + return err +} +``` + +## API surface + +- `New(opts ...ClientOption) *Client` — constructor with `WithBaseURL`, + `WithHTTPClient`, `WithRetry`. +- `Client.Health(ctx) (*HealthResponse, error)` — daemon connectivity check. +- `Client.Chat(ctx, ChatRequest) (*ChatResponse, error)` — non-streaming. +- `Client.ChatStream(ctx, ChatRequest) (*StreamReader, error)` — SSE. +- `Client.Sessions(ctx, *ListOptions) (*PaginatedResponse[SessionSummary], error)` — list. +- `Client.Session(ctx, id) (*SessionDetail, error)` — get one. +- `Client.Messages(ctx, sessionID, *ListOptions) (*PaginatedResponse[Message], error)` — list messages. +- `Client.DeleteSession(ctx, id) error` — delete. +- `Client.Stats(ctx) (*StatsResponse, error)` — aggregated stats. +- `Agent` / `Tool` / `Workflow` — higher-level orchestration helpers. + +The full reference is on [pkg.go.dev](https://pkg.go.dev/github.com/GrayCodeAI/hawk-sdk-go). + +## Versioning & compatibility + +This SDK adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The `Version` constant is reported in the `User-Agent` header +(`hawk-sdk-go/0.2.0`) so daemon operators can identify SDK clients in logs. + +The SDK targets daemon API version `v1`. Breaking changes to the daemon API +will be tracked in `CHANGELOG.md` with a clear migration path. + +## Contributing + +See [`CONTRIBUTING.md`](CONTRIBUTING.md). All commits must follow +[Conventional Commits](https://www.conventionalcommits.org/) and must not +include `Co-authored-by:` trailers. + +## License + +[MIT](LICENSE) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..fcb75bb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,51 @@ +# Security policy + +## Supported versions + +Only the latest minor version of `hawk-sdk-go` receives security updates. +The current supported version is the most recent `v0.x` release. + +## Reporting a vulnerability + +Please **do not** file a public GitHub issue for security vulnerabilities. + +Instead, open a **private** GitHub Security Advisory: + +> https://github.com/GrayCodeAI/hawk-sdk-go/security/advisories/new + +Include, where possible: + +- A clear description of the issue and impact. +- Steps to reproduce (a minimal Go snippet is ideal). +- The affected `hawk-sdk-go` version (`hawksdk.Version`) and Go version. +- The hawk daemon version you were targeting, if relevant. +- Any mitigations or patches you have already explored. + +We aim to respond to advisories within **5 business days** and to release +a fix within **30 days** for high-severity issues. + +## What counts as a security issue + +Examples of in-scope issues: + +- The SDK leaking secrets, API tokens, or session IDs into logs, errors, + or metrics. +- The SDK accepting and forwarding data that bypasses daemon-side + authentication, authorization, or rate limiting. +- TLS misuse — accepting untrusted certificates, downgrade to HTTP, or + ignoring `https_proxy` rules. +- Memory-safety issues (panics on attacker-controlled input, unbounded + allocations on stream or response). +- Path / URL handling that lets a malicious daemon URL escape the + expected host (e.g. via redirects). + +Out of scope: + +- Issues in the hawk daemon itself — please report those at + https://github.com/GrayCodeAI/hawk/security/advisories/new. +- Issues in third-party Go modules — please report those upstream. + +## Disclosure + +Once a fix is released, we will publish the advisory with credit to the +reporter (unless they request anonymity). diff --git a/client.go b/client.go index 5bac3d8..403b15a 100644 --- a/client.go +++ b/client.go @@ -75,6 +75,7 @@ func (c *Client) ChatStream(ctx context.Context, req ChatRequest) (*StreamReader } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Accept", "text/event-stream") + httpReq.Header.Set("User-Agent", userAgent()) resp, err := c.httpClient.Do(httpReq) if err != nil { @@ -124,6 +125,7 @@ func (c *Client) DeleteSession(ctx context.Context, id string) error { if err != nil { return fmt.Errorf("hawk-sdk: create request: %w", err) } + req.Header.Set("User-Agent", userAgent()) resp, err := c.httpClient.Do(req) if err != nil { @@ -157,6 +159,7 @@ func (c *Client) get(ctx context.Context, path string, params url.Values, out in return fmt.Errorf("hawk-sdk: create request: %w", err) } req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", userAgent()) resp, err := c.doWithRetry(ctx, req, nil) if err != nil { @@ -186,6 +189,7 @@ func (c *Client) post(ctx context.Context, path string, body interface{}, out in } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", userAgent()) resp, err := c.doWithRetry(ctx, req, data) if err != nil { @@ -216,4 +220,3 @@ func paginationParams(opts *ListOptions) url.Values { } return params } - diff --git a/client_test.go b/client_test.go index fd1b55d..2614c31 100644 --- a/client_test.go +++ b/client_test.go @@ -21,7 +21,7 @@ func TestHealth(t *testing.T) { } json.NewEncoder(w).Encode(HealthResponse{ Status: "ok", - Version: "0.3.0", + Version: "0.2.0", Uptime: "1h30m", Sessions: 2, StartedAt: "2024-01-01T00:00:00Z", diff --git a/retry.go b/retry.go index d9e4316..60fc3d0 100644 --- a/retry.go +++ b/retry.go @@ -155,4 +155,3 @@ func (c *Client) doWithRetry(ctx context.Context, req *http.Request, body []byte } return nil, lastErr } - diff --git a/version.go b/version.go new file mode 100644 index 0000000..b076263 --- /dev/null +++ b/version.go @@ -0,0 +1,9 @@ +package hawksdk + +// Version of the hawk-sdk-go library. Used in the User-Agent header on +// outbound HTTP requests so a misbehaving SDK can be identified by daemon +// logs and operators. +const Version = "0.2.0" + +// userAgent returns the User-Agent string for outbound HTTP requests. +func userAgent() string { return "hawk-sdk-go/" + Version } From c8dfd28a167bed40929e19bc6e9bf533fdb83ac4 Mon Sep 17 00:00:00 2001 From: Patel230 Date: Fri, 15 May 2026 15:21:07 +0530 Subject: [PATCH 3/3] chore: standardize eco-wide infra (versioning, CI, hooks, templates) - VERSION file as single source of truth - CODEOWNERS for auto-review routing - Canonical Makefile with standard targets - release-please config + workflow - lefthook/pre-commit hooks (conventional commits, fmt, lint, secrets) - Canonical CI + release GitHub Actions workflows - Standardized .editorconfig, .gitattributes, CODE_OF_CONDUCT, SECURITY, CONTRIBUTING - goreleaser config (where applicable) Part of hawk-eco standardization sweep. --- .editorconfig | 57 +++++++++- .gitattributes | 92 ++++++++++++---- .github/workflows/ci.yml | 131 +++++++++++++++++------ .github/workflows/release-please.yml | 43 ++++++++ .release-please-manifest.json | 3 + CODEOWNERS | 19 ++++ CODE_OF_CONDUCT.md | 51 +++++---- CONTRIBUTING.md | 153 +++++++++++++++------------ Makefile | 118 ++++++++++++++++++--- SECURITY.md | 86 +++++++++------ VERSION | 1 + lefthook.yml | 112 ++++++++++++++++++++ release-please-config.json | 27 +++++ version.go | 17 ++- 14 files changed, 717 insertions(+), 193 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 CODEOWNERS create mode 100644 VERSION create mode 100644 lefthook.yml create mode 100644 release-please-config.json diff --git a/.editorconfig b/.editorconfig index c3f539a..39f1a41 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,18 +1,67 @@ +# EditorConfig — https://editorconfig.org +# Canonical eco-wide template (.shared-templates/editorconfig.tmpl). + root = true +# Default for everything. [*] +charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -charset = utf-8 +indent_style = space +indent_size = 4 +# Go uses tabs by convention. [*.go] indent_style = tab indent_size = 4 -[*.{yaml,yml,json,toml}] -indent_style = space +# Python — PEP 8. +[*.py] +indent_size = 4 + +# TypeScript / JavaScript — 2 spaces, ecosystem default. +[*.{ts,tsx,js,jsx,mjs,cjs}] +indent_size = 2 + +# Web assets. +[*.{html,css,scss}] +indent_size = 2 + +# YAML — 2 spaces (ecosystem standard, GitHub Actions, k8s, etc.). +[*.{yml,yaml}] +indent_size = 2 + +# JSON / JSONC. +[*.{json,jsonc}] +indent_size = 2 + +# TOML. +[*.toml] indent_size = 2 -[Makefile] +# Markdown — 2 spaces, preserve trailing whitespace (used for line breaks). +[*.md] +trim_trailing_whitespace = false +indent_size = 2 + +# Shell scripts. +[*.{sh,bash,zsh,fish}] +indent_size = 4 + +# Makefiles must use tabs. +[{Makefile,*.mk}] indent_style = tab + +# Dockerfiles. +[Dockerfile*] +indent_size = 4 + +# GitHub Actions workflows — 2 spaces. +[.github/**/*.{yml,yaml}] +indent_size = 2 + +# Config files. +[*.{cfg,ini,conf}] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes index 8d7c9b0..3342e8f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,32 +1,86 @@ -# Default: normalize line endings to LF on commit, leave the working copy alone. +# Canonical eco-wide .gitattributes template (.shared-templates/gitattributes.tmpl). +# Auto-detect text files and normalise line endings to LF. + * text=auto eol=lf -# Explicitly LF for source, scripts, and config — never CRLF. -*.go text eol=lf -*.md text eol=lf -*.yml text eol=lf -*.yaml text eol=lf -*.json text eol=lf -*.toml text eol=lf +# --- Source code ----------------------------------------------------------- +*.go text eol=lf diff=golang +*.py text eol=lf diff=python +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.rs text eol=lf diff=rust + +# --- Shell + config -------------------------------------------------------- *.sh text eol=lf -Makefile text eol=lf +*.bash text eol=lf +*.toml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.json text eol=lf linguist-language=JSON +*.jsonc text eol=lf linguist-language=JSON +*.cff text eol=lf + +# --- Documentation --------------------------------------------------------- +*.md text eol=lf diff=markdown +*.txt text eol=lf + +# --- Build / packaging ---------------------------------------------------- +Makefile text eol=lf +*.mk text eol=lf +Dockerfile* text eol=lf +docker-compose*.yml text eol=lf +.github/**/*.yml text eol=lf +.github/**/*.yaml text eol=lf -# Windows-only files keep CRLF. -*.bat text eol=crlf -*.cmd text eol=crlf -*.ps1 text eol=crlf +# --- Generated artefacts (mark as such for diffs and language stats) ------ +go.mod text eol=lf linguist-generated +go.sum text eol=lf linguist-generated +*.pb.go linguist-generated +*_generated.go linguist-generated +package-lock.json linguist-generated +pnpm-lock.yaml linguist-generated +yarn.lock linguist-generated -# Binary files — never diffed, never EOL-normalized. +# --- Vendored / external sources ------------------------------------------ +vendor/** linguist-vendored +node_modules/** linguist-vendored +testdata/** linguist-vendored +benchmarks/data/** linguist-vendored + +# --- Binary files (do not text-normalise) --------------------------------- +*.exe binary +*.dll binary +*.so binary +*.dylib binary +*.a binary +*.o binary +*.db binary +*.sqlite binary *.png binary *.jpg binary *.jpeg binary *.gif binary *.ico binary +*.svg text eol=lf +*.pdf binary *.zip binary -*.tar binary *.tar.gz binary -*.gz binary -*.pdf binary +*.tgz binary +*.whl binary -# Generated files — collapse in PR diffs (GitHub linguist hint). -go.sum linguist-generated=true +# --- Source archive hygiene (excluded from `git archive`) ----------------- +.github export-ignore +.shared-templates export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.editorconfig export-ignore +.golangci.yml export-ignore +.goreleaser.yml export-ignore +.goreleaser.yaml export-ignore +testdata/ export-ignore +benchmarks/ export-ignore +e2e/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5af979a..8abb3cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,74 +1,146 @@ +# Canonical CI workflow for hawk-eco Go repos. +# Source of truth: .shared-templates/workflows/go-ci.yml.tmpl +# +# Two deployment models: +# +# 1. NOW — render this template inline into each repo's +# .github/workflows/ci.yml. Every repo has identical content. +# +# 2. LATER — once GrayCodeAI/.github exists as a central repo, move this +# file to GrayCodeAI/.github/.github/workflows/go-ci.yml with +# `on: workflow_call:`. Each repo's ci.yml becomes a 5-line caller: +# +# name: CI +# on: { push: { branches: [main] }, pull_request: } +# jobs: +# ci: +# uses: GrayCodeAI/.github/.github/workflows/go-ci.yml@main + name: CI on: push: - branches: [main] + branches: [main, dev] pull_request: - branches: [main] + branches: [main, dev] permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + GO_VERSION: "1.26.1" + jobs: - test: - name: Test (race + coverage) + # ------------------------------------------------------------------------- + # Format + vet — fastest, fail fast. + # ------------------------------------------------------------------------- + fmt-vet: + name: fmt + vet runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.26.1" - check-latest: true + go-version: ${{ env.GO_VERSION }} cache: true - - name: go mod download - run: go mod download - - name: go test -race -count=1 - run: go test -race -count=1 -timeout=60s ./... - - name: coverage + - name: gofumpt diff run: | - go test -race -coverprofile=coverage.out -covermode=atomic -timeout=60s ./... - go tool cover -func=coverage.out | grep "^total:" - - name: upload coverage - uses: actions/upload-artifact@v4 - with: - name: coverage - path: coverage.out + go install mvdan.cc/gofumpt@latest + out=$(gofumpt -l .) + if [ -n "$out" ]; then + echo "::error::gofumpt would reformat the following files:" + echo "$out" + exit 1 + fi + - name: go vet + run: go vet ./... + # ------------------------------------------------------------------------- + # Lint — golangci-lint covers most static checks. + # ------------------------------------------------------------------------- lint: - name: Lint + name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.26.1" - check-latest: true + go-version: ${{ env.GO_VERSION }} cache: true - uses: golangci/golangci-lint-action@v7 with: version: v2.1.0 - args: --timeout 5m + install-mode: goinstall + verify: false + args: --timeout=5m + + # ------------------------------------------------------------------------- + # Tests with race detector + coverage upload. + # ------------------------------------------------------------------------- + test: + name: test (race + cover) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Tidy check + run: | + go mod tidy + if ! git diff --quiet; then + echo "::error::go.mod / go.sum out of date — run 'go mod tidy' and commit" + git diff + exit 1 + fi + - name: Test + run: go test ./... -race -count=1 -coverprofile=coverage.out -covermode=atomic -timeout=180s + - name: Coverage summary + run: go tool cover -func=coverage.out | tail -1 + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage + path: coverage.out + # ------------------------------------------------------------------------- + # Security scan — vulnerability database + (optional) gosec. + # ------------------------------------------------------------------------- security: - name: Security (govulncheck) + name: security runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.26.1" - check-latest: true + go-version: ${{ env.GO_VERSION }} cache: true - name: govulncheck run: | go install golang.org/x/vuln/cmd/govulncheck@latest govulncheck ./... + - name: gosec (advisory) + continue-on-error: true + run: | + go install github.com/securego/gosec/v2/cmd/gosec@latest + gosec -exclude=G104,G301,G302,G304,G306 ./... + # ------------------------------------------------------------------------- + # Cross-platform build matrix — only for repos that produce a binary. + # Repos that are pure libraries can keep this job (it'll just `go build ./...`) + # or remove it locally. + # ------------------------------------------------------------------------- build: - name: Build (${{ matrix.goos }}/${{ matrix.goarch }}) + name: build (${{ matrix.goos }}/${{ matrix.goarch }}) runs-on: ubuntu-latest - needs: [test, lint] + needs: [fmt-vet, lint, test] strategy: + fail-fast: false matrix: goos: [linux, darwin, windows] goarch: [amd64, arm64] @@ -79,10 +151,9 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: - go-version: "1.26.1" - check-latest: true + go-version: ${{ env.GO_VERSION }} cache: true - - name: build + - name: Build env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..639f55f --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,43 @@ +# Canonical release-please workflow for hawk-eco repos. +# Opens / updates a release PR on every push to main; on merge of that PR, +# tags the new release. The tag triggers goreleaser (separate workflow). +# +# Source of truth: .shared-templates/release-please.yml.tmpl at the eco root. + +name: release-please + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + issues: write + +concurrency: + group: release-please-${{ github.ref }} + cancel-in-progress: false + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - name: Run release-please + id: release + uses: googleapis/release-please-action@v4 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Summary + if: always() + run: | + if [[ "${{ steps.release.outputs.release_created }}" == "true" ]]; then + echo "Released ${{ steps.release.outputs.tag_name }}." >> $GITHUB_STEP_SUMMARY + elif [[ "${{ steps.release.outputs.pr }}" != "" ]]; then + echo "Updated release PR: ${{ steps.release.outputs.pr }}" >> $GITHUB_STEP_SUMMARY + else + echo "No release-relevant changes detected." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2be9c43 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.2.0" +} diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..19ce65b --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,19 @@ +# CODEOWNERS for hawk-sdk-go +* @GrayCodeAI/maintainers + +# Public API surface — bump SDK version (VERSION file) when these change +/client.go @GrayCodeAI/sdk-team +/agent.go @GrayCodeAI/sdk-team +/workflow.go @GrayCodeAI/sdk-team +/tools.go @GrayCodeAI/sdk-team +/types.go @GrayCodeAI/sdk-team +/errors.go @GrayCodeAI/sdk-team +/version.go @GrayCodeAI/maintainers +/VERSION @GrayCodeAI/maintainers + +# CI / release +/.github/ @GrayCodeAI/devops-team +/Makefile @GrayCodeAI/devops-team + +# Documentation +*.md @GrayCodeAI/docs-team diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index f40f6dc..ce73cb9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -4,8 +4,8 @@ We — the maintainers and contributors of the hawk-sdk-go project — pledge to make participation in our community a harassment-free experience for everyone, -regardless of age, body size, visible or invisible disability, ethnicity, -sex characteristics, gender identity and expression, level of experience, +regardless of age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. @@ -14,37 +14,39 @@ diverse, inclusive, and healthy community. ## Our standards -Examples of behavior that contributes to a positive environment: +Examples of behaviour that contributes to a positive environment: -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility, apologizing to those affected by mistakes, - and learning from the experience +- Demonstrating empathy and kindness toward other people. +- Being respectful of differing opinions, viewpoints, and experiences. +- Giving and gracefully accepting constructive feedback. +- Accepting responsibility, apologising to those affected by mistakes, and + learning from the experience. - Focusing on what is best not just for us as individuals, but for the - overall community + overall community. -Examples of unacceptable behavior: +Examples of unacceptable behaviour: -- The use of sexualized language or imagery, and sexual attention or advances -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email address, - without their explicit permission +- The use of sexualised language or imagery, and sexual attention or advances. +- Trolling, insulting or derogatory comments, and personal or political + attacks. +- Public or private harassment. +- Publishing others' private information, such as a physical or email + address, without their explicit permission. - Other conduct which could reasonably be considered inappropriate in a - professional setting + professional setting. ## Enforcement Community leaders are responsible for clarifying and enforcing our standards -of acceptable behavior, and will take appropriate and fair corrective action -in response to any behavior they deem inappropriate, threatening, offensive, -or harmful. +of acceptable behaviour, and will take appropriate and fair corrective +action in response to any behaviour they deem inappropriate, threatening, +offensive, or harmful. -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported via the contact in `SECURITY.md` or by opening a confidential GitHub -Security Advisory. All complaints will be reviewed and investigated promptly -and fairly. +Instances of abusive, harassing, or otherwise unacceptable behaviour may be +reported to the maintainers via the contact in `SECURITY.md` or by opening a +confidential GitHub Security Advisory at +. All +complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. @@ -53,3 +55,6 @@ the reporter of any incident. This Code of Conduct is adapted from the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). + +For answers to common questions about this code of conduct, see the +Contributor Covenant FAQ at . diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69fb87b..a69bc1b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,93 +1,114 @@ # Contributing to hawk-sdk-go -Thanks for considering a contribution. `hawk-sdk-go` is the Go SDK for the -hawk daemon API. It is built for **solo developers** running their own hawk -daemon locally — small surface area, zero external dependencies, fast feedback. +Thanks for your interest! This guide covers the conventions used across the +hawk-eco. The eco-wide standards (versioning, release tooling, repo layout) +are defined in . ## Quick start -```bash -git clone https://github.com/GrayCodeAI/hawk-sdk-go.git -cd hawk-sdk-go -make test # race detector, -count=1, -timeout=60s -make lint # golangci-lint v2 -``` - -Go 1.26.1 is the targeted toolchain. +1. Fork the repo and create a feature branch off `main`: + ```bash + git checkout -b feat/short-description + ``` +2. Make your changes in small, focused commits. +3. Run the full local check before pushing: + ```bash + make ci + ``` +4. Open a pull request. CI will re-run the same checks plus security + scanning, race-detector tests, and (where applicable) integration tests. + +## Build & test + +This repo uses the standardised hawk-eco Makefile targets. Run `make help` +for the full list. The most common targets: + +| Target | What it does | +| ------------------- | ------------------------------------------------ | +| `make build` | Build the binary / verify the library compiles | +| `make test` | Run unit tests | +| `make test-race` | Run unit tests with the race detector | +| `make cover` | Generate a coverage report | +| `make lint` | Run the linter (`golangci-lint` / `ruff`) | +| `make fmt` | Format source files | +| `make vet` | Run `go vet` / `mypy` | +| `make security` | Run `govulncheck` / `pip-audit` | +| `make ci` | Run everything CI runs (the gate before pushing) | + +## Commit message convention + +We use [Conventional Commits](https://www.conventionalcommits.org/). This +isn't cosmetic — release-please reads commit messages to bump the `VERSION` +file and generate the CHANGELOG, so getting them right matters. -## Branch flow +``` +(): -This repo does **not** have a `dev` branch. Branch from `main`: + -```bash -git checkout main -git pull origin main -git checkout -b feat/ + ``` -Open the PR against `main`. Do **not** push directly to `main`. +**Types:** -One PR per logical change. Do not mix unrelated changes in a single PR. +- `feat:` — a new feature (triggers a minor version bump) +- `fix:` — a bug fix (triggers a patch version bump) +- `perf:` — performance improvement +- `refactor:` — code restructure with no behaviour change +- `docs:` — documentation only +- `test:` — adding or fixing tests +- `build:` — build system or dependencies +- `ci:` — CI configuration +- `chore:` — anything else (no release effect) +- `revert:` — reverts a previous commit -## Commit messages +**Breaking changes:** add `!` after the type/scope or include `BREAKING +CHANGE:` in the footer. This triggers a major version bump. -Use [Conventional Commits](https://www.conventionalcommits.org/): +Examples: ``` -feat(client): add ChatStream backpressure -fix(retry): respect Retry-After when value is a date -perf(stream): re-use buffer in SSE parser -docs(readme): document agent orchestration -test(retry): add coverage for context cancellation during backoff +feat(client): add streaming retry with exponential backoff +fix: handle empty response body in chat handler +refactor!: rename ClientV1 to Client (BREAKING CHANGE) ``` -Allowed types: `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `chore`, -`build`, `ci`, `style`. Add a scope when it clarifies the change. Do not add -`Co-authored-by:` trailers — this is solo-developer work. +## Pull request checklist -## Code standards +Before requesting review: -- `gofmt -l .` must be empty for files you touch. -- `go vet ./...` must be clean. -- `golangci-lint run ./...` must surface no new findings. The repo enables - `errcheck`, `staticcheck`, `gocritic`, `unused`, `ineffassign`, `misspell`, - `noctx`, `bodyclose`, `unconvert`, `whitespace`. Use `//nolint:` - only with a one-line justification. -- Public types and functions must have godoc comments. -- Prefer table-driven tests with `t.Parallel()` where independent. -- Wrap errors with context: `fmt.Errorf("hawk-sdk: : %w", err)`. -- Propagate `context.Context` everywhere; never call out to a network without it. -- Set `User-Agent: hawk-sdk-go/` on every new HTTP request via - the `userAgent()` helper. +- [ ] `make ci` passes locally. +- [ ] New behaviour has tests; bug fixes have a regression test. +- [ ] `CHANGELOG.md` entries are **not** edited manually — release-please + generates them from your commit messages. +- [ ] The `VERSION` file is **not** edited manually — release-please bumps + it on release. +- [ ] Public API changes have updated doc comments. +- [ ] No secrets, API keys, or PII in code, comments, tests, or fixtures. -## Bumping the SDK version +## Code review etiquette -The single source of truth is `version.go`. Bumping it automatically -updates the User-Agent header. When bumping: +- Reviewers focus on correctness, design, and tests; formatting is + enforced by tooling, not humans. +- Authors respond to every comment (resolved, addressed, or politely + declined with rationale) — no silent dismissals. +- Squash-merge by default; the PR title becomes the commit (so it must + be a valid Conventional Commit message). +- One approving review from a CODEOWNERS-listed reviewer is required. -1. Edit `version.go`. -2. Add a `## [X.Y.Z] — YYYY-MM-DD` entry at the top of `CHANGELOG.md`. -3. Tag the release: `git tag vX.Y.Z && git push origin vX.Y.Z`. +## Reporting bugs -This SDK adheres to [SemVer](https://semver.org/spec/v2.0.0.html). -Breaking changes to the daemon API or the SDK surface bump the major -version. New SDK methods or daemon endpoints bump the minor. Bug fixes -and internal improvements bump the patch. +Open an issue using the bug-report template. Include the `hawk-sdk-go` +version (`hawk-sdk-go --version` for binaries, `hawk-sdk-go.Version` for +libraries — see this repo's `VERSION` file), reproduction steps, expected +behaviour, and actual behaviour. -## Testing - -```bash -make test # full suite with race detector -make test-coverage # coverage totals -``` +## Reporting security issues -When adding a new client method, cover: success path, retryable error -(429 with `Retry-After`), non-retryable error, context cancellation, -and (for streaming) early `Close()` and EOF. +**Do not open a public issue.** See [SECURITY.md](./SECURITY.md) for +private reporting channels. -## Reporting bugs / requesting features +## License -- Bug: open an issue using the bug-report template. -- Feature: open an issue using the feature-request template. -- Security: do **not** file a public issue. Use a GitHub Security Advisory - per `SECURITY.md`. +By contributing, you agree that your contributions will be licensed under +the same license as this repo (see [LICENSE](./LICENSE)). diff --git a/Makefile b/Makefile index d74004e..807b338 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,112 @@ +# Canonical hawk-eco Makefile for Go library repos. +# Source of truth: .shared-templates/Makefile.library.tmpl at the eco root. +# Placeholders rendered per repo: hawk-sdk-go. + +# --------------------------------------------------------------------------- +# Project metadata +# --------------------------------------------------------------------------- NAME := hawk-sdk-go -.PHONY: all test lint fmt vet clean help +# --------------------------------------------------------------------------- +# Versioning — sourced from VERSION file; falls back to git describe. +# See https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md. +# --------------------------------------------------------------------------- +VERSION ?= $(shell cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]' || git describe --tags --always --dirty 2>/dev/null || echo "dev") +COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") +DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') + +# --------------------------------------------------------------------------- +# Tooling — pinned, install if missing. +# --------------------------------------------------------------------------- +GOBIN_DIR := $(shell go env GOPATH)/bin +GOLANGCI := $(GOBIN_DIR)/golangci-lint +GOFUMPT := $(GOBIN_DIR)/gofumpt +GOIMPORTS := $(GOBIN_DIR)/goimports +GOVULNCHECK := $(GOBIN_DIR)/govulncheck + +# --------------------------------------------------------------------------- +# Phony declarations (alphabetical). +# --------------------------------------------------------------------------- +.PHONY: all bench build ci clean cover fmt help lint lint-fix \ + security test test-10x test-race tidy version vet + +# --------------------------------------------------------------------------- +# Default target. +# --------------------------------------------------------------------------- +all: lint test build ## Default — lint, test, build. + +# --------------------------------------------------------------------------- +# Build (verify the library compiles). +# --------------------------------------------------------------------------- +build: ## Verify the library compiles. + CGO_ENABLED=0 go build ./... + +# --------------------------------------------------------------------------- +# Tests. +# --------------------------------------------------------------------------- +test: ## Run unit tests. + go test ./... -count=1 -timeout=120s -all: lint test +test-race: ## Run unit tests with the race detector. + go test ./... -race -count=1 -timeout=180s -test: ## Run tests with race detector - go test ./... -race -count=1 -timeout=60s +test-10x: ## Run tests 10 times to surface flakes. + go test ./... -race -count=10 -timeout=600s -test-coverage: ## Run tests with coverage - go test ./... -race -coverprofile=coverage.out -covermode=atomic - go tool cover -func=coverage.out | grep "^total:" +cover: ## Generate a coverage report (coverage.out + coverage.html). + go test ./... -race -coverprofile=coverage.out -covermode=atomic -timeout=180s + @go tool cover -func=coverage.out | grep "^total:" + @go tool cover -html=coverage.out -o coverage.html + @echo "Coverage report: coverage.html" -lint: ## Run linter - golangci-lint run ./... --timeout=5m +bench: ## Run benchmarks. + go test ./... -bench=. -benchmem -count=3 -timeout=300s -fmt: ## Format code - gofumpt -w . - goimports -w . +# --------------------------------------------------------------------------- +# Quality gates. +# --------------------------------------------------------------------------- +fmt: ## Format source files (gofumpt + goimports). + @command -v $(GOFUMPT) >/dev/null 2>&1 || (echo "install: go install mvdan.cc/gofumpt@latest" && exit 1) + @command -v $(GOIMPORTS) >/dev/null 2>&1 || (echo "install: go install golang.org/x/tools/cmd/goimports@latest" && exit 1) + $(GOFUMPT) -w . + $(GOIMPORTS) -w . -vet: ## Run go vet +vet: ## Run go vet. go vet ./... -clean: ## Clean artifacts - rm -f coverage.out +lint: ## Run golangci-lint. + @command -v $(GOLANGCI) >/dev/null 2>&1 || (echo "install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest" && exit 1) + $(GOLANGCI) run ./... --timeout=5m + +lint-fix: ## Run golangci-lint with --fix. + @command -v $(GOLANGCI) >/dev/null 2>&1 || (echo "install: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest" && exit 1) + $(GOLANGCI) run ./... --fix --timeout=5m + +security: ## Run govulncheck. + @command -v $(GOVULNCHECK) >/dev/null 2>&1 || (echo "install: go install golang.org/x/vuln/cmd/govulncheck@latest" && exit 1) + $(GOVULNCHECK) ./... + +tidy: ## Tidy go.mod / go.sum. + go mod tidy + go mod verify + +# --------------------------------------------------------------------------- +# Composite gate used by CI and pre-push. +# --------------------------------------------------------------------------- +ci: tidy fmt vet lint test-race security ## Run everything CI runs. + @echo "All CI checks passed." + +# --------------------------------------------------------------------------- +# Misc. +# --------------------------------------------------------------------------- +version: ## Print the version that will be embedded. + @echo "Version: $(VERSION)" + @echo "Commit: $(COMMIT)" + @echo "Date: $(DATE)" + +clean: ## Remove build artefacts. + rm -rf coverage.out coverage.html + go clean -testcache -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' +help: ## Show this help. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' diff --git a/SECURITY.md b/SECURITY.md index fcb75bb..ebe7032 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,51 +1,71 @@ -# Security policy +# Security Policy — hawk-sdk-go ## Supported versions -Only the latest minor version of `hawk-sdk-go` receives security updates. -The current supported version is the most recent `v0.x` release. +We support the latest minor version on each `0.x` line, and the latest two +minor versions once `1.x` ships. Older versions receive critical-severity +fixes only on a best-effort basis. + +The current canonical version is the contents of the [`VERSION`](./VERSION) +file at the repo root. See [`VERSIONING.md`](https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md) +for the eco-wide versioning scheme. ## Reporting a vulnerability -Please **do not** file a public GitHub issue for security vulnerabilities. +**Do not open a public GitHub issue for security vulnerabilities.** Instead: + +1. Open a private [GitHub Security Advisory](https://github.com/GrayCodeAI/hawk-sdk-go/security/advisories/new), **or** +2. Email `security@graycode.ai` with the details below. + +Include in your report: -Instead, open a **private** GitHub Security Advisory: +- A description of the vulnerability and the affected component. +- Steps to reproduce, ideally with a minimal proof-of-concept. +- The version (`VERSION` file or git SHA) you tested against. +- The potential impact and any suggested mitigation. -> https://github.com/GrayCodeAI/hawk-sdk-go/security/advisories/new +**Response targets:** -Include, where possible: +- Initial acknowledgement: within **48 hours**. +- Triage and severity assessment: within **5 business days**. +- Coordinated fix and disclosure: within **30 days** for high/critical, **90 + days** for medium/low (per industry-standard responsible disclosure). -- A clear description of the issue and impact. -- Steps to reproduce (a minimal Go snippet is ideal). -- The affected `hawk-sdk-go` version (`hawksdk.Version`) and Go version. -- The hawk daemon version you were targeting, if relevant. -- Any mitigations or patches you have already explored. +## Disclosure policy -We aim to respond to advisories within **5 business days** and to release -a fix within **30 days** for high-severity issues. +We follow [coordinated vulnerability disclosure](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure): -## What counts as a security issue +- Reporters receive credit in the advisory and CHANGELOG (unless they opt + out). +- We request that reporters refrain from public disclosure until a fix has + been released or the disclosure deadline above has elapsed. +- We will not pursue legal action against good-faith researchers acting + within this policy. -Examples of in-scope issues: +## Security practices in this repo -- The SDK leaking secrets, API tokens, or session IDs into logs, errors, - or metrics. -- The SDK accepting and forwarding data that bypasses daemon-side - authentication, authorization, or rate limiting. -- TLS misuse — accepting untrusted certificates, downgrade to HTTP, or - ignoring `https_proxy` rules. -- Memory-safety issues (panics on attacker-controlled input, unbounded - allocations on stream or response). -- Path / URL handling that lets a malicious daemon URL escape the - expected host (e.g. via redirects). +- **Dependency monitoring:** automated via Dependabot (see + `.github/dependabot.yml`). +- **Static analysis:** `golangci-lint` / `ruff` / `mypy` enforced in CI. +- **Vulnerability scanning:** `govulncheck` (Go) / `pip-audit` (Python) run + on every CI build. +- **Lockfiles:** `go.sum` / `pnpm-lock.yaml` / `pyproject.toml` are pinned + and committed. +- **Reproducible builds:** release artefacts ship with SHA-256 checksums via + goreleaser. +- **No secrets in source:** API keys are configuration, not constants. Pre- + commit hooks block accidental secret commits. -Out of scope: +## Scope -- Issues in the hawk daemon itself — please report those at - https://github.com/GrayCodeAI/hawk/security/advisories/new. -- Issues in third-party Go modules — please report those upstream. +This policy covers the code in this repository and the release artefacts +published from it. It does not cover: -## Disclosure +- Third-party dependencies (report to upstream). +- LLM provider services that hawk-sdk-go integrates with (report to the + provider). +- Local filesystem misuse where an attacker already has shell access (out of + threat model). -Once a fix is released, we will publish the advisory with credit to the -reporter (unless they request anonymity). +For hawk-sdk-go-specific threat-model notes, see the README and any docs in +this repo. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..ba5700d --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,112 @@ +# Canonical lefthook config for hawk-eco Go repos. +# Source of truth: .shared-templates/lefthook.yml.tmpl +# +# Install lefthook: +# brew install lefthook (macOS) +# go install github.com/evilmartians/lefthook@latest +# npm install -g lefthook (cross-platform) +# +# Activate hooks in this repo (one time): +# lefthook install +# +# Skip hooks for a single commit (use sparingly): +# LEFTHOOK=0 git commit -m "..." + +# --------------------------------------------------------------------------- +# pre-commit — runs before commit creation, on staged files only. +# --------------------------------------------------------------------------- +pre-commit: + parallel: true + commands: + + fmt: + glob: "*.go" + run: | + if ! command -v gofumpt >/dev/null 2>&1; then + echo "lefthook: gofumpt not installed (go install mvdan.cc/gofumpt@latest)"; exit 1 + fi + gofumpt -w {staged_files} + stage_fixed: true + + imports: + glob: "*.go" + run: | + if ! command -v goimports >/dev/null 2>&1; then + echo "lefthook: goimports not installed (go install golang.org/x/tools/cmd/goimports@latest)"; exit 1 + fi + goimports -w {staged_files} + stage_fixed: true + + lint: + glob: "*.go" + run: | + if ! command -v golangci-lint >/dev/null 2>&1; then + echo "lefthook: golangci-lint not installed — skipping (install: https://golangci-lint.run/usage/install/)" + exit 0 + fi + golangci-lint run --new-from-rev=HEAD~1 --fix {staged_files} + stage_fixed: true + + yaml-lint: + glob: "*.{yml,yaml}" + run: | + # Quick syntax check via Python's yaml module (already on most systems). + for f in {staged_files}; do + python3 -c "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" "$f" || exit 1 + done + + forbidden-strings: + run: | + # Catch obvious credential-shaped strings in staged additions. + bad=$(git diff --cached --diff-filter=AM -U0 -- {staged_files} \ + | grep -E '^\+' \ + | grep -Ei '(aws_secret|password\s*=|api[_-]?key\s*=|BEGIN [A-Z]+ PRIVATE KEY)' \ + | grep -v 'example\|placeholder\|TODO\|x-release-please' || true) + if [ -n "$bad" ]; then + echo "lefthook: possible secret in staged changes:" + echo "$bad" + echo "If this is a false positive, bypass with: LEFTHOOK=0 git commit" + exit 1 + fi + +# --------------------------------------------------------------------------- +# pre-push — heavier checks, runs only on push (not every commit). +# --------------------------------------------------------------------------- +pre-push: + commands: + + test: + run: go test ./... -count=1 -timeout=60s + + vet: + run: go vet ./... + + govulncheck: + run: | + if ! command -v govulncheck >/dev/null 2>&1; then + echo "lefthook: govulncheck not installed — skipping" + exit 0 + fi + govulncheck ./... + +# --------------------------------------------------------------------------- +# commit-msg — validate Conventional Commits (release-please depends on it). +# --------------------------------------------------------------------------- +commit-msg: + commands: + + conventional-commit: + run: | + msg=$(head -n1 "{1}") + # Allow merge commits, revert commits, and release-please bot commits to bypass. + case "$msg" in + "Merge "*|"Revert "*|"chore(main): release"*) exit 0 ;; + esac + # Conventional commits regex. + if ! echo "$msg" | grep -qE '^(feat|fix|perf|refactor|test|docs|build|ci|chore|revert|style)(\([a-z0-9 _-]+\))?!?: .{1,72}$'; then + echo "lefthook: commit message does not follow Conventional Commits." + echo " format: (): " + echo " example: feat(client): add streaming retry" + echo " full guide: https://www.conventionalcommits.org/" + exit 1 + fi diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..3ab4bd0 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "go", + "package-name": "hawk-sdk-go", + "include-v-in-tag": true, + "include-component-in-tag": false, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance" }, + { "type": "refactor", "section": "Refactoring" }, + { "type": "revert", "section": "Reverts" }, + { "type": "docs", "section": "Documentation", "hidden": false }, + { "type": "test", "section": "Tests", "hidden": false }, + { "type": "build", "section": "Build", "hidden": true }, + { "type": "ci", "section": "CI", "hidden": true }, + { "type": "chore", "section": "Chores", "hidden": true }, + { "type": "style", "section": "Style", "hidden": true } + ], + "extra-files": [{"type":"version-txt","path":"VERSION"}] + } + } +} diff --git a/version.go b/version.go index b076263..2adff1e 100644 --- a/version.go +++ b/version.go @@ -1,9 +1,24 @@ package hawksdk +import ( + _ "embed" + "strings" +) + +// versionFile is the canonical version, read at compile time from the VERSION +// file at the repo root. The VERSION file is the single source of truth used +// by release tooling (release-please, goreleaser, CI). +// +//go:embed VERSION +var versionFile string + // Version of the hawk-sdk-go library. Used in the User-Agent header on // outbound HTTP requests so a misbehaving SDK can be identified by daemon // logs and operators. -const Version = "0.2.0" +// +// Source of truth: the VERSION file at the repo root. Do not edit this +// constant directly — bump VERSION instead (or let release-please do it). +var Version = strings.TrimSpace(versionFile) // userAgent returns the User-Agent string for outbound HTTP requests. func userAgent() string { return "hawk-sdk-go/" + Version }