diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88e28452..6805af5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ env: # module's dependencies" and that "CI systems should generally not be allowed to # use the go.work file". Because ./.github/actions/checkout-eyrie clones the # siblings at branch HEAD into the workspace parent, workspace mode would silently - # hide version skew (e.g. a eagle pin that no longer matches). + # hide version skew (e.g. a shared pin that no longer matches). # The `module` job opts back in (GOWORK: "auto") since `go work sync` and the # go.work content checks genuinely require the workspace. GOWORK: "off" @@ -141,8 +141,6 @@ jobs: (cd "$dir" && GOWORK=off go mod verify) (cd "$dir" && GOWORK=off go test ./... -count=1 -timeout=300s -skip='TestDefaultSkillDirsCrossAgent|TestCopySelectionE2E') done < <(find . -name go.mod -not -path './.git/*' -print | sort) - - name: contracts parity guard - run: bash ./scripts/check-contracts-parity.sh public-modules: name: public module graph diff --git a/.shared-templates/docs/coverage-matrix.md b/.shared-templates/docs/coverage-matrix.md index c4eefaaf..620e9eec 100644 --- a/.shared-templates/docs/coverage-matrix.md +++ b/.shared-templates/docs/coverage-matrix.md @@ -17,7 +17,6 @@ repo's threshold, update both the CI file and this table in the same PR.** | `merlin` | 76% | `THRESHOLD=` in `ci.yml` (go-ci.yml.tmpl) | | `sparrow` | 80% | `THRESHOLD=` in `ci.yml` (go-ci.yml.tmpl) | | `robin` | 78% | `--cov-fail-under=` in `ci.yml` (python-ci.yml.tmpl) | -| `eagle` | none enforced | leaf library; add one before it grows past a handful of files | | `falcon` | none enforced | leaf library; add one before it grows past a handful of files | | `starling` | n/a | no Go/Python test suite (skill/content registry) | diff --git a/.shared-templates/lefthook.yml.tmpl b/.shared-templates/lefthook.yml.tmpl index d177327d..80d30873 100644 --- a/.shared-templates/lefthook.yml.tmpl +++ b/.shared-templates/lefthook.yml.tmpl @@ -139,7 +139,7 @@ prepare-commit-msg: sed '/^[Cc]o-[Aa]uthored-[Bb]y:/d' "{1}" > "{1}.tmp" && mv "{1}.tmp" "{1}" # --------------------------------------------------------------------------- -# Notes for foundation repos (eagle, falcon): these have +# Notes for foundation repos (falcon): these have # no graycode-eco dependencies at all, so `pre-push.commands.boundaries` checks # for *zero* GrayCodeAI/* imports rather than checking against a peer-engine # allowlist. The command line above is identical either way — only diff --git a/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl b/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl index c8f35c71..732ab550 100644 --- a/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl +++ b/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl @@ -13,7 +13,7 @@ cd "$ROOT_DIR" # ============================================================================= # VARIANT 1 — Support engine (eyrie, harrier, shrike, swift, kestrel, merlin). -# Engines are peers: they may depend on eagle and falcon, +# Engines are peers: they may depend on falcon, # but never on graycode/internal/* or another engine. # ============================================================================= FORBIDDEN_GRAYCODE='github\.com/GrayCodeAI/graycode-cli/(internal/|shared/types)' @@ -34,7 +34,7 @@ if [[ -n "${violations}" ]]; then echo "forbidden Graycode imports found:" echo "${violations}" echo - echo "support repos must use eagle or local contracts, not graycode/internal or removed graycode/shared/types" + echo "support repos must use local contracts, not graycode/internal or removed graycode/shared/types" exit_code=1 fi @@ -53,7 +53,7 @@ fi echo "ecosystem boundary guard passed" # ============================================================================= -# VARIANT 2 — Foundation repo (eagle, falcon). +# VARIANT 2 — Foundation repo (falcon). # Foundation repos sit below everything: zero graycode-eco dependencies at all. # ============================================================================= # diff --git a/.shared-templates/workflows/go-ci.yml.tmpl b/.shared-templates/workflows/go-ci.yml.tmpl index 676a7945..f3674249 100644 --- a/.shared-templates/workflows/go-ci.yml.tmpl +++ b/.shared-templates/workflows/go-ci.yml.tmpl @@ -228,7 +228,7 @@ jobs: run: go build ./... # ----------------------------------------------------------------------------- -# Foundation repos (eagle, falcon) have zero graycode-eco +# Foundation repos (falcon) have zero graycode-eco # dependencies, so they drop the GOPROXY/GOPRIVATE/GONOSUMDB env block above # and every "Clone " step that other repos may add for local # workspace deps — there is nothing to clone. diff --git a/AGENTS.md b/AGENTS.md index 13cf6b98..d11be339 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ alwaysApply: false # Extending graycode graycode is an open-source code intelligence platform. It lives in the `graycode-eco` -workspace alongside the ecosystem repos that power it (`eyrie`, `eagle`, +workspace alongside the ecosystem repos that power it (`eyrie`, `shrike`, `harrier`, `swift`, `kestrel`, `merlin`). This document describes how to extend graycode with custom tools, skills, hooks, and integrations. @@ -187,7 +187,7 @@ graycode verify ### Architecture note: cross-repo contracts -Legacy `graycode/shared/types` has been removed. Cross-repo severity and finding contracts now live in `github.com/GrayCodeAI/eagle` (`eagle/types`) — extensions and support repos must import that module instead of Graycode internals. +Legacy `graycode/shared/types` has been removed. Cross-repo severity and finding contracts now live in graycode-cli's `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) — extensions and support repos must vendor the needed DTOs instead of Graycode internals until a published contracts module exists. ### Architecture note: provider ownership @@ -245,7 +245,7 @@ This project is indexed by GitNexus as **graycode** (97743 symbols, 322940 relat ### Workspace workflow (sibling repos) -graycode depends on ecosystem repos (`eyrie`, `eagle`, etc.) as independent sibling repos in the `graycode-eco` workspace. Graycode's `go.work` lists them as `../`, so local changes in any sibling are automatically picked up by graycode. Each sibling is its own git repo, versioned and released independently. +graycode depends on ecosystem repos (`eyrie`, etc.) as independent sibling repos in the `graycode-eco` workspace. Graycode's `go.work` lists them as `../`, so local changes in any sibling are automatically picked up by graycode. Each sibling is its own git repo, versioned and released independently. 1. Edit + test in `../` — run its tests, run `make test` in graycode 2. Push from the sibling: `git push origin ` diff --git a/Makefile b/Makefile index 1594cd5e..75a936b8 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ GORELEASER := $(GOBIN_DIR)/goreleaser # --------------------------------------------------------------------------- # Phony declarations (alphabetical). # --------------------------------------------------------------------------- -.PHONY: all bench boundaries build check-replace ci clean contracts-guard contracts-parity 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 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 \ 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) @@ -133,10 +133,7 @@ internal-layers-guard: ## Enforce one-way dependencies across stable Graycode in package-boundaries-guard: ## Enforce AST/package-graph boundaries with file/line diagnostics. bash ./scripts/check-package-boundaries.sh -contracts-parity: ## Fail if ecosystem repos pin different Eagle versions (see ecosystem.yaml). - bash ./scripts/check-contracts-parity.sh - -boundaries: manifest-guard check-replace contracts-guard contracts-parity 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 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). release-parity: ## Verify every go.mod ecosystem version resolves to a reachable remote commit. bash ./scripts/check-module-release-parity.sh diff --git a/README.md b/README.md index 96a379ce..9cf22d26 100644 --- a/README.md +++ b/README.md @@ -437,7 +437,6 @@ graycode/ Ecosystem sibling repos (independent Git repos in the `graycode-eco` parent folder): ├── eyrie/ # LLM provider runtime -├── eagle/ # Shared cross-repo contracts ├── merlin/ # Merlin security audit library ├── kestrel/ # Kestrel diff-based code review ├── shrike/ # Shrike tokenizer, compression, secrets scanning @@ -452,7 +451,7 @@ three runtime layers plus optional tooling/platform services: - **Primary product:** **graycode** is the only end-user product surface in this ecosystem. - **Support engines mounted by Graycode:** **eyrie**, **harrier**, **shrike**, **swift**, **kestrel**, **merlin**. Graycode imports or shells into these engines behind its own command surface. -- **Shared foundations:** **eagle** holds neutral cross-repo types and **falcon** +- **Shared foundations:** **falcon** provides shared MCP server scaffolding. - **API consumers/extensions:** **sparrow**, **robin**, and **wren** consume Graycode's daemon API; **starling** provides Graycode skills. @@ -462,20 +461,26 @@ three runtime layers plus optional tooling/platform services: Local development uses: -- **`go.mod` modules:** pinned requirements for the support engines and `eagle` +- **`go.mod` modules:** pinned requirements for the support engines - **Workspace + `go.work`:** sibling support repos are cloned in the `graycode-eco` workspace (as `../`); `go.work` resolves the module paths to those local checkouts - **Module-mode builds:** standalone / Docker builds resolve the pinned `go.mod` versions from the module proxy (no workspace) -Cross-repo contracts now live in **`github.com/GrayCodeAI/eagle`** so support repos do not depend on Graycode internals. The old `graycode/shared/types` path has been removed; use `eagle/types` for shared severity and finding contracts. +Cross-repo contracts now live in `internal/contracts` (vendored from the +removed `github.com/GrayCodeAI/eagle` module) so support repos do not depend +on Graycode internals. The old `graycode/shared/types` path has been removed; +external consumers should vendor the needed DTOs from `internal/contracts` +until a published contracts module exists. -Current contract packages: +Current contract packages (`internal/contracts/`): -- `eagle/types` — severity, findings -- `eagle/review` — normalized review findings, comments, stats, results -- `eagle/verify` — normalized verification findings, stats, reports -- `eagle/tools` — tool call/result contracts -- `eagle/events` — normalized tool/swift events -- `eagle/policy` — permission and policy verdict contracts +- `types` — severity, findings +- `graph` — portable graph vocabulary: nodes, edges, events, provenance +- `agent` — typed subagent spawn DTOs and hook events +- `policy` — permission and policy verdict contracts +- `contracts/review` — normalized review findings, comments, stats, results +- `contracts/verify` — normalized verification findings, stats, reports +- `events` — tool, trace, and usage events +- `harness` — harness evaluation reports and dimension scores You may keep a **personal** parent **`go.work`** that lists alternate clones on disk for multi-repo development. @@ -488,7 +493,6 @@ You may keep a **personal** parent **`go.work`** that lists alternate clones on | **shrike** | [GrayCodeAI/shrike](https://github.com/GrayCodeAI/shrike) | Compression, redaction, token/cost budgets, and privacy-safe runtime graph facts | | **harrier** | [GrayCodeAI/harrier](https://github.com/GrayCodeAI/harrier) | Graph-based memory | | **swift** | [GrayCodeAI/swift](https://github.com/GrayCodeAI/swift) | Session capture and replay engine mounted as `graycode swift ...` | -| **eagle** | [GrayCodeAI/eagle](https://github.com/GrayCodeAI/eagle) | Shared contracts and neutral cross-repo vocabulary | For the consolidated repo map and the current-vs-proposed architecture diagrams, see [docs/architecture/graycode-current-vs-proposed.md](docs/architecture/graycode-current-vs-proposed.md). For execution-graph ownership, automatic capture seams, export/sync commands, diff --git a/cmd/chat_permission_keys_test.go b/cmd/chat_permission_keys_test.go index 634c4d0e..ed239c36 100644 --- a/cmd/chat_permission_keys_test.go +++ b/cmd/chat_permission_keys_test.go @@ -6,7 +6,7 @@ import ( "time" tea "charm.land/bubbletea/v2" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/engine" ) diff --git a/cmd/chat_prompt_timeout_test.go b/cmd/chat_prompt_timeout_test.go index b91505e1..83aac204 100644 --- a/cmd/chat_prompt_timeout_test.go +++ b/cmd/chat_prompt_timeout_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/engine" ) diff --git a/cmd/compat-test/drift.go b/cmd/compat-test/drift.go index 620bd0d6..2456bdfb 100644 --- a/cmd/compat-test/drift.go +++ b/cmd/compat-test/drift.go @@ -14,7 +14,6 @@ import ( // in graycode's newer version at build time without the consumer's own CI ever // having tested it. See docs/compatibility.md. var trackedPins = []string{ - "github.com/GrayCodeAI/eagle", "github.com/GrayCodeAI/falcon", } diff --git a/cmd/compat-test/drift_test.go b/cmd/compat-test/drift_test.go index f9ea66c6..27dac8dc 100644 --- a/cmd/compat-test/drift_test.go +++ b/cmd/compat-test/drift_test.go @@ -75,14 +75,14 @@ func TestCheckDrift_DetectsDrift(t *testing.T) { go 1.26 -require github.com/GrayCodeAI/eagle v1.5.0 +require github.com/GrayCodeAI/falcon v1.5.0 `) // Consumer sibling pins an older version of the shared contract. writeMod(t, filepath.Join(ws, "merlin", "go.mod"), `module github.com/GrayCodeAI/merlin go 1.26 -require github.com/GrayCodeAI/eagle v1.2.0 +require github.com/GrayCodeAI/falcon v1.2.0 `) var buf bytes.Buffer @@ -110,13 +110,13 @@ func TestCheckDrift_NoDriftWhenVersionsMatch(t *testing.T) { go 1.26 -require github.com/GrayCodeAI/eagle v1.5.0 +require github.com/GrayCodeAI/falcon v1.5.0 `) writeMod(t, filepath.Join(ws, "kestrel", "go.mod"), `module github.com/GrayCodeAI/kestrel go 1.26 -require github.com/GrayCodeAI/eagle v1.5.0 +require github.com/GrayCodeAI/falcon v1.5.0 `) var buf bytes.Buffer @@ -144,7 +144,7 @@ func TestCheckDrift_SkipsMissingSiblings(t *testing.T) { go 1.26 -require github.com/GrayCodeAI/eagle v1.5.0 +require github.com/GrayCodeAI/falcon v1.5.0 `) // Directory present but no go.mod — must be skipped silently. if err := os.MkdirAll(filepath.Join(ws, "not-checked-out"), 0o755); err != nil { diff --git a/cmd/execution_graph.go b/cmd/execution_graph.go index a1e1eaa2..14a266d7 100644 --- a/cmd/execution_graph.go +++ b/cmd/execution_graph.go @@ -10,8 +10,8 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/executiongraph" "github.com/GrayCodeAI/graycode-cli/internal/fsutil" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" diff --git a/cmd/execution_graph_test.go b/cmd/execution_graph_test.go index a19a1086..7d0931cd 100644 --- a/cmd/execution_graph_test.go +++ b/cmd/execution_graph_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/executiongraph" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" "github.com/GrayCodeAI/graycode-cli/internal/session" diff --git a/cmd/merlin_pipeline.go b/cmd/merlin_pipeline.go index 1900cdba..691cd012 100644 --- a/cmd/merlin_pipeline.go +++ b/cmd/merlin_pipeline.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - contracts "github.com/GrayCodeAI/eagle/types" - verifycontracts "github.com/GrayCodeAI/eagle/verify" graycodeMerlin "github.com/GrayCodeAI/graycode-cli/internal/bridge/merlin" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" + verifycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/verify" merlinLib "github.com/GrayCodeAI/merlin" ) diff --git a/cmd/merlin_pipeline_test.go b/cmd/merlin_pipeline_test.go index 89a3aab8..52e14e81 100644 --- a/cmd/merlin_pipeline_test.go +++ b/cmd/merlin_pipeline_test.go @@ -3,8 +3,8 @@ package cmd import ( "testing" - contracts "github.com/GrayCodeAI/eagle/types" - verifycontracts "github.com/GrayCodeAI/eagle/verify" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" + verifycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/verify" ) func TestMerlinToReviewFindings_NilReport(t *testing.T) { diff --git a/cmd/review_analyze.go b/cmd/review_analyze.go index 4086fbbb..0d0a5706 100644 --- a/cmd/review_analyze.go +++ b/cmd/review_analyze.go @@ -8,9 +8,9 @@ import ( "strings" "time" - reviewcontracts "github.com/GrayCodeAI/eagle/review" graycodeKestrel "github.com/GrayCodeAI/graycode-cli/internal/bridge/kestrel" graycodeconfig "github.com/GrayCodeAI/graycode-cli/internal/config" + reviewcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/review" "github.com/GrayCodeAI/graycode-cli/internal/engine" "github.com/GrayCodeAI/graycode-cli/internal/ui/icons" kestrelLib "github.com/GrayCodeAI/kestrel" diff --git a/cmd/review_run.go b/cmd/review_run.go index f1b1ab3b..2b7e0700 100644 --- a/cmd/review_run.go +++ b/cmd/review_run.go @@ -8,9 +8,9 @@ import ( "strings" "time" - reviewcontracts "github.com/GrayCodeAI/eagle/review" graycodeKestrel "github.com/GrayCodeAI/graycode-cli/internal/bridge/kestrel" graycodeconfig "github.com/GrayCodeAI/graycode-cli/internal/config" + reviewcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/review" "github.com/GrayCodeAI/graycode-cli/internal/engine" "github.com/GrayCodeAI/graycode-cli/internal/ui/icons" kestrelLib "github.com/GrayCodeAI/kestrel" diff --git a/cmd/review_run_test.go b/cmd/review_run_test.go index ffa3bfa6..7e1f4a35 100644 --- a/cmd/review_run_test.go +++ b/cmd/review_run_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - reviewcontracts "github.com/GrayCodeAI/eagle/review" - contracts "github.com/GrayCodeAI/eagle/types" + reviewcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/review" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" ) // captureStdout runs fn with stdout redirected to a pipe and returns what was diff --git a/cmd/review_store.go b/cmd/review_store.go index 51001ef9..71ac9402 100644 --- a/cmd/review_store.go +++ b/cmd/review_store.go @@ -10,7 +10,7 @@ import ( "sync" "time" - reviewcontracts "github.com/GrayCodeAI/eagle/review" + reviewcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/review" "github.com/GrayCodeAI/graycode-cli/internal/storage" ) diff --git a/cmd/review_test.go b/cmd/review_test.go index 5e03b311..cfd4c4ce 100644 --- a/cmd/review_test.go +++ b/cmd/review_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - reviewcontracts "github.com/GrayCodeAI/eagle/review" - contracts "github.com/GrayCodeAI/eagle/types" + reviewcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/review" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" "github.com/GrayCodeAI/graycode-cli/internal/storage" "github.com/GrayCodeAI/graycode-cli/internal/ui/icons" diff --git a/docs/DEVELOPER-PATH.md b/docs/DEVELOPER-PATH.md index 3441f4af..7c000b3a 100644 --- a/docs/DEVELOPER-PATH.md +++ b/docs/DEVELOPER-PATH.md @@ -33,7 +33,6 @@ workspace; they are not nested under Graycode: ```bash mkdir graycode-eco && cd graycode-eco git clone https://github.com/GrayCodeAI/graycode-cli -git clone https://github.com/GrayCodeAI/eagle git clone https://github.com/GrayCodeAI/eyrie git clone https://github.com/GrayCodeAI/falcon git clone https://github.com/GrayCodeAI/harrier diff --git a/docs/ECOSYSTEM-WIRING.md b/docs/ECOSYSTEM-WIRING.md index 97d669fd..c055b666 100644 --- a/docs/ECOSYSTEM-WIRING.md +++ b/docs/ECOSYSTEM-WIRING.md @@ -1,6 +1,10 @@ # GrayCode ecosystem wiring -This document is the implementation contract for the 15 repositories in the +> NOTE (2026-09-04): the `eagle` repository has been removed; its contracts +> were vendored into graycode-cli's `internal/contracts` and `ecosystem.yaml` +> no longer lists it. Diagrams below predate the removal. + +This document is the implementation contract for the 14 repositories in the GrayCodeAI ecosystem. `ecosystem.yaml` is the canonical machine-readable inventory; generated workspaces, boundary checks, release parity, and Owl's repository catalog derive from it. diff --git a/docs/architecture/ecosystem-implementation-plan.md b/docs/architecture/ecosystem-implementation-plan.md index 1e53e2c2..eb82dde9 100644 --- a/docs/architecture/ecosystem-implementation-plan.md +++ b/docs/architecture/ecosystem-implementation-plan.md @@ -74,7 +74,6 @@ bash owl/scripts/sync-ecosystem.sh Acceptance checks: ```text -bash graycode/scripts/check-contracts-parity.sh bash graycode/scripts/check-ecosystem-boundaries.sh bash graycode/scripts/check-support-repo-coupling.sh bash graycode/scripts/check-eyrie-engine-boundary.sh diff --git a/docs/architecture/graycode-contract-spec.md b/docs/architecture/graycode-contract-spec.md index 828990b8..198127db 100644 --- a/docs/architecture/graycode-contract-spec.md +++ b/docs/architecture/graycode-contract-spec.md @@ -97,8 +97,8 @@ list in sync with the code (it is the inventory the dependency rules assume). ## Current status - severity and finding contracts are live in `eagle` -- review result contracts now exist in `eagle/review` -- verification result contracts now exist in `eagle/verify` +- review result contracts now exist in `contracts/review` +- verification result contracts now exist in `contracts/verify` - tool contracts now exist in `eagle/tools` - event contracts now exist in `eagle/events` - policy contracts now exist in `eagle/policy` diff --git a/docs/architecture/graycode-product-architecture.md b/docs/architecture/graycode-product-architecture.md index 0b932c3d..88b5c1d3 100644 --- a/docs/architecture/graycode-product-architecture.md +++ b/docs/architecture/graycode-product-architecture.md @@ -169,13 +169,13 @@ Graycode does not own: - review findings - risk detection - code quality analysis -- review-engine-local output converted into shared `eagle/review` contracts at product boundaries +- review-engine-local output converted into shared `contracts/review` contracts at product boundaries ### `merlin` - verification checks - test/assertion normalization - final pass/fail validation -- verification-engine-local output converted into shared `eagle/verify` contracts at product boundaries +- verification-engine-local output converted into shared `contracts/verify` contracts at product boundaries ## Primary runtime flow diff --git a/docs/compatibility.md b/docs/compatibility.md index 8d3528e8..6cd0b041 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -70,8 +70,8 @@ It runs on: ## Pin freshness (advisory) -Separate from the matrix above: `graycode`'s own `go.mod` directly pins a couple -of shared leaf dependencies (currently `eagle`), and several sibling-repository +Separate from the matrix above: `graycode`'s own `go.mod` directly pins +shared leaf dependencies (currently `falcon` is tracked), and several sibling-repository consumers (`merlin`/Merlin, `kestrel`/Kestrel, ...) pin the *same* dependencies independently in their own `go.mod`. Go's minimal version selection means whatever `graycode` pins wins in `graycode`'s own build — but if a diff --git a/ecosystem.yaml b/ecosystem.yaml index b076118c..336aa0e5 100644 --- a/ecosystem.yaml +++ b/ecosystem.yaml @@ -5,7 +5,6 @@ schema_version: 1 # records the user-facing capability. Tooling must consume this file instead of # carrying its own repo-name list. contracts: - eagle_version: v0.0.0-20260902153929-5877bed17503 portable_graph_schema: graycode.graph/v1 cloud_graph_schema: graycode-cloud.graph/v1 usage_event: usage.recorded.v1 @@ -18,16 +17,6 @@ repositories: language: go module: github.com/GrayCodeAI/graycode-cli workspace: true - tracks_eagle: true - - - directory: eagle - github_repo: eagle - product_name: Eagle Contracts - kind: foundation - language: go - module: github.com/GrayCodeAI/eagle - workspace: true - tracks_eagle: false - directory: falcon github_repo: falcon @@ -36,7 +25,6 @@ repositories: language: go module: github.com/GrayCodeAI/falcon workspace: true - tracks_eagle: false - directory: eyrie github_repo: eyrie @@ -45,7 +33,6 @@ repositories: language: go module: github.com/GrayCodeAI/eyrie workspace: true - tracks_eagle: false facade: github.com/GrayCodeAI/eyrie/engine - directory: harrier @@ -55,7 +42,6 @@ repositories: language: go module: github.com/GrayCodeAI/harrier workspace: true - tracks_eagle: false facade: github.com/GrayCodeAI/harrier/engine - directory: shrike @@ -65,7 +51,6 @@ repositories: language: go module: github.com/GrayCodeAI/shrike workspace: true - tracks_eagle: false facade: github.com/GrayCodeAI/shrike - directory: swift @@ -75,7 +60,6 @@ repositories: language: go module: github.com/GrayCodeAI/swift workspace: true - tracks_eagle: false facade: github.com/GrayCodeAI/swift/cli - directory: kestrel @@ -85,7 +69,6 @@ repositories: language: go module: github.com/GrayCodeAI/kestrel workspace: true - tracks_eagle: false facade: github.com/GrayCodeAI/kestrel - directory: merlin @@ -95,7 +78,6 @@ repositories: language: go module: github.com/GrayCodeAI/merlin workspace: true - tracks_eagle: false facade: github.com/GrayCodeAI/merlin - directory: sparrow @@ -105,7 +87,6 @@ repositories: language: go module: github.com/GrayCodeAI/sparrow workspace: false - tracks_eagle: false - directory: robin github_repo: robin @@ -113,7 +94,6 @@ repositories: kind: sdk language: python workspace: false - tracks_eagle: false - directory: wren github_repo: wren @@ -121,7 +101,6 @@ repositories: kind: sdk language: typescript workspace: false - tracks_eagle: false - directory: starling github_repo: starling @@ -129,7 +108,6 @@ repositories: kind: extension language: python workspace: false - tracks_eagle: false - directory: owl github_repo: owl @@ -137,7 +115,6 @@ repositories: kind: tooling language: javascript workspace: false - tracks_eagle: false - directory: graycode-platform github_repo: graycode-platform @@ -145,4 +122,3 @@ repositories: kind: platform language: typescript workspace: false - tracks_eagle: false diff --git a/go.mod b/go.mod index 93c1eb2f..8dae49f5 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( charm.land/bubbles/v2 v2.1.1 charm.land/bubbletea/v2 v2.0.8 charm.land/lipgloss/v2 v2.0.5 - github.com/GrayCodeAI/eagle v0.0.0-20260902153929-5877bed17503 github.com/GrayCodeAI/eyrie v0.2.3-0.20260902140659-6bc3068cd48a github.com/GrayCodeAI/harrier v0.0.0-20260902154449-d52fa214feb7 github.com/GrayCodeAI/kestrel v0.0.0-20260902154440-1b4c8cf7ea62 @@ -53,7 +52,6 @@ require ( // by the current engine module commits. Remove these excludes after the next // ordered Eagle -> Falcon -> engine release. exclude ( - github.com/GrayCodeAI/eagle v0.1.13 github.com/GrayCodeAI/falcon v0.1.4 github.com/GrayCodeAI/falcon v0.1.5 github.com/GrayCodeAI/falcon v0.1.6-0.20260825010843-82c1c610efe3 diff --git a/go.sum b/go.sum index a61353a8..08eb5214 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8 github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/GrayCodeAI/eagle v0.0.0-20260902153929-5877bed17503 h1:n5y1Xpf+xzwpYixeyYg4TsZWnbEp3/dJ7RitphksnBE= -github.com/GrayCodeAI/eagle v0.0.0-20260902153929-5877bed17503/go.mod h1:wjWP4o2xfIAoPx2JdizFYvlsLMjufmKy7anToKjEj3U= github.com/GrayCodeAI/eyrie v0.2.3-0.20260902140659-6bc3068cd48a h1:6TnQbyidnPMBBfHBhIldTWzYQZx+bparWuWm1ft2q+s= github.com/GrayCodeAI/eyrie v0.2.3-0.20260902140659-6bc3068cd48a/go.mod h1:gphUZ6Vcml7zyXfKU+704HZcxtHbJLFqgLDzke/rvFc= github.com/GrayCodeAI/harrier v0.0.0-20260902154449-d52fa214feb7 h1:X5lmXWlBdPVyk7lMAX7M0FiRr9o16Sq7yIjfN3mnXGU= diff --git a/internal/bridge/kestrel/bridge.go b/internal/bridge/kestrel/bridge.go index 3f7bf71a..c34ef95f 100644 --- a/internal/bridge/kestrel/bridge.go +++ b/internal/bridge/kestrel/bridge.go @@ -5,9 +5,9 @@ import ( "sync" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - reviewcontracts "github.com/GrayCodeAI/eagle/review" - eagletypes "github.com/GrayCodeAI/eagle/types" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + reviewcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/review" + typescontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" "github.com/GrayCodeAI/graycode-cli/internal/types" kestrelLib "github.com/GrayCodeAI/kestrel" @@ -133,7 +133,7 @@ func (b *Bridge) ReviewContracts(ctx context.Context, diff string) (*reviewcontr if err != nil { return nil, err } - return toEagleResult(kestrelLib.ToContractResult(result)), nil + return toContractResult(kestrelLib.ToContractResult(result)), nil } // ReviewContractsObserved reviews a diff, journals Kestrel's portable quality @@ -170,9 +170,9 @@ func (b *Bridge) ReviewContractsObserved( observation.ToolCallID, stage, "kestrel", - toEagleNodes(export.Nodes), - toEagleEdges(export.Edges), - toEagleEvents(export.Events), + toContractNodes(export.Nodes), + toContractEdges(export.Edges), + toContractEvents(export.Events), observedAt, ); err != nil { return nil, err @@ -190,7 +190,7 @@ func (b *Bridge) ReviewContractsObserved( ); err != nil { return nil, err } - return toEagleResult(contractResult), nil + return toContractResult(contractResult), nil } // Describe generates a PR description from a unified diff string. @@ -218,86 +218,86 @@ func (b *Bridge) Improve(ctx context.Context, diff string) (*kestrelLib.ImproveR } // The following helpers convert Kestrel's vendored contract types into -// Graycode's eagle/* contract types (and the reverse for scope). The definitions +// Graycode's contracts/* contract types (and the reverse for scope). The definitions // are byte-identical, so conversion is a field-by-field copy at the boundary. func toKestrelScope(s graphcontracts.Scope) kestrelgraph.Scope { return kestrelgraph.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleNodes(nodes []kestrelgraph.Node) []graphcontracts.Node { +func toContractNodes(nodes []kestrelgraph.Node) []graphcontracts.Node { out := make([]graphcontracts.Node, len(nodes)) for i, n := range nodes { - out[i] = toEagleNode(n) + out[i] = toContractNode(n) } return out } -func toEagleNode(n kestrelgraph.Node) graphcontracts.Node { +func toContractNode(n kestrelgraph.Node) graphcontracts.Node { return graphcontracts.Node{ ID: n.ID, Kind: graphcontracts.NodeKind(n.Kind), - Scope: toEagleScope(n.Scope), + Scope: toContractScope(n.Scope), CreatedAt: n.CreatedAt, EffectiveAt: n.EffectiveAt, - Provenance: toEagleProvenance(n.Provenance), + Provenance: toContractProvenance(n.Provenance), Attributes: n.Attributes, } } -func toEagleEdges(edges []kestrelgraph.Edge) []graphcontracts.Edge { +func toContractEdges(edges []kestrelgraph.Edge) []graphcontracts.Edge { out := make([]graphcontracts.Edge, len(edges)) for i, e := range edges { - out[i] = toEagleEdge(e) + out[i] = toContractEdge(e) } return out } -func toEagleEdge(e kestrelgraph.Edge) graphcontracts.Edge { +func toContractEdge(e kestrelgraph.Edge) graphcontracts.Edge { return graphcontracts.Edge{ ID: e.ID, Kind: graphcontracts.EdgeKind(e.Kind), - From: toEagleRef(e.From), - To: toEagleRef(e.To), - Scope: toEagleScope(e.Scope), + From: toContractRef(e.From), + To: toContractRef(e.To), + Scope: toContractScope(e.Scope), CreatedAt: e.CreatedAt, EffectiveAt: e.EffectiveAt, - Provenance: toEagleProvenance(e.Provenance), + Provenance: toContractProvenance(e.Provenance), Attributes: e.Attributes, } } -func toEagleEvents(events []kestrelgraph.Event) []graphcontracts.Event { +func toContractEvents(events []kestrelgraph.Event) []graphcontracts.Event { out := make([]graphcontracts.Event, len(events)) for i, ev := range events { - out[i] = toEagleEvent(ev) + out[i] = toContractEvent(ev) } return out } -func toEagleEvent(ev kestrelgraph.Event) graphcontracts.Event { +func toContractEvent(ev kestrelgraph.Event) graphcontracts.Event { return graphcontracts.Event{ ID: ev.ID, Type: graphcontracts.EventType(ev.Type), - Subject: toEagleRef(ev.Subject), - Scope: toEagleScope(ev.Scope), + Subject: toContractRef(ev.Subject), + Scope: toContractScope(ev.Scope), OccurredAt: ev.OccurredAt, CorrelationID: ev.CorrelationID, CausationID: ev.CausationID, IdempotencyKey: ev.IdempotencyKey, - Provenance: toEagleProvenance(ev.Provenance), + Provenance: toContractProvenance(ev.Provenance), } } -func toEagleRef(r kestrelgraph.Ref) graphcontracts.Ref { +func toContractRef(r kestrelgraph.Ref) graphcontracts.Ref { return graphcontracts.Ref{Kind: graphcontracts.NodeKind(r.Kind), ID: r.ID} } -func toEagleScope(s kestrelgraph.Scope) graphcontracts.Scope { +func toContractScope(s kestrelgraph.Scope) graphcontracts.Scope { return graphcontracts.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleProvenance(p kestrelgraph.Provenance) graphcontracts.Provenance { +func toContractProvenance(p kestrelgraph.Provenance) graphcontracts.Provenance { evidence := make([]graphcontracts.ArtifactRef, len(p.Evidence)) for i, a := range p.Evidence { evidence[i] = graphcontracts.ArtifactRef{URI: a.URI, Digest: a.Digest, MediaType: a.MediaType} @@ -305,28 +305,28 @@ func toEagleProvenance(p kestrelgraph.Provenance) graphcontracts.Provenance { return graphcontracts.Provenance{Producer: p.Producer, Version: p.Version, SourceID: p.SourceID, Evidence: evidence} } -func toEagleResult(r *kestrelreview.Result) *reviewcontracts.Result { +func toContractResult(r *kestrelreview.Result) *reviewcontracts.Result { if r == nil { return nil } return &reviewcontracts.Result{ - Findings: toEagleFindings(r.Findings), - Comments: toEagleComments(r.Comments), - Stats: toEagleStats(r.Stats), + Findings: toContractFindings(r.Findings), + Comments: toContractComments(r.Comments), + Stats: toContractStats(r.Stats), Report: r.Report, - FailOn: eagletypes.Severity(r.FailOn), + FailOn: typescontracts.Severity(r.FailOn), FailOnSet: r.FailOnSet, - SASTFusion: toEagleSASTFusion(r.SASTFusion), - ConfidenceBreakdown: toEagleConfidenceBreakdown(r.ConfidenceBreakdown), + SASTFusion: toContractSASTFusion(r.SASTFusion), + ConfidenceBreakdown: toContractConfidenceBreakdown(r.ConfidenceBreakdown), } } -func toEagleFindings(findings []kestrelreview.Finding) []reviewcontracts.Finding { +func toContractFindings(findings []kestrelreview.Finding) []reviewcontracts.Finding { out := make([]reviewcontracts.Finding, len(findings)) for i, f := range findings { out[i] = reviewcontracts.Finding{ Concern: f.Concern, - Severity: eagletypes.Severity(f.Severity), + Severity: typescontracts.Severity(f.Severity), File: f.File, Line: f.Line, EndLine: f.EndLine, @@ -341,7 +341,7 @@ func toEagleFindings(findings []kestrelreview.Finding) []reviewcontracts.Finding return out } -func toEagleComments(comments []kestrelreview.InlineComment) []reviewcontracts.InlineComment { +func toContractComments(comments []kestrelreview.InlineComment) []reviewcontracts.InlineComment { out := make([]reviewcontracts.InlineComment, len(comments)) for i, c := range comments { out[i] = reviewcontracts.InlineComment{ @@ -355,10 +355,10 @@ func toEagleComments(comments []kestrelreview.InlineComment) []reviewcontracts.I return out } -func toEagleStats(s kestrelreview.Stats) reviewcontracts.Stats { - bySeverity := make(map[eagletypes.Severity]int, len(s.BySeverity)) +func toContractStats(s kestrelreview.Stats) reviewcontracts.Stats { + bySeverity := make(map[typescontracts.Severity]int, len(s.BySeverity)) for sev, count := range s.BySeverity { - bySeverity[eagletypes.Severity(sev)] = count + bySeverity[typescontracts.Severity(sev)] = count } return reviewcontracts.Stats{ FilesReviewed: s.FilesReviewed, @@ -375,24 +375,24 @@ func toEagleStats(s kestrelreview.Stats) reviewcontracts.Stats { } } -func toEagleSASTFusion(f *kestrelreview.SASTFusionResult) *reviewcontracts.SASTFusionResult { +func toContractSASTFusion(f *kestrelreview.SASTFusionResult) *reviewcontracts.SASTFusionResult { if f == nil { return nil } return &reviewcontracts.SASTFusionResult{ - Confirmed: toEagleFindings(f.Confirmed), - Dismissed: toEagleFindings(f.Dismissed), - Unaddressed: toEagleFindings(f.Unaddressed), + Confirmed: toContractFindings(f.Confirmed), + Dismissed: toContractFindings(f.Dismissed), + Unaddressed: toContractFindings(f.Unaddressed), } } -func toEagleConfidenceBreakdown(c *kestrelreview.ConfidenceBreakdown) *reviewcontracts.ConfidenceBreakdown { +func toContractConfidenceBreakdown(c *kestrelreview.ConfidenceBreakdown) *reviewcontracts.ConfidenceBreakdown { if c == nil { return nil } return &reviewcontracts.ConfidenceBreakdown{ - High: toEagleFindings(c.High), - Medium: toEagleFindings(c.Medium), - Low: toEagleFindings(c.Low), + High: toContractFindings(c.High), + Medium: toContractFindings(c.Medium), + Low: toContractFindings(c.Low), } } diff --git a/internal/bridge/kestrel/bridge_test.go b/internal/bridge/kestrel/bridge_test.go index aa21d8c8..ea4e86a4 100644 --- a/internal/bridge/kestrel/bridge_test.go +++ b/internal/bridge/kestrel/bridge_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" ) diff --git a/internal/bridge/merlin/bridge.go b/internal/bridge/merlin/bridge.go index 1f14c7d3..f9a2864f 100644 --- a/internal/bridge/merlin/bridge.go +++ b/internal/bridge/merlin/bridge.go @@ -5,9 +5,9 @@ import ( "sync" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - eagletypes "github.com/GrayCodeAI/eagle/types" - verifycontracts "github.com/GrayCodeAI/eagle/verify" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + typescontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" + verifycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/verify" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" merlinLib "github.com/GrayCodeAI/merlin" merlingraph "github.com/GrayCodeAI/merlin/graph" @@ -77,7 +77,7 @@ func (b *Bridge) RunContracts(ctx context.Context, target string, opts ...merlin if err != nil { return nil, err } - return toEagleReport(merlinLib.ToContractReport(report)), nil + return toContractReport(merlinLib.ToContractReport(report)), nil } // RunContractsObserved performs a scan, journals Merlin's portable quality @@ -114,9 +114,9 @@ func (b *Bridge) RunContractsObserved( observation.ToolCallID, stage, "merlin", - toEagleNodes(export.Nodes), - toEagleEdges(export.Edges), - toEagleEvents(export.Events), + toContractNodes(export.Nodes), + toContractEdges(export.Edges), + toContractEvents(export.Events), observedAt, ); err != nil { return nil, err @@ -134,90 +134,90 @@ func (b *Bridge) RunContractsObserved( ); err != nil { return nil, err } - return toEagleReport(contractReport), nil + return toContractReport(contractReport), nil } // The following helpers convert Merlin's vendored contract types into Graycode's -// eagle/* contract types (and the reverse for scope). The definitions are +// contracts/* contract types (and the reverse for scope). The definitions are // byte-identical, so conversion is a field-by-field copy at the boundary. func toMerlinScope(s graphcontracts.Scope) merlingraph.Scope { return merlingraph.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleNodes(nodes []merlingraph.Node) []graphcontracts.Node { +func toContractNodes(nodes []merlingraph.Node) []graphcontracts.Node { out := make([]graphcontracts.Node, len(nodes)) for i, n := range nodes { - out[i] = toEagleNode(n) + out[i] = toContractNode(n) } return out } -func toEagleNode(n merlingraph.Node) graphcontracts.Node { +func toContractNode(n merlingraph.Node) graphcontracts.Node { return graphcontracts.Node{ ID: n.ID, Kind: graphcontracts.NodeKind(n.Kind), - Scope: toEagleScope(n.Scope), + Scope: toContractScope(n.Scope), CreatedAt: n.CreatedAt, EffectiveAt: n.EffectiveAt, - Provenance: toEagleProvenance(n.Provenance), + Provenance: toContractProvenance(n.Provenance), Attributes: n.Attributes, } } -func toEagleEdges(edges []merlingraph.Edge) []graphcontracts.Edge { +func toContractEdges(edges []merlingraph.Edge) []graphcontracts.Edge { out := make([]graphcontracts.Edge, len(edges)) for i, e := range edges { - out[i] = toEagleEdge(e) + out[i] = toContractEdge(e) } return out } -func toEagleEdge(e merlingraph.Edge) graphcontracts.Edge { +func toContractEdge(e merlingraph.Edge) graphcontracts.Edge { return graphcontracts.Edge{ ID: e.ID, Kind: graphcontracts.EdgeKind(e.Kind), - From: toEagleRef(e.From), - To: toEagleRef(e.To), - Scope: toEagleScope(e.Scope), + From: toContractRef(e.From), + To: toContractRef(e.To), + Scope: toContractScope(e.Scope), CreatedAt: e.CreatedAt, EffectiveAt: e.EffectiveAt, - Provenance: toEagleProvenance(e.Provenance), + Provenance: toContractProvenance(e.Provenance), Attributes: e.Attributes, } } -func toEagleEvents(events []merlingraph.Event) []graphcontracts.Event { +func toContractEvents(events []merlingraph.Event) []graphcontracts.Event { out := make([]graphcontracts.Event, len(events)) for i, ev := range events { - out[i] = toEagleEvent(ev) + out[i] = toContractEvent(ev) } return out } -func toEagleEvent(ev merlingraph.Event) graphcontracts.Event { +func toContractEvent(ev merlingraph.Event) graphcontracts.Event { return graphcontracts.Event{ ID: ev.ID, Type: graphcontracts.EventType(ev.Type), - Subject: toEagleRef(ev.Subject), - Scope: toEagleScope(ev.Scope), + Subject: toContractRef(ev.Subject), + Scope: toContractScope(ev.Scope), OccurredAt: ev.OccurredAt, CorrelationID: ev.CorrelationID, CausationID: ev.CausationID, IdempotencyKey: ev.IdempotencyKey, - Provenance: toEagleProvenance(ev.Provenance), + Provenance: toContractProvenance(ev.Provenance), } } -func toEagleRef(r merlingraph.Ref) graphcontracts.Ref { +func toContractRef(r merlingraph.Ref) graphcontracts.Ref { return graphcontracts.Ref{Kind: graphcontracts.NodeKind(r.Kind), ID: r.ID} } -func toEagleScope(s merlingraph.Scope) graphcontracts.Scope { +func toContractScope(s merlingraph.Scope) graphcontracts.Scope { return graphcontracts.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleProvenance(p merlingraph.Provenance) graphcontracts.Provenance { +func toContractProvenance(p merlingraph.Provenance) graphcontracts.Provenance { evidence := make([]graphcontracts.ArtifactRef, len(p.Evidence)) for i, a := range p.Evidence { evidence[i] = graphcontracts.ArtifactRef{URI: a.URI, Digest: a.Digest, MediaType: a.MediaType} @@ -225,7 +225,7 @@ func toEagleProvenance(p merlingraph.Provenance) graphcontracts.Provenance { return graphcontracts.Provenance{Producer: p.Producer, Version: p.Version, SourceID: p.SourceID, Evidence: evidence} } -func toEagleReport(r *merlinverify.Report) *verifycontracts.Report { +func toContractReport(r *merlinverify.Report) *verifycontracts.Report { if r == nil { return nil } @@ -233,7 +233,7 @@ func toEagleReport(r *merlinverify.Report) *verifycontracts.Report { for i, f := range r.Findings { findings[i] = verifycontracts.Finding{ Check: f.Check, - Severity: eagletypes.Severity(f.Severity), + Severity: typescontracts.Severity(f.Severity), URL: f.URL, Element: f.Element, Message: f.Message, @@ -241,9 +241,9 @@ func toEagleReport(r *merlinverify.Report) *verifycontracts.Report { Evidence: f.Evidence, } } - bySeverity := make(map[eagletypes.Severity]int, len(r.Stats.BySeverity)) + bySeverity := make(map[typescontracts.Severity]int, len(r.Stats.BySeverity)) for sev, count := range r.Stats.BySeverity { - bySeverity[eagletypes.Severity(sev)] = count + bySeverity[typescontracts.Severity(sev)] = count } return &verifycontracts.Report{ Target: r.Target, @@ -257,7 +257,7 @@ func toEagleReport(r *merlinverify.Report) *verifycontracts.Report { }, CrawledURLs: r.CrawledURLs, Duration: r.Duration, - FailOn: eagletypes.Severity(r.FailOn), + FailOn: typescontracts.Severity(r.FailOn), FailOnSet: r.FailOnSet, } } diff --git a/internal/bridge/merlin/bridge_test.go b/internal/bridge/merlin/bridge_test.go index f734d69c..de9c9b90 100644 --- a/internal/bridge/merlin/bridge_test.go +++ b/internal/bridge/merlin/bridge_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" ) diff --git a/internal/contracts/agent/hooks.go b/internal/contracts/agent/hooks.go new file mode 100644 index 00000000..ff23bdc4 --- /dev/null +++ b/internal/contracts/agent/hooks.go @@ -0,0 +1,70 @@ +// Vendored from github.com/GrayCodeAI/eagle/agent at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package agent + +import "strings" + +// Hook event names for lifecycle and tool gates. +// +// Graycode may accept vendor aliases (Claude/Cursor); normalize to these +// canonical names at the boundary. Constants are shared so graycode, plugins, +// and SDKs agree on wire/event vocabulary without importing engines. +const ( + HookPreToolUse = "PreToolUse" + HookPostToolUse = "PostToolUse" + HookUserPromptSubmit = "UserPromptSubmit" + HookSessionStart = "SessionStart" + HookSessionEnd = "SessionEnd" + HookStop = "Stop" + HookSubagentStart = "SubagentStart" + HookSubagentStop = "SubagentStop" + HookNotification = "Notification" + HookPermissionRequest = "PermissionRequest" + HookPreCompact = "PreCompact" + HookFailure = "Failure" +) + +// VendorHookAliases maps common third-party hook names to canonical Graycode names. +// Keys are lower-case for case-insensitive lookup. +var VendorHookAliases = map[string]string{ + "pretooluse": HookPreToolUse, + "pre_tool_use": HookPreToolUse, + "posttooluse": HookPostToolUse, + "post_tool_use": HookPostToolUse, + "userpromptsubmit": HookUserPromptSubmit, + "user_prompt_submit": HookUserPromptSubmit, + "sessionstart": HookSessionStart, + "session_start": HookSessionStart, + "sessionend": HookSessionEnd, + "session_end": HookSessionEnd, + "stop": HookStop, + "subagentstart": HookSubagentStart, + "subagent_start": HookSubagentStart, + "subagentstop": HookSubagentStop, + "subagent_stop": HookSubagentStop, + "notification": HookNotification, + "permissionrequest": HookPermissionRequest, + "permission_request": HookPermissionRequest, + "precompact": HookPreCompact, + "pre_compact": HookPreCompact, + "failure": HookFailure, + "onerror": HookFailure, + "on_error": HookFailure, +} + +// CanonicalHookEvent returns the Graycode canonical event name for s, or "" if unknown. +func CanonicalHookEvent(s string) string { + if s == "" { + return "" + } + switch s { + case HookPreToolUse, HookPostToolUse, HookUserPromptSubmit, HookSessionStart, + HookSessionEnd, HookStop, HookSubagentStart, HookSubagentStop, + HookNotification, HookPermissionRequest, HookPreCompact, HookFailure: + return s + } + if c, ok := VendorHookAliases[strings.ToLower(strings.TrimSpace(s))]; ok { + return c + } + return "" +} diff --git a/internal/contracts/agent/spawn.go b/internal/contracts/agent/spawn.go new file mode 100644 index 00000000..47ba0d88 --- /dev/null +++ b/internal/contracts/agent/spawn.go @@ -0,0 +1,240 @@ +// Vendored from github.com/GrayCodeAI/eagle/agent at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +// Package agent defines shared DTOs for typed subagent spawn across graycode-eco. +// +// Stdlib only. No engine, CLI, or storage imports. +package agent + +import ( + "fmt" + "strings" +) + +// CapabilityMode limits what tools a subagent may use. +type CapabilityMode string + +const ( + CapReadOnly CapabilityMode = "read-only" + CapReadWrite CapabilityMode = "read-write" + CapExecute CapabilityMode = "execute" + CapAll CapabilityMode = "all" +) + +// IsolationMode selects filesystem isolation for a subagent. +type IsolationMode string + +const ( + IsoNone IsolationMode = "none" + IsoWorktree IsolationMode = "worktree" +) + +// SubagentType selects the built-in subagent profile. +type SubagentType string + +const ( + TypeGeneralPurpose SubagentType = "general-purpose" + TypeExplore SubagentType = "explore" + TypePlan SubagentType = "plan" +) + +// Thoroughness levels for explore subagents. +const ( + ThoroughnessQuick = "quick" + ThoroughnessMedium = "medium" + ThoroughnessVeryThorough = "very-thorough" +) + +// Spawn status values for SpawnResult.Status. +const ( + StatusRunning = "running" + StatusCompleted = "completed" + StatusFailed = "failed" +) + +// SpawnRequest is the cross-repo contract for spawning a subagent. +type SpawnRequest struct { + Prompt string `json:"prompt"` + Description string `json:"description,omitempty"` + SubagentType string `json:"subagent_type,omitempty"` + CapabilityMode string `json:"capability_mode,omitempty"` + Isolation string `json:"isolation,omitempty"` + ResumeFrom string `json:"resume_from,omitempty"` + CWD string `json:"cwd,omitempty"` + Model string `json:"model,omitempty"` + Background bool `json:"background,omitempty"` + Thoroughness string `json:"thoroughness,omitempty"` + ParentSession string `json:"parent_session,omitempty"` +} + +// SpawnResult is the cross-repo contract returned after spawn completes or is accepted. +type SpawnResult struct { + SubagentID string `json:"subagent_id,omitempty"` + SubagentType string `json:"subagent_type,omitempty"` + Status string `json:"status,omitempty"` + Output string `json:"output,omitempty"` + Summary string `json:"summary,omitempty"` + ToolCalls int `json:"tool_calls,omitempty"` + Turns int `json:"turns,omitempty"` + DurationMs int64 `json:"duration_ms,omitempty"` + WorktreePath string `json:"worktree_path,omitempty"` + Persona string `json:"persona,omitempty"` + Error string `json:"error,omitempty"` +} + +// ParseSubagentType normalizes aliases (e.g. "general" → general-purpose). +// Empty input defaults to explore (conservative read-oriented default). +func ParseSubagentType(s string) (SubagentType, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "", "explore": + return TypeExplore, nil + case "plan": + return TypePlan, nil + case "general", "general-purpose", "general_purpose", "generalpurpose": + return TypeGeneralPurpose, nil + default: + return "", fmt.Errorf("agent: unknown subagent_type %q", s) + } +} + +// ParseCapabilityMode normalizes capability aliases. +// Empty input returns CapReadOnly when defaultFromType is empty; callers +// should prefer DefaultCapabilityForType. +func ParseCapabilityMode(s string) (CapabilityMode, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "", "read-only", "readonly", "read_only", "ro": + return CapReadOnly, nil + case "read-write", "readwrite", "read_write", "rw": + return CapReadWrite, nil + case "execute", "exec": + return CapExecute, nil + case "all", "full": + return CapAll, nil + default: + return "", fmt.Errorf("agent: unknown capability_mode %q", s) + } +} + +// ParseIsolationMode normalizes isolation aliases. Empty → none. +func ParseIsolationMode(s string) (IsolationMode, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "", "none", "off", "false": + return IsoNone, nil + case "worktree", "wt", "git-worktree": + return IsoWorktree, nil + default: + return "", fmt.Errorf("agent: unknown isolation %q", s) + } +} + +// ParseThoroughness normalizes explore thoroughness. Empty → medium. +func ParseThoroughness(s string) (string, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "", "medium", "med", "default": + return ThoroughnessMedium, nil + case "quick", "fast": + return ThoroughnessQuick, nil + case "very-thorough", "very_thorough", "verythorough", "deep": + return ThoroughnessVeryThorough, nil + default: + return "", fmt.Errorf("agent: unknown thoroughness %q", s) + } +} + +// DefaultCapabilityForType returns the capability implied by a subagent type +// when the request does not set capability_mode. +func DefaultCapabilityForType(t SubagentType) CapabilityMode { + switch t { + case TypeGeneralPurpose: + return CapAll + case TypePlan, TypeExplore: + return CapReadOnly + default: + return CapReadOnly + } +} + +// Normalized is a validated, alias-resolved spawn request. +type Normalized struct { + Prompt string + Description string + SubagentType SubagentType + CapabilityMode CapabilityMode + Isolation IsolationMode + ResumeFrom string + CWD string + Model string + Background bool + Thoroughness string + ParentSession string +} + +// Normalize validates and resolves aliases on r. +// +// Rules: +// - prompt is required unless resume_from is set +// - cwd and isolation=worktree are mutually exclusive +// - thoroughness only applies to explore (ignored otherwise after parse) +// - empty capability_mode uses DefaultCapabilityForType +func (r SpawnRequest) Normalize() (Normalized, error) { + prompt := strings.TrimSpace(r.Prompt) + resume := strings.TrimSpace(r.ResumeFrom) + if prompt == "" && resume == "" { + return Normalized{}, fmt.Errorf("agent: prompt is required unless resume_from is set") + } + + st, err := ParseSubagentType(r.SubagentType) + if err != nil { + return Normalized{}, err + } + + var capMode CapabilityMode + if strings.TrimSpace(r.CapabilityMode) == "" { + capMode = DefaultCapabilityForType(st) + } else { + capMode, err = ParseCapabilityMode(r.CapabilityMode) + if err != nil { + return Normalized{}, err + } + } + + iso, err := ParseIsolationMode(r.Isolation) + if err != nil { + return Normalized{}, err + } + + cwd := strings.TrimSpace(r.CWD) + if cwd != "" && iso == IsoWorktree { + return Normalized{}, fmt.Errorf("agent: cwd and isolation=worktree are mutually exclusive") + } + + thorough := ThoroughnessMedium + if st == TypeExplore { + thorough, err = ParseThoroughness(r.Thoroughness) + if err != nil { + return Normalized{}, err + } + } else if strings.TrimSpace(r.Thoroughness) != "" { + // Explicit thoroughness on non-explore is an error to catch model mistakes. + return Normalized{}, fmt.Errorf("agent: thoroughness is only valid for explore subagents") + } + + return Normalized{ + Prompt: prompt, + Description: strings.TrimSpace(r.Description), + SubagentType: st, + CapabilityMode: capMode, + Isolation: iso, + ResumeFrom: resume, + CWD: cwd, + Model: strings.TrimSpace(r.Model), + Background: r.Background, + Thoroughness: thorough, + ParentSession: strings.TrimSpace(r.ParentSession), + }, nil +} + +// Validate is an alias for Normalize when only the error is needed. +func (r SpawnRequest) Validate() error { + _, err := r.Normalize() + return err +} diff --git a/internal/contracts/events/events.go b/internal/contracts/events/events.go new file mode 100644 index 00000000..bb3599c2 --- /dev/null +++ b/internal/contracts/events/events.go @@ -0,0 +1,36 @@ +// Vendored from github.com/GrayCodeAI/eagle/events at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package events + +import "time" + +// ToolEvent represents a normalized tool event emitted by Graycode workflows. +type ToolEvent struct { + ToolName string `json:"tool_name"` + ToolInput map[string]interface{} `json:"tool_input,omitempty"` + CWD string `json:"cwd,omitempty"` + Timestamp time.Time `json:"timestamp"` + SessionID string `json:"session_id,omitempty"` + Transcript string `json:"transcript,omitempty"` +} + +// TraceEvent represents a normalized trace record for model/runtime activity. +type TraceEvent struct { + ID string `json:"id"` + Name string `json:"name"` + Input string `json:"input,omitempty"` + Output string `json:"output,omitempty"` + Model string `json:"model,omitempty"` + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + Usage *UsageInfo `json:"usage,omitempty"` +} + +// UsageInfo captures token and cost information for a trace event. +type UsageInfo struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` + CostUSD float64 `json:"cost_usd,omitempty"` +} diff --git a/internal/contracts/graph/graph.go b/internal/contracts/graph/graph.go new file mode 100644 index 00000000..826c5eaf --- /dev/null +++ b/internal/contracts/graph/graph.go @@ -0,0 +1,338 @@ +// Vendored from github.com/GrayCodeAI/eagle/graph at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +// Package graph defines the portable graph vocabulary shared across graycode-eco. +// +// The package contains data contracts only. Individual repositories retain +// ownership of their graph storage, projections, and runtime behavior. +// +// Graph Engineering Patterns: +// This package implements the foundational patterns from LangGraph, +// Microsoft AutoGen, and Google ADK for multi-agent orchestration as +// nodes, edges, and shared state. +package graph + +import ( + "fmt" + "strings" + "time" +) + +// NodeType represents the specific type/behavior of a node in the graph +type NodeType string + +const ( + // NodeTypeAgent represents an autonomous agent that can make decisions + NodeTypeAgent NodeType = "agent" + // NodeTypeTool represents a tool/function that agents can invoke + NodeTypeTool NodeType = "tool" + // NodeTypeFunction represents a function node (deterministic operation) + NodeTypeFunction NodeType = "function" + // NodeTypeStart represents the entry point of a graph + NodeTypeStart NodeType = "start" + // NodeTypeEnd represents the termination point of a graph + NodeTypeEnd NodeType = "end" + // NodeTypeRouter represents a conditional routing node + NodeTypeRouter NodeType = "router" + // NodeTypeQuality represents a code quality analysis node + NodeTypeQuality NodeType = "quality" + // NodeTypeExecution represents an execution journal node + NodeTypeExecution NodeType = "execution" + // NodeTypeOperations represents an operations orchestration node + NodeTypeOperations NodeType = "operations" + // NodeTypeSystem is an alias for NodeKindSystem for backward compatibility + NodeTypeSystem NodeType = "system" +) + +// NodeKind classifies a node by the ecosystem view to which it belongs. +type NodeKind string + +const ( + NodeSystem NodeKind = "system" + NodeKnowledge NodeKind = "knowledge" + NodeExecution NodeKind = "execution" + NodePolicy NodeKind = "policy" + NodeQuality NodeKind = "quality" + NodeOperations NodeKind = "operations" +) + +// ParseNodeKind normalizes a graph node kind. +func ParseNodeKind(s string) (NodeKind, error) { + switch NodeKind(strings.ToLower(strings.TrimSpace(s))) { + case NodeSystem, NodeKnowledge, NodeExecution, NodePolicy, NodeQuality, NodeOperations: + return NodeKind(strings.ToLower(strings.TrimSpace(s))), nil + default: + return "", fmt.Errorf("graph: unknown node kind %q", s) + } +} + +// ParseNodeType normalizes a graph node type. +func ParseNodeType(s string) (NodeType, error) { + switch NodeType(strings.ToLower(strings.TrimSpace(s))) { + case NodeTypeAgent, NodeTypeTool, NodeTypeFunction, NodeTypeStart, NodeTypeEnd, NodeTypeRouter, NodeTypeQuality, NodeTypeExecution, NodeTypeOperations: + return NodeType(strings.ToLower(strings.TrimSpace(s))), nil + default: + return "", fmt.Errorf("graph: unknown node type %q", s) + } +} + +// NodeSpec describes a node in an orchestration graph. +type NodeSpec struct { + ID string `json:"id"` + Type NodeType `json:"type"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Config map[string]string `json:"config,omitempty"` +} + +// EdgeKind identifies a portable relationship between two graph nodes. +type EdgeKind string + +const ( + EdgeContains EdgeKind = "contains" + EdgeDependsOn EdgeKind = "depends_on" + EdgeReferences EdgeKind = "references" + EdgeProduced EdgeKind = "produced" + EdgeGovernedBy EdgeKind = "governed_by" + EdgeValidatedBy EdgeKind = "validated_by" +) + +// EdgeCondition represents a conditional edge in orchestration graphs. +// When non-empty, the edge is only followed if the condition evaluates to true. +type EdgeCondition struct { + Expression string `json:"expression,omitempty"` + Variables map[string]string `json:"variables,omitempty"` +} + +// EdgeSpec describes an edge in an orchestration graph. +type EdgeSpec struct { + From string `json:"from"` + To string `json:"to"` + Condition *EdgeCondition `json:"condition,omitempty"` + Weight float64 `json:"weight,omitempty"` +} + +// GraphSpec describes a complete orchestration graph. +type GraphSpec struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Nodes []NodeSpec `json:"nodes"` + Edges []EdgeSpec `json:"edges"` + Metadata map[string]string `json:"metadata,omitempty"` +} + +// ParseEdgeKind normalizes a graph edge kind. +func ParseEdgeKind(s string) (EdgeKind, error) { + switch EdgeKind(strings.ToLower(strings.TrimSpace(s))) { + case EdgeContains, EdgeDependsOn, EdgeReferences, EdgeProduced, EdgeGovernedBy, EdgeValidatedBy: + return EdgeKind(strings.ToLower(strings.TrimSpace(s))), nil + default: + return "", fmt.Errorf("graph: unknown edge kind %q", s) + } +} + +// EventType identifies a lifecycle event for a graph subject. +type EventType string + +const ( + EventCreated EventType = "created" + EventUpdated EventType = "updated" + EventTransitioned EventType = "transitioned" + EventObserved EventType = "observed" + EventDeleted EventType = "deleted" +) + +// ParseEventType normalizes a graph event type. +func ParseEventType(s string) (EventType, error) { + switch EventType(strings.ToLower(strings.TrimSpace(s))) { + case EventCreated, EventUpdated, EventTransitioned, EventObserved, EventDeleted: + return EventType(strings.ToLower(strings.TrimSpace(s))), nil + default: + return "", fmt.Errorf("graph: unknown event type %q", s) + } +} + +// Scope limits a graph fact to its authorized ecosystem boundary. Empty fields +// are valid for local-only or global facts. +type Scope struct { + TenantID string `json:"tenant_id,omitempty"` + ProjectID string `json:"project_id,omitempty"` + RepositoryID string `json:"repository_id,omitempty"` +} + +// Ref identifies a graph node without embedding its mutable attributes. +type Ref struct { + Kind NodeKind `json:"kind"` + ID string `json:"id"` +} + +// Validate reports whether r can safely identify a graph node. +func (r Ref) Validate() error { + if _, err := ParseNodeKind(string(r.Kind)); err != nil { + return err + } + if strings.TrimSpace(r.ID) == "" { + return fmt.Errorf("graph: reference ID is required") + } + return nil +} + +// ArtifactRef points to immutable evidence outside the contract payload. +type ArtifactRef struct { + URI string `json:"uri"` + Digest string `json:"digest,omitempty"` + MediaType string `json:"media_type,omitempty"` +} + +// Provenance identifies the producer and evidence for a graph fact. +type Provenance struct { + Producer string `json:"producer"` + Version string `json:"version,omitempty"` + SourceID string `json:"source_id,omitempty"` + Evidence []ArtifactRef `json:"evidence,omitempty"` +} + +// Validate reports whether p can support an auditable graph fact. +func (p Provenance) Validate() error { + if strings.TrimSpace(p.Producer) == "" { + return fmt.Errorf("graph: provenance producer is required") + } + for i, evidence := range p.Evidence { + if strings.TrimSpace(evidence.URI) == "" { + return fmt.Errorf("graph: evidence[%d] URI is required", i) + } + } + return nil +} + +// Node is a typed, temporal graph fact. Attributes are intentionally bounded +// to strings; large or sensitive data belongs in ArtifactRef evidence. +type Node struct { + ID string `json:"id"` + Kind NodeKind `json:"kind"` + Scope Scope `json:"scope,omitempty"` + CreatedAt time.Time `json:"created_at"` + EffectiveAt time.Time `json:"effective_at,omitempty"` + Provenance Provenance `json:"provenance"` + Attributes map[string]string `json:"attributes,omitempty"` +} + +// Validate reports whether n satisfies the minimum shared graph contract. +func (n Node) Validate() error { + if strings.TrimSpace(n.ID) == "" { + return fmt.Errorf("graph: node ID is required") + } + if _, err := ParseNodeKind(string(n.Kind)); err != nil { + return err + } + if n.CreatedAt.IsZero() { + return fmt.Errorf("graph: node created_at is required") + } + return n.Provenance.Validate() +} + +// Validate reports whether s satisfies the minimum node spec contract. +func (s NodeSpec) Validate() error { + if strings.TrimSpace(s.ID) == "" { + return fmt.Errorf("graph: node spec ID is required") + } + if _, err := ParseNodeType(string(s.Type)); err != nil { + return err + } + return nil +} + +// Validate reports whether s satisfies the minimum edge spec contract. +func (s EdgeSpec) Validate() error { + if strings.TrimSpace(s.From) == "" { + return fmt.Errorf("graph: edge spec from is required") + } + if strings.TrimSpace(s.To) == "" { + return fmt.Errorf("graph: edge spec to is required") + } + return nil +} + +// Validate reports whether s satisfies the minimum graph spec contract. +func (s GraphSpec) Validate() error { + if strings.TrimSpace(s.ID) == "" { + return fmt.Errorf("graph: graph spec ID is required") + } + if len(s.Nodes) == 0 { + return fmt.Errorf("graph: graph spec must have at least one node") + } + for i, node := range s.Nodes { + if err := node.Validate(); err != nil { + return fmt.Errorf("graph: node[%d]: %w", i, err) + } + } + for i, edge := range s.Edges { + if err := edge.Validate(); err != nil { + return fmt.Errorf("graph: edge[%d]: %w", i, err) + } + } + return nil +} + +// Edge is a typed, temporal relationship between two graph nodes. +type Edge struct { + ID string `json:"id"` + Kind EdgeKind `json:"kind"` + From Ref `json:"from"` + To Ref `json:"to"` + Scope Scope `json:"scope,omitempty"` + CreatedAt time.Time `json:"created_at"` + EffectiveAt time.Time `json:"effective_at,omitempty"` + Provenance Provenance `json:"provenance"` + Attributes map[string]string `json:"attributes,omitempty"` +} + +// Validate reports whether e satisfies the minimum shared graph contract. +func (e Edge) Validate() error { + if strings.TrimSpace(e.ID) == "" { + return fmt.Errorf("graph: edge ID is required") + } + if _, err := ParseEdgeKind(string(e.Kind)); err != nil { + return err + } + if err := e.From.Validate(); err != nil { + return fmt.Errorf("graph: edge from: %w", err) + } + if err := e.To.Validate(); err != nil { + return fmt.Errorf("graph: edge to: %w", err) + } + if e.CreatedAt.IsZero() { + return fmt.Errorf("graph: edge created_at is required") + } + return e.Provenance.Validate() +} + +// Event records an immutable lifecycle observation for a graph subject. +type Event struct { + ID string `json:"id"` + Type EventType `json:"type"` + Subject Ref `json:"subject"` + Scope Scope `json:"scope,omitempty"` + OccurredAt time.Time `json:"occurred_at"` + CorrelationID string `json:"correlation_id,omitempty"` + CausationID string `json:"causation_id,omitempty"` + IdempotencyKey string `json:"idempotency_key,omitempty"` + Provenance Provenance `json:"provenance"` +} + +// Validate reports whether e satisfies the minimum shared graph event contract. +func (e Event) Validate() error { + if strings.TrimSpace(e.ID) == "" { + return fmt.Errorf("graph: event ID is required") + } + if _, err := ParseEventType(string(e.Type)); err != nil { + return err + } + if err := e.Subject.Validate(); err != nil { + return fmt.Errorf("graph: event subject: %w", err) + } + if e.OccurredAt.IsZero() { + return fmt.Errorf("graph: event occurred_at is required") + } + return e.Provenance.Validate() +} diff --git a/internal/contracts/harness/harness.go b/internal/contracts/harness/harness.go new file mode 100644 index 00000000..080d8617 --- /dev/null +++ b/internal/contracts/harness/harness.go @@ -0,0 +1,97 @@ +// Vendored from github.com/GrayCodeAI/eagle/harness at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package harness + +import ( + "time" + + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" +) + +// Dimension represents one of the 5 core Agent Work Loop dimensions. +type Dimension string + +const ( + DimensionFeedforward Dimension = "Feedforward Guidance" + DimensionFeedback Dimension = "Feedback Sensors" + DimensionTaskUnderstanding Dimension = "Task Understanding" + DimensionStepPlanning Dimension = "Step Planning & Execution" + DimensionVerification Dimension = "Verification & Safeguards" +) + +// EvidenceState describes whether an evidence mechanism is Present, Partial, Missing, or Unobserved. +type EvidenceState string + +const ( + EvidenceStatePresent EvidenceState = "Present" + EvidenceStatePartial EvidenceState = "Partial" + EvidenceStateMissing EvidenceState = "Missing" + EvidenceStateUnobserved EvidenceState = "Unobserved" +) + +// Finding represents a single actionable evaluation discovery in the neutral harness contract. +type Finding struct { + ID string `json:"id"` + Dimension Dimension `json:"dimension"` + Severity contracts.Severity `json:"severity"` + Title string `json:"title"` + Description string `json:"description"` + Impact string `json:"impact"` + EvidenceSource string `json:"evidence_source"` + EvidenceState EvidenceState `json:"evidence_state"` + ExpectedOutcome string `json:"expected_outcome"` + ScopedRepair string `json:"scoped_repair"` + ValidationRoute string `json:"validation_route"` +} + +// DimensionScore holds aggregated scoring for a single Agent Work Loop dimension. +type DimensionScore struct { + Dimension Dimension `json:"dimension"` + Score int `json:"score"` // 0 to 100 + State EvidenceState `json:"state"` + Summary string `json:"summary"` + FindingsCount int `json:"findings_count"` +} + +// AssetsDetected lists the project harness assets detected during evaluation. +type AssetsDetected struct { + AgentsMD bool `json:"agents_md"` + AgentsMDPath string `json:"agents_md_path,omitempty"` + ZeroMD bool `json:"zero_md"` + ZeroMDPath string `json:"zero_md_path,omitempty"` + Skills []string `json:"skills"` + SpecsCount int `json:"specs_count"` + Linters []string `json:"linters"` + TestRunners []string `json:"test_runners"` + Hooks []string `json:"hooks"` + SandboxPolicy string `json:"sandbox_policy"` + AutonomyTier string `json:"autonomy_tier"` + MerlinBridge bool `json:"merlin_bridge"` + KestrelBridge bool `json:"kestrel_bridge"` +} + +// Report is the neutral cross-repo contract for Graycode Agent Harness evaluations. +type Report struct { + TargetPath string `json:"target_path"` + GeneratedAt time.Time `json:"generated_at"` + OverallScore int `json:"overall_score"` // 0 to 100 + OverallStatus string `json:"overall_status"` + Dimensions map[Dimension]DimensionScore `json:"dimensions"` + Findings []Finding `json:"findings"` + Assets AssetsDetected `json:"assets"` + Summary string `json:"summary"` +} + +// MaxSeverity returns the highest severity finding in the harness report. +func (r *Report) MaxSeverity() contracts.Severity { + if r == nil { + return contracts.SeverityInfo + } + max := contracts.SeverityInfo + for _, f := range r.Findings { + if f.Severity > max { + max = f.Severity + } + } + return max +} diff --git a/internal/contracts/policy/policy.go b/internal/contracts/policy/policy.go new file mode 100644 index 00000000..ebae6b4f --- /dev/null +++ b/internal/contracts/policy/policy.go @@ -0,0 +1,129 @@ +// Vendored from github.com/GrayCodeAI/eagle/policy at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package policy + +import ( + "fmt" + "strings" +) + +// Risk is the severity of a permission or policy verdict. +type Risk int + +const ( + RiskLow Risk = iota + RiskMedium + RiskHigh + RiskBlocked +) + +// String returns a human-readable risk name. +func (r Risk) String() string { + switch r { + case RiskLow: + return "low" + case RiskMedium: + return "medium" + case RiskHigh: + return "high" + case RiskBlocked: + return "blocked" + default: + return fmt.Sprintf("Risk(%d)", int(r)) + } +} + +// ParseRisk parses a risk name (case-insensitive) into a Risk value. +func ParseRisk(s string) (Risk, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "low": + return RiskLow, nil + case "medium", "med", "moderate": + return RiskMedium, nil + case "high", "hi": + return RiskHigh, nil + case "blocked", "block", "deny", "denied", "forbidden": + return RiskBlocked, nil + default: + return RiskMedium, fmt.Errorf("policy: unknown risk %q", s) + } +} + +// PermissionVerdict is the unified outcome type for permission subsystems. +type PermissionVerdict struct { + Allowed bool `json:"allowed"` + Reason string `json:"reason,omitempty"` + Rule string `json:"rule,omitempty"` + Risk Risk `json:"risk"` + Confidence float64 `json:"confidence,omitempty"` + Source string `json:"source,omitempty"` +} + +// Allow returns a permissive verdict. +func Allow(reason string) PermissionVerdict { + return PermissionVerdict{ + Allowed: true, + Reason: reason, + Risk: RiskLow, + Confidence: 1.0, + Source: "default", + } +} + +// Deny returns a reject verdict with the given reason and rule. +func Deny(reason, rule string) PermissionVerdict { + return PermissionVerdict{ + Allowed: false, + Reason: reason, + Rule: rule, + Risk: RiskBlocked, + Confidence: 1.0, + Source: "rules", + } +} + +// RequireApproval returns a "needs human approval" verdict. +func RequireApproval(reason, rule string, risk Risk) PermissionVerdict { + return PermissionVerdict{ + Allowed: false, + Reason: reason, + Rule: rule, + Risk: risk, + Confidence: 0.5, + Source: "guardian", + } +} + +// IsZero reports whether v is the zero value. +func (v PermissionVerdict) IsZero() bool { + return !v.Allowed && v.Reason == "" && v.Rule == "" && + v.Risk == 0 && v.Confidence == 0 && v.Source == "" +} + +// String returns a one-line summary for logs. +func (v PermissionVerdict) String() string { + action := "DENY" + if v.Allowed { + action = "ALLOW" + } + if v.Rule != "" { + return fmt.Sprintf("[%s] %s (%s, risk=%s, conf=%.2f): %s", + v.Source, action, v.Rule, v.Risk, v.Confidence, v.Reason) + } + return fmt.Sprintf("[%s] %s (risk=%s, conf=%.2f): %s", + v.Source, action, v.Risk, v.Confidence, v.Reason) +} + +// GuardianDecision is a provider-neutral automatic permission review response. +type GuardianDecision struct { + Allowed bool `json:"allowed"` + Reason string `json:"reason"` + Confidence float64 `json:"confidence"` +} + +// PermissionRequest represents a user-facing approval request. +type PermissionRequest struct { + ToolName string `json:"tool_name"` + ToolID string `json:"tool_id,omitempty"` + Summary string `json:"summary,omitempty"` +} diff --git a/internal/contracts/review/review.go b/internal/contracts/review/review.go new file mode 100644 index 00000000..c860fcdd --- /dev/null +++ b/internal/contracts/review/review.go @@ -0,0 +1,144 @@ +// Vendored from github.com/GrayCodeAI/eagle/review at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package review + +import ( + "fmt" + "strings" + "time" + + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" +) + +// Finding is the neutral review finding contract shared across Graycode and review engines. +type Finding struct { + Concern string `json:"concern"` + Severity contracts.Severity `json:"severity"` + File string `json:"file"` + Line int `json:"line"` + EndLine int `json:"end_line,omitempty"` + Message string `json:"message"` + Fix string `json:"fix,omitempty"` + Reasoning string `json:"reasoning,omitempty"` + CWE string `json:"cwe,omitempty"` + Confidence float64 `json:"confidence"` + SASTSource bool `json:"sast_source,omitempty"` +} + +// Validate reports whether the finding satisfies the minimum contract +// invariants: a non-blank Message, a non-negative Line, and a Confidence +// within [0, 1]. It returns a descriptive error naming the first violated +// field. +func (f Finding) Validate() error { + if strings.TrimSpace(f.Message) == "" { + return fmt.Errorf("finding message is empty") + } + if f.Line < 0 { + return fmt.Errorf("finding line %d is negative", f.Line) + } + if f.Confidence < 0 || f.Confidence > 1 { + return fmt.Errorf("finding confidence %v is outside [0, 1]", f.Confidence) + } + return nil +} + +// InlineComment is a review finding mapped to a concrete diff position. +type InlineComment struct { + Path string `json:"path"` + StartLine int `json:"start_line"` + EndLine int `json:"end_line,omitempty"` + Body string `json:"body"` + Suggestion string `json:"suggestion,omitempty"` +} + +// Stats captures review execution metrics. +type Stats struct { + FilesReviewed int `json:"files_reviewed"` + HunksAnalyzed int `json:"hunks_analyzed"` + FindingsTotal int `json:"findings_total"` + BySeverity map[contracts.Severity]int `json:"by_severity"` + ByConcern map[string]int `json:"by_concern"` + TokensUsed int `json:"tokens_used"` + DurationPerConcern map[string]time.Duration `json:"duration_per_concern"` + AverageConfidence float64 `json:"average_confidence"` + HighConfidenceCount int `json:"high_confidence_count"` + LowConfidenceCount int `json:"low_confidence_count"` + // LLMErrors records non-fatal provider errors encountered during + // analysis; findings may be partial when it is non-empty. + LLMErrors []string `json:"llm_errors,omitempty"` +} + +// ConfidenceBreakdown groups review findings by confidence band. +type ConfidenceBreakdown struct { + High []Finding `json:"high"` + Medium []Finding `json:"medium"` + Low []Finding `json:"low"` +} + +// SASTFusionResult tracks how the LLM handled SAST findings during a review. +// Only populated when SAST-LLM fusion is active (preAnalysis enabled). +type SASTFusionResult struct { + Confirmed []Finding `json:"confirmed"` + Dismissed []Finding `json:"dismissed"` + Unaddressed []Finding `json:"unaddressed"` +} + +// Result is the neutral review result contract. +type Result struct { + Findings []Finding `json:"findings"` + Comments []InlineComment `json:"comments"` + Stats Stats `json:"stats"` + Report string `json:"report"` + FailOn contracts.Severity `json:"fail_on"` + // FailOnSet reports whether FailOn was explicitly configured via + // SetFailOn. When it is false, Failed() treats SeverityCritical as the + // effective threshold: an unset FailOn must not fail the review on + // informational findings just because SeverityInfo is the zero value. + FailOnSet bool `json:"fail_on_set,omitempty"` + SASTFusion *SASTFusionResult `json:"sast_fusion,omitempty"` + ConfidenceBreakdown *ConfidenceBreakdown `json:"confidence_breakdown,omitempty"` +} + +// SetFailOn sets the fail threshold used by Failed. Set the threshold +// through this method rather than assigning FailOn directly, so that the +// threshold is recorded as explicitly configured. +func (r *Result) SetFailOn(sev contracts.Severity) { + r.FailOn = sev + r.FailOnSet = true +} + +// Failed reports whether any finding meets or exceeds the configured fail threshold. +// When the threshold was never set — a zero Result, or a Result whose FailOn +// field was assigned directly — SeverityCritical is used as the effective +// threshold, matching the kestrel and merlin engine defaults. Set the +// threshold via SetFailOn to make an explicit choice (including Info) take +// effect. +func (r *Result) Failed() bool { + if r == nil { + return false + } + threshold := r.FailOn + if !r.FailOnSet { + threshold = contracts.SeverityCritical + } + for _, f := range r.Findings { + if f.Severity.AtLeast(threshold) { + return true + } + } + return false +} + +// MaxSeverity returns the highest severity present in the result. +func (r *Result) MaxSeverity() contracts.Severity { + if r == nil { + return contracts.SeverityInfo + } + max := contracts.SeverityInfo + for _, f := range r.Findings { + if f.Severity > max { + max = f.Severity + } + } + return max +} diff --git a/internal/contracts/types/finding.go b/internal/contracts/types/finding.go new file mode 100644 index 00000000..5c1d17a4 --- /dev/null +++ b/internal/contracts/types/finding.go @@ -0,0 +1,175 @@ +// Vendored from github.com/GrayCodeAI/eagle/types at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package types + +import ( + "fmt" + "strings" + "time" +) + +// Finding represents a unified analysis concern sourced from Graycode support engines. +type Finding struct { + ID string `json:"id"` + Source string `json:"source"` + Concern string `json:"concern"` + Severity Severity `json:"severity"` + File string `json:"file,omitempty"` + URL string `json:"url,omitempty"` + Line int `json:"line,omitempty"` + EndLine int `json:"end_line,omitempty"` + Message string `json:"message"` + CWE string `json:"cwe,omitempty"` + Confidence float64 `json:"confidence"` + Fix string `json:"fix,omitempty"` + Reasoning string `json:"reasoning,omitempty"` + Tags []string `json:"tags,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +// Validate reports whether the finding satisfies the minimum contract +// invariants: a non-blank Message, a non-negative Line, and a Confidence +// within [0, 1]. It returns a descriptive error naming the first violated +// field. +func (f Finding) Validate() error { + if strings.TrimSpace(f.Message) == "" { + return fmt.Errorf("finding message is empty") + } + if f.Line < 0 { + return fmt.Errorf("finding line %d is negative", f.Line) + } + if f.Confidence < 0 || f.Confidence > 1 { + return fmt.Errorf("finding confidence %v is outside [0, 1]", f.Confidence) + } + return nil +} + +// FindingSlice is sortable by severity descending and confidence descending. +type FindingSlice []Finding + +func (s FindingSlice) Len() int { return len(s) } + +func (s FindingSlice) Less(i, j int) bool { + if s[i].Severity != s[j].Severity { + return s[i].Severity > s[j].Severity + } + return s[i].Confidence > s[j].Confidence +} + +func (s FindingSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// FilterBySource returns findings whose Source matches the given value. +func (s FindingSlice) FilterBySource(source string) FindingSlice { + out := make(FindingSlice, 0, len(s)) + for _, f := range s { + if f.Source == source { + out = append(out, f) + } + } + return out +} + +// FilterBySeverity returns findings whose Severity is at least min. +func (s FindingSlice) FilterBySeverity(min Severity) FindingSlice { + out := make(FindingSlice, 0, len(s)) + for _, f := range s { + if f.Severity.AtLeast(min) { + out = append(out, f) + } + } + return out +} + +// FilterByConfidence returns findings whose Confidence is >= min. +func (s FindingSlice) FilterByConfidence(min float64) FindingSlice { + out := make(FindingSlice, 0, len(s)) + for _, f := range s { + if f.Confidence >= min { + out = append(out, f) + } + } + return out +} + +// ByFile groups findings by their File field. +func (s FindingSlice) ByFile() map[string]FindingSlice { + m := make(map[string]FindingSlice, len(s)) + for _, f := range s { + m[f.File] = append(m[f.File], f) + } + return m +} + +// FindingSummary provides aggregate counts over a set of findings. +type FindingSummary struct { + Total int `json:"total"` + BySource map[string]int `json:"by_source"` + BySeverity map[string]int `json:"by_severity"` + AvgConfidence float64 `json:"avg_confidence"` +} + +// Summary returns a FindingSummary for the slice. +func (s FindingSlice) Summary() FindingSummary { + bySrc := make(map[string]int) + bySev := make(map[string]int) + var confSum float64 + + for _, f := range s { + bySrc[f.Source]++ + bySev[f.Severity.String()]++ + confSum += f.Confidence + } + + avg := 0.0 + if len(s) > 0 { + avg = confSum / float64(len(s)) + } + + return FindingSummary{ + Total: len(s), + BySource: bySrc, + BySeverity: bySev, + AvgConfidence: avg, + } +} + +// FindingFromKestrel constructs a Finding from a kestrel review result. +func FindingFromKestrel( + concern, file string, + line int, + message, cwe string, + sev Severity, + confidence float64, +) Finding { + return Finding{ + ID: fmt.Sprintf("kestrel:%s:%s:%d", concern, file, line), + Source: "kestrel", + Concern: concern, + Severity: sev, + File: file, + Line: line, + Message: message, + CWE: cwe, + Confidence: confidence, + CreatedAt: time.Now(), + } +} + +// FindingFromMerlin constructs a Finding from an merlin analysis result. +func FindingFromMerlin( + concern, url, message string, + sev Severity, + tags []string, +) Finding { + return Finding{ + ID: fmt.Sprintf("merlin:%s:%s", concern, url), + Source: "merlin", + Concern: concern, + Severity: sev, + URL: url, + Message: message, + Tags: tags, + CreatedAt: time.Now(), + } +} diff --git a/internal/contracts/types/severity.go b/internal/contracts/types/severity.go new file mode 100644 index 00000000..5eef2182 --- /dev/null +++ b/internal/contracts/types/severity.go @@ -0,0 +1,85 @@ +// Vendored from github.com/GrayCodeAI/eagle/types at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package types + +import ( + "fmt" + "strings" +) + +// Severity represents the impact level of a finding. +type Severity int + +const ( + SeverityInfo Severity = iota + SeverityLow + SeverityMedium + SeverityHigh + SeverityCritical +) + +var severityNames = [...]string{"info", "low", "medium", "high", "critical"} + +func (s Severity) String() string { + if int(s) >= 0 && int(s) < len(severityNames) { + return severityNames[s] + } + return "unknown" +} + +// ParseSeverity converts a string to a Severity. +// +// Deprecated: ParseSeverity fails open — unknown input (typos such as +// "critcal", empty strings, arbitrary text) silently maps to SeverityInfo, +// so a malformed value is indistinguishable from a legitimate "info". +// Callers handling untrusted input should use ParseSeverityStrict, which +// reports unknown values as errors instead. +func ParseSeverity(s string) Severity { + sev, _ := ParseSeverityStrict(s) + return sev +} + +// ParseSeverityStrict converts a string to a Severity, reporting unknown +// values as errors instead of failing open to SeverityInfo. Matching is +// case-insensitive and ignores surrounding whitespace, exactly like +// ParseSeverity; the two accept the same set of valid names. +func ParseSeverityStrict(s string) (Severity, error) { + switch strings.ToLower(strings.TrimSpace(s)) { + case "critical": + return SeverityCritical, nil + case "high": + return SeverityHigh, nil + case "medium": + return SeverityMedium, nil + case "low": + return SeverityLow, nil + case "info": + return SeverityInfo, nil + default: + return SeverityInfo, fmt.Errorf("unknown severity %q (want one of info, low, medium, high, critical)", s) + } +} + +// AtLeast returns true if s >= threshold. +func (s Severity) AtLeast(threshold Severity) bool { + return s >= threshold +} + +// TokenSeverity defines rule severity for compression error patterns. +type TokenSeverity string + +const ( + TokenSeverityCritical TokenSeverity = "critical" + TokenSeverityHigh TokenSeverity = "high" + TokenSeverityMedium TokenSeverity = "medium" + TokenSeverityLow TokenSeverity = "low" +) + +// AuditSeverity indicates how dangerous a security audit finding is. +type AuditSeverity string + +const ( + AuditSeverityCritical AuditSeverity = "CRITICAL" + AuditSeverityWarning AuditSeverity = "WARNING" + AuditSeverityInfo AuditSeverity = "INFO" +) diff --git a/internal/contracts/verify/verify.go b/internal/contracts/verify/verify.go new file mode 100644 index 00000000..d9269ec4 --- /dev/null +++ b/internal/contracts/verify/verify.go @@ -0,0 +1,86 @@ +// Vendored from github.com/GrayCodeAI/eagle/verify at v0.0.0-20260902153929-5877bed17503 (MIT, Copyright (c) 2026 GrayCode AI). +// The upstream repository no longer exists; this copy is owned by Graycode as its contract surface. +package verify + +import ( + "time" + + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" +) + +// Finding is the neutral verification finding contract shared across Graycode and verification engines. +type Finding struct { + Check string `json:"check"` + Severity contracts.Severity `json:"severity"` + URL string `json:"url"` + Element string `json:"element,omitempty"` + Message string `json:"message"` + Fix string `json:"fix,omitempty"` + Evidence string `json:"evidence,omitempty"` +} + +// Stats captures verification execution metrics. +type Stats struct { + PagesScanned int `json:"pages_scanned"` + FindingsTotal int `json:"findings_total"` + BySeverity map[contracts.Severity]int `json:"by_severity"` + ByCheck map[string]int `json:"by_check"` + DurationPerCheck map[string]time.Duration `json:"duration_per_check"` +} + +// Report is the neutral verification report contract. +type Report struct { + Target string `json:"target"` + Findings []Finding `json:"findings"` + Stats Stats `json:"stats"` + CrawledURLs int `json:"crawled_urls"` + Duration time.Duration `json:"duration"` + FailOn contracts.Severity `json:"fail_on"` + // FailOnSet reports whether FailOn was explicitly configured via + // SetFailOn. When it is false, Failed() treats SeverityCritical as the + // effective threshold: an unset FailOn must not fail the report on + // informational findings just because SeverityInfo is the zero value. + FailOnSet bool `json:"fail_on_set,omitempty"` +} + +// SetFailOn sets the fail threshold used by Failed. Set the threshold +// through this method rather than assigning FailOn directly, so that the +// threshold is recorded as explicitly configured. +func (r *Report) SetFailOn(sev contracts.Severity) { + r.FailOn = sev + r.FailOnSet = true +} + +// Failed reports whether any finding meets or exceeds the configured fail threshold. +// When the threshold was never set — a zero Report, or a Report whose FailOn +// field was assigned directly — SeverityCritical is used as the effective +// threshold, mirroring review.Result. +func (r *Report) Failed() bool { + if r == nil { + return false + } + threshold := r.FailOn + if !r.FailOnSet { + threshold = contracts.SeverityCritical + } + for _, f := range r.Findings { + if f.Severity.AtLeast(threshold) { + return true + } + } + return false +} + +// MaxSeverity returns the highest severity present in the report. +func (r *Report) MaxSeverity() contracts.Severity { + if r == nil { + return contracts.SeverityInfo + } + max := contracts.SeverityInfo + for _, f := range r.Findings { + if f.Severity > max { + max = f.Severity + } + } + return max +} diff --git a/internal/daemon/contract_parity_test.go b/internal/daemon/contract_parity_test.go index 07d7bccb..85bd6b46 100644 --- a/internal/daemon/contract_parity_test.go +++ b/internal/daemon/contract_parity_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/testutil" ) diff --git a/internal/daemon/routes_graph_sync.go b/internal/daemon/routes_graph_sync.go index d83a77e2..f7157918 100644 --- a/internal/daemon/routes_graph_sync.go +++ b/internal/daemon/routes_graph_sync.go @@ -12,7 +12,7 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/executiongraph" ) @@ -179,7 +179,7 @@ func (s *Server) handleGraphSync(w http.ResponseWriter, r *http.Request) { // validateGraphExport checks a portable graph against the shared `*.graph/v1` // contract: schema version, fact-count bounds, per-fact validity (reusing -// eagle/graph), unique identities, and self-contained topology (edges and +// contracts/graph), unique identities, and self-contained topology (edges and // events may only reference nodes present in the same export). func validateGraphExport(export executiongraph.Export) error { if !graphSchemaVersionPattern.MatchString(export.SchemaVersion) { diff --git a/internal/daemon/routes_graph_sync_test.go b/internal/daemon/routes_graph_sync_test.go index a2d62832..8160ed4b 100644 --- a/internal/daemon/routes_graph_sync_test.go +++ b/internal/daemon/routes_graph_sync_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/executiongraph" "github.com/GrayCodeAI/graycode-cli/internal/testutil" ) diff --git a/internal/engine/agent/background_agent.go b/internal/engine/agent/background_agent.go index 915403d1..016eed4f 100644 --- a/internal/engine/agent/background_agent.go +++ b/internal/engine/agent/background_agent.go @@ -6,7 +6,7 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/taskruntime" ) diff --git a/internal/engine/agent_resume_test.go b/internal/engine/agent_resume_test.go index 073ce187..380d6c42 100644 --- a/internal/engine/agent_resume_test.go +++ b/internal/engine/agent_resume_test.go @@ -3,7 +3,7 @@ package engine import ( "testing" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/session" "github.com/GrayCodeAI/graycode-cli/internal/storage" "github.com/GrayCodeAI/graycode-cli/internal/tool" diff --git a/internal/engine/agent_session_tool.go b/internal/engine/agent_session_tool.go index 0dcf9407..85f1f262 100644 --- a/internal/engine/agent_session_tool.go +++ b/internal/engine/agent_session_tool.go @@ -7,7 +7,7 @@ import ( "strings" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" engagent "github.com/GrayCodeAI/graycode-cli/internal/engine/agent" "github.com/GrayCodeAI/graycode-cli/internal/eventlog" diff --git a/internal/engine/background_runner.go b/internal/engine/background_runner.go index 46c3f6f8..135719fd 100644 --- a/internal/engine/background_runner.go +++ b/internal/engine/background_runner.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/taskruntime" "github.com/GrayCodeAI/graycode-cli/internal/tool" diff --git a/internal/engine/delegated_subagent_test.go b/internal/engine/delegated_subagent_test.go index 8c6529d2..5401e3d8 100644 --- a/internal/engine/delegated_subagent_test.go +++ b/internal/engine/delegated_subagent_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/eventlog" "github.com/GrayCodeAI/graycode-cli/internal/sandbox" "github.com/GrayCodeAI/graycode-cli/internal/tool" diff --git a/internal/engine/engine_integration_test.go b/internal/engine/engine_integration_test.go index 92614fd7..d3d9b433 100644 --- a/internal/engine/engine_integration_test.go +++ b/internal/engine/engine_integration_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/types" "github.com/GrayCodeAI/graycode-cli/internal/session" diff --git a/internal/engine/execution_graph_observations.go b/internal/engine/execution_graph_observations.go index 815ad644..fa99b14d 100644 --- a/internal/engine/execution_graph_observations.go +++ b/internal/engine/execution_graph_observations.go @@ -7,10 +7,10 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" eyrieengine "github.com/GrayCodeAI/eyrie/engine" eyriegraph "github.com/GrayCodeAI/eyrie/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/engine/token" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" "github.com/GrayCodeAI/graycode-cli/internal/types" @@ -162,7 +162,7 @@ func (s *Session) recordShrikeCompressionObservation(source, stage string, stats if err == nil { err = graphjournal.AppendRuntimeGraph( sessionID, "", stage, "shrike", - shrikeToEagleNodes(export.Nodes), shrikeToEagleEdges(export.Edges), shrikeToEagleEvents(export.Events), observedAt, + shrikeToContractNodes(export.Nodes), shrikeToContractEdges(export.Edges), shrikeToContractEvents(export.Events), observedAt, ) } if err != nil { @@ -200,7 +200,7 @@ func (s *Session) recordShrikeRedactionObservation(source string, matchCount int if err == nil { err = graphjournal.AppendRuntimeGraph( sessionID, "", "response-redaction", "shrike", - shrikeToEagleNodes(export.Nodes), shrikeToEagleEdges(export.Edges), shrikeToEagleEvents(export.Events), observedAt, + shrikeToContractNodes(export.Nodes), shrikeToContractEdges(export.Edges), shrikeToContractEvents(export.Events), observedAt, ) } if err != nil { @@ -258,7 +258,7 @@ func (s *Session) recordShrikeUsageBudgetObservation( if err == nil { err = graphjournal.AppendRuntimeGraph( sessionID, "", "usage-budget", "shrike", - shrikeToEagleNodes(export.Nodes), shrikeToEagleEdges(export.Edges), shrikeToEagleEvents(export.Events), observedAt, + shrikeToContractNodes(export.Nodes), shrikeToContractEdges(export.Edges), shrikeToContractEvents(export.Events), observedAt, ) } if err != nil { @@ -323,7 +323,7 @@ func (s *Session) recordEyrieOperationObservation( if err == nil { err = graphjournal.AppendRuntimeGraph( sessionID, "", "model-generation", "eyrie", - toEagleNodes(export.Nodes), toEagleEdges(export.Edges), toEagleEvents(export.Events), observedAt, + toContractNodes(export.Nodes), toContractEdges(export.Edges), toContractEvents(export.Events), observedAt, ) } if err != nil { @@ -335,82 +335,82 @@ func (s *Session) recordEyrieOperationObservation( } // The following helpers convert Eyrie's vendored graph contract types into -// Graycode's eagle/graph contract types. The definitions are byte-identical, so +// Graycode's contracts/graph contract types. The definitions are byte-identical, so // conversion is a field-by-field copy at the sibling boundary. -func toEagleNodes(nodes []eyriegraph.Node) []graphcontracts.Node { +func toContractNodes(nodes []eyriegraph.Node) []graphcontracts.Node { out := make([]graphcontracts.Node, len(nodes)) for i, n := range nodes { - out[i] = toEagleNode(n) + out[i] = toContractNode(n) } return out } -func toEagleNode(n eyriegraph.Node) graphcontracts.Node { +func toContractNode(n eyriegraph.Node) graphcontracts.Node { return graphcontracts.Node{ ID: n.ID, Kind: graphcontracts.NodeKind(n.Kind), - Scope: toEagleScope(n.Scope), + Scope: toContractScope(n.Scope), CreatedAt: n.CreatedAt, EffectiveAt: n.EffectiveAt, - Provenance: toEagleProvenance(n.Provenance), + Provenance: toContractProvenance(n.Provenance), Attributes: n.Attributes, } } -func toEagleEdges(edges []eyriegraph.Edge) []graphcontracts.Edge { +func toContractEdges(edges []eyriegraph.Edge) []graphcontracts.Edge { out := make([]graphcontracts.Edge, len(edges)) for i, e := range edges { - out[i] = toEagleEdge(e) + out[i] = toContractEdge(e) } return out } -func toEagleEdge(e eyriegraph.Edge) graphcontracts.Edge { +func toContractEdge(e eyriegraph.Edge) graphcontracts.Edge { return graphcontracts.Edge{ ID: e.ID, Kind: graphcontracts.EdgeKind(e.Kind), - From: toEagleRef(e.From), - To: toEagleRef(e.To), - Scope: toEagleScope(e.Scope), + From: toContractRef(e.From), + To: toContractRef(e.To), + Scope: toContractScope(e.Scope), CreatedAt: e.CreatedAt, EffectiveAt: e.EffectiveAt, - Provenance: toEagleProvenance(e.Provenance), + Provenance: toContractProvenance(e.Provenance), Attributes: e.Attributes, } } -func toEagleEvents(events []eyriegraph.Event) []graphcontracts.Event { +func toContractEvents(events []eyriegraph.Event) []graphcontracts.Event { out := make([]graphcontracts.Event, len(events)) for i, ev := range events { - out[i] = toEagleEvent(ev) + out[i] = toContractEvent(ev) } return out } -func toEagleEvent(ev eyriegraph.Event) graphcontracts.Event { +func toContractEvent(ev eyriegraph.Event) graphcontracts.Event { return graphcontracts.Event{ ID: ev.ID, Type: graphcontracts.EventType(ev.Type), - Subject: toEagleRef(ev.Subject), - Scope: toEagleScope(ev.Scope), + Subject: toContractRef(ev.Subject), + Scope: toContractScope(ev.Scope), OccurredAt: ev.OccurredAt, CorrelationID: ev.CorrelationID, CausationID: ev.CausationID, IdempotencyKey: ev.IdempotencyKey, - Provenance: toEagleProvenance(ev.Provenance), + Provenance: toContractProvenance(ev.Provenance), } } -func toEagleRef(r eyriegraph.Ref) graphcontracts.Ref { +func toContractRef(r eyriegraph.Ref) graphcontracts.Ref { return graphcontracts.Ref{Kind: graphcontracts.NodeKind(r.Kind), ID: r.ID} } -func toEagleScope(s eyriegraph.Scope) graphcontracts.Scope { +func toContractScope(s eyriegraph.Scope) graphcontracts.Scope { return graphcontracts.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleProvenance(p eyriegraph.Provenance) graphcontracts.Provenance { +func toContractProvenance(p eyriegraph.Provenance) graphcontracts.Provenance { evidence := make([]graphcontracts.ArtifactRef, len(p.Evidence)) for i, a := range p.Evidence { evidence[i] = graphcontracts.ArtifactRef{URI: a.URI, Digest: a.Digest, MediaType: a.MediaType} @@ -418,82 +418,82 @@ func toEagleProvenance(p eyriegraph.Provenance) graphcontracts.Provenance { return graphcontracts.Provenance{Producer: p.Producer, Version: p.Version, SourceID: p.SourceID, Evidence: evidence} } -// Shrike's vendored graph contract types are byte-identical to eagle/graph, so +// Shrike's vendored graph contract types are byte-identical to contracts/graph, so // conversion is a field-by-field copy at the sibling boundary. -func shrikeToEagleNodes(nodes []shrikegraph.Node) []graphcontracts.Node { +func shrikeToContractNodes(nodes []shrikegraph.Node) []graphcontracts.Node { out := make([]graphcontracts.Node, len(nodes)) for i, n := range nodes { - out[i] = shrikeToEagleNode(n) + out[i] = shrikeToContractNode(n) } return out } -func shrikeToEagleNode(n shrikegraph.Node) graphcontracts.Node { +func shrikeToContractNode(n shrikegraph.Node) graphcontracts.Node { return graphcontracts.Node{ ID: n.ID, Kind: graphcontracts.NodeKind(n.Kind), - Scope: shrikeToEagleScope(n.Scope), + Scope: shrikeToContractScope(n.Scope), CreatedAt: n.CreatedAt, EffectiveAt: n.EffectiveAt, - Provenance: shrikeToEagleProvenance(n.Provenance), + Provenance: shrikeToContractProvenance(n.Provenance), Attributes: n.Attributes, } } -func shrikeToEagleEdges(edges []shrikegraph.Edge) []graphcontracts.Edge { +func shrikeToContractEdges(edges []shrikegraph.Edge) []graphcontracts.Edge { out := make([]graphcontracts.Edge, len(edges)) for i, e := range edges { - out[i] = shrikeToEagleEdge(e) + out[i] = shrikeToContractEdge(e) } return out } -func shrikeToEagleEdge(e shrikegraph.Edge) graphcontracts.Edge { +func shrikeToContractEdge(e shrikegraph.Edge) graphcontracts.Edge { return graphcontracts.Edge{ ID: e.ID, Kind: graphcontracts.EdgeKind(e.Kind), - From: shrikeToEagleRef(e.From), - To: shrikeToEagleRef(e.To), - Scope: shrikeToEagleScope(e.Scope), + From: shrikeToContractRef(e.From), + To: shrikeToContractRef(e.To), + Scope: shrikeToContractScope(e.Scope), CreatedAt: e.CreatedAt, EffectiveAt: e.EffectiveAt, - Provenance: shrikeToEagleProvenance(e.Provenance), + Provenance: shrikeToContractProvenance(e.Provenance), Attributes: e.Attributes, } } -func shrikeToEagleEvents(events []shrikegraph.Event) []graphcontracts.Event { +func shrikeToContractEvents(events []shrikegraph.Event) []graphcontracts.Event { out := make([]graphcontracts.Event, len(events)) for i, ev := range events { - out[i] = shrikeToEagleEvent(ev) + out[i] = shrikeToContractEvent(ev) } return out } -func shrikeToEagleEvent(ev shrikegraph.Event) graphcontracts.Event { +func shrikeToContractEvent(ev shrikegraph.Event) graphcontracts.Event { return graphcontracts.Event{ ID: ev.ID, Type: graphcontracts.EventType(ev.Type), - Subject: shrikeToEagleRef(ev.Subject), - Scope: shrikeToEagleScope(ev.Scope), + Subject: shrikeToContractRef(ev.Subject), + Scope: shrikeToContractScope(ev.Scope), OccurredAt: ev.OccurredAt, CorrelationID: ev.CorrelationID, CausationID: ev.CausationID, IdempotencyKey: ev.IdempotencyKey, - Provenance: shrikeToEagleProvenance(ev.Provenance), + Provenance: shrikeToContractProvenance(ev.Provenance), } } -func shrikeToEagleRef(r shrikegraph.Ref) graphcontracts.Ref { +func shrikeToContractRef(r shrikegraph.Ref) graphcontracts.Ref { return graphcontracts.Ref{Kind: graphcontracts.NodeKind(r.Kind), ID: r.ID} } -func shrikeToEagleScope(s shrikegraph.Scope) graphcontracts.Scope { +func shrikeToContractScope(s shrikegraph.Scope) graphcontracts.Scope { return graphcontracts.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func shrikeToEagleProvenance(p shrikegraph.Provenance) graphcontracts.Provenance { +func shrikeToContractProvenance(p shrikegraph.Provenance) graphcontracts.Provenance { evidence := make([]graphcontracts.ArtifactRef, len(p.Evidence)) for i, a := range p.Evidence { evidence[i] = graphcontracts.ArtifactRef{URI: a.URI, Digest: a.Digest, MediaType: a.MediaType} diff --git a/internal/engine/safety/permission.go b/internal/engine/safety/permission.go index 734e2b28..7e979b73 100644 --- a/internal/engine/safety/permission.go +++ b/internal/engine/safety/permission.go @@ -7,7 +7,7 @@ import ( "sync" "time" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/permissions" "github.com/GrayCodeAI/graycode-cli/internal/tool" ) diff --git a/internal/engine/safety/permission_engine.go b/internal/engine/safety/permission_engine.go index 66fe3bb2..f8ddf353 100644 --- a/internal/engine/safety/permission_engine.go +++ b/internal/engine/safety/permission_engine.go @@ -11,7 +11,7 @@ import ( "sync" "time" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/governance" "github.com/GrayCodeAI/graycode-cli/internal/hooks" "github.com/GrayCodeAI/graycode-cli/internal/observability/metrics" diff --git a/internal/engine/session.go b/internal/engine/session.go index 56bb9365..4d9778e9 100644 --- a/internal/engine/session.go +++ b/internal/engine/session.go @@ -10,7 +10,7 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/conversationarc" "github.com/GrayCodeAI/graycode-cli/internal/engine/planning" "github.com/GrayCodeAI/graycode-cli/internal/eventlog" diff --git a/internal/engine/spawn_controller.go b/internal/engine/spawn_controller.go index 049a7f88..6567db05 100644 --- a/internal/engine/spawn_controller.go +++ b/internal/engine/spawn_controller.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/taskruntime" ) diff --git a/internal/executiongraph/execution_graph.go b/internal/executiongraph/execution_graph.go index b084b7ff..6aa75932 100644 --- a/internal/executiongraph/execution_graph.go +++ b/internal/executiongraph/execution_graph.go @@ -12,9 +12,9 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" - verifycontracts "github.com/GrayCodeAI/eagle/verify" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" + verifycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/verify" "github.com/GrayCodeAI/graycode-cli/internal/session" "github.com/GrayCodeAI/graycode-cli/internal/taskruntime" "github.com/GrayCodeAI/graycode-cli/internal/tool" diff --git a/internal/executiongraph/execution_graph_test.go b/internal/executiongraph/execution_graph_test.go index 06132906..38bdcf26 100644 --- a/internal/executiongraph/execution_graph_test.go +++ b/internal/executiongraph/execution_graph_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" - typescontracts "github.com/GrayCodeAI/eagle/types" - verifycontracts "github.com/GrayCodeAI/eagle/verify" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" + typescontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" + verifycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/verify" "github.com/GrayCodeAI/graycode-cli/internal/session" "github.com/GrayCodeAI/graycode-cli/internal/taskruntime" "github.com/GrayCodeAI/graycode-cli/internal/tool" diff --git a/internal/graphjournal/journal.go b/internal/graphjournal/journal.go index 59e566d0..d93dae4f 100644 --- a/internal/graphjournal/journal.go +++ b/internal/graphjournal/journal.go @@ -15,8 +15,8 @@ import ( "sync" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" "github.com/GrayCodeAI/graycode-cli/internal/storage" ) diff --git a/internal/graphjournal/journal_test.go b/internal/graphjournal/journal_test.go index 93c315f6..09b24c65 100644 --- a/internal/graphjournal/journal_test.go +++ b/internal/graphjournal/journal_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" - policycontracts "github.com/GrayCodeAI/eagle/policy" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" ) func TestJournalRoundTripDoesNotPersistSensitivePayloads(t *testing.T) { diff --git a/internal/harness/journal.go b/internal/harness/journal.go index 85884250..95d963b9 100644 --- a/internal/harness/journal.go +++ b/internal/harness/journal.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" ) diff --git a/internal/harness/workloop.go b/internal/harness/workloop.go index 2da74748..1f029093 100644 --- a/internal/harness/workloop.go +++ b/internal/harness/workloop.go @@ -3,8 +3,8 @@ package harness import ( "time" - harnesscontracts "github.com/GrayCodeAI/eagle/harness" - typescontracts "github.com/GrayCodeAI/eagle/types" + harnesscontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/harness" + typescontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" ) // Dimension represents one of the five core dimensions of the Agent Work Loop. @@ -100,7 +100,7 @@ type EvaluateOptions struct { OutputDir string } -// ToContractReport converts the native Graycode HarnessReport to the neutral eagle Report. +// ToContractReport converts the native Graycode HarnessReport to the neutral contracts Report. func (r *HarnessReport) ToContractReport() *harnesscontracts.Report { if r == nil { return nil diff --git a/internal/hooks/aliases.go b/internal/hooks/aliases.go index 98b437ec..10f52c35 100644 --- a/internal/hooks/aliases.go +++ b/internal/hooks/aliases.go @@ -3,7 +3,7 @@ package hooks import ( "strings" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) // Extended EventType values (Year 0 PACK-04). Existing snake_case constants in diff --git a/internal/hooks/audit/detectors.go b/internal/hooks/audit/detectors.go index b3a757d1..9ad41ff8 100644 --- a/internal/hooks/audit/detectors.go +++ b/internal/hooks/audit/detectors.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - contracts "github.com/GrayCodeAI/eagle/events" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/events" ) // DetectorHit represents one detected occurrence of wasteful behavior. diff --git a/internal/intelligence/memory/harrier_bridge.go b/internal/intelligence/memory/harrier_bridge.go index 7ef5376e..964992f9 100644 --- a/internal/intelligence/memory/harrier_bridge.go +++ b/internal/intelligence/memory/harrier_bridge.go @@ -13,7 +13,7 @@ import ( "sync" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" "github.com/GrayCodeAI/graycode-cli/internal/graycodeerr" harrier "github.com/GrayCodeAI/harrier" @@ -459,9 +459,9 @@ func (b *HarrierBridge) recordContextGraph(query string, result *harrierEngine.R b.graphSessionID, "harrier", projection.QuerySHA256, - toEagleNodes(projection.Nodes), - toEagleEdges(projection.Edges), - toEagleEvents(projection.Events), + toContractNodes(projection.Nodes), + toContractEdges(projection.Edges), + toContractEvents(projection.Events), projection.GeneratedAt, ); err != nil { slog.Warn("[graycode/memory] harrier context graph observation failed", "error", err) @@ -469,7 +469,7 @@ func (b *HarrierBridge) recordContextGraph(query string, result *harrierEngine.R } // The following helpers convert Harrier's vendored portable-graph contract -// types into Graycode's eagle/graph contract types (and the reverse for scope). +// types into Graycode's contracts/graph contract types (and the reverse for scope). // The definitions are byte-identical, so conversion is a field-by-field copy // at the sibling boundary. @@ -477,79 +477,79 @@ func toHarrierScope(s graphcontracts.Scope) harrierPortableGraph.Scope { return harrierPortableGraph.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleNodes(nodes []harrierPortableGraph.Node) []graphcontracts.Node { +func toContractNodes(nodes []harrierPortableGraph.Node) []graphcontracts.Node { out := make([]graphcontracts.Node, len(nodes)) for i, n := range nodes { - out[i] = toEagleNode(n) + out[i] = toContractNode(n) } return out } -func toEagleNode(n harrierPortableGraph.Node) graphcontracts.Node { +func toContractNode(n harrierPortableGraph.Node) graphcontracts.Node { return graphcontracts.Node{ ID: n.ID, Kind: graphcontracts.NodeKind(n.Kind), - Scope: toEagleScope(n.Scope), + Scope: toContractScope(n.Scope), CreatedAt: n.CreatedAt, EffectiveAt: n.EffectiveAt, - Provenance: toEagleProvenance(n.Provenance), + Provenance: toContractProvenance(n.Provenance), Attributes: n.Attributes, } } -func toEagleEdges(edges []harrierPortableGraph.Edge) []graphcontracts.Edge { +func toContractEdges(edges []harrierPortableGraph.Edge) []graphcontracts.Edge { out := make([]graphcontracts.Edge, len(edges)) for i, e := range edges { - out[i] = toEagleEdge(e) + out[i] = toContractEdge(e) } return out } -func toEagleEdge(e harrierPortableGraph.Edge) graphcontracts.Edge { +func toContractEdge(e harrierPortableGraph.Edge) graphcontracts.Edge { return graphcontracts.Edge{ ID: e.ID, Kind: graphcontracts.EdgeKind(e.Kind), - From: toEagleRef(e.From), - To: toEagleRef(e.To), - Scope: toEagleScope(e.Scope), + From: toContractRef(e.From), + To: toContractRef(e.To), + Scope: toContractScope(e.Scope), CreatedAt: e.CreatedAt, EffectiveAt: e.EffectiveAt, - Provenance: toEagleProvenance(e.Provenance), + Provenance: toContractProvenance(e.Provenance), Attributes: e.Attributes, } } -func toEagleEvents(events []harrierPortableGraph.Event) []graphcontracts.Event { +func toContractEvents(events []harrierPortableGraph.Event) []graphcontracts.Event { out := make([]graphcontracts.Event, len(events)) for i, ev := range events { - out[i] = toEagleEvent(ev) + out[i] = toContractEvent(ev) } return out } -func toEagleEvent(ev harrierPortableGraph.Event) graphcontracts.Event { +func toContractEvent(ev harrierPortableGraph.Event) graphcontracts.Event { return graphcontracts.Event{ ID: ev.ID, Type: graphcontracts.EventType(ev.Type), - Subject: toEagleRef(ev.Subject), - Scope: toEagleScope(ev.Scope), + Subject: toContractRef(ev.Subject), + Scope: toContractScope(ev.Scope), OccurredAt: ev.OccurredAt, CorrelationID: ev.CorrelationID, CausationID: ev.CausationID, IdempotencyKey: ev.IdempotencyKey, - Provenance: toEagleProvenance(ev.Provenance), + Provenance: toContractProvenance(ev.Provenance), } } -func toEagleRef(r harrierPortableGraph.Ref) graphcontracts.Ref { +func toContractRef(r harrierPortableGraph.Ref) graphcontracts.Ref { return graphcontracts.Ref{Kind: graphcontracts.NodeKind(r.Kind), ID: r.ID} } -func toEagleScope(s harrierPortableGraph.Scope) graphcontracts.Scope { +func toContractScope(s harrierPortableGraph.Scope) graphcontracts.Scope { return graphcontracts.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toEagleProvenance(p harrierPortableGraph.Provenance) graphcontracts.Provenance { +func toContractProvenance(p harrierPortableGraph.Provenance) graphcontracts.Provenance { evidence := make([]graphcontracts.ArtifactRef, len(p.Evidence)) for i, a := range p.Evidence { evidence[i] = graphcontracts.ArtifactRef{URI: a.URI, Digest: a.Digest, MediaType: a.MediaType} diff --git a/internal/intelligence/memory/harrier_bridge_integration_test.go b/internal/intelligence/memory/harrier_bridge_integration_test.go index eba52012..1e18d933 100644 --- a/internal/intelligence/memory/harrier_bridge_integration_test.go +++ b/internal/intelligence/memory/harrier_bridge_integration_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/graphjournal" ) diff --git a/internal/multiagent/portable_graph.go b/internal/multiagent/portable_graph.go index 75391aea..65a009fc 100644 --- a/internal/multiagent/portable_graph.go +++ b/internal/multiagent/portable_graph.go @@ -12,7 +12,7 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/executiongraph" ) diff --git a/internal/observability/oteltrace/langfuse.go b/internal/observability/oteltrace/langfuse.go index 0c023a2e..de2f598e 100644 --- a/internal/observability/oteltrace/langfuse.go +++ b/internal/observability/oteltrace/langfuse.go @@ -12,7 +12,7 @@ import ( "sync" "time" - contracts "github.com/GrayCodeAI/eagle/events" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/events" ) // LangfuseClient sends traces to Langfuse for LLM observability. diff --git a/internal/permissions/guardian.go b/internal/permissions/guardian.go index a9ca297d..0e6926e4 100644 --- a/internal/permissions/guardian.go +++ b/internal/permissions/guardian.go @@ -9,7 +9,7 @@ import ( "sync" "time" - contracts "github.com/GrayCodeAI/eagle/policy" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" ) // ErrCircuitBreakerOpen is returned when the guardian has denied too many diff --git a/internal/permissions/verdict.go b/internal/permissions/verdict.go index d7a3cf67..8638c9a5 100644 --- a/internal/permissions/verdict.go +++ b/internal/permissions/verdict.go @@ -12,7 +12,7 @@ // (PermissionVerdict). Ported to native Go. package permissions -import contracts "github.com/GrayCodeAI/eagle/policy" +import contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/policy" // Risk is the severity of a permission verdict. type Risk = contracts.Risk diff --git a/internal/platform/cloud/graph_test.go b/internal/platform/cloud/graph_test.go index e8abd55f..32e4b331 100644 --- a/internal/platform/cloud/graph_test.go +++ b/internal/platform/cloud/graph_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/eagle/graph" + graphcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/graph" "github.com/GrayCodeAI/graycode-cli/internal/executiongraph" ) diff --git a/internal/taskruntime/runtime.go b/internal/taskruntime/runtime.go index 95ec1899..df2da5f1 100644 --- a/internal/taskruntime/runtime.go +++ b/internal/taskruntime/runtime.go @@ -8,7 +8,7 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) // Kind classifies a background task. diff --git a/internal/taskruntime/runtime_extra_test.go b/internal/taskruntime/runtime_extra_test.go index 27dc8ec3..eb81ca2a 100644 --- a/internal/taskruntime/runtime_extra_test.go +++ b/internal/taskruntime/runtime_extra_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) // --- SpawnAgent tests --- diff --git a/internal/taskruntime/runtime_test.go b/internal/taskruntime/runtime_test.go index 821e2b8a..721bcd21 100644 --- a/internal/taskruntime/runtime_test.go +++ b/internal/taskruntime/runtime_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) func TestSpawnAndCollect(t *testing.T) { diff --git a/internal/testaudit/audit_test.go b/internal/testaudit/audit_test.go index b7e84a27..15738711 100644 --- a/internal/testaudit/audit_test.go +++ b/internal/testaudit/audit_test.go @@ -283,7 +283,7 @@ func TestNoDirectSharedTypesImports(t *testing.T) { continue } pos := pf.FSet.Position(imp.Pos()) - t.Fatalf("forbidden direct graycode/shared/types import at %s:%d; the path has been removed, use eagle instead", rel, pos.Line) + t.Fatalf("forbidden direct graycode/shared/types import at %s:%d; the path has been removed, use internal/contracts instead", rel, pos.Line) } } } diff --git a/internal/testaudit/docs_audit_test.go b/internal/testaudit/docs_audit_test.go index b87a84d9..41025592 100644 --- a/internal/testaudit/docs_audit_test.go +++ b/internal/testaudit/docs_audit_test.go @@ -45,16 +45,16 @@ func TestArchitectureDocsMentionCurrentReviewVerifyContracts(t *testing.T) { checks := map[string][]string{ "README.md": { - "eagle/review", - "eagle/verify", + "contracts/review", + "contracts/verify", }, "docs/architecture/graycode-product-architecture.md": { - "eagle/review", - "eagle/verify", + "contracts/review", + "contracts/verify", }, "docs/architecture/graycode-contract-spec.md": { - "eagle/review", - "eagle/verify", + "contracts/review", + "contracts/verify", }, } diff --git a/internal/tool/agent.go b/internal/tool/agent.go index a2213857..d8ad0cb1 100644 --- a/internal/tool/agent.go +++ b/internal/tool/agent.go @@ -7,7 +7,7 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) const ( diff --git a/internal/tool/agent_limits_test.go b/internal/tool/agent_limits_test.go index 586a95e0..ade39f6f 100644 --- a/internal/tool/agent_limits_test.go +++ b/internal/tool/agent_limits_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) func TestAgentTool_PromptTooLarge(t *testing.T) { diff --git a/internal/tool/agent_test.go b/internal/tool/agent_test.go index c9994913..ffa3603f 100644 --- a/internal/tool/agent_test.go +++ b/internal/tool/agent_test.go @@ -8,7 +8,7 @@ import ( "sync" "testing" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) func TestAgentTool_NoContext(t *testing.T) { diff --git a/internal/tool/agentic_fetch.go b/internal/tool/agentic_fetch.go index f1a4a4f0..e6372592 100644 --- a/internal/tool/agentic_fetch.go +++ b/internal/tool/agentic_fetch.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) func agentSpawnExplore(prompt string) agentcontracts.SpawnRequest { diff --git a/internal/tool/agentic_fetch_test.go b/internal/tool/agentic_fetch_test.go index 89338e34..3227d5d5 100644 --- a/internal/tool/agentic_fetch_test.go +++ b/internal/tool/agentic_fetch_test.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "testing" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" ) func TestAgenticFetch_BatchFanOut(t *testing.T) { diff --git a/internal/tool/background.go b/internal/tool/background.go index 801df58a..1288f5df 100644 --- a/internal/tool/background.go +++ b/internal/tool/background.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/taskruntime" ) diff --git a/internal/tool/spec_parallel.go b/internal/tool/spec_parallel.go index 318a137e..c773b292 100644 --- a/internal/tool/spec_parallel.go +++ b/internal/tool/spec_parallel.go @@ -9,7 +9,7 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/spec" ) diff --git a/internal/tool/tool.go b/internal/tool/tool.go index 4a70eb94..bb98782d 100644 --- a/internal/tool/tool.go +++ b/internal/tool/tool.go @@ -9,7 +9,7 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/eagle/agent" + agentcontracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/agent" "github.com/GrayCodeAI/graycode-cli/internal/intelligence/memory" "github.com/GrayCodeAI/graycode-cli/internal/lint" diff --git a/internal/types/severity.go b/internal/types/severity.go index 3d269c21..1ee3a0cd 100644 --- a/internal/types/severity.go +++ b/internal/types/severity.go @@ -1,10 +1,10 @@ // Package types provides Graycode-owned runtime types and shared compatibility aliases. -// Severity, TokenSeverity, and AuditSeverity are forwarded from eagle/types. +// Severity, TokenSeverity, and AuditSeverity are forwarded from internal/contracts/types. // Provider-facing compatibility now lives in explicit adapters inside internal/types/client.go. package types import ( - contracts "github.com/GrayCodeAI/eagle/types" + contracts "github.com/GrayCodeAI/graycode-cli/internal/contracts/types" ) // Severity represents the impact level of a finding. diff --git a/scripts/check-contracts-parity.sh b/scripts/check-contracts-parity.sh deleted file mode 100755 index 04389933..00000000 --- a/scripts/check-contracts-parity.sh +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env bash -# CI guard: fail if the ecosystem's shared kernel (eagle) is pinned -# to more than one version across the sibling Go modules. -# -# The expected version and the list of modules that must track it are DATA, read -# from ecosystem.yaml — not hardcoded here. Bump the manifest, -# not this script. (Parsed with grep/sed/awk on purpose: no yq dependency.) -# -# Skew across a breaking kernel change is otherwise invisible: release-please -# never rewrites a downstream go.mod, and a go.work workspace can mask the pins -# entirely. This guard turns that divergence into a red build. -# -# Exit codes: 0 = all agree (or siblings not checked out), 1 = skew / misconfig. -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -ECO_DIR="$(cd "${ROOT_DIR}/.." && pwd)" -SELF_REPO="$(basename "${ROOT_DIR}")" -MANIFEST="${ROOT_DIR}/ecosystem.yaml" - -# Colour only on an interactive terminal; CI logs and NO_COLOR stay plain. -if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then - C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_OFF=$'\033[0m' -else - C_RED=''; C_GREEN=''; C_YELLOW=''; C_OFF='' -fi - -if [[ ! -f "${MANIFEST}" ]]; then - echo "ERROR: manifest not found: ${MANIFEST}" >&2 - exit 1 -fi - -# --- manifest parsing ------------------------------------------------------- - -yaml_scalar() { - # yaml_scalar — first "key: value" at any indentation, comments stripped. - sed -n "s/^[[:space:]]*$1:[[:space:]]*\([^#]*\).*/\1/p" "${MANIFEST}" | - head -1 | sed 's/[[:space:]]*$//' -} - -yaml_list() { - # yaml_list — the "- item" entries under that key. - awk -v key="$1" ' - $0 ~ "^" key ":[[:space:]]*(#.*)?$" { inblock = 1; next } - inblock && /^[^[:space:]#]/ { inblock = 0 } - inblock && /^[[:space:]]*-[[:space:]]*/ { - line = $0 - sub(/^[[:space:]]*-[[:space:]]*/, "", line) - sub(/[[:space:]]*#.*$/, "", line) - gsub(/[[:space:]]/, "", line) - if (line != "") print line - } - ' "${MANIFEST}" -} - -MODULE_PATH="github.com/GrayCodeAI/eagle" -WANT_VERSION="$(yaml_scalar eagle_version)" -SOURCE_REPO="eagle" -MODULES=() -while IFS= read -r _m; do - [[ -n "${_m}" ]] && MODULES+=("${_m}") -done < <("${ROOT_DIR}/scripts/ecosystem-manifest.sh" list eagle-consumers) - -if [[ -z "${MODULE_PATH}" || -z "${WANT_VERSION}" || ${#MODULES[@]} -eq 0 ]]; then - echo "ERROR: ${MANIFEST} is missing contracts.module, contracts.version or modules[]" >&2 - exit 1 -fi - -# --- go.mod inspection ------------------------------------------------------ - -repo_dir() { - # The repo holding this script may be checked out under any directory name. - if [[ "$1" == "${SELF_REPO}" || "$1" == "graycode-cli" && ! -d "${ECO_DIR}/graycode-cli" ]]; then - echo "${ROOT_DIR}" - else - echo "${ECO_DIR}/$1" - fi -} - -mod_version() { - # Print the version this go.mod requires for MODULE_PATH ("" if none). - # Skips replace/exclude directives, single-line and block form alike. - awk -v mod="${MODULE_PATH}" ' - /^(replace|exclude)[[:space:]]*\($/ { skip = 1; next } - skip && /^\)/ { skip = 0; next } - skip { next } - $1 == "replace" || $1 == "exclude" { next } - { - for (i = 1; i <= NF; i++) { - if ($i == mod && (i + 1) <= NF && $(i + 1) ~ /^v[0-9]/) { print $(i + 1); exit } - } - } - ' "$1" -} - -# --- checks ----------------------------------------------------------------- - -echo "shared kernel: ${MODULE_PATH}" -echo "declared version (ecosystem.yaml): ${WANT_VERSION}" -echo - -checked=0 -mismatched=() -missing_require=() -undeclared=() -versions_seen="" - -printf '%-24s %-12s %s\n' MODULE VERSION STATUS -for name in "${MODULES[@]}"; do - dir="$(repo_dir "${name}")" - if [[ ! -f "${dir}/go.mod" ]]; then - printf '%-24s %-12s %s\n' "${name}" - NOT_CHECKED_OUT - continue - fi - - checked=$((checked + 1)) - got="$(mod_version "${dir}/go.mod")" - - if [[ -z "${got}" ]]; then - printf '%-24s %-12s %s%s%s\n' "${name}" - "${C_RED}" MISSING_REQUIRE "${C_OFF}" - missing_require+=("${name}") - continue - fi - - versions_seen+="${got}"$'\n' - if [[ "${got}" == "${WANT_VERSION}" ]]; then - printf '%-24s %-12s %s%s%s\n' "${name}" "${got}" "${C_GREEN}" OK "${C_OFF}" - else - printf '%-24s %-12s %s%s%s\n' "${name}" "${got}" "${C_RED}" MISMATCH "${C_OFF}" - mismatched+=("${name}=${got}") - fi -done - -# A sibling that requires the kernel but is not declared is skew waiting to happen. -while IFS= read -r gomod; do - [[ -f "${gomod}" ]] || continue - name="$(basename "$(dirname "${gomod}")")" - [[ "${name}" == "${SOURCE_REPO}" ]] && continue - for declared in "${MODULES[@]}"; do - [[ "${name}" == "${declared}" ]] && continue 2 - done - [[ -n "$(mod_version "${gomod}")" ]] || continue - printf '%-24s %-12s %s%s%s\n' "${name}" "$(mod_version "${gomod}")" "${C_RED}" UNDECLARED "${C_OFF}" - undeclared+=("${name}") -done < <(find "${ECO_DIR}" -mindepth 2 -maxdepth 2 -name go.mod 2>/dev/null | sort) - -echo - -if ((checked == 0)); then - echo "${C_YELLOW}NOTICE${C_OFF}: no sibling modules are checked out next to ${ROOT_DIR} — nothing to compare." - echo "contracts parity guard skipped (this is expected in a single-repo CI checkout)." - exit 0 -fi - -# Advisory only: VERSION declares the intended source release; a matching remote -# tag is checked separately by release-parity. -src_dir="$(repo_dir "${SOURCE_REPO}")" -if [[ -f "${src_dir}/VERSION" ]]; then - source_version="v$(tr -d '[:space:]' < "${src_dir}/VERSION" | sed 's/^v//')" - if [[ "${source_version}" != "${WANT_VERSION}" ]]; then - echo "${C_YELLOW}NOTICE${C_OFF}: ${SOURCE_REPO} source declares ${source_version}; consumers pin ${WANT_VERSION}." - echo " Expected while a reachable pseudo-version bridges the next ordered semver release." - echo - fi -fi - -if ((${#mismatched[@]} == 0 && ${#missing_require[@]} == 0 && ${#undeclared[@]} == 0)); then - echo "${C_GREEN}OK${C_OFF}: contracts parity guard passed — ${checked} module(s) all pinned to ${MODULE_PATH} ${WANT_VERSION}." - exit 0 -fi - -echo "${C_RED}FAIL${C_OFF}: shared-kernel version skew detected." -echo - -if ((${#mismatched[@]} > 0)); then - echo "modules disagreeing with the declared version ${WANT_VERSION}:" - for entry in "${mismatched[@]}"; do - echo " - ${entry%%=*} pins ${entry#*=}" - done - echo - echo "distinct versions in use:" - printf '%s' "${versions_seen}" | sort -u | sed 's/^/ - /' - echo - echo "to fix, in each disagreeing repo:" - for entry in "${mismatched[@]}"; do - echo " cd $(repo_dir "${entry%%=*}") && \\" - echo " GOWORK=off go get ${MODULE_PATH}@${WANT_VERSION} && GOWORK=off go mod tidy" - done - echo - echo "if the DECLARED version is the stale one, bump contracts.version in" - echo " ${MANIFEST}" - echo "and re-run this guard." - echo -fi - -if ((${#missing_require[@]} > 0)); then - echo "modules declared in the manifest but no longer requiring ${MODULE_PATH}:" - printf ' - %s\n' "${missing_require[@]}" - echo " add the dependency back, or drop the module from modules[] in ${MANIFEST}." - echo -fi - -if ((${#undeclared[@]} > 0)); then - echo "modules requiring ${MODULE_PATH} without being declared in the manifest:" - printf ' - %s\n' "${undeclared[@]}" - echo " add each to modules[] in ${MANIFEST} so it is held to the declared version." - echo -fi - -exit 1 diff --git a/scripts/check-ecosystem-boundaries.sh b/scripts/check-ecosystem-boundaries.sh index 790d1b8f..b08193bb 100755 --- a/scripts/check-ecosystem-boundaries.sh +++ b/scripts/check-ecosystem-boundaries.sh @@ -22,7 +22,7 @@ if [[ -n "${violations}" ]]; then echo "forbidden Graycode imports found in sibling ecosystem repos:" echo "${violations}" echo - echo "support repos must use eagle or their own contracts, not graycode/internal or removed graycode/shared/types" + echo "support repos must use their own contracts, not graycode/internal or removed graycode/shared/types" exit 1 fi diff --git a/scripts/check-shared-types-imports.sh b/scripts/check-shared-types-imports.sh index 80fda2ce..3d78bed0 100644 --- a/scripts/check-shared-types-imports.sh +++ b/scripts/check-shared-types-imports.sh @@ -14,7 +14,7 @@ if [[ -n "${violations}" ]]; then echo "forbidden imports of removed github.com/GrayCodeAI/graycode-cli/shared/types found:" echo "${violations}" echo - echo "graycode/shared/types has been removed; use github.com/GrayCodeAI/eagle/types instead" + echo "graycode/shared/types has been removed; use internal/contracts instead" exit 1 fi diff --git a/scripts/check-support-repo-coupling.sh b/scripts/check-support-repo-coupling.sh index 598e4989..95d539f7 100644 --- a/scripts/check-support-repo-coupling.sh +++ b/scripts/check-support-repo-coupling.sh @@ -54,7 +54,7 @@ if [[ -n "${violations}" ]]; then echo "forbidden cross-repo peer imports found:" echo "${violations}" echo - echo "support engines must not import each other; Graycode is the orchestrator and shared contracts belong in eagle" + echo "support engines must not import each other; Graycode is the orchestrator and shared contracts belong in graycode's internal/contracts" exit 1 fi diff --git a/scripts/ecosystem-manifest.sh b/scripts/ecosystem-manifest.sh index b59a2e5d..167979fc 100755 --- a/scripts/ecosystem-manifest.sh +++ b/scripts/ecosystem-manifest.sh @@ -6,7 +6,7 @@ ECO_DIR="$(cd "${ROOT_DIR}/.." && pwd)" MANIFEST="${ROOT_DIR}/ecosystem.yaml" usage() { - echo "usage: $0 validate | json | list " >&2 + echo "usage: $0 validate | json | list " >&2 exit 2 } @@ -45,6 +45,7 @@ json() { BEGIN { print "{\n \"schemaVersion\": 1,\n \"repositories\": [" } { split($7, flags, ":") + if (flags[2] == "") flags[2] = "false" if (NR > 1) print "," printf " {\"directory\":%s,\"githubRepo\":%s,\"productName\":%s,\"kind\":%s,\"language\":%s,\"module\":%s,\"workspace\":%s,\"tracksEagle\":%s,\"facade\":%s}", quote($1), quote($2), quote($3), quote($4), quote($5), @@ -61,7 +62,6 @@ list_records() { selector == "all" { print $1; next } selector == "workspace" && $7 ~ /^true:/ { print $1; next } selector == "engines" && $4 == "engine" { print $1; next } - selector == "eagle-consumers" && $7 ~ /:true$/ { print $1; next } ' } @@ -129,8 +129,8 @@ validate() { fi done < <(records) - if ((count != 15)); then - echo "expected 15 repositories, found ${count}" >&2 + if ((count != 14)); then + echo "expected 14 repositories, found ${count}" >&2 failed=1 fi ((failed == 0)) || exit 1 @@ -142,7 +142,7 @@ case "${1:-}" in json) json ;; list) [[ $# -eq 2 ]] || usage - case "$2" in all|workspace|engines|eagle-consumers) list_records "$2" ;; *) usage ;; esac + case "$2" in all|workspace|engines) list_records "$2" ;; *) usage ;; esac ;; *) usage ;; esac