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
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ 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: internal layer boundary guard
Expand Down
4 changes: 2 additions & 2 deletions .shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cd "$ROOT_DIR"
# Engines are peers: they may depend on falcon,
# but never on hawk/internal/* or another engine.
# =============================================================================
FORBIDDEN_HAWK='github\.com/GrayCodeAI/hawk/(internal/|shared/types)'
FORBIDDEN_HAWK='github\.com/GrayCodeAI/hawk/internal/'
FORBIDDEN_ENGINES='github\.com/GrayCodeAI/(harrier|shrike|swift|kestrel|merlin)(/|")'
# ^ list every OTHER engine here — never include yourself.

Expand All @@ -34,7 +34,7 @@ if [[ -n "${violations}" ]]; then
echo "forbidden Hawk imports found:"
echo "${violations}"
echo
echo "support repos must use local contracts, not hawk/internal or removed hawk/shared/types"
echo "support repos must use local contracts, not hawk/internal"
exit_code=1
fi

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ hawk verify

### Architecture note: cross-repo contracts

Legacy `hawk/shared/types` has been removed. Cross-repo severity and finding contracts now live in hawk's `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) — extensions and support repos must vendor the needed DTOs instead of Hawk internals until a published contracts module exists.
Cross-repo severity and finding contracts now live in hawk's `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) — extensions and support repos must vendor the needed DTOs instead of Hawk internals until a published contracts module exists.

### Architecture note: provider ownership

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Makefile lint pin matches CI**: `make lint`/`lint-fix`/`setup` install `golangci-lint@v2.1.0` (was `@latest`), the same version CI enforces.
- **Docs truth and housekeeping**: SECURITY.md/CONTRIBUTING.md now describe the actual Go toolchain (golangci-lint, go vet, govulncheck) instead of the polyglot template's ruff/mypy/pip-audit/pnpm-lock language, CONTRIBUTING documents `make setup`/`boundaries`/`test-10x`/`smoke`, and the planning docs (`SPEC_DRIVEN_PLAN.md`, `SPEC_DRIVEN_PHASE2_PLAN.md`, `internal/engine/REFACTOR_PLAN.md`) moved to `docs/plans/`.

### Removed
- **BREAKING — `hawk credentials migrate` removed**: the subcommand, its man page entry, and the `MigrateEnvFileCredentials` wrappers are gone, and `hawk path` no longer reports legacy `~/.hawk/env` / `~/.hawk/.env` files. Save keys through `/config`.
- **BREAKING — settings model/provider migration removed**: `LoadSettings` no longer moves `model`/`provider` from `settings.json` into Eyrie's `provider.json`, and `SetActiveSelection` is deleted. Stale `model`/`provider` values left in `settings.json` are now ignored on load (Eyrie's selection wins; `--settings` overrides still apply); select the model in `/config`.
- **BREAKING — startup provider-secrets migration removed**: print/REPL/watch/TUI startup no longer calls `MigrateProviderSecrets`, and the method is dropped from the gateway `CatalogMaintenance` interface. `hawk path` still fails when `provider.json` holds secrets; remove those fields manually.
- **BREAKING — `shared/types` guards removed**: `scripts/check-shared-types-imports.sh`, the `contracts-guard` make target, its lefthook and CI steps, and the matching testaudit checks are deleted because the package no longer exists. The ecosystem boundary guards still block `hawk/internal` imports.

## [0.2.0] — 2026-07-13

### Changed
Expand Down
9 changes: 3 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ GORELEASER := $(GOBIN_DIR)/goreleaser
# ---------------------------------------------------------------------------
# Phony declarations (alphabetical).
# ---------------------------------------------------------------------------
.PHONY: all bench boundaries build check-replace ci clean contracts-guard ecosystem-guard eyrie-client-guard eyrie-engine-guard manifest-guard peer-guard internal-layers-guard package-boundaries-guard release-parity cover cover-new fmt help install lint lint-fix \
.PHONY: all bench boundaries build check-replace ci clean ecosystem-guard eyrie-client-guard eyrie-engine-guard manifest-guard peer-guard internal-layers-guard package-boundaries-guard release-parity cover cover-new fmt help install lint lint-fix \
release security setup smoke path sync test test-10x test-live test-new test-race tidy version vet api-docs api-validate workspace

check-replace: ## Fail if go.mod has local replace directives (run before tagging)
Expand Down Expand Up @@ -112,10 +112,7 @@ 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.
ecosystem-guard: ## Fail if external ecosystem repos import hawk/internal.
bash ./scripts/check-ecosystem-boundaries.sh

eyrie-client-guard: ## Fail on any production eyrie/client import.
Expand All @@ -133,7 +130,7 @@ internal-layers-guard: ## Enforce one-way dependencies across stable Hawk intern
package-boundaries-guard: ## Enforce AST/package-graph boundaries with file/line diagnostics.
bash ./scripts/check-package-boundaries.sh

boundaries: manifest-guard check-replace contracts-guard ecosystem-guard eyrie-client-guard eyrie-engine-guard peer-guard internal-layers-guard package-boundaries-guard ## Alias for all boundary guards (matches `make boundaries` in engine repos).
boundaries: manifest-guard check-replace ecosystem-guard eyrie-client-guard eyrie-engine-guard peer-guard internal-layers-guard package-boundaries-guard ## Alias for all boundary guards (matches `make boundaries` in engine repos).

release-parity: ## Verify every go.mod ecosystem version resolves to a reachable remote commit.
bash ./scripts/check-module-release-parity.sh
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,7 @@ Local development uses:

Cross-repo contracts now live in `internal/contracts` (vendored from the
removed `github.com/GrayCodeAI/eagle` module) so support repos do not depend
on Hawk internals. The old `hawk/shared/types` path has been removed;
external consumers should vendor the needed DTOs from `internal/contracts`
on Hawk internals. External consumers should vendor the needed DTOs from `internal/contracts`
until a published contracts module exists.

Current contract packages (`internal/contracts/`):
Expand Down
23 changes: 0 additions & 23 deletions cmd/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,7 @@ var credentialsRemoveCmd = &cobra.Command{
},
}

var credentialsMigrateCmd = &cobra.Command{
Use: "migrate",
Short: "Import plaintext credential files into the OS secret store",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
storage := hawkconfig.CredentialStorageStatus(ctx)
if !storage.Writable {
return fmt.Errorf("cannot migrate: %s", storage.Detail)
}
n, err := hawkconfig.MigrateEnvFileCredentials(ctx)
if err != nil {
return err
}
if n == 0 {
cmd.Println(auditTint("No plaintext credential files found (already using secure storage).", textMuted))
} else {
cmd.Printf("%s\n", auditTint(fmt.Sprintf("Migrated %d key(s) to %s and removed plaintext credential files.", n, hawkconfig.CredentialStoreName()), doneGreen))
}
return nil
},
}

func init() {
credentialsCmd.AddCommand(credentialsStatusCmd)
credentialsCmd.AddCommand(credentialsMigrateCmd)
credentialsCmd.AddCommand(credentialsRemoveCmd)
}
1 change: 0 additions & 1 deletion cmd/manpage.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ func GenerateManPage() string {
b.WriteString("Use \\fBhawk\\fR and \\fB/config\\fR to save keys; hawk does not read API keys from .env files.\n")
b.WriteString(".TP\n\\fBhawk credentials status\\fR\nShow secure storage status\n")
b.WriteString(".TP\n\\fBhawk credentials remove <provider|env-var>\\fR\nRemove a stored API key from the OS secret store\n")
b.WriteString(".TP\n\\fBhawk credentials migrate\\fR\nImport legacy plaintext credential files into the OS store\n")

// Environment
b.WriteString(".SH ENVIRONMENT\n")
Expand Down
65 changes: 0 additions & 65 deletions cmd/migrate_secrets_test.go

This file was deleted.

33 changes: 0 additions & 33 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

hawkconfig "github.com/GrayCodeAI/hawk/internal/config"
"github.com/GrayCodeAI/hawk/internal/engine"
"github.com/GrayCodeAI/hawk/internal/observability/logger"
"github.com/GrayCodeAI/hawk/internal/onboarding"
"github.com/GrayCodeAI/hawk/internal/plugin"
"github.com/GrayCodeAI/hawk/internal/session"
Expand Down Expand Up @@ -147,11 +146,6 @@ Run hawk and use /config to set up your first provider.`, registeredProviderCoun
}

if printMode || promptFlag != "" || inputFormat == "stream-json" || replFlag || watchFlag {
// Credential migration is deferred until a path that actually
// uses credentials: `hawk path`, `hawk version`, auto-skill and
// other cold commands no longer construct the eyrie engine
// (M17 — was ~1.8s on every root command).
logMigrateProviderSecretsError(logger.Default(), hawkconfig.MigrateProviderSecrets())
if promptFlag == "" && !replFlag && !watchFlag {
stdinPrompt, err := readPromptFromStdin(inputFormat)
if err != nil {
Expand Down Expand Up @@ -205,9 +199,6 @@ Run hawk and use /config to set up your first provider.`, registeredProviderCoun
return err
}

// TUI path uses credentials — run the one-time hygiene pass here.
logMigrateProviderSecretsError(logger.Default(), hawkconfig.MigrateProviderSecrets())

// Folder trust check — block starting CLI in an untrusted directory
if tr := engine.ProjectTrust(""); tr.Blocked {
return fmt.Errorf("cannot start CLI: folder not trusted (%s)\nProject-scoped hooks, MCP servers, and custom specialists are blocked.\nRun 'hawk trust add' to trust this folder before starting hawk", tr.Path)
Expand Down Expand Up @@ -977,27 +968,3 @@ func resumeRecoveredSession(ctx context.Context, sessionID string) error {
}
return recoverRunChat()
}

// logMigrateProviderSecretsError surfaces a non-nil error from
// hawkconfig.MigrateProviderSecrets via the structured logger.
//
// MigrateProviderSecrets is a one-time hygiene pass that strips API keys
// from the on-disk provider.json (a known-bad location — see AGENTS.md).
// If it fails, the keys remain in the file and the user must be told so
// they can run hawk /config to move them to the OS keychain. Previously
// the error was silently discarded (cmd/root.go:114), so a failure left
// the user with secrets in plaintext and no indication that anything was
// wrong.
//
// We log and continue rather than failing startup: the migration is
// best-effort, and a missing or unreadable provider.json is not
// fatal — the rest of the app can still function.
func logMigrateProviderSecretsError(l *logger.Logger, err error) {
if err == nil {
return
}
l.Warn(
"provider secret migration failed; API keys may remain in provider.json. Run `hawk /config` to move them to the OS keychain.",
map[string]interface{}{"err": err.Error()},
)
}
2 changes: 1 addition & 1 deletion docs/COMPETITIVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ External star counts below are approximate web-search snapshots (2026-09-08), no
5. **Sandbox.** Docker-only fail-closed is strictest default alongside Codex net-off and Gemini gVisor. Tradeoff is onboarding friction without Docker. Must not add host-exec fallback (violates `docs/SECURITY-DEVELOPER.md:71-73`); fix with preflight/path/doctor messaging + image pull/build guidance. Gap-01.
6. **Memory/context.** AST repomap + Harrier graph + compaction segments + relevance-prune + conversation-arc + 80% tool-result clearing exceeds most. Missing: Hermes-style auto-skill learning loop (we have curator archive + harness — surface it).
7. **Multi-agent.** `mission` worktrees + family messenger + path reservations + budgets + portable `mission-graph.json` + `graph export` (hashes only) is unique verifiable-execution story. Surface it; no new runtime needed.
8. **MCP/skills/plugins.** MCP stdio/HTTP/SSE/WS + LSP + skills search/install/audit + curator matches Goose/Gemini/Codex. Contracts live in `internal/contracts` (no `shared/types`) — extensions vendor DTOs. Correct; don't regress.
8. **MCP/skills/plugins.** MCP stdio/HTTP/SSE/WS + LSP + skills search/install/audit + curator matches Goose/Gemini/Codex. Contracts live in `internal/contracts` — extensions vendor DTOs. Correct; don't regress.
9. **Media/computer-use.** Tools exist (`Browser/Screenshot/CodeMatch/SearchX/AppVerify/GenerateMedia/ComputerUse`) but media/computer/STT backends are nil-by-default seams. README notes router ships `ImageClient`/`AudioClient`; host wiring is the gap. Gap-05. Kitty graphics (image display) still missing despite terminal detection. Gap-03.
10. **Ops/determinism.** Daemon `:4590` health/ready/chat-SSE + cron + `exec --fanout N` + replay cache + circuit breaker + smart routing + harness eval is ahead of Pi minimalism and Aider single-agent. Keep; add published eval numbers (Gap-04).

Expand Down
3 changes: 1 addition & 2 deletions docs/DEVELOPER-PATH.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,13 @@ hawk models refresh
`hawk path` treats these as important security conditions:

- Eyrie's resolved `provider.json` must not contain secret fields
- legacy `~/.hawk/env` or `~/.hawk/.env` files should be migrated away
- sensitive files like provider config and SSH paths should be blocked from agent reads

Eyrie resolves provider state from `EYRIE_CONFIG_DIR` first, then the
platform user-config directory.
Hawk protects that resolved path even when it is customized or symlinked.

If Hawk detects old plaintext secrets, run Hawk once and complete `/config`, or remove the secret fields manually after backing up the file.
If Hawk detects secret fields in `provider.json`, back up the file, remove those fields manually, and save your keys again through `/config`.

Read the full credential and isolation model in [SECURITY-DEVELOPER.md](./SECURITY-DEVELOPER.md).

Expand Down
25 changes: 10 additions & 15 deletions docs/SECURITY-DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ This document describes how hawk and eyrie handle API keys and agent isolation f
|-------|------|--------|
| `/config` paste flow → `eyrie/engine.Engine.SaveCredential` | `Engine.ResolveCredential` (secret store only) | `/config key remove` or `hawk credentials remove` |

On startup, Hawk asks the Eyrie engine facade to migrate legacy
`~/.hawk/env` / `~/.hawk/.env` values into the secret store and delete those
files. It also imports recognized historical secret fields from
`provider.json` before atomically rewriting that file with metadata only. A
secret-store or state-write failure aborts the rewrite and rolls back newly
imported values.
Hawk does not import keys from legacy `~/.hawk/env` / `~/.hawk/.env` files or
from secret fields in `provider.json`. Save every key through `/config`.

Check status: `hawk credentials status`, `hawk path`, or `hawk preflight`.

Expand All @@ -41,7 +37,7 @@ Eyrie engine discover/apply (credentials from store, not JSON body)
SetupUI JSON (display_name + canonical_id per model)
|
v
User picks model -> settings.json (canonical id only)
User picks model -> Eyrie provider.json (canonical id only)
```

Remove a stored key: `/config key remove` (interactive picker).
Expand Down Expand Up @@ -84,15 +80,14 @@ required for users, and neither provisioning path enables host execution.
`~/.ssh/*`, etc.
- **Bash**: `printenv`, `env`, reading hawk env paths, echoing `*_API_KEY` variables.

## Migration
## Secrets left on disk

- **Legacy env files**: startup migration imports `~/.hawk/env` and
`~/.hawk/.env` into the OS secret store, then deletes the plaintext files.
- **provider.json secrets**: Eyrie transactionally imports recognized top-level
and deployment credentials, atomically writes sanitized metadata, and uses a
temporary `provider.json.pre-secret-migrate.bak` only during the transaction.
- **All subsequent writes**: the Eyrie engine applies the same sanitization and
atomic-write path, so migrated secret fields cannot be reintroduced.
- **provider.json secrets**: `hawk path` fails its `provider.json` security
check when the file still holds secret fields. Hawk does not remove them
automatically: back up the file, delete the secret fields, and save the keys
again through `/config`.
- **Provider state writes**: the Eyrie engine sanitizes provider state and
writes it atomically, so secret fields are never written back to disk.

## Provider state path

Expand Down
5 changes: 1 addition & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ hawk/
└── (ecosystem siblings live at ../<repo> in the graycode-eco workspace; see docs/architecture/ecosystem-design.md)
```

Legacy note: `hawk/shared/types` has been removed. Shared cross-repo severity
and finding contracts now live in `eagle/types`.

---

## <img src="https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/icons/globe.svg" width="16" height="16" alt="globe" /> Daemon HTTP API (:4590)
Expand Down Expand Up @@ -130,4 +127,4 @@ Tool Call → <img src="https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/i
| Zero CGO | Pure Go, cross-compilable. Tree-sitter is optional |
| `internal/` is private | Other repos should not import `internal/*` |
| `go.work` | Resolves the ecosystem siblings (`../<repo>`) for local and CI workspace integration |
| `eagle` | Shared cross-repo severity, findings, review, verify, tools, events, and policy contracts — engines import this instead of `hawk/internal` or removed `hawk/shared/types` |
| `eagle` | Shared cross-repo severity, findings, review, verify, tools, events, and policy contracts — engines import this instead of `hawk/internal` |
1 change: 0 additions & 1 deletion docs/architecture/ecosystem-implementation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ bash hawk/scripts/check-ecosystem-boundaries.sh
bash hawk/scripts/check-support-repo-coupling.sh
bash hawk/scripts/check-eyrie-engine-boundary.sh
bash hawk/scripts/check-eyrie-client-imports.sh
bash hawk/scripts/check-shared-types-imports.sh
bash hawk/scripts/check-no-replace-directives.sh
```

Expand Down
Loading
Loading