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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- name: gofumpt
run: |
go install mvdan.cc/gofumpt@v0.10.0
out=$(gofumpt -l .)
out=$(git ls-files -- '*.go' ':!external/**' | xargs gofumpt -l)
if [ -n "$out" ]; then
echo "::error::gofumpt would reformat:"
echo "$out" | head -20
Expand All @@ -54,7 +54,7 @@ jobs:
- name: goimports
run: |
go install golang.org/x/tools/cmd/goimports@v0.30.0
out=$(goimports -l .)
out=$(git ls-files -- '*.go' ':!external/**' | xargs goimports -l)
if [ -n "$out" ]; then
echo "::error::goimports would reformat:"
echo "$out" | head -20
Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/daemon-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Daemon image

on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
paths:
- "Dockerfile.daemon"
- "packaging/systemd/hawk-daemon.service"
- "internal/**"
- "cmd/**"
- "external/**"
- "go.mod"
- "go.sum"

permissions:
contents: read
packages: write
security-events: write

env:
REGISTRY: ghcr.io
IMAGE_NAME: graycodeai/hawk-daemon

jobs:
build:
name: build + scan + publish (daemon)
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
submodules: recursive

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build daemon image for scan
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: Dockerfile.daemon
platforms: linux/amd64
push: false
load: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan
cache-from: type=gha,scope=hawk-daemon
cache-to: type=gha,mode=max,scope=hawk-daemon
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}

- name: Scan daemon image with Trivy
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan
format: sarif
output: trivy-daemon-image.sarif
severity: CRITICAL,HIGH
# Go reachability is enforced separately by govulncheck in CI. The
# binary also carries the full workspace module graph, including
# non-reachable packages that Trivy reports as binary findings.
vuln-type: os
ignore-unfixed: true
exit-code: '1'

- name: Generate image metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=sha-,format=long

# The PR already exercised the daemon Dockerfile in the scan build above.
# Skip the redundant multi-arch publish build on pull requests so CI can
# finish as soon as the security gate passes.
- name: Build and publish daemon image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: Dockerfile.daemon
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=hawk-daemon
cache-to: type=gha,mode=max,scope=hawk-daemon
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}

# Publish the daemon scan to GitHub code scanning. The PR still runs the
# scan, but it skips the redundant publish build and release artifacts.
- name: Upload daemon image scan results
if: always()
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
sarif_file: trivy-daemon-image.sarif
38 changes: 28 additions & 10 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,13 @@ jobs:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan
format: sarif
output: trivy-image.sarif
severity: CRITICAL
severity: CRITICAL,HIGH
# Go reachability is enforced separately by govulncheck in CI. The
# binary also carries the full workspace module graph, including
# non-reachable packages that Trivy reports as binary findings.
vuln-type: os
ignore-unfixed: true
exit-code: '0' # Don't fail the build; results are uploaded for review
exit-code: '1' # Block publishing images with actionable vulnerabilities

# Second build is a cache hit (layers exported by the scan build), so it
# only re-links and pushes the platform image.
Expand All @@ -96,8 +100,10 @@ jobs:
COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}

# Publish the scan results to GitHub code scanning. The scan itself runs
# on PRs; only the release-side publish path stays off PRs.
- name: Upload Trivy image scan results
if: github.event_name != 'pull_request' && always()
if: always()
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
sarif_file: trivy-image.sarif
Expand Down Expand Up @@ -148,9 +154,13 @@ jobs:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan
format: sarif
output: trivy-image.sarif
severity: CRITICAL
severity: CRITICAL,HIGH
# Go reachability is enforced separately by govulncheck in CI. The
# binary also carries the full workspace module graph, including
# non-reachable packages that Trivy reports as binary findings.
vuln-type: os
ignore-unfixed: true
exit-code: '0' # Don't fail the build; results are uploaded for review
exit-code: '1' # Block publishing images with actionable vulnerabilities

# Second build is a cache hit (layers exported by the scan build), so it
# only re-links and pushes the platform image.
Expand All @@ -168,14 +178,16 @@ jobs:
COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp }}

# Publish the scan results to GitHub code scanning. The scan itself runs
# on PRs; only the release-side publish path stays off PRs.
- name: Upload Trivy image scan results
if: github.event_name != 'pull_request' && always()
if: always()
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
sarif_file: trivy-image.sarif

merge-manifest:
name: merge multi-arch manifest
name: merge multi-arch manifest (release only)
if: github.event_name != 'pull_request'
needs: [build-amd64, build-arm64]
runs-on: ubuntu-latest
Expand Down Expand Up @@ -255,9 +267,13 @@ jobs:
image-ref: ${{ env.REGISTRY }}/graycodeai/hawk-sandbox:scan
format: sarif
output: trivy-sandbox-image.sarif
severity: CRITICAL
severity: CRITICAL,HIGH
# This image gate covers the Debian OS package surface. npm's
# bundled CLI dependency tree is pinned by the Node base image and
# reviewed separately from the runtime OS scan.
vuln-type: os
ignore-unfixed: true
exit-code: '0'
exit-code: '1'

- name: Build and publish public sandbox image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
Expand All @@ -272,8 +288,10 @@ jobs:
cache-from: type=gha,scope=hawk-sandbox
cache-to: type=gha,mode=max,scope=hawk-sandbox

# Publish the sandbox scan to GitHub code scanning. PRs still run the
# scan; they just do not publish the release image artifacts.
- name: Upload sandbox image scan results
if: github.event_name != 'pull_request' && always()
if: always()
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
with:
sarif_file: trivy-sandbox-image.sarif
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ with its native Responses API (`/v1/responses`) under the
<!-- gitnexus:start -->
## GitNexus — Code Intelligence

This project is indexed by GitNexus as **hawk** (86470 symbols, 279855 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939).

Expand Down
44 changes: 44 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence

This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

> Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939).

## Always Do

- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. For regression review, compare against the default branch: `detect_changes({scope: "compare", base_ref: "main"})`.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `query({search_query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `context({name: "symbolName"})`.
- For security review, `explain({target: "fileOrSymbol"})` lists taint findings (source→sink flows; needs `analyze --pdg`).

## Never Do

- NEVER edit a function, class, or method without first running `impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `rename` which understands the call graph.
- NEVER commit changes without running `detect_changes()` to check affected scope.

## Resources

| Resource | Use for |
|----------|---------|
| `gitnexus://repo/hawk/context` | Codebase overview, check index freshness |
| `gitnexus://repo/hawk/clusters` | All functional areas |
| `gitnexus://repo/hawk/processes` | All execution flows |
| `gitnexus://repo/hawk/process/{name}` | Step-by-step execution trace |

## CLI

| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |

<!-- gitnexus:end -->
9 changes: 4 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Build stage
# TODO(supply-chain): pin base images by digest (tag@sha256:…) — tags are
# mutable and an upstream re-push silently changes the build. Applies to the
# alpine runtime stage below as well.
FROM golang:1.26.5-alpine AS builder
# Supply-chain hardening: both stages are pinned by digest so a mutable tag
# cannot silently change the build.
FROM golang:1.26.5-alpine@sha256:0178a641fbb4858c5f1b48e34bdaabe0350a330a1b1149aabd498d0699ff5fb2 AS builder

RUN apk upgrade --no-cache && \
apk add --no-cache git ca-certificates tzdata
Expand Down Expand Up @@ -56,7 +55,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
-o hawk ./cmd/hawk

# Runtime stage — Alpine (hawk requires git + bash for workspace operations; distroless excluded)
FROM alpine:3.23.5
FROM alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40

RUN apk upgrade --no-cache && \
apk add --no-cache ca-certificates git bash tini && \
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile.daemon
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# Build: docker build -f Dockerfile.daemon -t hawk-daemon .
# Run: docker run -p 4590:4590 -e HAWK_DAEMON_API_KEY=... hawk-daemon
FROM golang:1.26.5-alpine AS builder
FROM golang:1.26.5-alpine@sha256:0178a641fbb4858c5f1b48e34bdaabe0350a330a1b1149aabd498d0699ff5fb2 AS builder

RUN apk upgrade --no-cache && \
apk add --no-cache git ca-certificates tzdata
Expand Down Expand Up @@ -35,7 +35,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
-X main.BuildDate=${BUILD_DATE}" \
-o hawk ./cmd/hawk

FROM alpine:3.23.5
FROM alpine:3.23.5@sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40

RUN apk upgrade --no-cache && \
apk add --no-cache ca-certificates git bash curl tini && \
Expand All @@ -56,7 +56,7 @@ EXPOSE 4590

# Health check probes the daemon's health endpoint.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -sf http://127.0.0.1:4590/v1/health || exit 1
CMD curl -ksf https://127.0.0.1:4590/v1/health || curl -sf http://127.0.0.1:4590/v1/health || exit 1

ENTRYPOINT ["tini", "--", "hawk", "daemon", "start"]
CMD ["--host", "0.0.0.0", "--port", "4590"]
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ api-docs: ## Generate HTML API reference from OpenAPI spec.
@echo "API reference generated: api/reference.html"

api-validate: ## Validate the OpenAPI spec.
@command -v @redocly/cli >/dev/null 2>&1 || (echo "install: npm install -g @redocly/cli" && exit 1)
@redocly lint api/openapi.yaml
@command -v redocly >/dev/null 2>&1 || npm install -g @redocly/cli
redocly lint api/openapi.yaml

bench: ## Run benchmarks.
go test ./... -bench=. -benchmem -count=3 -timeout=300s
Expand All @@ -101,8 +101,8 @@ bench: ## Run benchmarks.
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 .
@git ls-files -- '*.go' ':!external/**' | xargs $(GOFUMPT) -w
@git ls-files -- '*.go' ':!external/**' | xargs $(GOIMPORTS) -w

vet: ## Run go vet.
go vet ./...
Expand Down
Loading
Loading