Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bd4d43f
feat: implement core contracts architecture
Patel230 Jun 21, 2026
1ec3292
docs(architecture): align contracts spec and dependency rules with re…
Patel230 Jun 21, 2026
d05176f
build(contracts): bind hawk-core-contracts like every other engine
Patel230 Jun 21, 2026
a255f7b
feat: enforce peer-isolated hawk architecture
Patel230 Jun 21, 2026
9c432e5
docs: add consolidated hawk repo architecture map
Patel230 Jun 21, 2026
5d16024
docs: add hawk ecosystem summary
Patel230 Jun 21, 2026
79f9f74
fix: harden hawk tool and daemon guardrails
Patel230 Jun 21, 2026
272ec5b
fix: harden plugin clone and cover persistence deadlock
Patel230 Jun 21, 2026
1f90ad4
docs: define shared types retirement gate
Patel230 Jun 21, 2026
d50fe04
refactor: remove legacy shared types shim
Patel230 Jun 21, 2026
8adff6e
docs: mark hawk-core-contracts as the completed shared types layer
Patel230 Jun 21, 2026
037cb6b
docs(architecture): add realistic v1 definition of done
Patel230 Jun 21, 2026
d62f0b1
docs(architecture): require human-only commit authorship in v1 bar
Patel230 Jun 21, 2026
4ec0c69
chore: bump external hawk-core-contracts after co-author cleanup
Patel230 Jun 21, 2026
a2a4583
chore: align hawk external architecture snapshot
Patel230 Jun 21, 2026
46697b9
docs: retire tok types shim references
Patel230 Jun 21, 2026
c204597
docs: normalize architecture status
Patel230 Jun 21, 2026
ecf2d0d
docs: add upstream release convergence plan
Patel230 Jun 21, 2026
d2b33c5
fix(ci): align setup-deps go.work format and add hawk-core-contracts
Patel230 Jun 21, 2026
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
6 changes: 3 additions & 3 deletions .github/actions/checkout-eyrie/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Checkout eyrie
description: Clone ecosystem repos into hawk/external for hawk go.work
name: Checkout ecosystem
description: Clone Hawk ecosystem repos into hawk/external for hawk go.work

inputs:
ref:
Expand All @@ -15,7 +15,7 @@ runs:
run: |
set -euo pipefail
mkdir -p "${GITHUB_WORKSPACE}/external"
for repo in eyrie inspect sight tok trace yaad; do
for repo in hawk-core-contracts eyrie inspect sight tok trace yaad; do
dest="${GITHUB_WORKSPACE}/external/${repo}"
if [ -d "$dest/.git" ]; then
echo "$repo already present at $dest"
Expand Down
3 changes: 2 additions & 1 deletion .github/actions/setup-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ runs:
echo "Failed to clone $repo after 3 attempts" && return 1
}
mkdir -p external
clone_with_retry hawk-core-contracts external/hawk-core-contracts main
clone_with_retry eyrie external/eyrie main
clone_with_retry tok external/tok main
clone_with_retry yaad external/yaad main
Expand All @@ -41,4 +42,4 @@ runs:
- name: Create workspace
shell: bash
run: |
printf 'go 1.26.4\n\nuse (\n\t.\n\t./external/eyrie\n\t./external/inspect\n\t./external/sight\n\t./external/tok\n\t./external/trace\n\t./external/yaad\n)\n' > go.work
printf 'go 1.26.4\n\nuse .\n\nreplace (\n\tgithub.com/GrayCodeAI/hawk-core-contracts => ./external/hawk-core-contracts\n\tgithub.com/GrayCodeAI/eyrie => ./external/eyrie\n\tgithub.com/GrayCodeAI/inspect => ./external/inspect\n\tgithub.com/GrayCodeAI/sight => ./external/sight\n\tgithub.com/GrayCodeAI/tok => ./external/tok\n\tgithub.com/GrayCodeAI/trace => ./external/trace\n\tgithub.com/GrayCodeAI/yaad => ./external/yaad\n)\n' > go.work
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ jobs:
echo "$out" | head -20
exit 1
fi
- name: shared types import guard
run: bash ./scripts/check-shared-types-imports.sh
- name: ecosystem boundary guard
run: bash ./scripts/check-ecosystem-boundaries.sh
- name: eyrie client boundary guard
run: bash ./scripts/check-eyrie-client-imports.sh
- name: support repo coupling guard
run: bash ./scripts/check-support-repo-coupling.sh

# -------------------------------------------------------------------------
# 2. Module hygiene — tidy, verify (hawk + external ecosystem repos via go.work).
Expand Down Expand Up @@ -89,7 +97,7 @@ jobs:
run: go mod verify
- name: workspace points at external checkouts
run: |
for module in eyrie inspect sight tok trace yaad; do
for module in hawk-core-contracts eyrie inspect sight tok trace yaad; do
if ! grep -q "./external/${module}" go.work; then
echo "::error::go.work must include ./external/${module}."
cat go.work
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ hawk
.gocache-*/
.gomodcache/
.gomodcache-*/
.tmp/
*.codegraph.db
.DS_Store

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "external/trace"]
path = external/trace
url = https://github.com/GrayCodeAI/trace.git
[submodule "external/hawk-core-contracts"]
path = external/hawk-core-contracts
url = https://github.com/GrayCodeAI/hawk-core-contracts.git
26 changes: 15 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,24 @@ hawk/
│ ├── daemon/ # Background HTTP/SSE server
│ ├── resilience/ # Circuit breaker, rate limiting, health checks
│ └── feature/ # Eval, fingerprint, scaffolding
├── shared/types/ # Cross-repo exported types (severity, etc.)
├── docs/ # Architecture docs, research notes
└── testdata/ # Test fixtures
```

## Key Design Decisions

- **Zero CGO:** Pure Go, cross-compilable. Tree-sitter is optional.
- **`internal/` is private:** Other repos import `shared/types/` only.
- **`internal/` is private:** Cross-repo contracts belong in `hawk-core-contracts`, not `internal/`.
- **Tool safety layer:** Every tool call goes through permissions (guardian,
rules DSL, boundary checker) before execution.
- **Engine-first:** The agent loop in `internal/engine/` orchestrates context
packing, tool dispatch, streaming, and session persistence.
- **Ecosystem integration:** eyrie handles all LLM API communication. hawk
never talks to LLM APIs directly.
never talks to LLM APIs directly, and production code should go through
`internal/types` transport adapters instead of importing `eyrie/client`.
- **Shared contracts:** cross-repo types now live in `hawk-core-contracts`
(`types`, `review`, `verify`, `tools`, `events`, `policy`). The old
`hawk/shared/types` path has been removed.

## Development Guidelines

Expand Down Expand Up @@ -145,8 +148,8 @@ test: add coverage for guardian

- `CONTRIBUTING.md` — PR process, commit conventions
- `docs/` — Architecture details, security model, ecosystem message flow
- `external/` — Ecosystem repo checkouts for `go.work` development
- `shared/types/` — Types exported for sight/inspect/tok (they must not import `internal/`)
- `external/` — Pinned ecosystem submodules used by `go.work` for local and CI integration
- `hawk-core-contracts/` — Shared cross-repo contracts; use this instead of any legacy Hawk-owned shared-type path

## Testing Philosophy

Expand All @@ -157,13 +160,13 @@ test: add coverage for guardian

## Common Pitfalls

- Do not import `internal/` from other ecosystem repos — use `shared/types/`
- Do not import `internal/` from other ecosystem repos — use `hawk-core-contracts`
- Do not put API keys in `.env` or shell env for hawk — use `/config` (OS keychain)
- The `external/` directory is for local dev only; CI clones repos separately
- The `external/` directory is part of the committed integration layout
- `go.work` and `go.work.sum` are committed — CI's `module hygiene` job
runs `go work sync` and asserts the result is in sync with the repo. Both
files point at `./external/*` checkouts; the `.github/actions/checkout-eyrie`
action populates `./external/` on CI runners before the build runs.
files point at `./external/*` submodules so Hawk can build against pinned
support-repo revisions.

## Naming Conventions

Expand Down Expand Up @@ -202,6 +205,7 @@ test: add coverage for guardian
- **Fuzz tests** for input parsing robustness: `func FuzzFoo(f *testing.F) { ... }`
- **No mocks framework** — use concrete types and test doubles
- **Meta-audit tests** in `internal/testaudit/` enforce architectural invariants via go/ast
including transport-boundary and deprecated-compatibility-package checks.

## Refactoring Guidelines

Expand All @@ -211,7 +215,7 @@ test: add coverage for guardian
- **Caution**: `internal/engine/session.go` Session struct — widely referenced across 30+ sub-packages
- **Caution**: `internal/config/settings.go` Settings struct — serialized to JSON, dual-format (snake_case + camelCase)
- **Caution**: `internal/tool/` Tool interface — implemented by 40+ tools
- **Blocked**: `shared/types/` — exported to eyrie, sight, inspect, tok; changes break ecosystem
- **Migration-sensitive**: `hawk/shared/types` has been removed; use `hawk-core-contracts/types` instead.

## Key File Locations

Expand Down Expand Up @@ -250,6 +254,6 @@ test: add coverage for guardian
- **No `panic()` for error handling** — return `error` values. Exception: `init()` functions for package-level assertions.
- **No `fmt.Print` for logging** — use `logger.Logger` with structured fields. Exception: `internal/onboarding/` and `internal/engine/scaffold/` for user-facing CLI output.
- **No API keys in settings.json** — use OS secret store via `credentials` package and `/config` command.
- **No importing `internal/` from other ecosystem repos** — use `shared/types/` for cross-repo types.
- **No importing `internal/` from other ecosystem repos** — use `hawk-core-contracts` for cross-repo types.
- **No global mutable state** — prefer dependency injection via `deps` structs or `context.WithValue`.
- **No `t.Skip()` without a tracking issue** — every skipped test needs a GitHub issue number.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Version re-baselined to `0.1.0`** across `cmd/hawk/main.go`, `cmd/daemon.go`,
`flake.nix`, `.github/workflows/release.yml`, and the `update`/daemon test suites, aligning hawk
with the rest of the GrayCodeAI ecosystem (`eyrie`, `tok`, `yaad`, `sight`, `inspect`).
- **Architecture boundary hardening**: Hawk now owns runtime request/response DTOs, transport config/provider seams, and review/verification product-boundary contracts, with `eyrie/client` usage restricted to internal adapters and guarded in CI.
- **`shared/types` removed**: Hawk no longer ships the old shared type path, and local boundary checks now block any attempt to reintroduce it.

### Added
- **Watch mode (`--watch`)**: file-watcher loop that acts on `AI!` (do-now) and `AI?` (answer) code comments. Off by default.
Expand All @@ -33,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`GET /v1/ready`**: dependency-aware readiness endpoint on the daemon.
- REPL magic commands (%reset, %undo, %tokens, %history, %copy, %save, %compact, %model, %clear)
- Prompt cache keep-alive pings
- Unified Finding type in shared/types for cross-tool interoperability
- Unified finding/severity contracts now live in `hawk-core-contracts/types`

### Added — Round 2 ecosystem improvements (2026-06-01)
- **Cavecrew personas** (`internal/multiagent/agents`): three new
Expand Down
29 changes: 22 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ GORELEASER := $(GOBIN_DIR)/goreleaser
# ---------------------------------------------------------------------------
# Phony declarations (alphabetical).
# ---------------------------------------------------------------------------
.PHONY: all bench build ci clean cover cover-new fmt help install lint lint-fix \
.PHONY: all bench build ci clean contracts-guard ecosystem-guard eyrie-client-guard peer-guard cover cover-new fmt help install lint lint-fix \
release security setup smoke path test test-10x test-live test-new test-race tidy version vet

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -99,6 +99,18 @@ fmt: ## Format source files (gofumpt + goimports).
vet: ## Run go vet.
go vet ./...

contracts-guard: ## Fail on any legacy imports of removed hawk/shared/types.
bash ./scripts/check-shared-types-imports.sh

ecosystem-guard: ## Fail if external ecosystem repos import hawk/internal or removed hawk/shared/types.
bash ./scripts/check-ecosystem-boundaries.sh

eyrie-client-guard: ## Fail on new direct eyrie/client imports outside Hawk transport adapters.
bash ./scripts/check-eyrie-client-imports.sh

peer-guard: ## Fail if support engines import each other instead of depending only on Hawk contracts.
bash ./scripts/check-support-repo-coupling.sh

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
Expand All @@ -118,7 +130,7 @@ tidy: ## Sync workspace modules and verify checksums.
# ---------------------------------------------------------------------------
# Composite gate used by CI and pre-push.
# ---------------------------------------------------------------------------
ci: tidy fmt vet lint test-race security ## Run everything CI runs.
ci: tidy fmt vet contracts-guard ecosystem-guard eyrie-client-guard peer-guard lint test-race security ## Run everything CI runs.
@echo "All CI checks passed."

smoke: ## Quick build + doctor + ecosystem verification.
Expand All @@ -142,12 +154,14 @@ clean: ## Remove build artefacts.
# ---------------------------------------------------------------------------
# Setup — bootstrap local development environment.
# ---------------------------------------------------------------------------
FOUNDATION_REPOS := hawk-core-contracts
ECO_REPOS := eyrie inspect sight tok trace yaad
WORKSPACE_REPOS := $(FOUNDATION_REPOS) $(ECO_REPOS)

setup: ## Set up local development environment (go.work + external repos).
@echo "=== Setting up hawk development environment ==="
@mkdir -p external
@for repo in $(ECO_REPOS); do \
@for repo in $(WORKSPACE_REPOS); do \
if [ ! -d "external/$$repo" ]; then \
echo "Cloning $$repo..."; \
git clone --depth=1 "https://github.com/GrayCodeAI/$$repo.git" "external/$$repo" 2>/dev/null || \
Expand All @@ -159,11 +173,12 @@ setup: ## Set up local development environment (go.work + external repos).
@echo "Generating go.work..."
@echo "go 1.26.4" > go.work
@echo "" >> go.work
@echo "use (" >> go.work
@echo " ." >> go.work
@for repo in $(ECO_REPOS); do \
@echo "use ." >> go.work
@echo "" >> go.work
@echo "replace (" >> go.work
@for repo in $(WORKSPACE_REPOS); do \
if [ -d "external/$$repo" ]; then \
echo " ./external/$$repo" >> go.work; \
echo " github.com/GrayCodeAI/$$repo => ./external/$$repo" >> go.work; \
fi; \
done
@echo ")" >> go.work
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,13 +314,28 @@ hawk/

### Ecosystem

hawk integrates these GrayCodeAI repos in three ways:
hawk integrates these GrayCodeAI repos in three layers:

- **`go.mod` modules:** **eyrie**, **sight**, **inspect**, **tok**, **yaad** — pinned module requirements.
- **External checkout + `go.work`:** **eyrie**, **sight**, **inspect**, **tok**, **yaad**, **trace** — clone ecosystem repos under `external/<repo>`. `go.work` lists the local external checkouts. CI clones the same layout via **`.github/actions/checkout-eyrie`**.
- **Optional CLI (no Go import):** **trace** — installed separately; `hawk` shells into `trace` for session capture when present.
- **Primary product:** **hawk** is the only end-user product surface in this ecosystem.
- **Support engines mounted by Hawk:** **eyrie**, **yaad**, **tok**, **trace**, **sight**, **inspect**. Hawk imports or shells into these engines behind its own command surface.
- **Shared foundation:** **hawk-core-contracts** holds the neutral cross-repo types that keep the engines independent from Hawk internals.

Cross-repo types (severity, etc.) are exported from **`github.com/GrayCodeAI/hawk/shared/types`** so **sight** / **inspect** / **tok** do not import **`internal/`**.
Local development uses:

- **`go.mod` modules:** pinned requirements for the support engines and `hawk-core-contracts`
- **External checkout + `go.work`:** clone support repos under `external/<repo>`; `go.work` maps the module paths to those local checkouts
- **Submodules in this repo:** the same external layout is pinned under `external/` for reproducible CI and multi-repo work

Cross-repo contracts now live in **`github.com/GrayCodeAI/hawk-core-contracts`** so support repos do not depend on Hawk internals. The old `hawk/shared/types` path has been removed; use `hawk-core-contracts/types` for shared severity and finding contracts.

Current contract packages:

- `hawk-core-contracts/types` — severity, findings
- `hawk-core-contracts/review` — normalized review findings, comments, stats, results
- `hawk-core-contracts/verify` — normalized verification findings, stats, reports
- `hawk-core-contracts/tools` — tool call/result contracts
- `hawk-core-contracts/events` — normalized tool/trace events
- `hawk-core-contracts/policy` — permission and policy verdict contracts

You may keep a **personal** parent **`go.work`** that lists alternate clones on disk for multi-repo development.

Expand All @@ -332,7 +347,10 @@ You may keep a **personal** parent **`go.work`** that lists alternate clones on
| **inspect** | [GrayCodeAI/inspect](https://github.com/GrayCodeAI/inspect) | Site audit library |
| **tok** | [GrayCodeAI/tok](https://github.com/GrayCodeAI/tok) | Tokenizer & compression |
| **yaad** | [GrayCodeAI/yaad](https://github.com/GrayCodeAI/yaad) | Graph-based memory |
| **trace** | [GrayCodeAI/trace](https://github.com/GrayCodeAI/trace) | Session capture CLI |
| **trace** | [GrayCodeAI/trace](https://github.com/GrayCodeAI/trace) | Session capture and replay engine mounted as `hawk trace ...` |
| **hawk-core-contracts** | [GrayCodeAI/hawk-core-contracts](https://github.com/GrayCodeAI/hawk-core-contracts) | Shared contracts and neutral cross-repo vocabulary |

For the consolidated repo map and the current-vs-proposed architecture diagrams, see [docs/architecture/hawk-current-vs-proposed.md](docs/architecture/hawk-current-vs-proposed.md).

## Development

Expand Down
2 changes: 1 addition & 1 deletion cmd/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func prepareSession(sess *engine.Session) (string, *session.Session, error) {
if err != nil {
return "", nil, err
}
sess.LoadMessages(toEyrieMessages(saved.Messages))
sess.LoadMessages(session.ToRuntimeMessages(saved.Messages))
if forkSessionFlag {
if sessionIDFlag != "" {
id = sessionIDFlag
Expand Down
31 changes: 3 additions & 28 deletions cmd/chat_commands_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

tea "github.com/charmbracelet/bubbletea"

"github.com/GrayCodeAI/eyrie/client"
"github.com/GrayCodeAI/hawk/internal/session"
"github.com/GrayCodeAI/hawk/internal/storage"
)
Expand All @@ -22,19 +21,9 @@ func (m *chatModel) saveSession() {
if len(raw) == 0 {
return
}
var msgs []session.Message
for _, rm := range raw {
sm := session.Message{Role: rm.Role, Content: rm.Content}
sm.ToolUse = append(sm.ToolUse, rm.ToolUse...)
if len(rm.ToolResults) > 0 {
sm.ToolResults = make([]session.ToolResult, len(rm.ToolResults))
copy(sm.ToolResults, rm.ToolResults)
}
msgs = append(msgs, sm)
}
err := session.Save(&session.Session{
ID: m.sessionID, Model: m.session.Model(), Provider: m.session.Provider(),
Messages: msgs, CreatedAt: time.Now(),
Messages: session.FromRuntimeMessages(raw), CreatedAt: time.Now(),
})
// On successful save, WAL is no longer needed (session file has everything)
if err == nil && m.wal != nil {
Expand Down Expand Up @@ -132,15 +121,8 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
m.sessionID = s.ID
m.invalidateViewportCache()
m.messages = []displayMsg{{role: "welcome", content: m.welcomeCache}}
var msgs []client.EyrieMessage
msgs := session.ToRuntimeMessages(s.Messages)
for _, sm := range s.Messages {
em := client.EyrieMessage{Role: sm.Role, Content: sm.Content}
em.ToolUse = append(em.ToolUse, sm.ToolUse...)
if len(sm.ToolResults) > 0 {
em.ToolResults = make([]client.ToolResult, len(sm.ToolResults))
copy(em.ToolResults, sm.ToolResults)
}
msgs = append(msgs, em)
if sm.Role == "user" || sm.Role == "assistant" {
m.messages = append(m.messages, displayMsg{role: sm.Role, content: sm.Content})
}
Expand Down Expand Up @@ -179,15 +161,8 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
m.sessionID = saved.ID
m.invalidateViewportCache()
m.messages = []displayMsg{{role: "welcome", content: m.welcomeCache}}
var msgs []client.EyrieMessage
msgs := session.ToRuntimeMessages(saved.Messages)
for _, sm := range saved.Messages {
em := client.EyrieMessage{Role: sm.Role, Content: sm.Content}
em.ToolUse = append(em.ToolUse, sm.ToolUse...)
if len(sm.ToolResults) > 0 {
em.ToolResults = make([]client.ToolResult, len(sm.ToolResults))
copy(em.ToolResults, sm.ToolResults)
}
msgs = append(msgs, em)
if sm.Role == "user" || sm.Role == "assistant" {
m.messages = append(m.messages, displayMsg{role: sm.Role, content: sm.Content})
}
Expand Down
Loading
Loading