diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 656486b..33f9686 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,8 +51,8 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: Clone hawk for shared/types - run: git clone --depth=1 https://github.com/GrayCodeAI/hawk.git ../hawk + - name: Boundary guard + run: bash ./scripts/check-ecosystem-boundaries.sh - name: gofumpt diff run: | go install mvdan.cc/gofumpt@v0.10.0 @@ -77,8 +77,8 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: Clone hawk for shared/types - run: git clone --depth=1 https://github.com/GrayCodeAI/hawk.git ../hawk + - name: Boundary guard + run: bash ./scripts/check-ecosystem-boundaries.sh - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v7.0.0 with: version: v2.1.0 @@ -98,8 +98,8 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: Clone hawk for shared/types - run: git clone --depth=1 https://github.com/GrayCodeAI/hawk.git ../hawk + - name: Boundary guard + run: bash ./scripts/check-ecosystem-boundaries.sh - name: Tidy check run: | go mod tidy @@ -139,8 +139,6 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: Clone hawk for shared/types - run: git clone --depth=1 https://github.com/GrayCodeAI/hawk.git ../hawk - name: govulncheck run: | go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 @@ -162,8 +160,6 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: Clone hawk for shared/types - run: git clone --depth=1 https://github.com/GrayCodeAI/hawk.git ../hawk - name: deadcode run: | go install golang.org/x/tools/cmd/deadcode@latest @@ -207,8 +203,6 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: Clone hawk for shared/types - run: git clone --depth=1 https://github.com/GrayCodeAI/hawk.git ../hawk - name: Build env: GOOS: ${{ matrix.goos }} diff --git a/.gitignore b/.gitignore index d873f57..73dc4b3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,12 @@ # Local state .claude/ .codegraph/ +.gocache/ coverage.out +# Go workspace (local dev only — each developer creates their own) +go.work +go.work.sum + # macOS .DS_Store diff --git a/AGENTS.md b/AGENTS.md index efb3660..b5107ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,7 @@ gofumpt -w . # Format - Conventional Commits: `feat:`, `fix:`, `docs:`, `refactor:`, `test:` - No `Co-authored-by:` trailers (auto-stripped by githook) - `gofumpt` formatting enforced in CI -- Import `shared/types` from hawk for cross-repo types +- Import `hawk-core-contracts/types` for cross-repo types ## Common Pitfalls @@ -49,7 +49,7 @@ gofumpt -w . # Format - **Option functions use `With` prefix**: `WithProvider()`, `WithMaxTokens()`, `WithParallel()` — never bare `Provider()` - **Preset options are bare vars**: `Quick`, `Thorough`, `SecurityFocus`, `CI` — exported `var Option` values - **Internal types mirror public ones**: public `Finding` maps to internal `review.Finding` via `toPublicFindings()` -- **Severity is a type alias**: `type Severity = types.Severity` from `hawk/shared/types` — never define your own +- **Severity is a type alias**: `type Severity = types.Severity` from `hawk-core-contracts/types` — never define your own - **Error sentinel naming**: `ErrNoProvider`, `ErrEmptyDiff`, `ErrContextCancelled` — always `Err` prefix, package-scoped - **Mock types in tests**: `mockProvider` (unexported), implements `Provider` with `response string` and `err error` fields @@ -85,7 +85,7 @@ gofumpt -w . # Format - **Safe to refactor**: `toPublicFindings()`, `toPublicComments()` — mapping functions, add fields freely - **Do not touch**: `Provider` interface signature — breaking change for all consumers (hawk, eyrie integration) - **Do not touch**: `Finding`, `Result`, `Stats` struct field names/tags — used in JSON serialization by consumers -- **Do not touch**: `Severity` type alias — it re-exports from `hawk/shared/types`; changing it breaks cross-repo compatibility +- **Do not touch**: `Severity` type alias — it re-exports from `hawk-core-contracts/types`; changing it breaks cross-repo compatibility - **Safe to extend**: add new `Option` functions, new presets, new `StaticRule` entries, new taint source/sink patterns - **When adding concerns**: add to `defaultConfig().concerns` list and create corresponding `review.Concern` in `internal/review/` @@ -97,7 +97,7 @@ gofumpt -w . # Format | Reviewer implementation | `reviewer.go` (orchestration, parallel concerns, reflection) | | Configuration & presets | `options.go` (`config` struct, `With*` functions, presets) | | LLM provider interface | `provider.go` (`Provider`, `Message`, `ChatOpts`, `Response`) | -| Severity type alias | `severity.go` (re-exports from `hawk/shared/types`) | +| Severity type alias | `severity.go` (re-exports from `hawk-core-contracts/types`) | | Static analysis rules | `static_rules.go` (`StaticRule`, `StaticAnalyzer`, 30+ rules) | | Taint analysis | `taint_analysis.go` (`TaintAnalyzer`, source/sink/sanitizer patterns) | | Diff parsing internals | `internal/diff/` | diff --git a/Makefile b/Makefile index b479c5f..b2e4beb 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,12 @@ GOVULNCHECK := $(GOBIN_DIR)/govulncheck # --------------------------------------------------------------------------- # Phony declarations (alphabetical). # --------------------------------------------------------------------------- -.PHONY: all bench build ci clean cover fmt help lint lint-fix \ +.PHONY: all bench boundaries build ci clean cover fmt help lint lint-fix \ security test test-10x test-race tidy version vet +boundaries: ## Enforce support-repo import boundaries. + bash ./scripts/check-ecosystem-boundaries.sh + # --------------------------------------------------------------------------- # Default target. # --------------------------------------------------------------------------- @@ -93,7 +96,7 @@ tidy: ## Tidy go.mod / go.sum. # --------------------------------------------------------------------------- # Composite gate used by CI and pre-push. # --------------------------------------------------------------------------- -ci: tidy fmt vet lint test-race security ## Run everything CI runs. +ci: tidy fmt vet lint boundaries test-race security ## Run everything CI runs. @echo "All CI checks passed." # --------------------------------------------------------------------------- diff --git a/README.md b/README.md index 65f55c3..78e18c5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ Sight provides intelligent code review capabilities by analyzing diffs with AI. It understands context, identifies issues, and suggests improvements. +## Ecosystem Boundaries + +Sight is a Hawk support engine. Keep the dependency edge one-way: + +- use `hawk-core-contracts` for any cross-repo shared contracts +- do not import `hawk/internal/*` +- do not import removed legacy path `hawk/shared/types`; use `hawk-core-contracts/types` + ## Features - **Diff-aware analysis** - Reviews only changed code with full context diff --git a/contracts.go b/contracts.go new file mode 100644 index 0000000..f534448 --- /dev/null +++ b/contracts.go @@ -0,0 +1,113 @@ +package sight + +import reviewcontracts "github.com/GrayCodeAI/hawk-core-contracts/review" + +// ToContractFinding converts a sight finding into the shared review contract. +func ToContractFinding(f Finding) reviewcontracts.Finding { + return reviewcontracts.Finding{ + Concern: f.Concern, + Severity: f.Severity, + File: f.File, + Line: f.Line, + EndLine: f.EndLine, + Message: f.Message, + Fix: f.Fix, + Reasoning: f.Reasoning, + CWE: f.CWE, + Confidence: f.Confidence, + SASTSource: f.SASTSource, + } +} + +// ToContractFindings converts sight findings into shared review contracts. +func ToContractFindings(findings []Finding) []reviewcontracts.Finding { + if len(findings) == 0 { + return nil + } + out := make([]reviewcontracts.Finding, len(findings)) + for i, f := range findings { + out[i] = ToContractFinding(f) + } + return out +} + +// FromContractFinding converts a shared review contract into a sight finding. +func FromContractFinding(f reviewcontracts.Finding) Finding { + return Finding{ + Concern: f.Concern, + Severity: f.Severity, + File: f.File, + Line: f.Line, + EndLine: f.EndLine, + Message: f.Message, + Fix: f.Fix, + Reasoning: f.Reasoning, + CWE: f.CWE, + Confidence: f.Confidence, + SASTSource: f.SASTSource, + } +} + +// ToContractInlineComment converts a sight inline comment into the shared review contract. +func ToContractInlineComment(c InlineComment) reviewcontracts.InlineComment { + return reviewcontracts.InlineComment{ + Path: c.Path, + StartLine: c.StartLine, + EndLine: c.EndLine, + Body: c.Body, + Suggestion: c.Suggestion, + } +} + +// ToContractInlineComments converts sight inline comments into shared review contracts. +func ToContractInlineComments(comments []InlineComment) []reviewcontracts.InlineComment { + if len(comments) == 0 { + return nil + } + out := make([]reviewcontracts.InlineComment, len(comments)) + for i, c := range comments { + out[i] = ToContractInlineComment(c) + } + return out +} + +func toContractConfidenceBreakdown(b *ConfidenceBreakdown) *reviewcontracts.ConfidenceBreakdown { + if b == nil { + return nil + } + return &reviewcontracts.ConfidenceBreakdown{ + High: ToContractFindings(b.High), + Medium: ToContractFindings(b.Medium), + Low: ToContractFindings(b.Low), + } +} + +func toContractStats(s Stats) reviewcontracts.Stats { + return reviewcontracts.Stats{ + FilesReviewed: s.FilesReviewed, + HunksAnalyzed: s.HunksAnalyzed, + FindingsTotal: s.FindingsTotal, + BySeverity: s.BySeverity, + ByConcern: s.ByConcern, + TokensUsed: s.TokensUsed, + DurationPerConcern: s.DurationPerConcern, + AverageConfidence: s.AverageConfidence, + HighConfidenceCount: s.HighConfidenceCount, + LowConfidenceCount: s.LowConfidenceCount, + } +} + +// ToContractResult converts a sight result into the shared review contract. +func ToContractResult(r *Result) *reviewcontracts.Result { + if r == nil { + return nil + } + return &reviewcontracts.Result{ + Findings: ToContractFindings(r.Findings), + Comments: ToContractInlineComments(r.Comments), + Stats: toContractStats(r.Stats), + Report: r.Report, + FailOn: r.FailOn, + ConfidenceBreakdown: toContractConfidenceBreakdown(r.ConfidenceBreakdown), + } +} diff --git a/contracts_test.go b/contracts_test.go new file mode 100644 index 0000000..e9a5575 --- /dev/null +++ b/contracts_test.go @@ -0,0 +1,51 @@ +package sight + +import "testing" + +func TestToContractResult(t *testing.T) { + t.Parallel() + + result := &Result{ + Findings: []Finding{ + { + Concern: "security", + Severity: SeverityHigh, + File: "main.go", + Line: 12, + Message: "issue", + Fix: "fix", + Confidence: 0.9, + }, + }, + Comments: []InlineComment{{Path: "main.go", StartLine: 12, Body: "comment"}}, + Stats: Stats{ + FilesReviewed: 1, + FindingsTotal: 1, + BySeverity: map[Severity]int{SeverityHigh: 1}, + ByConcern: map[string]int{"security": 1}, + TokensUsed: 42, + }, + Report: "report", + FailOn: SeverityMedium, + ConfidenceBreakdown: &ConfidenceBreakdown{ + High: []Finding{{Concern: "security", Severity: SeverityHigh, File: "main.go", Line: 12, Message: "issue", Confidence: 0.9}}, + }, + } + + got := ToContractResult(result) + if got == nil { + t.Fatal("expected non-nil contract result") + } + if got.Report != "report" { + t.Fatalf("Report = %q, want report", got.Report) + } + if len(got.Findings) != 1 || got.Findings[0].Severity != SeverityHigh { + t.Fatalf("unexpected findings conversion: %+v", got.Findings) + } + if got.Stats.TokensUsed != 42 { + t.Fatalf("TokensUsed = %d, want 42", got.Stats.TokensUsed) + } + if got.ConfidenceBreakdown == nil || len(got.ConfidenceBreakdown.High) != 1 { + t.Fatal("expected confidence breakdown to convert") + } +} diff --git a/docs/architecture.md b/docs/architecture.md index 7340d5d..8a50070 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -29,7 +29,7 @@ sight/ ├── reviewer.go 🔄 Reviewer: parallel concern orchestration ├── options.go ⚙️ config, With* functions, presets ├── provider.go 🔌 Provider interface (consumers implement) -├── severity.go 📊 Re-exports from hawk/shared/types +├── severity.go 📊 Re-exports from hawk-core-contracts/types ├── static_rules.go 🛡️ 30+ static analysis rules ├── taint_analysis.go 🔗 SSA-based taint tracking ├── sast_integration.go 🔒 SAST-LLM fusion diff --git a/go.mod b/go.mod index 3d185de..ab44868 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/GrayCodeAI/sight go 1.26.4 require ( - github.com/GrayCodeAI/hawk v0.1.0 + github.com/GrayCodeAI/hawk-core-contracts v0.1.0 github.com/mark3labs/mcp-go v0.49.0 golang.org/x/tools v0.45.0 ) diff --git a/go.sum b/go.sum index a879178..0ecfbec 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/GrayCodeAI/hawk v0.1.0 h1:xqFcH05JEZrSB5grybanyAzkRf8GaFjPQxDV/d+ARrE= github.com/GrayCodeAI/hawk v0.1.0/go.mod h1:JIiKVFiFJL52OKNW/ndHRtjzVbVy6CX3HBydPpDHkwA= +github.com/GrayCodeAI/hawk-core-contracts v0.1.0 h1:w4Y6jBFzjXJ8zmAtfCWuKtODlHKosn9TStADxHE/LsU= +github.com/GrayCodeAI/hawk-core-contracts v0.1.0/go.mod h1:Oq7h+CGkmI3HFTHBVGaWHQuhvtGt6e8UYz5zwF4cM2Q= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/go.work b/go.work deleted file mode 100644 index dba302f..0000000 --- a/go.work +++ /dev/null @@ -1,7 +0,0 @@ -go 1.26.4 - -use . - -// Local development overrides for unpublished modules. -// go.work is gitignored — each developer creates their own. -replace github.com/GrayCodeAI/hawk => ../hawk diff --git a/internal/review/concerns.go b/internal/review/concerns.go index aae5975..9d5f26e 100644 --- a/internal/review/concerns.go +++ b/internal/review/concerns.go @@ -1,7 +1,7 @@ // Package review implements the multi-concern LLM review pipeline. package review -import "github.com/GrayCodeAI/hawk/shared/types" +import "github.com/GrayCodeAI/hawk-core-contracts/types" // Severity mirrors the public type for internal use. type Severity = types.Severity diff --git a/lefthook.yml b/lefthook.yml index b952e5e..93ead66 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -110,3 +110,18 @@ commit-msg: echo " full guide: https://www.conventionalcommits.org/" exit 1 fi + + strip-co-authored-by: + run: | + # Strip Co-authored-by: trailers that AI tools (Claude, Cursor, etc.) add. + # This enforces the rule that commits list only the human author. + sed '/^[Cc]o-[Aa]uthored-[Bb]y:/d' "{1}" > "{1}.tmp" && mv "{1}.tmp" "{1}" + +# --------------------------------------------------------------------------- +# prepare-commit-msg — strip AI co-author trailers after tools inject them. +# --------------------------------------------------------------------------- +prepare-commit-msg: + commands: + strip-co-authored-by: + run: | + sed '/^[Cc]o-[Aa]uthored-[Bb]y:/d' "{1}" > "{1}.tmp" && mv "{1}.tmp" "{1}" diff --git a/scripts/check-ecosystem-boundaries.sh b/scripts/check-ecosystem-boundaries.sh new file mode 100644 index 0000000..04bb187 --- /dev/null +++ b/scripts/check-ecosystem-boundaries.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +violations="$( + rg -n 'github\.com/GrayCodeAI/hawk/(internal/|shared/types)' \ + --glob '*.go' \ + . || true +)" + +if [[ -n "${violations}" ]]; then + echo "forbidden Hawk imports found:" + echo "${violations}" + echo + echo "support repos must use hawk-core-contracts or local contracts, not hawk/internal or removed hawk/shared/types" + exit 1 +fi + +echo "ecosystem boundary guard passed" diff --git a/severity.go b/severity.go index 537abc0..2883e7f 100644 --- a/severity.go +++ b/severity.go @@ -1,6 +1,6 @@ package sight -import "github.com/GrayCodeAI/hawk/shared/types" +import "github.com/GrayCodeAI/hawk-core-contracts/types" // Severity represents the impact level of a review finding. // Aliased from shared types for cross-module compatibility.