diff --git a/.dockerignore b/.dockerignore index da96199ca..e6a3689df 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,8 +5,8 @@ .factory Dockerfile .dockerignore -hawk_bin -hawk_test_bin +rho_bin +rho_test_bin coverage.out coverage.html go.work diff --git a/.env.example b/.env.example index 991d5b848..2a8ad1446 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ -# hawk daemon environment variables — copy to .env and fill in. -# Only HAWK_DAEMON_API_KEY is consumed from the environment. -HAWK_DAEMON_API_KEY= +# rho daemon environment variables — copy to .env and fill in. +# Only RHO_DAEMON_API_KEY is consumed from the environment. +RHO_DAEMON_API_KEY= -# The following are NOT read by hawk — setting them has no effect: -# HAWK_DAEMON_PORT / HAWK_DAEMON_HOST — daemon binds 127.0.0.1:4590; -# override with the --host / --port flags (see `hawk daemon start --help`) +# The following are NOT read by rho — setting them has no effect: +# RHO_DAEMON_PORT / RHO_DAEMON_HOST — daemon binds 127.0.0.1:4590; +# override with the --host / --port flags (see `rho daemon start --help`) # EYRIE_API_KEY / EYRIE_BASE_URL, HARRIER_API_KEY / HARRIER_ADDR — # consumed by sibling services, not by this binary # See docs/user-guide/05-configuration.md for the full precedence chain. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8dae7ac45..ec9450106 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -# CODEOWNERS for hawk +# CODEOWNERS for rho # # These owners will be the default owners for everything in the repo. Unless # a later match takes precedence, they will be requested for review when diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bd16c3cbe..bf5955dd4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -11,7 +11,7 @@ body: of the form as you can — the more we know, the faster we can fix it. Before submitting: - - Search [existing issues](https://github.com/GrayCodeAI/hawk/issues) to avoid duplicates. + - Search [existing issues](https://github.com/GrayCodeAI/rho/issues) to avoid duplicates. - If this is a security issue, please **do not** file a public issue. See `SECURITY.md`. - type: textarea @@ -19,7 +19,7 @@ body: attributes: label: What happened? description: A clear, concise description of the bug. - placeholder: When I run `hawk ...`, I expected X but got Y. + placeholder: When I run `rho ...`, I expected X but got Y. validations: required: true @@ -29,7 +29,7 @@ body: label: Steps to reproduce description: Minimal steps that reliably reproduce the problem. placeholder: | - 1. Run `hawk ...` + 1. Run `rho ...` 2. Type `...` 3. See error `...` validations: @@ -44,10 +44,10 @@ body: required: true - type: input - id: hawk-version + id: rho-version attributes: - label: hawk version - description: Output of `hawk version` (or `hawk --version`). + label: rho version + description: Output of `rho version` (or `rho --version`). placeholder: "0.1.0" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a1885141b..a6fdeb91e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: false contact_links: - name: Security vulnerability - url: https://github.com/GrayCodeAI/hawk/security/advisories/new + url: https://github.com/GrayCodeAI/rho/security/advisories/new about: Please report security issues privately via a GitHub Security Advisory. See SECURITY.md. - name: Question / discussion - url: https://github.com/GrayCodeAI/hawk/discussions + url: https://github.com/GrayCodeAI/rho/discussions about: Have a question or want to discuss an idea? Open a discussion instead of an issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 6f62dab5c..992f0f913 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,5 +1,5 @@ name: Feature request -description: Suggest an improvement or new capability for hawk. +description: Suggest an improvement or new capability for rho. title: "feat: " labels: ["enhancement", "triage"] @@ -7,13 +7,13 @@ body: - type: markdown attributes: value: | - Thanks for proposing a feature. hawk is built **for developers first** + Thanks for proposing a feature. rho is built **for developers first** (one machine, local config, keychain credentials) — teams and enterprise come later. We evaluate every request against: _"would a single developer on their own machine benefit from this?"_ Before submitting: - - Search [existing issues](https://github.com/GrayCodeAI/hawk/issues) to avoid duplicates. + - Search [existing issues](https://github.com/GrayCodeAI/rho/issues) to avoid duplicates. - For large changes, consider opening a discussion first. - type: textarea @@ -21,7 +21,7 @@ body: attributes: label: What problem are you trying to solve? description: Describe the user problem first. Solutions can come later. - placeholder: When I'm doing X, hawk makes me do Y, which is painful because Z. + placeholder: When I'm doing X, rho makes me do Y, which is painful because Z. validations: required: true @@ -29,7 +29,7 @@ body: id: proposal attributes: label: Proposed solution - description: How would you like hawk to behave? CLI flags, output, config, etc. + description: How would you like rho to behave? CLI flags, output, config, etc. validations: required: true @@ -55,7 +55,7 @@ body: id: principles attributes: label: Developer fit - description: hawk avoids enterprise scope for now. Confirm this feature respects that. + description: rho avoids enterprise scope for now. Confirm this feature respects that. options: - label: Works with zero configuration (sensible defaults). - label: Works offline / does not require a cloud account. diff --git a/.github/actions/checkout-eyrie/action.yml b/.github/actions/checkout-eyrie/action.yml index f37455399..2b1fb2c56 100644 --- a/.github/actions/checkout-eyrie/action.yml +++ b/.github/actions/checkout-eyrie/action.yml @@ -1,5 +1,5 @@ name: Checkout ecosystem -description: Clone graycode-eco sibling repos into the workspace parent for hawk's go.work +description: Clone graycode-eco sibling repos into the workspace parent for rho's go.work inputs: ref: @@ -24,7 +24,7 @@ runs: # ecosystem migration; downstream guards skip absent checkouts too). ws_parent="$(cd "${GITHUB_WORKSPACE}/.." && pwd)" while IFS= read -r repo; do - [ "$repo" = hawk ] && continue + [ "$repo" = rho ] && continue dest="${ws_parent}/${repo}" if [ -d "$dest/.git" ]; then echo "$repo already present at $dest" diff --git a/.github/actions/hawk/action.yml b/.github/actions/rho/action.yml similarity index 61% rename from .github/actions/hawk/action.yml rename to .github/actions/rho/action.yml index 42f5c8031..fbc14296f 100644 --- a/.github/actions/hawk/action.yml +++ b/.github/actions/rho/action.yml @@ -1,7 +1,7 @@ -name: Hawk +name: Rho description: >- - Run the Hawk coding agent headlessly in a GitHub workflow. Hawk is + Run the Rho coding agent headlessly in a GitHub workflow. Rho is model- and provider-agnostic: you supply the provider and API key as inputs. author: GrayCodeAI @@ -12,7 +12,7 @@ branding: inputs: prompt: - description: The instruction for Hawk to execute. Mutually exclusive with prompt-file. + description: The instruction for Rho to execute. Mutually exclusive with prompt-file. required: false default: "" prompt-file: @@ -29,20 +29,20 @@ inputs: api-key: description: >- The provider API key. Pass it from a repository or organization secret; - it is exported only for the Hawk step and never written to the log. + it is exported only for the Rho step and never written to the log. required: false default: "" api-key-env: description: >- Name of the environment variable the chosen provider reads its key from (for example OPENAI_API_KEY or ANTHROPIC_API_KEY). When set together with - api-key, that variable is exported only for the Hawk step. Leave the action + api-key, that variable is exported only for the Rho step. Leave the action provider-agnostic by passing the env name your provider expects rather than hardcoding one here. required: false default: "" model: - description: Model id to use for the run. Passed to `hawk exec --model`. + description: Model id to use for the run. Passed to `rho exec --model`. required: false default: "" auto: @@ -54,19 +54,19 @@ inputs: default: supervised agent: description: >- - Agent persona to use (from Hawk user state). Maps to `hawk exec --agent`. + Agent persona to use (from Rho user state). Maps to `rho exec --agent`. required: false default: "" cwd: description: >- - Working directory override. Maps to `hawk exec --cwd`. + Working directory override. Maps to `rho exec --cwd`. Defaults to the action's working-directory input. required: false default: "" ephemeral: description: >- When true, skip session persistence. Ideal for CI runs. - Maps to `hawk exec --ephemeral`. + Maps to `rho exec --ephemeral`. required: false default: "false" worktree: @@ -80,7 +80,7 @@ inputs: default: text post-to: description: >- - Optional destination for a run summary after Hawk finishes: `pr-comment` + Optional destination for a run summary after Rho finishes: `pr-comment` posts to the triggering pull request, `slack` posts to slack-webhook-url, or `none` (default) posts nowhere. required: false @@ -98,26 +98,26 @@ inputs: required: false default: ${{ github.token }} working-directory: - description: Directory to run Hawk in. Defaults to ${{ github.workspace }}. + description: Directory to run Rho in. Defaults to ${{ github.workspace }}. required: false default: ${{ github.workspace }} - hawk-version: + rho-version: description: >- - Hawk release version/tag to install (e.g. v1.2.3 or latest). + Rho release version/tag to install (e.g. v1.2.3 or latest). Defaults to the ref this action was resolved at. required: false default: "" - hawk-repo: - description: Repository to install Hawk from (owner/repo). + rho-repo: + description: Repository to install Rho from (owner/repo). required: false - default: GrayCodeAI/hawk + default: GrayCodeAI/rho outputs: exit-code: - description: The exit code Hawk returned (0 success, 2 usage, 3 provider, non-zero otherwise). + description: The exit code Rho returned (0 success, 2 usage, 3 provider, non-zero otherwise). value: ${{ steps.run.outputs.exit-code }} output-file: - description: Path to the captured Hawk stdout (the raw output-format stream). + description: Path to the captured Rho stdout (the raw output-format stream). value: ${{ steps.run.outputs.output-file }} summary: description: A short human-readable summary line parsed from the run, when available. @@ -126,24 +126,24 @@ outputs: runs: using: composite steps: - - name: Install Hawk + - name: Install Rho id: install shell: bash env: - HAWK_REPO: ${{ inputs.hawk-repo }} - HAWK_VERSION_INPUT: ${{ inputs.hawk-version }} + RHO_REPO: ${{ inputs.rho-repo }} + RHO_VERSION_INPUT: ${{ inputs.rho-version }} ACTION_REF: ${{ github.action_ref }} ACTION_PATH: ${{ github.action_path }} RUNNER_TEMP: ${{ runner.temp }} run: | set -euo pipefail if [ "${RUNNER_OS}" = "Windows" ]; then - echo "::error::The Hawk action currently supports Linux and macOS runners only." >&2 + echo "::error::The Rho action currently supports Linux and macOS runners only." >&2 exit 1 fi # Resolve the version to install: explicit input wins; otherwise the ref # this action was checked out at (a tag); otherwise the latest release. - version="${HAWK_VERSION_INPUT}" + version="${RHO_VERSION_INPUT}" if [ -z "${version}" ]; then version="${ACTION_REF}" fi @@ -155,46 +155,46 @@ runs: if printf '%s' "${version}" | grep -qiE '^[0-9a-f]{40}$'; then version="latest" fi - install_dir="${RUNNER_TEMP}/hawk-bin" - src_dir="${RUNNER_TEMP}/hawk-src" + install_dir="${RUNNER_TEMP}/rho-bin" + src_dir="${RUNNER_TEMP}/rho-src" mkdir -p "${install_dir}" "${src_dir}" - # Download the Hawk source archive and extract it + # Download the Rho source archive and extract it if [ "${version}" = "latest" ]; then - tarball_url=$(curl -sL "https://api.github.com/repos/${HAWK_REPO}/releases/latest" \ + tarball_url=$(curl -sL "https://api.github.com/repos/${RHO_REPO}/releases/latest" \ | grep '"tarball_url":' | sed 's/.*"\([^"]*\)".*/\1/') else - tarball_url="https://github.com/${HAWK_REPO}/archive/refs/tags/v${version}.tar.gz" + tarball_url="https://github.com/${RHO_REPO}/archive/refs/tags/v${version}.tar.gz" fi curl -sL "${tarball_url}" | tar -xz -C "${src_dir}" --strip-components=1 - # Build the Hawk binary from source (Go must be available on the runner) + # Build the Rho binary from source (Go must be available on the runner) if ! command -v go &>/dev/null; then echo "::error::Go is required but not found on the runner PATH. Add actions/setup-go before this action, or install Go manually." >&2 exit 1 fi - go build -o "${install_dir}/hawk" "${src_dir}/cmd/hawk" - chmod +x "${install_dir}/hawk" + go build -o "${install_dir}/rho" "${src_dir}/cmd/rho" + chmod +x "${install_dir}/rho" echo "${install_dir}" >> "${GITHUB_PATH}" - echo "Installed Hawk from ${HAWK_REPO}@${version}" + echo "Installed Rho from ${RHO_REPO}@${version}" - - name: Run Hawk + - name: Run Rho id: run shell: bash working-directory: ${{ inputs.working-directory }} env: # Secrets are passed via the environment and never echoed. `set +x` and # the absence of any `echo` of these values keep them out of the log. - HAWK_INPUT_API_KEY: ${{ inputs.api-key }} - HAWK_INPUT_API_KEY_ENV: ${{ inputs.api-key-env }} - HAWK_INPUT_PROVIDER: ${{ inputs.provider }} - HAWK_INPUT_PROMPT: ${{ inputs.prompt }} - HAWK_INPUT_PROMPT_FILE: ${{ inputs.prompt-file }} - HAWK_INPUT_MODEL: ${{ inputs.model }} - HAWK_INPUT_AUTO: ${{ inputs.auto }} - HAWK_INPUT_AGENT: ${{ inputs.agent }} - HAWK_INPUT_CWD: ${{ inputs.cwd }} - HAWK_INPUT_EPHEMERAL: ${{ inputs.ephemeral }} - HAWK_INPUT_WORKTREE: ${{ inputs.worktree }} - HAWK_INPUT_OUTPUT_FORMAT: ${{ inputs.output-format }} + RHO_INPUT_API_KEY: ${{ inputs.api-key }} + RHO_INPUT_API_KEY_ENV: ${{ inputs.api-key-env }} + RHO_INPUT_PROVIDER: ${{ inputs.provider }} + RHO_INPUT_PROMPT: ${{ inputs.prompt }} + RHO_INPUT_PROMPT_FILE: ${{ inputs.prompt-file }} + RHO_INPUT_MODEL: ${{ inputs.model }} + RHO_INPUT_AUTO: ${{ inputs.auto }} + RHO_INPUT_AGENT: ${{ inputs.agent }} + RHO_INPUT_CWD: ${{ inputs.cwd }} + RHO_INPUT_EPHEMERAL: ${{ inputs.ephemeral }} + RHO_INPUT_WORKTREE: ${{ inputs.worktree }} + RHO_INPUT_OUTPUT_FORMAT: ${{ inputs.output-format }} ACTION_PATH: ${{ github.action_path }} RUNNER_TEMP: ${{ runner.temp }} run: | @@ -203,50 +203,50 @@ runs: # Export the provider key under the env name the provider expects. This # keeps the action provider-agnostic: no provider->env mapping is baked # in. The value is never printed. - if [ -n "${HAWK_INPUT_API_KEY}" ] && [ -n "${HAWK_INPUT_API_KEY_ENV}" ]; then - if ! printf '%s' "${HAWK_INPUT_API_KEY_ENV}" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then + if [ -n "${RHO_INPUT_API_KEY}" ] && [ -n "${RHO_INPUT_API_KEY_ENV}" ]; then + if ! printf '%s' "${RHO_INPUT_API_KEY_ENV}" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then echo "::error::api-key-env must be a valid environment variable name." >&2 exit 2 fi - export "${HAWK_INPUT_API_KEY_ENV}=${HAWK_INPUT_API_KEY}" + export "${RHO_INPUT_API_KEY_ENV}=${RHO_INPUT_API_KEY}" fi - if [ -n "${HAWK_INPUT_PROVIDER}" ]; then - export HAWK_PROVIDER="${HAWK_INPUT_PROVIDER}" + if [ -n "${RHO_INPUT_PROVIDER}" ]; then + export RHO_PROVIDER="${RHO_INPUT_PROVIDER}" fi args=(exec) - if [ -n "${HAWK_INPUT_PROMPT_FILE}" ] && [ -n "${HAWK_INPUT_PROMPT}" ]; then + if [ -n "${RHO_INPUT_PROMPT_FILE}" ] && [ -n "${RHO_INPUT_PROMPT}" ]; then echo "::error::The 'prompt' and 'prompt-file' inputs are mutually exclusive." >&2 exit 2 fi - if [ -n "${HAWK_INPUT_PROMPT_FILE}" ]; then - args+=(--file "${HAWK_INPUT_PROMPT_FILE}") - elif [ -n "${HAWK_INPUT_PROMPT}" ]; then - args+=("${HAWK_INPUT_PROMPT}") + if [ -n "${RHO_INPUT_PROMPT_FILE}" ]; then + args+=(--file "${RHO_INPUT_PROMPT_FILE}") + elif [ -n "${RHO_INPUT_PROMPT}" ]; then + args+=("${RHO_INPUT_PROMPT}") else echo "::error::Provide either the 'prompt' or 'prompt-file' input." >&2 exit 2 fi - [ -n "${HAWK_INPUT_MODEL}" ] && args+=(--model "${HAWK_INPUT_MODEL}") - [ -n "${HAWK_INPUT_AUTO}" ] && args+=(--auto "${HAWK_INPUT_AUTO}") - [ -n "${HAWK_INPUT_AGENT}" ] && args+=(--agent "${HAWK_INPUT_AGENT}") - [ -n "${HAWK_INPUT_CWD}" ] && args+=(--cwd "${HAWK_INPUT_CWD}") - if [ "${HAWK_INPUT_EPHEMERAL}" = "true" ]; then + [ -n "${RHO_INPUT_MODEL}" ] && args+=(--model "${RHO_INPUT_MODEL}") + [ -n "${RHO_INPUT_AUTO}" ] && args+=(--auto "${RHO_INPUT_AUTO}") + [ -n "${RHO_INPUT_AGENT}" ] && args+=(--agent "${RHO_INPUT_AGENT}") + [ -n "${RHO_INPUT_CWD}" ] && args+=(--cwd "${RHO_INPUT_CWD}") + if [ "${RHO_INPUT_EPHEMERAL}" = "true" ]; then args+=(--ephemeral) fi - if [ "${HAWK_INPUT_WORKTREE}" = "true" ]; then + if [ "${RHO_INPUT_WORKTREE}" = "true" ]; then args+=(--worktree) fi - [ -n "${HAWK_INPUT_OUTPUT_FORMAT}" ] && args+=(--output-format "${HAWK_INPUT_OUTPUT_FORMAT}") + [ -n "${RHO_INPUT_OUTPUT_FORMAT}" ] && args+=(--output-format "${RHO_INPUT_OUTPUT_FORMAT}") - output_file="${RUNNER_TEMP}/hawk-output.txt" + output_file="${RUNNER_TEMP}/rho-output.txt" - # Run Hawk. Capture stdout to a file AND echo it to the log. Surface the + # Run Rho. Capture stdout to a file AND echo it to the log. Surface the # real exit code as the step's status. set +e - hawk "${args[@]}" | tee "${output_file}" + rho "${args[@]}" | tee "${output_file}" code=${PIPESTATUS[0]} set -e @@ -255,14 +255,14 @@ runs: # Best-effort one-line summary if [ "${code}" = "0" ]; then - summary="Hawk run succeeded" + summary="Rho run succeeded" else - summary="Hawk run failed (exit ${code})" + summary="Rho run failed (exit ${code})" fi { - echo "summary<> "${GITHUB_OUTPUT}" exit "${code}" @@ -273,13 +273,13 @@ runs: env: GH_TOKEN: ${{ inputs.github-token }} PR_NUMBER: ${{ github.event.pull_request.number }} - HAWK_EXIT: ${{ steps.run.outputs.exit-code }} - HAWK_SUMMARY: ${{ steps.run.outputs.summary }} + RHO_EXIT: ${{ steps.run.outputs.exit-code }} + RHO_SUMMARY: ${{ steps.run.outputs.summary }} run: | set -euo pipefail status="succeeded" - [ "${HAWK_EXIT}" != "0" ] && status="failed (exit ${HAWK_EXIT})" - body="$(printf 'Hawk run %s.\n\n%s' "${status}" "${HAWK_SUMMARY}")" + [ "${RHO_EXIT}" != "0" ] && status="failed (exit ${RHO_EXIT})" + body="$(printf 'Rho run %s.\n\n%s' "${status}" "${RHO_SUMMARY}")" gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ -f body="${body}" >/dev/null @@ -288,14 +288,14 @@ runs: shell: bash env: SLACK_WEBHOOK_URL: ${{ inputs.slack-webhook-url }} - HAWK_EXIT: ${{ steps.run.outputs.exit-code }} - HAWK_SUMMARY: ${{ steps.run.outputs.summary }} + RHO_EXIT: ${{ steps.run.outputs.exit-code }} + RHO_SUMMARY: ${{ steps.run.outputs.summary }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail status="succeeded" - [ "${HAWK_EXIT}" != "0" ] && status="failed (exit ${HAWK_EXIT})" - text="Hawk run ${status} for ${GITHUB_REPOSITORY}@${GITHUB_REF_NAME}. ${HAWK_SUMMARY} ${RUN_URL}" + [ "${RHO_EXIT}" != "0" ] && status="failed (exit ${RHO_EXIT})" + text="Rho run ${status} for ${GITHUB_REPOSITORY}@${GITHUB_REF_NAME}. ${RHO_SUMMARY} ${RUN_URL}" payload="$(jq -n --arg text "${text}" '{text: $text}')" curl --fail --silent --show-error -X POST -H 'Content-Type: application/json' \ -d "${payload}" "${SLACK_WEBHOOK_URL}" >/dev/null || \ diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 805b02191..53a187601 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -28,7 +28,7 @@ runs: ws_parent="$(cd "${GITHUB_WORKSPACE}/.." && pwd)" mkdir -p "$ws_parent" while IFS= read -r repo; do - [ "$repo" = hawk ] && continue + [ "$repo" = rho ] && continue dest="$ws_parent/$repo" if [ -d "$dest/.git" ]; then echo "$repo already present at $dest" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4be2623e..513ee1926 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: run: bash ./scripts/check-eyrie-engine-boundary.sh # ------------------------------------------------------------------------- - # 2. Module hygiene — tidy, verify Hawk plus the sibling Go modules via go.work. + # 2. Module hygiene — tidy, verify Rho plus the sibling Go modules via go.work. # ------------------------------------------------------------------------- module: name: module hygiene @@ -109,7 +109,7 @@ jobs: # go mod tidy can mis-resolve workspace modules here; go work sync is # the supported workspace hygiene step. go work sync - go build -mod=readonly -o /dev/null ./cmd/hawk + go build -mod=readonly -o /dev/null ./cmd/rho if ! git diff --quiet -- go.mod go.sum go.work go.work.sum; then echo "::error::go.mod / go.sum / go.work files out of date — run 'go work sync' locally and commit" git diff -- go.mod go.sum go.work go.work.sum @@ -121,7 +121,7 @@ jobs: run: | workspace_file="${GITHUB_WORKSPACE}/../go.work" while IFS= read -r module; do - [ "$module" = hawk ] && continue + [ "$module" = rho ] && continue if ! grep -q "./${module}" "$workspace_file"; then echo "::error::go.work must include ./${module}." cat "$workspace_file" @@ -158,7 +158,7 @@ jobs: run: | go mod download go mod verify - go build -mod=readonly ./cmd/hawk + go build -mod=readonly ./cmd/rho go test ./... -count=1 -timeout=300s -skip='TestDefaultSkillDirsCrossAgent|TestCopySelectionE2E' release-parity: @@ -196,8 +196,8 @@ jobs: cache: true - name: go vet run: go vet ./... - - name: hawk harness evaluation audit - run: go run ./cmd/hawk harness --out-dir .hawk/harness + - name: rho harness evaluation audit + run: go run ./cmd/rho harness --out-dir .rho/harness - name: support repo coupling guard run: bash ./scripts/check-support-repo-coupling.sh @@ -232,7 +232,7 @@ jobs: runs-on: ubuntu-latest # NOTE: depends only on `format`, deliberately NOT on `vet`. # A `vet` (or upstream) failure must never skip the test job — that - # historically masked real test regressions (see hawk PR #155, where + # historically masked real test regressions (see rho PR #155, where # DrainAlerts code shipped with failing tests because `test` was skipped # when `vet` failed on the trailer-strip force-push). Tests must always # run so regressions surface instead of being hidden. @@ -296,7 +296,7 @@ jobs: echo "" echo "Policy: Every t.Skip() should have a comment linking to a GitHub issue." echo "Example:" - echo " // TODO: https://github.com/GrayCodeAI/hawk/issues/123" + echo " // TODO: https://github.com/GrayCodeAI/rho/issues/123" echo " t.Skip(\"not yet implemented\")" fi @@ -495,7 +495,7 @@ jobs: run: | # Use -ldflags="-s -w" to match release build size (Makefile LDFLAGS). # This catches binary bloat regressions against the actual shipped artifact size. - size=$(go build -trimpath -ldflags="-s -w" -o /tmp/hawk-bin ./cmd/hawk && wc -c < /tmp/hawk-bin) + size=$(go build -trimpath -ldflags="-s -w" -o /tmp/rho-bin ./cmd/rho && wc -c < /tmp/rho-bin) size_mb=$((size / 1024 / 1024)) echo "Binary size: ${size_mb}MB" # Threshold history: 110MB → 80MB (binary was ~76MB) → 98MB. @@ -509,7 +509,7 @@ jobs: if [ "$size_mb" -gt 98 ]; then echo "::warning::Binary size ${size_mb}MB exceeds 98MB threshold" fi - rm -f /tmp/hawk-bin + rm -f /tmp/rho-bin # ------------------------------------------------------------------------- # Fuzz — short corpus runs to catch panics in fuzz targets. @@ -531,15 +531,18 @@ jobs: cache: true - name: Run fuzz targets run: | - go test -fuzz=FuzzScanForAIComments -fuzztime=60s ./cmd/... || true - go test -fuzz=FuzzValidateSettings -fuzztime=60s ./internal/config/... || true - go test -fuzz=FuzzIsSuspicious -fuzztime=60s ./internal/tool/... || true - go test -fuzz=FuzzIsSafeGitCommit -fuzztime=60s ./internal/tool/... || true - go test -fuzz=FuzzParseMessage -fuzztime=60s ./internal/session/... || true - go test -fuzz=FuzzParseSessionMeta -fuzztime=60s ./internal/session/... || true + # Each target must name a single package: `-fuzz` rejects `./x/...` + # when it expands to more than one package, which previously made the + # cmd target fail setup silently under `|| true`. + go test -run='^$' -fuzz=FuzzScanForAIComments -fuzztime=60s ./cmd + go test -run='^$' -fuzz=FuzzValidateSettings -fuzztime=60s ./internal/config + go test -run='^$' -fuzz=FuzzIsSuspicious -fuzztime=60s ./internal/tool + go test -run='^$' -fuzz=FuzzIsSafeGitCommit -fuzztime=60s ./internal/tool + go test -run='^$' -fuzz=FuzzParseMessage -fuzztime=60s ./internal/session + go test -run='^$' -fuzz=FuzzParseSessionMeta -fuzztime=60s ./internal/session # ------------------------------------------------------------------------- - # 10. Smoke — build hawk and verify ecosystem CLI wiring. + # 10. Smoke — build rho and verify ecosystem CLI wiring. # ------------------------------------------------------------------------- smoke: name: smoke @@ -556,5 +559,5 @@ jobs: with: go-version: ${{ env.GO_VERSION }} cache: true - - name: smoke-hawk.sh - run: ./scripts/smoke-hawk.sh + - name: smoke-rho.sh + run: ./scripts/smoke-rho.sh diff --git a/.github/workflows/daemon-image.yml b/.github/workflows/daemon-image.yml index 71fdf0c60..541988637 100644 --- a/.github/workflows/daemon-image.yml +++ b/.github/workflows/daemon-image.yml @@ -8,7 +8,7 @@ on: branches: [main] paths: - "Dockerfile.daemon" - - "packaging/systemd/hawk-daemon.service" + - "packaging/systemd/rho-daemon.service" - "internal/**" - "cmd/**" - "go.mod" @@ -21,7 +21,7 @@ permissions: env: REGISTRY: ghcr.io - IMAGE_NAME: graycodeai/hawk-daemon + IMAGE_NAME: graycodeai/rho-daemon jobs: build: @@ -51,8 +51,8 @@ jobs: push: false load: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - cache-from: type=gha,scope=hawk-daemon - cache-to: type=gha,mode=max,scope=hawk-daemon + cache-from: type=gha,scope=rho-daemon + cache-to: type=gha,mode=max,scope=rho-daemon build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} @@ -106,8 +106,8 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha,scope=hawk-daemon - cache-to: type=gha,mode=max,scope=hawk-daemon + cache-from: type=gha,scope=rho-daemon + cache-to: type=gha,mode=max,scope=rho-daemon build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a413c1f82..6beb49faa 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,7 +19,7 @@ permissions: env: REGISTRY: ghcr.io - IMAGE_NAME: graycodeai/hawk + IMAGE_NAME: graycodeai/rho jobs: # Build each platform natively on its own runner (arm64 natively via the @@ -57,8 +57,8 @@ jobs: push: false load: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - cache-from: type=gha,scope=hawk-amd64 - cache-to: type=gha,mode=max,scope=hawk-amd64 + cache-from: type=gha,scope=rho-amd64 + cache-to: type=gha,mode=max,scope=rho-amd64 build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} @@ -96,8 +96,8 @@ jobs: platforms: linux/amd64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:amd64-${{ github.sha }} - cache-from: type=gha,scope=hawk-amd64 - cache-to: type=gha,mode=max,scope=hawk-amd64 + cache-from: type=gha,scope=rho-amd64 + cache-to: type=gha,mode=max,scope=rho-amd64 build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} @@ -140,8 +140,8 @@ jobs: push: false load: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - cache-from: type=gha,scope=hawk-arm64 - cache-to: type=gha,mode=max,scope=hawk-arm64 + cache-from: type=gha,scope=rho-arm64 + cache-to: type=gha,mode=max,scope=rho-arm64 build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} @@ -179,8 +179,8 @@ jobs: platforms: linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:arm64-${{ github.sha }} - cache-from: type=gha,scope=hawk-arm64 - cache-to: type=gha,mode=max,scope=hawk-arm64 + cache-from: type=gha,scope=rho-arm64 + cache-to: type=gha,mode=max,scope=rho-arm64 build-args: | VERSION=${{ github.ref_name }} COMMIT=${{ github.sha }} diff --git a/.gitignore b/.gitignore index 314e07a1b..b45cf612d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ # Binaries -/hawk +/rho # Ignore build output in bin/, but track committed source scripts like bin/buf. bin/* !bin/buf -hawk_bin +rho_bin *.exe *.dll *.so @@ -12,7 +12,7 @@ hawk_bin .claude/ .codegraph/ .commandcode/ -.hawk/ +.rho/ .harrier/ .openclaude/ .pi/ @@ -45,8 +45,8 @@ go.work.sum # Lefthook-generated git hook wrappers .githooks/ __pycache__/ -hawk_bin +rho_bin # Logs *.log -hawk-sec105b.log +rho-sec105b.log diff --git a/.golangci.yml b/.golangci.yml index 9358dd360..2f7923bbb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,7 +24,7 @@ linters: # production code with this set). Deliberately excluded floods: # G104 — unhandled errors, already covered by errcheck # G301/G306 — 0755/0644 perms are intentional for user-visible files - # G304 — hawk reads user-supplied file paths by design (CLI agent) + # G304 — rho reads user-supplied file paths by design (CLI agent) includes: - G101 # hardcoded credentials - G102 # bind to all interfaces diff --git a/.goreleaser.yml b/.goreleaser.yml index 0afb1d9fb..0a1251674 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,17 +2,17 @@ # Source of truth: .shared-templates/.goreleaser.yml.tmpl # # Placeholders rendered per repo: -# hawk — short repo name (e.g. hawk, harrier, swift) +# rho — short repo name (e.g. rho, harrier, swift) # ./ — main package path (e.g. ./ or ./cmd/harrier) # AI coding agent — reads, writes, and runs code in your terminal — short single-line description for brew/nfpms # main — Go package path holding Version vars -# (e.g. main or github.com/GrayCodeAI/hawk/internal/version) +# (e.g. main or github.com/GrayCodeAI/rho/internal/version) # # Repos with PRO/special features (e.g. swift's macOS notarization, shrike's # nfpms) extend this template with extra sections instead of replacing it. version: 2 -project_name: hawk +project_name: rho # --------------------------------------------------------------------------- # Pre-build hooks — keep go.mod tidy and verified. @@ -27,9 +27,9 @@ before: # rather than the build host's clock. # --------------------------------------------------------------------------- builds: - - id: hawk - main: ./cmd/hawk - binary: hawk + - id: rho + main: ./cmd/rho + binary: rho env: - CGO_ENABLED=0 goos: @@ -121,7 +121,7 @@ changelog: # --------------------------------------------------------------------------- # Homebrew — publishes a formula to GrayCodeAI/homebrew-tap so users can -# `brew install graycodeai/tap/hawk`. Requires HOMEBREW_TAP_TOKEN or +# `brew install graycodeai/tap/rho`. Requires HOMEBREW_TAP_TOKEN or # TAP_GITHUB_TOKEN in the release workflow (both already wired there). # --------------------------------------------------------------------------- brews: @@ -130,13 +130,13 @@ brews: name: homebrew-tap token: "{{ .Env.HOMEBREW_TAP_TOKEN }}" directory: Formula - homepage: "https://github.com/GrayCodeAI/hawk" + homepage: "https://github.com/GrayCodeAI/rho" description: "AI coding agent — reads, writes, and runs code in your terminal" license: "MIT" install: | - bin.install "hawk" + bin.install "rho" test: | - system "#{bin}/hawk", "--version" + system "#{bin}/rho", "--version" # --------------------------------------------------------------------------- # Release — auto-detect prereleases (rc/beta tags). Created on the repo @@ -158,9 +158,9 @@ release: prerelease: auto name_template: "v{{ .Version }}" header: | - ## hawk v{{ .Version }} + ## rho v{{ .Version }} AI coding agent — reads, writes, and runs code in your terminal footer: | - **Full changelog:** https://github.com/GrayCodeAI/hawk/compare/{{ .PreviousTag }}...{{ .Tag }} + **Full changelog:** https://github.com/GrayCodeAI/rho/compare/{{ .PreviousTag }}...{{ .Tag }} diff --git a/.shared-templates/.goreleaser.yml.tmpl b/.shared-templates/.goreleaser.yml.tmpl index 9edda1bb9..fc9db117f 100644 --- a/.shared-templates/.goreleaser.yml.tmpl +++ b/.shared-templates/.goreleaser.yml.tmpl @@ -3,11 +3,11 @@ # # Placeholders rendered per repo (ALL-CAPS, distinct from goreleaser's own # `{{.Version}}`-style Go template fields, which are left as-is below): -# {{NAME}} — short repo name (e.g. hawk, harrier, swift) +# {{NAME}} — short repo name (e.g. rho, harrier, swift) # {{MAIN_PKG}} — main package path (e.g. ./ or ./cmd/harrier) # {{DESCRIPTION}} — short single-line description for brew/nfpms # {{VERSION_PKG}} — Go package path holding Version vars -# (e.g. main or github.com/GrayCodeAI/hawk/internal/version) +# (e.g. main or github.com/GrayCodeAI/rho/internal/version) # # Repos with PRO/special features (e.g. swift's macOS notarization, shrike's # nfpms) extend this template with extra sections instead of replacing it. diff --git a/.shared-templates/Makefile.binary.tmpl b/.shared-templates/Makefile.binary.tmpl index f9403f077..e9b055e55 100644 --- a/.shared-templates/Makefile.binary.tmpl +++ b/.shared-templates/Makefile.binary.tmpl @@ -2,13 +2,13 @@ # Source of truth: .shared-templates/Makefile.binary.tmpl at the eco root. # Placeholders rendered per repo: {{NAME}}, {{MAIN_PKG}}. # -# hawk is currently the only Go binary repo in the ecosystem, and its real -# Makefile extends this core with hawk-specific targets (workspace `setup`, +# rho is currently the only Go binary repo in the ecosystem, and its real +# Makefile extends this core with rho-specific targets (workspace `setup`, # `compat-test`/`compat-check`, `sync-submodules`/`sync-external`, # `build-all`/`build-static`/`size-check`, and split `*-guard` boundary # checks instead of the single generic `boundaries` below). Treat this file # as the floor every Go binary repo starts from, not a literal copy of -# hawk/Makefile. +# rho/Makefile. # --------------------------------------------------------------------------- # Project metadata @@ -18,7 +18,7 @@ MAIN_PKG := {{MAIN_PKG}} # --------------------------------------------------------------------------- # Versioning — sourced from VERSION file; falls back to git describe. -# See https://github.com/GrayCodeAI/hawk/blob/main/docs/versioning.md. +# See https://github.com/GrayCodeAI/rho/blob/main/docs/versioning.md. # --------------------------------------------------------------------------- VERSION ?= $(shell v=$$(cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]'); if [ -n "$$v" ]; then echo "$$v"; else git describe --tags --always --dirty 2>/dev/null || echo "dev"; fi) COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") @@ -138,5 +138,5 @@ hooks: ## Install git hooks via lefthook (formatting, linting, conventional comm # --------------------------------------------------------------------------- # Repo-specific extensions go below this line — workspace setup, submodule # sync, cross-platform build matrices, compatibility-matrix checks, etc. -# See hawk/Makefile for the real, extended reference implementation. +# See rho/Makefile for the real, extended reference implementation. # --------------------------------------------------------------------------- diff --git a/.shared-templates/Makefile.library.tmpl b/.shared-templates/Makefile.library.tmpl index 3ed3921a1..d2b16e25a 100644 --- a/.shared-templates/Makefile.library.tmpl +++ b/.shared-templates/Makefile.library.tmpl @@ -9,7 +9,7 @@ NAME := {{NAME}} # --------------------------------------------------------------------------- # Versioning — sourced from VERSION file; falls back to git describe. -# See https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md. +# See https://github.com/GrayCodeAI/rho/blob/main/VERSIONING.md. # --------------------------------------------------------------------------- VERSION ?= $(shell v=$$(cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]'); if [ -n "$$v" ]; then echo "$$v"; else git describe --tags --always --dirty 2>/dev/null || echo "dev"; fi) COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") diff --git a/.shared-templates/Makefile.python.tmpl b/.shared-templates/Makefile.python.tmpl index 76e029d4d..03cad0870 100644 --- a/.shared-templates/Makefile.python.tmpl +++ b/.shared-templates/Makefile.python.tmpl @@ -63,7 +63,7 @@ fmt: ## Format with ruff. vet: ## Type-check with mypy. $(PYTHON) -m mypy src/ -boundary-guard: ## Fail if the SDK references support engines or Hawk private packages. +boundary-guard: ## Fail if the SDK references support engines or Rho private packages. bash ./scripts/check-consumer-boundaries.sh lint: ## Lint with ruff. diff --git a/.shared-templates/README.md b/.shared-templates/README.md index 1a3617a95..bcfb91eb4 100644 --- a/.shared-templates/README.md +++ b/.shared-templates/README.md @@ -7,17 +7,17 @@ Every graycode-ecosystem repo's `Makefile`, `lefthook.yml`, and # Source of truth: .shared-templates/Makefile.library.tmpl at the eco root. ``` -This directory is that source of truth. It lives here, in `hawk`, because -`hawk` is the one repo every consumer already depends on or references — +This directory is that source of truth. It lives here, in `rho`, because +`rho` is the one repo every consumer already depends on or references — there is no separate monorepo at the workspace root to hold it. -**This directory is not built or run by hawk itself.** It is a template +**This directory is not built or run by rho itself.** It is a template library that other graycode-ecosystem repos copy from and diff against. ## Layout - `Makefile.library.tmpl` — Go library repos (engines, SDKs, foundation repos) -- `Makefile.binary.tmpl` — Go binary repos (currently only `hawk`) +- `Makefile.binary.tmpl` — Go binary repos (currently only `rho`) - `Makefile.python.tmpl` — Python repos (`robin`) - `lefthook.yml.tmpl` — git hooks config, identical across all Go repos - `.goreleaser.yml.tmpl` — goreleaser config for Go binary repos @@ -28,7 +28,7 @@ library that other graycode-ecosystem repos copy from and diff against. - `workflows/python-release.yml.tmpl` — PyPI publish workflow (Trusted Publishing) - `workflows/compatibility-test.yml.tmpl` — cross-repo compatibility matrix check (see `docs/compatibility.md`) - `scripts/check-ecosystem-boundaries.sh.tmpl` — the import-boundary guard, parameterized per repo role -- `scripts/sync-external.sh` — read-only drift report for `hawk`'s `external/` submodule pins (hawk-specific, not templated elsewhere) +- `scripts/sync-external.sh` — read-only drift report for `rho`'s `external/` submodule pins (rho-specific, not templated elsewhere) - `docs/coverage-matrix.md` — per-repo test coverage thresholds enforced in CI, kept in one place so they don't silently drift out of sync with each repo's `go-ci.yml` ## How repos use this @@ -37,9 +37,9 @@ There is currently no rendering tool — repos copy a template, replace the placeholders marked `{{LIKE_THIS}}`, and keep the "Source of truth" header comment pointing back here. When you change a template, the repos that copied it are now stale; update them in the same PR or file a follow-up -per repo. `hawk`'s own `Makefile`/`lefthook.yml`/CI predate this directory +per repo. `rho`'s own `Makefile`/`lefthook.yml`/CI predate this directory and intentionally diverge in a few binary-specific ways (see -`Makefile.binary.tmpl`, which documents the real deltas against `hawk`'s +`Makefile.binary.tmpl`, which documents the real deltas against `rho`'s Makefile). ## Boundary rule diff --git a/.shared-templates/docs/coverage-matrix.md b/.shared-templates/docs/coverage-matrix.md index 445ce6ac5..0b8e5c6cf 100644 --- a/.shared-templates/docs/coverage-matrix.md +++ b/.shared-templates/docs/coverage-matrix.md @@ -8,7 +8,7 @@ repo's threshold, update both the CI file and this table in the same PR.** | Repo | Threshold | Mechanism | |---|---|---| -| `hawk` | 65% | inline `bc` check in `ci.yml` | +| `rho` | 65% | inline `bc` check in `ci.yml` | | `eyrie` | 60% | inline `bc` check in `ci.yml` | | `graycode-skills` | n/a | no Go/Python test suite (skill/content registry) | diff --git a/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl b/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl index 0fe3c5f63..890991e51 100644 --- a/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl +++ b/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl @@ -14,27 +14,27 @@ cd "$ROOT_DIR" # ============================================================================= # VARIANT 1 — Support engine (eyrie, harrier, shrike, swift, kestrel, merlin). # Engines are peers: they may depend on falcon, -# but never on hawk/internal/* or another engine. +# but never on rho/internal/* or another engine. # ============================================================================= -FORBIDDEN_HAWK='github\.com/GrayCodeAI/hawk/internal/' +FORBIDDEN_RHO='github\.com/GrayCodeAI/rho/internal/' FORBIDDEN_ENGINES='github\.com/GrayCodeAI/(harrier|shrike|swift|kestrel|merlin)(/|")' # ^ list every OTHER engine here — never include yourself. exit_code=0 if command -v rg >/dev/null 2>&1; then - violations="$(rg -n "$FORBIDDEN_HAWK" --glob '*.go' . || true)" + violations="$(rg -n "$FORBIDDEN_RHO" --glob '*.go' . || true)" engine_violations="$(rg -n "$FORBIDDEN_ENGINES" --glob '*.go' . || true)" else - violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_HAWK" . || true)" + violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_RHO" . || true)" engine_violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_ENGINES" . || true)" fi if [[ -n "${violations}" ]]; then - echo "forbidden Hawk imports found:" + echo "forbidden Rho imports found:" echo "${violations}" echo - echo "support repos must use local contracts, not hawk/internal" + echo "support repos must use local contracts, not rho/internal" exit_code=1 fi @@ -70,19 +70,19 @@ echo "ecosystem boundary guard passed" # echo "forbidden graycode-eco imports found in {{OWN_MODULE}}:" # echo "${violations}" # echo -# echo "{{OWN_MODULE}} is a foundation repo — it must not depend on hawk, engines, or any other GrayCodeAI/* package" +# echo "{{OWN_MODULE}} is a foundation repo — it must not depend on rho, engines, or any other GrayCodeAI/* package" # exit 1 # fi # # echo "ecosystem boundary guard passed" # ============================================================================= -# VARIANT 3 — SDK / skills consumer (graycode-skills). Consumers may depend on hawk's public surfaces -# only — never on a support engine directly, and never on hawk/internal. +# VARIANT 3 — SDK / skills consumer (graycode-skills). Consumers may depend on rho's public surfaces +# only — never on a support engine directly, and never on rho/internal. # ============================================================================= # # FORBIDDEN_ENGINES='github\.com/GrayCodeAI/(eyrie|harrier|shrike|swift|kestrel|merlin)(/|")' -# FORBIDDEN_INTERNAL='github\.com/GrayCodeAI/hawk/internal' +# FORBIDDEN_INTERNAL='github\.com/GrayCodeAI/rho/internal' # # (same rg/grep + report pattern as Variant 1, naming "SDKs/skills must go -# through hawk's public API, not engines directly" as the violation message) +# through rho's public API, not engines directly" as the violation message) diff --git a/.shared-templates/scripts/sync-external.sh b/.shared-templates/scripts/sync-external.sh index 8f6fc20c6..a911365bf 100755 --- a/.shared-templates/scripts/sync-external.sh +++ b/.shared-templates/scripts/sync-external.sh @@ -5,7 +5,7 @@ # the submodule checkout), this makes no changes — it only reports. # # Typical drift: you commit changes in ../shrike, but forget `make -# sync-submodules` + a commit in hawk to bump the external/tok pin. This +# sync-submodules` + a commit in rho to bump the external/tok pin. This # script catches that before it becomes a stale-dependency surprise in CI. set -euo pipefail @@ -62,7 +62,7 @@ done < <(git config -f .gitmodules --get-regexp path | awk '{print $2}') if [[ $exit_code -ne 0 ]]; then echo - echo "drift detected — run 'make sync-submodules' in hawk after confirming the sibling repos are what you expect, then commit the updated external/ pins" + echo "drift detected — run 'make sync-submodules' in rho after confirming the sibling repos are what you expect, then commit the updated external/ pins" fi exit $exit_code diff --git a/.shared-templates/workflows/compatibility-test.yml.tmpl b/.shared-templates/workflows/compatibility-test.yml.tmpl index 5182755c5..d53d0abbd 100644 --- a/.shared-templates/workflows/compatibility-test.yml.tmpl +++ b/.shared-templates/workflows/compatibility-test.yml.tmpl @@ -1,7 +1,7 @@ -# Cross-repo compatibility matrix check, referenced from hawk/docs/compatibility.md. +# Cross-repo compatibility matrix check, referenced from rho/docs/compatibility.md. # Source of truth: .shared-templates/workflows/compatibility-test.yml.tmpl # -# Lives (and runs) in hawk only — hawk owns testdata/compatibility-matrix.json +# Lives (and runs) in rho only — rho owns testdata/compatibility-matrix.json # and the `compat-test`/`compat-check` Make targets that validate it. Other # repos don't need a copy of this workflow; they're read as data, not as a # workflow trigger. diff --git a/.shared-templates/workflows/go-release.yml.tmpl b/.shared-templates/workflows/go-release.yml.tmpl index cd043fec7..3a5d4d5ca 100644 --- a/.shared-templates/workflows/go-release.yml.tmpl +++ b/.shared-templates/workflows/go-release.yml.tmpl @@ -23,7 +23,7 @@ jobs: fetch-depth: 0 # goreleaser needs full history for changelog # Only needed if this repo has local workspace dependencies on other - # graycode-eco repos at build time (hawk itself clones eyrie this way via + # graycode-eco repos at build time (rho itself clones eyrie this way via # ./.github/actions/checkout-eyrie). Omit for repos with none. - name: Set up Go diff --git a/.shared-templates/workflows/python-ci.yml.tmpl b/.shared-templates/workflows/python-ci.yml.tmpl index 8a62864a1..ead5a693b 100644 --- a/.shared-templates/workflows/python-ci.yml.tmpl +++ b/.shared-templates/workflows/python-ci.yml.tmpl @@ -2,7 +2,7 @@ # Source of truth: .shared-templates/workflows/python-ci.yml.tmpl # # Placeholders rendered per repo: -# {{PACKAGE}} — importable package name for --cov (e.g. hawk) +# {{PACKAGE}} — importable package name for --cov (e.g. rho) # {{THRESHOLD}} — minimum coverage percentage, kept in sync with # docs/coverage-matrix.md diff --git a/.trivyignore b/.trivyignore index 8dca84a98..8eb51b575 100644 --- a/.trivyignore +++ b/.trivyignore @@ -1,10 +1,10 @@ -# Trivy OS-package ignore list for Hawk Docker images. +# Trivy OS-package ignore list for Rho Docker images. # # CVE-2026-14456 — OpenSSL DoS via unbounded memory (libcrypto3/libssl3). # Fixed upstream in OpenSSL 3.5.8-r0, but that package is NOT yet published in # the Alpine 3.23 repository (the latest alpine:3.23 still ships 3.5.7-r0, as -# of 2026-08-27). Hawk is a Go binary and does not link libcrypto; this affects -# only the base OS TLS stack and is not reachable from Hawk's runtime. Re-add a +# of 2026-08-27). Rho is a Go binary and does not link libcrypto; this affects +# only the base OS TLS stack and is not reachable from Rho's runtime. Re-add a # base-image bump to remove this entry once Alpine 3.23 publishes OpenSSL # 3.5.8-r0. CVE-2026-14456 diff --git a/AGENTS.md b/AGENTS.md index 1ba0998d9..b8ad7f194 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,15 +1,15 @@ --- -description: Extending hawk — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins. +description: Extending rho — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins. globs: "*.go, *.js, *.md, *.json, *.toml, *.yaml, *.yml" alwaysApply: false --- -# Extending hawk +# Extending rho -hawk is an open-source code intelligence platform. It lives in the `graycode-eco` +rho is an open-source code intelligence platform. It lives in the `graycode-eco` workspace alongside the ecosystem repos that power it (`eyrie`, `shrike`, `harrier`, `swift`, `kestrel`, `merlin`). This document describes how to extend -hawk with custom tools, skills, hooks, and integrations. +rho with custom tools, skills, hooks, and integrations. ## Development workflow @@ -17,7 +17,7 @@ When starting any new work (feature, fix, refactor, chore), always create a feat ## 1. Drop a project `AGENTS.md` -When hawk starts in a directory, it looks for project-level instructions and injects them into the system prompt. The lookup walks from your current working directory **up to the nearest git root** and reads the first matching file at each level — general rules at the repo root, more specific rules in sub-trees. Files are labeled with their directory in the prompt (e.g. `## Project guidelines (services/api/AGENTS.md)`). +When rho starts in a directory, it looks for project-level instructions and injects them into the system prompt. The lookup walks from your current working directory **up to the nearest git root** and reads the first matching file at each level — general rules at the repo root, more specific rules in sub-trees. Files are labeled with their directory in the prompt (e.g. `## Project guidelines (services/api/AGENTS.md)`). Accepted file names, in priority order at each level: @@ -29,7 +29,7 @@ Accepted file names, in priority order at each level: Matching is **case-insensitive** on the basename, so `AGENTS.md`, `Agents.md`, and `agents.md` resolve to the same file on Windows and macOS. The git-tracked filename in this repo is `AGENTS.md` — keep that on case-sensitive filesystems (Linux, the WSL filesystem, or a CI runner) to match what the loader looks for. -Both files use the same format. YAML frontmatter is optional; the markdown body is loaded as instructions for the agent. hawk reads the file once at session start, so changes take effect on the next launch — not mid-session. +Both files use the same format. YAML frontmatter is optional; the markdown body is loaded as instructions for the agent. rho reads the file once at session start, so changes take effect on the next launch — not mid-session. ```markdown # Project conventions for @@ -42,26 +42,26 @@ Both files use the same format. YAML frontmatter is optional; the markdown body Tips: -- Keep each file under ~8 KiB. hawk caps the **total** across all matched files at 32 KiB; everything past the cap is dropped. +- Keep each file under ~8 KiB. rho caps the **total** across all matched files at 32 KiB; everything past the cap is dropped. - Re-state rules in the imperative voice: "Run `make lint`", not "you should consider running the linter". - Don't put secrets, model IDs, or environment-specific paths in `AGENTS.md`. Use config files for those. -- In a monorepo, drop a narrower `AGENTS.md` in each sub-tree (e.g. `services/api/AGENTS.md`). hawk picks those up automatically when you launch from inside the sub-tree. +- In a monorepo, drop a narrower `AGENTS.md` in each sub-tree (e.g. `services/api/AGENTS.md`). rho picks those up automatically when you launch from inside the sub-tree. - A YAML frontmatter block (`---\n...\n---`) at the top is preserved verbatim in the injected prompt but is not parsed for `globs:` or `alwaysApply:` scoping today — keep the body self-contained. ### Personal guidelines, across every project -For preferences that follow *you*, not a specific repo (tone, tooling habits, workflow), drop a `ZERO.md` in your user config directory: `~/.hawk/ZERO.md` on Linux/macOS, `%AppData%\hawk\ZERO.md` on Windows — the same directory as config files and your personal specialists. Same format and 8 KiB cap as the project files above, and the same case-insensitive basename match. +For preferences that follow *you*, not a specific repo (tone, tooling habits, workflow), drop a `ZERO.md` in your user config directory: `~/.rho/ZERO.md` on Linux/macOS, `%AppData%\rho\ZERO.md` on Windows — the same directory as config files and your personal specialists. Same format and 8 KiB cap as the project files above, and the same case-insensitive basename match. This file is injected as its own `## User guidelines` section, before the project's `AGENTS.md`/`ZERO.md`, and is labeled as personal preference in the prompt: project guidelines are the later, more specific instruction and take precedence over it when the two conflict. ## 2. Custom specialists -Specialists are hawk's sub-agents. Three scopes, in priority order: +Specialists are rho's sub-agents. Three scopes, in priority order: | Scope | Path | Shared? | | --- | --- | --- | -| Built-in | compiled into hawk | yes | -| User | `~/.hawk/specialists/*.md` | no — your machine only | +| Built-in | compiled into rho | yes | +| User | `~/.rho/specialists/*.md` | no — your machine only | | Project | `./.zero/specialists/*.md` | yes — the repo team | Project overrides user overrides built-in when names collide. @@ -89,33 +89,33 @@ Reply with one JSON object per finding: `{"file", "line", "severity", "message", CLI management: ```bash -hawk specialist list -hawk specialist show api-reviewer -hawk specialist create api-reviewer \ +rho specialist list +rho specialist show api-reviewer +rho specialist create api-reviewer \ --project \ --description "Reviews API changes" \ --tools read-only,plan \ --prompt "$(cat api-reviewer.md)" -hawk specialist edit api-reviewer --project -hawk specialist delete api-reviewer --project -hawk specialist path # prints the resolved specialists directory +rho specialist edit api-reviewer --project +rho specialist delete api-reviewer --project +rho specialist path # prints the resolved specialists directory ``` ## 3. Skills -hawk ships **no bundled skills** by default. Skills are markdown instruction +rho ships **no bundled skills** by default. Skills are markdown instruction files that extend agent capabilities, sourced from the separate `GrayCodeAI/graycode-skills` repo and installed on demand: ```bash -hawk skills search # find skills in graycode-skills -hawk skills install [skill-name] # install after user approval -hawk skills list # list installed skills -hawk skills remove +rho skills search # find skills in graycode-skills +rho skills install [skill-name] # install after user approval +rho skills list # list installed skills +rho skills remove ``` Installed skills live in user or project scope: -- User-scoped: `~/.hawk/skills/` +- User-scoped: `~/.rho/skills/` - Project-scoped: `./.zero/skills/` or `./skills/` A skill manifest: @@ -144,56 +144,56 @@ Hooks allow custom commands to run at specific lifecycle points: - `sessionEnd` — runs at session teardown ```bash -hawk hook add beforeReview --command "lint-check" -hawk hook remove beforeReview -hawk hook list +rho hook add beforeReview --command "lint-check" +rho hook remove beforeReview +rho hook list ``` ## 5. MCP integration -MCP (Model Context Protocol) servers can expose tools to hawk: +MCP (Model Context Protocol) servers can expose tools to rho: ```bash -hawk mcp add --name server --url http://localhost:8080 -hawk mcp remove server -hawk mcp list +rho mcp add --name server --url http://localhost:8080 +rho mcp remove server +rho mcp list ``` ## 6. Plugins -Plugins extend hawk with custom tools and capabilities: +Plugins extend rho with custom tools and capabilities: ```bash -hawk plugin add --name my-plugin --path ./my-plugin -hawk plugin remove my-plugin -hawk plugin list +rho plugin add --name my-plugin --path ./my-plugin +rho plugin remove my-plugin +rho plugin list ``` ## 7. Verification -hawk includes a self-verification system to validate local changes before contributing: +rho includes a self-verification system to validate local changes before contributing: ```bash -hawk verify -hawk verify --fix +rho verify +rho verify --fix ``` ## Development ```bash make lint -hawk verify +rho verify ``` ### Architecture note: cross-repo contracts -Cross-repo severity and finding contracts now live in hawk's `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) — extensions and support repos must vendor the needed DTOs instead of Hawk internals until a published contracts module exists. +Cross-repo severity and finding contracts now live in rho's `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) — extensions and support repos must vendor the needed DTOs instead of Rho internals until a published contracts module exists. ### Architecture note: provider ownership Implement provider protocols, adapters, catalog metadata, credential mappings, and -provider contract tests in `../eyrie` (the eyrie engine's repo) first. Hawk consumes providers only -through Eyrie's stable engine facade; Hawk changes should be limited to host UX +provider contract tests in `../eyrie` (the eyrie engine's repo) first. Rho consumes providers only +through Eyrie's stable engine facade; Rho changes should be limited to host UX and facade integration. Concentrate AI is a pay-as-you-go gateway implemented with its native Responses API (`/v1/responses`) under the `concentrate-payg` deployment. @@ -201,7 +201,7 @@ with its native Responses API (`/v1/responses`) under the ## GitNexus — Code Intelligence -This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **rho** (97743 symbols, 322940 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). @@ -225,10 +225,10 @@ This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relations | Resource | Use for | |----------|---------| -| `gitnexus://repo/hawk/context` | Codebase overview, check index freshness | -| `gitnexus://repo/hawk/clusters` | All functional areas | -| `gitnexus://repo/hawk/processes` | All execution flows | -| `gitnexus://repo/hawk/process/{name}` | Step-by-step execution trace | +| `gitnexus://repo/rho/context` | Codebase overview, check index freshness | +| `gitnexus://repo/rho/clusters` | All functional areas | +| `gitnexus://repo/rho/processes` | All execution flows | +| `gitnexus://repo/rho/process/{name}` | Step-by-step execution trace | ## CLI @@ -245,10 +245,10 @@ This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relations ### Workspace workflow (sibling repos) -hawk depends on ecosystem repos (`eyrie`, etc.) as independent sibling repos in the `graycode-eco` workspace. Hawk's `go.work` lists them as `../`, so local changes in any sibling are automatically picked up by hawk. Each sibling is its own git repo, versioned and released independently. +rho depends on ecosystem repos (`eyrie`, etc.) as independent sibling repos in the `graycode-eco` workspace. Rho's `go.work` lists them as `../`, so local changes in any sibling are automatically picked up by rho. Each sibling is its own git repo, versioned and released independently. -1. Edit + test in `../` — run its tests, run `make test` in hawk +1. Edit + test in `../` — run its tests, run `make test` in rho 2. Push from the sibling: `git push origin ` 3. Open a PR in the sibling repo → merge to `main` -4. Ensure hawk's `go.mod` pins a version that resolves to (or is an ancestor of) the sibling's `main` — run `make sync` to verify parity -5. No pointer commits: hawk resolves the sibling via `go.work` for local dev and via the pinned `go.mod` version for standalone/module-mode builds (Docker, released consumers) +4. Ensure rho's `go.mod` pins a version that resolves to (or is an ancestor of) the sibling's `main` — run `make sync` to verify parity +5. No pointer commits: rho resolves the sibling via `go.work` for local dev and via the pinned `go.mod` version for standalone/module-mode builds (Docker, released consumers) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c25e3101..12cb6cf97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,15 +21,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Docs truth and housekeeping**: SECURITY.md/CONTRIBUTING.md now describe the actual Go toolchain (golangci-lint, go vet, govulncheck) instead of the polyglot template's ruff/mypy/pip-audit/pnpm-lock language, CONTRIBUTING documents `make setup`/`boundaries`/`test-10x`/`smoke`, and the planning docs (`SPEC_DRIVEN_PLAN.md`, `SPEC_DRIVEN_PHASE2_PLAN.md`, `internal/engine/REFACTOR_PLAN.md`) moved to `docs/plans/`. ### Removed -- **BREAKING — `hawk credentials migrate` removed**: the subcommand, its man page entry, and the `MigrateEnvFileCredentials` wrappers are gone, and `hawk path` no longer reports legacy `~/.hawk/env` / `~/.hawk/.env` files. Save keys through `/config`. +- **BREAKING — `rho credentials migrate` removed**: the subcommand, its man page entry, and the `MigrateEnvFileCredentials` wrappers are gone, and `rho path` no longer reports legacy `~/.rho/env` / `~/.rho/.env` files. Save keys through `/config`. - **BREAKING — settings model/provider migration removed**: `LoadSettings` no longer moves `model`/`provider` from `settings.json` into Eyrie's `provider.json`, and `SetActiveSelection` is deleted. Stale `model`/`provider` values left in `settings.json` are now ignored on load (Eyrie's selection wins; `--settings` overrides still apply); select the model in `/config`. -- **BREAKING — startup provider-secrets migration removed**: print/REPL/watch/TUI startup no longer calls `MigrateProviderSecrets`, and the method is dropped from the gateway `CatalogMaintenance` interface. `hawk path` still fails when `provider.json` holds secrets; remove those fields manually. -- **BREAKING — `shared/types` guards removed**: `scripts/check-shared-types-imports.sh`, the `contracts-guard` make target, its lefthook and CI steps, and the matching testaudit checks are deleted because the package no longer exists. The ecosystem boundary guards still block `hawk/internal` imports. +- **BREAKING — startup provider-secrets migration removed**: print/REPL/watch/TUI startup no longer calls `MigrateProviderSecrets`, and the method is dropped from the gateway `CatalogMaintenance` interface. `rho path` still fails when `provider.json` holds secrets; remove those fields manually. +- **BREAKING — `shared/types` guards removed**: `scripts/check-shared-types-imports.sh`, the `contracts-guard` make target, its lefthook and CI steps, and the matching testaudit checks are deleted because the package no longer exists. The ecosystem boundary guards still block `rho/internal` imports. ## [0.2.0] — 2026-07-13 ### Changed -- **Hawk/Eyrie production boundary completed**: Hawk owns the product face, +- **Rho/Eyrie production boundary completed**: Rho owns the product face, sessions, tools, permissions, and public schemas while Eyrie v0.2.1 owns credentials, catalog resolution, provider transport, resilience, and usage telemetry behind the stable `eyrie/engine` facade. @@ -46,18 +46,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Permission system unified into two independent axes**: the old `PermissionMode` (`default`/`acceptEdits`/`bypassPermissions`/`dontAsk`/`plan`) is removed. `/autonomy` now controls the 5-tier trust ladder (`Always Ask`/`Scout`/`Builder`/`Operator`/`Autonomous`, bare `/autonomy` opens a picker), and `/spec` controls an independent, orthogonal spec-driven workflow gate (`Specify → Plan → Tasks → ApproveImplementation`, bare `/spec` opens a picker) that blocks Write/Edit/Bash regardless of trust tier — including at Autonomous. Fixes a real bug where the old Plan Mode's write-block could be silently bypassed at high autonomy tiers, since tier and mode were checked independently with no ordering guarantee. - **Fixed `PermissionService.SetAutonomy`/`Autonomy()`**: previously wrote to/read from a shadow field the permission engine's `CheckTool` never consulted, meaning autonomy tier changes may not have reliably taken effect. Now both read/write the same `PermissionEngine.Autonomy` field the check logic uses. - **`--permission-mode` CLI flag removed**; `--dangerously-skip-permissions` unchanged (now maps to the Autonomous tier). New `--dry-run` flag added as an unconditional kill switch (deny every tool call, regardless of tier or spec stage) — replaces `dontAsk`'s hard-lockout role. -- **Version re-baselined to `0.1.0`** across `cmd/hawk/main.go`, `cmd/daemon.go`, - `flake.nix`, `.github/workflows/release.yml`, and the `update`/daemon test suites, aligning hawk +- **Version re-baselined to `0.1.0`** across `cmd/rho/main.go`, `cmd/daemon.go`, + `flake.nix`, `.github/workflows/release.yml`, and the `update`/daemon test suites, aligning rho with the rest of the GrayCodeAI ecosystem (`eyrie`, `shrike`, `harrier`, `kestrel`, `merlin`). -- **Architecture boundary hardening**: Hawk now owns runtime request/response DTOs, transport config/provider seams, and review/verification product-boundary contracts, with `eyrie/client` usage restricted to internal adapters and guarded in CI. -- **`shared/types` removed**: Hawk no longer ships the old shared type path, and local boundary checks now block any attempt to reintroduce it. +- **Architecture boundary hardening**: Rho now owns runtime request/response DTOs, transport config/provider seams, and review/verification product-boundary contracts, with `eyrie/client` usage restricted to internal adapters and guarded in CI. +- **`shared/types` removed**: Rho no longer ships the old shared type path, and local boundary checks now block any attempt to reintroduce it. ### Added -- **Spec-driven workflow (`/spec`)**: independent, orthogonal permission gate that walks the model through `Specify → Plan → Tasks`, writing real `spec.md`/`plan.md`/`tasks.md` files to `.hawk/specs//`, and requires explicit `ApproveImplementation` approval (always prompts, at any trust tier) before Write/Edit/Bash unlock. The approval prompt shows the actual written content, not a blind yes/no. +- **Spec-driven workflow (`/spec`)**: independent, orthogonal permission gate that walks the model through `Specify → Plan → Tasks`, writing real `spec.md`/`plan.md`/`tasks.md` files to `.rho/specs//`, and requires explicit `ApproveImplementation` approval (always prompts, at any trust tier) before Write/Edit/Bash unlock. The approval prompt shows the actual written content, not a blind yes/no. - **`/autonomy` and `/spec` picker overlays**: bare `/autonomy` or `/spec` opens an arrow-key-navigable, filterable picker (Esc/Enter) instead of requiring subcommand syntax; typed subcommands (`/autonomy tier scout`, `/spec status`, etc.) still work. - **Watch mode (`--watch`)**: file-watcher loop that acts on `AI!` (do-now) and `AI?` (answer) code comments. Off by default. -- **GitHub Action** (`.github/actions/hawk`): interactive mode on `@hawk` mentions, automation mode on labeled issues/PRs, and skill dispatch for `/`-prefixed prompts. -- **Messaging gateways**: opt-in Telegram, Discord, and Slack gateways on the daemon for chatting with hawk from messaging apps. +- **GitHub Action** (`.github/actions/rho`): interactive mode on `@rho` mentions, automation mode on labeled issues/PRs, and skill dispatch for `/`-prefixed prompts. +- **Messaging gateways**: opt-in Telegram, Discord, and Slack gateways on the daemon for chatting with rho from messaging apps. - **AST repo-map** (`internal/context/repomap`): structural repository map for richer model context. - **Auto codebase analysis on first run** (`internal/autoinit`): opt-in seeding of project context. - **Auto-lint / auto-fix cycle**: runs the matching linter after edits and iterates on fixes with bounded retries (opt-in). @@ -79,7 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added — Round 2 ecosystem improvements (2026-06-01) - **Cavecrew personas** (`internal/multiagent/agents`): three new - built-in personas built into GrayCode Hawk + built-in personas built into GrayCode Rho (`cavecrew-investigator`, `cavecrew-builder`, `cavecrew-reviewer`). Each enforces a strict output format so downstream agents can parse outputs mechanically: @@ -118,7 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and a command label. API: `Record`, `AggregateForSession`, `ListForSession`, `PruneForSession`. Scoped to a single session so callers can selectively compact their own history. Companion to - shrike's `internal/tracking.Tracker` (shrike tracks globally, hawk tracks + shrike's `internal/tracking.Tracker` (shrike tracks globally, rho tracks per-session). ### Added — Production Hardening (top-50 OSS parity) @@ -153,16 +153,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.4.0] — 2026-05-05 ### Added -- **Exec Subcommand**: `hawk exec "prompt"` — full engine non-interactive mode with `--output-format json`, `--auto` autonomy levels, `--worktree` isolation, `--agent` personas, `--session-id` resume, stdin piping -- **Daemon Server**: `hawk daemon start/stop/status` — background HTTP server with JSON + SSE streaming on `/v1/chat`, `/v1/health`, `/v1/sessions` -- **Mission Mode**: `hawk mission "prompt"` — multi-agent orchestration decomposing work into parallel features executed in isolated git worktrees. `--dry-run` for planning only -- **Session Search**: `hawk search "query"` — full-text search across all saved sessions with `--json` output -- **Custom Agents**: `hawk agent list/create/show/remove` — markdown persona definitions in `~/.hawk/agents/` with YAML frontmatter (name, description, model) -- **Snapshot System**: Shadow git tracking of every file change. `hawk snapshot list/restore/diff` + `/snapshot` slash command. Auto-snapshots on every Write/Edit tool call +- **Exec Subcommand**: `rho exec "prompt"` — full engine non-interactive mode with `--output-format json`, `--auto` autonomy levels, `--worktree` isolation, `--agent` personas, `--session-id` resume, stdin piping +- **Daemon Server**: `rho daemon start/stop/status` — background HTTP server with JSON + SSE streaming on `/v1/chat`, `/v1/health`, `/v1/sessions` +- **Mission Mode**: `rho mission "prompt"` — multi-agent orchestration decomposing work into parallel features executed in isolated git worktrees. `--dry-run` for planning only +- **Session Search**: `rho search "query"` — full-text search across all saved sessions with `--json` output +- **Custom Agents**: `rho agent list/create/show/remove` — markdown persona definitions in `~/.rho/agents/` with YAML frontmatter (name, description, model) +- **Snapshot System**: Shadow git tracking of every file change. `rho snapshot list/restore/diff` + `/snapshot` slash command. Auto-snapshots on every Write/Edit tool call - **Waza Workflows**: `/think` (plan before code), `/hunt` (root-cause diagnosis), `/check` (pre-ship review with auto-fix), `/design` (screenshot-driven UI iteration) - **Structured Compaction**: Summary template with Goal/Constraints/Progress/Files/Decisions/Errors/Instructions/Next sections for better intent preservation - **Doom Loop Detection**: Lowered threshold to 3 (from 4). Two-tier escalation: first detection injects redirect prompt, doom loop hard-stops with "ask user for help" -- **Session Persistence for exec**: All exec runs saved to `~/.hawk/sessions/` and searchable via `hawk search` +- **Session Persistence for exec**: All exec runs saved to `~/.rho/sessions/` and searchable via `rho search` ### Packages Added - `mission/` — Multi-agent orchestration with worktree-based parallel workers diff --git a/CLAUDE.md b/CLAUDE.md index 4bd51dd5d..bd05494f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **rho** (97743 symbols, 322940 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > Index stale? Run `node .gitnexus/run.cjs analyze` from the project root — it auto-selects an available runner. No `.gitnexus/run.cjs` yet? `npx gitnexus analyze` (npm 11 crash → `npm i -g gitnexus`; #1939). @@ -25,10 +25,10 @@ This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relations | Resource | Use for | |----------|---------| -| `gitnexus://repo/hawk/context` | Codebase overview, check index freshness | -| `gitnexus://repo/hawk/clusters` | All functional areas | -| `gitnexus://repo/hawk/processes` | All execution flows | -| `gitnexus://repo/hawk/process/{name}` | Step-by-step execution trace | +| `gitnexus://repo/rho/context` | Codebase overview, check index freshness | +| `gitnexus://repo/rho/clusters` | All functional areas | +| `gitnexus://repo/rho/processes` | All execution flows | +| `gitnexus://repo/rho/process/{name}` | Step-by-step execution trace | ## CLI diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index bc0e9e3a4..e4bb52a8b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,7 +2,7 @@ ## Our pledge -We — the maintainers and contributors of the hawk project — pledge to +We — the maintainers and contributors of the rho project — pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, @@ -45,7 +45,7 @@ offensive, or harmful. Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported to the maintainers via the contact in `SECURITY.md` or by opening a confidential GitHub Security Advisory at -. All +. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c1db48e7..1fecc3482 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ -# Contributing to hawk +# Contributing to rho Thanks for your interest! This guide covers the conventions used across the GrayCodeAI repositories. The shared standards (versioning, release tooling, repo layout) -are defined in . +are defined in . ## Quick start @@ -139,8 +139,8 @@ Before requesting review: ## Reporting bugs -Open an issue using the bug-report template. Include the `hawk` -version (`hawk --version` for binaries, `hawk.Version` for +Open an issue using the bug-report template. Include the `rho` +version (`rho --version` for binaries, `rho.Version` for libraries — see this repo's `VERSION` file), reproduction steps, expected behaviour, and actual behaviour. diff --git a/Dockerfile b/Dockerfile index 3df4d1e11..7c66f9dd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ ENV GOPROXY=file:///build/third_party/modproxy,https://proxy.golang.org,direct # Build-time provenance (passed by .github/workflows/docker.yml or `docker build # --build-arg VERSION=... --build-arg COMMIT=... --build-arg BUILD_DATE=...`). # Default to "dev"/"none"/"unknown" so plain `docker build .` still produces a -# runnable image — matching the cmd/hawk/main.go ldflags fallbacks. +# runnable image — matching the cmd/rho/main.go ldflags fallbacks. ARG VERSION=dev ARG COMMIT=none ARG BUILD_DATE=unknown @@ -46,20 +46,20 @@ RUN --mount=type=cache,target=/go/pkg/mod \ -X main.Version=${VERSION} \ -X main.Commit=${COMMIT} \ -X main.BuildDate=${BUILD_DATE}" \ - -o hawk ./cmd/hawk + -o rho ./cmd/rho -# Runtime stage — Alpine (hawk requires git + bash for workspace operations; distroless excluded) +# Runtime stage — Alpine (rho requires git + bash for workspace operations; distroless excluded) FROM alpine:3.24@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b RUN apk upgrade --no-cache && \ apk add --no-cache ca-certificates git bash tini && \ - adduser -D -u 1000 -h /home/hawk hawk + adduser -D -u 1000 -h /home/rho rho -COPY --from=builder /build/hawk /usr/local/bin/hawk +COPY --from=builder /build/rho /usr/local/bin/rho COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo -USER hawk +USER rho WORKDIR /workspace -ENTRYPOINT ["tini", "--", "hawk"] +ENTRYPOINT ["tini", "--", "rho"] CMD ["--help"] diff --git a/Dockerfile.daemon b/Dockerfile.daemon index 4a8a7a7e2..087f5ad4d 100644 --- a/Dockerfile.daemon +++ b/Dockerfile.daemon @@ -1,9 +1,9 @@ -# Dockerfile for the Hawk daemon (background HTTP server). +# Dockerfile for the Rho daemon (background HTTP server). # The binary is identical to the CLI image — this Dockerfile just sets the # daemon as the default entrypoint and exposes the daemon port. # -# Build: docker build -f Dockerfile.daemon -t hawk-daemon . -# Run: docker run -p 4590:4590 -e HAWK_DAEMON_API_KEY=... hawk-daemon +# Build: docker build -f Dockerfile.daemon -t rho-daemon . +# Run: docker run -p 4590:4590 -e RHO_DAEMON_API_KEY=... rho-daemon FROM golang:1.26.6-alpine@sha256:af8d6740070b8906d12eae1c3e3ea0957fb63f492051ea05e354c38ef9fe88df AS builder RUN apk upgrade --no-cache && \ @@ -28,23 +28,23 @@ RUN --mount=type=cache,target=/go/pkg/mod \ -X main.Version=${VERSION} \ -X main.Commit=${COMMIT} \ -X main.BuildDate=${BUILD_DATE}" \ - -o hawk ./cmd/hawk + -o rho ./cmd/rho FROM alpine:3.24@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b RUN apk upgrade --no-cache && \ apk add --no-cache ca-certificates git bash curl tini && \ - adduser -D -u 1000 -h /home/hawk hawk + adduser -D -u 1000 -h /home/rho rho # Create state directory for daemon logs, API key, and audit log. -RUN mkdir -p /home/hawk/.hawk/state && \ - chown -R hawk:hawk /home/hawk/.hawk +RUN mkdir -p /home/rho/.rho/state && \ + chown -R rho:rho /home/rho/.rho -COPY --from=builder /build/hawk /usr/local/bin/hawk +COPY --from=builder /build/rho /usr/local/bin/rho COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo -COPY packaging/systemd/hawk-daemon.service /etc/systemd/system/hawk-daemon.service +COPY packaging/systemd/rho-daemon.service /etc/systemd/system/rho-daemon.service -USER hawk +USER rho WORKDIR /workspace EXPOSE 4590 @@ -53,5 +53,5 @@ EXPOSE 4590 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD curl -ksf https://127.0.0.1:4590/v1/health || curl -sf http://127.0.0.1:4590/v1/health || exit 1 -ENTRYPOINT ["tini", "--", "hawk", "daemon", "start"] +ENTRYPOINT ["tini", "--", "rho", "daemon", "start"] CMD ["--host", "0.0.0.0", "--port", "4590"] diff --git a/Makefile b/Makefile index b3426bd27..3d0553de2 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ # Canonical GrayCodeAI Makefile for Go binary repos. # Source of truth: .shared-templates/Makefile.binary.tmpl at the eco root. -# Placeholders rendered per repo: hawk, .. +# Placeholders rendered per repo: rho, .. # --------------------------------------------------------------------------- # Project metadata # --------------------------------------------------------------------------- -NAME := hawk -MAIN_PKG := ./cmd/hawk +NAME := rho +MAIN_PKG := ./cmd/rho # --------------------------------------------------------------------------- # Versioning — sourced from VERSION file; falls back to git describe. -# See https://github.com/GrayCodeAI/hawk/blob/main/docs/versioning.md. +# See https://github.com/GrayCodeAI/rho/blob/main/docs/versioning.md. # --------------------------------------------------------------------------- VERSION ?= $(shell v=$$(cat VERSION 2>/dev/null | head -n1 | tr -d '[:space:]'); if [ -n "$$v" ]; then echo "$$v"; else git describe --tags --always --dirty 2>/dev/null || echo "dev"; fi) COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") @@ -73,7 +73,7 @@ test-10x: ## Run tests 10 times to surface flakes. go test ./... -race -count=10 -timeout=600s test-new: ## Run only the Round 2 ecosystem packages (fast iteration). - go test -race -count=1 -timeout=60s ./internal/safewrite/... ./internal/jsonc/... ./internal/providers/... ./internal/session/... ./internal/permissions/... + go test -race -count=1 -timeout=60s ./internal/safewrite/... ./internal/session/... ./internal/permissions/... test-live: ## Run opt-in live integration tests (requires real LLM credentials). @echo "Running live integration tests — requires OPENCODEGO_API_KEY" @@ -86,7 +86,7 @@ cover: ## Generate a coverage report (coverage.out + coverage.html). @echo "Coverage report: coverage.html" cover-new: ## Coverage report for Round 2 ecosystem packages only. - go test -cover -timeout=30s ./internal/safewrite/... ./internal/jsonc/... ./internal/providers/... ./internal/session/... ./internal/permissions/... + go test -cover -timeout=30s ./internal/safewrite/... ./internal/session/... ./internal/permissions/... api-docs: ## Generate HTML API reference from OpenAPI spec. @command -v redoc-cli >/dev/null 2>&1 || (echo "install: npm install -g redoc-cli" && exit 1) @@ -109,10 +109,16 @@ fmt: ## Format source files (gofumpt + goimports). @git ls-files -- '*.go' | xargs $(GOFUMPT) -w @git ls-files -- '*.go' | xargs $(GOIMPORTS) -w +fmt-check: ## Verify formatting without rewriting files (CI-safe). + @command -v $(GOFUMPT) >/dev/null 2>&1 || (echo "install: go install mvdan.cc/gofumpt@latest" && exit 1) + @command -v $(GOIMPORTS) >/dev/null 2>&1 || (echo "install: go install golang.org/x/tools/cmd/goimports@latest" && exit 1) + @out=$$(git ls-files -- '*.go' | xargs $(GOFUMPT) -l); if [ -n "$$out" ]; then echo "gofumpt found unformatted files:"; echo "$$out"; exit 1; fi + @out=$$(git ls-files -- '*.go' | xargs $(GOIMPORTS) -l); if [ -n "$$out" ]; then echo "goimports found unformatted files:"; echo "$$out"; exit 1; fi + vet: ## Run go vet. go vet ./... -ecosystem-guard: ## Fail if external ecosystem repos import hawk/internal. +ecosystem-guard: ## Fail if external ecosystem repos import rho/internal. bash ./scripts/check-ecosystem-boundaries.sh eyrie-client-guard: ## Fail on any production eyrie/client import. @@ -121,10 +127,10 @@ eyrie-client-guard: ## Fail on any production eyrie/client import. eyrie-engine-guard: ## Require all production Eyrie imports to use the stable engine facade. bash ./scripts/check-eyrie-engine-boundary.sh -peer-guard: ## Fail if support engines import each other instead of depending only on Hawk contracts. +peer-guard: ## Fail if support engines import each other instead of depending only on Rho contracts. bash ./scripts/check-support-repo-coupling.sh -internal-layers-guard: ## Enforce one-way dependencies across stable Hawk internal layers. +internal-layers-guard: ## Enforce one-way dependencies across stable Rho internal layers. bash ./scripts/check-internal-layer-imports.sh package-boundaries-guard: ## Enforce AST/package-graph boundaries with file/line diagnostics. @@ -154,11 +160,11 @@ tidy: ## Sync workspace modules and verify checksums. # --------------------------------------------------------------------------- # Composite gate used by CI and pre-push. # --------------------------------------------------------------------------- -ci: tidy fmt vet boundaries lint test-race security api-validate ## Run everything CI runs. +ci: tidy fmt-check vet boundaries lint test-race security api-validate ## Run everything CI runs. @echo "All CI checks passed." smoke: ## Quick build + doctor + ecosystem verification. - ./scripts/smoke-hawk.sh + ./scripts/smoke-rho.sh path: ## Verify developer path (setup, security, milestone tests). ./scripts/verify-developer-path.sh @@ -182,7 +188,7 @@ workspace: ## Regenerate the ecosystem root go.work from ecosystem.yaml. @bash ./scripts/generate-workspace.sh setup: workspace ## Set up local development environment and development tools. - @echo "=== Setting up hawk development environment ===" + @echo "=== Setting up rho development environment ===" @echo "✓ go.work generated and synced from ecosystem.yaml" @echo "" @echo "=== Environment check ===" @@ -208,7 +214,7 @@ help: ## Show this help. @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' # --------------------------------------------------------------------------- -# Compatibility matrix (hawk-specific extension to the canonical template). +# Compatibility matrix (rho-specific extension to the canonical template). # Validates compatibility-matrix.json and reports the resolved versions for # a chosen matrix entry. Wired into the compatibility-test workflow. # --------------------------------------------------------------------------- @@ -220,7 +226,7 @@ compat-test: ## Validate testdata/compatibility-matrix.json and report the 'next compat-check: ## Strict validation — non-zero exit if any component lacks a version. @go run ./cmd/compat-test -matrix=next -strict -file=testdata/compatibility-matrix.json -compat-drift: ## Advisory: report pin drift between Hawk and sibling repositories. Never fails. +compat-drift: ## Advisory: report pin drift between Rho and sibling repositories. Never fails. @go run ./cmd/compat-test -check-external -file=testdata/compatibility-matrix.json .PHONY: hooks sync diff --git a/README.md b/README.md index 21ce133d6..dab384b49 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@

Go License - CI - Release - GoDoc + CI + Release + GoDoc

@@ -25,13 +25,13 @@ --- -## Why hawk +## Why rho -hawk is an AI-powered coding agent that lives in your terminal. It reads your codebase, writes and edits files, runs tests, and manages git — all through natural language. Unlike IDE-bound tools, hawk works over SSH and on any machine with a shell. +rho is an AI-powered coding agent that lives in your terminal. It reads your codebase, writes and edits files, runs tests, and manages git — all through natural language. Unlike IDE-bound tools, rho works over SSH and on any machine with a shell. -**Developer path:** one machine, keychain credentials, local memory. Run `hawk path` to check readiness. +**Developer path:** one machine, keychain credentials, local memory. Run `rho path` to check readiness. -- **Model-agnostic** — supports many first-class providers through [eyrie](https://github.com/GrayCodeAI/eyrie) (the exact count is dynamic — see `hawk --help`), including Anthropic, OpenAI, Gemini, Fireworks AI, Concentrate AI (pay-as-you-go), DeepSeek, and Ollama +- **Model-agnostic** — supports many first-class providers through [eyrie](https://github.com/GrayCodeAI/eyrie) (the exact count is dynamic — see `rho --help`), including Anthropic, OpenAI, Gemini, Fireworks AI, Concentrate AI (pay-as-you-go), DeepSeek, and Ollama - **Zero CGO** — single static binary, cross-compiled for linux/darwin/windows on amd64/arm64 - **Privacy-first** — your code never leaves your machine except to the LLM API you choose - **Runs anywhere** — host execution on any machine with a shell, including over SSH @@ -39,58 +39,58 @@ hawk is an AI-powered coding agent that lives in your terminal. It reads your co ## Status -**Hawk is in active development.** Contributor source builds are the primary path today while we keep hardening the product in the open. Tagged releases and install assets may exist for validation, but they are not the recommended first path yet. +**Rho is in active development.** Contributor source builds are the primary path today while we keep hardening the product in the open. Tagged releases and install assets may exist for validation, but they are not the recommended first path yet. -Follow [GrayCode](https://github.com/GrayCodeAI) for progress. When Hawk is ready to try, we will announce it on [graycodeai.com](https://graycodeai.com/changelog). +Follow [GrayCode](https://github.com/GrayCodeAI) for progress. When Rho is ready to try, we will announce it on [graycodeai.com](https://graycodeai.com/changelog). ## Install (60 seconds) -Pick one — all install the same `hawk` binary (versioned into `~/.hawk/bin`, symlinked as `hawk`): +Pick one — all install the same `rho` binary (versioned into `~/.rho/bin`, symlinked as `rho`): ```bash # 1. Script (any shell, verifies checksum; cosign signature when available) -curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/hawk/main/install.sh | sh +curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/rho/main/install.sh | sh # 2. Homebrew (macOS / Linuxbrew) — after the next tagged release -brew install graycodeai/tap/hawk +brew install graycodeai/tap/rho # 3. npm (wraps the same release binaries) -npm install -g @graycodeai/hawk +npm install -g @graycodeai/rho ``` -If `~/.hawk/bin` is not on your `PATH`, add it to your shell profile. +If `~/.rho/bin` is not on your `PATH`, add it to your shell profile. Then: ```bash -hawk # interactive REPL (/config on first run: API key + model) -hawk path # verify readiness +rho # interactive REPL (/config on first run: API key + model) +rho path # verify readiness ``` ## Quick Start (contributors — from source) ```bash -git clone https://github.com/GrayCodeAI/hawk && cd hawk +git clone https://github.com/GrayCodeAI/rho && cd rho make setup # generates go.work referencing sibling support repos in the graycode-eco workspace -go build -o hawk ./cmd/hawk -./hawk +go build -o rho ./cmd/rho +./rho # First run — paste API key in /config (stored in macOS Keychain / Linux keyring) # Verify readiness -./hawk path +./rho path ``` -**No Docker or container runtime is required.** Hawk executes agent commands -directly on the host. Run `hawk path` (or `hawk doctor`) to see an onboarding +**No Docker or container runtime is required.** Rho executes agent commands +directly on the host. Run `rho path` (or `rho doctor`) to see an onboarding checklist: credentials → model → catalog → ecosystem. See [docs/SECURITY-DEVELOPER.md](docs/SECURITY-DEVELOPER.md) for the credential model. -Do not put API keys in shell env or `.env` for hawk. +Do not put API keys in shell env or `.env` for rho. Optional for contributors: ```bash -go install github.com/GrayCodeAI/hawk/cmd/hawk@latest +go install github.com/GrayCodeAI/rho/cmd/rho@latest ``` ## Features @@ -113,22 +113,22 @@ Built with [Bubble Tea](https://github.com/charmbracelet/bubbletea) for a smooth ### Portable Execution Graph -Export the latest or a selected Hawk session as validated graph nodes, edges, +Export the latest or a selected Rho session as validated graph nodes, edges, and lifecycle events: ```bash -hawk graph export -hawk graph export -hawk graph export --mission-dir /path/to/mission +rho graph export +rho graph export +rho graph export --mission-dir /path/to/mission # Explicitly privacy-normalize and sync the graph for a connected cloud project -hawk cloud graph sync -hawk cloud graph sync --mission-dir /path/to/mission +rho cloud graph sync +rho cloud graph sync --mission-dir /path/to/mission ``` Cloud commands require an endpoint. There is no default: pass `--endpoint` or -set `HAWK_CLOUD_URL` to your Hawk Cloud worker URL before running -`hawk cloud login`. `https://api.graycodeai.com` is the browser BFF and +set `RHO_CLOUD_URL` to your Rho Cloud worker URL before running +`rho cloud login`. `https://api.graycodeai.com` is the browser BFF and will reject a device token. The export contains metadata and hashes, not prompts, tool arguments/results, @@ -143,7 +143,7 @@ synchronized explicitly with the `--mission-dir` variants above. For larger tasks, decompose work into parallel feature branches (power-user / future team workflows): ```bash -hawk mission "Add auth, rate limiting, and logging" +rho mission "Add auth, rate limiting, and logging" ``` Each sub-agent runs in its own git worktree with full autonomy. @@ -153,14 +153,14 @@ Each sub-agent runs in its own git worktree with full autonomy. Discover and install modular instruction packages for specialized workflows: ```bash -hawk skills search api # Search community registry -hawk skills install go-review # Install from GitHub -hawk skills audit # Security scan installed skills +rho skills search api # Search community registry +rho skills install go-review # Install from GitHub +rho skills audit # Security scan installed skills ``` ### Permission Center -hawk exposes two independent chat command centers — trust tier and the +rho exposes two independent chat command centers — trust tier and the spec-driven workflow gate — rather than one merged permission mode: ```text @@ -192,7 +192,7 @@ The model is: - `Rules` control explicit allow/deny exceptions. - `Spec` is a separate, independent workflow gate (bare `/spec` opens a picker): starting it walks the model through `Specify → Plan → Tasks`, - writing real files to `.hawk/specs//`, and blocks Write/Edit/Bash + writing real files to `.rho/specs//`, and blocks Write/Edit/Bash until you approve moving to implementation — at **any** trust tier, including Autonomous. @@ -206,23 +206,23 @@ Connect external tools via [Model Context Protocol](https://modelcontextprotocol ### Watch Mode (AI-comment loop) -`hawk --watch` watches your tree for `AI!` (do it now) and `AI?` (answer my question) comments and acts on them automatically — leave a directive in code, save, and hawk responds. Off by default; enabled via the `--watch` flag. +`rho --watch` watches your tree for `AI!` (do it now) and `AI?` (answer my question) comments and acts on them automatically — leave a directive in code, save, and rho responds. Off by default; enabled via the `--watch` flag. ### CI / GitHub Action -A bundled GitHub Action (`.github/actions/hawk`) runs hawk in your pipeline: interactive mode on `@hawk` mentions in issue/PR comments, automation mode on labeled issues/PRs, and skill dispatch when a prompt begins with `/` (e.g. `/code-review`). +A bundled GitHub Action (`.github/actions/rho`) runs rho in your pipeline: interactive mode on `@rho` mentions in issue/PR comments, automation mode on labeled issues/PRs, and skill dispatch when a prompt begins with `/` (e.g. `/code-review`). ### Messaging Gateways (opt-in) -The daemon exposes Telegram, Discord, and Slack gateways so you can chat with hawk from your messaging app. Disabled by default; enabled per-channel via daemon config. +The daemon exposes Telegram, Discord, and Slack gateways so you can chat with rho from your messaging app. Disabled by default; enabled per-channel via daemon config. ### AST Repo-Map & Codebase Analysis -An AST-based repository map (`internal/context/repomap`) gives the model a structural overview of your code. On first run, hawk can auto-analyze the codebase to seed context (default-off, opt-in). +An AST-based repository map (`internal/context/repomap`) gives the model a structural overview of your code. On first run, rho can auto-analyze the codebase to seed context (default-off, opt-in). ### Auto-Lint / Auto-Fix Cycle -After edits, hawk can run the matching linter and iterate on fixes (bounded retries) before handing back. Opt-in; preserves existing behavior when disabled. +After edits, rho can run the matching linter and iterate on fixes (bounded retries) before handing back. Opt-in; preserves existing behavior when disabled. ### Image / Multimodal Context @@ -258,16 +258,16 @@ Features adopted from open-source agent projects. All are off by default unless | Feature | Flag / Command | What it does | |---|---|---| -| Best-of-N fan-out | `hawk exec --fanout N` | Run the same prompt in N isolated worktrees, compare, merge winner | -| Completion notifications | `HAWK_NOTIFY_WEBHOOK_URL` / `HAWK_NOTIFY_TELEGRAM_TOKEN` + `_CHAT_ID` | Webhook or Telegram ping when a run finishes | -| Incremental system-context | `HAWK_INCREMENTAL_CONTEXT=1` | Reconcile dynamic sections instead of rebuilding the prompt | -| Tool-catalog shrink | `HAWK_TOOL_SHRINK=1` | Compress the tool catalog sent on every request | -| Compaction segments | `HAWK_COMPACTION_SEGMENT_DETAIL=verbose\|balanced\|minimal\|none` | Persist verbatim compacted turns to disk | -| Skill curator | `hawk skills curator status/run/pin/unpin/archive` + `HAWK_SKILL_CURATOR=1` | Auto-archive cold agent-created skills (recoverable) | +| Best-of-N fan-out | `rho exec --fanout N` | Run the same prompt in N isolated worktrees, compare, merge winner | +| Completion notifications | `RHO_NOTIFY_WEBHOOK_URL` / `RHO_NOTIFY_TELEGRAM_TOKEN` + `_CHAT_ID` | Webhook or Telegram ping when a run finishes | +| Incremental system-context | `RHO_INCREMENTAL_CONTEXT=1` | Reconcile dynamic sections instead of rebuilding the prompt | +| Tool-catalog shrink | `RHO_TOOL_SHRINK=1` | Compress the tool catalog sent on every request | +| Compaction segments | `RHO_COMPACTION_SEGMENT_DETAIL=verbose\|balanced\|minimal\|none` | Persist verbatim compacted turns to disk | +| Skill curator | `rho skills curator status/run/pin/unpin/archive` + `RHO_SKILL_CURATOR=1` | Auto-archive cold agent-created skills (recoverable) | | Structural code match | `CodeMatch` tool | Tree-sitter query search over Go/Python/TS/TSX | -| Composable toolsets | `hawk toolset [name]` + `Toolset` tool | Named tool groups (research, dev, ops, full_stack) | +| Composable toolsets | `rho toolset [name]` + `Toolset` tool | Named tool groups (research, dev, ops, full_stack) | | App verification | `AppVerify` tool | Boot-smoke check with readiness polling and evidence artifacts | -| Media generation | `GenerateMedia` tool | Image/video generation with local persistence. Backend via `tool.SetMediaEngine`; an OpenAI-compatible client ships in `eyrie/client` (`ImageClient`), wired by the host (boundary-guarded — hawk routes through the eyrie facade) | +| Media generation | `GenerateMedia` tool | Image/video generation with local persistence. Backend via `tool.SetMediaEngine`; an OpenAI-compatible client ships in `eyrie/client` (`ImageClient`), wired by the host (boundary-guarded — rho routes through the eyrie facade) | | Voice transcription | Telegram voice notes + `stt` package | Transcribe Telegram voice/audio into the prompt. Backend via `stt.SetTranscriber`; an OpenAI-compatible client ships in `eyrie/client` (`AudioClient`), wired by the host | | Bounded autonomous budgets | `internal/engine` (`AutonomousBudget`) | Track turns/tokens/time/continuations; report why a run stopped (budget vs gate-passed vs error) | | Agent family messaging | `internal/multiagent` (`FamilyMessenger`) | Direct parent/sibling/child messages with pending caps + rate limits | @@ -276,27 +276,27 @@ Features adopted from open-source agent projects. All are off by default unless | X/Twitter search | `SearchX` tool | Live X search by forwarding a query to an xAI endpoint with server-side search; returns a cited summary. Requires `XAI_API_KEY` (or `GROK_API_KEY`) | | Desktop computer-use | `ComputerUse` tool | snapshot/click/type/scroll/press/screenshot via a pluggable `tool.SetComputerBackend` seam (host wires a native macOS accessibility backend) | | Token-cheaper file views | `Read` tool `--minify` | Read-only, comment-stripped, whitespace-dense file view (Go via `go/parser`; other languages string-aware; never touches disk) — fewer tokens per read | -| Classified provider hints | `internal/errhint` | Buckets provider errors (Auth/RateLimit/Connectivity/ModelNotFound/ContextOverflow) into a one-line fixable next step. Wired into TUI error rows (`friendlyErrorMessage`) and `hawk exec` CLI errors | +| Classified provider hints | `internal/errhint` | Buckets provider errors (Auth/RateLimit/Connectivity/ModelNotFound/ContextOverflow) into a one-line fixable next step. Wired into TUI error rows (`friendlyErrorMessage`) and `rho exec` CLI errors | | Atomic install transactions | `internal/installtxn` | Cross-process staged install/remove with rollback. Wired into skill install (atomic `SKILL.md` publish) | -| Test command discovery | `internal/testrunner` | Auto-detect test/verify commands (Go/npm/bun/pnpm/yarn/pytest/cargo) and parse runner output into structured results. Wired into `hawk verify` | +| Test command discovery | `internal/testrunner` | Auto-detect test/verify commands (Go/npm/bun/pnpm/yarn/pytest/cargo) and parse runner output into structured results. Wired into `rho verify` | | Circuit breaker | `internal/circuitbreaker` | Closed/open/half-open retry-storm protection with cooldown. Wired into auto-compaction (cooldown + half-open auto-retry) | | Smart turn routing | `internal/smartrouting` | Deterministic simple/strong turn classifier with fail-toward-strong safety. Wired into per-turn model selection (`settings.smart_routing`) | | Conversation arc | `internal/conversationarc` | Durable sidecar memory of goals/decisions/milestones/phase with a byte-stable summary. Wired into sessions (loaded on open, saved on close, injected into the system prompt) | | Relevance pruning | `internal/relevanceprune` | Token-budgeted context pruning preserving recent turns/tool calls/errors. Wired into compaction as a `relevance` strategy | | Tool-result clearing | `internal/engine` (`ClearOldToolResults`) | Two-tier context management: at 80% of the context window, stale tool-result content is replaced with `[output cleared]` placeholders (tool_use kept intact) before compacting — a gentler tier below compaction | | Approval pause timing | `internal/permissions` | Approval requests record decision timestamp + human deliberation duration (`DecisionAt`/`PauseDuration`) for approval-latency observability | -| Graceful exhaustion | `internal/engine` (`SynthesisForExhaustion`) | When turn/token/time limits hit, one final tools-disabled LLM call synthesizes a coherent completion (accomplished/remaining/next steps) instead of a bare stop line. Opt-in via `HAWK_GRACEFUL_EXHAUSTION=1` | -| Deterministic replay cache | `internal/replaycache` (`HAWK_REPLAY_CACHE_DIR`) | Disk-persisted SHA-256-keyed cache of completions; identical requests replay stored responses for reproducible regression runs | +| Graceful exhaustion | `internal/engine` (`SynthesisForExhaustion`) | When turn/token/time limits hit, one final tools-disabled LLM call synthesizes a coherent completion (accomplished/remaining/next steps) instead of a bare stop line. Opt-in via `RHO_GRACEFUL_EXHAUSTION=1` | +| Deterministic replay cache | `internal/replaycache` (`RHO_REPLAY_CACHE_DIR`) | Disk-persisted SHA-256-keyed cache of completions; identical requests replay stored responses for reproducible regression runs | ## Usage ### Interactive Mode ```bash -hawk # Start REPL -hawk -r abc123 # Resume session -hawk -c # Continue latest session -hawk --provider openai --model gpt-4o # Override provider +rho # Start REPL +rho -r abc123 # Resume session +rho -c # Continue latest session +rho --provider openai --model gpt-4o # Override provider ``` ### Permission Examples @@ -315,21 +315,21 @@ hawk --provider openai --model gpt-4o # Override provider ### Non-Interactive Mode ```bash -hawk -p "explain this repo" # Print response, exit -hawk -p "fix tests" --allowed-tools "Bash(go test:*) Edit Read" -hawk exec "refactor auth module" # Full engine, non-interactive -hawk exec --auto full "add error handling" # Full autonomy -hawk exec --worktree "add rate limiting" # Isolated branch -hawk exec --agent reviewer "review last commit" # Custom persona +rho -p "explain this repo" # Print response, exit +rho -p "fix tests" --allowed-tools "Bash(go test:*) Edit Read" +rho exec "refactor auth module" # Full engine, non-interactive +rho exec --auto full "add error handling" # Full autonomy +rho exec --worktree "add rate limiting" # Isolated branch +rho exec --agent reviewer "review last commit" # Custom persona ``` ### Diagnostics & ecosystem ```bash -hawk path # Developer path readiness (setup + security) -hawk doctor # Full health report (eyrie + token pipeline panel) -hawk ecosystem # Ecosystem panel only -hawk preflight # Quick ready-to-chat check +rho path # Developer path readiness (setup + security) +rho doctor # Full health report (eyrie + token pipeline panel) +rho ecosystem # Ecosystem panel only +rho preflight # Quick ready-to-chat check make path # Developer path verification make smoke # Build + quick verification script ``` @@ -343,9 +343,9 @@ In the TUI: `/path`, `/ecosystem`, `/memory` (AGENTS.md). ### Daemon Mode ```bash -hawk daemon start # Background HTTP server on port 4590 -hawk daemon status # Check if running -hawk daemon stop # Graceful shutdown +rho daemon start # Background HTTP server on port 4590 +rho daemon status # Check if running +rho daemon stop # Graceful shutdown ``` Endpoints: `GET /v1/health`, `GET /v1/ready` (dependency-aware readiness), `POST /v1/chat` (JSON or SSE streaming) @@ -353,15 +353,15 @@ Endpoints: `GET /v1/health`, `GET /v1/ready` (dependency-aware readiness), `POST ### Mission Mode ```bash -hawk mission "Add auth, rate limiting, and logging" -hawk mission --workers 6 "Refactor into microservices" -hawk mission --dry-run "What would this decompose into?" -hawk mission --from-tasks # Execute validated dependency waves +rho mission "Add auth, rate limiting, and logging" +rho mission --workers 6 "Refactor into microservices" +rho mission --dry-run "What would this decompose into?" +rho mission --from-tasks # Execute validated dependency waves ``` ## Providers -hawk works with any LLM provider. **Developer path:** paste keys in `/config` (stored in OS keychain) — not shell env or `.env`. Use `hawk credentials status` to verify. +rho works with any LLM provider. **Developer path:** paste keys in `/config` (stored in OS keychain) — not shell env or `.env`. Use `rho credentials status` to verify. | Provider | ID | Key (via `/config`) | |---|---|---| @@ -380,19 +380,19 @@ hawk works with any LLM provider. **Developer path:** paste keys in `/config` (s | Ollama (local) | `ollama` | `OLLAMA_BASE_URL` (no API key) | Provider routing, model resolution, and retries are handled by [eyrie](https://github.com/GrayCodeAI/eyrie). -For deployment-aware routing, set `"deployment_routing": true` in `.hawk/settings.json` -or export `HAWK_DEPLOYMENT_ROUTING=true`. Hawk will route canonical model IDs through +For deployment-aware routing, set `"deployment_routing": true` in `.rho/settings.json` +or export `RHO_DEPLOYMENT_ROUTING=true`. Rho will route canonical model IDs through Eyrie's deployment catalog, so new models can be exposed by refreshing the catalog -instead of changing Hawk. In chat, run `/refresh-model-catalog` to fetch the latest +instead of changing Rho. In chat, run `/refresh-model-catalog` to fetch the latest deployment-aware catalog into `~/.eyrie/model_catalog.json`. ## Architecture -hawk is built in Go with a modular, layered architecture: +rho is built in Go with a modular, layered architecture: ``` -hawk/ -├── bin/ # Built binaries (hawk, hawk_bin) +rho/ +├── bin/ # Built binaries (rho, rho_bin) ├── cmd/ # CLI entry point (Cobra + Bubble Tea TUI) ├── internal/ │ ├── engine/ # Agent loop, compaction, self-improvement @@ -425,15 +425,15 @@ folder): ### Ecosystem -hawk is the main CLI/product and integrates these GrayCodeAI repositories in +rho is the main CLI/product and integrates these GrayCodeAI repositories in three runtime layers plus optional tooling/platform services: -- **Primary product:** **hawk** is the only end-user product surface in this ecosystem. -- **Provider engine mounted by Hawk:** **eyrie** is the LLM provider runtime, consumed through its stable engine facade. -- **API consumers/extensions:** **graycode-skills** provides Hawk skills - installed on demand (`hawk skills install`). -- **Tooling/platform:** **graycode-platform** contains the optional web/BFF/Hawk - Cloud plane and is outside the Hawk Go runtime graph. +- **Primary product:** **rho** is the only end-user product surface in this ecosystem. +- **Provider engine mounted by Rho:** **eyrie** is the LLM provider runtime, consumed through its stable engine facade. +- **API consumers/extensions:** **graycode-skills** provides Rho skills + installed on demand (`rho skills install`). +- **Tooling/platform:** **graycode-platform** contains the optional web/BFF/Rho + Cloud plane and is outside the Rho Go runtime graph. Local development uses: @@ -443,7 +443,7 @@ Local development uses: Cross-repo contracts now live in `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) so support repos do not depend -on Hawk internals. External consumers should vendor the needed DTOs from `internal/contracts` +on Rho internals. External consumers should vendor the needed DTOs from `internal/contracts` until a published contracts module exists. Current contract packages (`internal/contracts/`): @@ -461,17 +461,17 @@ You may keep a **personal** parent **`go.work`** that lists alternate clones on | Component | Repository | Purpose | |---|---|---| -| **hawk** | This repo | AI coding agent | +| **rho** | This repo | AI coding agent | | **eyrie** | [GrayCodeAI/eyrie](https://github.com/GrayCodeAI/eyrie) | LLM provider runtime | | **graycode-skills** | [GrayCodeAI/graycode-skills](https://github.com/GrayCodeAI/graycode-skills) | Community skill registry | -| **graycode-platform** | [GrayCodeAI/graycode-platform](https://github.com/GrayCodeAI/graycode-platform) | Web, BFF, and Hawk Cloud | +| **graycode-platform** | [GrayCodeAI/graycode-platform](https://github.com/GrayCodeAI/graycode-platform) | Web, BFF, and Rho Cloud | `ecosystem.yaml` is the canonical inventory of repositories cloned as siblings in this local workspace; tooling reads it rather than carrying its own repo-name list. `eyrie` is the only Go module dependency outside this repo; it is consumed through its stable engine facade. -For the consolidated repo map and the current-vs-proposed architecture diagrams, see [docs/architecture/hawk-current-vs-proposed.md](docs/architecture/hawk-current-vs-proposed.md). +For the consolidated repo map and the current-vs-proposed architecture diagrams, see [docs/architecture/rho-current-vs-proposed.md](docs/architecture/rho-current-vs-proposed.md). For execution-graph ownership, automatic capture seams, and export/sync commands, see [docs/architecture/execution-graph.md](docs/architecture/execution-graph.md). @@ -485,7 +485,7 @@ commands, see ### Build & Test ```bash -go build ./cmd/hawk # Build binary +go build ./cmd/rho # Build binary go test -race ./... # Run all tests with race detector make ci # Run full CI suite (lint, test, security) make cover # Generate coverage report @@ -509,7 +509,7 @@ Headline numbers (Go 1.26.6, AMD EPYC 7543P, `go test -bench -benchmem -count=3` ### Project Structure -hawk follows Go conventions: `cmd/` for entry points, `internal/` for private code, tests alongside source files. See [docs/architecture.md](docs/architecture.md) for details. +rho follows Go conventions: `cmd/` for entry points, `internal/` for private code, tests alongside source files. See [docs/architecture.md](docs/architecture.md) for details. ## Contributing diff --git a/SECURITY.md b/SECURITY.md index 7fecadd6b..fc1f0c273 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ -# Security Policy — hawk +# Security Policy — rho ## Supported versions @@ -7,15 +7,15 @@ minor versions once `1.x` ships. Older versions receive critical-severity fixes only on a best-effort basis. The current canonical version is the contents of the [`VERSION`](./VERSION) -file at the repo root. See [`docs/versioning.md`](https://github.com/GrayCodeAI/hawk/blob/main/docs/versioning.md) +file at the repo root. See [`docs/versioning.md`](https://github.com/GrayCodeAI/rho/blob/main/docs/versioning.md) for the eco-wide versioning scheme. ## Reporting a vulnerability **Do not open a public GitHub issue for security vulnerabilities.** Instead: -1. Open a private [GitHub Security Advisory](https://github.com/GrayCodeAI/hawk/security/advisories/new), **or** -2. Email `security@hawk.ai` with the details below. +1. Open a private [GitHub Security Advisory](https://github.com/GrayCodeAI/rho/security/advisories/new), **or** +2. Email `security@rho.ai` with the details below. Include in your report: @@ -63,28 +63,28 @@ This policy covers the code in this repository and the release artefacts published from it. It does not cover: - Third-party dependencies (report to upstream). -- LLM provider services that hawk integrates with (report to the +- LLM provider services that rho integrates with (report to the provider). - Local filesystem misuse where an attacker already has shell access (out of threat model). -For hawk-specific threat-model notes, see the README and any docs in +For rho-specific threat-model notes, see the README and any docs in this repo. ## Config security model ### Clone-and-load attack defense -Project-level `.hawk/settings.json` can be committed to a git repository. +Project-level `.rho/settings.json` can be committed to a git repository. An attacker who controls a repository could define MCP servers that execute -arbitrary commands when a developer clones and runs hawk in that directory. +arbitrary commands when a developer clones and runs rho in that directory. **Mitigation:** Project-level MCP servers are stripped from project config (`projectSafeSettings` in `internal/config/settings.go`) and project hooks/MCP/plugins/LSP additionally require folder trust: the project root -must be trusted via `hawk trust add` (`AllowProjectAutomation` in +must be trusted via `rho trust add` (`AllowProjectAutomation` in `internal/trust/store.go`). There is no `--allow-project-mcp` flag. -Global MCP servers (from `~/.hawk/settings.json`) are always loaded. +Global MCP servers (from `~/.rho/settings.json`) are always loaded. ### Security-sensitive fields @@ -107,8 +107,8 @@ Highest priority first (`LoadSettings` / `LoadSettingsWithOverride` in 1. CLI `--settings` JSON override 2. Per-command CLI flags (e.g., `--model`, `--provider`) 3. Environment variables (only where explicitly read; there is no global env layer) -4. Project `.hawk/settings.json` (repository-safe subset only — see above) -5. Global `~/.hawk/settings.json` +4. Project `.rho/settings.json` (repository-safe subset only — see above) +5. Global `~/.rho/settings.json` 6. Built-in defaults (lowest priority) Project-level config CANNOT escalate permissions beyond what global config diff --git a/api/openapi.yaml b/api/openapi.yaml index e52930c47..d27564d16 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1,19 +1,19 @@ openapi: "3.1.0" info: - title: Hawk Daemon API + title: Rho Daemon API description: | - HTTP API served by the hawk daemon on port 4590. + HTTP API served by the rho daemon on port 4590. Used by sparrow, robin, and external integrations. - The daemon must be running (`hawk daemon start`) before calling these endpoints. + The daemon must be running (`rho daemon start`) before calling these endpoints. This spec is kept in lockstep with the registered routes in internal/daemon; the daemon test suite fails if they drift. version: "0.1.0" license: name: MIT - url: https://github.com/GrayCodeAI/hawk/blob/main/LICENSE + url: https://github.com/GrayCodeAI/rho/blob/main/LICENSE contact: - url: https://github.com/GrayCodeAI/hawk + url: https://github.com/GrayCodeAI/rho servers: - url: http://127.0.0.1:4590 @@ -60,7 +60,7 @@ components: description: Existing directory associated with session metadata. The daemon executes tools from its startup working directory; it never performs a process-wide chdir per request. agent: type: string - description: Named Hawk agent persona to apply. Continuations inherit the persisted persona when omitted. + description: Named Rho agent persona to apply. Continuations inherit the persisted persona when omitted. ChatResponse: type: object @@ -254,7 +254,7 @@ components: properties: schema_version: type: string - enum: [hawk.graph/v1] + enum: [rho.graph/v1] generated_at: type: string format: date-time @@ -457,7 +457,7 @@ components: properties: status: type: string - description: Output of `hawk review status` + description: Output of `rho review status` Error: type: object @@ -600,7 +600,7 @@ paths: Omit `session_id` to create and durably persist a new session. Supply a previously returned `session_id` to load its transcript, append a turn, and update that same durable session. The returned JSON field and the - `X-Hawk-Session-ID` header identify a retrievable `/v1/sessions/{id}`. + `X-Rho-Session-ID` header identify a retrievable `/v1/sessions/{id}`. requestBody: required: true content: @@ -611,7 +611,7 @@ paths: "200": description: Agent response (or SSE stream) headers: - X-Hawk-Session-ID: + X-Rho-Session-ID: description: Durable session ID created or continued by this request. schema: type: string @@ -873,10 +873,10 @@ paths: tags: [graphs] summary: Project a persisted session as a portable execution graph description: | - Returns Hawk's privacy-safe, read-only `hawk.graph/v1` projection. + Returns Rho's privacy-safe, read-only `rho.graph/v1` projection. Prompt text, tool arguments, tool output, and verification details are excluded. Explicit Swift checkpoint IDs are additive to authoritative - Swift session correlation performed by Hawk. + Swift session correlation performed by Rho. parameters: - name: id in: path diff --git a/cmd/acp.go b/cmd/acp.go index f0cc3f3c2..0ee51b3c0 100644 --- a/cmd/acp.go +++ b/cmd/acp.go @@ -7,19 +7,19 @@ import ( "path/filepath" "syscall" - "github.com/GrayCodeAI/hawk/internal/acp" - "github.com/GrayCodeAI/hawk/internal/attachment" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/observability/logger" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/acp" + "github.com/GrayCodeAI/rho/internal/attachment" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/observability/logger" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) var acpCmd = &cobra.Command{ Use: "acp", - Short: "Run hawk as an Agent Client Protocol (ACP) server", - Long: "Run hawk as an ACP server over stdio (JSON-RPC 2.0) so editors such as " + + Short: "Run rho as an Agent Client Protocol (ACP) server", + Long: "Run rho as an ACP server over stdio (JSON-RPC 2.0) so editors such as " + "Zed can drive it. Tool-permission prompts are routed back to the client " + "via session/request_permission.", RunE: runACP, @@ -30,8 +30,8 @@ func init() { } func runACP(cmd *cobra.Command, _ []string) error { - settings := hawkconfig.LoadSettings() - newSession := newConfiguredHawkSessionFactory(settings, logger.New(io.Discard, logger.Error)) + settings := rhoconfig.LoadSettings() + newSession := newConfiguredRhoSessionFactory(settings, logger.New(io.Discard, logger.Error)) factory := func() (*engine.Session, error) { systemPrompt, err := buildSystemPrompt() diff --git a/cmd/agent.go b/cmd/agent.go index 2f0dab515..6bc58a379 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -6,8 +6,8 @@ import ( "os" "path/filepath" - "github.com/GrayCodeAI/hawk/internal/multiagent/agents" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/multiagent/agents" + "github.com/GrayCodeAI/rho/internal/theme" "github.com/spf13/cobra" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -16,7 +16,7 @@ import ( var agentCmd = &cobra.Command{ Use: "agent", Short: "Manage custom agent personas", - Long: "Create, list, and manage custom agent personas stored in Hawk user state.", + Long: "Create, list, and manage custom agent personas stored in Rho user state.", } var agentListJSON bool @@ -81,7 +81,7 @@ func runAgentList(cmd *cobra.Command, _ []string) error { return nil } if len(all) == 0 { - fmt.Printf("%s\n", auditTint("No agents found. Create one with: hawk agent create ", textMuted)) + fmt.Printf("%s\n", auditTint("No agents found. Create one with: rho agent create ", textMuted)) fmt.Printf("%s\n", auditTint("Agent directory: "+agents.DefaultDir(), textPrimary)) return nil } @@ -164,7 +164,7 @@ func runAgentShow(_ *cobra.Command, args []string) error { } fmt.Printf("%s %s\n", auditTint("Model:", textMuted), auditTint(model, textPrimary)) fmt.Printf("%s %s\n", auditTint("File:", textMuted), auditTint(a.FilePath, textPrimary)) - fmt.Printf("\n%s\n%s\n", auditTint("--- Prompt ---", hawkColor), a.Prompt) + fmt.Printf("\n%s\n%s\n", auditTint("--- Prompt ---", rhoColor), a.Prompt) return nil } diff --git a/cmd/agent_grid.go b/cmd/agent_grid.go index 30cacfc15..0ebae3ae3 100644 --- a/cmd/agent_grid.go +++ b/cmd/agent_grid.go @@ -9,7 +9,7 @@ import ( "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var ( @@ -17,7 +17,7 @@ var ( // can read agent state at a glance. Active matches Talon Gold, // done matches the success palette, fail matches error, idle // matches disabled. - agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(hawkColor).Padding(0, 1) + agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(rhoColor).Padding(0, 1) agentDoneStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(doneGreen).Padding(0, 1) agentFailStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(errorCoral).Padding(0, 1) agentIdleStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(textDisabled).Padding(0, 1) diff --git a/cmd/agent_grid_test.go b/cmd/agent_grid_test.go index 8ece3017c..06b796535 100644 --- a/cmd/agent_grid_test.go +++ b/cmd/agent_grid_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestAgentStateString(t *testing.T) { diff --git a/cmd/agent_test.go b/cmd/agent_test.go index ef4bad437..3dfac3d3b 100644 --- a/cmd/agent_test.go +++ b/cmd/agent_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/multiagent/agents" + "github.com/GrayCodeAI/rho/internal/multiagent/agents" ) // sampleAgentMarkdown returns a minimal valid agent definition. @@ -17,7 +17,7 @@ func sampleAgentMarkdown(name, description, model string) string { func TestAgentList_JSON_Empty(t *testing.T) { dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("RHO_STATE_DIR", filepath.Join(dir, "state")) if err := os.MkdirAll(filepath.Join(dir, "state", "agents"), 0o755); err != nil { t.Fatal(err) } @@ -49,7 +49,7 @@ func TestAgentList_JSON_Empty(t *testing.T) { func TestAgentList_JSON(t *testing.T) { dir := t.TempDir() stateDir := filepath.Join(dir, "state") - t.Setenv("HAWK_STATE_DIR", stateDir) + t.Setenv("RHO_STATE_DIR", stateDir) agentDir := filepath.Join(stateDir, "agents") if err := os.MkdirAll(agentDir, 0o755); err != nil { t.Fatal(err) diff --git a/cmd/ai_comments.go b/cmd/ai_comments.go index 617206c97..d29984040 100644 --- a/cmd/ai_comments.go +++ b/cmd/ai_comments.go @@ -9,7 +9,7 @@ import ( "sort" "strings" - "github.com/GrayCodeAI/hawk/internal/fsutil" + "github.com/GrayCodeAI/rho/internal/fsutil" ) // AIDirective represents a found AI comment directive in a source file. diff --git a/cmd/audit.go b/cmd/audit.go index e400c856d..ce94e3fbb 100644 --- a/cmd/audit.go +++ b/cmd/audit.go @@ -11,8 +11,8 @@ import ( "time" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/hooks/audit" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/hooks/audit" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) @@ -31,7 +31,7 @@ var auditCmd = &cobra.Command{ redundant cd commands, unnecessary cat/head usage, long sleep loops, and other patterns that waste tokens and wall-clock time. -Reports what hawk would have caught with current policies enabled, +Reports what rho would have caught with current policies enabled, plus audit-only detectors that identify optimization opportunities.`, RunE: runAudit, } @@ -213,9 +213,9 @@ func discoverSessions(days int, projectFilter string) ([]SessionInfo, error) { cutoff := time.Now().AddDate(0, 0, -days) var sessions []SessionInfo - // Scan hawk sessions directory - hawkDir := storage.SessionsDir() - entries, err := os.ReadDir(hawkDir) + // Scan rho sessions directory + rhoDir := storage.SessionsDir() + entries, err := os.ReadDir(rhoDir) if err != nil && !os.IsNotExist(err) { return nil, err } @@ -224,7 +224,7 @@ func discoverSessions(days int, projectFilter string) ([]SessionInfo, error) { if e.IsDir() || !strings.HasSuffix(e.Name(), ".jsonl") { continue } - path := filepath.Join(hawkDir, e.Name()) + path := filepath.Join(rhoDir, e.Name()) info, err := os.Stat(path) if err != nil { continue @@ -311,7 +311,7 @@ func printAuditText(cmd *cobra.Command, result AuditResult) { _, _ = fmt.Fprintf(w, "\n") _, _ = fmt.Fprintf(w, "═══════════════════════════════════════════════════════════════\n") - _, _ = fmt.Fprintf(w, " %s\n", auditTint("Hawk Audit Report", hawkColor)) + _, _ = fmt.Fprintf(w, " %s\n", auditTint("Rho Audit Report", rhoColor)) _, _ = fmt.Fprintf(w, "═══════════════════════════════════════════════════════════════\n") _, _ = fmt.Fprintf(w, "\n") _, _ = fmt.Fprintf(w, " %s %d sessions (last %d days)\n", diff --git a/cmd/autoinit.go b/cmd/autoinit.go index ddff08eb1..385f32cf0 100644 --- a/cmd/autoinit.go +++ b/cmd/autoinit.go @@ -8,8 +8,8 @@ import ( "path/filepath" "time" - "github.com/GrayCodeAI/hawk/internal/autoinit" - "github.com/GrayCodeAI/hawk/internal/intelligence/repomap" + "github.com/GrayCodeAI/rho/internal/autoinit" + "github.com/GrayCodeAI/rho/internal/intelligence/repomap" ) // autoInitContextFile is the project-level context file the auto-init runner @@ -22,9 +22,9 @@ const autoInitContextFile = "AGENTS.md" // project. It is intentionally additive and safe-by-default: // // - It runs in a background goroutine so it never blocks chat startup. -// - It is a no-op unless the project has NO context file (AGENTS.md / HAWK.md +// - It is a no-op unless the project has NO context file (AGENTS.md / RHO.md // / CLAUDE.md / CONTEXT.md), no auto-init marker exists yet, and the -// HAWK_DISABLE_AUTO_INIT kill switch is unset — all enforced by +// RHO_DISABLE_AUTO_INIT kill switch is unset — all enforced by // autoinit.MaybeRun. // - Any failure (analysis error, write error) is swallowed; startup proceeds. // @@ -50,7 +50,7 @@ func maybeAutoInit(ctx context.Context) { // autoInitRunner drives the existing codebase-analysis machinery (the same // hierarchical repomap summary that powers init/repomap) and writes a starter // AGENTS.md context file for the project. It mirrors the intent of the -// init-deep skill / `hawk init` without coupling to the agentic flow: a cheap, +// init-deep skill / `rho init` without coupling to the agentic flow: a cheap, // deterministic, dependency-free pass that gives the project a baseline context // file. The function is only ever invoked by autoinit.MaybeRun, which has // already confirmed the project has no context file. @@ -103,8 +103,8 @@ func autoInitContextContent(projectName, summary string) string { } return fmt.Sprintf(`# %s -> Auto-generated by hawk on first run. Edit freely or delete to regenerate. -> Set HAWK_DISABLE_AUTO_INIT=1 to opt out of automatic context generation. +> Auto-generated by rho on first run. Edit freely or delete to regenerate. +> Set RHO_DISABLE_AUTO_INIT=1 to opt out of automatic context generation. ## Package map diff --git a/cmd/autoinit_test.go b/cmd/autoinit_test.go index 12bb09776..2df70aba5 100644 --- a/cmd/autoinit_test.go +++ b/cmd/autoinit_test.go @@ -6,14 +6,14 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/autoinit" + "github.com/GrayCodeAI/rho/internal/autoinit" ) // TestAutoInitRunner_WritesContextFileOnce verifies the cmd-layer runner // produces a recognized context file and that MaybeRun invokes it exactly once // for a fresh project, then gates on the marker thereafter. func TestAutoInitRunner_WritesContextFileOnce(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", filepath.Join(t.TempDir(), "state")) + t.Setenv("RHO_STATE_DIR", filepath.Join(t.TempDir(), "state")) root := t.TempDir() // A trivial Go file so BuildHierarchy has something to summarize. if err := os.WriteFile(filepath.Join(root, "main.go"), []byte("package main\n\nfunc Foo() {}\n"), 0o644); err != nil { diff --git a/cmd/autonomy_picker.go b/cmd/autonomy_picker.go index daf529115..09c0e9ded 100644 --- a/cmd/autonomy_picker.go +++ b/cmd/autonomy_picker.go @@ -6,7 +6,7 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" "github.com/mattn/go-runewidth" ) diff --git a/cmd/autonomy_picker_test.go b/cmd/autonomy_picker_test.go index fc06a75c2..b5bd4df3a 100644 --- a/cmd/autonomy_picker_test.go +++ b/cmd/autonomy_picker_test.go @@ -4,7 +4,7 @@ import ( "testing" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" "github.com/mattn/go-runewidth" ) diff --git a/cmd/autonomy_tiers.go b/cmd/autonomy_tiers.go index 6585cd30c..03405cdd6 100644 --- a/cmd/autonomy_tiers.go +++ b/cmd/autonomy_tiers.go @@ -5,7 +5,7 @@ import ( "image/color" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // Five container autonomy tiers (Scout → Builder → Operator → Autonomous → Always Ask). diff --git a/cmd/autonomy_tiers_copy_test.go b/cmd/autonomy_tiers_copy_test.go index 045f63a56..1181678b2 100644 --- a/cmd/autonomy_tiers_copy_test.go +++ b/cmd/autonomy_tiers_copy_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestAutonomyTierDescriptions_PlainLanguage(t *testing.T) { diff --git a/cmd/autonomy_tiers_test.go b/cmd/autonomy_tiers_test.go index f129b9e3b..050954d3e 100644 --- a/cmd/autonomy_tiers_test.go +++ b/cmd/autonomy_tiers_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestAutonomyTierNames(t *testing.T) { diff --git a/cmd/bg_sessions.go b/cmd/bg_sessions.go index 788c91f54..1d401fb25 100644 --- a/cmd/bg_sessions.go +++ b/cmd/bg_sessions.go @@ -11,12 +11,12 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) // ───────────────────────────────────────────────────────────────────────────── -// Background Sessions — run hawk sessions in the background and manage them. +// Background Sessions — run rho sessions in the background and manage them. // ───────────────────────────────────────────────────────────────────────────── func bgSessionsDir() string { @@ -119,15 +119,15 @@ func KillBGSession(id string) error { return SaveBGSession(info) } -// StartBGSession launches hawk in background mode. +// StartBGSession launches rho in background mode. func StartBGSession(prompt string, args []string) (*BGSessionInfo, error) { id := genID() cwd, _ := os.Getwd() logFile := filepath.Join(bgSessionsDir(), id+".log") - // Build command: hawk --print with all inherited flags + // Build command: rho --print with all inherited flags cmdArgs := append([]string{"--print", "--session-id", id, prompt}, args...) - cmd := exec.CommandContext(context.Background(), "hawk", cmdArgs...) // #nosec G204 -- fixed command 'hawk' relaunching self with internal flags + cmd := exec.CommandContext(context.Background(), "rho", cmdArgs...) // #nosec G204 -- fixed command 'rho' relaunching self with internal flags cmd.Dir = cwd // 0600: the log captures full session output (private user state, matching @@ -207,11 +207,11 @@ func FormatBGSessions(sessions []*BGSessionInfo) string { var bgCmd = &cobra.Command{ Use: "bg [prompt]", Short: "Run a session in the background", - Long: `Start hawk in the background and continue working in your terminal. + Long: `Start rho in the background and continue working in your terminal. Examples: - hawk bg "Refactor the auth module" - hawk bg "Run tests and fix failures"`, + rho bg "Refactor the auth module" + rho bg "Run tests and fix failures"`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return fmt.Errorf("prompt required") @@ -229,7 +229,7 @@ Examples: if len(attachID) > 8 { attachID = attachID[:8] } - cmd.Printf("%s\n", auditTint("Attach: hawk attach "+attachID, textMuted)) + cmd.Printf("%s\n", auditTint("Attach: rho attach "+attachID, textMuted)) return nil }, } diff --git a/cmd/braille_spinner.go b/cmd/braille_spinner.go index 4429358b7..4de1382ef 100644 --- a/cmd/braille_spinner.go +++ b/cmd/braille_spinner.go @@ -12,8 +12,8 @@ type SpinnerStyle string const ( SpinnerBraille SpinnerStyle = "braille" SpinnerBrailleWave SpinnerStyle = "braillewave" - SpinnerHawk SpinnerStyle = "hawk" - SpinnerHawkQuad SpinnerStyle = "hawkquad" + SpinnerRho SpinnerStyle = "rho" + SpinnerRhoQuad SpinnerStyle = "rhoquad" SpinnerDNA SpinnerStyle = "dna" SpinnerScan SpinnerStyle = "scan" SpinnerPulse SpinnerStyle = "pulse" @@ -24,18 +24,18 @@ const ( SpinnerRandom SpinnerStyle = "random" ) -// hawkSpinnerGlyphs is the default TUI spinner — partial-circle compass (smooth, readable). -var hawkSpinnerGlyphs = []string{"◐", "◓", "◑", "◒"} +// rhoSpinnerGlyphs is the default TUI spinner — partial-circle compass (smooth, readable). +var rhoSpinnerGlyphs = []string{"◐", "◓", "◑", "◒"} -// hawkQuadBlockGlyphs is the legacy QUADBLOCK animation (kept for tests / bubbles compat). -var hawkQuadBlockGlyphs = []string{"▛", "▜", "▟", "▙"} +// rhoQuadBlockGlyphs is the legacy QUADBLOCK animation (kept for tests / bubbles compat). +var rhoQuadBlockGlyphs = []string{"▛", "▜", "▟", "▙"} // spinnerFrames maps style names to their animation frames. var spinnerFrames = map[SpinnerStyle][]string{ SpinnerBraille: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, SpinnerBrailleWave: {"⠁⠂⠄⡀", "⠂⠄⡀⢀", "⠄⡀⢀⠠", "⡀⢀⠠⠐", "⢀⠠⠐⠈", "⠠⠐⠈⠁", "⠐⠈⠁⠂", "⠈⠁⠂⠄"}, - SpinnerHawk: hawkSpinnerGlyphs, - SpinnerHawkQuad: hawkQuadBlockGlyphs, + SpinnerRho: rhoSpinnerGlyphs, + SpinnerRhoQuad: rhoQuadBlockGlyphs, SpinnerDNA: {"⠋⠉⠙⠚", "⠉⠙⠚⠒", "⠙⠚⠒⠂", "⠚⠒⠂⠂", "⠒⠂⠂⠒", "⠂⠂⠒⠲", "⠂⠒⠲⠴", "⠒⠲⠴⠤", "⠲⠴⠤⠄", "⠴⠤⠄⠋", "⠤⠄⠋⠉", "⠄⠋⠉⠙"}, SpinnerScan: {"⡇⠀⠀⠀", "⣿⠀⠀⠀", "⢸⡇⠀⠀", "⠀⣿⠀⠀", "⠀⢸⡇⠀", "⠀⠀⣿⠀", "⠀⠀⢸⡇", "⠀⠀⠀⣿", "⠀⠀⠀⢸", "⠀⠀⠀⠀"}, SpinnerPulse: {"⠀", "⠄", "⠆", "⠇", "⡇", "⣇", "⣧", "⣷", "⣿", "⣷", "⣧", "⣇", "⡇", "⠇", "⠆", "⠄"}, @@ -45,8 +45,8 @@ var spinnerFrames = map[SpinnerStyle][]string{ SpinnerTalons: {"⩤", "⩥", "⩦", "⩧"}, } -// hawkTypingDots is the number of trailing typing-indicator dots. -const hawkTypingDots = 3 +// rhoTypingDots is the number of trailing typing-indicator dots. +const rhoTypingDots = 3 // BrailleSpinner renders the glyph frame (◐◓◑◒) and a 20-color wave on the // whole status strip: glyph → verb → ▪▫▫. @@ -57,7 +57,7 @@ type BrailleSpinner struct { frame int // glyph animation frame (mod len(frames)) wavePhase int // 0..19 flowing color wave (glyph + verb + dots) text string - dots int // 0..hawkTypingDots-1 — position of the highlighted dot + dots int // 0..rhoTypingDots-1 — position of the highlighted dot running bool stopCh chan struct{} } @@ -65,7 +65,7 @@ type BrailleSpinner struct { // NewBrailleSpinner creates a spinner with the given style and label text. func NewBrailleSpinner(style SpinnerStyle, text string) *BrailleSpinner { if style == SpinnerRandom { - styles := []SpinnerStyle{SpinnerHawk, SpinnerBraille, SpinnerBrailleWave, SpinnerDNA, SpinnerScan, SpinnerPulse, SpinnerSnake, SpinnerOrbit} + styles := []SpinnerStyle{SpinnerRho, SpinnerBraille, SpinnerBrailleWave, SpinnerDNA, SpinnerScan, SpinnerPulse, SpinnerSnake, SpinnerOrbit} style = styles[rand.Intn(len(styles))] // #nosec G404 -- non-cryptographic use (random spinner style selection) } frames := spinnerFrames[style] @@ -110,7 +110,7 @@ func (s *BrailleSpinner) Tick() string { s.mu.Lock() s.frame++ s.wavePhase = (s.wavePhase + 1) % spinnerWaveLen - s.dots = (s.dots + 1) % hawkTypingDots + s.dots = (s.dots + 1) % rhoTypingDots s.mu.Unlock() return s.Frame() } diff --git a/cmd/braille_spinner_test.go b/cmd/braille_spinner_test.go index 96c1c6f05..2375c0ebc 100644 --- a/cmd/braille_spinner_test.go +++ b/cmd/braille_spinner_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestBrailleSpinner_Tick(t *testing.T) { @@ -29,7 +29,7 @@ func TestBrailleSpinner_Tick(t *testing.T) { func TestBrailleSpinner_AllStyles(t *testing.T) { styles := []SpinnerStyle{ - SpinnerBraille, SpinnerBrailleWave, SpinnerHawk, SpinnerDNA, + SpinnerBraille, SpinnerBrailleWave, SpinnerRho, SpinnerDNA, SpinnerScan, SpinnerPulse, SpinnerSnake, SpinnerOrbit, SpinnerWing, SpinnerTalons, } @@ -50,14 +50,14 @@ func TestBrailleSpinner_Random(t *testing.T) { } } -func TestHawkSpinner_Frames(t *testing.T) { - if len(hawkSpinnerGlyphs) != 4 { - t.Fatalf("expected 4 compass glyphs, got %d", len(hawkSpinnerGlyphs)) +func TestRhoSpinner_Frames(t *testing.T) { + if len(rhoSpinnerGlyphs) != 4 { + t.Fatalf("expected 4 compass glyphs, got %d", len(rhoSpinnerGlyphs)) } - if hawkSpinnerGlyphs[0] != "◐" { - t.Fatalf("expected first compass frame ◐, got %q", hawkSpinnerGlyphs[0]) + if rhoSpinnerGlyphs[0] != "◐" { + t.Fatalf("expected first compass frame ◐, got %q", rhoSpinnerGlyphs[0]) } - s := NewBrailleSpinner(SpinnerHawk, "Working") + s := NewBrailleSpinner(SpinnerRho, "Working") f0 := s.Frame() if !strings.Contains(f0, "◐") { t.Fatalf("expected compass glyph, got %q", f0) @@ -70,20 +70,20 @@ func TestHawkSpinner_Frames(t *testing.T) { } } -func TestHawkQuadBlock_LegacyFrames(t *testing.T) { - s := NewBrailleSpinner(SpinnerHawkQuad, "Working") +func TestRhoQuadBlock_LegacyFrames(t *testing.T) { + s := NewBrailleSpinner(SpinnerRhoQuad, "Working") if !strings.Contains(s.Frame(), "▛") { t.Fatalf("expected QuadBlock glyph, got %q", s.Frame()) } } -func TestHawkAnimatedDots_PresentInFrame(t *testing.T) { - s := NewBrailleSpinner(SpinnerHawk, "Crafting") +func TestRhoAnimatedDots_PresentInFrame(t *testing.T) { + s := NewBrailleSpinner(SpinnerRho, "Crafting") f := s.Frame() // Three progress dots ride after the verb: one bright, two dim. total := strings.Count(f, icons.CircleFilled()) + strings.Count(f, icons.CircleOutline()) - if total != hawkTypingDots { - t.Errorf("expected %d trailing circle-dots, got %d in %q", hawkTypingDots, total, f) + if total != rhoTypingDots { + t.Errorf("expected %d trailing circle-dots, got %d in %q", rhoTypingDots, total, f) } // Tick advances the highlighted dot position. idxBefore := s.dots diff --git a/cmd/cascade_diag_test.go b/cmd/cascade_diag_test.go index 55aaee42f..521bd443f 100644 --- a/cmd/cascade_diag_test.go +++ b/cmd/cascade_diag_test.go @@ -3,14 +3,14 @@ package cmd import ( "testing" - hawkbranch "github.com/GrayCodeAI/hawk/internal/engine/branching" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + rhobranch "github.com/GrayCodeAI/rho/internal/engine/branching" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) func TestCascadeSelectsForOpenCodeGoHi(t *testing.T) { roles := routing.DefaultRoles("opencodego/minimax-m2.5") t.Logf("cheapest=%s commit=%s", routing.CheapestForProvider("opencodego", "minimax-m2.5"), roles.Commit) - cr := hawkbranch.NewCascadeRouter("opencodego/minimax-m2.5", roles) + cr := rhobranch.NewCascadeRouter("opencodego/minimax-m2.5", roles) cr.Enabled = true got := cr.SelectModel("Hi", "opencodego/minimax-m2.5", "") t.Logf("selected=%s", got) diff --git a/cmd/catalog_startup.go b/cmd/catalog_startup.go index b69aeac86..5bf74fde8 100644 --- a/cmd/catalog_startup.go +++ b/cmd/catalog_startup.go @@ -4,7 +4,7 @@ import ( "context" "os" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) var ( @@ -13,15 +13,15 @@ var ( ) func ensureCatalogBeforeAgent(ctx context.Context, strict bool) error { - opts := hawkconfig.CatalogStartupOptions{ + opts := rhoconfig.CatalogStartupOptions{ ForceRefresh: refreshCatalogFlag, SkipAutoRefresh: skipCatalogRefreshFlag, VerboseOutput: refreshCatalogFlag, } if strict { - return hawkconfig.PrepareCatalogForSession(ctx, os.Stderr, opts) + return rhoconfig.PrepareCatalogForSession(ctx, os.Stderr, opts) } - hawkconfig.StartupCatalogPrefetch(ctx) + rhoconfig.StartupCatalogPrefetch(ctx) return nil } @@ -29,5 +29,5 @@ func startBackgroundCatalogRefresh(ctx context.Context) { if skipCatalogRefreshFlag { return } - hawkconfig.ScheduleBackgroundCatalogRefresh(ctx) + rhoconfig.ScheduleBackgroundCatalogRefresh(ctx) } diff --git a/cmd/chat.go b/cmd/chat.go index 0051afd21..739abe48f 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -25,22 +25,22 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/bridge/sessioncapture" - "github.com/GrayCodeAI/hawk/internal/codegraph" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" - "github.com/GrayCodeAI/hawk/internal/feature/taste" - "github.com/GrayCodeAI/hawk/internal/intelligence/repomap" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/startup" - hawkstorage "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/system/staleness" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" - - "github.com/GrayCodeAI/hawk/internal/conversationarc" + "github.com/GrayCodeAI/rho/internal/bridge/sessioncapture" + "github.com/GrayCodeAI/rho/internal/codegraph" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/feature/taste" + "github.com/GrayCodeAI/rho/internal/intelligence/repomap" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/startup" + rhostorage "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/system/staleness" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" + + "github.com/GrayCodeAI/rho/internal/conversationarc" ) // Types, styles, and model struct are in chat_model.go @@ -49,7 +49,7 @@ import ( // Tool-registry construction (essential/optional tools) is in chat_tools.go // The Bubble Tea event loop (Update, applyPromptArrowKey) is in chat_update.go -const workInputPlaceholder = `Ask Hawk to inspect, edit, or run something... (Shift+Enter for newline, ? for help)` +const workInputPlaceholder = `Ask Rho to inspect, edit, or run something... (Shift+Enter for newline, ? for help)` func genID() string { b := make([]byte, 8) @@ -70,7 +70,7 @@ func prepareSession(sess *engine.Session) (string, *session.Session, error) { } if sessionIDFlag != "" && (resumeID != "" || continueFlag) { // --session-id is ignored when --resume or --continue is also given. - fmt.Fprintf(os.Stderr, "%s\n", auditTint("hawk: --session-id ignored during resume/continue", textMuted)) + fmt.Fprintf(os.Stderr, "%s\n", auditTint("rho: --session-id ignored during resume/continue", textMuted)) } if resumeID == "" && !continueFlag { return id, nil, nil @@ -104,7 +104,7 @@ func prepareSession(sess *engine.Session) (string, *session.Session, error) { return saved.ID, saved, nil } -func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkconfig.Settings, registry *tool.Registry) (chatModel, error) { +func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhoconfig.Settings, registry *tool.Registry) (chatModel, error) { startup.MarkPhase("newChatModel:total") startup.MarkPhase("newChatModel:ui-init") @@ -124,15 +124,15 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco CursorLine: lipgloss.NewStyle(), Base: lipgloss.NewStyle().Foreground(textPrimary), Placeholder: lipgloss.NewStyle().Foreground(textPlaceholder), - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), }, Blurred: textarea.StyleState{ Base: lipgloss.NewStyle().Foreground(textPlaceholder), Placeholder: lipgloss.NewStyle().Foreground(textPlaceholder), - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), }, Cursor: textarea.CursorStyle{ - Color: hawkColor, + Color: rhoColor, }, }) ta.Prompt = icons.ChevronRight() + " " @@ -144,8 +144,8 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco ci.EchoMode = textinput.EchoNormal sp := spinner.New() - sp.Spinner = spinner.Spinner{Frames: hawkSpinnerFrames, FPS: hawkSpinnerFrameInterval} - sp.Style = lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + sp.Spinner = spinner.Spinner{Frames: rhoSpinnerFrames, FPS: rhoSpinnerFrameInterval} + sp.Style = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) startup.EndPhase("newChatModel:ui-init") startup.MarkPhase("newChatModel:effectiveModelAndProvider") @@ -156,9 +156,9 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco startup.MarkPhase("newChatModel:defaultRegistry") startup.EndPhase("newChatModel:defaultRegistry") - startup.MarkPhase("newChatModel:newHawkSession") - sess := newStartupHawkSession(selection, systemPrompt, registry) - startup.EndPhase("newChatModel:newHawkSession") + startup.MarkPhase("newChatModel:newRhoSession") + sess := newStartupRhoSession(selection, systemPrompt, registry) + startup.EndPhase("newChatModel:newRhoSession") startup.MarkPhase("newChatModel:configureSession") if cfgErr := prepareInteractiveSessionStartup(sess, settings); cfgErr != nil { @@ -182,7 +182,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco // Initialize conversation DAG for branching support startup.MarkPhase("newChatModel:dag") - graphPath := filepath.Join(hawkstorage.SessionsDir(), "conversations", sid+".json") + graphPath := filepath.Join(rhostorage.SessionsDir(), "conversations", sid+".json") if graph, err := session.OpenConversationGraph(graphPath, sid); err == nil { sess.SetConversationGraph(graph) } @@ -242,7 +242,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco m.contextualHelp = NewContextualHelp() m.modeManager = shellmode.NewModeManager() m.modeManager.LoadPersistedMode() - m.brailleSpinner = NewBrailleSpinner(SpinnerHawk, "") + m.brailleSpinner = NewBrailleSpinner(SpinnerRho, "") m.brailleSpinner.SetLabel(m.spinnerVerb) startup.EndPhase("newChatModel:lacy-features") @@ -285,7 +285,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco // Prefetch live models for the active provider so footer ctx/pricing stay current. go func() { providerName := effectiveProvider - entries, _ := hawkconfig.ListEngineModels(context.Background(), providerName, false) + entries, _ := rhoconfig.ListEngineModels(context.Background(), providerName, false) opts := configModelOptionsFromEyrie(entries) if len(opts) > 0 { modelCacheMu.Lock() @@ -312,7 +312,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco m.welcomeCache = buildWelcomeMessageWithSnapshot(sess, sid, registry, saved, settings, 0, connectedMCPCount(registry), 0, initWidth, initHeight, quickSnapshot, "") m.messages = append(m.messages, displayMsg{role: "welcome", content: m.welcomeCache}) // First-session control-plane tip (skip when resuming history or when quiet env var is set). - if saved == nil && os.Getenv("HAWK_QUIET_START") == "" && os.Getenv("HAWK_SUPPRESS_HINTS") == "" && os.Getenv("HAWK_QUIET") == "" { + if saved == nil && os.Getenv("RHO_QUIET_START") == "" && os.Getenv("RHO_SUPPRESS_HINTS") == "" && os.Getenv("RHO_QUIET") == "" { m.messages = append(m.messages, displayMsg{role: "system", content: controlPlaneOnboardingHint(sess)}) } startup.EndPhase("newChatModel:welcome") @@ -375,7 +375,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco // the initial UI. go func(currentSessionID string) { if recovered := session.CheckForRecovery(); len(recovered) > 0 { - walDir := hawkstorage.SessionsDir() + walDir := rhostorage.SessionsDir() for _, rid := range recovered { if rid == currentSessionID { continue // current session WAL @@ -391,7 +391,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings hawkco // Warm footer data and the model catalog after the first frame. go func(model chatModel) { startup.MarkPhase("newChatModel:ui-cache-warm") - hawkconfig.RefreshConfigCredSnapshot(context.Background()) + rhoconfig.RefreshConfigCredSnapshot(context.Background()) // Network reachability runs off the startup critical path: an offline // machine stalls here (background) instead of before first paint. if msg := checkNetworkReachability(model.settings); msg != "" { @@ -598,7 +598,7 @@ func runChat() error { // One-time, gated codebase analysis for projects with no context file. // Runs in the background and never blocks startup; fully opt-out via - // HAWK_DISABLE_AUTO_INIT. No-op for projects that already have context. + // RHO_DISABLE_AUTO_INIT. No-op for projects that already have context. maybeAutoInit(context.Background()) ref := &progRef{} @@ -607,7 +607,7 @@ func runChat() error { err error } type startupSettingsResult struct { - settings hawkconfig.Settings + settings rhoconfig.Settings err error } type startupRegistryResult struct { @@ -732,7 +732,7 @@ func runChat() error { fmt.Print(formatQuitResumeMessage(fm.sessionID)) return nil } - hawkC := ansiOrange + rhoC := ansiOrange rst := ansiReset fmt.Print(fm.welcomeCache) @@ -740,10 +740,10 @@ func runChat() error { for _, msg := range fm.messages { switch msg.role { case "user": - fmt.Println(hawkC + "█" + rst + " " + msg.content) + fmt.Println(rhoC + "█" + rst + " " + msg.content) fmt.Println() case "assistant": - fmt.Println(hawkC + icons.Robot() + " " + rst + msg.content) + fmt.Println(rhoC + icons.Robot() + " " + rst + msg.content) fmt.Println() case "system": fmt.Println(dimStyle.Render("● " + msg.content)) @@ -775,12 +775,12 @@ func runChat() error { border := strings.Repeat("─", viewWidth) borderStyle := lipgloss.NewStyle().Foreground(borderDim) fmt.Println(borderStyle.Render(border)) - fmt.Println(lipgloss.NewStyle().Foreground(hawkColor).Bold(true).Render(">") + " ") + fmt.Println(lipgloss.NewStyle().Foreground(rhoColor).Bold(true).Render(">") + " ") fmt.Println(borderStyle.Render(border)) fmt.Println(dimStyle.Render("? for help")) if fm.sessionID != "" { - fmt.Println(dimStyle.Render(fmt.Sprintf("To resume this session, run: hawk --resume %s", fm.sessionID))) + fmt.Println(dimStyle.Render(fmt.Sprintf("To resume this session, run: rho --resume %s", fm.sessionID))) } return nil } diff --git a/cmd/chat_commands.go b/cmd/chat_commands.go index 74ff9340f..b2c368fa2 100644 --- a/cmd/chat_commands.go +++ b/cmd/chat_commands.go @@ -11,8 +11,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/multiagent/parallel" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/multiagent/parallel" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // slashCmdCache caches the slash commands list to avoid rebuilding. @@ -74,7 +74,7 @@ var allSlashCommands = []string{ "/check", "/color", "/commit", "/compact", "/compress", "/config", "/context", "/council", "/design", "/copy", "/cost", "/cron", "/ctx", "/diff", "/doctor", "/drop", "/effort", "/env", "/exit", "/explain", "/export", "/fast", "/feedback", "/files", "/focus", "/follow", "/fork", "/help", "/history", "/home", "/hooks", "/init", - "/integrity", "/keybindings", "/learn", "/lint", "/loop", "/mcp", "/memory", "/metrics", "/model", "/new", + "/integrity", "/keybindings", "/learn", "/lint", "/login", "/loop", "/mcp", "/memory", "/metrics", "/model", "/new", "/hunt", "/insights", "/mode", "/output-style", "/party", "/pin", "/plugin", "/plugins", "/power", "/pr-comments", "/provider-status", "/quit", "/recipe", "/recover", "/reflect", "/refresh-model-catalog", "/release-notes", "/image", "/reload-plugins", "/remote-env", "/rename", "/render", "/research", "/resume", "/retry", "/review", "/rewind", @@ -201,7 +201,7 @@ var slashDescriptions = map[string]string{ "/export": "Export session", "/follow": "Toggle stream follow (auto-scroll)", "/home": "Jump to top of chat and welcome header", - "/feedback": "Submit feedback about hawk", + "/feedback": "Submit feedback about rho", "/fast": "Toggle fast mode", "/files": "Show modified files", "/focus": "Narrow agent attention to specific files/dirs", @@ -213,6 +213,7 @@ var slashDescriptions = map[string]string{ "/init": "Analyze project structure", "/integrity": "Validate session integrity", "/lint": "Run linter, add issues to context", + "/login": "Authenticate a provider (opens the config panel)", "/loop": "Schedule recurring command", "/mcp": "Show MCP server status", "/memory": "Show AGENTS.md project instructions", @@ -243,7 +244,7 @@ var slashDescriptions = map[string]string{ "/status": "Show session info (mode, trust, cost)", "/start": "Guided setup: trust, mode, branch, first tasks", "/trust": "Folder trust status / add / remove", - "/branch-agent": "Create hawk/agent-* branch if on main/master", + "/branch-agent": "Create rho/agent-* branch if on main/master", "/auto-commit": "Toggle git auto-commit after Write/Edit (on|off)", "/summary": "Summarize the session", "/tasks": "Show task list", @@ -252,7 +253,7 @@ var slashDescriptions = map[string]string{ "/tools": "List enabled tools", "/undo": "Undo the most recent file change", "/usage": "Show cost summary", - "/version": "Show hawk version", + "/version": "Show rho version", "/vim": "Toggle vim mode", "/welcome": "Re-print the welcome header", "/ecosystem": "Show eyrie and token-engine integration status", @@ -300,7 +301,7 @@ var slashDescriptions = map[string]string{ "/refresh-model-catalog": "Refresh the model catalog from providers", "/image": "Generate or process images", "/recipe": "Run a saved recipe (command template)", - "/soul": "Show or update hawk's personality/soul", + "/soul": "Show or update rho's personality/soul", "/mode": "Switch interaction mode", "/party": "Start a multi-agent party session", } diff --git a/cmd/chat_commands_config.go b/cmd/chat_commands_config.go index 6d6251847..3c32974f8 100644 --- a/cmd/chat_commands_config.go +++ b/cmd/chat_commands_config.go @@ -6,14 +6,14 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) // handleConfigCommand handles the /config command and all its subcommands. func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, tea.Cmd) { if len(parts) >= 3 && parts[1] == "provider" { value := strings.TrimSpace(strings.Join(parts[2:], " ")) - if err := hawkconfig.SetGlobalSetting("provider", value); err != nil { + if err := rhoconfig.SetGlobalSetting("provider", value); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } @@ -24,7 +24,7 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, modelCacheMu.RUnlock() if cacheHit && len(cached) > 0 { m.session.SetModel(cached[0].ID) - _ = hawkconfig.SetGlobalSetting("model", cached[0].ID) + _ = rhoconfig.SetGlobalSetting("model", cached[0].ID) } m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Provider set to: %s\nModel: %s\nSaved in eyrie (provider.json).", value, m.session.Model())}) return m, nil @@ -47,7 +47,7 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, return m, nil } } - if err := hawkconfig.SetGlobalSetting("model", value); err != nil { + if err := rhoconfig.SetGlobalSetting("model", value); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } @@ -72,7 +72,7 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } - value, ok := hawkconfig.SettingValue(settings, parts[2]) + value, ok := rhoconfig.SettingValue(settings, parts[2]) if !ok { m.messages = append(m.messages, displayMsg{role: "error", content: fmt.Sprintf("Unsupported setting key %q", parts[2])}) return m, nil @@ -86,7 +86,7 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, if len(parts) >= 4 && parts[1] == "set" { key := parts[2] value := strings.TrimSpace(strings.Join(parts[3:], " ")) - if err := hawkconfig.SetGlobalSetting(key, value); err != nil { + if err := rhoconfig.SetGlobalSetting(key, value); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } diff --git a/cmd/chat_commands_image.go b/cmd/chat_commands_image.go index 079218b28..3b935b9c2 100644 --- a/cmd/chat_commands_image.go +++ b/cmd/chat_commands_image.go @@ -8,7 +8,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // handleImageCommand implements the /image slash command: diff --git a/cmd/chat_commands_session.go b/cmd/chat_commands_session.go index 446ac1b56..005d4c305 100644 --- a/cmd/chat_commands_session.go +++ b/cmd/chat_commands_session.go @@ -11,8 +11,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" ) type sessionSaveResultMsg struct { @@ -85,9 +85,9 @@ func (m *chatModel) saveSessionCmd() tea.Cmd { func formatQuitResumeMessage(sessionID string) string { if strings.TrimSpace(sessionID) == "" { - return "Thank you for using Hawk!\n" + return "Thank you for using Rho!\n" } - return fmt.Sprintf("Thank you for using Hawk!\n\nTo resume this session, run: hawk --resume %s\n", sessionID) + return fmt.Sprintf("Thank you for using Rho!\n\nTo resume this session, run: rho --resume %s\n", sessionID) } // handleSessionCommand dispatches session-management slash commands. diff --git a/cmd/chat_commands_skills.go b/cmd/chat_commands_skills.go index eb1a09e62..2d2450bf3 100644 --- a/cmd/chat_commands_skills.go +++ b/cmd/chat_commands_skills.go @@ -10,9 +10,9 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // handleSkillsCommand handles the /skills command and all its subcommands. diff --git a/cmd/chat_commands_test.go b/cmd/chat_commands_test.go index 791f3ed20..c7f27e4e5 100644 --- a/cmd/chat_commands_test.go +++ b/cmd/chat_commands_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestAdditionalDirContextLoadsInstructions(t *testing.T) { @@ -69,7 +69,7 @@ func TestLocalSlashCommands(t *testing.T) { m := &chatModel{ session: sess, registry: tool.NewRegistry(tool.LSTool{}), - settings: hawkconfig.Settings{MCPServers: []hawkconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}}, + settings: rhoconfig.Settings{MCPServers: []rhoconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}}, sessionID: "test", width: 80, height: 24, @@ -94,16 +94,16 @@ func TestLocalSlashCommands(t *testing.T) { func TestDiagnosticSummaries(t *testing.T) { preserveCLICompilerVersionState(t) version = "test-version" - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ Provider: "openai", Model: "gpt-4o", - MCPServers: []hawkconfig.MCPServerConfig{ + MCPServers: []rhoconfig.MCPServerConfig{ {Name: "demo", Command: "demo-mcp", Args: []string{"--stdio"}}, }, } report := doctorReport(settings) - if !strings.Contains(report, "Hawk doctor") || !strings.Contains(report, "Built-in tools") { + if !strings.Contains(report, "Rho doctor") || !strings.Contains(report, "Built-in tools") { t.Fatalf("unexpected doctor report: %s", report) } if summary := mcpConfigSummary(settings); !strings.Contains(summary, "demo") { diff --git a/cmd/chat_commands_tools.go b/cmd/chat_commands_tools.go index e53bb2cd5..2878ed3c6 100644 --- a/cmd/chat_commands_tools.go +++ b/cmd/chat_commands_tools.go @@ -8,9 +8,9 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // explainCode traces a file/line back to the git commit and session that created it. @@ -109,12 +109,12 @@ func (m *chatModel) handleShellEscape(command string) (tea.Model, tea.Cmd) { // handleNamespacedSkill handles /vendor:skill-name invocations. func (m *chatModel) handleNamespacedSkill(cmd, fullText string) (tea.Model, tea.Cmd) { // Parse /vendor:skill-name - invoke := cmd // e.g. "/hawk:go-review" + invoke := cmd // e.g. "/rho:go-review" // Search active and installed skills for matching invoke pattern var matched *plugin.SmartSkill for name, skill := range m.activeSkills { - if skill.Invoke == invoke || "/hawk:"+name == invoke { + if skill.Invoke == invoke || "/rho:"+name == invoke { matched = &skill break } @@ -124,7 +124,7 @@ func (m *chatModel) handleNamespacedSkill(cmd, fullText string) (tea.Model, tea. // Try loading from installed skills skills := plugin.LoadSmartSkills(plugin.DefaultSkillDirs()) for i := range skills { - if skills[i].Invoke == invoke || "/hawk:"+skills[i].Name == invoke { + if skills[i].Invoke == invoke || "/rho:"+skills[i].Name == invoke { matched = &skills[i] break } diff --git a/cmd/chat_commands_util.go b/cmd/chat_commands_util.go index d0352176c..00bd46dd2 100644 --- a/cmd/chat_commands_util.go +++ b/cmd/chat_commands_util.go @@ -8,11 +8,11 @@ import ( "path/filepath" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/feature/taste" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/system/staleness" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/feature/taste" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/system/staleness" ) func gitOutput(args ...string) (string, error) { @@ -73,7 +73,7 @@ func additionalDirContext(dir string) (string, string, error) { } var b strings.Builder b.WriteString("Additional directory: " + abs) - if md := hawkconfig.LoadAgentsMDFrom(abs); md != "" { + if md := rhoconfig.LoadAgentsMDFrom(abs); md != "" { b.WriteString("\nAdditional directory instructions (" + abs + "):\n" + md) } return abs, b.String(), nil @@ -129,7 +129,7 @@ func sessionStats(sess *engine.Session, id string) string { } func hooksSummary() string { - return "Hooks: pre_query, post_query, pre_tool, post_tool, session_start, session_end, permission_ask, error\nConfigure in Hawk user settings" + return "Hooks: pre_query, post_query, pre_tool, post_tool, session_start, session_end, permission_ask, error\nConfigure in Rho user settings" } func pluginsSummary(rt *plugin.Runtime) string { diff --git a/cmd/chat_config_deployment.go b/cmd/chat_config_deployment.go index 7c4718ba5..85e5ebcab 100644 --- a/cmd/chat_config_deployment.go +++ b/cmd/chat_config_deployment.go @@ -9,9 +9,9 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) type configApplyCredentialsMsg struct { @@ -24,39 +24,39 @@ type configApplyCredentialsMsg struct { func firstRunModelProvider(m chatModel) string { ctx := context.Background() - if p := strings.TrimSpace(m.configModelProvider); p != "" && hawkconfig.HasStoredCredentialForProvider(ctx, p) { + if p := strings.TrimSpace(m.configModelProvider); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { return p } if m.session != nil { - if p := strings.TrimSpace(m.session.Provider()); p != "" && hawkconfig.HasStoredCredentialForProvider(ctx, p) { + if p := strings.TrimSpace(m.session.Provider()); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { return p } } - if p := strings.TrimSpace(hawkconfig.ActiveGateway(ctx)); p != "" && hawkconfig.HasStoredCredentialForProvider(ctx, p) { + if p := strings.TrimSpace(rhoconfig.ActiveGateway(ctx)); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { return p } - if p := hawkconfig.DefaultModelProviderFilter(ctx); p != "" && hawkconfig.HasStoredCredentialForProvider(ctx, p) { + if p := rhoconfig.DefaultModelProviderFilter(ctx); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { return p } - for _, p := range hawkconfig.AllSetupGateways() { - if hawkconfig.HasStoredCredentialForProvider(ctx, p) { + for _, p := range rhoconfig.AllSetupGateways() { + if rhoconfig.HasStoredCredentialForProvider(ctx, p) { return p } } return "" } -func saveProviderKeyAsync(inference hawkconfig.CredentialInference, secret string) tea.Cmd { +func saveProviderKeyAsync(inference rhoconfig.CredentialInference, secret string) tea.Cmd { return saveCredentialAsync(inference, secret) } func saveOllamaAsync(baseURL string) tea.Cmd { return func() tea.Msg { - inference, err := hawkconfig.LocalCredentialInference(configProviderOllama) + inference, err := rhoconfig.LocalCredentialInference(configProviderOllama) if err != nil { return configApplyCredentialsMsg{err: err} } - inf := hawkconfig.CredentialInference{ + inf := rhoconfig.CredentialInference{ ProviderID: inference.ProviderID, DeploymentID: inference.DeploymentID, EnvVar: inference.EnvVar, @@ -66,22 +66,22 @@ func saveOllamaAsync(baseURL string) tea.Cmd { } } -func saveCredentialAsync(inference hawkconfig.CredentialInference, secret string) tea.Cmd { +func saveCredentialAsync(inference rhoconfig.CredentialInference, secret string) tea.Cmd { return func() tea.Msg { ctx := context.Background() - if err := hawkconfig.SaveCredential(ctx, inference, secret); err != nil { + if err := rhoconfig.SaveCredential(ctx, inference, secret); err != nil { return configApplyCredentialsMsg{ err: err, providerID: inference.ProviderID, deploymentID: inference.DeploymentID, } } - hawkconfig.InvalidateConfigUICache() - hawkconfig.RefreshConfigCredSnapshot(ctx) - result, err := hawkconfig.ApplyEyrieCredentialsForProvider(ctx, inference.ProviderID) - if err != nil && hawkconfig.IsCatalogCacheRequired(err) { - if refreshErr := hawkconfig.RefreshCatalogAfterCredentials(ctx, nil); refreshErr == nil { - result, err = hawkconfig.ApplyEyrieCredentialsForProvider(ctx, inference.ProviderID) + rhoconfig.InvalidateConfigUICache() + rhoconfig.RefreshConfigCredSnapshot(ctx) + result, err := rhoconfig.ApplyEyrieCredentialsForProvider(ctx, inference.ProviderID) + if err != nil && rhoconfig.IsCatalogCacheRequired(err) { + if refreshErr := rhoconfig.RefreshCatalogAfterCredentials(ctx, nil); refreshErr == nil { + result, err = rhoconfig.ApplyEyrieCredentialsForProvider(ctx, inference.ProviderID) } else { err = fmt.Errorf("%w; automatic catalog refresh failed: %w", err, refreshErr) } @@ -94,7 +94,7 @@ func saveCredentialAsync(inference hawkconfig.CredentialInference, secret string } } - entries, listErr := hawkconfig.ListEngineModels(ctx, inference.ProviderID, false) + entries, listErr := rhoconfig.ListEngineModels(ctx, inference.ProviderID, false) if listErr != nil { return configApplyCredentialsMsg{ err: listErr, @@ -104,7 +104,7 @@ func saveCredentialAsync(inference hawkconfig.CredentialInference, secret string } opts := configModelOptionsFromEyrie(entries) return configApplyCredentialsMsg{ - summary: hawkconfig.FormatApplyCredentialsSummary(result), + summary: rhoconfig.FormatApplyCredentialsSummary(result), providerID: inference.ProviderID, deploymentID: inference.DeploymentID, modelOptions: opts, @@ -134,15 +134,15 @@ func (m chatModel) startConfigURLInput(defaultURL string) (chatModel, tea.Cmd) { m.configInput.EchoMode = textinput.EchoNormal m.configInput.SetStyles(textinput.Styles{ Focused: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Blurred: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Cursor: textinput.CursorStyle{ - Color: hawkColor, + Color: rhoColor, }, }) m.configInput.Focus() @@ -152,27 +152,27 @@ func (m chatModel) startConfigURLInput(defaultURL string) (chatModel, tea.Cmd) { func (m chatModel) handleConfigApplyCredentialsMsg(msg configApplyCredentialsMsg) (chatModel, tea.Cmd) { m.configSaving = false ctx := context.Background() - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.RefreshConfigCredSnapshot(ctx) m = m.refreshConfigGatewayRows() if msg.err != nil { m.invalidateConnStatus() if msg.providerID == configProviderOllama { return m.returnToOllamaURLAfterError(msg.err) } - notice := sanitizeConfigNotice(hawkconfig.FormatConfigProviderError(msg.providerID, msg.err)) - saved := hawkconfig.HasStoredCredentialForProvider(ctx, msg.providerID) || + notice := sanitizeConfigNotice(rhoconfig.FormatConfigProviderError(msg.providerID, msg.err)) + saved := rhoconfig.HasStoredCredentialForProvider(ctx, msg.providerID) || strings.Contains(strings.ToLower(msg.err.Error()), "key saved in keychain") if saved { - if hawkconfig.IsCatalogCacheRequired(msg.err) { + if rhoconfig.IsCatalogCacheRequired(msg.err) { notice = "Key saved in " + credentialsStoreLabel() + " — model catalog unavailable: " + notice - notice += " · run hawk models refresh" + notice += " · run rho models refresh" } else if configCredentialRejected(msg.err) { notice = "Key saved in " + credentialsStoreLabel() + " — provider rejected this key: " + notice } else { notice = "Key saved in " + credentialsStoreLabel() + " — catalog refresh failed: " + notice } - if !hawkconfig.IsCatalogCacheRequired(msg.err) && !strings.Contains(strings.ToLower(notice), "refresh") { - notice += " · press r on " + hawkconfig.GatewayDisplayName(msg.providerID) + " to retry" + if !rhoconfig.IsCatalogCacheRequired(msg.err) && !strings.Contains(strings.ToLower(notice), "refresh") { + notice += " · press r on " + rhoconfig.GatewayDisplayName(msg.providerID) + " to retry" } } else { notice = "Could not save key — " + notice @@ -206,11 +206,11 @@ func (m chatModel) handleConfigApplyCredentialsMsg(msg configApplyCredentialsMsg if idx := next.configGatewayRowIndex(post); idx >= 0 { next.configSel = idx } - next.configNotice = "Key updated for " + hawkconfig.GatewayDisplayName(post) + next.configNotice = "Key updated for " + rhoconfig.GatewayDisplayName(post) return next, cmd } if msg.providerID == configProviderOllama { - _ = hawkconfig.SetGlobalSetting("provider", configProviderOllama) + _ = rhoconfig.SetGlobalSetting("provider", configProviderOllama) next.syncSessionSelection() } next.configGuideAfterKey = false @@ -253,9 +253,9 @@ func configCredentialRejected(err error) bool { } func (m chatModel) rebuildSessionTransport() (chatModel, tea.Cmd) { - m.settings = hawkconfig.LoadSettings() + m.settings = rhoconfig.LoadSettings() m.syncSessionSelection() - selection := hawkconfig.EffectiveSelectionWithSettings(context.Background(), m.settings, hawkconfig.SelectionOptions{ + selection := rhoconfig.EffectiveSelectionWithSettings(context.Background(), m.settings, rhoconfig.SelectionOptions{ ProviderOverride: firstNonEmptyTrimmed(m.session.Provider(), m.settings.Provider), ModelOverride: firstNonEmptyTrimmed(m.session.Model(), m.settings.Model), }) diff --git a/cmd/chat_config_gateways.go b/cmd/chat_config_gateways.go index 23c086616..9f96f83ef 100644 --- a/cmd/chat_config_gateways.go +++ b/cmd/chat_config_gateways.go @@ -7,8 +7,8 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) type configGatewayRow struct { @@ -47,7 +47,7 @@ func (m chatModel) loadConfigGatewayRows() []configGatewayRow { if m.session != nil { activeModel = strings.TrimSpace(m.session.Model()) } - statuses := hawkconfig.GatewayStatuses(ctx, active, activeModel) + statuses := rhoconfig.GatewayStatuses(ctx, active, activeModel) rows := make([]configGatewayRow, 0, len(statuses)) for _, status := range statuses { if status.ID == "" { @@ -64,10 +64,10 @@ func (m chatModel) loadConfigGatewayRows() []configGatewayRow { hasKey := status.HasStoredCredential credentialEnv, keyConflict := "", false if hasKey { - credentialEnv, keyConflict = hawkconfig.CredentialEnvironmentConflict(ctx, status.ID) + credentialEnv, keyConflict = rhoconfig.CredentialEnvironmentConflict(ctx, status.ID) } display := status.DisplayName - if status.RegionRequired || status.RegionLabel != "" || hawkconfig.HasRegionOptions(status.ID) { + if status.RegionRequired || status.RegionLabel != "" || rhoconfig.HasRegionOptions(status.ID) { if reg := status.RegionLabel; reg != "" { display += " · " + reg } else if status.RegionRequired { @@ -82,7 +82,7 @@ func (m chatModel) loadConfigGatewayRows() []configGatewayRow { HasKey: hasKey, Configured: status.HasConfiguredDeployment || hasKey, ModelCount: count, - Active: status.Active || hawkconfig.ActiveProviderID(status.ID) == hawkconfig.ActiveProviderID(active), + Active: status.Active || rhoconfig.ActiveProviderID(status.ID) == rhoconfig.ActiveProviderID(active), RegionLabel: status.RegionLabel, RegionRequired: status.RegionRequired, CredentialEnv: credentialEnv, @@ -266,18 +266,18 @@ func (m chatModel) configGatewaysView() string { ctx := context.Background() indent := strings.Repeat(" ", configTableIndent) if m.configKeysPendingRemove != "" { - name := hawkconfig.GatewayDisplayName(m.configKeysPendingRemove) + name := rhoconfig.GatewayDisplayName(m.configKeysPendingRemove) b.WriteString("\n" + mutedStyle.Render(indent+configGatewayRemovePrompt(m.configKeysRemoveStep, name))) - } else if !hawkconfig.HasConfiguredDeploymentCached(ctx) { + } else if !rhoconfig.HasConfiguredDeploymentCached(ctx) { hint := "Select a gateway · enter · paste API key · then Models tab" - if targetIdx >= 0 && targetIdx < len(rows) && (rows[targetIdx].RegionRequired || rows[targetIdx].RegionLabel != "" || hawkconfig.HasRegionOptions(rows[targetIdx].ID)) { + if targetIdx >= 0 && targetIdx < len(rows) && (rows[targetIdx].RegionRequired || rows[targetIdx].RegionLabel != "" || rhoconfig.HasRegionOptions(rows[targetIdx].ID)) { hint = rows[targetIdx].DisplayName + ": enter pick region then key · g change region" } b.WriteString("\n" + mutedStyle.Render(indent+hint)) } else { hints := "enter use gateway · k view key · delete remove · r refresh" - if targetIdx >= 0 && targetIdx < len(rows) && (rows[targetIdx].RegionRequired || rows[targetIdx].RegionLabel != "" || hawkconfig.HasRegionOptions(rows[targetIdx].ID)) { + if targetIdx >= 0 && targetIdx < len(rows) && (rows[targetIdx].RegionRequired || rows[targetIdx].RegionLabel != "" || rhoconfig.HasRegionOptions(rows[targetIdx].ID)) { hints = "enter · g region · k key · delete · r refresh" } @@ -321,7 +321,7 @@ func (m chatModel) handleConfigGatewaysSelect() (chatModel, tea.Cmd) { return m, nil } row := rows[m.configSel] - if (row.RegionRequired || hawkconfig.HasRegionOptions(row.ID)) && (!row.HasKey || row.RegionRequired) { + if (row.RegionRequired || rhoconfig.HasRegionOptions(row.ID)) && (!row.HasKey || row.RegionRequired) { m.configGatewayFocus = m.configSel return m.startConfigGatewayRegion(row.ID), nil } @@ -336,8 +336,8 @@ func (m chatModel) handleConfigGatewaysSelect() (chatModel, tea.Cmd) { gw := row.ID m.configGatewayFocus = m.configSel m.configModelProvider = gw - _ = hawkconfig.SetGlobalSetting("provider", gw) - if active := hawkconfig.ActiveProvider(context.Background()); active != "" { + _ = rhoconfig.SetGlobalSetting("provider", gw) + if active := rhoconfig.ActiveProvider(context.Background()); active != "" { m.session.SetProvider(active) } m.configTab = configTabModels @@ -349,7 +349,7 @@ func (m chatModel) handleConfigGatewaysSelect() (chatModel, tea.Cmd) { func refreshGatewayAsync(providerID string) tea.Cmd { return func() tea.Msg { - summary, err := hawkconfig.RefreshGatewayCatalog(context.Background(), providerID) + summary, err := rhoconfig.RefreshGatewayCatalog(context.Background(), providerID) return configGatewayRefreshMsg{providerID: providerID, summary: summary, err: err} } } @@ -359,7 +359,7 @@ func (m chatModel) handleConfigGatewayRefreshMsg(msg configGatewayRefreshMsg) ch InvalidateModelCacheProvider(msg.providerID) m = m.refreshConfigGatewayRows() if msg.err != nil { - m.configNotice = sanitizeConfigNotice(hawkconfig.FormatConfigProviderError(msg.providerID, msg.err)) + m.configNotice = sanitizeConfigNotice(rhoconfig.FormatConfigProviderError(msg.providerID, msg.err)) return m } m.configNotice = msg.summary diff --git a/cmd/chat_config_gateways_test.go b/cmd/chat_config_gateways_test.go index 9de3552e6..434edea1a 100644 --- a/cmd/chat_config_gateways_test.go +++ b/cmd/chat_config_gateways_test.go @@ -7,9 +7,9 @@ import ( "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func chatModelForConfigPasteTest() chatModel { @@ -37,12 +37,12 @@ func TestConfigGatewayRows_UsesPanelCacheUntilInvalidated(t *testing.T) { func TestConfigGatewayRowsKeepRegistryOrderAfterKeySaved(t *testing.T) { isolateCredentialHome(t) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{} @@ -50,7 +50,7 @@ func TestConfigGatewayRowsKeepRegistryOrderAfterKeySaved(t *testing.T) { if err := store.Set(t.Context(), gateway.AccountForEnv("CONCENTRATE_API_KEY"), "test-key-1234567890"); err != nil { t.Fatal(err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() after := m.loadConfigGatewayRows() if len(after) != len(before) { @@ -65,12 +65,12 @@ func TestConfigGatewayRowsKeepRegistryOrderAfterKeySaved(t *testing.T) { func TestConfigGatewaysView_RequiresKeyForModelCounts(t *testing.T) { isolateCredentialHome(t) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{configTab: configTabGateways} @@ -87,12 +87,12 @@ func TestConfigGatewaysView_RequiresKeyForModelCounts(t *testing.T) { } func TestConfigGatewaysView_ShowsSaveOrProbeNotice(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{ @@ -109,17 +109,17 @@ func TestConfigGatewaysView_ShowsSaveOrProbeNotice(t *testing.T) { } func TestConfigGatewayRefreshTargetIndex_UsesSelectedRow(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() sess := engine.NewSession("", "", "", nil) sess.SetProvider("openrouter") @@ -138,17 +138,17 @@ func TestConfigGatewayRefreshTargetIndex_UsesSelectedRow(t *testing.T) { } func TestConfigGatewayRefreshTargetIndex_UsesFocusOnRefreshRow(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() rows := []configGatewayRow{ {ID: "openai", DisplayName: "OpenAI", HasKey: false}, @@ -162,17 +162,17 @@ func TestConfigGatewayRefreshTargetIndex_UsesFocusOnRefreshRow(t *testing.T) { } func TestFocusConfigActiveGateway_SelectsActiveRow(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() sess := engine.NewSession("", "", "", nil) sess.SetProvider("openrouter") @@ -196,19 +196,19 @@ func TestFocusConfigActiveGateway_SelectsActiveRow(t *testing.T) { } func TestHandleConfigGatewaysSelect_TokenPlanNoKeyShowsRegion(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{configTab: configTabGateways} rows := m.configGatewayRows() idx := -1 for i, row := range rows { - if row.ID == hawkconfig.ProviderXiaomiTokenPlan { + if row.ID == rhoconfig.ProviderXiaomiTokenPlan { idx = i break } @@ -221,18 +221,18 @@ func TestHandleConfigGatewaysSelect_TokenPlanNoKeyShowsRegion(t *testing.T) { if next.configEntry != configEntryXiaomiRegion { t.Fatalf("entry = %q, want xiaomi region picker", next.configEntry) } - if next.configProvider != hawkconfig.ProviderXiaomiTokenPlan { + if next.configProvider != rhoconfig.ProviderXiaomiTokenPlan { t.Fatalf("provider = %q", next.configProvider) } } func TestHandleConfigGatewaysSelect_NoKeyStartsPaste(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModelForConfigPasteTest() @@ -243,7 +243,7 @@ func TestHandleConfigGatewaysSelect_NoKeyStartsPaste(t *testing.T) { } sel := 0 for i, row := range gwRows { - if row.ID == hawkconfig.ProviderXiaomiTokenPlan { + if row.ID == rhoconfig.ProviderXiaomiTokenPlan { continue } sel = i diff --git a/cmd/chat_config_hub.go b/cmd/chat_config_hub.go index 58ca7431f..8fd3527ac 100644 --- a/cmd/chat_config_hub.go +++ b/cmd/chat_config_hub.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func (m chatModel) openConfigPanel() (chatModel, tea.Cmd) { @@ -69,7 +69,7 @@ func catalogPricesAreStale(opts []configModelOption) bool { } func providerHasLiveFetcher(providerID string) bool { - return hawkconfig.GatewaySupportsLiveDiscovery(providerID) + return rhoconfig.GatewaySupportsLiveDiscovery(providerID) } func (m chatModel) returnToOllamaURLAfterError(err error) (chatModel, tea.Cmd) { @@ -80,7 +80,7 @@ func (m chatModel) returnToOllamaURLAfterError(err error) (chatModel, tea.Cmd) { url = configDefaultOllamaURL } if err != nil { - m.configNotice = hawkconfig.FormatConfigProviderError(configProviderOllama, err) + m.configNotice = rhoconfig.FormatConfigProviderError(configProviderOllama, err) } return m.startConfigOllamaURLWithValue(url) } diff --git a/cmd/chat_config_keys.go b/cmd/chat_config_keys.go index d766204de..aa88fd77f 100644 --- a/cmd/chat_config_keys.go +++ b/cmd/chat_config_keys.go @@ -7,11 +7,11 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func credentialsStoreLabel() string { - return hawkconfig.CredentialStoreName() + return rhoconfig.CredentialStoreName() } func configGatewayRemovePrompt(step int, gatewayName string) string { @@ -37,15 +37,15 @@ func (m chatModel) configKeyDetailView() string { accentStyle := configAccentStyle() activeStyle := configActiveStyle() providerName := strings.TrimSpace(m.configProvider) - displayName := hawkconfig.GatewayDisplayName(providerName) - masked := hawkconfig.MaskCredentialForProvider(context.Background(), providerName) + displayName := rhoconfig.GatewayDisplayName(providerName) + masked := rhoconfig.MaskCredentialForProvider(context.Background(), providerName) var b strings.Builder b.WriteString(renderConfigBreadcrumb(displayName+" key") + "\n\n") b.WriteString(mutedStyle.Render(" Gateway: ") + accentStyle.Render(displayName) + "\n") b.WriteString(mutedStyle.Render(" Key: ") + activeStyle.Render(masked) + "\n") b.WriteString(mutedStyle.Render(" Stored in: "+credentialsStoreLabel()) + "\n") - if reg := hawkconfig.GatewayRegionLabel(providerName); reg != "" || hawkconfig.NeedsGatewayRegion(providerName) || hawkconfig.HasRegionOptions(providerName) { + if reg := rhoconfig.GatewayRegionLabel(providerName); reg != "" || rhoconfig.NeedsGatewayRegion(providerName) || rhoconfig.HasRegionOptions(providerName) { if reg == "" { reg = "(not set — press g)" } @@ -68,32 +68,32 @@ func (m chatModel) startConfigKeyForProvider(provider string) (chatModel, tea.Cm if provider == "" { return m, nil } - if hawkconfig.NeedsGatewayRegion(provider) { + if rhoconfig.NeedsGatewayRegion(provider) { m.configPostSaveKeysProvider = provider return m.startConfigGatewayRegion(provider), nil } - name := hawkconfig.GatewayDisplayName(provider) + name := rhoconfig.GatewayDisplayName(provider) m.configNotice = "Paste API key for " + name return m.startConfigEntry(configEntryAPIKeyPaste, provider) } func (m chatModel) startConfigKeyReplace(provider string) (chatModel, tea.Cmd) { - if hawkconfig.NeedsGatewayRegion(provider) { + if rhoconfig.NeedsGatewayRegion(provider) { m.configPostSaveKeysProvider = provider return m.startConfigGatewayRegion(provider), nil } m.configReplaceProvider = provider m.configEntry = configEntryNone - m.configNotice = "Paste replacement API key for " + hawkconfig.GatewayDisplayName(provider) + m.configNotice = "Paste replacement API key for " + rhoconfig.GatewayDisplayName(provider) return m.startConfigEntry(configEntryAPIKeyPaste, provider) } func (m chatModel) beginConfigGatewayKeyRemove(provider string) chatModel { m.configKeysPendingRemove = provider m.configKeysRemoveStep = 1 - name := hawkconfig.GatewayDisplayName(provider) + name := rhoconfig.GatewayDisplayName(provider) m.configNotice = configGatewayRemoveNotice(1, name) return m } @@ -116,7 +116,7 @@ func (m chatModel) advanceConfigGatewayKeyRemove() (chatModel, tea.Cmd) { } if m.configKeysRemoveStep < 2 { m.configKeysRemoveStep = 2 - name := hawkconfig.GatewayDisplayName(trimmedProvider) + name := rhoconfig.GatewayDisplayName(trimmedProvider) m.configNotice = configGatewayRemoveNotice(2, name) return m, nil } @@ -131,7 +131,7 @@ func (m chatModel) confirmConfigGatewayKeyRemove() (chatModel, tea.Cmd) { m.configKeysPendingRemove = "" m.configKeysRemoveStep = 0 m.configSaving = true - m.configNotice = fmt.Sprintf("Removing key for %s…", hawkconfig.GatewayDisplayName(trimmedProvider)) + m.configNotice = fmt.Sprintf("Removing key for %s…", rhoconfig.GatewayDisplayName(trimmedProvider)) if m.configEntry == configEntryKeyView { m.configEntry = configEntryNone m.configProvider = "" @@ -164,7 +164,7 @@ func (m chatModel) handleConfigKeyViewKey(msg tea.KeyMsg) (chatModel, tea.Cmd) { } return m.startConfigKeyReplace(trimmedProvider) default: - if (hawkconfig.HasRegionOptions(trimmedProvider) || hawkconfig.GatewayRegionLabel(trimmedProvider) != "") && strings.EqualFold(key.Text, "g") { + if (rhoconfig.HasRegionOptions(trimmedProvider) || rhoconfig.GatewayRegionLabel(trimmedProvider) != "") && strings.EqualFold(key.Text, "g") { return m.startConfigGatewayRegion(trimmedProvider), nil } return m, nil diff --git a/cmd/chat_config_keys_test.go b/cmd/chat_config_keys_test.go index 15aee677e..f8502db99 100644 --- a/cmd/chat_config_keys_test.go +++ b/cmd/chat_config_keys_test.go @@ -5,22 +5,22 @@ import ( "testing" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestConfigGatewaysView_KeyHintsWithCredentials(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() m := chatModel{configTab: configTabGateways} view := m.configGatewaysView() @@ -30,17 +30,17 @@ func TestConfigGatewaysView_KeyHintsWithCredentials(t *testing.T) { } func TestConfigGatewaysKeyView_OpenWithK(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() sel := 0 @@ -61,17 +61,17 @@ func TestConfigGatewaysKeyView_OpenWithK(t *testing.T) { } func TestConfigGatewaysDelete_PendingRemove(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() sel := 0 @@ -92,17 +92,17 @@ func TestConfigGatewaysDelete_PendingRemove(t *testing.T) { } func TestConfigGatewaysDelete_DoubleConfirm(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() sel := 0 @@ -131,12 +131,12 @@ func TestConfigGatewaysDelete_DoubleConfirm(t *testing.T) { } func TestOpenConfigRemoveKeyPanel_OpensGatewaysTab(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{} diff --git a/cmd/chat_config_models.go b/cmd/chat_config_models.go index 44522f1f0..cc2e13036 100644 --- a/cmd/chat_config_models.go +++ b/cmd/chat_config_models.go @@ -7,8 +7,8 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" ) // configModelOption is one row in the /config model picker (display from eyrie, id for settings). @@ -42,7 +42,7 @@ func InvalidateModelCache() { modelSyncAttempted = make(map[string]bool) modelSyncMu.Unlock() invalidatePlatformContextCache() - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() } // InvalidateModelCacheProvider drops one gateway's cached picker rows. @@ -54,7 +54,7 @@ func InvalidateModelCacheProvider(provider string) { modelSyncMu.Lock() delete(modelSyncAttempted, provider) modelSyncMu.Unlock() - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() } func fetchModelsAsync(provider string) tea.Cmd { @@ -62,13 +62,13 @@ func fetchModelsAsync(provider string) tea.Cmd { ctx := context.Background() provider = strings.TrimSpace(provider) if provider == "" { - provider = hawkconfig.DefaultModelProviderFilter(ctx) + provider = rhoconfig.DefaultModelProviderFilter(ctx) } - entries, err := hawkconfig.ListEngineModels(ctx, provider, false) + entries, err := rhoconfig.ListEngineModels(ctx, provider, false) if err != nil { - if _, derr := hawkconfig.ListEngineModels(ctx, provider, true); derr == nil { + if _, derr := rhoconfig.ListEngineModels(ctx, provider, true); derr == nil { InvalidateModelCacheProvider(provider) - entries, err = hawkconfig.ListEngineModels(ctx, provider, false) + entries, err = rhoconfig.ListEngineModels(ctx, provider, false) } } if err != nil { @@ -84,7 +84,7 @@ func fetchModelsAsync(provider string) tea.Cmd { } } -func configModelOptionsFromEyrie(entries []hawkconfig.EngineModel) []configModelOption { +func configModelOptionsFromEyrie(entries []rhoconfig.EngineModel) []configModelOption { opts := make([]configModelOption, len(entries)) for i, e := range entries { opts[i] = configModelOption{ @@ -168,10 +168,10 @@ func ensureModelCacheLoaded(provider string) { modelSyncMu.Unlock() ctx := context.Background() - entries, err := hawkconfig.ListEngineModels(ctx, provider, false) + entries, err := rhoconfig.ListEngineModels(ctx, provider, false) if err != nil { - if _, derr := hawkconfig.ListEngineModels(ctx, provider, true); derr == nil { - entries, err = hawkconfig.ListEngineModels(ctx, provider, false) + if _, derr := rhoconfig.ListEngineModels(ctx, provider, true); derr == nil { + entries, err = rhoconfig.ListEngineModels(ctx, provider, false) } } if err != nil || len(entries) == 0 { @@ -250,7 +250,7 @@ func loadConfigModelOptions(provider string) []configModelOption { return cached } modelCacheMu.RUnlock() - entries, err := hawkconfig.ListEngineModels(context.Background(), provider, false) + entries, err := rhoconfig.ListEngineModels(context.Background(), provider, false) if err == nil && len(entries) > 0 { opts := configModelOptionsFromEyrie(entries) modelCacheMu.Lock() diff --git a/cmd/chat_config_models_test.go b/cmd/chat_config_models_test.go index c23732e1b..7526a7e4b 100644 --- a/cmd/chat_config_models_test.go +++ b/cmd/chat_config_models_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestFilterConfigModelOptions(t *testing.T) { @@ -45,7 +45,7 @@ func TestModelOptionIsActive(t *testing.T) { } func TestConfigModelOptionsCarryResolvedEngineIdentity(t *testing.T) { - opts := configModelOptionsFromEyrie([]hawkconfig.EngineModel{{ + opts := configModelOptionsFromEyrie([]rhoconfig.EngineModel{{ ID: "models/gemini-pro", CanonicalID: "google/gemini-pro", ProviderID: "google", GatewayID: "gemini", Capabilities: []string{"tools", "vision"}, }}) diff --git a/cmd/chat_config_panel.go b/cmd/chat_config_panel.go index 006940f5c..648dd2104 100644 --- a/cmd/chat_config_panel.go +++ b/cmd/chat_config_panel.go @@ -9,8 +9,8 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func configModelChoices(opts []configModelOption, showProvider bool) []string { @@ -87,11 +87,11 @@ func (m chatModel) configProviderKeyView() string { title := icons.Key() + " Paste API key" hint := "validates with provider API · stored in " + credentialsStoreLabel() if providerName != "" { - title = icons.Key() + " " + hawkconfig.GatewayDisplayName(providerName) + title = icons.Key() + " " + rhoconfig.GatewayDisplayName(providerName) hint = "paste key for this gateway only · " + hint } - if providerName == hawkconfig.ProviderXiaomiTokenPlan { - reg := hawkconfig.GatewayRegionLabel(hawkconfig.ProviderXiaomiTokenPlan) + if providerName == rhoconfig.ProviderXiaomiTokenPlan { + reg := rhoconfig.GatewayRegionLabel(rhoconfig.ProviderXiaomiTokenPlan) if reg == "" { reg = "not set — esc and pick region with g or enter on gateway row" } @@ -198,7 +198,7 @@ func (m chatModel) configModelsTabView() string { gw = strings.TrimSpace(m.session.Provider()) } if gw != "" { - body.WriteString(renderConfigGatewayLine(hawkconfig.GatewayDisplayName(gw)) + "\n\n") + body.WriteString(renderConfigGatewayLine(rhoconfig.GatewayDisplayName(gw)) + "\n\n") } if len(m.configModelOptions) > 0 || m.configModelSearchActive { @@ -245,15 +245,15 @@ func (m chatModel) startConfigModelSearch() (chatModel, tea.Cmd) { m.configInput.EchoCharacter = 0 m.configInput.SetStyles(textinput.Styles{ Focused: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Blurred: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Cursor: textinput.CursorStyle{ - Color: hawkColor, + Color: rhoColor, }, }) m.configInput.Focus() @@ -333,11 +333,11 @@ func (m chatModel) configActiveModelID() string { return modelName } } - return strings.TrimSpace(hawkconfig.ActiveModel(context.Background())) + return strings.TrimSpace(rhoconfig.ActiveModel(context.Background())) } func modelOptionIsActive(opt configModelOption, activeModelID string) bool { - return modelOptionIsActiveResolved(opt, activeModelID, hawkconfig.CanonicalModelID(context.Background(), activeModelID)) + return modelOptionIsActiveResolved(opt, activeModelID, rhoconfig.CanonicalModelID(context.Background(), activeModelID)) } func modelOptionIsActiveResolved(opt configModelOption, activeModelID, activeCanonicalID string) bool { @@ -364,7 +364,7 @@ func (m chatModel) focusConfigActiveModelSelection() chatModel { return m } activeID := m.configActiveModelID() - activeCanonicalID := hawkconfig.CanonicalModelID(context.Background(), activeID) + activeCanonicalID := rhoconfig.CanonicalModelID(context.Background(), activeID) windowSize := m.configVisibleRows() for i, opt := range opts { if modelOptionIsActiveResolved(opt, activeID, activeCanonicalID) { @@ -405,7 +405,7 @@ func (m chatModel) configModelsBody() string { total := len(opts) allTotal := len(m.configModelOptions) activeModelID := m.configActiveModelID() - activeCanonicalID := hawkconfig.CanonicalModelID(context.Background(), activeModelID) + activeCanonicalID := rhoconfig.CanonicalModelID(context.Background(), activeModelID) windowSize := m.configVisibleRows() maxScroll := maxInt(0, total-windowSize) if m.configScroll > maxScroll { @@ -446,7 +446,7 @@ func (m chatModel) configModelsBody() string { return b.String() } b.WriteString(mutedStyle.Render(" No models available.") + "\n") - if hint := hawkconfig.CatalogEmptyHint(context.Background()); hint != "" { + if hint := rhoconfig.CatalogEmptyHint(context.Background()); hint != "" { b.WriteString(mutedStyle.Render(" "+hint) + "\n") } if gw == configProviderOllama { @@ -523,15 +523,15 @@ func (m chatModel) startConfigEntry(kind, provider string) (chatModel, tea.Cmd) m.configInput.EchoCharacter = '*' m.configInput.SetStyles(textinput.Styles{ Focused: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Blurred: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(hawkColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Cursor: textinput.CursorStyle{ - Color: hawkColor, + Color: rhoColor, }, }) m.configInput.Focus() @@ -580,7 +580,7 @@ func (m chatModel) finishConfigEntry() (chatModel, tea.Cmd) { m.restoreChatInput() return m, nil } - if providerName == hawkconfig.ProviderXiaomiTokenPlan && hawkconfig.NeedsGatewayRegion(providerName) { + if providerName == rhoconfig.ProviderXiaomiTokenPlan && rhoconfig.NeedsGatewayRegion(providerName) { m.configEntry = configEntryNone m.wipeConfigKeyInput() m.restoreChatInput() @@ -592,7 +592,7 @@ func (m chatModel) finishConfigEntry() (chatModel, tea.Cmd) { m.configProvider = "" m.wipeConfigKeyInput() m.restoreChatInput() - inference, err := hawkconfig.CredentialInferenceForProvider(providerName) + inference, err := rhoconfig.CredentialInferenceForProvider(providerName) if err != nil { m.configTab = configTabGateways m.configNotice = "Could not save key: " + sanitizeConfigNotice(err.Error()) @@ -604,7 +604,7 @@ func (m chatModel) finishConfigEntry() (chatModel, tea.Cmd) { m.configPostSaveKeysProvider = providerName m.configSaving = true notice := fmt.Sprintf("Validating key for %s…", inference.DisplayName) - if hint := hawkconfig.CredentialGuidance(providerName, value); hint != "" { + if hint := rhoconfig.CredentialGuidance(providerName, value); hint != "" { notice = hint + " · " + notice } m.configNotice = notice @@ -867,7 +867,7 @@ func (m chatModel) handleConfigKey(msg tea.KeyMsg) (chatModel, tea.Cmd) { return m.startConfigKeyView(row.ID), nil } case "g", "G": - if row, ok := m.selectedConfigGateway(); ok && row.ID == hawkconfig.ProviderXiaomiTokenPlan { + if row, ok := m.selectedConfigGateway(); ok && row.ID == rhoconfig.ProviderXiaomiTokenPlan { return m.startConfigGatewayRegion(row.ID), nil } } @@ -960,7 +960,7 @@ func (m chatModel) selectConfigModelFromOptions(opts []configModelOption) (chatM } selected := opts[m.configSel] modelID := selected.ID - if err := hawkconfig.SetGlobalSetting("model", modelID); err != nil { + if err := rhoconfig.SetGlobalSetting("model", modelID); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m.closeConfigPanel(), nil } @@ -976,7 +976,7 @@ func (m chatModel) selectConfigModelFromOptions(opts []configModelOption) (chatM provider = prov } if provider != "" { - if err := hawkconfig.SetGlobalSetting("provider", provider); err != nil { + if err := rhoconfig.SetGlobalSetting("provider", provider); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: "model set, but saving provider failed: " + err.Error()}) } } @@ -986,7 +986,7 @@ func (m chatModel) selectConfigModelFromOptions(opts []configModelOption) (chatM next.invalidateConnStatus() next = next.stopConfigModelSearch(true) next = next.closeConfigPanel() - if !hawkconfig.EvaluateSetupCached(context.Background()).NeedsSetup { + if !rhoconfig.EvaluateSetupCached(context.Background()).NeedsSetup { next.messages = append(next.messages, displayMsg{ role: "setup_complete", content: next.session.Model(), @@ -1001,19 +1001,19 @@ func (m chatModel) toggleConfigModelThinking() (chatModel, tea.Cmd) { return m, nil } selected := opts[m.configSel] - if !hawkconfig.ModelCapabilitySupportsThinking(selected.Capabilities) { + if !rhoconfig.ModelCapabilitySupportsThinking(selected.Capabilities) { m.configNotice = "Thinking not supported for this model" return m, nil } - settings := hawkconfig.LoadSettings() - pref := hawkconfig.ThinkingPrefForModel(settings, selected.ID) + settings := rhoconfig.LoadSettings() + pref := rhoconfig.ThinkingPrefForModel(settings, selected.ID) // Current effective display: unset → off. Toggle flips that. currentlyOn := false if pref != nil { currentlyOn = *pref } next := !currentlyOn - if err := hawkconfig.SetModelThinking(selected.ID, next); err != nil { + if err := rhoconfig.SetModelThinking(selected.ID, next); err != nil { m.configNotice = err.Error() return m, nil } @@ -1027,7 +1027,7 @@ func (m chatModel) toggleConfigModelThinking() (chatModel, tea.Cmd) { m.configNotice = label + " thinking → off" } // If this is the active model, apply immediately. - if m.session != nil && modelOptionIsActiveResolved(selected, m.configActiveModelID(), hawkconfig.CanonicalModelID(context.Background(), m.configActiveModelID())) { + if m.session != nil && modelOptionIsActiveResolved(selected, m.configActiveModelID(), rhoconfig.CanonicalModelID(context.Background(), m.configActiveModelID())) { m.session.SetThinkingEnabled(&next) } return m, nil @@ -1037,7 +1037,7 @@ func (m chatModel) applyModelThinkingPref(selected configModelOption) { if m.session == nil { return } - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() provider := strings.TrimSpace(m.configModelProvider) if provider == "" { provider = strings.TrimSpace(selected.GatewayID) @@ -1045,5 +1045,5 @@ func (m chatModel) applyModelThinkingPref(selected configModelOption) { if provider == "" { provider = strings.TrimSpace(selected.ProviderID) } - m.session.SetThinkingEnabled(hawkconfig.ResolveThinkingForModel(settings, selected.ID, provider)) + m.session.SetThinkingEnabled(rhoconfig.ResolveThinkingForModel(settings, selected.ID, provider)) } diff --git a/cmd/chat_config_region.go b/cmd/chat_config_region.go index 418842569..4cbba73a6 100644 --- a/cmd/chat_config_region.go +++ b/cmd/chat_config_region.go @@ -7,7 +7,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func gatewayRegionOptionIndex(providerID, region string) int { @@ -15,7 +15,7 @@ func gatewayRegionOptionIndex(providerID, region string) int { if region == "" { return 0 } - opts := hawkconfig.GatewayRegionOptions(providerID) + opts := rhoconfig.GatewayRegionOptions(providerID) for i, r := range opts { if strings.EqualFold(r.Value, region) { return i @@ -32,23 +32,23 @@ func (m chatModel) closeConfigEntry() chatModel { func (m chatModel) startConfigGatewayRegion(providerID string) chatModel { switch providerID { - case hawkconfig.ProviderXiaomiTokenPlan: + case rhoconfig.ProviderXiaomiTokenPlan: m.configEntry = configEntryXiaomiRegion - case hawkconfig.ProviderZAICoding, hawkconfig.ProviderZAIPayg: + case rhoconfig.ProviderZAICoding, rhoconfig.ProviderZAIPayg: m.configEntry = configEntryZAIRegion default: m.configEntry = configEntryGatewayRegion } m.configProvider = providerID idx := 0 - if !hawkconfig.NeedsGatewayRegion(providerID) { - idx = gatewayRegionOptionIndex(providerID, hawkconfig.GatewayRegionLabel(providerID)) + if !rhoconfig.NeedsGatewayRegion(providerID) { + idx = gatewayRegionOptionIndex(providerID, rhoconfig.GatewayRegionLabel(providerID)) } m.configGatewayRegionSel = idx m.configZAIRegionSel = idx - name := hawkconfig.GatewayDisplayName(providerID) + name := rhoconfig.GatewayDisplayName(providerID) notice := fmt.Sprintf("Select %s region (↑↓ · enter · esc cancel)", name) - if saved := hawkconfig.GatewayRegionLabel(providerID); saved != "" { + if saved := rhoconfig.GatewayRegionLabel(providerID); saved != "" { notice = fmt.Sprintf("%s region · current %s (↑↓ · enter · esc cancel)", name, saved) } m.configNotice = notice @@ -61,9 +61,9 @@ func (m chatModel) configGatewayRegionView() string { rowStyle := configRowStyle() var b strings.Builder prov := m.configProvider - name := hawkconfig.GatewayDisplayName(prov) + name := rhoconfig.GatewayDisplayName(prov) b.WriteString(renderConfigBreadcrumb(name+" region") + "\n\n") - opts := hawkconfig.GatewayRegionOptions(prov) + opts := rhoconfig.GatewayRegionOptions(prov) for i, r := range opts { prefix := " " if i == m.configGatewayRegionSel { @@ -84,7 +84,7 @@ func (m chatModel) configGatewayRegionView() string { } func (m chatModel) handleConfigGatewayRegionKey(msg tea.KeyMsg) (chatModel, tea.Cmd) { - opts := hawkconfig.GatewayRegionOptions(m.configProvider) + opts := rhoconfig.GatewayRegionOptions(m.configProvider) count := len(opts) if count == 0 { return m.closeConfigEntry(), nil @@ -105,7 +105,7 @@ func (m chatModel) handleConfigGatewayRegionKey(msg tea.KeyMsg) (chatModel, tea. case "enter": if m.configGatewayRegionSel >= 0 && m.configGatewayRegionSel < count { chosen := opts[m.configGatewayRegionSel] - if err := hawkconfig.SetGatewayRegion(m.configProvider, chosen.Value); err != nil { + if err := rhoconfig.SetGatewayRegion(m.configProvider, chosen.Value); err != nil { m.configNotice = "Error saving region: " + err.Error() return m, nil } @@ -116,8 +116,8 @@ func (m chatModel) handleConfigGatewayRegionKey(msg tea.KeyMsg) (chatModel, tea. m.configPostSaveKeysProvider = "" return m.startConfigKeyReplace(m.configProvider) } - if hawkconfig.HasStoredCredentialForProvider(ctx, m.configProvider) { - m.configNotice = "Saved region for " + hawkconfig.GatewayDisplayName(m.configProvider) + if rhoconfig.HasStoredCredentialForProvider(ctx, m.configProvider) { + m.configNotice = "Saved region for " + rhoconfig.GatewayDisplayName(m.configProvider) if idx := m.configGatewayRowIndex(m.configProvider); idx >= 0 { m.configSel = idx } diff --git a/cmd/chat_config_remove.go b/cmd/chat_config_remove.go index 41b53ead5..b4304e217 100644 --- a/cmd/chat_config_remove.go +++ b/cmd/chat_config_remove.go @@ -6,7 +6,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) type configRemoveCredentialMsg struct { @@ -17,7 +17,7 @@ type configRemoveCredentialMsg struct { func removeCredentialAsync(provider string) tea.Cmd { return func() tea.Msg { - removed, err := hawkconfig.RemoveStoredCredential(context.Background(), provider) + removed, err := rhoconfig.RemoveStoredCredential(context.Background(), provider) return configRemoveCredentialMsg{ provider: provider, removed: removed, @@ -34,10 +34,10 @@ func (m chatModel) handleConfigRemoveCredentialMsg(msg configRemoveCredentialMsg } delete(modelCache, msg.provider) ctx := context.Background() - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.RefreshConfigCredSnapshot(ctx) m = m.refreshConfigGatewayRows() - if hawkconfig.ShouldClearSelectionAfterCredentialRemove(ctx, msg.provider) { - _ = hawkconfig.ClearActiveSelection(ctx) + if rhoconfig.ShouldClearSelectionAfterCredentialRemove(ctx, msg.provider) { + _ = rhoconfig.ClearActiveSelection(ctx) m.configModelProvider = "" m.configModelOptions = nil m.session.SetProvider("") @@ -46,8 +46,8 @@ func (m chatModel) handleConfigRemoveCredentialMsg(msg configRemoveCredentialMsg m.configTab = configTabGateways m.configSel = 0 m.configScroll = 0 - m.configNotice = fmt.Sprintf("Removed API key for %s", hawkconfig.GatewayDisplayName(msg.provider)) - if !hawkconfig.HasConfiguredDeploymentCached(ctx) { + m.configNotice = fmt.Sprintf("Removed API key for %s", rhoconfig.GatewayDisplayName(msg.provider)) + if !rhoconfig.HasConfiguredDeploymentCached(ctx) { m.configNotice += " — add an API key to continue" } next, cmd := m.rebuildSessionTransport() @@ -59,7 +59,7 @@ func (m chatModel) handleConfigRemoveCredentialMsg(msg configRemoveCredentialMsg func (m chatModel) openConfigRemoveKeyPanel() (chatModel, tea.Cmd) { next, cmd := m.openConfigAtTab(configTabGateways) - if len(hawkconfig.ConfiguredCredentialProviders()) == 0 { + if len(rhoconfig.ConfiguredCredentialProviders()) == 0 { next.configNotice = "No stored API keys" } return next, cmd diff --git a/cmd/chat_config_remove_test.go b/cmd/chat_config_remove_test.go index ae83e0fde..01de618b0 100644 --- a/cmd/chat_config_remove_test.go +++ b/cmd/chat_config_remove_test.go @@ -4,22 +4,22 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestConfigGatewayRows_ShowsSavedKey(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() var found bool @@ -35,36 +35,36 @@ func TestConfigGatewayRows_ShowsSavedKey(t *testing.T) { } func TestConfiguredCredentialProviders_UsedByGatewaysTab(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() - got := hawkconfig.ConfiguredCredentialProviders() + got := rhoconfig.ConfiguredCredentialProviders() if len(got) != 1 || got[0] != "openrouter" { t.Fatalf("configured = %v", got) } } func TestRemoveCredentialAsync(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() cmd := removeCredentialAsync("openrouter") if cmd == nil { diff --git a/cmd/chat_config_save_flow_test.go b/cmd/chat_config_save_flow_test.go index 3a4442614..d5be5faa2 100644 --- a/cmd/chat_config_save_flow_test.go +++ b/cmd/chat_config_save_flow_test.go @@ -10,23 +10,23 @@ import ( tea "charm.land/bubbletea/v2" "github.com/GrayCodeAI/eyrie/catalog" "github.com/GrayCodeAI/eyrie/credentials" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestFinishConfigEntry_APIKeyPaste_SavesBeforeProbe(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) oldTransport := http.DefaultTransport http.DefaultTransport = configSaveTestTransport(http.StatusUnauthorized) t.Cleanup(func() { http.DefaultTransport = oldTransport }) - if _, err := hawkconfig.CredentialInferenceForProvider("xiaomi_mimo_payg"); err != nil { + if _, err := rhoconfig.CredentialInferenceForProvider("xiaomi_mimo_payg"); err != nil { t.Fatalf("CredentialInferenceForProvider: %v", err) } @@ -111,7 +111,7 @@ func TestConfigGatewaysSelect_AddKeyOpensPaste(t *testing.T) { } } if sel < 0 { - t.Skip("all gateways already have keys in this environment") // TODO: https://github.com/GrayCodeAI/hawk/issues/28 + t.Skip("all gateways already have keys in this environment") // TODO: https://github.com/GrayCodeAI/rho/issues/28 } m.configSel = sel next, _ := m.handleConfigGatewaysSelect() @@ -134,7 +134,7 @@ func configSaveTestTransport(status int) http.RoundTripper { }) } -// roundTripFunc is defined in eyrie/runtime tests; duplicate here for hawk cmd tests. +// roundTripFunc is defined in eyrie/runtime tests; duplicate here for rho cmd tests. type roundTripFunc func(*http.Request) (*http.Response, error) func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { @@ -142,12 +142,12 @@ func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { } func TestHandleConfigKey_EnterOnPasteSubmits(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) oldTransport := http.DefaultTransport http.DefaultTransport = configSaveTestTransport(http.StatusUnauthorized) @@ -197,12 +197,12 @@ func TestStartConfigEntry_APIKeyPasteHasNoPlaceholder(t *testing.T) { } func TestHandleConfigApplyCredentialsMsg_CatalogFailureDoesNotBlameProvider(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), credentials.AccountForEnv("POOLSIDE_API_KEY"), "poolside-test-key"); err != nil { t.Fatalf("store key: %v", err) @@ -218,7 +218,7 @@ func TestHandleConfigApplyCredentialsMsg_CatalogFailureDoesNotBlameProvider(t *t t.Fatalf("catalog failure blamed provider key: %q", next.configNotice) } if !strings.Contains(next.configNotice, "model catalog unavailable") || - !strings.Contains(next.configNotice, "hawk models refresh") { + !strings.Contains(next.configNotice, "rho models refresh") { t.Fatalf("catalog recovery guidance missing: %q", next.configNotice) } } @@ -227,12 +227,12 @@ func TestHandleConfigApplyCredentialsMsg_CatalogFailureDoesNotBlameProvider(t *t func TestHandleConfigApplyCredentialsMsg_ValidationFailureDoesNotBlameProvider(t *testing.T) { // TODO: enable once eyrie catalog fixtures pin the claude-fable-5 model state. t.Skip("requires specific eyrie model catalog state (claude-fable-5)") - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), credentials.AccountForEnv("CONCENTRATE_API_KEY"), "concentrate-test-key"); err != nil { t.Fatalf("store key: %v", err) @@ -256,12 +256,12 @@ func TestHandleConfigApplyCredentialsMsg_ValidationFailureDoesNotBlameProvider(t func TestHandleConfigApplyCredentialsMsg_AuthenticationFailureBlamesKey(t *testing.T) { // TODO: enable once eyrie catalog fixtures pin the auth-failure model state. t.Skip("requires specific eyrie model catalog state") - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), credentials.AccountForEnv("CONCENTRATE_API_KEY"), "concentrate-test-key"); err != nil { t.Fatalf("store key: %v", err) diff --git a/cmd/chat_config_security.go b/cmd/chat_config_security.go index aa8d10e41..91334dfd5 100644 --- a/cmd/chat_config_security.go +++ b/cmd/chat_config_security.go @@ -4,7 +4,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) var ( diff --git a/cmd/chat_config_tabs.go b/cmd/chat_config_tabs.go index 7ac1a93e5..18b9b46b1 100644 --- a/cmd/chat_config_tabs.go +++ b/cmd/chat_config_tabs.go @@ -7,20 +7,20 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func (m chatModel) configStatus() (gateway, model string, configured bool) { ctx := context.Background() - if !hawkconfig.HasConfiguredDeploymentCached(ctx) { + if !rhoconfig.HasConfiguredDeploymentCached(ctx) { return "none", "", false } gw := strings.TrimSpace(m.configModelProvider) - if gw != "" && hawkconfig.IsSetupGateway(gw) { - gw = hawkconfig.GatewayDisplayName(gw) - } else if active := hawkconfig.ActiveGateway(ctx); active != "" { - gw = hawkconfig.GatewayDisplayName(active) + if gw != "" && rhoconfig.IsSetupGateway(gw) { + gw = rhoconfig.GatewayDisplayName(gw) + } else if active := rhoconfig.ActiveGateway(ctx); active != "" { + gw = rhoconfig.GatewayDisplayName(active) } else { gw = "none" } @@ -28,7 +28,7 @@ func (m chatModel) configStatus() (gateway, model string, configured bool) { model = strings.TrimSpace(m.session.Model()) } if model == "" { - model = strings.TrimSpace(hawkconfig.ActiveModel(ctx)) + model = strings.TrimSpace(rhoconfig.ActiveModel(ctx)) } return gw, model, true } @@ -44,7 +44,7 @@ func configTabDot(active bool) string { func configTabLabelStyle(active bool) lipgloss.Style { if active { - return lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + return lipgloss.NewStyle().Foreground(rhoColor).Bold(true) } return configMutedStyle() } @@ -90,7 +90,7 @@ func (m chatModel) switchConfigTab(tab int) (chatModel, tea.Cmd) { m = m.stopConfigModelSearch(true) } ctx := context.Background() - if tab == configTabModels && !hawkconfig.HasConfiguredDeploymentCached(ctx) { + if tab == configTabModels && !rhoconfig.HasConfiguredDeploymentCached(ctx) { tab = configTabGateways m.configNotice = "Select a gateway first · enter · paste API key" m = m.focusConfigActiveGateway() @@ -122,9 +122,9 @@ func (m chatModel) openConfigAtTab(tab int) (chatModel, tea.Cmd) { m.configSel = 0 m.configScroll = 0 m.viewDirty = true - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.RefreshConfigCredSnapshot(ctx) m = m.refreshConfigGatewayRows() - setup := hawkconfig.EvaluateSetupCached(ctx) + setup := rhoconfig.EvaluateSetupCached(ctx) if tab < 0 { if setup.HasCredentials { diff --git a/cmd/chat_config_tabs_test.go b/cmd/chat_config_tabs_test.go index 743e77873..24af97697 100644 --- a/cmd/chat_config_tabs_test.go +++ b/cmd/chat_config_tabs_test.go @@ -4,9 +4,9 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestRenderConfigTabBar_DotIndicators(t *testing.T) { @@ -28,12 +28,12 @@ func TestRenderConfigTabBar_DotIndicators(t *testing.T) { } func TestOpenConfigPanel_FirstRunOpensGateways(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{} @@ -47,12 +47,12 @@ func TestOpenConfigPanel_FirstRunOpensGateways(t *testing.T) { } func TestOpenConfigAtTab_ModelsWithoutCredentialsFallsBackToGateways(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{} diff --git a/cmd/chat_config_ui.go b/cmd/chat_config_ui.go index 30cdbaa86..5c287da97 100644 --- a/cmd/chat_config_ui.go +++ b/cmd/chat_config_ui.go @@ -4,7 +4,7 @@ import ( "strings" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func configMutedStyle() lipgloss.Style { @@ -13,19 +13,19 @@ func configMutedStyle() lipgloss.Style { func configTitleStyle() lipgloss.Style { // Talon Gold — title is the voice of the config panel. - return lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + return lipgloss.NewStyle().Foreground(rhoColor).Bold(true) } func configSelectedStyle() lipgloss.Style { // Talon Gold (bold) marks the focused row. Keep it distinct from // the active/current value, which uses configActiveStyle. - return lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + return lipgloss.NewStyle().Foreground(rhoColor).Bold(true) } func configAccentStyle() lipgloss.Style { // Accent for inline highlights (breadcrumb, status line values). // Same as title — both are the panel's voice. - return lipgloss.NewStyle().Foreground(hawkColor) + return lipgloss.NewStyle().Foreground(rhoColor) } func renderConfigBreadcrumb(title string) string { diff --git a/cmd/chat_copy_e2e_test.go b/cmd/chat_copy_e2e_test.go index 94a776eda..49959ff0b 100644 --- a/cmd/chat_copy_e2e_test.go +++ b/cmd/chat_copy_e2e_test.go @@ -8,7 +8,7 @@ import ( "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) // runCopySelectionE2EPass exercises chat + input copy/select/mouse flows in one pass. @@ -88,7 +88,7 @@ func runCopySelectionE2EPass(t *testing.T, pass int) { } // --- Mouse toggle (OpenCode-style) --- - t.Setenv("HAWK_MOUSE", "") + t.Setenv("RHO_MOUSE", "") m.handleMouseCommand([]string{"/mouse", "off"}) if m.mouseEnabled() { t.Fatalf("pass %d: expected mouse off after /mouse off", pass) @@ -109,13 +109,13 @@ func runCopySelectionE2EPass(t *testing.T, pass int) { } // --- Pass B: assistant reply path --- - m.messages = append(m.messages, displayMsg{role: "assistant", content: "Hello from hawk"}) + m.messages = append(m.messages, displayMsg{role: "assistant", content: "Hello from rho"}) m.input.SetValue("") - if content, _, got := m.copyContent(copyModeAssistant); !got || content != "Hello from hawk" { + if content, _, got := m.copyContent(copyModeAssistant); !got || content != "Hello from rho" { t.Fatalf("pass %d: /copy assistant content = %q got=%v", pass, content, got) } - if line, got := m.lastMessageContent(); !got || !strings.Contains(line, "Hello from hawk") { + if line, got := m.lastMessageContent(); !got || !strings.Contains(line, "Hello from rho") { t.Fatalf("pass %d: last message = %q got=%v", pass, line, got) } @@ -128,7 +128,7 @@ func runCopySelectionE2EPass(t *testing.T, pass int) { // Settings-backed mouse default disabled := false - m2 := chatModel{settings: hawkconfig.Settings{TuiMouse: &disabled}} + m2 := chatModel{settings: rhoconfig.Settings{TuiMouse: &disabled}} if m2.mouseEnabled() { t.Fatalf("pass %d: settings tui_mouse=false should disable capture", pass) } diff --git a/cmd/chat_copy_test.go b/cmd/chat_copy_test.go index f143ee374..d6d7ca158 100644 --- a/cmd/chat_copy_test.go +++ b/cmd/chat_copy_test.go @@ -7,7 +7,7 @@ import ( "charm.land/bubbles/v2/textarea" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestCopyableTranscript_IncludesInputDraft(t *testing.T) { @@ -80,24 +80,24 @@ func TestIsCopyToClipboardKey(t *testing.T) { } func TestMouseEnabled_SettingsAndEnv(t *testing.T) { - t.Setenv("HAWK_MOUSE", "") + t.Setenv("RHO_MOUSE", "") if !(chatModel{}).mouseEnabled() { t.Fatal("expected mouse capture to default on") } disabled := false - m := chatModel{settings: hawkconfig.Settings{TuiMouse: &disabled}} + m := chatModel{settings: rhoconfig.Settings{TuiMouse: &disabled}} if m.mouseEnabled() { t.Fatal("expected settings tui_mouse=false to disable capture") } - t.Setenv("HAWK_MOUSE", "0") + t.Setenv("RHO_MOUSE", "0") if m.mouseEnabled() { - t.Fatal("expected HAWK_MOUSE=0 to disable capture") + t.Fatal("expected RHO_MOUSE=0 to disable capture") } - t.Setenv("HAWK_MOUSE", "1") + t.Setenv("RHO_MOUSE", "1") if !m.mouseEnabled() { - t.Fatal("expected HAWK_MOUSE=1 to enable capture") + t.Fatal("expected RHO_MOUSE=1 to enable capture") } } diff --git a/cmd/chat_export.go b/cmd/chat_export.go index 54b51a3fe..63e1ca60a 100644 --- a/cmd/chat_export.go +++ b/cmd/chat_export.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" ) func writeRedactedChatMarkdownExport(m *chatModel) (string, error) { @@ -160,7 +160,7 @@ func exportSessionTxt(m *chatModel) (string, error) { return "", fmt.Errorf("no active session") } var b strings.Builder - b.WriteString(fmt.Sprintf("Hawk Session: %s\n", m.sessionID)) + b.WriteString(fmt.Sprintf("Rho Session: %s\n", m.sessionID)) b.WriteString(fmt.Sprintf("Model: %s/%s\n", m.session.Provider(), m.session.Model())) b.WriteString(fmt.Sprintf("Exported: %s\n\n", time.Now().Format(time.RFC3339))) b.WriteString(strings.Repeat("=", 60) + "\n\n") diff --git a/cmd/chat_focus.go b/cmd/chat_focus.go index 09a0ec282..5d03eff29 100644 --- a/cmd/chat_focus.go +++ b/cmd/chat_focus.go @@ -9,9 +9,9 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // UI focus areas — Grok Build cycles Prompt ↔ Scrollback (Tab). diff --git a/cmd/chat_history_search.go b/cmd/chat_history_search.go index d9dafe061..f694492ea 100644 --- a/cmd/chat_history_search.go +++ b/cmd/chat_history_search.go @@ -5,8 +5,8 @@ import ( "strings" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/textutil" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/textutil" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/mattn/go-runewidth" ) diff --git a/cmd/chat_journey_e2e_test.go b/cmd/chat_journey_e2e_test.go index 019b5df60..647f5a6dc 100644 --- a/cmd/chat_journey_e2e_test.go +++ b/cmd/chat_journey_e2e_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func requireChatModel(t *testing.T, model any) *chatModel { @@ -23,20 +23,20 @@ func requireChatModel(t *testing.T, model any) *chatModel { } func TestChatJourney_ConfigPermissionsAndCoreCommands(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() if err := store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatal(err) } - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() m := newTestChatModel() diff --git a/cmd/chat_layout_mouse_test.go b/cmd/chat_layout_mouse_test.go index b0465d140..00e5ba0f2 100644 --- a/cmd/chat_layout_mouse_test.go +++ b/cmd/chat_layout_mouse_test.go @@ -14,7 +14,7 @@ func TestView_LineCountMatchesHeight(t *testing.T) { m := chatModel{ height: 24, width: 80, - welcomeCache: "HAWK LOGO\nv0.1.0", + welcomeCache: "RHO LOGO\nv0.1.0", input: textarea.New(), viewport: viewport.New(viewport.WithWidth(80), viewport.WithHeight(8)), ghostText: NewGhostText(), diff --git a/cmd/chat_layout_test.go b/cmd/chat_layout_test.go index b5fab6c71..95b0582cd 100644 --- a/cmd/chat_layout_test.go +++ b/cmd/chat_layout_test.go @@ -12,7 +12,7 @@ func TestView_PinsWelcomeAboveViewport(t *testing.T) { m := chatModel{ height: 24, width: 80, - welcomeCache: "HAWK LOGO\nv0.1.0", + welcomeCache: "RHO LOGO\nv0.1.0", input: textarea.New(), viewport: viewport.New(viewport.WithWidth(80), viewport.WithHeight(8)), ghostText: NewGhostText(), @@ -21,7 +21,7 @@ func TestView_PinsWelcomeAboveViewport(t *testing.T) { m.viewDirty = true m.updateViewportContent() got := m.View().Content - if !strings.Contains(got, "HAWK LOGO") { + if !strings.Contains(got, "RHO LOGO") { t.Fatalf("welcome should be pinned at top, got prefix: %q", got[:min(40, len(got))]) } if !strings.Contains(got, "─") { @@ -33,20 +33,20 @@ func TestPrimeInitialViewportContent_RendersWelcomeBeforeFirstFrame(t *testing.T m := chatModel{ height: 24, width: 80, - welcomeCache: "HAWK LOGO\nv0.1.0", + welcomeCache: "RHO LOGO\nv0.1.0", input: textarea.New(), viewport: viewport.New(viewport.WithWidth(80), viewport.WithHeight(8)), ghostText: NewGhostText(), } m = m.withSyncedLayout() - if strings.Contains(m.viewport.View(), "HAWK LOGO") { + if strings.Contains(m.viewport.View(), "RHO LOGO") { t.Fatal("expected empty initial viewport before priming") } m.primeInitialViewportContent() - if !strings.Contains(m.viewport.View(), "HAWK LOGO") { + if !strings.Contains(m.viewport.View(), "RHO LOGO") { t.Fatalf("expected primed viewport to include welcome content, got %q", m.viewport.View()) } } diff --git a/cmd/chat_model.go b/cmd/chat_model.go index cf0761423..45ddebe0b 100644 --- a/cmd/chat_model.go +++ b/cmd/chat_model.go @@ -14,15 +14,15 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/bridge/sessioncapture" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" - "github.com/GrayCodeAI/hawk/internal/feature/taste" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/system/staleness" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/bridge/sessioncapture" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/feature/taste" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/system/staleness" + "github.com/GrayCodeAI/rho/internal/tool" ) // sessionWAL is the durability surface the chat model needs from its @@ -54,10 +54,10 @@ func (m *chatModel) recordWALError(err error) { if err == nil || m.durabilityWarning != "" { return } - m.durabilityWarning = "Warning: session persistence is failing — recent messages may be lost if hawk crashes. Check disk space and permissions." + m.durabilityWarning = "Warning: session persistence is failing — recent messages may be lost if rho crashes. Check disk space and permissions." } -// All hawk color/icon/glyph constants live in theme.go. This file holds +// All rho color/icon/glyph constants live in theme.go. This file holds // the pre-built lipgloss styles that combine a color with attributes // (bold, italic, border, etc.) for the most common patterns. @@ -73,8 +73,8 @@ var ( slashCmdStyle = lipgloss.NewStyle().Foreground(textDisabled) slashDescStyle = lipgloss.NewStyle().Foreground(textDisabled) - slashSelCmdStyle = lipgloss.NewStyle().Foreground(hawkColor).Bold(true) - slashSelDescStyle = lipgloss.NewStyle().Foreground(hawkColor) + slashSelCmdStyle = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + slashSelDescStyle = lipgloss.NewStyle().Foreground(rhoColor) inputBorderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false, true, false).BorderForeground(borderDim) ghostHintStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("238")).Italic(true) @@ -83,13 +83,13 @@ var ( dimColor = textDisabled ) -// hawkSpinnerFrames feeds the bubbles spinner (matches BrailleSpinner default). -var hawkSpinnerFrames = hawkSpinnerGlyphs +// rhoSpinnerFrames feeds the bubbles spinner (matches BrailleSpinner default). +var rhoSpinnerFrames = rhoSpinnerGlyphs -// hawkSpinnerFrameInterval — compass frame cadence. -const hawkSpinnerFrameInterval = 80 * time.Millisecond +// rhoSpinnerFrameInterval — compass frame cadence. +const rhoSpinnerFrameInterval = 80 * time.Millisecond -// Spinner verbs (from hawk-archive) — picked randomly per session +// Spinner verbs (from rho-archive) — picked randomly per session var spinnerVerbs = []string{ "Abstracting", "Architecting", "Brewing", "Calculating", "Cogitating", "Compiling", "Computing", "Conjuring", "Contemplating", "Cooking", @@ -149,7 +149,7 @@ type ( statusLeftBranch string connStatusVal string connStatusKey string - welcomeSetup hawkconfig.SetupState + welcomeSetup rhoconfig.SetupState welcomeAgentsOK bool } processArrowTickMsg struct { @@ -206,7 +206,7 @@ type chatModel struct { viewport viewport.Model session *engine.Session registry *tool.Registry - settings hawkconfig.Settings + settings rhoconfig.Settings ref *progRef cancel context.CancelFunc // cancel current stream sessionID string @@ -304,7 +304,7 @@ type chatModel struct { sessionBootstrapDone bool toolStartTime time.Time welcomeCache string - welcomeSetupState hawkconfig.SetupState + welcomeSetupState rhoconfig.SetupState welcomeAgentsOK bool viewDirty bool layoutKey int // input lines + slash menu height fingerprint diff --git a/cmd/chat_model_test.go b/cmd/chat_model_test.go index 8bc4b7e5d..d9ed693c0 100644 --- a/cmd/chat_model_test.go +++ b/cmd/chat_model_test.go @@ -11,14 +11,14 @@ import ( "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/viewport" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/bridge/sessioncapture" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/bridge/sessioncapture" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/tool" ) func newTestChatModel() *chatModel { @@ -43,7 +43,7 @@ func newTestChatModel() *chatModel { hintsLoader: engine.NewHintsLoader(), selfImprover: engine.NewSelfImprover(), codingSoul: engine.LoadCodingSoul(), - brailleSpinner: NewBrailleSpinner(SpinnerHawk, "Thinking"), + brailleSpinner: NewBrailleSpinner(SpinnerRho, "Thinking"), testStreamStarter: func() {}, } return m @@ -62,12 +62,12 @@ func isolateChatCommandSweepEnv(t *testing.T) { root := t.TempDir() storage.SetTestDirs(t, root) isolateCredentialHome(t) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() gateway.SetDefaultStore(&gateway.MapStore{}) restoreThemeGlobals(t) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) } @@ -77,7 +77,7 @@ func isolateChatCommandSweepEnv(t *testing.T) { // (e.g. TestAdaptiveNeutralsPreserveDarkAppearance). func restoreThemeGlobals(t *testing.T) { t.Helper() - savedHawk, savedSuccess, savedWarn, savedErr, savedInfo := hawkColor, successTeal, warnAmber, errorCoral, infoSky + savedRho, savedSuccess, savedWarn, savedErr, savedInfo := rhoColor, successTeal, warnAmber, errorCoral, infoSky savedTool, savedAgent, savedDone, savedContainer := toolGold, agentGold, doneGreen, containerBlue savedInspect, savedEdit, savedRun, savedTrust := tierInspect, tierEdit, tierRun, tierTrust savedHudBorder, savedHudLabel := hudBorderPurple, hudLabelPink @@ -86,7 +86,7 @@ func restoreThemeGlobals(t *testing.T) { savedBorderDim, savedBgCode := borderDim, bgCode hasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) t.Cleanup(func() { - hawkColor, successTeal, warnAmber, errorCoral, infoSky = savedHawk, savedSuccess, savedWarn, savedErr, savedInfo + rhoColor, successTeal, warnAmber, errorCoral, infoSky = savedRho, savedSuccess, savedWarn, savedErr, savedInfo toolGold, agentGold, doneGreen, containerBlue = savedTool, savedAgent, savedDone, savedContainer tierInspect, tierEdit, tierRun, tierTrust = savedInspect, savedEdit, savedRun, savedTrust hudBorderPurple, hudLabelPink = savedHudBorder, savedHudLabel @@ -141,7 +141,7 @@ func TestChatModel_SlashClear(t *testing.T) { func TestFormatQuitResumeMessage(t *testing.T) { got := formatQuitResumeMessage("44418bdd52745678") - want := "Thank you for using Hawk!\n\nTo resume this session, run: hawk --resume 44418bdd52745678\n" + want := "Thank you for using Rho!\n\nTo resume this session, run: rho --resume 44418bdd52745678\n" if got != want { t.Fatalf("quit message mismatch:\nwant %q\ngot %q", want, got) } @@ -149,7 +149,7 @@ func TestFormatQuitResumeMessage(t *testing.T) { func TestFormatQuitResumeMessage_NoSession(t *testing.T) { got := formatQuitResumeMessage("") - want := "Thank you for using Hawk!\n" + want := "Thank you for using Rho!\n" if got != want { t.Fatalf("quit message mismatch:\nwant %q\ngot %q", want, got) } diff --git a/cmd/chat_mouse.go b/cmd/chat_mouse.go index 0be52a4b1..4f403a81b 100644 --- a/cmd/chat_mouse.go +++ b/cmd/chat_mouse.go @@ -5,11 +5,11 @@ import ( "os" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func mouseEnabledFromEnv() bool { - v := strings.TrimSpace(os.Getenv("HAWK_MOUSE")) + v := strings.TrimSpace(os.Getenv("RHO_MOUSE")) if v == "0" || strings.EqualFold(v, "false") || strings.EqualFold(v, "off") { return false } @@ -17,13 +17,13 @@ func mouseEnabledFromEnv() bool { } func envOverridesMouse() bool { - v := strings.TrimSpace(os.Getenv("HAWK_MOUSE")) + v := strings.TrimSpace(os.Getenv("RHO_MOUSE")) return v != "" } // mouseEnabled reports whether the TUI should capture mouse events for chat wheel // scroll. When false, the terminal handles click-drag selection natively (OpenCode -// "mouse": false). Priority: HAWK_MOUSE env → runtime override → settings → default on. +// "mouse": false). Priority: RHO_MOUSE env → runtime override → settings → default on. func (m chatModel) mouseEnabled() bool { if envOverridesMouse() { return mouseEnabledFromEnv() @@ -54,7 +54,7 @@ func (m *chatModel) handleMouseCommand(parts []string) { source := "default" switch { case envOverridesMouse(): - source = "HAWK_MOUSE env" + source = "RHO_MOUSE env" case m.mouseOverride != nil: source = "session" case m.settings.TuiMouse != nil: @@ -76,7 +76,7 @@ func (m *chatModel) handleMouseCommand(parts []string) { if envOverridesMouse() { m.messages = append(m.messages, displayMsg{ role: "system", - content: "Mouse is controlled by HAWK_MOUSE env in this session. " + + content: "Mouse is controlled by RHO_MOUSE env in this session. " + "Unset it to use /mouse or settings.json tui_mouse.", }) return @@ -85,11 +85,11 @@ func (m *chatModel) handleMouseCommand(parts []string) { switch strings.ToLower(parts[1]) { case "on", "true", "1", "enable": m.setMouseEnabled(true) - _ = hawkconfig.SetGlobalSetting("tui_mouse", "true") + _ = rhoconfig.SetGlobalSetting("tui_mouse", "true") m.messages = append(m.messages, displayMsg{role: "system", content: "Mouse capture on — chat wheel scroll enabled."}) case "off", "false", "0", "disable": m.setMouseEnabled(false) - _ = hawkconfig.SetGlobalSetting("tui_mouse", "false") + _ = rhoconfig.SetGlobalSetting("tui_mouse", "false") m.messages = append(m.messages, displayMsg{ role: "system", content: "Mouse capture off — use click-drag to select text. /copy and Ctrl+Shift+C still work.", @@ -103,7 +103,7 @@ func (m *chatModel) handleMouseCommand(parts []string) { val = "true" msg = "Mouse capture on — chat wheel scroll enabled." } - _ = hawkconfig.SetGlobalSetting("tui_mouse", val) + _ = rhoconfig.SetGlobalSetting("tui_mouse", val) m.messages = append(m.messages, displayMsg{role: "system", content: msg}) default: m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /mouse [on|off|toggle]"}) diff --git a/cmd/chat_multiturn_e2e_test.go b/cmd/chat_multiturn_e2e_test.go index c1eb67a05..e90bfea30 100644 --- a/cmd/chat_multiturn_e2e_test.go +++ b/cmd/chat_multiturn_e2e_test.go @@ -6,35 +6,35 @@ import ( "testing" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func configureReadyChatState(t *testing.T) { t.Helper() isolateChatCommandSweepEnv(t) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() if err := store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatal(err) } - if err := hawkconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + if err := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := hawkconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } - hawkconfig.InvalidateConfigUICache() - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.InvalidateConfigUICache() + rhoconfig.RefreshConfigCredSnapshot(ctx) } func countMessagesByRole(msgs []displayMsg, role string) int { diff --git a/cmd/chat_permission_keys_test.go b/cmd/chat_permission_keys_test.go index 552b24215..c5c1ba397 100644 --- a/cmd/chat_permission_keys_test.go +++ b/cmd/chat_permission_keys_test.go @@ -6,8 +6,8 @@ import ( "time" tea "charm.land/bubbletea/v2" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/policy" - "github.com/GrayCodeAI/hawk/internal/engine" + contracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestPermissionAlwaysAllowDoesNotNilDeref(t *testing.T) { diff --git a/cmd/chat_platform_ctx.go b/cmd/chat_platform_ctx.go index 0ced98469..008307a18 100644 --- a/cmd/chat_platform_ctx.go +++ b/cmd/chat_platform_ctx.go @@ -8,7 +8,7 @@ import ( "time" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) var platformCtxCache struct { @@ -51,7 +51,7 @@ type platformContextIndexMsg struct { func fetchPlatformContextIndexCmd() tea.Cmd { return func() tea.Msg { - models, err := hawkconfig.ListPublicEngineModels(context.Background(), "xiaomi_mimo_payg") + models, err := rhoconfig.ListPublicEngineModels(context.Background(), "xiaomi_mimo_payg") if err != nil { return platformContextIndexMsg{err: err} } diff --git a/cmd/chat_print.go b/cmd/chat_print.go index 9358470dd..29ce48109 100644 --- a/cmd/chat_print.go +++ b/cmd/chat_print.go @@ -14,12 +14,12 @@ import ( "unicode/utf8" lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - aiwatch "github.com/GrayCodeAI/hawk/internal/engine/io" - "github.com/GrayCodeAI/hawk/internal/engine/lifecycle" - "github.com/GrayCodeAI/hawk/internal/observability/logger" - "github.com/GrayCodeAI/hawk/internal/session" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + aiwatch "github.com/GrayCodeAI/rho/internal/engine/io" + "github.com/GrayCodeAI/rho/internal/engine/lifecycle" + "github.com/GrayCodeAI/rho/internal/observability/logger" + "github.com/GrayCodeAI/rho/internal/session" ) // Print mode and session persistence functions extracted from chat.go @@ -40,7 +40,7 @@ func runPrint(text string) error { return err } - sess, cfgErr := newConfiguredHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) + sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) if cfgErr != nil { return cfgErr } @@ -132,6 +132,8 @@ func runPrint(text string) error { case "text": printTextResponse(printed.String()) printTextUsageFooter(lastUsage, started, turns, effectiveModel) + case "transcript": + printTranscript(sessionID, effectiveModel, printed.String(), lastUsage, started, turns) case "json": writePrintResult(printed.String(), sessionID, sess, false, nil) case "stream-json": @@ -198,6 +200,27 @@ func printTextUsageFooter(usage *engine.StreamUsage, started time.Time, turns in _, _ = fmt.Fprintf(os.Stderr, "%s\n", auditTint("tokens: "+strings.Join(parts, " · "), textMuted)) } +// printTranscript emits a plain, timestamped transcript of the turn to stdout. +// It is the "transcript" output format: human-readable, stable, and free of +// ANSI so it can be pasted into a bug report or piped to a file. +func printTranscript(sessionID, model, response string, usage *engine.StreamUsage, started time.Time, turns int) { + var b strings.Builder + fmt.Fprintf(&b, "# rho transcript\n") + fmt.Fprintf(&b, "session: %s\n", sessionID) + if model != "" { + fmt.Fprintf(&b, "model: %s\n", model) + } + fmt.Fprintf(&b, "started: %s\n", started.Format(time.RFC3339)) + fmt.Fprintf(&b, "turns: %d\n", turns) + if usage != nil { + fmt.Fprintf(&b, "tokens: %d in · %d out\n", usage.PromptTokens, usage.CompletionTokens) + } + fmt.Fprintf(&b, "\n---\n\n") + b.WriteString(strings.TrimRight(response, "\n")) + b.WriteString("\n") + fmt.Print(b.String()) +} + // printTextResponse emits the final text-mode response, rendering markdown to // styled ANSI when --markdown is set and color is enabled. Raw markdown is // preserved for piped/NO_COLOR output so scripts stay machine-parseable. @@ -304,7 +327,7 @@ func saveEyrieSession(id string, sess *engine.Session) { // runRepl starts an interactive REPL mode for multi-turn conversation without TUI. func runRepl() error { - fmt.Fprintln(os.Stderr, auditTint("Hawk REPL", textPrimary)+auditTint(" — type 'exit' or 'quit' to leave, 'help' for commands", textMuted)) + fmt.Fprintln(os.Stderr, auditTint("Rho REPL", textPrimary)+auditTint(" — type 'exit' or 'quit' to leave, 'help' for commands", textMuted)) fmt.Fprintln(os.Stderr) systemPrompt, err := buildSystemPrompt() @@ -323,7 +346,7 @@ func runRepl() error { return err } - sess, cfgErr := newConfiguredHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) + sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) if cfgErr != nil { return cfgErr } @@ -469,7 +492,7 @@ func runRepl() error { } } -func replBuiltinResponse(input string, sess *engine.Session, settings hawkconfig.Settings, sessionID string) (string, bool, error) { +func replBuiltinResponse(input string, sess *engine.Session, settings rhoconfig.Settings, sessionID string) (string, bool, error) { switch strings.TrimSpace(input) { case "/tools": return builtInToolsSummary(), true, nil @@ -488,12 +511,12 @@ func replBuiltinResponse(input string, sess *engine.Session, settings hawkconfig } } -func replModelsSummary(settings hawkconfig.Settings, sessionProvider string) (string, bool, error) { +func replModelsSummary(settings rhoconfig.Settings, sessionProvider string) (string, bool, error) { providerName := effectiveProviderForREPL(settings, sessionProvider) if providerName == "" { - return "No active provider selected. Set one with `hawk config provider ` or start REPL with `--provider`.", true, nil + return "No active provider selected. Set one with `rho config provider ` or start REPL with `--provider`.", true, nil } - models, err := hawkconfig.FetchModelsForProvider(providerName) + models, err := rhoconfig.FetchModelsForProvider(providerName) if err != nil { return "", true, err } @@ -519,7 +542,7 @@ func replModelsSummary(settings hawkconfig.Settings, sessionProvider string) (st return b.String(), true, nil } -func effectiveProviderForREPL(settings hawkconfig.Settings, sessionProvider string) string { +func effectiveProviderForREPL(settings rhoconfig.Settings, sessionProvider string) string { if provider != "" { return strings.TrimSpace(provider) } @@ -544,7 +567,7 @@ func formatModelTablePlain(rows []modelTableRow) string { } // watchIgnoreDirs are directory names skipped when scanning for AI directives. -var watchIgnoreDirs = []string{".git", "node_modules", "vendor", "__pycache__", ".hawk"} +var watchIgnoreDirs = []string{".git", "node_modules", "vendor", "__pycache__", ".rho"} // runWatch watches the working directory for AI!/AI? comment directives and // dispatches a targeted LLM edit for each one as files change (Aider-style diff --git a/cmd/chat_print_usage_test.go b/cmd/chat_print_usage_test.go index ca4c1b556..df0b0f683 100644 --- a/cmd/chat_print_usage_test.go +++ b/cmd/chat_print_usage_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // captureStderr runs fn with os.Stderr redirected to a pipe and returns the @@ -38,7 +38,7 @@ func TestPrintTextUsageFooter(t *testing.T) { printTextUsageFooter(&engine.StreamUsage{ PromptTokens: 100, CompletionTokens: 50, - }, started, 3, "hawk-pro") + }, started, 3, "rho-pro") }) if !strings.Contains(got, "100 in · 50 out") { t.Errorf("footer missing token counts: %q", got) @@ -49,7 +49,7 @@ func TestPrintTextUsageFooter(t *testing.T) { if !strings.Contains(got, "3 turn(s)") { t.Errorf("footer missing turn count: %q", got) } - if !strings.Contains(got, "hawk-pro") { + if !strings.Contains(got, "rho-pro") { t.Errorf("footer missing model: %q", got) } if !strings.Contains(got, "2s") { @@ -90,7 +90,7 @@ func TestPrintTextUsageFooter(t *testing.T) { CompletionTokens: 5, }, started, 1, "") }) - if strings.Contains(got, "hawk-pro") { + if strings.Contains(got, "rho-pro") { t.Errorf("footer should omit empty model: %q", got) } }) diff --git a/cmd/chat_prompt_timeout_test.go b/cmd/chat_prompt_timeout_test.go index 3920bc925..3459635a6 100644 --- a/cmd/chat_prompt_timeout_test.go +++ b/cmd/chat_prompt_timeout_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/policy" - "github.com/GrayCodeAI/hawk/internal/engine" + contracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestPermissionPromptTimeoutClearsStaleState(t *testing.T) { diff --git a/cmd/chat_scrollbar.go b/cmd/chat_scrollbar.go index 8ca748ebc..041e90773 100644 --- a/cmd/chat_scrollbar.go +++ b/cmd/chat_scrollbar.go @@ -19,7 +19,7 @@ const ( // scrollbarThumbStyle — Talon Gold thumb so it reads as a brand control. var ( - scrollbarThumbStyle = lipgloss.NewStyle().Foreground(hawkColor) + scrollbarThumbStyle = lipgloss.NewStyle().Foreground(rhoColor) scrollbarTrackStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("238")) ) diff --git a/cmd/chat_select.go b/cmd/chat_select.go index 499da7b29..50956c235 100644 --- a/cmd/chat_select.go +++ b/cmd/chat_select.go @@ -55,7 +55,7 @@ func enterSelectionMode(ref *progRef, transcript string, restoreMouse bool) tea. fmt.Fprintln(os.Stderr, " Click and drag to select text in this terminal.") fmt.Fprintln(os.Stderr, " Copy with your terminal's normal copy shortcut (e.g. Cmd+C,") fmt.Fprintln(os.Stderr, " Ctrl+Shift+C, or Ctrl+Insert).") - fmt.Fprintln(os.Stderr, " Press any key to return to hawk.") + fmt.Fprintln(os.Stderr, " Press any key to return to rho.") fmt.Fprintln(os.Stderr, "────────────────────────────────────────────────────────────") fmt.Fprintln(os.Stderr, "") // Block on stdin in raw mode so any single keypress resumes the @@ -109,7 +109,7 @@ func plainTranscript(messages []displayMsg, partial string) string { b.WriteString("\n\n") } if partial != "" { - b.WriteString("hawk: ") + b.WriteString("rho: ") b.WriteString(partial) b.WriteString("\n\n") } @@ -127,7 +127,7 @@ func plainTranscriptLine(msg displayMsg) (string, bool) { case "user": return "You: " + content, true case "assistant": - return "hawk: " + content, true + return "rho: " + content, true case "error": return "Error: " + content, true case "system": diff --git a/cmd/chat_session_picker.go b/cmd/chat_session_picker.go index d4b5f762b..85ea5630e 100644 --- a/cmd/chat_session_picker.go +++ b/cmd/chat_session_picker.go @@ -8,10 +8,10 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/mattn/go-runewidth" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/session" ) // sessionPickerStyles holds the lipgloss styles for the session picker overlay. diff --git a/cmd/chat_status.go b/cmd/chat_status.go index 7014878dc..3bc1fdfb8 100644 --- a/cmd/chat_status.go +++ b/cmd/chat_status.go @@ -8,8 +8,8 @@ import ( lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" ) func modelStatusMeta(gateway, modelID string) (displayName, contextLabel string) { @@ -61,7 +61,7 @@ func (m *chatModel) invalidateConnStatus() { func (m chatModel) connStatusFingerprint() string { gw, modelName := m.sessionGatewayModel() - creds := strings.Join(hawkconfig.ConfiguredCredentialProviders(), ",") + creds := strings.Join(rhoconfig.ConfiguredCredentialProviders(), ",") api := 0 if m.session != nil { api = m.session.LastPromptTokens() @@ -83,7 +83,7 @@ func (m chatModel) sessionGatewayModel() (gateway, model string) { model = explicitModel } if gateway == "" && model != "" { - gateway = strings.TrimSpace(hawkconfig.ProviderOfModel(model)) + gateway = strings.TrimSpace(rhoconfig.ProviderOfModel(model)) } if explicitGateway == "" && explicitModel == "" && model == "" { switch strings.TrimSpace(gateway) { @@ -96,10 +96,10 @@ func (m chatModel) sessionGatewayModel() (gateway, model string) { func (m *chatModel) chatConnectionStatus() string { ctx := context.Background() - if !hawkconfig.CredentialSnapshotReady() { + if !rhoconfig.CredentialSnapshotReady() { return "" } - if !hawkconfig.HasConfiguredDeploymentCached(ctx) { + if !rhoconfig.HasConfiguredDeploymentCached(ctx) { return "" } m.syncSessionSelection() @@ -138,7 +138,7 @@ func (m chatModel) buildConnectionStatusPlain() string { func (m chatModel) connectionStatusParts() (gateway, model, contextLabel string) { gw, modelID := m.sessionGatewayModel() - gateway = hawkconfig.GatewayDisplayName(gw) + gateway = rhoconfig.GatewayDisplayName(gw) if gateway == "" { gateway = gw } @@ -196,10 +196,10 @@ func trimRepeatedGatewayPrefix(gateway, model string) string { // footer segments so context can sit flush on the right edge. func (m chatModel) renderConnectionStatusSplit() (modelRendered string, modelVis int, ctxRendered string, ctxVis int) { ctx := context.Background() - if !hawkconfig.CredentialSnapshotReady() { + if !rhoconfig.CredentialSnapshotReady() { return "", 0, "", 0 } - if !hawkconfig.HasConfiguredDeploymentCached(ctx) { + if !rhoconfig.HasConfiguredDeploymentCached(ctx) { return "", 0, "", 0 } diff --git a/cmd/chat_status_metadata_test.go b/cmd/chat_status_metadata_test.go index 4a0660f7a..ce33becea 100644 --- a/cmd/chat_status_metadata_test.go +++ b/cmd/chat_status_metadata_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" ) // seedPlatformContextCacheForTest primes the platform context cache so tests @@ -133,10 +133,10 @@ func TestConnectionStatusParts_MimoShowsPlatformContext_HyphenProvider(t *testin } func TestGatewayDisplayName_XiaomiTokenPlanHyphen(t *testing.T) { - if got := hawkconfig.GatewayDisplayName("xiaomi-mimo-token-plan"); got != "Xiaomi MiMo — Token Plan" { + if got := rhoconfig.GatewayDisplayName("xiaomi-mimo-token-plan"); got != "Xiaomi MiMo — Token Plan" { t.Fatalf("GatewayDisplayName(hyphen) = %q, want nice name", got) } - if got := hawkconfig.GatewayDisplayName("xiaomi_mimo_token_plan"); got != "Xiaomi MiMo — Token Plan" { + if got := rhoconfig.GatewayDisplayName("xiaomi_mimo_token_plan"); got != "Xiaomi MiMo — Token Plan" { t.Fatalf("GatewayDisplayName(underscore) = %q, want nice name", got) } } diff --git a/cmd/chat_status_test.go b/cmd/chat_status_test.go index 75b10300e..5443490a4 100644 --- a/cmd/chat_status_test.go +++ b/cmd/chat_status_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/provider/gateway" "github.com/charmbracelet/x/ansi" ) @@ -91,23 +91,23 @@ func TestFormatContextUsedLabel(t *testing.T) { } func TestChatConnectionStatus_WithModel(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() if err := store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.SetActiveProvider(ctx, "openrouter") - _ = hawkconfig.SetActiveModel(ctx, "moonshotai/kimi-k2.6") - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.SetActiveProvider(ctx, "openrouter") + _ = rhoconfig.SetActiveModel(ctx, "moonshotai/kimi-k2.6") + rhoconfig.RefreshConfigCredSnapshot(ctx) sess := engine.NewSession("openrouter", "moonshotai/kimi-k2.6", "", nil) @@ -125,23 +125,23 @@ func TestChatConnectionStatus_WithModel(t *testing.T) { } func TestChatConnectionStatus_KeyNoModel(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() if err := store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.ClearActiveSelection(ctx) - _ = hawkconfig.SetActiveProvider(ctx, "openrouter") - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.ClearActiveSelection(ctx) + _ = rhoconfig.SetActiveProvider(ctx, "openrouter") + rhoconfig.RefreshConfigCredSnapshot(ctx) m := chatModel{session: &engine.Session{}} got := m.chatConnectionStatus() @@ -151,22 +151,22 @@ func TestChatConnectionStatus_KeyNoModel(t *testing.T) { } func TestChatConnectionStatus_NoGatewayNoModel(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() if err := store.Set(ctx, gateway.AccountForEnv("ANTHROPIC_API_KEY"), "sk-ant-test-key-long-enough"); err != nil { t.Fatalf("store.Set: %v", err) } - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.ClearActiveSelection(ctx) - hawkconfig.RefreshConfigCredSnapshot(ctx) + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.ClearActiveSelection(ctx) + rhoconfig.RefreshConfigCredSnapshot(ctx) m := chatModel{session: &engine.Session{}} got := m.chatConnectionStatus() @@ -183,7 +183,7 @@ func TestStartupWarmMsg_RefreshesFooterCache(t *testing.T) { statusLeftBranch: "main", connStatusVal: "OpenRouter · gpt-4", connStatusKey: "cache-key", - welcomeSetup: hawkconfig.SetupState{NeedsSetup: true}, + welcomeSetup: rhoconfig.SetupState{NeedsSetup: true}, welcomeAgentsOK: true, }) next := nextModel.(chatModel) @@ -247,7 +247,7 @@ func TestShowWelcomeBanner_WithMessages(t *testing.T) { func TestBuildWelcomeMessage_UsesDisplayVersion(t *testing.T) { SetVersion("dev") - msg := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, false, 80, 24) + msg := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 80, 24) if strings.Contains(msg, "vdev") { t.Fatal("welcome should not show vdev; DisplayVersion should read VERSION file or dev") } diff --git a/cmd/chat_stream.go b/cmd/chat_stream.go index d46f209f3..ceaf431b0 100644 --- a/cmd/chat_stream.go +++ b/cmd/chat_stream.go @@ -8,7 +8,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // Streaming and prompt command functions extracted from chat.go diff --git a/cmd/chat_subcommand_brainstorm.go b/cmd/chat_subcommand_brainstorm.go index c9062d4cd..2585d8cdd 100644 --- a/cmd/chat_subcommand_brainstorm.go +++ b/cmd/chat_subcommand_brainstorm.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // brainstormSubcommand implements the /brainstorm slash command. It diff --git a/cmd/chat_subcommand_branch_agent.go b/cmd/chat_subcommand_branch_agent.go index 6ece33d58..58f32103c 100644 --- a/cmd/chat_subcommand_branch_agent.go +++ b/cmd/chat_subcommand_branch_agent.go @@ -5,17 +5,17 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) -// branchAgentSubcommand creates a hawk/agent-* branch from main/master. +// branchAgentSubcommand creates a rho/agent-* branch from main/master. type branchAgentSubcommand struct{} func (c *branchAgentSubcommand) Name() string { return "branch-agent" } func (c *branchAgentSubcommand) Aliases() []string { return []string{"agent-branch"} } func (c *branchAgentSubcommand) Description() string { - return "create hawk/agent-* branch if on main/master" + return "create rho/agent-* branch if on main/master" } func (c *branchAgentSubcommand) Usage() string { return "/branch-agent" } diff --git a/cmd/chat_subcommand_checkpoint.go b/cmd/chat_subcommand_checkpoint.go index bf5ae7402..c135c512e 100644 --- a/cmd/chat_subcommand_checkpoint.go +++ b/cmd/chat_subcommand_checkpoint.go @@ -3,7 +3,7 @@ package cmd import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // checkpointSubcommand implements the /checkpoint slash command. diff --git a/cmd/chat_subcommand_context.go b/cmd/chat_subcommand_context.go index 76b04e866..db9f862ad 100644 --- a/cmd/chat_subcommand_context.go +++ b/cmd/chat_subcommand_context.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine/project" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine/project" ) // contextSubcommand implements the /context slash command. It @@ -37,7 +37,7 @@ func (c *contextSubcommand) Handle(m *chatModel, args []string, text string) (te } return m, nil } - m.messages = append(m.messages, displayMsg{role: "system", content: hawkconfig.BuildContextWithDirs(addDirs)}) + m.messages = append(m.messages, displayMsg{role: "system", content: rhoconfig.BuildContextWithDirs(addDirs)}) return m, nil } diff --git a/cmd/chat_subcommand_council.go b/cmd/chat_subcommand_council.go index 57db644f3..ff9b4a21a 100644 --- a/cmd/chat_subcommand_council.go +++ b/cmd/chat_subcommand_council.go @@ -7,8 +7,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // councilSubcommand implements the /council slash command. It diff --git a/cmd/chat_subcommand_ecosystem.go b/cmd/chat_subcommand_ecosystem.go index ec4288b94..16ad6240b 100644 --- a/cmd/chat_subcommand_ecosystem.go +++ b/cmd/chat_subcommand_ecosystem.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) // ecosystemSubcommand implements the /ecosystem slash command. It @@ -28,7 +28,7 @@ func (e *ecosystemSubcommand) Handle(m *chatModel, args []string, text string) ( if providerName == "" { providerName = "auto" } - m.messages = append(m.messages, displayMsg{role: "system", content: hawkconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)}) + m.messages = append(m.messages, displayMsg{role: "system", content: rhoconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)}) return m, nil } diff --git a/cmd/chat_subcommand_harness.go b/cmd/chat_subcommand_harness.go index 3861e4640..40c2a6d13 100644 --- a/cmd/chat_subcommand_harness.go +++ b/cmd/chat_subcommand_harness.go @@ -6,7 +6,7 @@ import ( "os" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/harness" + "github.com/GrayCodeAI/rho/internal/harness" ) type harnessSubcommand struct{} @@ -29,7 +29,7 @@ func (h *harnessSubcommand) Handle(m *chatModel, args []string, text string) (te return m.startPromptCommand("/harness", prompt) } - prompt := fmt.Sprintf("I ran a Hawk Agent Harness Review on this repository (%s).\n\nOverall Score: %d/100 (%s)\nPrioritized Findings (%d):\n\n%s\n\nPlease help me address the highest priority findings to improve our AI coding harness.", + prompt := fmt.Sprintf("I ran a Rho Agent Harness Review on this repository (%s).\n\nOverall Score: %d/100 (%s)\nPrioritized Findings (%d):\n\n%s\n\nPlease help me address the highest priority findings to improve our AI coding harness.", report.TargetPath, report.OverallScore, report.OverallStatus, len(report.Findings), harness.RenderMarkdown(report)) return m.startPromptCommand("/harness", prompt) diff --git a/cmd/chat_subcommand_investigate.go b/cmd/chat_subcommand_investigate.go index afd05bda6..971a14946 100644 --- a/cmd/chat_subcommand_investigate.go +++ b/cmd/chat_subcommand_investigate.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // investigateSubcommand implements the /investigate slash command. diff --git a/cmd/chat_subcommand_login.go b/cmd/chat_subcommand_login.go new file mode 100644 index 000000000..2cc4b534d --- /dev/null +++ b/cmd/chat_subcommand_login.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "strings" + + tea "charm.land/bubbletea/v2" +) + +// loginSubcommand implements the /login slash command. Tau uses /login as its +// provider-auth entry point; Rho routes it to the existing config panel, which +// owns credential storage (OS keychain) and provider/model selection. +// +// `/login ` preselects a provider before opening the panel so the +// common "log in to X" flow is one command. +type loginSubcommand struct{} + +func (l *loginSubcommand) Name() string { return "login" } +func (l *loginSubcommand) Aliases() []string { return []string{"auth"} } +func (l *loginSubcommand) Description() string { + return "authenticate a provider (opens the config panel)" +} +func (l *loginSubcommand) Usage() string { return "/login [provider]" } + +func (l *loginSubcommand) Handle(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { + if len(args) > 0 { + provider := strings.TrimSpace(args[0]) + if provider != "" { + return m.handleConfigCommand([]string{"config", "provider", provider}, "") + } + } + // No provider given: open the config panel, which is the credential and + // model setup surface. + return m.handleConfigCommand([]string{"config"}, "") +} + +func init() { + subcommandRegistry.Register(&loginSubcommand{}) +} diff --git a/cmd/chat_subcommand_memory.go b/cmd/chat_subcommand_memory.go index 2195e0cd2..e13fbd2f7 100644 --- a/cmd/chat_subcommand_memory.go +++ b/cmd/chat_subcommand_memory.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) // memorySubcommand implements the /memory slash command. @@ -17,7 +17,7 @@ func (m *memorySubcommand) Aliases() []string { return nil } func (m *memorySubcommand) Description() string { return "print project instructions (AGENTS.md)" } func (m *memorySubcommand) Usage() string { return "" } func (m *memorySubcommand) Handle(ml *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - md := strings.TrimSpace(hawkconfig.LoadAgentsMD()) + md := strings.TrimSpace(rhoconfig.LoadAgentsMD()) if md == "" { ml.messages = append(ml.messages, displayMsg{role: "system", content: "No AGENTS.md project instructions found."}) } else { diff --git a/cmd/chat_subcommand_mode.go b/cmd/chat_subcommand_mode.go index a30ce20bc..4da0f82b5 100644 --- a/cmd/chat_subcommand_mode.go +++ b/cmd/chat_subcommand_mode.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" ) // modeSubcommand implements the /mode slash command. diff --git a/cmd/chat_subcommand_model.go b/cmd/chat_subcommand_model.go index b37f6bd13..f9c020a35 100644 --- a/cmd/chat_subcommand_model.go +++ b/cmd/chat_subcommand_model.go @@ -6,7 +6,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) // modelSubcommand implements the /model slash command. It shows the @@ -60,15 +60,15 @@ func (mo *modelSubcommand) Handle(m *chatModel, args []string, text string) (tea return m, nil } } - if hawkconfig.DeploymentRoutingEnabled(m.settings) { - arg = hawkconfig.ResolveCanonicalModel(arg) + if rhoconfig.DeploymentRoutingEnabled(m.settings) { + arg = rhoconfig.ResolveCanonicalModel(arg) } prevModel := m.session.Model() if strings.EqualFold(strings.TrimSpace(prevModel), strings.TrimSpace(arg)) { m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Already using %s — no change.", prevModel)}) return m, nil } - if err := hawkconfig.SetGlobalSetting("model", arg); err != nil { + if err := rhoconfig.SetGlobalSetting("model", arg); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } @@ -81,11 +81,11 @@ func (mo *modelSubcommand) Handle(m *chatModel, args []string, text string) (tea if m.session != nil { provider = m.session.Provider() } - m.session.SetThinkingEnabled(hawkconfig.ResolveThinkingForModel(hawkconfig.LoadSettings(), arg, provider)) + m.session.SetThinkingEnabled(rhoconfig.ResolveThinkingForModel(rhoconfig.LoadSettings(), arg, provider)) } - thinkLabel := hawkconfig.FormatModelThinkingLabel( - selected != nil && hawkconfig.ModelCapabilitySupportsThinking(selected.Capabilities), - hawkconfig.ThinkingPrefForModel(hawkconfig.LoadSettings(), arg), + thinkLabel := rhoconfig.FormatModelThinkingLabel( + selected != nil && rhoconfig.ModelCapabilitySupportsThinking(selected.Capabilities), + rhoconfig.ThinkingPrefForModel(rhoconfig.LoadSettings(), arg), m.session.Provider(), ) m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf( diff --git a/cmd/chat_subcommand_party.go b/cmd/chat_subcommand_party.go index 898d0cd7b..4aa0c8347 100644 --- a/cmd/chat_subcommand_party.go +++ b/cmd/chat_subcommand_party.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // partySubcommand implements the /party slash command. It starts a diff --git a/cmd/chat_subcommand_path.go b/cmd/chat_subcommand_path.go index 1fd773f57..94d4c6f35 100644 --- a/cmd/chat_subcommand_path.go +++ b/cmd/chat_subcommand_path.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) // pathSubcommand implements the /path slash command. It prints @@ -17,7 +17,7 @@ func (p *pathSubcommand) Aliases() []string { return nil } func (p *pathSubcommand) Description() string { return "show developer path (HOME, GOROOT, etc.)" } func (p *pathSubcommand) Usage() string { return "" } func (p *pathSubcommand) Handle(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - m.messages = append(m.messages, displayMsg{role: "system", content: hawkconfig.FormatDeveloperPathReport(context.Background())}) + m.messages = append(m.messages, displayMsg{role: "system", content: rhoconfig.FormatDeveloperPathReport(context.Background())}) return m, nil } diff --git a/cmd/chat_subcommand_recipe.go b/cmd/chat_subcommand_recipe.go index 7bed6af84..3ccd5bf80 100644 --- a/cmd/chat_subcommand_recipe.go +++ b/cmd/chat_subcommand_recipe.go @@ -7,7 +7,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/recipe" + "github.com/GrayCodeAI/rho/internal/recipe" ) // recipeSubcommand implements the /recipe slash command. It @@ -25,7 +25,7 @@ func (r *recipeSubcommand) Handle(m *chatModel, args []string, text string) (tea rn := recipe.NewRunner() recipes := rn.List() if len(recipes) == 0 { - m.messages = append(m.messages, displayMsg{role: "system", content: "No recipes found in Hawk user state or .agents/recipes/"}) + m.messages = append(m.messages, displayMsg{role: "system", content: "No recipes found in Rho user state or .agents/recipes/"}) } else { var list string for _, r := range recipes { diff --git a/cmd/chat_subcommand_reflect.go b/cmd/chat_subcommand_reflect.go index 9b69ab5f2..efdba6c3a 100644 --- a/cmd/chat_subcommand_reflect.go +++ b/cmd/chat_subcommand_reflect.go @@ -3,7 +3,7 @@ package cmd import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // reflectSubcommand implements the /reflect slash command. It diff --git a/cmd/chat_subcommand_render.go b/cmd/chat_subcommand_render.go index 90fbf48ac..5e5d67bb1 100644 --- a/cmd/chat_subcommand_render.go +++ b/cmd/chat_subcommand_render.go @@ -6,7 +6,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // renderSubcommand implements the /render slash command. It diff --git a/cmd/chat_subcommand_review.go b/cmd/chat_subcommand_review.go index 84fc9cdef..80c181682 100644 --- a/cmd/chat_subcommand_review.go +++ b/cmd/chat_subcommand_review.go @@ -3,7 +3,7 @@ package cmd import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // reviewSubcommand implements the /review slash command. It diff --git a/cmd/chat_subcommand_shell_test.go b/cmd/chat_subcommand_shell_test.go index 7bd5564e2..d18fef640 100644 --- a/cmd/chat_subcommand_shell_test.go +++ b/cmd/chat_subcommand_shell_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestRunSubcommand_UsesBashToolForSafeCommand(t *testing.T) { diff --git a/cmd/chat_subcommand_simple.go b/cmd/chat_subcommand_simple.go index c666ec80b..fdc9a5e0e 100644 --- a/cmd/chat_subcommand_simple.go +++ b/cmd/chat_subcommand_simple.go @@ -11,13 +11,13 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - analytics "github.com/GrayCodeAI/hawk/internal/observability" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/theme" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + analytics "github.com/GrayCodeAI/rho/internal/observability" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // init() registers a large batch of simple /slash commands via @@ -88,7 +88,7 @@ func init() { m.themePicker = NewThemePicker() } // Pre-select the current saved theme. - current := hawkconfig.LoadGlobalSettings().Theme + current := rhoconfig.LoadGlobalSettings().Theme m.themePicker.OpenWithCurrent(current) m.viewDirty = true m.updateViewportContent() @@ -96,7 +96,7 @@ func init() { } // Inline: /theme themeName := args[0] - if err := hawkconfig.SetGlobalSetting("theme", themeName); err != nil { + if err := rhoconfig.SetGlobalSetting("theme", themeName); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { // Apply immediately — full palette swap, no restart needed. @@ -117,7 +117,7 @@ func init() { m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /color "}) return m, nil } - if err := hawkconfig.SetGlobalSetting("agentColor", args[0]); err != nil { + if err := rhoconfig.SetGlobalSetting("agentColor", args[0]); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Agent color set to: %s", args[0])}) @@ -132,12 +132,12 @@ func init() { description: "toggle fast mode (cheapest model for this provider)", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - savedModel := hawkconfig.ActiveModel(context.Background()) + savedModel := rhoconfig.ActiveModel(context.Background()) if m.session.Model() == savedModel { providerName := strings.TrimSpace(m.session.Provider()) - fastModel := hawkconfig.CheapestModelForProvider(providerName, m.session.Model()) + fastModel := rhoconfig.CheapestModelForProvider(providerName, m.session.Model()) if strings.TrimSpace(fastModel) == "" { - fastModel = hawkconfig.DefaultModelForProvider(providerName) + fastModel = rhoconfig.DefaultModelForProvider(providerName) } if strings.TrimSpace(fastModel) == "" { m.messages = append(m.messages, displayMsg{role: "error", content: "Fast mode: no catalog model resolved for this provider"}) @@ -166,7 +166,7 @@ func init() { level := strings.ToLower(args[0]) switch level { case "low", "medium", "high": - _ = hawkconfig.SetGlobalSetting("reasoningEffort", level) + _ = rhoconfig.SetGlobalSetting("reasoningEffort", level) m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Reasoning effort → %s", level)}) default: m.messages = append(m.messages, displayMsg{role: "error", content: "Valid levels: low, medium, high"}) @@ -320,14 +320,14 @@ func init() { description: "toggle compact mode (removes outer padding)", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - settings := hawkconfig.LoadGlobalSettings() + settings := rhoconfig.LoadGlobalSettings() current := settings.CompactMode newVal := !current valStr := "false" if newVal { valStr = "true" } - if err := hawkconfig.SetGlobalSetting("compact_mode", valStr); err != nil { + if err := rhoconfig.SetGlobalSetting("compact_mode", valStr); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { state := "disabled" @@ -349,7 +349,7 @@ func init() { usage: "/scroll-speed <1-100>", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { if len(args) < 1 { - m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Usage: /scroll-speed <1-100>\nCurrent: %d", hawkconfig.LoadGlobalSettings().ScrollSpeed)}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Usage: /scroll-speed <1-100>\nCurrent: %d", rhoconfig.LoadGlobalSettings().ScrollSpeed)}) return m, nil } speed, err := strconv.Atoi(args[0]) @@ -357,7 +357,7 @@ func init() { m.messages = append(m.messages, displayMsg{role: "error", content: "Scroll speed must be 1-100"}) return m, nil } - if err := hawkconfig.SetGlobalSetting("scroll_speed", args[0]); err != nil { + if err := rhoconfig.SetGlobalSetting("scroll_speed", args[0]); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: "Scroll speed → " + args[0]}) @@ -372,7 +372,7 @@ func init() { description: "toggle natural scrolling (invert direction)", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - settings := hawkconfig.LoadGlobalSettings() + settings := rhoconfig.LoadGlobalSettings() current := settings.InvertScroll newVal := !current valStr := "false" @@ -383,7 +383,7 @@ func init() { if newVal { enabled = "enabled" } - if err := hawkconfig.SetGlobalSetting("invert_scroll", valStr); err != nil { + if err := rhoconfig.SetGlobalSetting("invert_scroll", valStr); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: "Natural scrolling " + enabled}) @@ -399,13 +399,13 @@ func init() { usage: "/scroll-mode ", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { if len(args) < 1 { - m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Usage: /scroll-mode \nCurrent: %s", hawkconfig.LoadGlobalSettings().ScrollMode)}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Usage: /scroll-mode \nCurrent: %s", rhoconfig.LoadGlobalSettings().ScrollMode)}) return m, nil } mode := strings.ToLower(args[0]) switch mode { case "auto", "wheel", "trackpad": - if err := hawkconfig.SetGlobalSetting("scrollmode", mode); err != nil { + if err := rhoconfig.SetGlobalSetting("scrollmode", mode); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Scroll mode → %s", mode)}) @@ -444,7 +444,7 @@ func init() { level = "256-color" } - m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Terminal Setup Recommendations:\n\nColor Support: %s\nScroll Mode: %s (use /scroll-mode to change)\nScroll Speed: %d (use /scroll-speed to change)\nCompact Mode: %v (use /compact-mode to toggle)\n\nTips:\n- Set COLORTERM=truecolor for best color experience\n- Use tmux with set -g default-terminal \"tmux-256color\" for 256-color support\n- Enable mouse reporting in your terminal for full TUI interaction", level, hawkconfig.LoadGlobalSettings().ScrollMode, hawkconfig.LoadGlobalSettings().ScrollSpeed, hawkconfig.LoadGlobalSettings().CompactMode)}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Terminal Setup Recommendations:\n\nColor Support: %s\nScroll Mode: %s (use /scroll-mode to change)\nScroll Speed: %d (use /scroll-speed to change)\nCompact Mode: %v (use /compact-mode to toggle)\n\nTips:\n- Set COLORTERM=truecolor for best color experience\n- Use tmux with set -g default-terminal \"tmux-256color\" for 256-color support\n- Enable mouse reporting in your terminal for full TUI interaction", level, rhoconfig.LoadGlobalSettings().ScrollMode, rhoconfig.LoadGlobalSettings().ScrollSpeed, rhoconfig.LoadGlobalSettings().CompactMode)}) return m, nil }, }) @@ -456,7 +456,7 @@ func init() { usage: "/pager-config ", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { if len(args) < 2 { - s := hawkconfig.LoadGlobalSettings() + s := rhoconfig.LoadGlobalSettings() ln := false messages := fmt.Sprintf("Pager Configuration:\n lines: %d (0 = unlimited)\n linenumbers: %v\n\nUsage: /pager-config ", s.PaginatorLines, ln) if s.PaginatorShowLineNums != nil { @@ -474,7 +474,7 @@ func init() { m.messages = append(m.messages, displayMsg{role: "error", content: "Lines must be a positive number (0 = unlimited)"}) return m, nil } - if err := hawkconfig.SetGlobalSetting("paginatorlines", value); err != nil { + if err := rhoconfig.SetGlobalSetting("paginatorlines", value); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Pager lines → %d", lines)}) @@ -483,13 +483,13 @@ func init() { case "linenumbers", "linenums", "ln": switch strings.ToLower(value) { case "1", "true", "yes", "on": - if err := hawkconfig.SetGlobalSetting("paginatorshowlinenumbers", "true"); err != nil { + if err := rhoconfig.SetGlobalSetting("paginatorshowlinenumbers", "true"); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: "Pager line numbers → enabled"}) } case "0", "false", "no", "off": - if err := hawkconfig.SetGlobalSetting("paginatorshowlinenumbers", "false"); err != nil { + if err := rhoconfig.SetGlobalSetting("paginatorshowlinenumbers", "false"); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { m.messages = append(m.messages, displayMsg{role: "system", content: "Pager line numbers → disabled"}) @@ -530,10 +530,10 @@ func init() { // /upgrade — check for updates subcommandRegistry.Register(&delegatingCommand{ name: "upgrade", - description: "check for hawk updates", + description: "check for rho updates", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - return m.startPromptCommand("/upgrade", "Check for hawk updates and show the latest available version.") + return m.startPromptCommand("/upgrade", "Check for rho updates and show the latest available version.") }, }) @@ -783,7 +783,7 @@ func init() { style := strings.ToLower(args[0]) switch style { case "concise", "normal", "detailed": - _ = hawkconfig.SetGlobalSetting("outputStyle", style) + _ = rhoconfig.SetGlobalSetting("outputStyle", style) m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Output style → %s", style)}) default: m.messages = append(m.messages, displayMsg{role: "error", content: "Valid styles: concise, normal, detailed"}) @@ -917,15 +917,15 @@ func init() { }, }) - // /feedback — submit feedback saved to Hawk user state. + // /feedback — submit feedback saved to Rho user state. subcommandRegistry.Register(&delegatingCommand{ name: "feedback", - description: "submit feedback (saved to Hawk user state)", + description: "submit feedback (saved to Rho user state)", usage: "/feedback ", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { body := strings.TrimSpace(strings.TrimPrefix(text, "/feedback")) if body == "" { - m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /feedback \nCaptures session context and saves feedback to Hawk user state."}) + m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /feedback \nCaptures session context and saves feedback to Rho user state."}) return m, nil } feedDir := filepath.Join(storage.StateDir(), "feedback") @@ -1030,7 +1030,7 @@ func init() { description: "show provider deployment status", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - report, err := hawkconfig.DeploymentStatusReport(context.Background(), m.session.Model()) + report, err := rhoconfig.DeploymentStatusReport(context.Background(), m.session.Model()) if err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: fmt.Sprintf("Provider status failed: %v", err)}) return m, nil @@ -1046,7 +1046,7 @@ func init() { description: "refresh the model catalog", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - summary, err := hawkconfig.RefreshModelCatalogV1(context.Background()) + summary, err := rhoconfig.RefreshModelCatalogV1(context.Background()) if err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: fmt.Sprintf("Model catalog refresh failed: %v", err)}) return m, nil diff --git a/cmd/chat_subcommand_soul.go b/cmd/chat_subcommand_soul.go index 0dd3f3bc6..333d6c01e 100644 --- a/cmd/chat_subcommand_soul.go +++ b/cmd/chat_subcommand_soul.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // soulSubcommand implements the /soul slash command. It shows the diff --git a/cmd/chat_subcommand_spec.go b/cmd/chat_subcommand_spec.go index 7f2f2efee..ae9e0ba0c 100644 --- a/cmd/chat_subcommand_spec.go +++ b/cmd/chat_subcommand_spec.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/spec" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/spec" ) // specSubcommand implements the /spec slash command: starts (or reports diff --git a/cmd/chat_subcommand_start.go b/cmd/chat_subcommand_start.go index 338bf0d24..fef470b0f 100644 --- a/cmd/chat_subcommand_start.go +++ b/cmd/chat_subcommand_start.go @@ -6,7 +6,7 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // startSubcommand is the first-run / guided success path. @@ -32,7 +32,7 @@ func (c *startSubcommand) Handle(m *chatModel, args []string, text string) (tea. } var b strings.Builder - b.WriteString("## Hawk quick start\n\n") + b.WriteString("## Rho quick start\n\n") // 1. Model / session if m.session != nil { diff --git a/cmd/chat_subcommand_status.go b/cmd/chat_subcommand_status.go index 0d9463bbf..b0c2489e4 100644 --- a/cmd/chat_subcommand_status.go +++ b/cmd/chat_subcommand_status.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // statusSubcommand implements the /status slash command. It prints diff --git a/cmd/chat_subcommand_trust.go b/cmd/chat_subcommand_trust.go index f205b8ccd..786c3d0ec 100644 --- a/cmd/chat_subcommand_trust.go +++ b/cmd/chat_subcommand_trust.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // trustSubcommand manages folder trust from the chat TUI. diff --git a/cmd/chat_subcommand_version.go b/cmd/chat_subcommand_version.go index 98ff13528..f9eac8efb 100644 --- a/cmd/chat_subcommand_version.go +++ b/cmd/chat_subcommand_version.go @@ -7,16 +7,16 @@ import ( ) // versionSubcommand implements the /version slash command. It prints -// the running hawk version. Follows the SubcommandRegistry pattern +// the running rho version. Follows the SubcommandRegistry pattern // demonstrated in chat_subcommand_branch.go. type versionSubcommand struct{} func (v *versionSubcommand) Name() string { return "version" } func (v *versionSubcommand) Aliases() []string { return nil } -func (v *versionSubcommand) Description() string { return "print the running hawk version" } +func (v *versionSubcommand) Description() string { return "print the running rho version" } func (v *versionSubcommand) Usage() string { return "" } func (v *versionSubcommand) Handle(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("hawk v%s", DisplayVersion())}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("rho v%s", DisplayVersion())}) return m, nil } diff --git a/cmd/chat_subcommand_voice.go b/cmd/chat_subcommand_voice.go index 0c16b47e7..b3cfb62bc 100644 --- a/cmd/chat_subcommand_voice.go +++ b/cmd/chat_subcommand_voice.go @@ -47,7 +47,7 @@ func (v *voiceSubcommand) Handle(m *chatModel, args []string, text string) (tea. // outcome as a voiceResultMsg handled in the update loop. func (v *voiceSubcommand) recordAndTranscribe() tea.Cmd { return func() tea.Msg { - tmpFile := filepath.Join(os.TempDir(), "hawk_voice_input.wav") + tmpFile := filepath.Join(os.TempDir(), "rho_voice_input.wav") var recordCmd *exec.Cmd if _, err := exec.LookPath("sox"); err == nil { recordCmd = exec.Command("sox", "-d", tmpFile, "trim", "0", "10") // #nosec G204 -- fixed command 'sox' resolved via exec.LookPath diff --git a/cmd/chat_submit.go b/cmd/chat_submit.go index 276568739..43bbe4a7f 100644 --- a/cmd/chat_submit.go +++ b/cmd/chat_submit.go @@ -11,11 +11,11 @@ import ( tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // submitUserMessage handles Enter on a non-empty prompt (slash commands, shell, or agent turn). @@ -77,7 +77,7 @@ func (m chatModel) submitUserMessage() (chatModel, tea.Cmd) { } return m, cmd } - if setup := hawkconfig.EvaluateSetupCached(context.Background()); setup.NeedsSetup { + if setup := rhoconfig.EvaluateSetupCached(context.Background()); setup.NeedsSetup { hint := setup.Hint if hint == "" { hint = "Complete setup in /config (keychain + model)." diff --git a/cmd/chat_terminal_mouse.go b/cmd/chat_terminal_mouse.go index 7221aace6..a9ea61eb6 100644 --- a/cmd/chat_terminal_mouse.go +++ b/cmd/chat_terminal_mouse.go @@ -11,7 +11,7 @@ import ( // scroll events to arrive as literal "[<65;99;16M" KeyRunes in the input. const ( disableMouseCSI = "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l" - // Explicitly disable any-motion mode left by an older Hawk process, then + // Explicitly disable any-motion mode left by an older Rho process, then // enable button/cell tracking. Wheel and click events still arrive, while // ordinary pointer movement no longer floods the Bubble Tea update loop. enableMouseCSI = "\x1b[?1003l\x1b[?1006h\x1b[?1002h" diff --git a/cmd/chat_tools.go b/cmd/chat_tools.go index ec168b5a7..49315aae9 100644 --- a/cmd/chat_tools.go +++ b/cmd/chat_tools.go @@ -6,9 +6,9 @@ import ( "sync" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/lsp" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/lsp" + "github.com/GrayCodeAI/rho/internal/tool" ) // This file holds the tool-registry construction used by the chat TUI: @@ -188,7 +188,7 @@ func optionalTools() []tool.Tool { } } -func configuredStartupMCPServers(settings hawkconfig.Settings) []startupMCPServerSpec { +func configuredStartupMCPServers(settings rhoconfig.Settings) []startupMCPServerSpec { servers := make([]startupMCPServerSpec, 0, len(settings.MCPServers)+len(mcpServers)) for _, cfg := range settings.MCPServers { if cfg.Name == "" { @@ -237,7 +237,7 @@ func configuredStartupMCPServers(settings hawkconfig.Settings) []startupMCPServe // non-expired OAuth token is stored for this server (see // internal/tool/mcp_auth.go). A configured static Authorization header, if // any, takes precedence over the auto-injected one. -func mergedMCPHeaders(cfg hawkconfig.MCPServerConfig) map[string]string { +func mergedMCPHeaders(cfg rhoconfig.MCPServerConfig) map[string]string { headers := make(map[string]string, len(cfg.Headers)+1) for k, v := range cfg.Headers { headers[k] = v @@ -286,7 +286,7 @@ func loadStartupMCPToolSetsWith(loadMCP func(context.Context, string, string, .. return results } -func defaultRegistry(settings hawkconfig.Settings) (*tool.Registry, error) { +func defaultRegistry(settings rhoconfig.Settings) (*tool.Registry, error) { // Load essential tools first for fast startup tools := essentialTools() if tool.IsPowerShellAvailable() { diff --git a/cmd/chat_tools_lsp_test.go b/cmd/chat_tools_lsp_test.go index 373efeb5c..d1984ea55 100644 --- a/cmd/chat_tools_lsp_test.go +++ b/cmd/chat_tools_lsp_test.go @@ -3,12 +3,12 @@ package cmd import ( "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestDefaultRegistryWiresLanguageServerManager(t *testing.T) { - registry, err := defaultRegistry(hawkconfig.Settings{}) + registry, err := defaultRegistry(rhoconfig.Settings{}) if err != nil { t.Fatal(err) } diff --git a/cmd/chat_tools_test.go b/cmd/chat_tools_test.go index 5e226751e..0a9077c78 100644 --- a/cmd/chat_tools_test.go +++ b/cmd/chat_tools_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/tool" ) type registryTestTool struct { @@ -72,8 +72,8 @@ func TestLoadStartupMCPToolSets_UsesTimeoutAndPreservesOrder(t *testing.T) { } func TestConfiguredStartupMCPServers_DispatchesByType(t *testing.T) { - settings := hawkconfig.Settings{ - MCPServers: []hawkconfig.MCPServerConfig{ + settings := rhoconfig.Settings{ + MCPServers: []rhoconfig.MCPServerConfig{ {Name: "stdio-default", Command: "stdio-mcp"}, {Name: "stdio-explicit", Type: "stdio", Command: "stdio-mcp-2"}, {Name: "http-server", Type: "http", URL: "https://example.com/mcp"}, @@ -176,7 +176,7 @@ func TestLoadStartupMCPToolSets_DispatchesRemoteSpecsToRemoteLoader(t *testing.T } func TestMergedMCPHeaders_ConfiguredAuthorizationTakesPrecedence(t *testing.T) { - cfg := hawkconfig.MCPServerConfig{ + cfg := rhoconfig.MCPServerConfig{ Name: "svc", Headers: map[string]string{"Authorization": "Bearer static-token", "X-Other": "1"}, } @@ -220,8 +220,8 @@ func TestDefaultRegistry_SkipsFailedStartupMCPServers(t *testing.T) { return nil, errors.New("boom") } - registry, err := defaultRegistry(hawkconfig.Settings{ - MCPServers: []hawkconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}, + registry, err := defaultRegistry(rhoconfig.Settings{ + MCPServers: []rhoconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}, }) if err != nil { t.Fatalf("defaultRegistry returned error: %v", err) diff --git a/cmd/chat_update.go b/cmd/chat_update.go index 4b020b87b..393b93b21 100644 --- a/cmd/chat_update.go +++ b/cmd/chat_update.go @@ -11,12 +11,12 @@ import ( "charm.land/bubbles/v2/spinner" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/spec" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/spec" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // This file holds the Bubble Tea event loop for the chat TUI: the central @@ -581,7 +581,7 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { chosen, handled := m.themePicker.Update(msg) if handled { if chosen != nil { - if err := hawkconfig.SetGlobalSetting("theme", chosen.Name); err != nil { + if err := rhoconfig.SetGlobalSetting("theme", chosen.Name); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { // Apply immediately — repaints with full palette on next frame. @@ -1069,7 +1069,7 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.configSaving = false if msg.err != nil { if m.configOpen { - m.configNotice = sanitizeConfigNotice(hawkconfig.FormatConfigProviderError(msg.provider, msg.err)) + m.configNotice = sanitizeConfigNotice(rhoconfig.FormatConfigProviderError(msg.provider, msg.err)) m.viewDirty = true m.updateViewportContent() } @@ -1086,7 +1086,7 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.configNotice = "" } } else if m.configOpen { - m.configNotice = hawkconfig.CatalogEmptyHint(context.Background()) + m.configNotice = rhoconfig.CatalogEmptyHint(context.Background()) } if m.session != nil && msg.provider != "" { gw, _ := m.sessionGatewayModel() @@ -1381,8 +1381,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { line := fmt.Sprintf( "Context compacted (%s): ~%s → ~%s tokens", msg.strategy, - formatHawkTokenCount(msg.tokensBefore), - formatHawkTokenCount(msg.tokensAfter), + formatRhoTokenCount(msg.tokensBefore), + formatRhoTokenCount(msg.tokensAfter), ) m.messages = append(m.messages, displayMsg{role: "system", content: line}) m.invalidateConnStatus() @@ -1466,7 +1466,7 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Send terminal notification if terminal was not focused during the // turn and the agent produced output (not just tool activity). if m.backgrounded && !wasCancelled && hadOutput { - sendTerminalNotification("hawk", "Agent turn complete") + sendTerminalNotification("rho", "Agent turn complete") } m.backgrounded = false m.notifiedComplete = false diff --git a/cmd/chat_view.go b/cmd/chat_view.go index b6a3bceec..16d18b180 100644 --- a/cmd/chat_view.go +++ b/cmd/chat_view.go @@ -11,7 +11,7 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/mattn/go-runewidth" ) @@ -61,11 +61,11 @@ var ( reCreator = regexp.MustCompile(`(?i)(made|created|developed|built|trained|designed)\s+by\s+(?:a\s+company\s+called\s+|a\s+team\s+(?:at|called)\s+|the\s+team\s+at\s+)?\*{0,2}(Moonshot\s*AI|OpenAI|Anthropic|Google|Google\s*DeepMind|DeepMind|Meta|Meta\s*AI|Alibaba|Alibaba\s*Cloud|Mistral\s*AI|xAI|Microsoft|Microsoft\s*AI|Amazon|AWS|Cohere|01\.AI|Baidu|Huawei|IBM|Nvidia|EleutherAI|Hugging\s*Face|AI21\s*Labs|Yandex|Databricks|StepFun|Xiaomi|Sarvam\s*AI|MiniMax|BharatGen|Z\.ai|Zhipu\s*AI|Cerebras|Technology\s*Innovation\s*Institute|TII|Inflection\s*AI|Stability\s*AI|Anysphere|Cognition\s*AI|Scale\s*AI|Sakana\s*AI)\*{0,2}`) ) -// sanitizeIdentity replaces model self-identifications with "hawk" / "GrayCode AI". +// sanitizeIdentity replaces model self-identifications with "rho" / "GrayCode AI". func sanitizeIdentity(s string) string { s = reModelName.ReplaceAllStringFunc(s, func(m string) string { parts := reModelName.FindStringSubmatch(m) - return parts[1] + " hawk" + return parts[1] + " rho" }) s = reCreator.ReplaceAllString(s, "${1} by GrayCode AI") return s @@ -547,7 +547,7 @@ func renderPermissionBox(summary string, width int, timeoutAt time.Time) string } } body := lipgloss.JoinVertical(lipgloss.Left, bodyParts...) - options := lipgloss.NewStyle().Foreground(hawkColor).Render("[y] allow once [n] deny [a] always allow tool [d] always deny tool") + options := lipgloss.NewStyle().Foreground(rhoColor).Render("[y] allow once [n] deny [a] always allow tool [d] always deny tool") hint := lipgloss.NewStyle().Foreground(textMuted).Render("Esc cancels · prompt times out after 5 minutes") rows := []string{title, "", body} @@ -642,7 +642,7 @@ func renderReflectionBox(reflection string, width int) string { boxW = 40 } - titleStyle := lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + titleStyle := lipgloss.NewStyle().Foreground(rhoColor).Bold(true) labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true) // blue contentStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("252")) // light gray @@ -672,7 +672,7 @@ func renderReflectionBox(reflection string, width int) string { border := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(hawkColor). + BorderForeground(rhoColor). Width(boxW). Padding(0, 1) @@ -711,12 +711,12 @@ func (m *chatModel) renderTokenCounters() string { var b strings.Builder b.WriteString(ansiMagenta + ansiBold + "↓" + ansiReset) b.WriteString(ansiMagenta) - b.WriteString(formatHawkTokenCount(outTok)) + b.WriteString(formatRhoTokenCount(outTok)) b.WriteString(ansiReset) b.WriteString(ansiDim + " " + ansiReset) b.WriteString(ansiCyan + ansiBold + "↑" + ansiReset) b.WriteString(ansiCyan) - b.WriteString(formatHawkTokenCount(inTok)) + b.WriteString(formatRhoTokenCount(inTok)) b.WriteString(ansiReset) return b.String() } @@ -753,9 +753,9 @@ func (m *chatModel) tokenOutputTarget() int { return m.turnEstimatedOutputRunes / 4 } -// formatHawkTokenCount renders a token count in hawk's compact form: +// formatRhoTokenCount renders a token count in rho's compact form: // ≥1m → "1.5m", ≥10k → "150k", else raw digits. -func formatHawkTokenCount(tokens int) string { +func formatRhoTokenCount(tokens int) string { if tokens <= 0 { return "0" } diff --git a/cmd/chat_view_test.go b/cmd/chat_view_test.go index 11927fe25..4662015da 100644 --- a/cmd/chat_view_test.go +++ b/cmd/chat_view_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestFormatHawkTokenCount(t *testing.T) { +func TestFormatRhoTokenCount(t *testing.T) { t.Parallel() cases := []struct { in int @@ -25,9 +25,9 @@ func TestFormatHawkTokenCount(t *testing.T) { tc := tc t.Run("", func(t *testing.T) { t.Parallel() - got := formatHawkTokenCount(tc.in) + got := formatRhoTokenCount(tc.in) if got != tc.want { - t.Fatalf("formatHawkTokenCount(%d) = %q, want %q", tc.in, got, tc.want) + t.Fatalf("formatRhoTokenCount(%d) = %q, want %q", tc.in, got, tc.want) } }) } diff --git a/cmd/chat_viewport.go b/cmd/chat_viewport.go index 5df6beef6..3e4230871 100644 --- a/cmd/chat_viewport.go +++ b/cmd/chat_viewport.go @@ -237,7 +237,7 @@ func (m chatModel) effectiveWheelY(msg tea.MouseMsg) int { return y } -// syncViewportMouseWheel disables bubbletea viewport auto-wheel; hawk routes wheel +// syncViewportMouseWheel disables bubbletea viewport auto-wheel; rho routes wheel // events manually so chat scrolls only when the pointer is over the chat pane. func (m chatModel) syncViewportMouseWheel() chatModel { m.viewport.MouseWheelEnabled = false diff --git a/cmd/chat_viewport_render.go b/cmd/chat_viewport_render.go index b47d721c4..7e8fa60f0 100644 --- a/cmd/chat_viewport_render.go +++ b/cmd/chat_viewport_render.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // Viewport render cache — avoids re-wrapping and re-rendering markdown for the @@ -30,7 +30,7 @@ const ( ) func renderDisplayMessage(msg displayMsg, i int, messages []displayMsg, viewWidth int, expanded map[int]bool) string { - hawkC := ansiOrange + rhoC := ansiOrange rst := ansiReset bgDark := "\033[48;2;30;30;40m" @@ -50,7 +50,7 @@ func renderDisplayMessage(msg displayMsg, i int, messages []displayMsg, viewWidt wrappedLines := strings.Split(wrapped, "\n") for li, wl := range wrappedLines { if li == 0 { - b.WriteString(bgDark + hawkC + "█" + rst + bgDark + " " + wl) + b.WriteString(bgDark + rhoC + "█" + rst + bgDark + " " + wl) } else { b.WriteString(bgDark + " " + wl) } @@ -70,7 +70,7 @@ func renderDisplayMessage(msg displayMsg, i int, messages []displayMsg, viewWidt // message entirely rather than leaving an orphan "◈" line. return "" } - b.WriteString(hawkC + icons.Robot() + " " + rst + renderMarkdown(content, viewWidth-3)) + b.WriteString(rhoC + icons.Robot() + " " + rst + renderMarkdown(content, viewWidth-3)) case "tool_use": b.WriteString(toolStyle.Render(icons.CircleFilled() + " " + msg.content)) case "tool_result": diff --git a/cmd/chat_viewport_test.go b/cmd/chat_viewport_test.go index af9988b9b..b812e331f 100644 --- a/cmd/chat_viewport_test.go +++ b/cmd/chat_viewport_test.go @@ -8,7 +8,7 @@ import ( "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestRouteKeyToViewport_ArrowsInPromptFocus(t *testing.T) { @@ -84,20 +84,20 @@ func TestShouldRouteMouseToViewport_SplitPaneUX(t *testing.T) { } func TestSyncViewportMouseWheel_ManualRouting(t *testing.T) { - t.Setenv("HAWK_MOUSE", "") + t.Setenv("RHO_MOUSE", "") vp := viewport.New(viewport.WithWidth(80), viewport.WithHeight(10)) m := chatModel{viewport: vp, uiFocus: focusPrompt} m = m.syncViewportMouseWheel() if m.viewport.MouseWheelEnabled { - t.Fatal("viewport auto-wheel must stay off; hawk routes wheel by pane") + t.Fatal("viewport auto-wheel must stay off; rho routes wheel by pane") } } func TestSyncViewportMouseWheel_DisabledWithOptOut(t *testing.T) { - t.Setenv("HAWK_MOUSE", "0") + t.Setenv("RHO_MOUSE", "0") vp := viewport.New(viewport.WithWidth(80), viewport.WithHeight(10)) disabled := false - m := chatModel{viewport: vp, uiFocus: focusPrompt, settings: hawkconfig.Settings{TuiMouse: &disabled}} + m := chatModel{viewport: vp, uiFocus: focusPrompt, settings: rhoconfig.Settings{TuiMouse: &disabled}} m = m.syncViewportMouseWheel() if m.viewport.MouseWheelEnabled { t.Fatal("wheel should be disabled when mouse capture is off") diff --git a/cmd/chat_welcome.go b/cmd/chat_welcome.go index e397d6299..180968b9c 100644 --- a/cmd/chat_welcome.go +++ b/cmd/chat_welcome.go @@ -9,23 +9,23 @@ import ( "github.com/mattn/go-runewidth" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) type welcomeStatusSnapshot struct { - setup hawkconfig.SetupState + setup rhoconfig.SetupState agentsOK bool } func loadWelcomeStatusSnapshot() welcomeStatusSnapshot { ctx := context.Background() return welcomeStatusSnapshot{ - setup: hawkconfig.EvaluateSetupCached(ctx), - agentsOK: hawkconfig.LoadAgentsMD() != "", + setup: rhoconfig.EvaluateSetupCached(ctx), + agentsOK: rhoconfig.LoadAgentsMD() != "", } } @@ -71,8 +71,8 @@ func (m *chatModel) rebuildWelcomeCache(opts ...any) { m.welcomeCache = buildWelcomeMessageWithSnapshot(m.session, m.sessionID, m.registry, nil, m.settings, skillsCount, connectedMCPCount(m.registry), frame, width, height, m.welcomeStatusSnapshot(), m.lastCommand) } -// buildWelcomeMessage renders the branded inline HAWK welcome block. -func buildWelcomeMessage(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings hawkconfig.Settings, skillsCount int, blinkClosed bool, width, height int) string { +// buildWelcomeMessage renders the branded inline RHO welcome block. +func buildWelcomeMessage(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings rhoconfig.Settings, skillsCount int, blinkClosed bool, width, height int) string { frame := 0 if blinkClosed { frame = 2 @@ -80,8 +80,8 @@ func buildWelcomeMessage(sess *engine.Session, sessionID string, registry *tool. return buildWelcomeMessageWithSnapshot(sess, sessionID, registry, saved, settings, skillsCount, connectedMCPCount(registry), frame, width, height, loadWelcomeStatusSnapshot(), "") } -func buildWelcomeMessageWithSnapshot(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings hawkconfig.Settings, skillsCount, mcpCount int, eyeFrame int, width, height int, snapshot welcomeStatusSnapshot, lastCommand string) string { - // Talon Gold is used for the HAWK wordmark. All escapes come from the +func buildWelcomeMessageWithSnapshot(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings rhoconfig.Settings, skillsCount, mcpCount int, eyeFrame int, width, height int, snapshot welcomeStatusSnapshot, lastCommand string) string { + // Talon Gold is used for the RHO wordmark. All escapes come from the // theme palette (theme.go) so a rebrand stays a one-file change. logoC := ansiOrange dimC := ansiDim @@ -119,7 +119,7 @@ func buildWelcomeMessageWithSnapshot(sess *engine.Session, sessionID string, reg return strings.Repeat(" ", pad) + styled } - art := hawkLogoArtLines + art := rhoLogoArtLines var eyeGlyph string switch eyeFrame { case 1, 3: @@ -128,13 +128,13 @@ func buildWelcomeMessageWithSnapshot(sess *engine.Session, sessionID string, reg eyeGlyph = "|-\\/-|" } if eyeGlyph != "" { - art = append([]string(nil), hawkLogoArtLines...) + art = append([]string(nil), rhoLogoArtLines...) for i, line := range art { art[i] = strings.Replace(line, "|0\\/0|", eyeGlyph, 1) } } - // Inject the version into the hawk's body — centered in the lower gap. + // Inject the version into the rho's body — centered in the lower gap. verStr := DisplayVersion() if verStr != "" && !strings.HasPrefix(verStr, "v") && !strings.HasPrefix(verStr, "V") { verStr = "v" + verStr @@ -157,13 +157,13 @@ func buildWelcomeMessageWithSnapshot(sess *engine.Session, sessionID string, reg if tight { // Compact single-line wordmark for small terminals — version sits - // inline so it's always visible even when the full hawk is hidden. + // inline so it's always visible even when the full rho is hidden. verDisplay := DisplayVersion() if verDisplay != "" && !strings.HasPrefix(verDisplay, "v") && !strings.HasPrefix(verDisplay, "V") { verDisplay = "v" + verDisplay } - compactArt := logoC + "HAWK" + rst + " " + verDisplay - b.WriteString(center(runewidth.StringWidth("HAWK "+verDisplay), compactArt) + "\n") + compactArt := logoC + "RHO" + rst + " " + verDisplay + b.WriteString(center(runewidth.StringWidth("RHO "+verDisplay), compactArt) + "\n") } else { artW := blockLinesWidth(art) for _, line := range art { @@ -346,7 +346,7 @@ func envSummary(provider, model string) string { func envSummaryWithSelection(provider, model string, includeSelection bool) string { var providers []string - for _, gateway := range hawkconfig.GatewayStatuses(context.Background(), provider, model) { + for _, gateway := range rhoconfig.GatewayStatuses(context.Background(), provider, model) { providers = append(providers, gateway.ID) } sort.Strings(providers) @@ -354,17 +354,17 @@ func envSummaryWithSelection(provider, model string, includeSelection bool) stri if includeSelection { b.WriteString(fmt.Sprintf("Provider: %s\nModel: %s\n\n", provider, model)) } - b.WriteString(fmt.Sprintf("Credentials (%s):\n", hawkconfig.CredentialStoreName())) + b.WriteString(fmt.Sprintf("Credentials (%s):\n", rhoconfig.CredentialStoreName())) for _, providerID := range providers { - b.WriteString(fmt.Sprintf(" %s: %s\n", providerID, hawkconfig.EnvKeyStatus(providerID))) + b.WriteString(fmt.Sprintf(" %s: %s\n", providerID, rhoconfig.EnvKeyStatus(providerID))) } return strings.TrimRight(b.String(), "\n") } -func configCommandSummary(settings hawkconfig.Settings) string { +func configCommandSummary(settings rhoconfig.Settings) string { _ = settings - providerName := displayConfigValue(hawkconfig.ActiveProvider(context.Background())) - modelName := displayConfigValue(hawkconfig.ActiveModel(context.Background())) + providerName := displayConfigValue(rhoconfig.ActiveProvider(context.Background())) + modelName := displayConfigValue(rhoconfig.ActiveModel(context.Background())) keys := configuredKeyList() keysColor := infoSky if keys == "(none)" { @@ -374,14 +374,14 @@ func configCommandSummary(settings hawkconfig.Settings) string { /config → paste API key (OS keychain) + pick model /path → verify readiness in TUI - hawk path (CLI) + rho path (CLI) %s: %s %s %s %s %s %s -Model catalog and routing live in eyrie — hawk is the UI only.`, +Model catalog and routing live in eyrie — rho is the UI only.`, auditTint("Setup (eyrie)", textPrimary), auditTint("Current", textPrimary), auditTint("provider:", textMuted), auditTint(providerName, infoSky), @@ -390,7 +390,7 @@ Model catalog and routing live in eyrie — hawk is the UI only.`, } func apiKeyConfigSummary() string { - return auditTint("API keys ("+hawkconfig.CredentialStoreName()+")", textPrimary) + "\n" + indentedAPIKeyLines() + return auditTint("API keys ("+rhoconfig.CredentialStoreName()+")", textPrimary) + "\n" + indentedAPIKeyLines() } func configuredKeyList() string { @@ -436,11 +436,11 @@ func apiKeyStatusColor(status string) color.Color { } func apiKeyStatusLines() []string { - providers := hawkconfig.AllSetupGateways() + providers := rhoconfig.AllSetupGateways() sort.Strings(providers) var lines []string for _, provider := range providers { - lines = append(lines, fmt.Sprintf("%s: %s", provider, hawkconfig.EnvKeyStatus(provider))) + lines = append(lines, fmt.Sprintf("%s: %s", provider, rhoconfig.EnvKeyStatus(provider))) } return lines } diff --git a/cmd/chat_yolo_confirm_test.go b/cmd/chat_yolo_confirm_test.go index b6987c80f..4df3e49ae 100644 --- a/cmd/chat_yolo_confirm_test.go +++ b/cmd/chat_yolo_confirm_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestYOLOConfirm_PendingConsumesNextInput(t *testing.T) { diff --git a/cmd/checkpoint.go b/cmd/checkpoint.go index de7a04d4a..b406e25db 100644 --- a/cmd/checkpoint.go +++ b/cmd/checkpoint.go @@ -5,19 +5,19 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/session" "github.com/spf13/cobra" ) // checkpointCmd groups named session-checkpoint operations: snapshot the current // (or latest) session under a label, list snapshots, restore one, or delete one. // This is additive on top of `--resume `; named checkpoints let you save a -// labeled point you can come back to with `hawk resume `. +// labeled point you can come back to with `rho resume `. var checkpointCmd = &cobra.Command{ Use: "checkpoint", Short: "Save and restore named session checkpoints", Long: `checkpoint snapshots a session under a human-friendly label so you can -return to it later with "hawk resume ". +return to it later with "rho resume ". Subcommands: save Snapshot the latest session in this directory under @@ -49,7 +49,7 @@ var checkpointSaveCmd = &cobra.Command{ return err } cmd.Printf("%s\n", auditTint("Saved checkpoint ", doneGreen)+auditTint(cp.Name, textPrimary)+auditTint(fmt.Sprintf(" (session %s, %d messages, %s/%s)", cp.Session.ID, len(cp.Session.Messages), cp.Session.Provider, cp.Session.Model), textMuted)) - cmd.Printf("%s\n", auditTint("Resume with: hawk resume "+name, textMuted)) + cmd.Printf("%s\n", auditTint("Resume with: rho resume "+name, textMuted)) return nil }, } @@ -121,12 +121,12 @@ var checkpointDeleteCmd = &cobra.Command{ }, } -// resumeCmd is a top-level convenience for `hawk resume `: it restores a +// resumeCmd is a top-level convenience for `rho resume `: it restores a // named checkpoint into a session file and tells the user how to continue it. var resumeCmd = &cobra.Command{ Use: "resume ", Short: "Restore a named session checkpoint and resume it", - Long: `resume restores a session previously saved with "hawk checkpoint save" + Long: `resume restores a session previously saved with "rho checkpoint save" into a resumable session, then prints the command to continue the conversation.`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -150,7 +150,7 @@ func restoreNamedCheckpoint(cmd *cobra.Command, name string) error { return fmt.Errorf("restore session: %w", err) } cmd.Printf("%s\n", auditTint("Restored checkpoint ", doneGreen)+auditTint(cp.Name, textPrimary)+auditTint(fmt.Sprintf(" into session %s (%d messages, %s/%s)", cp.Session.ID, len(cp.Session.Messages), cp.Session.Provider, cp.Session.Model), textMuted)) - cmd.Printf("%s\n", auditTint("Continue with: hawk --resume "+cp.Session.ID, textMuted)) + cmd.Printf("%s\n", auditTint("Continue with: rho --resume "+cp.Session.ID, textMuted)) return nil } diff --git a/cmd/cli_contracts_test.go b/cmd/cli_contracts_test.go index 6fb96ef5b..010b99b8f 100644 --- a/cmd/cli_contracts_test.go +++ b/cmd/cli_contracts_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/session" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/session" ) func TestResumeRecoveredSession_StartsChatFlow(t *testing.T) { @@ -62,7 +62,7 @@ func TestResumeRecoveredSession_StartsChatFlow(t *testing.T) { } func TestPrepareSession_ResumeUsesRecoveryPath(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) oldResumeID := resumeID oldContinueFlag := continueFlag @@ -121,7 +121,7 @@ func TestPrepareSession_ResumeUsesRecoveryPath(t *testing.T) { t.Fatalf("loaded persisted messages = %d, want %d", sess.MessageCount(), len(saved.Messages)) } - walPath := filepath.Join(os.Getenv("HAWK_STATE_DIR"), "sessions", saved.ID+".wal") + walPath := filepath.Join(os.Getenv("RHO_STATE_DIR"), "sessions", saved.ID+".wal") if _, err := os.Stat(walPath); !os.IsNotExist(err) { t.Fatalf("expected stale WAL to be removed, stat err = %v", err) } @@ -154,7 +154,7 @@ func TestReplBuiltinResponse_ToolsAndSession(t *testing.T) { sess := engine.NewSession("demo-provider", "demo-model", "system", nil) sess.AddUser("hello") - toolsOut, handled, err := replBuiltinResponse("/tools", sess, hawkconfig.Settings{}, "session-1") + toolsOut, handled, err := replBuiltinResponse("/tools", sess, rhoconfig.Settings{}, "session-1") if err != nil { t.Fatalf("/tools error = %v", err) } @@ -165,7 +165,7 @@ func TestReplBuiltinResponse_ToolsAndSession(t *testing.T) { t.Fatalf("/tools output missing tool summary: %q", toolsOut) } - sessionOut, handled, err := replBuiltinResponse("/session", sess, hawkconfig.Settings{}, "session-1") + sessionOut, handled, err := replBuiltinResponse("/session", sess, rhoconfig.Settings{}, "session-1") if err != nil { t.Fatalf("/session error = %v", err) } @@ -181,7 +181,7 @@ func TestReplBuiltinResponse_ToolsAndSession(t *testing.T) { func TestReplBuiltinResponse_Models(t *testing.T) { sess := engine.NewSession("openrouter", "openrouter/auto", "system", nil) - out, handled, err := replBuiltinResponse("/models", sess, hawkconfig.Settings{}, "session-1") + out, handled, err := replBuiltinResponse("/models", sess, rhoconfig.Settings{}, "session-1") if err != nil { t.Fatalf("/models error = %v", err) } @@ -218,7 +218,7 @@ func TestPluginListSubcommandUsesCobraTree(t *testing.T) { } func TestRecoverCommand_ExecutesResumeFlow(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) oldResumeID := resumeID oldContinueFlag := continueFlag diff --git a/cmd/clipboard.go b/cmd/clipboard.go index 3cf3f1d38..5dc2e99bc 100644 --- a/cmd/clipboard.go +++ b/cmd/clipboard.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) // copyResult describes where the copied content ended up. @@ -68,7 +68,7 @@ func copyToClipboardNative(text string) error { } // copyToFallbackFile writes the given text to a timestamped file in the -// hawk state directory when the system clipboard is unavailable. This +// rho state directory when the system clipboard is unavailable. This // ensures the user can still recover copied content from SSH sessions, // containers, or headless environments. func copyToFallbackFile(text string) (string, error) { diff --git a/cmd/clipboard_test.go b/cmd/clipboard_test.go index cd762fdd2..c0437147b 100644 --- a/cmd/clipboard_test.go +++ b/cmd/clipboard_test.go @@ -30,7 +30,7 @@ func TestClipboardRoundTrip(t *testing.T) { t.Skip("skipping clipboard test on linux (requires xclip/xsel)") } - text := "hawk clipboard test" + text := "rho clipboard test" if err := copyToClipboardNative(text); err != nil { t.Skipf("native clipboard unavailable: %v", err) } diff --git a/cmd/cloud.go b/cmd/cloud.go index ec44e9b7d..0b09bbd38 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -7,15 +7,15 @@ import ( "runtime" "time" - cloud "github.com/GrayCodeAI/hawk/internal/platform/cloud" + cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" "github.com/spf13/cobra" ) -var cloudCmd = &cobra.Command{Use: "cloud", Short: "Manage optional Hawk Cloud synchronization"} +var cloudCmd = &cobra.Command{Use: "cloud", Short: "Manage optional Rho Cloud synchronization"} var cloudConnectCmd = &cobra.Command{ Use: "connect", - Short: "Connect this Hawk device to Hawk Cloud", + Short: "Connect this Rho device to Rho Cloud", RunE: func(cmd *cobra.Command, _ []string) error { endpoint, _ := cmd.Flags().GetString("endpoint") deviceID, _ := cmd.Flags().GetString("device-id") @@ -27,22 +27,22 @@ var cloudConnectCmd = &cobra.Command{ if err := cloud.SaveDeviceConfig(cloud.DeviceConfig{Endpoint: endpoint, DeviceID: deviceID, ProjectID: projectID}, token); err != nil { return err } - cmd.Println(auditTint("Hawk Cloud connected. Usage synchronization is opt-in and fail-open.", doneGreen)) + cmd.Println(auditTint("Rho Cloud connected. Usage synchronization is opt-in and fail-open.", doneGreen)) return nil }, } var cloudLoginCmd = &cobra.Command{ Use: "login", - Short: "Sign in to Hawk Cloud in a browser", + Short: "Sign in to Rho Cloud in a browser", RunE: func(cmd *cobra.Command, _ []string) error { endpoint, _ := cmd.Flags().GetString("endpoint") label, _ := cmd.Flags().GetString("label") if endpoint == "" { - endpoint = os.Getenv("HAWK_CLOUD_URL") + endpoint = os.Getenv("RHO_CLOUD_URL") } if endpoint == "" { - return fmt.Errorf("hawk cloud endpoint is required (use --endpoint or HAWK_CLOUD_URL)") + return fmt.Errorf("rho cloud endpoint is required (use --endpoint or RHO_CLOUD_URL)") } if label == "" { label, _ = os.Hostname() @@ -54,7 +54,7 @@ var cloudLoginCmd = &cobra.Command{ if err != nil { return err } - cmd.Printf("%s\n", auditTint("Open ", textPrimary)+auditTint(start.VerificationURI, infoSky)+auditTint(" and enter code ", textPrimary)+auditTint(start.UserCode, hawkColor)) + cmd.Printf("%s\n", auditTint("Open ", textPrimary)+auditTint(start.VerificationURI, infoSky)+auditTint(" and enter code ", textPrimary)+auditTint(start.UserCode, rhoColor)) if err := openBrowser(start.VerificationURI + "?code=" + start.UserCode); err != nil { cmd.Printf("%s\n", auditTint(fmt.Sprintf("Could not open the browser automatically: %v", err), textMuted)) } @@ -82,7 +82,7 @@ var cloudLoginCmd = &cobra.Command{ case "approved": if poll.Token == "" || poll.DeviceID == "" || poll.ProjectID == "" { prog.FailStep(0, "incomplete device authorization") - return fmt.Errorf("hawk cloud returned an incomplete device authorization") + return fmt.Errorf("rho cloud returned an incomplete device authorization") } if err := cloud.SaveDeviceConfig(cloud.DeviceConfig{Endpoint: endpoint, DeviceID: poll.DeviceID, ProjectID: poll.ProjectID}, poll.Token); err != nil { prog.FailStep(0, err.Error()) @@ -90,39 +90,39 @@ var cloudLoginCmd = &cobra.Command{ } prog.CompleteStep(0) prog.Done() - cmd.Println(auditTint("Hawk Cloud connected for project ", doneGreen) + auditTint(poll.ProjectID, textPrimary) + auditTint(".", doneGreen)) + cmd.Println(auditTint("Rho Cloud connected for project ", doneGreen) + auditTint(poll.ProjectID, textPrimary) + auditTint(".", doneGreen)) return nil case "expired": prog.FailStep(0, "device authorization expired") - return fmt.Errorf("hawk cloud device authorization expired") + return fmt.Errorf("rho cloud device authorization expired") default: prog.FailStep(0, fmt.Sprintf("unknown status %q", poll.Status)) - return fmt.Errorf("hawk cloud returned unknown device authorization status %q", poll.Status) + return fmt.Errorf("rho cloud returned unknown device authorization status %q", poll.Status) } } }, } var cloudStatusCmd = &cobra.Command{ - Use: "status", Short: "Show Hawk Cloud connection status", + Use: "status", Short: "Show Rho Cloud connection status", RunE: func(cmd *cobra.Command, _ []string) error { client, cfg, err := cloud.LoadClient() if err != nil || !client.Enabled() { - cmd.Println(auditTint("Hawk Cloud is not connected.", textMuted)) + cmd.Println(auditTint("Rho Cloud is not connected.", textMuted)) return nil } - cmd.Println(auditTint("Hawk Cloud connected: ", doneGreen) + auditTint(cfg.Endpoint, textPrimary) + auditTint(fmt.Sprintf(" (device %s, project %s)", cfg.DeviceID, cfg.ProjectID), textMuted)) + cmd.Println(auditTint("Rho Cloud connected: ", doneGreen) + auditTint(cfg.Endpoint, textPrimary) + auditTint(fmt.Sprintf(" (device %s, project %s)", cfg.DeviceID, cfg.ProjectID), textMuted)) return nil }, } var cloudContextCmd = &cobra.Command{ Use: "context", - Short: "Sync repository context to Hawk Cloud", + Short: "Sync repository context to Rho Cloud", RunE: func(cmd *cobra.Command, _ []string) error { client, cfg, err := cloud.LoadClient() if err != nil || !client.Enabled() { - return fmt.Errorf("hawk cloud is not connected") + return fmt.Errorf("rho cloud is not connected") } detected, detectErr := detectGitContext(cmd.Context()) repository, _ := cmd.Flags().GetString("repository") @@ -182,7 +182,7 @@ var cloudContextCmd = &cobra.Command{ event.Deployment = &cloud.DeploymentContext{Provider: contextProvider, ExternalID: deploymentID, Environment: deploymentEnvironment, Status: deploymentStatus} } client.RecordDeliveryContext(cmd.Context(), event) - cmd.Println(auditTint("Repository context queued for Hawk Cloud.", doneGreen)) + cmd.Println(auditTint("Repository context queued for Rho Cloud.", doneGreen)) return nil }, } @@ -197,12 +197,12 @@ func firstValue(values ...string) string { } func init() { - cloudLoginCmd.Flags().String("endpoint", "", "Hawk Cloud endpoint (or HAWK_CLOUD_URL)") - cloudLoginCmd.Flags().String("label", "", "Name for this Hawk device") - cloudConnectCmd.Flags().String("endpoint", "", "Hawk Cloud endpoint") - cloudConnectCmd.Flags().String("device-id", "", "Hawk Cloud device ID") - cloudConnectCmd.Flags().String("project-id", "", "Hawk Cloud project ID") - cloudConnectCmd.Flags().String("token", "", "Hawk Cloud device token") + cloudLoginCmd.Flags().String("endpoint", "", "Rho Cloud endpoint (or RHO_CLOUD_URL)") + cloudLoginCmd.Flags().String("label", "", "Name for this Rho device") + cloudConnectCmd.Flags().String("endpoint", "", "Rho Cloud endpoint") + cloudConnectCmd.Flags().String("device-id", "", "Rho Cloud device ID") + cloudConnectCmd.Flags().String("project-id", "", "Rho Cloud project ID") + cloudConnectCmd.Flags().String("token", "", "Rho Cloud device token") cloudContextCmd.Flags().String("repository", "", "Repository name (auto-detected from Git when omitted)") cloudContextCmd.Flags().String("provider", "", "Repository provider (auto-detected when omitted)") cloudContextCmd.Flags().String("external-id", "", "Provider repository identifier (defaults to repository)") diff --git a/cmd/cloud_context_test.go b/cmd/cloud_context_test.go index c4f05c309..6941350c2 100644 --- a/cmd/cloud_context_test.go +++ b/cmd/cloud_context_test.go @@ -12,7 +12,7 @@ func TestDetectGitContext(t *testing.T) { runCloudGit = func(_ context.Context, args ...string) (string, error) { switch args[0] { case "config": - return "git@github.com:GrayCodeAI/hawk.git", nil + return "git@github.com:GrayCodeAI/rho.git", nil case "branch": return "main", nil case "rev-parse": @@ -25,7 +25,7 @@ func TestDetectGitContext(t *testing.T) { if err != nil { t.Fatal(err) } - if got.Repository != "GrayCodeAI/hawk" || got.Provider != "github" || got.Branch != "main" || got.Commit != "abc123" { + if got.Repository != "GrayCodeAI/rho" || got.Provider != "github" || got.Branch != "main" || got.Commit != "abc123" { t.Fatalf("context = %+v", got) } } diff --git a/cmd/cloud_graph.go b/cmd/cloud_graph.go index 7df5e3d6f..8be34a963 100644 --- a/cmd/cloud_graph.go +++ b/cmd/cloud_graph.go @@ -5,8 +5,8 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - cloud "github.com/GrayCodeAI/hawk/internal/platform/cloud" + "github.com/GrayCodeAI/rho/internal/executiongraph" + cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" "github.com/spf13/cobra" ) @@ -22,15 +22,15 @@ func newCloudGraphCmd() *cobra.Command { var missionDir string syncCmd := &cobra.Command{ Use: "sync [session-id]", - Short: "Upload a privacy-normalized execution graph to Hawk Cloud", - Long: `Build the same read-only execution graph as "hawk graph export", hash -cloud-sensitive metadata, enforce Hawk Cloud's upload bounds, and upload it + Short: "Upload a privacy-normalized execution graph to Rho Cloud", + Long: `Build the same read-only execution graph as "rho graph export", hash +cloud-sensitive metadata, enforce Rho Cloud's upload bounds, and upload it with a deterministic idempotency key. Sync completed session snapshots: graph facts are immutable after acceptance. This is explicit and opt-in; local execution never depends on cloud synchronization.`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - prog := NewCLIProgress("Graph sync", []string{"Building execution graph", "Uploading to Hawk Cloud"}) + prog := NewCLIProgress("Graph sync", []string{"Building execution graph", "Uploading to Rho Cloud"}) defer prog.Abort() prog.StartStep(0) var export executiongraph.Export @@ -53,11 +53,11 @@ execution never depends on cloud synchronization.`, } client, cfg, err := cloud.LoadClient() if err != nil || !client.Enabled() { - return fmt.Errorf("hawk cloud is not connected") + return fmt.Errorf("rho cloud is not connected") } prepared, err := cloud.PrepareGraph(export) if err != nil { - return fmt.Errorf("prepare graph for Hawk Cloud: %w", err) + return fmt.Errorf("prepare graph for Rho Cloud: %w", err) } prog.CompleteStep(0) prog.StartStep(1) diff --git a/cmd/cmdhistory_cmd.go b/cmd/cmdhistory_cmd.go index 3ad938fb4..67c93dc1c 100644 --- a/cmd/cmdhistory_cmd.go +++ b/cmd/cmdhistory_cmd.go @@ -6,8 +6,8 @@ import ( "path/filepath" "strconv" - "github.com/GrayCodeAI/hawk/internal/cmdhistory" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/cmdhistory" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) diff --git a/cmd/command_history.go b/cmd/command_history.go index 04a1a50ad..7ceda8148 100644 --- a/cmd/command_history.go +++ b/cmd/command_history.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) // commandHistory persists recently-used slash commands so the command diff --git a/cmd/compact_ui.go b/cmd/compact_ui.go index a8b9268db..a85852274 100644 --- a/cmd/compact_ui.go +++ b/cmd/compact_ui.go @@ -11,8 +11,8 @@ import ( lipgloss "charm.land/lipgloss/v2" "github.com/mattn/go-runewidth" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) const compactProgressBarWidth = 40 diff --git a/cmd/compact_ui_test.go b/cmd/compact_ui_test.go index 8effc7322..0257de29e 100644 --- a/cmd/compact_ui_test.go +++ b/cmd/compact_ui_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestRenderContextUsageBar(t *testing.T) { @@ -32,7 +32,7 @@ func TestContextUsagePercentForBar(t *testing.T) { func TestRenderCompactProgressPanel(t *testing.T) { m := chatModel{ session: &engine.Session{}, - brailleSpinner: NewBrailleSpinner(SpinnerHawk, "Compacting conversation"), + brailleSpinner: NewBrailleSpinner(SpinnerRho, "Compacting conversation"), manualCompacting: true, } out := m.renderCompactProgressPanel(80) diff --git a/cmd/compat-test/drift.go b/cmd/compat-test/drift.go index decf73efd..f9820c35c 100644 --- a/cmd/compat-test/drift.go +++ b/cmd/compat-test/drift.go @@ -9,22 +9,22 @@ import ( ) // trackedPins are the shared leaf dependencies most likely to drift silently: -// a sibling repo can pin an older version than what hawk's own go.mod requires, -// and Go's minimal version selection will silently pull in hawk's newer version -// at build time without the consumer's own CI ever having tested it. Hawk's +// a sibling repo can pin an older version than what rho's own go.mod requires, +// and Go's minimal version selection will silently pull in rho's newer version +// at build time without the consumer's own CI ever having tested it. Rho's // only external GrayCodeAI dependency is eyrie. See docs/compatibility.md. var trackedPins = []string{ "github.com/GrayCodeAI/eyrie", } -// checkDrift compares hawk's own go.mod requirements for trackedPins against -// what each sibling repo (a peer checkout of a hawk dependency in the shared +// checkDrift compares rho's own go.mod requirements for trackedPins against +// what each sibling repo (a peer checkout of a rho dependency in the shared // workspace) declares for the same modules in its own go.mod. It never fails — // this is advisory, printed for humans/CI logs to notice, not a build gate. func checkDrift(repoRoot string) error { - hawkRequires, err := readRequires(filepath.Join(repoRoot, "go.mod")) + rhoRequires, err := readRequires(filepath.Join(repoRoot, "go.mod")) if err != nil { - return fmt.Errorf("read hawk go.mod: %w", err) + return fmt.Errorf("read rho go.mod: %w", err) } workspaceDir := filepath.Join(repoRoot, "..") @@ -45,18 +45,18 @@ func checkDrift(repoRoot string) error { continue // sibling not a Go module / no go.mod — skip silently } for _, pin := range trackedPins { - hawkVer, hawkHas := hawkRequires[pin] + rhoVer, rhoHas := rhoRequires[pin] consumerVer, consumerHas := consumerRequires[pin] - if !hawkHas || !consumerHas || hawkVer == consumerVer { + if !rhoHas || !consumerHas || rhoVer == consumerVer { continue } drifted++ - fmt.Printf(" %-22s requires %s@%s, hawk requires %s@%s\n", - e.Name(), pin, consumerVer, pin, hawkVer) + fmt.Printf(" %-22s requires %s@%s, rho requires %s@%s\n", + e.Name(), pin, consumerVer, pin, rhoVer) } } if drifted == 0 { - fmt.Println(" OK — no drift between hawk's pins and sibling consumers") + fmt.Println(" OK — no drift between rho's pins and sibling consumers") } return nil } diff --git a/cmd/compat-test/drift_test.go b/cmd/compat-test/drift_test.go index 3e2971c4c..c6546ddf2 100644 --- a/cmd/compat-test/drift_test.go +++ b/cmd/compat-test/drift_test.go @@ -68,10 +68,10 @@ func TestReadRequires_InvalidMod(t *testing.T) { } } -// TestCheckDrift reports drift when a consumer pins an older version than hawk. +// TestCheckDrift reports drift when a consumer pins an older version than rho. func TestCheckDrift_DetectsDrift(t *testing.T) { ws := t.TempDir() - writeMod(t, filepath.Join(ws, "hawk", "go.mod"), `module github.com/GrayCodeAI/hawk + writeMod(t, filepath.Join(ws, "rho", "go.mod"), `module github.com/GrayCodeAI/rho go 1.26 @@ -89,7 +89,7 @@ require github.com/GrayCodeAI/eyrie v1.2.0 old := os.Stdout r, w, _ := os.Pipe() os.Stdout = w - err := checkDrift(filepath.Join(ws, "hawk")) + err := checkDrift(filepath.Join(ws, "rho")) _ = w.Close() os.Stdout = old _, _ = buf.ReadFrom(r) @@ -106,7 +106,7 @@ require github.com/GrayCodeAI/eyrie v1.2.0 // pins produce the "OK" line and no per-consumer drift lines. func TestCheckDrift_NoDriftWhenVersionsMatch(t *testing.T) { ws := t.TempDir() - writeMod(t, filepath.Join(ws, "hawk", "go.mod"), `module github.com/GrayCodeAI/hawk + writeMod(t, filepath.Join(ws, "rho", "go.mod"), `module github.com/GrayCodeAI/rho go 1.26 @@ -123,7 +123,7 @@ require github.com/GrayCodeAI/eyrie v1.5.0 r, w, _ := os.Pipe() old := os.Stdout os.Stdout = w - err := checkDrift(filepath.Join(ws, "hawk")) + err := checkDrift(filepath.Join(ws, "rho")) _ = w.Close() os.Stdout = old _, _ = buf.ReadFrom(r) @@ -140,7 +140,7 @@ require github.com/GrayCodeAI/eyrie v1.5.0 // without a go.mod (e.g. not a Go module) is skipped without failing. func TestCheckDrift_SkipsMissingSiblings(t *testing.T) { ws := t.TempDir() - writeMod(t, filepath.Join(ws, "hawk", "go.mod"), `module github.com/GrayCodeAI/hawk + writeMod(t, filepath.Join(ws, "rho", "go.mod"), `module github.com/GrayCodeAI/rho go 1.26 @@ -154,7 +154,7 @@ require github.com/GrayCodeAI/eyrie v1.5.0 r, w, _ := os.Pipe() old := os.Stdout os.Stdout = w - err := checkDrift(filepath.Join(ws, "hawk")) + err := checkDrift(filepath.Join(ws, "rho")) _ = w.Close() os.Stdout = old _, _ = io.Copy(io.Discard, r) diff --git a/cmd/compat-test/main.go b/cmd/compat-test/main.go index 8f7ae7d8f..4fe909e4d 100644 --- a/cmd/compat-test/main.go +++ b/cmd/compat-test/main.go @@ -17,7 +17,7 @@ // go run ./cmd/compat-test -matrix=stable -strict // # exit non-zero if any // # component lacks a version -// go run ./cmd/compat-test -check-external # advisory: compare hawk's own +// go run ./cmd/compat-test -check-external # advisory: compare rho's own // # go.mod pins for shared leaf // # deps against what each // # sibling repo declares. @@ -176,7 +176,7 @@ func report(mf matrixFile, m matrix, strict bool) error { } // findMatrixFile locates the cross-repo compatibility matrix (testdata/compatibility-matrix.json). -// It must not pick hawk/platform-capabilities.json, which is a different document. +// It must not pick rho/platform-capabilities.json, which is a different document. func findMatrixFile() string { dir, err := os.Getwd() if err != nil { @@ -184,7 +184,7 @@ func findMatrixFile() string { } candidates := []string{ "testdata/compatibility-matrix.json", - "hawk/testdata/compatibility-matrix.json", + "rho/testdata/compatibility-matrix.json", } for i := 0; i < 6; i++ { for _, rel := range candidates { diff --git a/cmd/completions.go b/cmd/completions.go index 04b69aa39..548c7fcf0 100644 --- a/cmd/completions.go +++ b/cmd/completions.go @@ -9,8 +9,8 @@ import ( "sort" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/routing" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -32,7 +32,7 @@ type CommandInfo struct { Flags []FlagInfo `json:"flags,omitempty"` } -// CompletionGenerator generates shell completion scripts for hawk. +// CompletionGenerator generates shell completion scripts for rho. type CompletionGenerator struct { Commands []CommandInfo `json:"commands"` Flags []FlagInfo `json:"flags"` @@ -41,7 +41,7 @@ type CompletionGenerator struct { Providers []string `json:"providers"` } -// NewCompletionGenerator creates a CompletionGenerator pre-populated with hawk's +// NewCompletionGenerator creates a CompletionGenerator pre-populated with rho's // command structure, flags, providers, models, and slash commands. func NewCompletionGenerator() *CompletionGenerator { g := &CompletionGenerator{} @@ -63,7 +63,7 @@ func (g *CompletionGenerator) populateFlags() { } func (g *CompletionGenerator) populateProviders() { - g.Providers = hawkconfig.AllSetupGateways() + g.Providers = rhoconfig.AllSetupGateways() } func (g *CompletionGenerator) populateModels() { @@ -91,12 +91,12 @@ func (g *CompletionGenerator) populateSlashCommands() { } } -// GenerateBash returns a complete bash completion script for hawk. +// GenerateBash returns a complete bash completion script for rho. func (g *CompletionGenerator) GenerateBash() string { var b strings.Builder - b.WriteString("# bash completion for hawk\n") - b.WriteString("# Auto-generated by hawk completions generator\n\n") + b.WriteString("# bash completion for rho\n") + b.WriteString("# Auto-generated by rho completions generator\n\n") // Build subcommand list subcommands := make([]string, 0, len(g.Commands)) @@ -119,7 +119,7 @@ func (g *CompletionGenerator) GenerateBash() string { // Build slash commands list slashCmds := strings.Join(g.SlashCommands, " ") - b.WriteString("_hawk_completions() {\n") + b.WriteString("_rho_completions() {\n") b.WriteString(" local cur prev words cword\n") b.WriteString(" _init_completion || return\n") b.WriteString("\n") @@ -166,7 +166,7 @@ func (g *CompletionGenerator) GenerateBash() string { b.WriteString(" return 0\n") b.WriteString(" fi\n") b.WriteString("\n") - b.WriteString(" # Complete subcommands after hawk\n") + b.WriteString(" # Complete subcommands after rho\n") b.WriteString(" if [[ ${COMP_CWORD} -eq 1 ]]; then\n") b.WriteString(fmt.Sprintf(" COMPREPLY=($(compgen -W \"%s\" -- \"$cur\"))\n", strings.Join(subcommands, " "))) b.WriteString(" return 0\n") @@ -194,20 +194,20 @@ func (g *CompletionGenerator) GenerateBash() string { b.WriteString(" return 0\n") b.WriteString("}\n") b.WriteString("\n") - b.WriteString("complete -F _hawk_completions hawk\n") + b.WriteString("complete -F _rho_completions rho\n") return b.String() } -// GenerateZsh returns a complete zsh completion script for hawk. +// GenerateZsh returns a complete zsh completion script for rho. func (g *CompletionGenerator) GenerateZsh() string { var b strings.Builder - b.WriteString("#compdef hawk\n") - b.WriteString("# zsh completion for hawk\n") - b.WriteString("# Auto-generated by hawk completions generator\n\n") + b.WriteString("#compdef rho\n") + b.WriteString("# zsh completion for rho\n") + b.WriteString("# Auto-generated by rho completions generator\n\n") - b.WriteString("_hawk() {\n") + b.WriteString("_rho() {\n") b.WriteString(" local -a commands\n") b.WriteString(" local -a global_flags\n") b.WriteString("\n") @@ -250,7 +250,7 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString(" case $state in\n") b.WriteString(" commands)\n") - b.WriteString(" _describe -t commands 'hawk commands' commands\n") + b.WriteString(" _describe -t commands 'rho commands' commands\n") b.WriteString(" ;;\n") b.WriteString(" args)\n") b.WriteString(" case $words[1] in\n") @@ -273,7 +273,7 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString("}\n\n") // Slash commands completion function - b.WriteString("_hawk_slash_commands() {\n") + b.WriteString("_rho_slash_commands() {\n") b.WriteString(" local -a slash_commands\n") b.WriteString(" slash_commands=(\n") for _, sc := range g.SlashCommands { @@ -284,7 +284,7 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString("}\n\n") // Provider completion function - b.WriteString("_hawk_providers() {\n") + b.WriteString("_rho_providers() {\n") b.WriteString(" local -a providers\n") b.WriteString(" providers=(\n") for _, p := range g.Providers { @@ -294,25 +294,25 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString(" compadd -a providers\n") b.WriteString("}\n\n") - b.WriteString("_hawk \"$@\"\n") + b.WriteString("_rho \"$@\"\n") return b.String() } -// GenerateFish returns a complete fish shell completion script for hawk. +// GenerateFish returns a complete fish shell completion script for rho. func (g *CompletionGenerator) GenerateFish() string { var b strings.Builder - b.WriteString("# fish completion for hawk\n") - b.WriteString("# Auto-generated by hawk completions generator\n\n") + b.WriteString("# fish completion for rho\n") + b.WriteString("# Auto-generated by rho completions generator\n\n") // Disable file completions by default - b.WriteString("complete -c hawk -f\n\n") + b.WriteString("complete -c rho -f\n\n") // Subcommands b.WriteString("# Subcommands\n") for _, cmd := range g.Commands { - b.WriteString(fmt.Sprintf("complete -c hawk -n '__fish_use_subcommand' -a '%s' -d '%s'\n", + b.WriteString(fmt.Sprintf("complete -c rho -n '__fish_use_subcommand' -a '%s' -d '%s'\n", cmd.Name, escapeFish(cmd.Description))) } b.WriteString("\n") @@ -322,7 +322,7 @@ func (g *CompletionGenerator) GenerateFish() string { if len(cmd.Subcommands) > 0 { b.WriteString(fmt.Sprintf("# %s subcommands\n", cmd.Name)) for _, sc := range cmd.Subcommands { - b.WriteString(fmt.Sprintf("complete -c hawk -n '__fish_seen_subcommand_from %s' -a '%s' -d '%s'\n", + b.WriteString(fmt.Sprintf("complete -c rho -n '__fish_seen_subcommand_from %s' -a '%s' -d '%s'\n", cmd.Name, sc.Name, escapeFish(sc.Description))) } b.WriteString("\n") @@ -332,10 +332,10 @@ func (g *CompletionGenerator) GenerateFish() string { b.WriteString(fmt.Sprintf("# %s flags\n", cmd.Name)) for _, f := range cmd.Flags { if f.Short != "" { - b.WriteString(fmt.Sprintf("complete -c hawk -n '__fish_seen_subcommand_from %s' -l '%s' -s '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c rho -n '__fish_seen_subcommand_from %s' -l '%s' -s '%s' -d '%s'", cmd.Name, f.Name, f.Short, escapeFish(f.Description))) } else { - b.WriteString(fmt.Sprintf("complete -c hawk -n '__fish_seen_subcommand_from %s' -l '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c rho -n '__fish_seen_subcommand_from %s' -l '%s' -d '%s'", cmd.Name, f.Name, escapeFish(f.Description))) } if f.Type == "bool" { @@ -356,10 +356,10 @@ func (g *CompletionGenerator) GenerateFish() string { b.WriteString("# Global flags\n") for _, f := range g.Flags { if f.Short != "" { - b.WriteString(fmt.Sprintf("complete -c hawk -l '%s' -s '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c rho -l '%s' -s '%s' -d '%s'", f.Name, f.Short, escapeFish(f.Description))) } else { - b.WriteString(fmt.Sprintf("complete -c hawk -l '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c rho -l '%s' -d '%s'", f.Name, escapeFish(f.Description))) } if f.Type == "bool" { @@ -376,14 +376,14 @@ func (g *CompletionGenerator) GenerateFish() string { // Provider completions for --provider b.WriteString("# Provider completions\n") - b.WriteString(fmt.Sprintf("complete -c hawk -l 'provider' -r -a '%s' -d 'LLM provider'\n", + b.WriteString(fmt.Sprintf("complete -c rho -l 'provider' -r -a '%s' -d 'LLM provider'\n", strings.Join(g.Providers, " "))) b.WriteString("\n") // Slash commands b.WriteString("# Slash commands (for interactive mode reference)\n") for _, sc := range g.SlashCommands { - b.WriteString(fmt.Sprintf("complete -c hawk -a '%s' -d 'Slash command'\n", sc)) + b.WriteString(fmt.Sprintf("complete -c rho -a '%s' -d 'Slash command'\n", sc)) } return b.String() @@ -404,7 +404,7 @@ func (g *CompletionGenerator) GenerateJSON() (string, error) { Providers []string `json:"providers"` Models []string `json:"models"` }{ - Name: "hawk", + Name: "rho", Version: v, Commands: g.Commands, GlobalFlags: g.Flags, @@ -534,11 +534,11 @@ func bashInstallPath() string { // Prefer user-local path home, err := os.UserHomeDir() if err != nil { - return "/etc/bash_completion.d/hawk" + return "/etc/bash_completion.d/rho" } localDir := filepath.Join(home, ".local", "share", "bash-completion", "completions") if info, err := os.Stat(localDir); err == nil && info.IsDir() { - return filepath.Join(localDir, "hawk") + return filepath.Join(localDir, "rho") } // On macOS, use homebrew path if available. // ARM Macs (M1+) install Homebrew to /opt/homebrew; Intel Macs use /usr/local. @@ -546,17 +546,17 @@ func bashInstallPath() string { for _, prefix := range []string{"/opt/homebrew", "/usr/local"} { brewDir := filepath.Join(prefix, "etc", "bash_completion.d") if info, err := os.Stat(brewDir); err == nil && info.IsDir() { - return filepath.Join(brewDir, "hawk") + return filepath.Join(brewDir, "rho") } } } // Fallback: try system-wide sysDir := "/etc/bash_completion.d" if info, err := os.Stat(sysDir); err == nil && info.IsDir() { - return filepath.Join(sysDir, "hawk") + return filepath.Join(sysDir, "rho") } // Final fallback: user-local - return filepath.Join(home, ".local", "share", "bash-completion", "completions", "hawk") + return filepath.Join(home, ".local", "share", "bash-completion", "completions", "rho") } func zshInstallPath() string { @@ -567,28 +567,28 @@ func zshInstallPath() string { for _, p := range parts { if p != "" { if info, err := os.Stat(p); err == nil && info.IsDir() { // #nosec G703 -- shell completion only probes the user-selected directory - return filepath.Join(p, "_hawk") + return filepath.Join(p, "_rho") } } } // Use first entry even if it doesn't exist yet if parts[0] != "" { - return filepath.Join(parts[0], "_hawk") + return filepath.Join(parts[0], "_rho") } } home, err := os.UserHomeDir() if err != nil { - return "/usr/local/share/zsh/site-functions/_hawk" + return "/usr/local/share/zsh/site-functions/_rho" } - return filepath.Join(home, ".zsh", "completions", "_hawk") + return filepath.Join(home, ".zsh", "completions", "_rho") } func fishInstallPath() string { home, err := os.UserHomeDir() if err != nil { - return filepath.Join(os.TempDir(), "hawk.fish") + return filepath.Join(os.TempDir(), "rho.fish") } - return filepath.Join(home, ".config", "fish", "completions", "hawk.fish") + return filepath.Join(home, ".config", "fish", "completions", "rho.fish") } // escapeZsh escapes single quotes for zsh completion descriptions. diff --git a/cmd/completions_test.go b/cmd/completions_test.go index 9949bfd46..ee5dd0c7e 100644 --- a/cmd/completions_test.go +++ b/cmd/completions_test.go @@ -35,10 +35,10 @@ func TestGenerateBashContainsFunctionDefinition(t *testing.T) { g := NewCompletionGenerator() bash := g.GenerateBash() - if !strings.Contains(bash, "_hawk_completions()") { - t.Error("Bash completion should contain _hawk_completions() function definition") + if !strings.Contains(bash, "_rho_completions()") { + t.Error("Bash completion should contain _rho_completions() function definition") } - if !strings.Contains(bash, "complete -F _hawk_completions hawk") { + if !strings.Contains(bash, "complete -F _rho_completions rho") { t.Error("Bash completion should register the completion function with 'complete'") } } @@ -97,8 +97,8 @@ func TestGenerateZshContainsCompdefHeader(t *testing.T) { g := NewCompletionGenerator() zsh := g.GenerateZsh() - if !strings.HasPrefix(zsh, "#compdef hawk") { - t.Error("Zsh completion should start with #compdef hawk header") + if !strings.HasPrefix(zsh, "#compdef rho") { + t.Error("Zsh completion should start with #compdef rho header") } } @@ -106,8 +106,8 @@ func TestGenerateZshContainsFunction(t *testing.T) { g := NewCompletionGenerator() zsh := g.GenerateZsh() - if !strings.Contains(zsh, "_hawk()") { - t.Error("Zsh completion should contain _hawk() function") + if !strings.Contains(zsh, "_rho()") { + t.Error("Zsh completion should contain _rho() function") } if !strings.Contains(zsh, "_arguments") { t.Error("Zsh completion should use _arguments for flag completion") @@ -156,8 +156,8 @@ func TestGenerateFishContainsCompleteDirectives(t *testing.T) { g := NewCompletionGenerator() fish := g.GenerateFish() - if !strings.Contains(fish, "complete -c hawk") { - t.Error("Fish completion should contain 'complete -c hawk' directives") + if !strings.Contains(fish, "complete -c rho") { + t.Error("Fish completion should contain 'complete -c rho' directives") } } @@ -319,8 +319,8 @@ func TestInstallCompletionBash(t *testing.T) { if path == "" { t.Error("InstallCompletion(bash) returned empty path") } - if !strings.Contains(path, "hawk") { - t.Errorf("Bash install path should contain 'hawk', got %q", path) + if !strings.Contains(path, "rho") { + t.Errorf("Bash install path should contain 'rho', got %q", path) } // Should be a bash-related path if !strings.Contains(path, "bash") && !strings.Contains(path, "completion") { @@ -336,8 +336,8 @@ func TestInstallCompletionZsh(t *testing.T) { if path == "" { t.Error("InstallCompletion(zsh) returned empty path") } - if !strings.Contains(path, "_hawk") { - t.Errorf("Zsh install path should contain '_hawk', got %q", path) + if !strings.Contains(path, "_rho") { + t.Errorf("Zsh install path should contain '_rho', got %q", path) } } @@ -349,8 +349,8 @@ func TestInstallCompletionFish(t *testing.T) { if path == "" { t.Error("InstallCompletion(fish) returned empty path") } - if !strings.Contains(path, "hawk.fish") { - t.Errorf("Fish install path should contain 'hawk.fish', got %q", path) + if !strings.Contains(path, "rho.fish") { + t.Errorf("Fish install path should contain 'rho.fish', got %q", path) } if !strings.Contains(path, "fish") { t.Errorf("Fish install path should be fish-related, got %q", path) @@ -507,8 +507,8 @@ func TestGenerateZshProviderChoices(t *testing.T) { zsh := g.GenerateZsh() // The provider completion function should list all providers - if !strings.Contains(zsh, "_hawk_providers()") { - t.Error("Zsh completion should contain _hawk_providers() function") + if !strings.Contains(zsh, "_rho_providers()") { + t.Error("Zsh completion should contain _rho_providers() function") } } diff --git a/cmd/config_table.go b/cmd/config_table.go index 3bfa9157e..4aa68cb99 100644 --- a/cmd/config_table.go +++ b/cmd/config_table.go @@ -5,7 +5,7 @@ import ( "strings" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/mattn/go-runewidth" ) diff --git a/cmd/context_export.go b/cmd/context_export.go index 81ab48fe0..e46b901ca 100644 --- a/cmd/context_export.go +++ b/cmd/context_export.go @@ -9,9 +9,9 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/fsutil" - "github.com/GrayCodeAI/hawk/internal/intelligence/repomap" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/fsutil" + "github.com/GrayCodeAI/rho/internal/intelligence/repomap" + "github.com/GrayCodeAI/rho/internal/tool" ) // ExportContext generates a comprehensive context document about the current project. @@ -62,7 +62,7 @@ func ExportContext(dir string, focus string) (string, error) { } // AGENTS.md / project instructions - for _, instrFile := range []string{"AGENTS.md", "AGENTS.md", "CLAUDE.md", ".hawk.md"} { + for _, instrFile := range []string{"AGENTS.md", "AGENTS.md", "CLAUDE.md", ".rho.md"} { data, err := os.ReadFile(filepath.Join(dir, instrFile)) // #nosec G304 -- instrFile is one of a fixed set of well-known project instruction filenames if err == nil && len(data) > 0 { b.WriteString(fmt.Sprintf("## Project Instructions (%s)\n\n%s\n\n", instrFile, strings.TrimSpace(string(data)))) diff --git a/cmd/contextual_help.go b/cmd/contextual_help.go index 401931b26..bf7317a2e 100644 --- a/cmd/contextual_help.go +++ b/cmd/contextual_help.go @@ -78,7 +78,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "/undo", Summary: "Undo last action safely", - Detail: "Reverts the last hawk action (commit, file edit, etc.) using safe snapshot-based rollback.", + Detail: "Reverts the last rho action (commit, file edit, etc.) using safe snapshot-based rollback.", Examples: []string{"/undo — undo last action", "/undo --hard — undo and discard changes", "/undo 3 — undo last 3 actions"}, Related: []string{"/commit", "/snapshot", "/history"}, Category: "slash-commands", @@ -102,7 +102,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "/config", Summary: "Open configuration panel", - Detail: "Opens the interactive configuration panel for hawk settings, model selection, and preferences.", + Detail: "Opens the interactive configuration panel for rho settings, model selection, and preferences.", Examples: []string{"/config — open config panel", "/config model — change model", "/config key remove — remove stored API key", "/config keys — show key status"}, Related: []string{"/session", "/profile", "/rules"}, Category: "slash-commands", @@ -165,8 +165,8 @@ func (ch *ContextualHelp) registerAllEntries() { }, { Topic: "/session", - Summary: "Manage hawk sessions", - Detail: "Start, stop, resume, or list hawk working sessions with full context preservation.", + Summary: "Manage rho sessions", + Detail: "Start, stop, resume, or list rho working sessions with full context preservation.", Examples: []string{"/session — show current session", "/session new — start new session", "/session resume 3 — resume session #3"}, Related: []string{"/status", "/history", "/config"}, Category: "slash-commands", @@ -230,7 +230,7 @@ func (ch *ContextualHelp) registerAllEntries() { // ─── Common Tasks ──────────────────────────────────────────── { Topic: "how to fix tests", - Summary: "Fixing failing tests with hawk", + Summary: "Fixing failing tests with rho", Detail: "Use /test to run tests and identify failures, then /fix test to apply AI-generated fixes. For complex failures, use /chat to discuss the issue.", Examples: []string{"/test — identify failures", "/fix test — auto-fix test failures", "/chat \"why is TestX failing?\""}, Related: []string{"/test", "/fix", "/bugfind"}, @@ -238,7 +238,7 @@ func (ch *ContextualHelp) registerAllEntries() { }, { Topic: "how to commit", - Summary: "Making commits with hawk", + Summary: "Making commits with rho", Detail: "Stage your changes with git add, then use /commit to create a commit with an AI-generated message. Use /diff first to review what you are committing.", Examples: []string{"/diff --staged — review staged changes", "/commit — commit with AI message", `/commit "my message" — commit with custom message`}, Related: []string{"/commit", "/diff", "/branch"}, @@ -246,7 +246,7 @@ func (ch *ContextualHelp) registerAllEntries() { }, { Topic: "how to debug", - Summary: "Debugging with hawk AI assistance", + Summary: "Debugging with rho AI assistance", Detail: "Describe the bug in /chat or use /bugfind for automated detection. For test failures, /test with /fix provides targeted debugging.", Examples: []string{"/bugfind — automated bug detection", "/chat \"I see panic at line 42\"", "/test -v ./pkg/... — verbose test output"}, Related: []string{"/bugfind", "/test", "/fix", "/chat"}, @@ -263,16 +263,16 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "how to refactor", Summary: "Refactoring code with AI assistance", - Detail: "Use /chat to discuss refactoring strategies, then hawk can apply changes. Use /snapshot before large refactors for safety.", + Detail: "Use /chat to discuss refactoring strategies, then rho can apply changes. Use /snapshot before large refactors for safety.", Examples: []string{"/snapshot — save state before refactor", "/chat \"refactor auth to use interfaces\"", "/test — verify nothing broke"}, Related: []string{"/chat", "/snapshot", "/test", "/undo"}, Category: "common-tasks", }, { Topic: "how to start a session", - Summary: "Starting a new hawk session", - Detail: "Run hawk in your project directory to start a session. Your context is preserved across the session. Use /session to manage sessions.", - Examples: []string{"hawk — start hawk in current dir", "/session new — start fresh session", "/session resume — resume last session"}, + Summary: "Starting a new rho session", + Detail: "Run rho in your project directory to start a session. Your context is preserved across the session. Use /session to manage sessions.", + Examples: []string{"rho — start rho in current dir", "/session new — start fresh session", "/session resume — resume last session"}, Related: []string{"/session", "/config", "/status"}, Category: "common-tasks", }, @@ -281,14 +281,14 @@ func (ch *ContextualHelp) registerAllEntries() { Topic: "error: api key invalid", Summary: "API key is missing or invalid", Detail: "Your API key is not configured or has expired. Save a new key via /config (paste in the panel). Keys are stored in the OS secret store (macOS Keychain / Linux keyring).", - Examples: []string{"/config — paste API key in the config panel", "hawk credentials status — verify stored keys"}, + Examples: []string{"/config — paste API key in the config panel", "rho credentials status — verify stored keys"}, Related: []string{"/config", "error: rate limit", "error: network"}, Category: "errors", }, { Topic: "error: rate limit", Summary: "API rate limit exceeded", - Detail: "You have exceeded the API rate limit. Hawk will automatically retry with exponential backoff. Consider upgrading your plan for higher limits.", + Detail: "You have exceeded the API rate limit. Rho will automatically retry with exponential backoff. Consider upgrading your plan for higher limits.", Examples: []string{"/config model — switch to a lower-tier model", "/status — check rate limit status"}, Related: []string{"error: api key invalid", "/config", "error: timeout"}, Category: "errors", @@ -312,7 +312,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "error: git conflict", Summary: "Git merge conflict detected", - Detail: "A merge conflict was encountered. Hawk can assist with conflict resolution using AI to understand both sides.", + Detail: "A merge conflict was encountered. Rho can assist with conflict resolution using AI to understand both sides.", Examples: []string{"/merge --resolve — AI-assisted resolution", "/diff --conflicts — show conflict details", "/undo — abort and go back"}, Related: []string{"/merge", "/diff", "/undo"}, Category: "errors", @@ -320,7 +320,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "error: no git repo", Summary: "Not in a git repository", - Detail: "Hawk requires a git repository. Initialize one with git init or navigate to an existing repo.", + Detail: "Rho requires a git repository. Initialize one with git init or navigate to an existing repo.", Examples: []string{"git init — initialize new repo", "cd /path/to/repo — navigate to a repo"}, Related: []string{"/status", "/branch", "/commit"}, Category: "errors", @@ -354,7 +354,7 @@ func (ch *ContextualHelp) registerAllEntries() { Topic: "config: api-key", Summary: "Set the API key", Detail: "API keys are stored in the OS secret store. Use /config to paste a key, or /config key remove to delete one.", - Examples: []string{"/config — paste API key in the config panel", "/config key remove — remove a stored key", "hawk credentials status — list configured providers"}, + Examples: []string{"/config — paste API key in the config panel", "/config key remove — remove a stored key", "rho credentials status — list configured providers"}, Related: []string{"/config", "config: model", "error: api key invalid"}, Category: "configuration", }, @@ -377,7 +377,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "config: editor", Summary: "Set preferred editor", - Detail: "Configure which editor hawk uses when opening files for editing.", + Detail: "Configure which editor rho uses when opening files for editing.", Examples: []string{"/config editor vim — use vim", "/config editor code — use VS Code", "/config editor nano — use nano"}, Related: []string{"/config", "config: theme", "config: shell"}, Category: "configuration", @@ -385,7 +385,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "config: theme", Summary: "Set color theme", - Detail: "Choose the color theme for hawk terminal output. Supports light and dark terminal backgrounds.", + Detail: "Choose the color theme for rho terminal output. Supports light and dark terminal backgrounds.", Examples: []string{"/config theme dark — dark background theme", "/config theme light — light background theme", "/config theme none — disable colors"}, Related: []string{"/config", "config: editor", "config: shell"}, Category: "configuration", @@ -393,7 +393,7 @@ func (ch *ContextualHelp) registerAllEntries() { { Topic: "config: shell", Summary: "Set shell for command execution", - Detail: "Configure which shell hawk uses when running commands.", + Detail: "Configure which shell rho uses when running commands.", Examples: []string{"/config shell bash — use bash", "/config shell zsh — use zsh", "/config shell fish — use fish"}, Related: []string{"/config", "config: editor", "config: theme"}, Category: "configuration", @@ -467,7 +467,7 @@ func (ch *ContextualHelp) registerAllEntries() { Topic: "tool: daemon", Summary: "Background daemon service", Detail: "Runs in the background to provide file watching, indexing, and real-time analysis of your project.", - Examples: []string{"hawk daemon start — start the daemon", "hawk daemon status — check daemon status", "hawk daemon stop — stop the daemon"}, + Examples: []string{"rho daemon start — start the daemon", "rho daemon status — check daemon status", "rho daemon stop — stop the daemon"}, Related: []string{"/status", "tool: repomap", "tool: memory"}, Category: "tools", }, diff --git a/cmd/control_plane_hints.go b/cmd/control_plane_hints.go index c379a1c9c..443466101 100644 --- a/cmd/control_plane_hints.go +++ b/cmd/control_plane_hints.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // controlPlaneOnboardingHint is a short first-session tip (not a wall of text). diff --git a/cmd/cost.go b/cmd/cost.go index 3469c8985..007bfdea3 100644 --- a/cmd/cost.go +++ b/cmd/cost.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - analytics "github.com/GrayCodeAI/hawk/internal/observability" + analytics "github.com/GrayCodeAI/rho/internal/observability" "github.com/spf13/cobra" ) @@ -64,7 +64,7 @@ var costAnalyzeCmd = &cobra.Command{ cmd.Println(auditTint(" - Model routing recommendations", textMuted)) cmd.Println(auditTint(" - Prompt caching suggestions", textMuted)) cmd.Println() - cmd.Println(auditTint("To track progress: https://github.com/GrayCodeAI/hawk/issues", textMuted)) + cmd.Println(auditTint("To track progress: https://github.com/GrayCodeAI/rho/issues", textMuted)) return nil } diff --git a/cmd/cost_test.go b/cmd/cost_test.go index bad0476d2..d7ec4b3e7 100644 --- a/cmd/cost_test.go +++ b/cmd/cost_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - analytics "github.com/GrayCodeAI/hawk/internal/observability" + analytics "github.com/GrayCodeAI/rho/internal/observability" ) func TestCostAnalyze_JSON_Empty(t *testing.T) { diff --git a/cmd/credential_gate.go b/cmd/credential_gate.go index 0554975b4..169de452e 100644 --- a/cmd/credential_gate.go +++ b/cmd/credential_gate.go @@ -3,7 +3,7 @@ package cmd import ( "sync/atomic" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/tool" ) // credentialGate holds the current host-side credential gate callback. It is diff --git a/cmd/credentials.go b/cmd/credentials.go index 80e560019..5a34098b0 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" "github.com/spf13/cobra" ) @@ -19,7 +19,7 @@ var credentialsStatusCmd = &cobra.Command{ Short: "Show where API keys are stored", RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - cmd.Println(hawkconfig.FormatCredentialCLIStatus(ctx)) + cmd.Println(rhoconfig.FormatCredentialCLIStatus(ctx)) return nil }, } @@ -30,7 +30,7 @@ var credentialsRemoveCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - ok, err := confirmDestructive(fmt.Sprintf("Remove stored API key(s) for %q from %s?", args[0], hawkconfig.CredentialStoreName())) + ok, err := confirmDestructive(fmt.Sprintf("Remove stored API key(s) for %q from %s?", args[0], rhoconfig.CredentialStoreName())) if err != nil { return err } @@ -38,11 +38,11 @@ var credentialsRemoveCmd = &cobra.Command{ cmd.Printf("%s\n", auditTint("Cancelled.", textMuted)) return nil } - removed, err := hawkconfig.RemoveStoredCredential(ctx, args[0]) + removed, err := rhoconfig.RemoveStoredCredential(ctx, args[0]) if err != nil { return err } - cmd.Printf("%s\n", auditTint(fmt.Sprintf("Removed %d key(s) from %s: %s", len(removed), hawkconfig.CredentialStoreName(), strings.Join(removed, ", ")), doneGreen)) + cmd.Printf("%s\n", auditTint(fmt.Sprintf("Removed %d key(s) from %s: %s", len(removed), rhoconfig.CredentialStoreName(), strings.Join(removed, ", ")), doneGreen)) return nil }, } diff --git a/cmd/daemon.go b/cmd/daemon.go index b224c0568..7daab7d39 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -14,18 +14,18 @@ import ( "syscall" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/daemon" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - "github.com/GrayCodeAI/hawk/internal/fsutil" - "github.com/GrayCodeAI/hawk/internal/multiagent/agents" - "github.com/GrayCodeAI/hawk/internal/netutil" - "github.com/GrayCodeAI/hawk/internal/observability/logger" - "github.com/GrayCodeAI/hawk/internal/observability/otellog" - "github.com/GrayCodeAI/hawk/internal/observability/oteltrace" - "github.com/GrayCodeAI/hawk/internal/securitylog" - "github.com/GrayCodeAI/hawk/internal/storage" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/daemon" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/fsutil" + "github.com/GrayCodeAI/rho/internal/multiagent/agents" + "github.com/GrayCodeAI/rho/internal/netutil" + "github.com/GrayCodeAI/rho/internal/observability/logger" + "github.com/GrayCodeAI/rho/internal/observability/otellog" + "github.com/GrayCodeAI/rho/internal/observability/oteltrace" + "github.com/GrayCodeAI/rho/internal/securitylog" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) @@ -43,8 +43,8 @@ var ( var daemonCmd = &cobra.Command{ Use: "daemon", - Short: "Manage the hawk background server", - Long: "Run hawk as a background HTTP server for programmatic/CI access.", + Short: "Manage the rho background server", + Long: "Run rho as a background HTTP server for programmatic/CI access.", } var daemonStartCmd = &cobra.Command{ @@ -68,7 +68,7 @@ var daemonStatusCmd = &cobra.Command{ func init() { daemonStartCmd.Flags().IntVarP(&daemonPort, "port", "p", 4590, "Port to listen on") daemonStartCmd.Flags().StringVar(&daemonHost, "host", netutil.LoopbackHost, "Host to bind to (default: 127.0.0.1, use 0.0.0.0 for remote access)") - daemonStartCmd.Flags().StringVar(&daemonAPIKey, "api-key", "", "API key for protected daemon endpoints (defaults to HAWK_DAEMON_API_KEY or a generated key)") + daemonStartCmd.Flags().StringVar(&daemonAPIKey, "api-key", "", "API key for protected daemon endpoints (defaults to RHO_DAEMON_API_KEY or a generated key)") daemonStartCmd.Flags().StringVar(&daemonLogLevel, "log-level", "INFO", "Log level for daemon output (DEBUG, INFO, WARN, ERROR)") daemonStartCmd.Flags().StringSliceVar(&daemonCORSOrigins, "cors", []string{}, "Comma-separated list of allowed CORS origins (empty disables CORS, '*' allows all)") daemonStartCmd.Flags().StringVar(&daemonTLSCertFile, "tls-cert", "", "Path to TLS certificate file (enables HTTPS when paired with --tls-key)") @@ -81,9 +81,9 @@ func init() { } func runDaemonStart(_ *cobra.Command, _ []string) error { - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() - // Initialize OpenTelemetry telemetry (opt-in via HAWK_ENABLE_TELEMETRY=1). + // Initialize OpenTelemetry telemetry (opt-in via RHO_ENABLE_TELEMETRY=1). telemetryProviders, telemetryErr := oteltrace.InitTelemetry(oteltrace.DefaultTelemetryConfig()) if telemetryErr != nil { fmt.Fprintf(os.Stderr, "%s\n", auditTint(fmt.Sprintf("warning: telemetry initialization failed: %v", telemetryErr), warnAmber)) @@ -111,7 +111,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } // Set up file-backed logging for the daemon. Logs go to - // ~/.hawk/state/daemon.log with slog structured output. + // ~/.rho/state/daemon.log with slog structured output. logFile, logErr := openDaemonLogFile() var daemonLogger *logger.Logger if logErr != nil { @@ -128,10 +128,10 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } // Replace the discarded logger with the real file-backed logger. - newSession := newConfiguredHawkSessionFactory(settings, daemonLogger) + newSession := newConfiguredRhoSessionFactory(settings, daemonLogger) // Log startup banner. - daemonLogger.Info("hawk daemon starting", map[string]interface{}{ + daemonLogger.Info("rho daemon starting", map[string]interface{}{ "host": daemonHost, "port": daemonPort, "telemetry_enabled": telemetryProviders != nil && telemetryProviders.IsEnabled(), @@ -139,7 +139,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { apiKey := daemonAPIKey if apiKey == "" { - apiKey = os.Getenv("HAWK_DAEMON_API_KEY") + apiKey = os.Getenv("RHO_DAEMON_API_KEY") } if apiKey == "" { var err error @@ -210,7 +210,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { }) // Wire Eyrie's authoritative local preflight into GET /v1/ready. A session - // factory only proves Hawk can attempt construction; readiness additionally + // factory only proves Rho can attempt construction; readiness additionally // requires Eyrie's provider state, catalog, credentials, and model selection. srv.SetReadyFn(daemonReadyProbe(factory)) addr, err := srv.Start() @@ -227,19 +227,19 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { }) defer preheater.Stop() - fmt.Printf("hawk daemon running on http://%s\n", addr) + fmt.Printf("rho daemon running on http://%s\n", addr) fmt.Println("Endpoints: GET /v1/health, GET /v1/ready, POST /v1/chat, GET /v1/sessions, GET /v1/metrics") fmt.Println("Protected endpoints require Authorization: Bearer or X-API-Key.") if len(apiKey) > 8 { fmt.Printf("API key: %s...%s\n", apiKey[:4], apiKey[len(apiKey)-4:]) } else { - fmt.Println("API key: (set via --api-key or HAWK_DAEMON_API_KEY)") + fmt.Println("API key: (set via --api-key or RHO_DAEMON_API_KEY)") } fmt.Printf("Logs: %s\n", filepath.Join(storage.DaemonRunDir(), "daemon.log")) if telemetryProviders != nil && telemetryProviders.IsEnabled() { fmt.Println("Telemetry: enabled (OTLP export configured)") } else { - fmt.Println("Telemetry: disabled (set HAWK_ENABLE_TELEMETRY=1 to enable)") + fmt.Println("Telemetry: disabled (set RHO_ENABLE_TELEMETRY=1 to enable)") } keyFile := filepath.Join(storage.DaemonRunDir(), "daemon.key") _ = os.MkdirAll(filepath.Dir(keyFile), 0o700) @@ -283,7 +283,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } // openDaemonLogFile opens (or creates) the daemon log file at -// ~/.hawk/state/daemon.log and returns it. The directory is created if needed. +// ~/.rho/state/daemon.log and returns it. The directory is created if needed. func openDaemonLogFile() (*os.File, error) { dir := storage.DaemonRunDir() if err := os.MkdirAll(dir, 0o750); err != nil { // #nosec G301 -- daemon run dir needs group traversal @@ -337,10 +337,10 @@ func slogLevelFromString(s string) slog.Level { // This never performs a paid/live model call — Eyrie preflight only inspects // local catalog/credential/model state — so it is safe to call on every probe. func daemonReadyProbe(factory daemon.SessionFactory) func() (bool, string) { - return daemonReadyProbeWithPreflight(factory, hawkconfig.EnginePreflightReport) + return daemonReadyProbeWithPreflight(factory, rhoconfig.EnginePreflightReport) } -func daemonReadyProbeWithPreflight(factory daemon.SessionFactory, preflight func(context.Context) hawkconfig.EnginePreflight) func() (bool, string) { +func daemonReadyProbeWithPreflight(factory daemon.SessionFactory, preflight func(context.Context) rhoconfig.EnginePreflight) func() (bool, string) { return func() (bool, string) { if factory == nil { return false, "engine not configured" @@ -397,14 +397,14 @@ func generateDaemonAPIKey() (string, error) { return base64.RawURLEncoding.EncodeToString(b[:]), nil } -// daemonAutonomyFromFlag resolves the --autonomy flag / HAWK_DAEMON_AUTONOMY +// daemonAutonomyFromFlag resolves the --autonomy flag / RHO_DAEMON_AUTONOMY // env var into the server-side autonomy cap. An empty value leaves the // default cap (AutonomySemi) in place; invalid values fail closed at // "supervised" rather than silently allowing full autonomy. func daemonAutonomyFromFlag(s string) engine.AutonomyLevel { s = strings.TrimSpace(s) if s == "" { - s = os.Getenv("HAWK_DAEMON_AUTONOMY") + s = os.Getenv("RHO_DAEMON_AUTONOMY") } if s == "" { return 0 // zero => DefaultMaxAutonomy in the daemon diff --git a/cmd/daemon_ready_test.go b/cmd/daemon_ready_test.go index d67bc3ee2..82581e854 100644 --- a/cmd/daemon_ready_test.go +++ b/cmd/daemon_ready_test.go @@ -12,10 +12,10 @@ import ( "testing" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/daemon" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/storage" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/daemon" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/storage" ) // TestDaemonReadyProbe_NilFactory verifies the probe reports not-ready when no @@ -34,8 +34,8 @@ func TestDaemonReadyProbe_NilFactory(t *testing.T) { // make the daemon ready when Eyrie's authoritative preflight is incomplete. func TestDaemonReadyProbe_FailedEyriePreflight(t *testing.T) { factory := func(daemon.ChatRequest) (*engine.Session, error) { return nil, nil } - probe := daemonReadyProbeWithPreflight(factory, func(context.Context) hawkconfig.EnginePreflight { - return hawkconfig.EnginePreflight{Ready: false} + probe := daemonReadyProbeWithPreflight(factory, func(context.Context) rhoconfig.EnginePreflight { + return rhoconfig.EnginePreflight{Ready: false} }) ok, reason := probe() if ok { @@ -48,8 +48,8 @@ func TestDaemonReadyProbe_FailedEyriePreflight(t *testing.T) { func TestDaemonReadyProbe_ReadyEyriePreflight(t *testing.T) { factory := func(daemon.ChatRequest) (*engine.Session, error) { return nil, nil } - probe := daemonReadyProbeWithPreflight(factory, func(context.Context) hawkconfig.EnginePreflight { - return hawkconfig.EnginePreflight{Ready: true} + probe := daemonReadyProbeWithPreflight(factory, func(context.Context) rhoconfig.EnginePreflight { + return rhoconfig.EnginePreflight{Ready: true} }) ok, reason := probe() if !ok || reason != "" { @@ -62,8 +62,8 @@ func TestDaemonReadyProbe_ReadyEyriePreflight(t *testing.T) { func TestDaemonReadyProbe_AffectsReadyEndpoint(t *testing.T) { factory := func(daemon.ChatRequest) (*engine.Session, error) { return nil, nil } srv := daemon.New(daemon.Config{Port: 0, Host: "127.0.0.1"}, factory) - srv.SetReadyFn(daemonReadyProbeWithPreflight(factory, func(context.Context) hawkconfig.EnginePreflight { - return hawkconfig.EnginePreflight{Ready: false} + srv.SetReadyFn(daemonReadyProbeWithPreflight(factory, func(context.Context) rhoconfig.EnginePreflight { + return rhoconfig.EnginePreflight{Ready: false} })) addr, err := srv.Start() diff --git a/cmd/diagnostics.go b/cmd/diagnostics.go index 90558dcf8..a2a3b9a3c 100644 --- a/cmd/diagnostics.go +++ b/cmd/diagnostics.go @@ -12,16 +12,16 @@ import ( "strings" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/resilience/health" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/stt" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/resilience/health" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/stt" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) -func doctorReport(settings hawkconfig.Settings) string { +func doctorReport(settings rhoconfig.Settings) string { // Diagnostics must report the requested/effective selection even when its // credential is missing; readiness and health sections explain why it is // not yet usable. Hiding it as auto/default makes misconfiguration harder @@ -37,7 +37,7 @@ func doctorReport(settings hawkconfig.Settings) string { cwd, _ := os.Getwd() var b strings.Builder - b.WriteString("Hawk doctor\n") + b.WriteString("Rho doctor\n") b.WriteString(fmt.Sprintf("Version: %s\n", version)) b.WriteString(fmt.Sprintf("Go version: %s\n", runtime.Version())) b.WriteString(fmt.Sprintf("Directory: %s\n", cwd)) @@ -64,16 +64,16 @@ func doctorReport(settings hawkconfig.Settings) string { b.WriteString(fmt.Sprintf(" %s (%s): not checked out\n", component.product, component.directory)) } } - b.WriteString("\n" + hawkconfig.FormatEcosystemPanel(context.Background(), providerName, modelName) + "\n") - b.WriteString("\n" + hawkconfig.FormatCatalogHealth(hawkconfig.CatalogHealthReport(context.Background())) + "\n") - preflight := hawkconfig.EnginePreflightReportWithSettings(context.Background(), settings, hawkconfig.EnginePreflightOptions{}) - b.WriteString("\n" + hawkconfig.FormatEnginePreflight(preflight) + "\n") + b.WriteString("\n" + rhoconfig.FormatEcosystemPanel(context.Background(), providerName, modelName) + "\n") + b.WriteString("\n" + rhoconfig.FormatCatalogHealth(rhoconfig.CatalogHealthReport(context.Background())) + "\n") + preflight := rhoconfig.EnginePreflightReportWithSettings(context.Background(), settings, rhoconfig.EnginePreflightOptions{}) + b.WriteString("\n" + rhoconfig.FormatEnginePreflight(preflight) + "\n") b.WriteString("\nBackends (Gap-05):\n") b.WriteString(fmt.Sprintf(" media: %s\n", backendStatus(tool.MediaEngineName(), tool.MediaEngineName() != ""))) b.WriteString(fmt.Sprintf(" stt: %s\n", backendStatus("", stt.Enabled()))) b.WriteString(fmt.Sprintf(" computer: %s\n", backendStatus(tool.ComputerBackendName(), tool.ComputerBackendName() != ""))) - b.WriteString("\n" + hawkconfig.CredentialStorageStatus(context.Background()).Formatted + "\n") - if deployReport, err := hawkconfig.DeploymentStatusReportWithSettings(context.Background(), settings, modelName); err == nil { + b.WriteString("\n" + rhoconfig.CredentialStorageStatus(context.Background()).Formatted + "\n") + if deployReport, err := rhoconfig.DeploymentStatusReportWithSettings(context.Background(), settings, modelName); err == nil { b.WriteString("\n" + deployReport + "\n") } b.WriteString("\n" + envSummaryWithSelection(providerName, modelName, false) + "\n") @@ -85,18 +85,18 @@ func doctorReport(settings hawkconfig.Settings) string { } // Project instructions - if md := hawkconfig.LoadAgentsMD(); md != "" { + if md := rhoconfig.LoadAgentsMD(); md != "" { b.WriteString("\nProject instructions: found\n") } else { b.WriteString("\nProject instructions: not found (consider creating AGENTS.md)\n") } - // Installed skills (hawk ships none; skills come from user/marketplace installs) + // Installed skills (rho ships none; skills come from user/marketplace installs) skillsDir := filepath.Join(storage.StateDir(), "skills") if entries, err := os.ReadDir(skillsDir); err == nil && len(entries) > 0 { b.WriteString(fmt.Sprintf("Installed skills: %d\n", len(entries))) } else { - b.WriteString("Installed skills: none (install with `hawk skills install`)\n") + b.WriteString("Installed skills: none (install with `rho skills install`)\n") } b.WriteString(fmt.Sprintf("Configured MCP servers: %d\n", len(settings.MCPServers)+len(mcpServers))) @@ -105,7 +105,7 @@ func doctorReport(settings hawkconfig.Settings) string { // Session recovery status recoveryCandidates := session.ScanForRecovery() if len(recoveryCandidates) > 0 { - b.WriteString(fmt.Sprintf("\nInterrupted sessions: %d (run hawk recover)\n", len(recoveryCandidates))) + b.WriteString(fmt.Sprintf("\nInterrupted sessions: %d (run rho recover)\n", len(recoveryCandidates))) } else { b.WriteString("\nInterrupted sessions: none\n") } @@ -127,14 +127,14 @@ func backendStatus(name string, enabled bool) string { return "wired (" + name + ")" } -func healthCheckReport(settings hawkconfig.Settings, provider string) string { +func healthCheckReport(settings rhoconfig.Settings, provider string) string { registry := health.NewRegistry() registry.Register("api_key", providerCredentialHealthChecker(provider)) // Settings validation registry.Register("config", func(ctx context.Context) health.Check { - result := hawkconfig.ValidateSettings(settings) + result := rhoconfig.ValidateSettings(settings) if result.Valid { return health.Check{Name: "config", Status: health.Healthy, Message: "Configuration valid"} } @@ -196,7 +196,7 @@ func providerCredentialHealthChecker(provider string) health.Checker { status := health.Unhealthy message := label + " credential not configured" - if providerID != "" && hawkconfig.HasStoredCredentialForProvider(ctx, providerID) { + if providerID != "" && rhoconfig.HasStoredCredentialForProvider(ctx, providerID) { status = health.Healthy message = label + " credential configured" } @@ -214,19 +214,19 @@ func providerCredentialHealthChecker(provider string) health.Checker { func diagnosticsProvider(ctx context.Context, provider string) string { provider = strings.TrimSpace(provider) if provider == "" || strings.EqualFold(provider, "auto") { - provider = strings.TrimSpace(hawkconfig.ActiveGateway(ctx)) + provider = strings.TrimSpace(rhoconfig.ActiveGateway(ctx)) if provider == "" || strings.EqualFold(provider, "auto") { - provider = strings.TrimSpace(hawkconfig.EffectiveSelection(ctx, hawkconfig.SelectionOptions{}).Provider) + provider = strings.TrimSpace(rhoconfig.EffectiveSelection(ctx, rhoconfig.SelectionOptions{}).Provider) } } - return hawkconfig.ActiveProviderID(provider) + return rhoconfig.ActiveProviderID(provider) } -func settingsSummary(settings hawkconfig.Settings) string { +func settingsSummary(settings rhoconfig.Settings) string { return configCommandSummary(settings) } -func mcpConfigSummary(settings hawkconfig.Settings) string { +func mcpConfigSummary(settings rhoconfig.Settings) string { if len(settings.MCPServers) == 0 && len(mcpServers) == 0 { return auditTint("No MCP servers configured.", textMuted) } diff --git a/cmd/diagnostics_test.go b/cmd/diagnostics_test.go index 6863ee27d..49b026fb2 100644 --- a/cmd/diagnostics_test.go +++ b/cmd/diagnostics_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/resilience/health" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/resilience/health" ) type diagnosticsContextKey struct{} @@ -25,7 +25,7 @@ func (s *contextRecordingCredentialStore) Get(ctx context.Context, account strin func TestDoctorReport(t *testing.T) { t.Parallel() - settings := hawkconfig.Settings{} + settings := rhoconfig.Settings{} report := doctorReport(settings) if report == "" { t.Error("doctorReport should produce non-empty output") @@ -40,7 +40,7 @@ func TestDoctorReport(t *testing.T) { func TestDoctorReportProviderModelOrder(t *testing.T) { t.Parallel() - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ Model: "claude-sonnet-4-20250514", Provider: "anthropic", } @@ -56,14 +56,14 @@ func TestDoctorReportProviderModelOrder(t *testing.T) { func TestDoctorReportUsesResolvedProviderForChecks(t *testing.T) { isolateCredentialHome(t) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() gateway.SetDefaultStore(&gateway.MapStore{}) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) - report := doctorReport(hawkconfig.Settings{ + report := doctorReport(rhoconfig.Settings{ Model: "claude-sonnet-4-20250514", Provider: "anthropic", }) @@ -75,22 +75,22 @@ func TestDoctorReportUsesResolvedProviderForChecks(t *testing.T) { func TestProviderCredentialHealthCheckerResolvesAuto(t *testing.T) { isolateCredentialHome(t) t.Setenv("EYRIE_CONFIG_DIR", t.TempDir()) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &contextRecordingCredentialStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.WithValue(context.Background(), diagnosticsContextKey{}, "checker-context") if err := store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatal(err) } - if err := hawkconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + if err := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := hawkconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } store.contextValue = nil @@ -118,11 +118,11 @@ func TestProviderCredentialHealthCheckerResolvesAuto(t *testing.T) { func TestProviderCredentialHealthCheckerMissingIsUnhealthy(t *testing.T) { isolateCredentialHome(t) t.Setenv("EYRIE_CONFIG_DIR", t.TempDir()) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() gateway.SetDefaultStore(&gateway.MapStore{}) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) result := providerCredentialHealthChecker("openai")(context.Background()) @@ -142,7 +142,7 @@ func TestProviderCredentialHealthCheckerMissingIsUnhealthy(t *testing.T) { func TestSettingsSummary(t *testing.T) { t.Parallel() - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ Model: "claude-sonnet-4-20250514", Provider: "anthropic", } @@ -154,7 +154,7 @@ func TestSettingsSummary(t *testing.T) { func TestMcpConfigSummary(t *testing.T) { t.Parallel() - settings := hawkconfig.Settings{} + settings := rhoconfig.Settings{} summary := mcpConfigSummary(settings) if summary == "" { t.Error("mcpConfigSummary should produce output") diff --git a/cmd/dx.go b/cmd/dx.go index 18dde767b..7222c50ae 100644 --- a/cmd/dx.go +++ b/cmd/dx.go @@ -10,19 +10,19 @@ import ( "strings" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/storage" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/storage" ) // startTime records when the process started, used by debugOutput for uptime. var startTime = time.Now() // doctorOutput returns a comprehensive system diagnostics string. -func doctorOutput(settings hawkconfig.Settings) string { +func doctorOutput(settings rhoconfig.Settings) string { var b strings.Builder - b.WriteString("=== Hawk Doctor ===\n\n") + b.WriteString("=== Rho Doctor ===\n\n") // Go version, OS, arch b.WriteString("System:\n") @@ -49,12 +49,12 @@ func doctorOutput(settings hawkconfig.Settings) string { b.WriteString(fmt.Sprintf(" TERM: %s\n", termVal)) b.WriteString(fmt.Sprintf(" COLORTERM: %s\n", colorTerm)) - // Hawk version + // Rho version v := version if v == "" { v = "(dev)" } - b.WriteString("\nHawk:\n") + b.WriteString("\nRho:\n") b.WriteString(fmt.Sprintf(" Version: %s\n", v)) if buildDate != "" && buildDate != "unknown" { b.WriteString(fmt.Sprintf(" Build date: %s\n", buildDate)) @@ -67,10 +67,10 @@ func doctorOutput(settings hawkconfig.Settings) string { } b.WriteString("\nProvider:\n") b.WriteString(fmt.Sprintf(" Provider: %s\n", effectiveProvider)) - b.WriteString(fmt.Sprintf(" API key: %s\n", maskedKeyStatus(hawkconfig.ActiveProvider(context.Background())))) + b.WriteString(fmt.Sprintf(" API key: %s\n", maskedKeyStatus(rhoconfig.ActiveProvider(context.Background())))) // Model configured (eyrie provider.json) - effectiveModel := strings.TrimSpace(hawkconfig.ActiveModel(context.Background())) + effectiveModel := strings.TrimSpace(rhoconfig.ActiveModel(context.Background())) if effectiveModel == "" { effectiveModel = "(not configured)" } @@ -111,7 +111,7 @@ func doctorOutput(settings hawkconfig.Settings) string { } // AGENTS.md found - agentsMD := hawkconfig.LoadAgentsMD() + agentsMD := rhoconfig.LoadAgentsMD() if agentsMD != "" { b.WriteString("AGENTS.md: found\n") } else { @@ -146,7 +146,7 @@ func doctorOutput(settings hawkconfig.Settings) string { // doctorJSON returns the doctor diagnostics as indented JSON, mirroring the // fields of doctorOutput but as machine-parseable structured data. -func doctorJSON(settings hawkconfig.Settings) string { +func doctorJSON(settings rhoconfig.Settings) string { type sessionDirInfo struct { Path string `json:"path"` Status string `json:"status"` @@ -165,7 +165,7 @@ func doctorJSON(settings hawkconfig.Settings) string { if effectiveProvider == "" { effectiveProvider = "(not configured)" } - effectiveModel := strings.TrimSpace(hawkconfig.ActiveModel(context.Background())) + effectiveModel := strings.TrimSpace(rhoconfig.ActiveModel(context.Background())) if effectiveModel == "" { effectiveModel = "(not configured)" } @@ -219,7 +219,7 @@ func doctorJSON(settings hawkconfig.Settings) string { plugins = len(manifests) } - agentsMD := hawkconfig.LoadAgentsMD() + agentsMD := rhoconfig.LoadAgentsMD() agentsState := "not found" if agentsMD != "" { agentsState = "found" @@ -274,7 +274,7 @@ func doctorJSON(settings hawkconfig.Settings) string { Version: v, BuildDate: buildDate, Provider: effectiveProvider, - APIKey: maskedKeyStatus(hawkconfig.ActiveProvider(context.Background())), + APIKey: maskedKeyStatus(rhoconfig.ActiveProvider(context.Background())), Model: effectiveModel, SessionDir: sessDir, MCPServers: mcpCount, @@ -293,7 +293,7 @@ func maskedKeyStatus(provider string) string { if provider == "" { return "(no provider set)" } - status := hawkconfig.EnvKeyStatus(provider) + status := rhoconfig.EnvKeyStatus(provider) if status == "set" { return "configured (masked)" } @@ -414,7 +414,7 @@ func countOpenFDs() int { // Returns the file path and any error. func exportMarkdown(messages []displayMsg, sessionID string) (string, error) { var b strings.Builder - b.WriteString(fmt.Sprintf("# Hawk Session: %s\n\n", sessionID)) + b.WriteString(fmt.Sprintf("# Rho Session: %s\n\n", sessionID)) b.WriteString(fmt.Sprintf("Exported: %s\n\n", time.Now().Format(time.RFC3339))) b.WriteString("---\n\n") @@ -443,7 +443,7 @@ func exportMarkdown(messages []displayMsg, sessionID string) (string, error) { } } - filename := fmt.Sprintf("hawk-session-%s.md", sessionID) + filename := fmt.Sprintf("rho-session-%s.md", sessionID) if err := os.WriteFile(filename, []byte(b.String()), 0o600); err != nil { return "", fmt.Errorf("failed to write %s: %w", filename, err) } @@ -490,7 +490,7 @@ func exportJSON(messages []displayMsg, sessionID string) (string, error) { return "", fmt.Errorf("failed to marshal session: %w", err) } - filename := fmt.Sprintf("hawk-session-%s.json", sessionID) + filename := fmt.Sprintf("rho-session-%s.json", sessionID) if err := os.WriteFile(filename, data, 0o600); err != nil { return "", fmt.Errorf("failed to write %s: %w", filename, err) } diff --git a/cmd/dx_test.go b/cmd/dx_test.go index 57bad5a63..e730ccac3 100644 --- a/cmd/dx_test.go +++ b/cmd/dx_test.go @@ -7,15 +7,15 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestDoctorOutputContainsSections(t *testing.T) { preserveCLICompilerVersionState(t) version = "test-dx-version" - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ Provider: "openai", Model: "gpt-4o", } @@ -23,7 +23,7 @@ func TestDoctorOutputContainsSections(t *testing.T) { out := doctorOutput(settings) sections := []string{ - "Hawk Doctor", + "Rho Doctor", "Go version:", "OS:", "Arch:", @@ -50,10 +50,10 @@ func TestDoctorOutputContainsSections(t *testing.T) { func TestDoctorOutputWithMCPServers(t *testing.T) { preserveCLICompilerVersionState(t) version = "test-dx-version" - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ Provider: "anthropic", Model: "claude-sonnet-4-20250514", - MCPServers: []hawkconfig.MCPServerConfig{ + MCPServers: []rhoconfig.MCPServerConfig{ {Name: "test-mcp", Command: "test-cmd"}, }, } @@ -67,10 +67,10 @@ func TestDoctorOutputWithMCPServers(t *testing.T) { func TestDoctorJSONIsValidStructuredOutput(t *testing.T) { preserveCLICompilerVersionState(t) version = "test-dx-version" - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ Provider: "anthropic", Model: "claude-sonnet-4-20250514", - MCPServers: []hawkconfig.MCPServerConfig{ + MCPServers: []rhoconfig.MCPServerConfig{ {Name: "test-mcp", Command: "test-cmd"}, }, } @@ -168,7 +168,7 @@ func TestExportMarkdownCreatesFile(t *testing.T) { defer os.Chdir(origDir) messages := []displayMsg{ - {role: "user", content: "Hello hawk"}, + {role: "user", content: "Hello rho"}, {role: "assistant", content: "Hello! How can I help?"}, {role: "system", content: "System message here"}, {role: "welcome", content: "Should be skipped"}, @@ -189,10 +189,10 @@ func TestExportMarkdownCreatesFile(t *testing.T) { } content := string(data) - if !strings.Contains(content, "# Hawk Session: test-export-id") { + if !strings.Contains(content, "# Rho Session: test-export-id") { t.Errorf("export missing session header") } - if !strings.Contains(content, "Hello hawk") { + if !strings.Contains(content, "Hello rho") { t.Errorf("export missing user message") } if !strings.Contains(content, "Hello! How can I help?") { diff --git a/cmd/ecosystem.go b/cmd/ecosystem.go index 1a9d4ec02..d76604a31 100644 --- a/cmd/ecosystem.go +++ b/cmd/ecosystem.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" "github.com/spf13/cobra" ) @@ -13,7 +13,7 @@ var ecosystemJSON bool var ecosystemCmd = &cobra.Command{ Use: "ecosystem", Short: "Show eyrie and token-pipeline integration status", - Long: "Print the ecosystem panel summarizing the LLM provider runtime (eyrie) and the local token/compression pipeline. Same block as the top of hawk doctor.", + Long: "Print the ecosystem panel summarizing the LLM provider runtime (eyrie) and the local token/compression pipeline. Same block as the top of rho doctor.", RunE: func(cmd *cobra.Command, args []string) error { settings, err := loadEffectiveSettings() if err != nil { @@ -24,12 +24,12 @@ var ecosystemCmd = &cobra.Command{ providerName = "auto" } if ecosystemJSON { - report := hawkconfig.BuildEcosystemReport(context.Background(), providerName, modelName) + report := rhoconfig.BuildEcosystemReport(context.Background(), providerName, modelName) enc := json.NewEncoder(cmd.OutOrStdout()) enc.SetIndent("", " ") return enc.Encode(report) } - cmd.Println(hawkconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)) + cmd.Println(rhoconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)) return nil }, } diff --git a/cmd/ecosystem_test.go b/cmd/ecosystem_test.go index 6aaa48f8d..244e24f74 100644 --- a/cmd/ecosystem_test.go +++ b/cmd/ecosystem_test.go @@ -4,16 +4,16 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestEcosystemCmdRuns(t *testing.T) { - settings := hawkconfig.Settings{} + settings := rhoconfig.Settings{} model, provider := effectiveModelAndProvider(settings) if provider == "" { provider = "auto" } - out := hawkconfig.FormatEcosystemPanel(t.Context(), provider, model) + out := rhoconfig.FormatEcosystemPanel(t.Context(), provider, model) if !strings.Contains(out, "Ecosystem (eyrie · token engine)") { t.Fatalf("unexpected panel: %q", out) } diff --git a/cmd/error_classify.go b/cmd/error_classify.go index e52162476..849b485e1 100644 --- a/cmd/error_classify.go +++ b/cmd/error_classify.go @@ -4,41 +4,41 @@ import ( "fmt" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/errhint" - "github.com/GrayCodeAI/hawk/internal/hawkerr" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/errhint" + "github.com/GrayCodeAI/rho/internal/rhoerr" ) // friendlyErrorMessage returns the user-friendly message for an error. -// Delegates to the shared hawkerr.ClassifyErrorMessage for the base message, +// Delegates to the shared rhoerr.ClassifyErrorMessage for the base message, // then enriches specific cases with dynamic hints that require the config -// package (hawkerr can't import internal/config without a cycle). +// package (rhoerr can't import internal/config without a cycle). func friendlyErrorMessage(err error) string { - msg := hawkerr.ClassifyErrorMessage(err) + msg := rhoerr.ClassifyErrorMessage(err) if err == nil { return msg } - ec := hawkerr.ClassifyError(err) + ec := rhoerr.ClassifyError(err) low := strings.ToLower(err.Error()) switch ec.ExitCode { - case hawkerr.ExitNotFound: + case rhoerr.ExitNotFound: // Enrich model-not-found errors with concrete examples from the catalog. if strings.Contains(low, "model") || strings.Contains(low, "unknown") || strings.Contains(low, "does not exist") { - ex1, ex2 := hawkconfig.ExampleModelHints() + ex1, ex2 := rhoconfig.ExampleModelHints() msg = fmt.Sprintf( "Model not found. Check your model name with /model.\n Examples from the eyrie catalog: %s, %s\n Use /models to list all models, or /config to change provider.", ex1, ex2, ) } - case hawkerr.ExitAuth: + case rhoerr.ExitAuth: msg += "\n Check your API key with /config. Keys can expire or be revoked." - case hawkerr.ExitNetwork: + case rhoerr.ExitNetwork: msg += "\n Check your internet connection. If you're behind a proxy, configure it with /config." - case hawkerr.ExitTimeout: + case rhoerr.ExitTimeout: msg += "\n The request took too long. Try again, or use /model to switch to a faster provider." } diff --git a/cmd/error_classify_test.go b/cmd/error_classify_test.go index 82eca742f..057af69b8 100644 --- a/cmd/error_classify_test.go +++ b/cmd/error_classify_test.go @@ -15,7 +15,7 @@ func TestFriendlyErrorMessageAppendsProviderHint(t *testing.T) { } func TestFriendlyErrorMessageNoHintForLocalError(t *testing.T) { - // A local error must not draw an errhint provider hint (hawk's own + // A local error must not draw an errhint provider hint (rho's own // ExitNotFound enrichment is separate and fine). msg := friendlyErrorMessage(errors.New("file not found: x")) for _, marker := range []string{"API key rejected", "Rate limited", "Can't reach the provider", "Context window full", "Model unavailable"} { diff --git a/cmd/errors.go b/cmd/errors.go index 40138c04a..57f7dc564 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -11,8 +11,8 @@ import ( "sync" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/storage" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/storage" ) // friendlyError translates a raw error into a user-friendly message with an @@ -25,7 +25,7 @@ func friendlyError(err error) string { // ─── panicRecovery ──────────────────────────────────────────────────────────── // Catches panics, saves the current session state, logs the stack trace to -// Hawk's user state crash log, and exits with a user-friendly message. +// Rho's user state crash log, and exits with a user-friendly message. // panicSaveFn is set by runChat to a closure that persists the active session. // panicRecovery invokes it on an unexpected panic so a crash saves as much work @@ -49,7 +49,7 @@ func panicRecovery(saveFn func()) { stack := string(debug.Stack()) // Generate a short, unique error ID for support reference. - // Format: hawk-YYMMDD-<6 hex chars from stack hash> + // Format: rho-YYMMDD-<6 hex chars from stack hash> errorID := generateErrorID(stack) // Attempt to save session @@ -82,14 +82,14 @@ func panicRecovery(saveFn func()) { } // Print user-friendly message - _, _ = fmt.Fprintf(os.Stderr, "\nhawk encountered an unexpected error and needs to exit.\n") + _, _ = fmt.Fprintf(os.Stderr, "\nrho encountered an unexpected error and needs to exit.\n") if saveFn != nil { _, _ = fmt.Fprintf(os.Stderr, "Your session has been saved.\n") } else { _, _ = fmt.Fprintf(os.Stderr, "Session messages are persisted incrementally; the in-flight message may be lost.\n") } _, _ = fmt.Fprintf(os.Stderr, "Details logged to %s\n", filepath.Join(storage.StateDir(), "crash.log")) - _, _ = fmt.Fprintf(os.Stderr, "Please report this at: https://github.com/GrayCodeAI/hawk/issues\n") + _, _ = fmt.Fprintf(os.Stderr, "Please report this at: https://github.com/GrayCodeAI/rho/issues\n") _, _ = fmt.Fprintf(os.Stderr, "Include this error ID: %s\n\n", errorID) _, _ = fmt.Fprintf(os.Stderr, "panic: %v\n", r) os.Exit(1) // os.Exit intentional: panic recovery, defer already unwound @@ -97,7 +97,7 @@ func panicRecovery(saveFn func()) { } // generateErrorID creates a short, unique error ID from the stack trace. -// Format: hawk-YYMMDD-<6 hex chars> — enough to correlate with crash.log +// Format: rho-YYMMDD-<6 hex chars> — enough to correlate with crash.log // entries without requiring a random source. func generateErrorID(stack string) string { // Simple hash of the stack trace for uniqueness. @@ -105,18 +105,18 @@ func generateErrorID(stack string) string { for i := 0; i < len(stack) && i < 2000; i++ { hash = ((hash << 5) + hash) ^ uint32(stack[i]) } - return fmt.Sprintf("hawk-%s-%06x", time.Now().Format("060102"), hash&0xFFFFFF) + return fmt.Sprintf("rho-%s-%06x", time.Now().Format("060102"), hash&0xFFFFFF) } // ─── errorLogger ────────────────────────────────────────────────────────────── -// Writes errors to Hawk's user state error log with timestamps. Thread-safe. +// Writes errors to Rho's user state error log with timestamps. Thread-safe. type errorLoggerT struct { mu sync.Mutex path string } -// LogError writes a timestamped error entry to the Hawk error log. +// LogError writes a timestamped error entry to the Rho error log. func (l *errorLoggerT) LogError(context string, err error) { if l == nil || err == nil { return @@ -140,7 +140,7 @@ func (l *errorLoggerT) LogError(context string, err error) { _, _ = f.WriteString(entry) } -// LogErrorf writes a formatted, timestamped error entry to the Hawk error log. +// LogErrorf writes a formatted, timestamped error entry to the Rho error log. func (l *errorLoggerT) LogErrorf(format string, args ...interface{}) { if l == nil { return @@ -179,24 +179,24 @@ func (w StartupWarning) String() string { return fmt.Sprintf("[%s] %s", w.Check, w.Message) } -func validateStartup(settings hawkconfig.Settings) []StartupWarning { +func validateStartup(settings rhoconfig.Settings) []StartupWarning { var warnings []StartupWarning // 1. Check API key for configured provider. - // Hawk reads credentials from the OS secret store (macOS Keychain / + // Rho reads credentials from the OS secret store (macOS Keychain / // Linux Keyring), not just env vars — so we must check there too. providerName := strings.TrimSpace(settings.Provider) if providerName == "" { - providerName = strings.TrimSpace(hawkconfig.ActiveProvider(context.Background())) + providerName = strings.TrimSpace(rhoconfig.ActiveProvider(context.Background())) } if providerName != "" && providerName != "ollama" { hasEnv := false - if envKey := hawkconfig.ProviderAPIKeyEnv(providerName); envKey != "" { + if envKey := rhoconfig.ProviderAPIKeyEnv(providerName); envKey != "" { hasEnv = os.Getenv(envKey) != "" } - hasStored := hawkconfig.HasStoredCredentialForProvider(context.Background(), providerName) + hasStored := rhoconfig.HasStoredCredentialForProvider(context.Background(), providerName) if !hasEnv && !hasStored { - envKey := hawkconfig.ProviderAPIKeyEnv(providerName) + envKey := rhoconfig.ProviderAPIKeyEnv(providerName) warnings = append(warnings, StartupWarning{ Check: "api_key", Message: fmt.Sprintf("No API key found for %s. Set %s in your environment or run /config.", providerName, envKey), @@ -232,10 +232,10 @@ func validateStartup(settings hawkconfig.Settings) []StartupWarning { // post-first-paint (background), not on the TUI startup critical path where an // offline machine would stall the UI for seconds. Returns a warning message, or // "" when reachable/not applicable. -func checkNetworkReachability(settings hawkconfig.Settings) string { +func checkNetworkReachability(settings rhoconfig.Settings) string { providerName := strings.TrimSpace(settings.Provider) if providerName == "" { - providerName = strings.TrimSpace(hawkconfig.ActiveProvider(context.Background())) + providerName = strings.TrimSpace(rhoconfig.ActiveProvider(context.Background())) } if providerName == "" || providerName == "ollama" { return "" @@ -252,5 +252,5 @@ func checkNetworkReachability(settings hawkconfig.Settings) string { // providerDNSHost returns a hostname to check DNS resolution for a provider. func providerDNSHost(provider string) string { - return hawkconfig.GatewayDNSHost(provider) + return rhoconfig.GatewayDNSHost(provider) } diff --git a/cmd/errors_test.go b/cmd/errors_test.go index 26880194d..ae29dc980 100644 --- a/cmd/errors_test.go +++ b/cmd/errors_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestFriendlyErrorNil(t *testing.T) { @@ -151,7 +151,7 @@ func TestFriendlyErrorInvalidModel(t *testing.T) { if !strings.Contains(got, "/model") { t.Errorf("friendlyError(%q) = %q, should suggest /model", tt.errMsg, got) } - example1, example2 := hawkconfig.ExampleModelHints() + example1, example2 := rhoconfig.ExampleModelHints() if !strings.Contains(got, example1) || !strings.Contains(got, example2) { t.Errorf("friendlyError(%q) = %q, should suggest valid model names", tt.errMsg, got) } @@ -604,8 +604,8 @@ func TestFriendlyErrorBackwardCompat(t *testing.T) { } // helper for tests -func emptySettings() hawkconfig.Settings { - return hawkconfig.Settings{} +func emptySettings() rhoconfig.Settings { + return rhoconfig.Settings{} } // ── signalHandler test ──────────────────────────────────────────────────────── diff --git a/cmd/eval.go b/cmd/eval.go index d41d952fe..237c45793 100644 --- a/cmd/eval.go +++ b/cmd/eval.go @@ -9,10 +9,10 @@ import ( "strings" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/feature/eval" - "github.com/GrayCodeAI/hawk/internal/feature/evalloop" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/feature/eval" + "github.com/GrayCodeAI/rho/internal/feature/evalloop" + "github.com/GrayCodeAI/rho/internal/tool" "github.com/spf13/cobra" ) @@ -98,22 +98,22 @@ func runEvalLoop(cmd *cobra.Command, _ []string) error { if strings.TrimSpace(evalLoopPrompt) == "" { return fmt.Errorf("--prompt is required") } - settings := hawkconfig.LoadGlobalSettings() + settings := rhoconfig.LoadGlobalSettings() ctx := context.Background() - gw, err := hawkconfig.NewEyrieEngineForSettings(settings) + gw, err := rhoconfig.NewEyrieEngineForSettings(settings) if err != nil { return fmt.Errorf("eval loop: build engine client: %w", err) } model := strings.TrimSpace(evalLoopModel) if model == "" { - model = strings.TrimSpace(hawkconfig.ActiveModel(ctx)) + model = strings.TrimSpace(rhoconfig.ActiveModel(ctx)) } if model == "" { model = strings.TrimSpace(settings.Model) } - workDir, err := os.MkdirTemp("", "hawk-eval-loop-*") + workDir, err := os.MkdirTemp("", "rho-eval-loop-*") if err != nil { return fmt.Errorf("eval loop: create temp dir: %w", err) } @@ -225,7 +225,7 @@ func runEval(_ *cobra.Command, _ []string) error { fmt.Printf("%s\n", auditTint(fmt.Sprintf("Running %d tasks with model %s...", len(tasks), modelName), textPrimary)) - suite := &eval.BenchmarkSuite{Name: "hawk-eval", Tasks: tasks} + suite := &eval.BenchmarkSuite{Name: "rho-eval", Tasks: tasks} runner := eval.NewRunner(modelName, "") runner.NoCache = evalNoCache if !evalNoCache { diff --git a/cmd/eval_tools.go b/cmd/eval_tools.go index 37769598e..79024457f 100644 --- a/cmd/eval_tools.go +++ b/cmd/eval_tools.go @@ -6,9 +6,9 @@ import ( "fmt" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/feature/eval" - "github.com/GrayCodeAI/hawk/internal/types" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/feature/eval" + "github.com/GrayCodeAI/rho/internal/types" "github.com/spf13/cobra" ) @@ -29,7 +29,7 @@ var evalToolsCmd = &cobra.Command{ } // defaultToolUseCases is a small built-in set exercising clear positive and -// negative tool-trigger situations against hawk's standard tools. +// negative tool-trigger situations against rho's standard tools. func defaultToolUseCases() []eval.ToolUseCase { return []eval.ToolUseCase{ { @@ -68,7 +68,7 @@ func defaultToolUseCases() []eval.ToolUseCase { } func runEvalTools(cmd *cobra.Command, _ []string) error { - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() registry, err := defaultRegistry(settings) if err != nil { @@ -79,7 +79,7 @@ func runEvalTools(cmd *cobra.Command, _ []string) error { return err } modelName, providerName := effectiveModelAndProvider(settings) - sess, err := newConfiguredHawkSession(settings, providerName, modelName, systemPrompt, registry, nil) + sess, err := newConfiguredRhoSession(settings, providerName, modelName, systemPrompt, registry, nil) if err != nil { return err } diff --git a/cmd/exec.go b/cmd/exec.go index 5fd36de69..604edfd91 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -14,16 +14,16 @@ import ( "time" "unicode/utf8" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/errhint" - "github.com/GrayCodeAI/hawk/internal/multiagent/agents" - "github.com/GrayCodeAI/hawk/internal/notify" - "github.com/GrayCodeAI/hawk/internal/observability/logger" - cloud "github.com/GrayCodeAI/hawk/internal/platform/cloud" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/errhint" + "github.com/GrayCodeAI/rho/internal/multiagent/agents" + "github.com/GrayCodeAI/rho/internal/notify" + "github.com/GrayCodeAI/rho/internal/observability/logger" + cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/spf13/cobra" ) @@ -93,13 +93,13 @@ Autonomy Levels: yolo Never ask for permission Examples: - hawk exec "analyze this codebase" - hawk exec --auto full "fix the tests and commit" - hawk exec --json "what files are in src/" - hawk exec --ephemeral --json "run tests and report" > result.json - echo "explain main.go" | hawk exec - - hawk exec --agent reviewer "review the latest commit" - hawk exec --model claude-sonnet-4-6 "quick fix: typo in README"`, + rho exec "analyze this codebase" + rho exec --auto full "fix the tests and commit" + rho exec --json "what files are in src/" + rho exec --ephemeral --json "run tests and report" > result.json + echo "explain main.go" | rho exec - + rho exec --agent reviewer "review the latest commit" + rho exec --model claude-sonnet-4-6 "quick fix: typo in README"`, Args: cobra.MaximumNArgs(1), RunE: runExec, } @@ -110,7 +110,7 @@ func init() { execCmd.Flags().StringVarP(&execModel, "model", "m", "", "Model ID to use") execCmd.Flags().IntVar(&execMaxTurns, "max-turns", 0, "Maximum agentic turns (0 = unlimited)") execCmd.Flags().StringVar(&execCWD, "cwd", "", "Working directory") - execCmd.Flags().StringVar(&execAgent, "agent", "", "Agent persona to use (from Hawk user state)") + execCmd.Flags().StringVar(&execAgent, "agent", "", "Agent persona to use (from Rho user state)") execCmd.Flags().StringVarP(&execSessionID, "session-id", "s", "", "Continue an existing session") execCmd.Flags().StringVar(&execTag, "tag", "", "Session tag for categorization") execCmd.Flags().BoolVarP(&execWorktree, "worktree", "w", false, "Run in an isolated git worktree") @@ -176,7 +176,7 @@ func runExec(_ *cobra.Command, args []string) error { base := getCurrentBranch(cwd) branch := execWorktreeName if branch == "" { - branch = fmt.Sprintf("hawk-exec/%d-%s", start.UnixMilli(), randomHex(4)) + branch = fmt.Sprintf("rho-exec/%d-%s", start.UnixMilli(), randomHex(4)) } var wtErr error wtPath, wtErr = createExecWorktree(cwd, base, branch) @@ -191,7 +191,7 @@ func runExec(_ *cobra.Command, args []string) error { } // Load settings - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() // Build system prompt systemPrompt, err := buildSystemPrompt() @@ -227,7 +227,7 @@ func runExec(_ *cobra.Command, args []string) error { } // Create engine session - sess, cfgErr := newConfiguredHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) + sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) if cfgErr != nil { return cfgErr } @@ -244,11 +244,11 @@ func runExec(_ *cobra.Command, args []string) error { // GitHub Actions event (an outside contributor's issue/PR/comment body), // clamp autonomy to read-only auto-approval so attacker-controlled text // cannot drive writes or Bash. Maintainers can opt out with - // HAWK_GHA_TRUST_EVENT=1. + // RHO_GHA_TRUST_EVENT=1. if ghaCtx.Active && !ghaCtx.Trusted { const ceiling = engine.AutonomyBasic if sess.PermSvc().Autonomy() > ceiling { - fmt.Fprintf(os.Stderr, "%s\n", auditTint(fmt.Sprintf("hawk: untrusted GitHub event (author_association=%q); capping autonomy at %s", ghaCtx.AuthorAssociation, ceiling), warnAmber)) + fmt.Fprintf(os.Stderr, "%s\n", auditTint(fmt.Sprintf("rho: untrusted GitHub event (author_association=%q); capping autonomy at %s", ghaCtx.AuthorAssociation, ceiling), warnAmber)) sess.PermSvc().SetAutonomy(ceiling) } } @@ -375,7 +375,7 @@ func runExec(_ *cobra.Command, args []string) error { DeviceID: cfg.DeviceID, ProjectID: cfg.ProjectID, SessionID: sessionID, - Capability: "hawk", + Capability: "rho", Model: effectiveModel, InputTokens: totalIn, OutputTokens: totalOut, @@ -396,10 +396,10 @@ func runExec(_ *cobra.Command, args []string) error { fmt.Println() } if !IsQuiet() { - summary := fmt.Sprintf("hawk: %d tokens in / %d out · %d turn(s) · %s · %s", + summary := fmt.Sprintf("rho: %d tokens in / %d out · %d turn(s) · %s · %s", totalIn, totalOut, turns, time.Since(start).Round(time.Millisecond), effectiveModel) if cacheRead > 0 || cacheWrite > 0 { - summary = fmt.Sprintf("hawk: %d tokens in / %d out (cache %d read · %d write) · %d turn(s) · %s · %s", + summary = fmt.Sprintf("rho: %d tokens in / %d out (cache %d read · %d write) · %d turn(s) · %s · %s", totalIn, totalOut, cacheRead, cacheWrite, turns, time.Since(start).Round(time.Millisecond), effectiveModel) } fmt.Fprintf(os.Stderr, "%s\n", auditTint(summary, textMuted)) @@ -464,16 +464,16 @@ type GHAMode string const ( // GHAModeNone means we are not running inside GitHub Actions. GHAModeNone GHAMode = "" - // GHAModeInteractive is used when a human mentioned @hawk in a comment and + // GHAModeInteractive is used when a human mentioned @rho in a comment and // expects a conversational reply. GHAModeInteractive GHAMode = "interactive" - // GHAModeAutomation is used for label/issue triggers where hawk should act + // GHAModeAutomation is used for label/issue triggers where rho should act // autonomously on the issue/PR body. GHAModeAutomation GHAMode = "automation" ) // ghMention is the trigger token that promotes an event to interactive mode. -const ghMention = "@hawk" +const ghMention = "@rho" // ghTrustedAssociations are the GitHub author_association values that identify // a repository insider. Everyone else (CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR, @@ -491,7 +491,7 @@ type GHAContext struct { EventName string // GITHUB_EVENT_NAME Mode GHAMode // resolved operating mode Prompt string // event-derived prompt body - Mention bool // whether an @hawk mention was found in a comment + Mention bool // whether an @rho mention was found in a comment AuthorAssociation string // GitHub author_association of the triggering actor Trusted bool // author is a repo insider (or explicitly trusted) } @@ -524,7 +524,7 @@ func detectGitHubActions(getenv func(string) string, readFile func(string) ([]by // Trust signal: GitHub reports the actor's relationship to the repo. // Only insiders are trusted to drive high-autonomy tool use; content // from outside contributors is untrusted (prompt-injection surface). - // HAWK_GHA_TRUST_EVENT=1 lets a maintainer opt into trusting all events. + // RHO_GHA_TRUST_EVENT=1 lets a maintainer opt into trusting all events. ctx.AuthorAssociation = ghAuthorAssociation(payload) ctx.Trusted = ghTrustedAssociations[strings.ToUpper(strings.TrimSpace(ctx.AuthorAssociation))] || ghTrustEventOverride(getenv) @@ -552,7 +552,7 @@ func detectGitHubActions(getenv func(string) string, readFile func(string) ([]by // ghTrustEventOverride reports whether the maintainer has opted into // trusting GitHub Actions event content regardless of author association. func ghTrustEventOverride(getenv func(string) string) bool { - switch strings.ToLower(strings.TrimSpace(getenv("HAWK_GHA_TRUST_EVENT"))) { + switch strings.ToLower(strings.TrimSpace(getenv("RHO_GHA_TRUST_EVENT"))) { case "1", "true", "yes", "on": return true default: @@ -609,7 +609,7 @@ func ghIssueBody(payload map[string]interface{}) string { return strings.TrimSpace(ghCommentBody(payload)) } -// ghStripMention removes the leading @hawk mention from a comment so the +// ghStripMention removes the leading @rho mention from a comment so the // remaining text becomes the prompt. func ghStripMention(body string) string { out := body @@ -631,7 +631,7 @@ type skillRunner interface { Run(name string) (string, error) } -// pluginSkillRunner is the production skillRunner backed by Hawk skill storage. +// pluginSkillRunner is the production skillRunner backed by Rho skill storage. type pluginSkillRunner struct{} func (pluginSkillRunner) Run(name string) (string, error) { @@ -641,7 +641,7 @@ func (pluginSkillRunner) Run(name string) (string, error) { return fmt.Sprintf("[Skill: %s]\n\n%s", s.Name, s.Content), nil } } - return "", fmt.Errorf("skill %q not found (run `hawk skills` to list available skills)", name) + return "", fmt.Errorf("skill %q not found (run `rho skills` to list available skills)", name) } // defaultSkillRunner is overridable in tests. @@ -770,7 +770,7 @@ func runExecFanout(prompt string, n int) error { fmt.Fprintf(os.Stderr, "\n%s\n", auditTint(fmt.Sprintf("=== fanout attempt %d/%d ===", i, n), infoSky)) att := fanoutAttempt{Attempt: i} - branch := fmt.Sprintf("hawk-exec/%d-fanout%d-%s", start.UnixMilli(), i, randomHex(4)) + branch := fmt.Sprintf("rho-exec/%d-fanout%d-%s", start.UnixMilli(), i, randomHex(4)) wtPath, wtErr := createExecWorktree(cwd, base, branch) if wtErr != nil { att.Error = fmt.Sprintf("worktree: %v", wtErr) @@ -839,7 +839,7 @@ func runExecFanout(prompt string, n int) error { // best-effort and only when a channel is configured. title := fmt.Sprintf("Fan-out finished: %d/%d attempts succeeded", countOK(attempts), n) _ = notify.SendCompletion(notify.Completion{ - Title: title, Source: "hawk exec --fanout", OK: anyOK, Body: fanoutSummaryLines(attempts), + Title: title, Source: "rho exec --fanout", OK: anyOK, Body: fanoutSummaryLines(attempts), }) if !anyOK { @@ -903,7 +903,7 @@ func printFanoutReport(attempts []fanoutAttempt) { // (expected to be the attempt's worktree) and returns the structured result. // Stream events are captured rather than printed so N attempts do not interleave. func execOnceInWorktree(prompt string, attemptIdx int) (*ExecResult, error) { - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() systemPrompt, err := buildSystemPrompt() if err != nil { @@ -926,7 +926,7 @@ func execOnceInWorktree(prompt string, attemptIdx int) (*ExecResult, error) { if err != nil { return nil, err } - sess, cfgErr := newConfiguredHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) + sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) if cfgErr != nil { return nil, cfgErr } diff --git a/cmd/exec_test.go b/cmd/exec_test.go index de312db97..59e24400d 100644 --- a/cmd/exec_test.go +++ b/cmd/exec_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) // --- Skill dispatch tests --------------------------------------------------- @@ -134,7 +134,7 @@ func TestDetectGitHubActions_InteractiveMention(t *testing.T) { "GITHUB_EVENT_NAME": "issue_comment", "GITHUB_EVENT_PATH": "/tmp/event.json", } - payload := `{"comment":{"body":"@hawk please fix the failing test"}}` + payload := `{"comment":{"body":"@rho please fix the failing test"}}` gha := detectGitHubActions(envFunc(env), fileFunc(payload)) if !gha.Active { t.Fatal("expected Active=true") @@ -156,7 +156,7 @@ func TestDetectGitHubActions_ReviewCommentMention(t *testing.T) { "GITHUB_EVENT_NAME": "pull_request_review_comment", "GITHUB_EVENT_PATH": "/tmp/event.json", } - payload := `{"comment":{"body":"@Hawk explain this change"}}` + payload := `{"comment":{"body":"@Rho explain this change"}}` gha := detectGitHubActions(envFunc(env), fileFunc(payload)) if gha.Mode != GHAModeInteractive { t.Errorf("expected interactive mode for review comment, got %q", gha.Mode) @@ -223,14 +223,14 @@ func TestDetectGitHubActions_TrustFromAssociation(t *testing.T) { func TestDetectGitHubActions_TrustEnvOverride(t *testing.T) { env := map[string]string{ - "GITHUB_ACTIONS": "true", - "GITHUB_EVENT_NAME": "issues", - "GITHUB_EVENT_PATH": "/tmp/event.json", - "HAWK_GHA_TRUST_EVENT": "1", + "GITHUB_ACTIONS": "true", + "GITHUB_EVENT_NAME": "issues", + "GITHUB_EVENT_PATH": "/tmp/event.json", + "RHO_GHA_TRUST_EVENT": "1", } outsider := `{"issue":{"title":"t","body":"b","author_association":"NONE"}}` if gha := detectGitHubActions(envFunc(env), fileFunc(outsider)); !gha.Trusted { - t.Error("HAWK_GHA_TRUST_EVENT=1 should trust even NONE association") + t.Error("RHO_GHA_TRUST_EVENT=1 should trust even NONE association") } } @@ -254,7 +254,7 @@ func TestResolveExecPrompt_Empty(t *testing.T) { func TestPersistExecSession(t *testing.T) { // Set up temp session dir dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("RHO_STATE_DIR", filepath.Join(dir, "state")) persistExecSession("test-123", "claude-opus", "anthropic", "hello", "world") @@ -276,7 +276,7 @@ func TestExecResult_JSON(t *testing.T) { Duration: "1.5s", Model: "test-model", Worktree: "/tmp/wt", - Branch: "hawk-exec/123", + Branch: "rho-exec/123", } data, err := json.Marshal(r) if err != nil { @@ -292,7 +292,7 @@ func TestExecResult_JSON(t *testing.T) { if decoded.Worktree != "/tmp/wt" { t.Errorf("expected worktree path, got %s", decoded.Worktree) } - if decoded.Branch != "hawk-exec/123" { + if decoded.Branch != "rho-exec/123" { t.Errorf("expected branch, got %s", decoded.Branch) } } diff --git a/cmd/execution_graph.go b/cmd/execution_graph.go index 1b784ad07..58d8fa5fd 100644 --- a/cmd/execution_graph.go +++ b/cmd/execution_graph.go @@ -9,14 +9,14 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/hawk/internal/contracts/graph" - policycontracts "github.com/GrayCodeAI/hawk/internal/contracts/policy" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - "github.com/GrayCodeAI/hawk/internal/fsutil" - "github.com/GrayCodeAI/hawk/internal/graphjournal" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/taskruntime" - "github.com/GrayCodeAI/hawk/internal/tool" + graphcontracts "github.com/GrayCodeAI/rho/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + "github.com/GrayCodeAI/rho/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/fsutil" + "github.com/GrayCodeAI/rho/internal/graphjournal" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/taskruntime" + "github.com/GrayCodeAI/rho/internal/tool" "github.com/spf13/cobra" ) @@ -24,7 +24,7 @@ func newExecutionGraphCmd() *cobra.Command { graphCmd := &cobra.Command{ Use: "graph", Short: "Export a portable execution graph", - Long: `Project Hawk-owned sessions, task requests, structured tasks, runtime tasks, + Long: `Project Rho-owned sessions, task requests, structured tasks, runtime tasks, tool calls, policy observations, verification results, and explicit checkpoint links into the shared graph contract. @@ -38,7 +38,7 @@ truth for scheduling, tools, policy, verification, persistence, and tracing.`, var missionDir string exportCmd := &cobra.Command{ Use: "export [session-id]", - Short: "Export a Hawk session or mission as graph JSON", + Short: "Export a Rho session or mission as graph JSON", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { var export executiongraph.Export @@ -280,12 +280,12 @@ func loadRuntimeGraphObservations( for _, entry := range entries { subject := graphcontracts.Ref{ Kind: graphcontracts.NodeExecution, - ID: "hawk/session/" + saved.ID, + ID: "rho/session/" + saved.ID, } if _, ok := toolCallIDs[entry.ToolCallID]; ok && entry.ToolCallID != "" { subject = graphcontracts.Ref{ Kind: graphcontracts.NodeExecution, - ID: "hawk/tool-call/" + saved.ID + "/" + entry.ToolCallID, + ID: "rho/tool-call/" + saved.ID + "/" + entry.ToolCallID, } } if entry.Policy != nil { diff --git a/cmd/execution_graph_test.go b/cmd/execution_graph_test.go index 260eef1dc..4c5cee66a 100644 --- a/cmd/execution_graph_test.go +++ b/cmd/execution_graph_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/hawk/internal/contracts/graph" - policycontracts "github.com/GrayCodeAI/hawk/internal/contracts/policy" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - "github.com/GrayCodeAI/hawk/internal/graphjournal" - "github.com/GrayCodeAI/hawk/internal/session" + graphcontracts "github.com/GrayCodeAI/rho/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + "github.com/GrayCodeAI/rho/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/graphjournal" + "github.com/GrayCodeAI/rho/internal/session" ) func TestExecutionGraphCommandIsVisible(t *testing.T) { @@ -32,18 +32,18 @@ func TestLoadMissionGraphExportValidatesTopology(t *testing.T) { t.Parallel() now := time.Date(2026, time.July, 25, 7, 0, 0, 0, time.UTC) - mission := graphcontracts.Ref{Kind: graphcontracts.NodeExecution, ID: "hawk/mission/m1"} + mission := graphcontracts.Ref{Kind: graphcontracts.NodeExecution, ID: "rho/mission/m1"} export := executiongraph.Export{ SchemaVersion: executiongraph.SchemaVersion, GeneratedAt: now, Nodes: []graphcontracts.Node{{ ID: mission.ID, Kind: mission.Kind, CreatedAt: now, - Provenance: graphcontracts.Provenance{Producer: "hawk"}, + Provenance: graphcontracts.Provenance{Producer: "rho"}, }}, Events: []graphcontracts.Event{{ - ID: "hawk/event/mission/m1/created", Type: graphcontracts.EventCreated, + ID: "rho/event/mission/m1/created", Type: graphcontracts.EventCreated, Subject: mission, OccurredAt: now, - Provenance: graphcontracts.Provenance{Producer: "hawk"}, + Provenance: graphcontracts.Provenance{Producer: "rho"}, }}, } dir := t.TempDir() @@ -62,7 +62,7 @@ func TestLoadMissionGraphExportValidatesTopology(t *testing.T) { t.Fatalf("loaded graph = %#v", loaded) } - export.Events[0].Subject.ID = "hawk/mission/missing" + export.Events[0].Subject.ID = "rho/mission/missing" data, _ = json.Marshal(export) if err := os.WriteFile(filepath.Join(dir, "mission-graph.json"), data, 0o600); err != nil { t.Fatalf("rewrite graph: %v", err) @@ -75,11 +75,11 @@ func TestLoadMissionGraphExportValidatesTopology(t *testing.T) { func TestExecutionGraphRepositoryID(t *testing.T) { t.Parallel() - if got := executionGraphRepositoryID("custom", "/work/hawk"); got != "custom" { + if got := executionGraphRepositoryID("custom", "/work/rho"); got != "custom" { t.Fatalf("override repository ID = %q, want custom", got) } - if got := executionGraphRepositoryID("", "/work/hawk"); got != "hawk" { - t.Fatalf("derived repository ID = %q, want hawk", got) + if got := executionGraphRepositoryID("", "/work/rho"); got != "rho" { + t.Fatalf("derived repository ID = %q, want rho", got) } } @@ -97,11 +97,11 @@ func TestValidateSwiftCheckpointID(t *testing.T) { } func TestExecutionGraphExportCommand(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) saved := &session.Session{ ID: "graph-command-session", - CWD: "/workspace/hawk", + CWD: "/workspace/rho", CreatedAt: time.Date(2026, time.July, 25, 4, 0, 0, 0, time.UTC), Messages: []session.Message{{ Role: "user", @@ -209,8 +209,8 @@ func TestExecutionGraphExportCommand(t *testing.T) { if export.SchemaVersion != executiongraph.SchemaVersion { t.Fatalf("SchemaVersion = %q, want %q", export.SchemaVersion, executiongraph.SchemaVersion) } - if export.Scope.RepositoryID != "hawk" { - t.Fatalf("Scope.RepositoryID = %q, want hawk", export.Scope.RepositoryID) + if export.Scope.RepositoryID != "rho" { + t.Fatalf("Scope.RepositoryID = %q, want rho", export.Scope.RepositoryID) } if output.String() == "" { t.Fatal("graph export command produced no output") @@ -224,10 +224,10 @@ func TestExecutionGraphExportCommand(t *testing.T) { t.Fatalf("graph export command leaked %q", secret) } } - if !hasExportNodePrefix(export, "hawk/policy/") { + if !hasExportNodePrefix(export, "rho/policy/") { t.Fatal("graph export omitted automatic policy observation") } - if !hasExportNodePrefix(export, "hawk/verification/") { + if !hasExportNodePrefix(export, "rho/verification/") { t.Fatal("graph export omitted automatic verification observation") } if !hasExportNodePrefix(export, "memory/") { diff --git a/cmd/features.go b/cmd/features.go index 5a2b1ddc4..1170e3eed 100644 --- a/cmd/features.go +++ b/cmd/features.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/GrayCodeAI/hawk/internal/feature" + "github.com/GrayCodeAI/rho/internal/feature" "github.com/spf13/cobra" ) @@ -21,14 +21,14 @@ capabilities without code changes or restarts (some changes may require a daemon restart). Override a flag via environment variable: - HAWK_FEATURE_=1 hawk daemon start + RHO_FEATURE_=1 rho daemon start Show a specific flag: - hawk features get `, + rho features get `, RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 && args[0] == "get" { if len(args) < 2 { - return fmt.Errorf("usage: hawk features get ") + return fmt.Errorf("usage: rho features get ") } f, ok := feature.Info(args[1]) if !ok { @@ -38,7 +38,7 @@ Show a specific flag: fmt.Printf("%s\n", auditTint("Default: ", textMuted)+auditTint(fmt.Sprintf("%v", f.DefaultValue()), textPrimary)) fmt.Printf("%s\n", auditTint("Current: ", textMuted)+auditTint(fmt.Sprintf("%v", feature.EnabledByName(args[1])), textPrimary)) fmt.Printf("%s\n", auditTint("Description: ", textMuted)+auditTint(f.Description(), textPrimary)) - envVar := "HAWK_FEATURE_" + strings.ReplaceAll(strings.ToUpper(args[1]), "-", "_") + envVar := "RHO_FEATURE_" + strings.ReplaceAll(strings.ToUpper(args[1]), "-", "_") fmt.Printf("%s\n", auditTint("Env var: ", textMuted)+auditTint(envVar, textPrimary)) return nil } @@ -50,7 +50,7 @@ Show a specific flag: } sort.Strings(names) - fmt.Println(auditTint("Feature Flags:", hawkColor)) + fmt.Println(auditTint("Feature Flags:", rhoColor)) fmt.Println() for _, name := range names { f, _ := feature.Info(name) @@ -65,7 +65,7 @@ Show a specific flag: if f != nil { fmt.Printf("%s\n", auditTint(fmt.Sprintf(" default: %v", f.DefaultValue()), textMuted)) fmt.Printf("%s\n", auditTint(" description: "+f.Description(), textMuted)) - envVar := "HAWK_FEATURE_" + strings.ReplaceAll(strings.ToUpper(name), "-", "_") + envVar := "RHO_FEATURE_" + strings.ReplaceAll(strings.ToUpper(name), "-", "_") fmt.Printf("%s\n", auditTint(" env: "+envVar, textMuted)) } fmt.Println() diff --git a/cmd/feedback.go b/cmd/feedback.go index 7d730128c..7a4ea11d9 100644 --- a/cmd/feedback.go +++ b/cmd/feedback.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) @@ -37,17 +37,17 @@ type FeedbackReport struct { var feedbackCmd = &cobra.Command{ Use: "feedback [message]", - Short: "Submit feedback about hawk", - Long: `Capture feedback about your hawk experience. By default, opens a + Short: "Submit feedback about rho", + Long: `Capture feedback about your rho experience. By default, opens a GitHub issue template URL in your browser. Use --local to save feedback -to Hawk's user state directory for later submission. +to Rho's user state directory for later submission. Categories: bug, feature, ux, performance, other Examples: - hawk feedback "The completion is slow" - hawk feedback --category bug "Crash when using /compact" - hawk feedback --local "Wish it could do X"`, + rho feedback "The completion is slow" + rho feedback --category bug "Crash when using /compact" + rho feedback --local "Wish it could do X"`, Args: cobra.MinimumNArgs(0), RunE: runFeedback, } @@ -141,7 +141,7 @@ func openFeedbackIssue(report FeedbackReport) error { bodyBuilder.WriteString(fmt.Sprintf("- **Timestamp:** %s\n", report.Timestamp)) issueURL := fmt.Sprintf( - "https://github.com/GrayCodeAI/hawk/issues/new?title=%s&body=%s&labels=%s", + "https://github.com/GrayCodeAI/rho/issues/new?title=%s&body=%s&labels=%s", url.QueryEscape(title), url.QueryEscape(bodyBuilder.String()), url.QueryEscape(report.Category), diff --git a/cmd/fingerprint.go b/cmd/fingerprint.go index bfd4fb4c9..b7759e8b4 100644 --- a/cmd/fingerprint.go +++ b/cmd/fingerprint.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/GrayCodeAI/hawk/internal/feature/fingerprint" + "github.com/GrayCodeAI/rho/internal/feature/fingerprint" "github.com/spf13/cobra" ) @@ -19,10 +19,10 @@ including detected languages, dependency counts, CI presence, license, and git metadata. Examples: - hawk fingerprint - hawk fingerprint ./myproject - hawk fingerprint --format json . - hawk fingerprint --format markdown /path/to/repo`, + rho fingerprint + rho fingerprint ./myproject + rho fingerprint --format json . + rho fingerprint --format markdown /path/to/repo`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { dir := "." diff --git a/cmd/footer_layout_clip_test.go b/cmd/footer_layout_clip_test.go index eb03f451d..940851274 100644 --- a/cmd/footer_layout_clip_test.go +++ b/cmd/footer_layout_clip_test.go @@ -9,7 +9,7 @@ import ( func TestLayoutFooterRow_TokensSurviveFinishFooterLine(t *testing.T) { m := chatModel{width: 70, height: 24} - left := lipgloss.NewStyle().Foreground(statusCWDColor).Inline(true).Render("~/OSS2026/RealWork/graycode-eco/hawk") + left := lipgloss.NewStyle().Foreground(statusCWDColor).Inline(true).Render("~/OSS2026/RealWork/graycode-eco/rho") left += " " + lipgloss.NewStyle().Foreground(statusBranchColor).Inline(true).Render("⎇ main") right := lipgloss.NewStyle().Foreground(statusTokenColor).Inline(true).Render("[db] 13k") right += lipgloss.NewStyle().Foreground(dimColor).Inline(true).Render(" · ") @@ -42,7 +42,7 @@ func TestLayoutFooterRow_LeftWiderThanFooterStillShowsTokens(t *testing.T) { func TestLayoutFooterRow_ClipDoesNotDropStyledTokens(t *testing.T) { m := chatModel{width: 55, height: 24} - left := lipgloss.NewStyle().Foreground(statusCWDColor).Inline(true).Render("~/Desktop/OSS2026/RealWork/graycode-eco/hawk") + left := lipgloss.NewStyle().Foreground(statusCWDColor).Inline(true).Render("~/Desktop/OSS2026/RealWork/graycode-eco/rho") left += " " + lipgloss.NewStyle().Foreground(statusBranchColor).Inline(true).Render("⎇ feature/footer-fix") dim := lipgloss.NewStyle().Foreground(dimColor).Inline(true) tok := lipgloss.NewStyle().Foreground(statusTokenColor).Inline(true) diff --git a/cmd/footer_layout_width_test.go b/cmd/footer_layout_width_test.go index d85687001..eb169e04a 100644 --- a/cmd/footer_layout_width_test.go +++ b/cmd/footer_layout_width_test.go @@ -13,7 +13,7 @@ func TestLayoutFooterRow_StyledStringsAlignRight(t *testing.T) { tokenStyle := lipgloss.NewStyle().Foreground(statusTokenColor).Inline(true) dim := lipgloss.NewStyle().Foreground(dimColor).Inline(true) - left := cwdStyle.Render("~/hawk") + " " + cwdStyle.Render("⎇ main") + left := cwdStyle.Render("~/rho") + " " + cwdStyle.Render("⎇ main") right := tokenStyle.Render("[db] 13k") + dim.Render(" · ") + tokenStyle.Render("$0.00") width := 80 diff --git a/cmd/formatter.go b/cmd/formatter.go index d6d01263c..25a6b6cb8 100644 --- a/cmd/formatter.go +++ b/cmd/formatter.go @@ -9,12 +9,12 @@ import ( "golang.org/x/term" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // stdoutIsTerminal reports whether stdout is connected to a terminal (TTY). // When stdout is a pipe or file — which is exactly the case when an agent or -// shell script captures hawk's output — this is false, and color/Unicode +// shell script captures rho's output — this is false, and color/Unicode // chrome must be suppressed so the payload stays clean. It is a var so tests // can override it. var stdoutIsTerminal = func() bool { diff --git a/cmd/formatter_test.go b/cmd/formatter_test.go index 8c8aa1349..11a688759 100644 --- a/cmd/formatter_test.go +++ b/cmd/formatter_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestDetectColorSupport_NonTTYStdout(t *testing.T) { diff --git a/cmd/golden_test.go b/cmd/golden_test.go index b4a146309..6d61e26e0 100644 --- a/cmd/golden_test.go +++ b/cmd/golden_test.go @@ -52,8 +52,8 @@ func TestGoldenHelp(t *testing.T) { t.Fatalf("golden file %s not found (run with -update-golden to create): %v", golden, err) } - if !strings.Contains(got, "hawk") { - t.Error("help output should contain 'hawk'") + if !strings.Contains(got, "rho") { + t.Error("help output should contain 'rho'") } if len(got) < 100 { t.Error("help output seems too short") diff --git a/cmd/governance_cmd.go b/cmd/governance_cmd.go index 089d3fcbb..89462bf8a 100644 --- a/cmd/governance_cmd.go +++ b/cmd/governance_cmd.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/GrayCodeAI/hawk/internal/governance" + "github.com/GrayCodeAI/rho/internal/governance" "github.com/spf13/cobra" ) @@ -20,10 +20,10 @@ var governanceCmd = &cobra.Command{ per-session PROFILE (tightest-wins). Tools are permitted only when both layers allow them. - hawk governance Show the managed policy status - hawk governance show Print the effective capability rows - hawk governance validate Validate a policy or profile document - hawk governance explain Evaluate a tool against the policy`, + rho governance Show the managed policy status + rho governance show Print the effective capability rows + rho governance validate Validate a policy or profile document + rho governance explain Evaluate a tool against the policy`, RunE: func(cmd *cobra.Command, args []string) error { return runGovernanceStatus(cmd) }, diff --git a/cmd/harness.go b/cmd/harness.go index 734bfc1af..a4c18173a 100644 --- a/cmd/harness.go +++ b/cmd/harness.go @@ -7,7 +7,7 @@ import ( "os" "path/filepath" - "github.com/GrayCodeAI/hawk/internal/harness" + "github.com/GrayCodeAI/rho/internal/harness" "github.com/spf13/cobra" ) @@ -84,7 +84,7 @@ Use --fix to automatically repair missing AGENTS.md, skills, or spec directories if fixErr != nil { return fmt.Errorf("harness auto-fix failed: %w", fixErr) } - fmt.Printf("%s\n", auditTint("[FIX] Hawk Harness Auto-Repair Results:", warnAmber)) + fmt.Printf("%s\n", auditTint("[FIX] Rho Harness Auto-Repair Results:", warnAmber)) for _, repair := range fixResult.RepairsPerformed { fmt.Printf("%s\n", auditTint(" + "+repair, doneGreen)) } @@ -95,7 +95,7 @@ Use --fix to automatically repair missing AGENTS.md, skills, or spec directories outDir := harnessOutDir if outDir == "" { - outDir = filepath.Join(targetDir, ".hawk", "harness") + outDir = filepath.Join(targetDir, ".rho", "harness") } if mkdirErr := os.MkdirAll(outDir, 0o750); mkdirErr != nil { @@ -132,11 +132,11 @@ Use --fix to automatically repair missing AGENTS.md, skills, or spec directories return fmt.Errorf("failed to write findings.json: %w", writeErr) } - // Journal quality observation to Hawk execution graph + // Journal quality observation to Rho execution graph _ = harness.JournalHarnessReport(report, "") finish() - fmt.Printf("%s\n", auditTint("[HAWK] Hawk Harness Evaluation Complete", hawkColor)) + fmt.Printf("%s\n", auditTint("[RHO] Rho Harness Evaluation Complete", rhoColor)) fmt.Printf(" %s : %s (%s)\n", auditTint("Overall Score", textPrimary), auditTint(fmt.Sprintf("%d/100", report.OverallScore), textPrimary), @@ -151,7 +151,7 @@ Use --fix to automatically repair missing AGENTS.md, skills, or spec directories } func init() { - harnessCmd.Flags().StringVar(&harnessOutDir, "out-dir", "", "Directory to save harness reports (default: .hawk/harness)") + harnessCmd.Flags().StringVar(&harnessOutDir, "out-dir", "", "Directory to save harness reports (default: .rho/harness)") harnessCmd.Flags().StringVar(&harnessFormat, "format", "all", "Report output format (html, markdown, json, all)") harnessCmd.Flags().BoolVar(&harnessFix, "fix", false, "Automatically repair missing harness assets (AGENTS.md, skills, specs)") } diff --git a/cmd/help_template.go b/cmd/help_template.go index 70b15191a..f0436ecd8 100644 --- a/cmd/help_template.go +++ b/cmd/help_template.go @@ -10,7 +10,7 @@ import ( // last column so coloring them (zero-width ANSI) cannot break alignment. All // color honors ShouldColor() (NO_COLOR, --quiet, non-TTY) via auditTint. func init() { - cobra.AddTemplateFunc("gcHeader", func(s string) string { return auditTint(s, hawkColor) }) + cobra.AddTemplateFunc("gcHeader", func(s string) string { return auditTint(s, rhoColor) }) cobra.AddTemplateFunc("gcCmd", func(s string) string { return auditTint(s, textPrimary) }) cobra.AddTemplateFunc("gcDesc", func(s string) string { return auditTint(s, textMuted) }) rootCmd.SetUsageTemplate(modernUsageTemplate) diff --git a/cmd/history.go b/cmd/history.go index 506bdba3b..c89d89a4b 100644 --- a/cmd/history.go +++ b/cmd/history.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) const maxHistoryEntries = 1000 @@ -15,7 +15,7 @@ func historyFilePath() string { return filepath.Join(storage.StateDir(), "history") } -// loadInputHistory loads input history from Hawk's user state directory. +// loadInputHistory loads input history from Rho's user state directory. // Returns an empty slice if the file does not exist. func loadInputHistory() []string { data, err := os.ReadFile(historyFilePath()) @@ -33,7 +33,7 @@ func loadInputHistory() []string { return entries } -// saveInputHistory writes the history list to Hawk's user state directory. +// saveInputHistory writes the history list to Rho's user state directory. // Deduplicates entries (keeping the last occurrence) and caps at maxHistoryEntries. func saveInputHistory(history []string) { // Deduplicate: keep the last occurrence of each entry diff --git a/cmd/history_test.go b/cmd/history_test.go index cda9aab7b..d11dce66c 100644 --- a/cmd/history_test.go +++ b/cmd/history_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestLoadInputHistory_Empty(t *testing.T) { @@ -94,7 +94,7 @@ func TestAppendToHistory_EmptySkipped(t *testing.T) { func TestHistoryFilePath(t *testing.T) { stateDir := filepath.Join(t.TempDir(), "state") - t.Setenv("HAWK_STATE_DIR", stateDir) + t.Setenv("RHO_STATE_DIR", stateDir) expected := filepath.Join(stateDir, "history") if got := historyFilePath(); got != expected { t.Fatalf("got %q, want %q", got, expected) diff --git a/cmd/hud_panel.go b/cmd/hud_panel.go index e81d92ee4..535536af0 100644 --- a/cmd/hud_panel.go +++ b/cmd/hud_panel.go @@ -6,7 +6,7 @@ import ( lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // HUDData is a snapshot of agent/mission/memory state rendered by the HUD panel. diff --git a/cmd/image.go b/cmd/image.go index 62876cddf..71b9f8254 100644 --- a/cmd/image.go +++ b/cmd/image.go @@ -13,8 +13,8 @@ import ( "regexp" "strings" - "github.com/GrayCodeAI/hawk/internal/tui" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/tui" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // ImageAttachment represents an image ready to be attached to a message. diff --git a/cmd/input_indicator.go b/cmd/input_indicator.go index cb3c0ac8a..7034340af 100644 --- a/cmd/input_indicator.go +++ b/cmd/input_indicator.go @@ -3,8 +3,8 @@ package cmd import ( lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // InputClass represents the classification of user input. diff --git a/cmd/input_indicator_test.go b/cmd/input_indicator_test.go index e39c519e0..c8772bec8 100644 --- a/cmd/input_indicator_test.go +++ b/cmd/input_indicator_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/hawk/internal/feature/shellmode" + "github.com/GrayCodeAI/rho/internal/feature/shellmode" ) func TestInputIndicator_Classify(t *testing.T) { diff --git a/cmd/issue.go b/cmd/issue.go index 583a65187..0d3d200b2 100644 --- a/cmd/issue.go +++ b/cmd/issue.go @@ -134,5 +134,5 @@ func generateIssueBody(ctx string) string { if ctx == "" { return "No additional context was provided." } - return "**Reported via hawk**\n\n```\n" + ctx + "\n```\n" + return "**Reported via rho**\n\n```\n" + ctx + "\n```\n" } diff --git a/cmd/learn_cmd.go b/cmd/learn_cmd.go index 417eed957..97138cc28 100644 --- a/cmd/learn_cmd.go +++ b/cmd/learn_cmd.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/spf13/cobra" ) @@ -24,13 +24,13 @@ var ( var learnCmd = &cobra.Command{ Use: "learn", Short: "Manage lessons learned across sessions", - Long: `Hawk persists lessons from failures (and manual entries) so future + Long: `Rho persists lessons from failures (and manual entries) so future sessions avoid repeating them. Lessons are injected into the system prompt. - hawk learn List recent lessons - hawk learn add Add a lesson manually - hawk learn prompt Print the lesson-extraction prompt for a context - hawk learn clear Remove all lessons`, + rho learn List recent lessons + rho learn add Add a lesson manually + rho learn prompt Print the lesson-extraction prompt for a context + rho learn clear Remove all lessons`, RunE: func(cmd *cobra.Command, args []string) error { return runLearnList(cmd) }, @@ -104,7 +104,7 @@ func runLearnList(cmd *cobra.Command) error { si := engine.NewSelfImprover() lessons := si.Lessons("") if len(lessons) == 0 { - cmd.Println(auditTint("No lessons yet. Add one with: hawk learn add --what ... --lesson ...", textMuted)) + cmd.Println(auditTint("No lessons yet. Add one with: rho learn add --what ... --lesson ...", textMuted)) return nil } diff --git a/cmd/main_test.go b/cmd/main_test.go index 1f6d9ee7b..9e0b493ae 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" - "github.com/GrayCodeAI/hawk/internal/catalogtest" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/catalogtest" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestMain(m *testing.M) { diff --git a/cmd/manpage.go b/cmd/manpage.go index bc32d7fcb..d4533824f 100644 --- a/cmd/manpage.go +++ b/cmd/manpage.go @@ -12,7 +12,7 @@ import ( var manpageCmd = &cobra.Command{ Use: "manpage", Short: "Generate man page in roff format", - Long: "Generate a man page for hawk in roff format and print it to stdout.\nRedirect to a file in your man path, e.g.: hawk manpage > /usr/local/share/man/man1/hawk.1", + Long: "Generate a man page for rho in roff format and print it to stdout.\nRedirect to a file in your man path, e.g.: rho manpage > /usr/local/share/man/man1/rho.1", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { if _, err := fmt.Fprint(cmd.OutOrStdout(), GenerateManPage()); err != nil { @@ -22,7 +22,7 @@ var manpageCmd = &cobra.Command{ }, } -// GenerateManPage produces a man page in roff format for hawk. +// GenerateManPage produces a man page in roff format for rho. // The OPTIONS section is generated from the live Cobra flag set so it // never drifts from the actual CLI surface. func GenerateManPage() string { @@ -35,19 +35,19 @@ func GenerateManPage() string { var b strings.Builder // Header - b.WriteString(fmt.Sprintf(`.TH HAWK 1 "%s" "%s" "User Commands"`, date, ver)) + b.WriteString(fmt.Sprintf(`.TH RHO 1 "%s" "%s" "User Commands"`, date, ver)) b.WriteString("\n") // Name - b.WriteString(".SH NAME\nhawk \\- AI coding agent powered by eyrie\n") + b.WriteString(".SH NAME\nrho \\- AI coding agent powered by eyrie\n") // Synopsis b.WriteString(".SH SYNOPSIS\n") - b.WriteString(".B hawk\n[\\fIOPTIONS\\fR] [\\fIPROMPT\\fR]\n") + b.WriteString(".B rho\n[\\fIOPTIONS\\fR] [\\fIPROMPT\\fR]\n") // Description b.WriteString(".SH DESCRIPTION\n") - b.WriteString("hawk is an AI coding agent that reads, writes, and runs code in your terminal.\n") + b.WriteString("rho is an AI coding agent that reads, writes, and runs code in your terminal.\n") fmt.Fprintf(&b, "It connects to %d first-class LLM providers through eyrie, executes tools (file I/O,\n", registeredProviderCount()) b.WriteString("shell, git, web search), and manages sessions from a keyboard-driven TUI\n") b.WriteString("or headless mode for scripts and CI.\n") @@ -81,7 +81,7 @@ func GenerateManPage() string { if sub.Hidden { continue } - b.WriteString(fmt.Sprintf(".TP\n\\fBhawk %s\\fR\n%s\n", sub.Use, sub.Short)) + b.WriteString(fmt.Sprintf(".TP\n\\fBrho %s\\fR\n%s\n", sub.Use, sub.Short)) } // Slash Commands @@ -99,7 +99,7 @@ func GenerateManPage() string { {"/review", "Code review"}, {"/doctor", "Run diagnostics"}, {"/tools", "List enabled tools"}, - {"/quit", "Exit hawk"}, + {"/quit", "Exit rho"}, } for _, sc := range slashCmds { b.WriteString(fmt.Sprintf(".TP\n\\fB%s\\fR\n%s\n", sc.cmd, sc.desc)) @@ -107,16 +107,16 @@ func GenerateManPage() string { // Files b.WriteString(".SH FILES\n") - b.WriteString(".TP\n\\fBHawk user config directory\\fR\nGlobal configuration files\n") + b.WriteString(".TP\n\\fBRho user config directory\\fR\nGlobal configuration files\n") b.WriteString(".TP\n\\fBAGENTS.md\\fR\nProject instructions file\n") - b.WriteString(".TP\n\\fBHawk user state directory\\fR\nSaved session data, plans, skills, and runtime state\n") + b.WriteString(".TP\n\\fBRho user state directory\\fR\nSaved session data, plans, skills, and runtime state\n") // Credentials b.WriteString(".SH CREDENTIALS\n") b.WriteString("API keys are stored in the OS secret service (macOS Keychain or Linux GNOME Keyring / KWallet).\n") - b.WriteString("Use \\fBhawk\\fR and \\fB/config\\fR to save keys; hawk does not read API keys from .env files.\n") - b.WriteString(".TP\n\\fBhawk credentials status\\fR\nShow secure storage status\n") - b.WriteString(".TP\n\\fBhawk credentials remove \\fR\nRemove a stored API key from the OS secret store\n") + b.WriteString("Use \\fBrho\\fR and \\fB/config\\fR to save keys; rho does not read API keys from .env files.\n") + b.WriteString(".TP\n\\fBrho credentials status\\fR\nShow secure storage status\n") + b.WriteString(".TP\n\\fBrho credentials remove \\fR\nRemove a stored API key from the OS secret store\n") // Environment b.WriteString(".SH ENVIRONMENT\n") @@ -124,14 +124,14 @@ func GenerateManPage() string { envVars := []struct{ env, desc string }{ {"OPENAI_MODEL", "Override default OpenAI model"}, {"OLLAMA_BASE_URL", "Ollama server URL (also saved via /config for Ollama)"}, - {"HAWK_CONFIG_DIR", "Override hawk config directory"}, + {"RHO_CONFIG_DIR", "Override rho config directory"}, } for _, ev := range envVars { b.WriteString(fmt.Sprintf(".TP\n\\fB%s\\fR\n%s\n", ev.env, ev.desc)) } // Authors - b.WriteString(".SH AUTHORS\nGrayCode AI \n") + b.WriteString(".SH AUTHORS\nGrayCode AI \n") return b.String() } diff --git a/cmd/manpage_test.go b/cmd/manpage_test.go index 6bfa55321..0174c5185 100644 --- a/cmd/manpage_test.go +++ b/cmd/manpage_test.go @@ -26,7 +26,7 @@ func TestGenerateManPage(t *testing.T) { version = "1.0.0" page := GenerateManPage() - if !strings.Contains(page, ".TH HAWK 1") { + if !strings.Contains(page, ".TH RHO 1") { t.Fatal("missing .TH header") } if !strings.Contains(page, "1.0.0") { diff --git a/cmd/markdown.go b/cmd/markdown.go index 7707c54c2..91c7fabd2 100644 --- a/cmd/markdown.go +++ b/cmd/markdown.go @@ -25,12 +25,12 @@ import ( // Markdown rendering styles using the project's purpose-named palette. var ( - mdH1Style = lipgloss.NewStyle().Foreground(hawkColor).Bold(true).Underline(true) + mdH1Style = lipgloss.NewStyle().Foreground(rhoColor).Bold(true).Underline(true) mdH2Style = lipgloss.NewStyle().Foreground(successTeal).Bold(true) mdH3Style = lipgloss.NewStyle().Foreground(infoSky).Bold(true) mdH4Style = lipgloss.NewStyle().Foreground(costViolet).Bold(true) mdHeaderStyle = lipgloss.NewStyle().Foreground(textPrimary).Bold(true) - mdBoldStyle = lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + mdBoldStyle = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) mdItalicStyle = lipgloss.NewStyle().Italic(true) mdInlineCodeStyle = lipgloss.NewStyle().Foreground(infoSky) mdCodeBlockStyle = lipgloss.NewStyle().Background(bgCode) @@ -621,13 +621,13 @@ func protectInlineCode(text string, render func(string) string) (string, func(st if len(parts) < 2 { return m } - placeholder := fmt.Sprintf("\x00HAWK_INLINE_CODE_%d\x00", len(replacements)) + placeholder := fmt.Sprintf("\x00RHO_INLINE_CODE_%d\x00", len(replacements)) replacements = append(replacements, render(parts[1])) return placeholder }) restore := func(s string) string { for i, repl := range replacements { - s = strings.ReplaceAll(s, fmt.Sprintf("\x00HAWK_INLINE_CODE_%d\x00", i), repl) + s = strings.ReplaceAll(s, fmt.Sprintf("\x00RHO_INLINE_CODE_%d\x00", i), repl) } return s } diff --git a/cmd/markdown_renderer.go b/cmd/markdown_renderer.go index c206c5dbe..6fb60ad3d 100644 --- a/cmd/markdown_renderer.go +++ b/cmd/markdown_renderer.go @@ -310,13 +310,13 @@ func protectRendererInlineCode(text string, render func(string) string) (string, if len(parts) < 2 { return m } - placeholder := fmt.Sprintf("\x00HAWK_R_INLINE_CODE_%d\x00", len(replacements)) + placeholder := fmt.Sprintf("\x00RHO_R_INLINE_CODE_%d\x00", len(replacements)) replacements = append(replacements, render(parts[1])) return placeholder }) restore := func(s string) string { for i, repl := range replacements { - s = strings.ReplaceAll(s, fmt.Sprintf("\x00HAWK_R_INLINE_CODE_%d\x00", i), repl) + s = strings.ReplaceAll(s, fmt.Sprintf("\x00RHO_R_INLINE_CODE_%d\x00", i), repl) } return s } diff --git a/cmd/markdown_test.go b/cmd/markdown_test.go index 5e25fe56b..91c3abad4 100644 --- a/cmd/markdown_test.go +++ b/cmd/markdown_test.go @@ -210,10 +210,10 @@ func TestRenderMarkdownOrderedList(t *testing.T) { } func TestRenderMarkdownLinks(t *testing.T) { - input := "Visit [Hawk](https://example.com) for info" + input := "Visit [Rho](https://example.com) for info" out := renderMarkdown(input, 80) plain := stripAnsi(out) - if !strings.Contains(plain, "Hawk") { + if !strings.Contains(plain, "Rho") { t.Errorf("expected link text in output, got %q", plain) } if !strings.Contains(plain, "https://example.com") { @@ -831,7 +831,7 @@ func TestMarkdownRendererPlainText(t *testing.T) { func TestRenderTableFunction(t *testing.T) { rows := [][]string{ {"Name", "Language", "Stars"}, - {"hawk", "Go", "1200"}, + {"rho", "Go", "1200"}, {"glow", "Go", "15000"}, {"bat", "Rust", "47000"}, } diff --git a/cmd/mcp_serve.go b/cmd/mcp_serve.go index 8997af389..4598bc6bb 100644 --- a/cmd/mcp_serve.go +++ b/cmd/mcp_serve.go @@ -6,8 +6,8 @@ import ( "os/signal" "syscall" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/mcp" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/mcp" "github.com/spf13/cobra" ) @@ -20,28 +20,28 @@ func init() { mcpCmd.AddCommand(mcpConfigCmd) } -// mcpServeCmd runs hawk itself as an MCP server over stdio, exposing hawk's +// mcpServeCmd runs rho itself as an MCP server over stdio, exposing rho's // capabilities (chat, search, memory, review, scan, compress) to MCP clients // such as Claude Desktop, Cursor, and Windsurf. var mcpServeCmd = &cobra.Command{ Use: "serve", - Short: "Run hawk as an MCP server over stdio", - Long: "Run hawk as a Model Context Protocol server over stdio (JSON-RPC 2.0), " + - "exposing hawk's tools to MCP clients like Claude Desktop, Cursor, and Windsurf.\n\n" + - "Use `hawk mcp config` to print the JSON block that registers this command in a client.", + Short: "Run rho as an MCP server over stdio", + Long: "Run rho as a Model Context Protocol server over stdio (JSON-RPC 2.0), " + + "exposing rho's tools to MCP clients like Claude Desktop, Cursor, and Windsurf.\n\n" + + "Use `rho mcp config` to print the JSON block that registers this command in a client.", RunE: runMCPServe, } func runMCPServe(cmd *cobra.Command, _ []string) error { - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() serverVersion := version if serverVersion == "" { serverVersion = "dev" } - server := mcp.NewMCPServer(mcp.ServerInfo{Name: "hawk", Version: serverVersion}) + server := mcp.NewMCPServer(mcp.ServerInfo{Name: "rho", Version: serverVersion}) - // Wire hawk's tool registry in as the executor so delegating tools run for + // Wire rho's tool registry in as the executor so delegating tools run for // real; a registry build failure degrades to not-configured rather than // aborting (the server still answers initialize/tools/list). registry, err := defaultRegistry(settings) @@ -56,24 +56,24 @@ func runMCPServe(cmd *cobra.Command, _ []string) error { return server.ServeStdio(ctx) } -// mcpConfigCmd emits the JSON block that registers hawk as an MCP server in a +// mcpConfigCmd emits the JSON block that registers rho as an MCP server in a // client's config file, so users don't hand-edit JSON. var mcpConfigCmd = &cobra.Command{ Use: "config", - Short: "Print the MCP-server config block to register hawk in a client", - Long: "Print the JSON block that registers hawk as an MCP server (pointing at " + - "`hawk mcp serve`) for clients like Claude Desktop, Cursor, and Windsurf.\n\n" + + Short: "Print the MCP-server config block to register rho in a client", + Long: "Print the JSON block that registers rho as an MCP server (pointing at " + + "`rho mcp serve`) for clients like Claude Desktop, Cursor, and Windsurf.\n\n" + "Pipe it to the client's config file, e.g.:\n" + - " hawk mcp config >> ~/Library/Application Support/Claude/claude_desktop_config.json", + " rho mcp config >> ~/Library/Application Support/Claude/claude_desktop_config.json", RunE: runMCPConfig, } func runMCPConfig(cmd *cobra.Command, _ []string) error { - exe := hawkExecutablePath() + exe := rhoExecutablePath() block := map[string]any{ "mcpServers": map[string]any{ - "hawk": map[string]any{ + "rho": map[string]any{ "command": exe, "args": []string{"mcp", "serve"}, }, @@ -85,7 +85,7 @@ func runMCPConfig(cmd *cobra.Command, _ []string) error { } if mcpConfigWrite { - cmd.Println("# Add the \"hawk\" entry below into the \"mcpServers\" object of your client config:") + cmd.Println("# Add the \"rho\" entry below into the \"mcpServers\" object of your client config:") cmd.Println("# Claude Desktop (macOS): ~/Library/Application Support/Claude/claude_desktop_config.json") cmd.Println("# Cursor: ~/.cursor/mcp.json") cmd.Println("# Windsurf: ~/.codeium/windsurf/mcp_config.json") @@ -95,12 +95,12 @@ func runMCPConfig(cmd *cobra.Command, _ []string) error { return nil } -// hawkExecutablePath returns the absolute path to the running hawk binary, or -// the bare name "hawk" if it cannot be resolved (e.g. during `go run`), so the +// rhoExecutablePath returns the absolute path to the running rho binary, or +// the bare name "rho" if it cannot be resolved (e.g. during `go run`), so the // emitted config is still copy-pasteable. -func hawkExecutablePath() string { +func rhoExecutablePath() string { if exe, err := os.Executable(); err == nil && exe != "" { return exe } - return "hawk" + return "rho" } diff --git a/cmd/mcp_serve_test.go b/cmd/mcp_serve_test.go index 8b37d46b2..82995e16f 100644 --- a/cmd/mcp_serve_test.go +++ b/cmd/mcp_serve_test.go @@ -30,15 +30,15 @@ func TestMCPConfigEmitsValidServerBlock(t *testing.T) { t.Fatalf("emitted config is not valid JSON: %v\n%s", err, buf.String()) } - hawk, ok := cfg.MCPServers["hawk"] + rho, ok := cfg.MCPServers["rho"] if !ok { - t.Fatal(`config missing "hawk" server entry`) + t.Fatal(`config missing "rho" server entry`) } - if hawk.Command == "" { - t.Error("hawk server entry missing command") + if rho.Command == "" { + t.Error("rho server entry missing command") } - if len(hawk.Args) != 2 || hawk.Args[0] != "mcp" || hawk.Args[1] != "serve" { - t.Errorf(`args = %v, want ["mcp" "serve"]`, hawk.Args) + if len(rho.Args) != 2 || rho.Args[0] != "mcp" || rho.Args[1] != "serve" { + t.Errorf(`args = %v, want ["mcp" "serve"]`, rho.Args) } } diff --git a/cmd/mentions.go b/cmd/mentions.go index a78aa9d34..a9c3f76dc 100644 --- a/cmd/mentions.go +++ b/cmd/mentions.go @@ -6,8 +6,8 @@ import ( "strings" "unicode/utf8" - "github.com/GrayCodeAI/hawk/internal/mention" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/mention" + "github.com/GrayCodeAI/rho/internal/tool" ) // handleMentions processes @-prefixed file mentions in user input. diff --git a/cmd/mentions_test.go b/cmd/mentions_test.go index 63802f007..377387475 100644 --- a/cmd/mentions_test.go +++ b/cmd/mentions_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestHandleMentions_BlocksSensitivePaths(t *testing.T) { diff --git a/cmd/mission.go b/cmd/mission.go index 06df34d78..d70c55c0a 100644 --- a/cmd/mission.go +++ b/cmd/mission.go @@ -9,12 +9,12 @@ import ( "strings" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - mission "github.com/GrayCodeAI/hawk/internal/multiagent" - "github.com/GrayCodeAI/hawk/internal/observability/logger" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + mission "github.com/GrayCodeAI/rho/internal/multiagent" + "github.com/GrayCodeAI/rho/internal/observability/logger" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/spf13/cobra" ) @@ -35,10 +35,10 @@ Each feature runs in its own worktree with a full engine session. Results are committed on separate branches for review/merge. Examples: - hawk mission "Add auth, rate limiting, and logging to the API" - hawk mission --workers 6 "Refactor the database layer into 3 services" - hawk mission --model claude-sonnet-4-6 "Add tests for all untested packages" - hawk mission --from-tasks`, + rho mission "Add auth, rate limiting, and logging to the API" + rho mission --workers 6 "Refactor the database layer into 3 services" + rho mission --model claude-sonnet-4-6 "Add tests for all untested packages" + rho mission --from-tasks`, Args: cobra.ArbitraryArgs, RunE: runMission, } @@ -60,7 +60,7 @@ func runMission(_ *cobra.Command, args []string) error { cwd, _ := os.Getwd() baseBranch := getCurrentBranch(cwd) - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() effectiveModel, effectiveProvider := effectiveModelAndProvider(settings) if missionModel != "" { effectiveModel = missionModel @@ -78,7 +78,7 @@ func runMission(_ *cobra.Command, args []string) error { m := mission.New(prompt, cfg) // Clean up the mission's temp directory when the command finishes, - // whether it succeeded or failed. Without this, /tmp/hawk-missions/ + // whether it succeeded or failed. Without this, /tmp/rho-missions/ // accumulates one directory per run indefinitely (C6 fix). defer func() { _ = m.Cleanup() }() @@ -174,7 +174,7 @@ func runMission(_ *cobra.Command, args []string) error { // Propagate feature failures as a non-zero exit so CI sees a real // failure instead of green: Mission.Run historically returned nil even - // when every feature failed (H9), so `hawk mission` exited 0. + // when every feature failed (H9), so `rho mission` exited 0. failed := 0 for _, f := range m.Features { if f.Status == mission.FeatureFailed { @@ -188,7 +188,7 @@ func runMission(_ *cobra.Command, args []string) error { return nil } -func planWithLLM(ctx context.Context, prompt, provider, model string, settings hawkconfig.Settings) ([]mission.Feature, error) { +func planWithLLM(ctx context.Context, prompt, provider, model string, settings rhoconfig.Settings) ([]mission.Feature, error) { planPrompt := fmt.Sprintf( "Decompose this task into independent features that can be implemented in parallel.\n\n"+ "Task: %s\n\n"+ @@ -201,7 +201,7 @@ func planWithLLM(ctx context.Context, prompt, provider, model string, settings h ) registry, _ := defaultRegistry(settings) - sess, err := newConfiguredHawkSession(settings, provider, model, planPrompt, registry, logger.New(io.Discard, logger.Error)) + sess, err := newConfiguredRhoSession(settings, provider, model, planPrompt, registry, logger.New(io.Discard, logger.Error)) if err != nil { return nil, err } diff --git a/cmd/mission_graph.go b/cmd/mission_graph.go index 3f221b8cd..6b30c7918 100644 --- a/cmd/mission_graph.go +++ b/cmd/mission_graph.go @@ -6,8 +6,8 @@ import ( "strings" "time" - mission "github.com/GrayCodeAI/hawk/internal/multiagent" - "github.com/GrayCodeAI/hawk/internal/tool" + mission "github.com/GrayCodeAI/rho/internal/multiagent" + "github.com/GrayCodeAI/rho/internal/tool" ) var missionFromTasks bool @@ -57,7 +57,7 @@ func missionFeaturesFromTasks(store *tool.TaskStore, missionID string) ([]missio ID: task.ID, Description: task.Subject, ExpectedBehavior: expected, - Branch: fmt.Sprintf("hawk-mission/%s/%s", missionID, task.ID), + Branch: fmt.Sprintf("rho-mission/%s/%s", missionID, task.ID), Status: status, }) } diff --git a/cmd/mission_graph_test.go b/cmd/mission_graph_test.go index d9e204c9f..5df0550f7 100644 --- a/cmd/mission_graph_test.go +++ b/cmd/mission_graph_test.go @@ -3,8 +3,8 @@ package cmd import ( "testing" - mission "github.com/GrayCodeAI/hawk/internal/multiagent" - "github.com/GrayCodeAI/hawk/internal/tool" + mission "github.com/GrayCodeAI/rho/internal/multiagent" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestMissionFeaturesFromTasks(t *testing.T) { @@ -26,7 +26,7 @@ func TestMissionFeaturesFromTasks(t *testing.T) { if len(waves) != 2 || len(waves[0]) != 1 || waves[0][0] != root.ID || waves[1][0] != child.ID { t.Fatalf("waves = %#v", waves) } - if features[0].Branch != "hawk-mission/mission123/"+root.ID { + if features[0].Branch != "rho-mission/mission123/"+root.ID { t.Fatalf("branch = %q", features[0].Branch) } if features[0].Status != mission.FeaturePending { diff --git a/cmd/model_table.go b/cmd/model_table.go index 14f6884f2..242437a41 100644 --- a/cmd/model_table.go +++ b/cmd/model_table.go @@ -6,8 +6,8 @@ import ( "strings" lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/mattn/go-runewidth" ) @@ -119,17 +119,17 @@ func modelTableRowFromOption(o configModelOption) modelTableRow { } func formatModelThinkingCell(o configModelOption) string { - supports := hawkconfig.ModelCapabilitySupportsThinking(o.Capabilities) - settings := hawkconfig.LoadSettings() - pref := hawkconfig.ThinkingPrefForModel(settings, o.ID) + supports := rhoconfig.ModelCapabilitySupportsThinking(o.Capabilities) + settings := rhoconfig.LoadSettings() + pref := rhoconfig.ThinkingPrefForModel(settings, o.ID) if pref == nil && o.CanonicalID != "" && o.CanonicalID != o.ID { - pref = hawkconfig.ThinkingPrefForModel(settings, o.CanonicalID) + pref = rhoconfig.ThinkingPrefForModel(settings, o.CanonicalID) } provider := strings.TrimSpace(o.GatewayID) if provider == "" { provider = strings.TrimSpace(o.ProviderID) } - return hawkconfig.FormatModelThinkingLabel(supports, pref, provider) + return rhoconfig.FormatModelThinkingLabel(supports, pref, provider) } func formatModelCapabilities(capabilities []string) string { @@ -370,7 +370,7 @@ func modelTableFooter(total, scroll, end, allTotal int, muted lipgloss.Style) st return muted.Render(fmt.Sprintf("%s%s · t toggle thinking · enter to select", prefix, label)) } -func modelTableRowFromCatalogEntry(m hawkconfig.EngineModel) modelTableRow { +func modelTableRowFromCatalogEntry(m rhoconfig.EngineModel) modelTableRow { name := strings.TrimSpace(m.DisplayName) if name == "" { name = m.ID @@ -401,7 +401,7 @@ func modelTableRowFromCatalogEntry(m hawkconfig.EngineModel) modelTableRow { Model: name, Provider: owner, Caps: formatModelCapabilities(m.Capabilities), - Think: hawkconfig.FormatModelThinkingLabel(hawkconfig.ModelCapabilitySupportsThinking(m.Capabilities), hawkconfig.ThinkingPrefForModel(hawkconfig.LoadSettings(), m.ID), provider), + Think: rhoconfig.FormatModelThinkingLabel(rhoconfig.ModelCapabilitySupportsThinking(m.Capabilities), rhoconfig.ThinkingPrefForModel(rhoconfig.LoadSettings(), m.ID), provider), Price: price, Context: formatModelTableContext(m.ContextWindow), Free: free, diff --git a/cmd/model_table_test.go b/cmd/model_table_test.go index 6e482be4e..504a6c403 100644 --- a/cmd/model_table_test.go +++ b/cmd/model_table_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestFormatModelTablePrice(t *testing.T) { @@ -67,7 +67,7 @@ func TestFormatModelCapabilities(t *testing.T) { } func TestFormatModelThinkingCell(t *testing.T) { - // Isolate from the developer's real hawk settings (per-model thinking + // Isolate from the developer's real rho settings (per-model thinking // preferences would otherwise change the asserted defaults). t.Setenv("HOME", t.TempDir()) row := modelTableRowFromOption(configModelOption{ @@ -109,7 +109,7 @@ func TestModelTableOwnerFallback(t *testing.T) { if option.Provider != "deployment" { t.Fatalf("option provider = %q, want deployment", option.Provider) } - entry := modelTableRowFromCatalogEntry(hawkconfig.EngineModel{GatewayID: "gateway"}) + entry := modelTableRowFromCatalogEntry(rhoconfig.EngineModel{GatewayID: "gateway"}) if entry.Provider != "gateway" { t.Fatalf("entry provider = %q, want gateway", entry.Provider) } diff --git a/cmd/models.go b/cmd/models.go index 3b4c1f1e0..20b10e31d 100644 --- a/cmd/models.go +++ b/cmd/models.go @@ -7,7 +7,7 @@ import ( "strings" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" "github.com/spf13/cobra" ) @@ -20,11 +20,11 @@ var ( var modelsCmd = &cobra.Command{ Use: "models", Short: "Deployment-aware model catalog (via eyrie)", - Long: `Manage the eyrie model catalog used by hawk for models, pricing, and deployment routing. + Long: `Manage the eyrie model catalog used by rho for models, pricing, and deployment routing. The catalog is stored at ~/.eyrie/model_catalog.json (override with EYRIE_MODEL_CATALOG_PATH). -Hawk refreshes the catalog automatically on startup when the cache is missing, empty, or stale (disable with --no-auto-catalog-refresh or HAWK_AUTO_REFRESH_CATALOG=0). -Use 'hawk models refresh' for a manual refresh or full discover report.`, +Rho refreshes the catalog automatically on startup when the cache is missing, empty, or stale (disable with --no-auto-catalog-refresh or RHO_AUTO_REFRESH_CATALOG=0). +Use 'rho models refresh' for a manual refresh or full discover report.`, } var modelsRefreshCmd = &cobra.Command{ @@ -41,7 +41,7 @@ var modelsRefreshCmd = &cobra.Command{ prog := NewCLIProgress("Models", []string{"Discovering catalog"}) defer prog.Abort() prog.StartStep(0) - summary, err := hawkconfig.RefreshModelCatalogV1WithSettings(ctx, settings) + summary, err := rhoconfig.RefreshModelCatalogV1WithSettings(ctx, settings) if err != nil { prog.FailStep(0, err.Error()) return err @@ -62,13 +62,13 @@ var modelsStatusCmd = &cobra.Command{ if err != nil { return err } - cmd.Println(hawkconfig.FormatCatalogHealth(hawkconfig.CatalogHealthReport(ctx))) + cmd.Println(rhoconfig.FormatCatalogHealth(rhoconfig.CatalogHealthReport(ctx))) cmd.Println() modelName, _ := effectiveModelAndProvider(settings) if len(args) > 0 { modelName = args[0] } - report, err := hawkconfig.DeploymentStatusReportWithSettings(ctx, settings, modelName) + report, err := rhoconfig.DeploymentStatusReportWithSettings(ctx, settings, modelName) if err != nil { return err } @@ -87,7 +87,7 @@ var modelsRoutingPreviewCmd = &cobra.Command{ return err } modelName := args[0] - out, err := hawkconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, modelName) + out, err := rhoconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, modelName) if err != nil { return err } @@ -109,7 +109,7 @@ var modelsListCmd = &cobra.Command{ providerName = args[0] } ctx := cmd.Context() - var models []hawkconfig.EngineModel + var models []rhoconfig.EngineModel // Only the live provider fetch is slow enough to animate, and only when // the output is a human table (JSON/raw must stay pure). animate := modelsListLive && !modelsListJSON && !modelsListRaw @@ -121,11 +121,11 @@ var modelsListCmd = &cobra.Command{ } if modelsListLive { if providerName == "" { - return fmt.Errorf("provider required with --live (e.g. hawk models list canopywave --live --json)") + return fmt.Errorf("provider required with --live (e.g. rho models list canopywave --live --json)") } - models, err = hawkconfig.ListLiveEngineModelsWithSettings(ctx, settings, hawkconfig.ActiveProviderID(providerName)) + models, err = rhoconfig.ListLiveEngineModelsWithSettings(ctx, settings, rhoconfig.ActiveProviderID(providerName)) } else { - models, err = hawkconfig.FetchModelsForProviderWithSettings(ctx, settings, providerName) + models, err = rhoconfig.FetchModelsForProviderWithSettings(ctx, settings, providerName) } if err != nil { if prog != nil { @@ -159,9 +159,9 @@ var modelsListCmd = &cobra.Command{ }, } -// modelListJSONEntry is Hawk's versioned command-output contract. Keep this +// modelListJSONEntry is Rho's versioned command-output contract. Keep this // separate from Eyrie's host-facing Model DTO so engine-only fields can evolve -// without breaking users that consume `hawk models list --json`. +// without breaking users that consume `rho models list --json`. type modelListJSONEntry struct { ID string `json:"id"` InputPricePer1M float64 `json:"input_price_per_1m"` @@ -175,7 +175,7 @@ type modelListJSONEntry struct { LiveMetadata json.RawMessage `json:"live_metadata,omitempty"` } -func modelListJSONEntryFromEngine(m hawkconfig.EngineModel) modelListJSONEntry { +func modelListJSONEntryFromEngine(m rhoconfig.EngineModel) modelListJSONEntry { return modelListJSONEntry{ ID: m.ID, InputPricePer1M: m.InputPricePer1M, @@ -198,7 +198,7 @@ func validModelLiveMetadata(raw json.RawMessage) json.RawMessage { return append(json.RawMessage(nil), metadata...) } -func marshalModelListJSON(models []hawkconfig.EngineModel, rawOnly, live bool) ([]byte, error) { +func marshalModelListJSON(models []rhoconfig.EngineModel, rawOnly, live bool) ([]byte, error) { entries := make([]modelListJSONEntry, len(models)) for i, model := range models { entries[i] = modelListJSONEntryFromEngine(model) diff --git a/cmd/models_test.go b/cmd/models_test.go index 3d8967567..fb15b2984 100644 --- a/cmd/models_test.go +++ b/cmd/models_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestMarshalModelListJSONCompatibilityGolden(t *testing.T) { @@ -72,7 +72,7 @@ func TestMarshalModelListJSONCompatibilityGolden(t *testing.T) { } func TestModelListJSONEntryFromEnginePreservesLegacyFieldMapping(t *testing.T) { - model := hawkconfig.EngineModel{ + model := rhoconfig.EngineModel{ ID: "vendor/model", DisplayName: "Model", Description: "Description", @@ -97,7 +97,7 @@ func TestModelListJSONEntryFromEnginePreservesLegacyFieldMapping(t *testing.T) { t.Fatalf("ServerTools = %v, want %v", entry.ServerTools, model.Capabilities) } - out, err := marshalModelListJSON([]hawkconfig.EngineModel{model}, false, false) + out, err := marshalModelListJSON([]rhoconfig.EngineModel{model}, false, false) if err != nil { t.Fatalf("marshalModelListJSON() error = %v", err) } diff --git a/cmd/notifications.go b/cmd/notifications.go index 95ebd81af..c438c6260 100644 --- a/cmd/notifications.go +++ b/cmd/notifications.go @@ -15,7 +15,7 @@ func notifyCompletion(duration time.Duration) { return } - msg := "Hawk query completed" + msg := "Rho query completed" switch runtime.GOOS { case "darwin": @@ -23,10 +23,10 @@ func notifyCompletion(duration time.Duration) { _ = exec.CommandContext( context.Background(), "osascript", "-e", - `display notification "`+msg+`" with title "Hawk"`, + `display notification "`+msg+`" with title "Rho"`, ).Start() case "linux": // Linux: use notify-send if available - _ = exec.CommandContext(context.Background(), "notify-send", "Hawk", msg).Start() + _ = exec.CommandContext(context.Background(), "notify-send", "Rho", msg).Start() } } diff --git a/cmd/notify.go b/cmd/notify.go index 781140b83..5f673310e 100644 --- a/cmd/notify.go +++ b/cmd/notify.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // Notification represents a single notification event. @@ -23,7 +23,7 @@ type Notification struct { Read bool } -// Notifier manages terminal notifications for hawk events. +// Notifier manages terminal notifications for rho events. type Notifier struct { Enabled bool Level string // "all", "important", "critical" @@ -151,7 +151,7 @@ $textNodes = $template.GetElementsByTagName('text') $textNodes.Item(0).AppendChild($template.CreateTextNode('%s')) | Out-Null $textNodes.Item(1).AppendChild($template.CreateTextNode('%s')) | Out-Null $toast = [Windows.UI.Notifications.ToastNotification]::new($template) -[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('hawk').Show($toast)`, +[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('rho').Show($toast)`, escapePowerShell(title), escapePowerShell(message)) cmd := exec.CommandContext(context.Background(), "powershell", "-Command", script) // #nosec G204 -- fixed command 'powershell'; script built from escaped internal strings return cmd.Run() diff --git a/cmd/notify_test.go b/cmd/notify_test.go index 9a5f0a078..b7cfc982a 100644 --- a/cmd/notify_test.go +++ b/cmd/notify_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestNewNotifier(t *testing.T) { @@ -253,7 +253,7 @@ func TestSetTerminalTitle(t *testing.T) { n.Desktop = false // Just verify it doesn't panic - n.SetTerminalTitle("hawk: running") + n.SetTerminalTitle("rho: running") n.ClearTitle() } diff --git a/cmd/ocg_live_test.go b/cmd/ocg_live_test.go index 2ec04ee9e..09a6bec71 100644 --- a/cmd/ocg_live_test.go +++ b/cmd/ocg_live_test.go @@ -4,7 +4,7 @@ // Live integration test for the OpenCodeGo provider adapter end-to-end. // Opt-in only — not run by default `go test ./...` or CI. Run with: // -// OPENCODEGO_API_KEY=... go test -tags=live_test -run TestLiveOpenCodeGoMiniMaxM3FullHawkPath ./cmd +// OPENCODEGO_API_KEY=... go test -tags=live_test -run TestLiveOpenCodeGoMiniMaxM3FullRhoPath ./cmd // // Or via `make test-live` in this repo. package cmd @@ -18,12 +18,12 @@ import ( eyriecfg "github.com/GrayCodeAI/eyrie/config" "github.com/GrayCodeAI/eyrie/credentials" "github.com/GrayCodeAI/eyrie/setup" - "github.com/GrayCodeAI/hawk/internal/observability/logger" + "github.com/GrayCodeAI/rho/internal/observability/logger" ) -func TestLiveOpenCodeGoMiniMaxM3FullHawkPath(t *testing.T) { +func TestLiveOpenCodeGoMiniMaxM3FullRhoPath(t *testing.T) { if credentials.LookupSecret(context.Background(), "OPENCODEGO_API_KEY") == "" { - t.Skip("OPENCODEGO_API_KEY not configured") // TODO: https://github.com/GrayCodeAI/hawk/issues/29 + t.Skip("OPENCODEGO_API_KEY not configured") // TODO: https://github.com/GrayCodeAI/rho/issues/29 } settings, err := loadEffectiveSettings() if err != nil { @@ -43,7 +43,7 @@ func TestLiveOpenCodeGoMiniMaxM3FullHawkPath(t *testing.T) { adapter := setup.ConfiguredDeploymentAdapters(eyriecfg.LoadProviderConfig(""))["opencodego"] t.Logf("adapter_type=%T", adapter.Provider) - sess := newHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry) + sess := newRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry) sess.SetLogger(logger.New(ioDiscard{}, logger.Info)) if cfgErr := configureSession(sess, settings); cfgErr != nil { t.Fatal(cfgErr) diff --git a/cmd/options.go b/cmd/options.go index f1861a0af..66dc44bbe 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -9,19 +9,19 @@ import ( "os" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - ctxrepomap "github.com/GrayCodeAI/hawk/internal/context/repomap" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/engine/branching" - "github.com/GrayCodeAI/hawk/internal/engine/lifecycle" - "github.com/GrayCodeAI/hawk/internal/intelligence/memory" - "github.com/GrayCodeAI/hawk/internal/intelligence/repomap" - "github.com/GrayCodeAI/hawk/internal/observability/logger" - "github.com/GrayCodeAI/hawk/internal/prompt" - "github.com/GrayCodeAI/hawk/internal/prompts" - hawkmodel "github.com/GrayCodeAI/hawk/internal/provider/routing" - "github.com/GrayCodeAI/hawk/internal/snapshot" - "github.com/GrayCodeAI/hawk/internal/tool" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + ctxrepomap "github.com/GrayCodeAI/rho/internal/context/repomap" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine/branching" + "github.com/GrayCodeAI/rho/internal/engine/lifecycle" + "github.com/GrayCodeAI/rho/internal/intelligence/memory" + "github.com/GrayCodeAI/rho/internal/intelligence/repomap" + "github.com/GrayCodeAI/rho/internal/observability/logger" + "github.com/GrayCodeAI/rho/internal/prompt" + "github.com/GrayCodeAI/rho/internal/prompts" + rhomodel "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/snapshot" + "github.com/GrayCodeAI/rho/internal/tool" ) func buildSystemPrompt() (string, error) { @@ -69,7 +69,7 @@ func buildSystemPromptWithOptions(includeWorkspaceContext, includeRepoMap bool) modularPrompt = "" } - base := prompt.System() + "\n\n" + hawkconfig.BuildStartupContextWithDirs(addDirs) + base := prompt.System() + "\n\n" + rhoconfig.BuildStartupContextWithDirs(addDirs) if modularPrompt != "" { base += "\n\n" + modularPrompt } @@ -119,7 +119,7 @@ func buildDeferredWorkspacePromptContext() string { return "" } var sections []string - if deferred := strings.TrimSpace(hawkconfig.BuildDeferredContextWithDirs(addDirs)); deferred != "" { + if deferred := strings.TrimSpace(rhoconfig.BuildDeferredContextWithDirs(addDirs)); deferred != "" { sections = append(sections, deferred) } if ws := prompts.GatherWorkspaceContext(cwd); ws != nil { @@ -156,7 +156,7 @@ func injectRepoMap(base string) string { return base } - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() if settings.RepoMap == nil || !*settings.RepoMap { return base } @@ -182,43 +182,43 @@ func injectRepoMap(base string) string { return base + "\n\n# Repository Map\n" + formatted } -func loadEffectiveSettings() (hawkconfig.Settings, error) { - return hawkconfig.LoadSettingsWithOverride(settingsFlag) +func loadEffectiveSettings() (rhoconfig.Settings, error) { + return rhoconfig.LoadSettingsWithOverride(settingsFlag) } -func resolveSelection(settings hawkconfig.Settings) hawkconfig.Selection { - return hawkconfig.EffectiveSelectionWithSettings(context.Background(), settings, hawkconfig.SelectionOptions{ +func resolveSelection(settings rhoconfig.Settings) rhoconfig.Selection { + return rhoconfig.EffectiveSelectionWithSettings(context.Background(), settings, rhoconfig.SelectionOptions{ ProviderOverride: firstNonEmptyTrimmed(provider, settings.Provider), ModelOverride: firstNonEmptyTrimmed(model, settings.Model), }) } -func startupSelection(settings hawkconfig.Settings) hawkconfig.Selection { +func startupSelection(settings rhoconfig.Settings) rhoconfig.Selection { providerOverride := firstNonEmptyTrimmed(provider, settings.Provider) modelOverride := firstNonEmptyTrimmed(model, settings.Model) explicitProvider, explicitModel := explicitSelection(context.Background()) - providerID := hawkconfig.ActiveProviderID(firstNonEmptyTrimmed(providerOverride, explicitProvider)) + providerID := rhoconfig.ActiveProviderID(firstNonEmptyTrimmed(providerOverride, explicitProvider)) modelID := strings.TrimSpace(firstNonEmptyTrimmed(modelOverride, explicitModel)) if providerID == "" && modelID != "" { - providerID = hawkconfig.ActiveProviderID(hawkconfig.ProviderOfModelWithSettings(settings, modelID)) + providerID = rhoconfig.ActiveProviderID(rhoconfig.ProviderOfModelWithSettings(settings, modelID)) } if modelID == "" && providerID != "" { - modelID = strings.TrimSpace(hawkconfig.DefaultModelForProviderWithSettings(settings, providerID)) + modelID = strings.TrimSpace(rhoconfig.DefaultModelForProviderWithSettings(settings, providerID)) } if providerID == "" { providerID = startupPlaceholderProvider } - return hawkconfig.Selection{ + return rhoconfig.Selection{ Provider: providerID, Model: modelID, } } -func effectiveModelAndProvider(settings hawkconfig.Settings) (string, string) { +func effectiveModelAndProvider(settings rhoconfig.Settings) (string, string) { selection := resolveSelection(settings) if !selection.HasConfiguredDeployment { return "", "" @@ -226,7 +226,7 @@ func effectiveModelAndProvider(settings hawkconfig.Settings) (string, string) { return selection.Model, selection.Provider } -func newStartupHawkSession(selection hawkconfig.Selection, systemPrompt string, registry *tool.Registry) *engine.Session { +func newStartupRhoSession(selection rhoconfig.Selection, systemPrompt string, registry *tool.Registry) *engine.Session { providerID := strings.TrimSpace(selection.Provider) if providerID == "" { providerID = startupPlaceholderProvider @@ -234,7 +234,7 @@ func newStartupHawkSession(selection hawkconfig.Selection, systemPrompt string, return engine.NewSession(providerID, strings.TrimSpace(selection.Model), systemPrompt, registry) } -func newHawkSession(settings hawkconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry) *engine.Session { +func newRhoSession(settings rhoconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry) *engine.Session { selection := resolveSelection(settings) if strings.TrimSpace(selection.Provider) == "" { selection.Provider = effectiveProvider @@ -242,15 +242,15 @@ func newHawkSession(settings hawkconfig.Settings, effectiveProvider, effectiveMo if strings.TrimSpace(selection.Model) == "" { selection.Model = effectiveModel } - sess := engine.NewHawkSessionForSettings(context.Background(), settings, selection, selection.Provider, selection.Model, systemPrompt, registry) + sess := engine.NewRhoSessionForSettings(context.Background(), settings, selection, selection.Provider, selection.Model, systemPrompt, registry) return sess } -// newConfiguredHawkSession is the non-interactive command composition root. +// newConfiguredRhoSession is the non-interactive command composition root. // Interactive chat intentionally keeps its lightweight startup and deferred // heavy configuration split; batch/daemon/ACP callers use this atomic path. -func newConfiguredHawkSession(settings hawkconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry, sessionLogger *logger.Logger, maxTurnsOverride ...int) (*engine.Session, error) { - sess := newHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry) +func newConfiguredRhoSession(settings rhoconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry, sessionLogger *logger.Logger, maxTurnsOverride ...int) (*engine.Session, error) { + sess := newRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry) if sessionLogger != nil { sess.SetLogger(sessionLogger) } @@ -260,11 +260,11 @@ func newConfiguredHawkSession(settings hawkconfig.Settings, effectiveProvider, e return sess, nil } -// newConfiguredHawkSessionFactory is the shared composition seam for +// newConfiguredRhoSessionFactory is the shared composition seam for // non-interactive protocol/server entry points. It owns registry creation and // settings-based model selection while allowing each protocol to provide its // own prompt and optional model override. -func newConfiguredHawkSessionFactory(settings hawkconfig.Settings, sessionLogger *logger.Logger) func(string, string, ...int) (*engine.Session, error) { +func newConfiguredRhoSessionFactory(settings rhoconfig.Settings, sessionLogger *logger.Logger) func(string, string, ...int) (*engine.Session, error) { return func(systemPrompt, modelOverride string, maxTurnsOverride ...int) (*engine.Session, error) { registry, err := defaultRegistry(settings) if err != nil { @@ -274,14 +274,14 @@ func newConfiguredHawkSessionFactory(settings hawkconfig.Settings, sessionLogger if strings.TrimSpace(modelOverride) != "" { effectiveModel = modelOverride } - return newConfiguredHawkSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, sessionLogger, maxTurnsOverride...) + return newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, sessionLogger, maxTurnsOverride...) } } // prepareInteractiveSessionStartup applies only the cheap TUI startup slice. // Transport rebuild and heavy memory setup remain deferred until the first // real chat request in bootstrapSessionForChat. -func prepareInteractiveSessionStartup(sess *engine.Session, settings hawkconfig.Settings) error { +func prepareInteractiveSessionStartup(sess *engine.Session, settings rhoconfig.Settings) error { syncSessionFromPersistedSelection(sess) sess.SetLogger(logger.New(io.Discard, logger.Error)) return configureSessionStartup(sess, settings) @@ -296,7 +296,7 @@ func firstNonEmptyTrimmed(values ...string) string { return "" } -func configureSession(sess *engine.Session, settings hawkconfig.Settings, maxTurnsOverride ...int) error { +func configureSession(sess *engine.Session, settings rhoconfig.Settings, maxTurnsOverride ...int) error { if err := configureSessionStartup(sess, settings, maxTurnsOverride...); err != nil { return err } @@ -304,7 +304,7 @@ func configureSession(sess *engine.Session, settings hawkconfig.Settings, maxTur return nil } -func configureSessionStartup(sess *engine.Session, settings hawkconfig.Settings, maxTurnsOverride ...int) error { +func configureSessionStartup(sess *engine.Session, settings rhoconfig.Settings, maxTurnsOverride ...int) error { sess.WireAgentTool() sess.SetAllowedDirs(addDirs) _ = sess.SetWorkMode(engine.WorkModeAct) @@ -357,7 +357,7 @@ func configureSessionStartup(sess *engine.Session, settings hawkconfig.Settings, } // Model cascade router: automatically routes tasks to optimal model tier - roles := hawkmodel.DefaultRoles(sess.Model()) + roles := rhomodel.DefaultRoles(sess.Model()) if settings.ModelRoles != nil { roles = *settings.ModelRoles } @@ -405,7 +405,7 @@ func configureSessionStartup(sess *engine.Session, settings hawkconfig.Settings, // provider-specific defaults (e.g. LongCat off). modelID := strings.TrimSpace(sess.Model()) providerID := strings.TrimSpace(sess.Provider()) - sess.SetThinkingEnabled(hawkconfig.ResolveThinkingForModel(settings, modelID, providerID)) + sess.SetThinkingEnabled(rhoconfig.ResolveThinkingForModel(settings, modelID, providerID)) return nil } @@ -441,8 +441,8 @@ func bindChatSession(sess *engine.Session, sessionID string) { } func validateRootFlags() error { - if outputFormat != "text" && outputFormat != "json" && outputFormat != "stream-json" { - return fmt.Errorf("--output-format must be one of: text, json, stream-json") + if outputFormat != "text" && outputFormat != "json" && outputFormat != "stream-json" && outputFormat != "transcript" { + return fmt.Errorf("--output-format must be one of: text, json, stream-json, transcript") } if inputFormat != "text" && inputFormat != "stream-json" { return fmt.Errorf("--input-format must be one of: text, stream-json") diff --git a/cmd/options_test.go b/cmd/options_test.go index 8b3ad56b0..a17908826 100644 --- a/cmd/options_test.go +++ b/cmd/options_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestParseToolListFromCLI(t *testing.T) { diff --git a/cmd/options_welcome_test.go b/cmd/options_welcome_test.go index b003a1d84..36fb48320 100644 --- a/cmd/options_welcome_test.go +++ b/cmd/options_welcome_test.go @@ -7,65 +7,65 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func isolateCredentialHome(t *testing.T) { t.Helper() home := t.TempDir() - hawkDir := filepath.Join(home, ".hawk") - _ = os.MkdirAll(hawkDir, 0o700) + rhoDir := filepath.Join(home, ".rho") + _ = os.MkdirAll(rhoDir, 0o700) t.Setenv("HOME", home) - t.Setenv("HAWK_CONFIG_DIR", hawkDir) + t.Setenv("RHO_CONFIG_DIR", rhoDir) t.Setenv("EYRIE_CONFIG_DIR", filepath.Join(home, "eyrie")) } func TestEffectiveModelAndProvider_ClearsWithoutCredentials(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() - if err := hawkconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + if err := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := hawkconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } - model, provider := effectiveModelAndProvider(hawkconfig.Settings{}) + model, provider := effectiveModelAndProvider(rhoconfig.Settings{}) if model != "" || provider != "" { t.Fatalf("expected empty selection without credentials, got model=%q provider=%q", model, provider) } } func TestEffectiveModelAndProvider_KeepsWithCredentials(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - hawkconfig.InvalidateConfigUICache() - if err := hawkconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + rhoconfig.InvalidateConfigUICache() + if err := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := hawkconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } - model, provider := effectiveModelAndProvider(hawkconfig.Settings{}) + model, provider := effectiveModelAndProvider(rhoconfig.Settings{}) if provider == "" { t.Fatalf("expected provider with credentials, got model=%q provider=%q", model, provider) } diff --git a/cmd/pager.go b/cmd/pager.go index a923979a6..ae1d3df91 100644 --- a/cmd/pager.go +++ b/cmd/pager.go @@ -95,7 +95,7 @@ func StopPager() { // Returns "" if paging should be disabled. func resolvePager() string { // Disable paging via environment. - if v := os.Getenv("HAWK_PAGER"); v != "" { + if v := os.Getenv("RHO_PAGER"); v != "" { if v == "cat" || v == "none" { return "" } diff --git a/cmd/path.go b/cmd/path.go index 03c3ee6d1..b6a3f35ac 100644 --- a/cmd/path.go +++ b/cmd/path.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" "github.com/spf13/cobra" ) @@ -17,7 +17,7 @@ var ( var pathCmd = &cobra.Command{ Use: "path", Short: "Developer path readiness (setup, security, ecosystem)", - Long: `Check whether hawk is configured on the developer path: + Long: `Check whether rho is configured on the developer path: API keys in OS secret store, model selected, no secrets on disk, and eyrie integration. @@ -26,7 +26,7 @@ Built for individual developers first — teams and enterprise later. See docs/DEVELOPER-PATH.md and docs/SECURITY-DEVELOPER.md.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - report := hawkconfig.EvaluateDeveloperPath(ctx) + report := rhoconfig.EvaluateDeveloperPath(ctx) if pathJSON { enc := json.NewEncoder(cmd.OutOrStdout()) @@ -34,7 +34,7 @@ See docs/DEVELOPER-PATH.md and docs/SECURITY-DEVELOPER.md.`, return enc.Encode(report) } - cmd.Println(hawkconfig.FormatDeveloperPathReport(ctx)) + cmd.Println(rhoconfig.FormatDeveloperPathReport(ctx)) if !report.Ready { return fmt.Errorf("developer path not ready — %s", report.NextStep) diff --git a/cmd/path_test.go b/cmd/path_test.go index ae0d3dc8a..48a1dbda0 100644 --- a/cmd/path_test.go +++ b/cmd/path_test.go @@ -5,20 +5,20 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestPathCmdRuns(t *testing.T) { useInMemoryCredentials(t) if err := pathCmd.RunE(pathCmd, nil); err == nil { - t.Skip("machine has full developer path setup") // TODO: https://github.com/GrayCodeAI/hawk/issues/30 + t.Skip("machine has full developer path setup") // TODO: https://github.com/GrayCodeAI/rho/issues/30 } } func TestPathCmdPrintsReport(t *testing.T) { useInMemoryCredentials(t) - out := hawkconfig.FormatDeveloperPathReport(context.Background()) + out := rhoconfig.FormatDeveloperPathReport(context.Background()) if !strings.Contains(out, "Developer path") { t.Fatalf("unexpected output: %s", out) } diff --git a/cmd/permissions.go b/cmd/permissions.go index 505ebfce3..653440032 100644 --- a/cmd/permissions.go +++ b/cmd/permissions.go @@ -7,8 +7,8 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/hawk/internal/permissions" - "github.com/GrayCodeAI/hawk/internal/permissions/stableid" + "github.com/GrayCodeAI/rho/internal/permissions" + "github.com/GrayCodeAI/rho/internal/permissions/stableid" "github.com/spf13/cobra" ) diff --git a/cmd/permissions_center.go b/cmd/permissions_center.go index 664021e14..884628275 100644 --- a/cmd/permissions_center.go +++ b/cmd/permissions_center.go @@ -7,8 +7,8 @@ import ( "time" tea "charm.land/bubbletea/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" ) func normalizePermissionTier(raw string) (engine.AutonomyLevel, string, bool) { @@ -264,7 +264,7 @@ func permissionRulesSummary(m *chatModel) string { return strings.TrimRight(b.String(), "\n") } -func effectiveAllowRules(settings hawkconfig.Settings) []string { +func effectiveAllowRules(settings rhoconfig.Settings) []string { var rules []string rules = append(rules, settings.AutoAllow...) rules = append(rules, settings.AllowedTools...) @@ -272,7 +272,7 @@ func effectiveAllowRules(settings hawkconfig.Settings) []string { return dedupeStrings(rules) } -func effectiveDenyRules(settings hawkconfig.Settings) []string { +func effectiveDenyRules(settings rhoconfig.Settings) []string { rules := append([]string{}, settings.DisallowedTools...) rules = append(rules, parseToolListFromCLI(disallowedToolsFlag)...) return dedupeStrings(rules) @@ -292,7 +292,7 @@ func dedupeStrings(values []string) []string { return out } -func rebuildSessionPermissionRules(sess *engine.Session, settings hawkconfig.Settings) { +func rebuildSessionPermissionRules(sess *engine.Session, settings rhoconfig.Settings) { if sess == nil { return } @@ -323,7 +323,7 @@ func rebuildSessionPermissionRules(sess *engine.Session, settings hawkconfig.Set } } -func savePermissionSettings(scope string, settings hawkconfig.Settings, level engine.AutonomyLevel) (string, error) { +func savePermissionSettings(scope string, settings rhoconfig.Settings, level engine.AutonomyLevel) (string, error) { scope = strings.ToLower(strings.TrimSpace(scope)) if scope == "" { scope = "global" @@ -337,13 +337,13 @@ func savePermissionSettings(scope string, settings hawkconfig.Settings, level en case "project": return "", fmt.Errorf("project-local settings writes are disabled; use scope \"global\" or an explicit --settings file") case "global": - target := hawkconfig.LoadGlobalSettings() + target := rhoconfig.LoadGlobalSettings() target.AutoAllow = append([]string{}, settings.AutoAllow...) target.AllowedTools = append([]string{}, settings.AllowedTools...) target.DisallowedTools = append([]string{}, settings.DisallowedTools...) target.Autonomy = settings.Autonomy target.AutonomyExplicit = true - if err := hawkconfig.SaveGlobal(target); err != nil { + if err := rhoconfig.SaveGlobal(target); err != nil { return "", err } return "user settings", nil diff --git a/cmd/permissions_center_test.go b/cmd/permissions_center_test.go index 9c6c4bd6d..67e726963 100644 --- a/cmd/permissions_center_test.go +++ b/cmd/permissions_center_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestNormalizePermissionTier(t *testing.T) { @@ -34,7 +34,7 @@ func TestNormalizePermissionSandbox(t *testing.T) { } func TestEffectivePermissionRules(t *testing.T) { - settings := hawkconfig.Settings{ + settings := rhoconfig.Settings{ AutoAllow: []string{"Read"}, AllowedTools: []string{"Bash(git:*)", "Read"}, DisallowedTools: []string{"Bash(rm -rf *)"}, @@ -55,7 +55,7 @@ func TestAutonomyCenterSummary(t *testing.T) { sess.PermSvc().SetSpecStage(engine.SpecStageSpecify) model := &chatModel{ session: sess, - settings: hawkconfig.Settings{ + settings: rhoconfig.Settings{ AllowedTools: []string{"Bash(git:*)"}, DisallowedTools: []string{"Bash(rm -rf *)"}, }, @@ -137,7 +137,7 @@ func TestResetPermissionCenter(t *testing.T) { sess.PermSvc().SetDryRun(true) model := &chatModel{ session: sess, - settings: hawkconfig.Settings{ + settings: rhoconfig.Settings{ Autonomy: permissionTierSettingValue(engine.AutonomyYOLO), AutoAllow: []string{"Read"}, AllowedTools: []string{"Bash(git:*)"}, diff --git a/cmd/plan.go b/cmd/plan.go index 8390e08da..7a1731225 100644 --- a/cmd/plan.go +++ b/cmd/plan.go @@ -8,8 +8,8 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/hawk/internal/intelligence/planner" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/intelligence/planner" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) @@ -19,7 +19,7 @@ var planCmd = &cobra.Command{ Use: "plan", Short: "Create and manage structured development plans", Long: `plan helps you create, list, and track structured development plans. -Plans are stored in Hawk's user state directory, partitioned by project. +Plans are stored in Rho's user state directory, partitioned by project. Subcommands: create Create a new plan (generates a plan prompt) @@ -38,13 +38,13 @@ var planCreateCmd = &cobra.Command{ // Generate the plan prompt (would normally be sent to an LLM). prompt := planner.Generate(description, "") cmd.Println(auditTint("Plan prompt generated. Send this to an LLM to produce a plan:", textMuted)) - cmd.Println(auditTint("--- System ---", hawkColor)) + cmd.Println(auditTint("--- System ---", rhoColor)) cmd.Println(prompt.System) cmd.Println() - cmd.Println(auditTint("--- User ---", hawkColor)) + cmd.Println(auditTint("--- User ---", rhoColor)) cmd.Println(prompt.User) cmd.Println() - cmd.Println(auditTint("Once you have the LLM response, save it with an explicit output path or import it into Hawk plans.", textMuted)) + cmd.Println(auditTint("Once you have the LLM response, save it with an explicit output path or import it into Rho plans.", textMuted)) return nil }, } @@ -60,7 +60,7 @@ var planListCmd = &cobra.Command{ if planJSON { fmt.Println("[]") } else { - cmd.Println(auditTint("No plans found. Create one with: hawk plan create ", textMuted)) + cmd.Println(auditTint("No plans found. Create one with: rho plan create ", textMuted)) } return nil } @@ -92,7 +92,7 @@ var planListCmd = &cobra.Command{ } if len(plans) == 0 { - cmd.Println(auditTint("No plans found. Create one with: hawk plan create ", textMuted)) + cmd.Println(auditTint("No plans found. Create one with: rho plan create ", textMuted)) return nil } @@ -147,10 +147,10 @@ var planDoneCmd = &cobra.Command{ Use: "done ", Short: "Mark a task as completed in the most recent plan", Long: `Mark a task as done by its numeric ID. This operates on the most -recently modified plan in Hawk's user state directory for the current project. +recently modified plan in Rho's user state directory for the current project. To target a specific plan, set the plan name as the first argument -followed by the task ID: hawk plan done `, +followed by the task ID: rho plan done `, Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { var planPath string @@ -204,7 +204,7 @@ func init() { planShowCmd.Flags().BoolVar(&planJSON, "json", false, "output plan as JSON") } -// resolvePlanPath converts a plan name to a file path in Hawk's user state dir. +// resolvePlanPath converts a plan name to a file path in Rho's user state dir. func resolvePlanPath(name string) string { plansDir := currentProjectPlansDir() // If the name already has a .json extension, use it directly. diff --git a/cmd/plugin_dynamic.go b/cmd/plugin_dynamic.go index 325fde2f5..f40bbb6a8 100644 --- a/cmd/plugin_dynamic.go +++ b/cmd/plugin_dynamic.go @@ -8,8 +8,8 @@ import ( "path/filepath" "time" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/theme" "github.com/spf13/cobra" ) @@ -90,7 +90,7 @@ var pluginStatusCmd = &cobra.Command{ statuses := dm.Status() if len(statuses) == 0 { - cmd.Println(auditTint("No plugins discovered. Run 'hawk plugin install' to add plugins.", textMuted)) + cmd.Println(auditTint("No plugins discovered. Run 'rho plugin install' to add plugins.", textMuted)) return nil } @@ -209,7 +209,7 @@ var pluginCreateCmd = &cobra.Command{ manifest := &plugin.ManifestV2{ Name: name, Version: "0.1.0", - Description: fmt.Sprintf("A hawk plugin: %s", name), + Description: fmt.Sprintf("A rho plugin: %s", name), Author: "", Mode: "subprocess", Tools: []plugin.ManifestTool{ @@ -281,12 +281,12 @@ func main() { // Write README.md readme := fmt.Sprintf(`# %s -A hawk plugin. +A rho plugin. ## Installation `+"```bash"+` -hawk plugin install ./%s +rho plugin install ./%s `+"```"+` ## Usage @@ -339,8 +339,8 @@ See `+"`plugin.json`"+` for the full manifest configuration. cmd.Println() cmd.Printf("%s\n", auditTint("Next steps:", textPrimary)) cmd.Printf("%s\n", auditTint(" cd "+name+" && go mod init "+name, textMuted)) - cmd.Printf("%s\n", auditTint(" hawk plugin install ./"+name, textMuted)) - cmd.Printf("%s\n", auditTint(" hawk plugin activate "+name, textMuted)) + cmd.Printf("%s\n", auditTint(" rho plugin install ./"+name, textMuted)) + cmd.Printf("%s\n", auditTint(" rho plugin activate "+name, textMuted)) return nil }, } @@ -422,13 +422,13 @@ var pluginMarketplaceListCmd = &cobra.Command{ entries, err := mc.FetchAll() if err != nil { prog.Abort() - return fmt.Errorf("fetch marketplace: %w (indexes may be unpublished; add a source with hawk plugin marketplace add)", err) + return fmt.Errorf("fetch marketplace: %w (indexes may be unpublished; add a source with rho plugin marketplace add)", err) } prog.CompleteStep(0) prog.Done() if len(entries) == 0 { cmd.Println(auditTint("No marketplace plugins found.", textMuted)) - cmd.Println(auditTint("Add a source: hawk plugin marketplace add ", textMuted)) + cmd.Println(auditTint("Add a source: rho plugin marketplace add ", textMuted)) return nil } rows := make([][]string, 0, len(entries)) diff --git a/cmd/power.go b/cmd/power.go index 178d5b04f..7bcfe672e 100644 --- a/cmd/power.go +++ b/cmd/power.go @@ -3,8 +3,8 @@ package cmd import ( "fmt" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) // PowerConfig maps a power level (1-10) to all relevant settings. diff --git a/cmd/pr.go b/cmd/pr.go index 060d406b3..d8f3d1e21 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -30,10 +30,10 @@ Subcommands: describe Generate or update a PR description Examples: - hawk pr review - hawk pr review 42 --post-comments - hawk pr create --base develop --draft - hawk pr describe 42 --update`, + rho pr review + rho pr review 42 --post-comments + rho pr create --base develop --draft + rho pr describe 42 --update`, } var prReviewCmd = &cobra.Command{ diff --git a/cmd/preflight_test.go b/cmd/preflight_test.go index c1479d617..bd173c719 100644 --- a/cmd/preflight_test.go +++ b/cmd/preflight_test.go @@ -23,7 +23,7 @@ type preflightCheckShape struct { func TestPreflightJSON_Structure(t *testing.T) { dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("RHO_STATE_DIR", filepath.Join(dir, "state")) old := preflightJSON oldLive := preflightLiveFlag @@ -59,7 +59,7 @@ func TestPreflightJSON_Structure(t *testing.T) { func TestPreflightText_NotJSON(t *testing.T) { dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("RHO_STATE_DIR", filepath.Join(dir, "state")) old := preflightJSON oldLive := preflightLiveFlag diff --git a/cmd/progress_cli.go b/cmd/progress_cli.go index 1895c4fa6..767c524c5 100644 --- a/cmd/progress_cli.go +++ b/cmd/progress_cli.go @@ -9,7 +9,7 @@ import ( "time" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // CLIProgress renders a ProgressTracker as live single-line progress for @@ -36,7 +36,7 @@ func newCLIProgress(title string, steps []string, w io.Writer, tty bool) *CLIPro for _, s := range steps { pt.AddStep(s) } - return &CLIProgress{w: w, pt: pt, spinner: NewBrailleSpinner(SpinnerHawk, ""), tty: tty} + return &CLIProgress{w: w, pt: pt, spinner: NewBrailleSpinner(SpinnerRho, ""), tty: tty} } // StartStep marks step i active and, on a TTY, starts animating it in place. @@ -47,7 +47,7 @@ func (c *CLIProgress) StartStep(i int) { if IsQuiet() || !c.tty || i < 0 || i >= len(c.pt.Steps) { return } - c.spinner = NewBrailleSpinner(SpinnerHawk, c.pt.Steps[i].Name) + c.spinner = NewBrailleSpinner(SpinnerRho, c.pt.Steps[i].Name) c.spinner.Start(80*time.Millisecond, func(frame string) { eta := "" if remaining := c.pt.EstimateRemaining(); remaining > 0 { diff --git a/cmd/progress_debug_test.go b/cmd/progress_debug_test.go index 0ac60e579..e14b91b5f 100644 --- a/cmd/progress_debug_test.go +++ b/cmd/progress_debug_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestZDebugProgress(t *testing.T) { diff --git a/cmd/progress_tracker.go b/cmd/progress_tracker.go index 3cbb70e3d..f0957fcc1 100644 --- a/cmd/progress_tracker.go +++ b/cmd/progress_tracker.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // ProgressStep represents a single step in a multi-step task. diff --git a/cmd/progress_tracker_test.go b/cmd/progress_tracker_test.go index a3825cd15..87a2ab390 100644 --- a/cmd/progress_tracker_test.go +++ b/cmd/progress_tracker_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestNewProgressTracker(t *testing.T) { diff --git a/cmd/progressive_disclosure.go b/cmd/progressive_disclosure.go index 146f252e8..4cb6d8733 100644 --- a/cmd/progressive_disclosure.go +++ b/cmd/progressive_disclosure.go @@ -52,22 +52,22 @@ func DefaultDisclosureConfig() DisclosureConfig { // BeginnerHelp returns a simplified help message for new users. func BeginnerHelp() string { - return `Getting Started with hawk + return `Getting Started with rho - Type your question or task and hawk will help. - hawk reads your project and understands your code. + Type your question or task and rho will help. + rho reads your project and understands your code. Essential Commands: /help Show all commands /test Run your project's tests /diff See what you've changed /commit Save your work with a smart message - /review Have hawk review your code + /review Have rho review your code /clear Start a fresh conversation Tips: - Just describe what you want in plain English - - hawk will read files, run commands, and make changes + - rho will read files, run commands, and make changes - Use /help to discover more features as you get comfortable Type anything to get started!` @@ -75,7 +75,7 @@ Type anything to get started!` // IntermediateHelp returns the standard help message. func IntermediateHelp() string { - return `hawk Commands + return `rho Commands Workflow: /test Run tests and fix failures @@ -110,7 +110,7 @@ Use /help all for the complete list.` // AdvancedHelp returns the full command reference. func AdvancedHelp() string { - return `hawk Full Command Reference + return `rho Full Command Reference Workflow: /test [cmd] Run tests (default: go test ./...) diff --git a/cmd/prompt_input.go b/cmd/prompt_input.go index 12b7a5694..acf6c2935 100644 --- a/cmd/prompt_input.go +++ b/cmd/prompt_input.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) var errNoInteractivePromptInput = errors.New("no interactive terminal available for permission prompt") diff --git a/cmd/record.go b/cmd/record.go index 88e80d6ba..f268d6824 100644 --- a/cmd/record.go +++ b/cmd/record.go @@ -4,7 +4,7 @@ import ( "io" "os" - "github.com/GrayCodeAI/hawk/internal/terminal/tape" + "github.com/GrayCodeAI/rho/internal/terminal/tape" ) // recordPath is set by --record; when non-empty, interactive REPL output is diff --git a/cmd/record_test.go b/cmd/record_test.go index 7ee56f426..7e505da9f 100644 --- a/cmd/record_test.go +++ b/cmd/record_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/terminal/tape" + "github.com/GrayCodeAI/rho/internal/terminal/tape" ) func TestStartRecordingCapturesOutput(t *testing.T) { diff --git a/cmd/resize_unix.go b/cmd/resize_unix.go index 33482420d..514a6dcc8 100644 --- a/cmd/resize_unix.go +++ b/cmd/resize_unix.go @@ -7,7 +7,7 @@ import ( "os/signal" "syscall" - "github.com/GrayCodeAI/hawk/internal/terminal/tape" + "github.com/GrayCodeAI/rho/internal/terminal/tape" ) // watchTerminalResize records terminal resize (SIGWINCH) events into the tape diff --git a/cmd/resize_windows.go b/cmd/resize_windows.go index 11038a9cb..e52743d7c 100644 --- a/cmd/resize_windows.go +++ b/cmd/resize_windows.go @@ -2,7 +2,7 @@ package cmd -import "github.com/GrayCodeAI/hawk/internal/terminal/tape" +import "github.com/GrayCodeAI/rho/internal/terminal/tape" // watchTerminalResize is a no-op on Windows, where SIGWINCH is not defined. func watchTerminalResize(rec *tape.Recorder) func() { diff --git a/cmd/review.go b/cmd/review.go index 61b82134f..44370a883 100644 --- a/cmd/review.go +++ b/cmd/review.go @@ -10,17 +10,17 @@ import ( "github.com/spf13/cobra" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var reviewCmd = &cobra.Command{ Use: "review", Short: "Continuous AI code review on commits", - Long: `hawk review provides continuous background code review. + Long: `rho review provides continuous background code review. -Run 'hawk review init' to install a post-commit hook, then every commit -is automatically reviewed. View findings with 'hawk review tui' -or fix them with 'hawk review fix'.`, +Run 'rho review init' to install a post-commit hook, then every commit +is automatically reviewed. View findings with 'rho review tui' +or fix them with 'rho review fix'.`, } var reviewInitCmd = &cobra.Command{ @@ -38,10 +38,10 @@ func init() { } const hookScript = `#!/bin/sh -# hawk review — continuous code review hook -# Installed by 'hawk review init' +# rho review — continuous code review hook +# Installed by 'rho review init' SHA=$(git rev-parse HEAD) -hawk review run "$SHA" --background & +rho review run "$SHA" --background & ` func runReviewInit(_ *cobra.Command, _ []string) error { @@ -60,8 +60,8 @@ func runReviewInit(_ *cobra.Command, _ []string) error { // Check for existing hook. if _, err := os.Stat(hookPath); err == nil && !reviewInitForce { existing, _ := os.ReadFile(hookPath) // #nosec G304 -- hookPath built from internal hooksDir constant, not external input - if strings.Contains(string(existing), "hawk review") { - fmt.Println(auditTint(icons.CheckBold()+" ", doneGreen) + auditTint("hawk review hook already installed", textPrimary)) + if strings.Contains(string(existing), "rho review") { + fmt.Println(auditTint(icons.CheckBold()+" ", doneGreen) + auditTint("rho review hook already installed", textPrimary)) return nil } return fmt.Errorf("post-commit hook already exists at %s\nUse --force to overwrite, or manually add:\n %s", hookPath, strings.TrimSpace(hookScript)) @@ -74,8 +74,8 @@ func runReviewInit(_ *cobra.Command, _ []string) error { fmt.Println(auditTint(icons.CheckBold()+" ", doneGreen) + auditTint("Installed post-commit hook at ", textPrimary) + auditTint(hookPath, textMuted)) fmt.Println(auditTint(" Every commit will now be reviewed automatically.", textMuted)) - fmt.Println(auditTint(" View reviews: hawk review status", textMuted)) - fmt.Println(auditTint(" Interactive: hawk review tui", textMuted)) + fmt.Println(auditTint(" View reviews: rho review status", textMuted)) + fmt.Println(auditTint(" Interactive: rho review tui", textMuted)) return nil } diff --git a/cmd/review_fix.go b/cmd/review_fix.go index 10a5eeb3d..d74770569 100644 --- a/cmd/review_fix.go +++ b/cmd/review_fix.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var ( @@ -21,8 +21,8 @@ var ( var reviewFixCmd = &cobra.Command{ Use: "fix [id...]", - Short: "Auto-fix review findings using hawk exec", - Long: `Feeds review findings to hawk's engine which applies fixes and commits. + Short: "Auto-fix review findings using rho exec", + Long: `Feeds review findings to rho's engine which applies fixes and commits. Without arguments, fixes all open reviews. Specify IDs to fix specific ones.`, RunE: runReviewFix, } @@ -82,7 +82,7 @@ func fixReview(store *ReviewStore, r *ReviewRecord) error { prompt := buildFixPrompt(r) - // Invoke hawk exec with the fix prompt. + // Invoke rho exec with the fix prompt. execArgs := []string{"exec", "--auto", "full"} if reviewFixWorktree { execArgs = append(execArgs, "--worktree") @@ -92,18 +92,18 @@ func fixReview(store *ReviewStore, r *ReviewRecord) error { } execArgs = append(execArgs, prompt) - hawkBin, err := os.Executable() + rhoBin, err := os.Executable() if err != nil { - hawkBin = "hawk" + rhoBin = "rho" } - cmd := exec.CommandContext(context.Background(), hawkBin, execArgs...) // #nosec G204 -- hawkBin resolved via os.Executable() or literal 'hawk'; args are internal flags + cmd := exec.CommandContext(context.Background(), rhoBin, execArgs...) // #nosec G204 -- rhoBin resolved via os.Executable() or literal 'rho'; args are internal flags cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { - return fmt.Errorf("hawk exec: %w", err) + return fmt.Errorf("rho exec: %w", err) } // Mark as fixed. diff --git a/cmd/review_read.go b/cmd/review_read.go index 94c93c1ed..2d518d8f4 100644 --- a/cmd/review_read.go +++ b/cmd/review_read.go @@ -10,8 +10,8 @@ import ( "github.com/spf13/cobra" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/types" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + contracts "github.com/GrayCodeAI/rho/internal/contracts/types" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var reviewStatusCmd = &cobra.Command{ @@ -72,7 +72,7 @@ func runReviewStatus(_ *cobra.Command, _ []string) error { total += v } if total == 0 { - fmt.Println(auditTint("No reviews yet. Run 'hawk review init' to start.", textMuted)) + fmt.Println(auditTint("No reviews yet. Run 'rho review init' to start.", textMuted)) return nil } diff --git a/cmd/review_refine.go b/cmd/review_refine.go index 90f2643ba..3ff008546 100644 --- a/cmd/review_refine.go +++ b/cmd/review_refine.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var ( @@ -69,7 +69,7 @@ func runReviewRefine(_ *cobra.Command, args []string) error { fmt.Printf("%s\n\n", auditTint(fmt.Sprintf("Refining %d review(s), max %d iterations...", len(reviews), refineMaxIter), textPrimary)) for iter := 1; iter <= refineMaxIter; iter++ { - fmt.Printf("%s\n", auditTint(fmt.Sprintf("── Iteration %d/%d ──", iter, refineMaxIter), hawkColor)) + fmt.Printf("%s\n", auditTint(fmt.Sprintf("── Iteration %d/%d ──", iter, refineMaxIter), rhoColor)) // Fix all open reviews. for _, r := range reviews { @@ -129,7 +129,7 @@ func runReviewRefine(_ *cobra.Command, args []string) error { } if len(remaining) > 0 { fmt.Printf("\n%s %s\n", auditTint(icons.Alert(), warnAmber), auditTint(fmt.Sprintf("%d review(s) still open after %d iterations.", len(remaining), refineMaxIter), textPrimary)) - fmt.Println(auditTint(" Run 'hawk review show' to inspect, or increase --max-iterations.", textMuted)) + fmt.Println(auditTint(" Run 'rho review show' to inspect, or increase --max-iterations.", textMuted)) } return nil } @@ -137,9 +137,9 @@ func runReviewRefine(_ *cobra.Command, args []string) error { func fixReviewRefine(store *ReviewStore, r *ReviewRecord) error { prompt := buildFixPrompt(r) - hawkBin, err := os.Executable() + rhoBin, err := os.Executable() if err != nil { - hawkBin = "hawk" + rhoBin = "rho" } execArgs := []string{"exec", "--auto", "full"} @@ -148,7 +148,7 @@ func fixReviewRefine(store *ReviewStore, r *ReviewRecord) error { } execArgs = append(execArgs, prompt) - cmd := exec.CommandContext(context.Background(), hawkBin, execArgs...) // #nosec G204 -- hawkBin resolved via os.Executable() or literal 'hawk'; args are internal flags + cmd := exec.CommandContext(context.Background(), rhoBin, execArgs...) // #nosec G204 -- rhoBin resolved via os.Executable() or literal 'rho'; args are internal flags cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -159,9 +159,9 @@ func fixReviewRefine(store *ReviewStore, r *ReviewRecord) error { } func runReviewOnSHA(store *ReviewStore, sha string) error { - hawkBin, err := os.Executable() + rhoBin, err := os.Executable() if err != nil { - hawkBin = "hawk" + rhoBin = "rho" } args := []string{"review", "run", sha} @@ -172,7 +172,7 @@ func runReviewOnSHA(store *ReviewStore, sha string) error { args = append(args, "--model", refineModel) } - cmd := exec.CommandContext(context.Background(), hawkBin, args...) // #nosec G204 -- hawkBin resolved via os.Executable() or literal 'hawk'; args are internal flags + cmd := exec.CommandContext(context.Background(), rhoBin, args...) // #nosec G204 -- rhoBin resolved via os.Executable() or literal 'rho'; args are internal flags cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() diff --git a/cmd/review_run.go b/cmd/review_run.go index 63b895670..163e6de6b 100644 --- a/cmd/review_run.go +++ b/cmd/review_run.go @@ -8,12 +8,12 @@ import ( "strings" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - reviewcontracts "github.com/GrayCodeAI/hawk/internal/contracts/review" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/types" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/types" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + reviewcontracts "github.com/GrayCodeAI/rho/internal/contracts/review" + contracts "github.com/GrayCodeAI/rho/internal/contracts/types" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/rho/internal/ui/icons" "github.com/spf13/cobra" ) @@ -121,9 +121,9 @@ func runReviewRun(_ *cobra.Command, args []string) error { } } - // Resolve the provider through Hawk's Eyrie engine boundary. + // Resolve the provider through Rho's Eyrie engine boundary. ctx := context.Background() - selection := hawkconfig.EffectiveSelection(ctx, hawkconfig.SelectionOptions{ + selection := rhoconfig.EffectiveSelection(ctx, rhoconfig.SelectionOptions{ ProviderOverride: strings.TrimSpace(provider), ModelOverride: strings.TrimSpace(reviewRunModel), }) @@ -144,7 +144,7 @@ func runReviewRun(_ *cobra.Command, args []string) error { defer cancel() } - // Run Hawk's own multi-concern review pipeline through the provider. + // Run Rho's own multi-concern review pipeline through the provider. concerns := DefaultConcerns() if strings.TrimSpace(reviewRunConcerns) != "" { wanted := map[string]bool{} diff --git a/cmd/review_store.go b/cmd/review_store.go index 5a95abab5..0d4bb8c9b 100644 --- a/cmd/review_store.go +++ b/cmd/review_store.go @@ -10,8 +10,8 @@ import ( "sync" "time" - reviewcontracts "github.com/GrayCodeAI/hawk/internal/contracts/review" - "github.com/GrayCodeAI/hawk/internal/storage" + reviewcontracts "github.com/GrayCodeAI/rho/internal/contracts/review" + "github.com/GrayCodeAI/rho/internal/storage" ) // ReviewStatus represents the state of a review. diff --git a/cmd/review_test.go b/cmd/review_test.go index 6ec6e6e18..bd1e69853 100644 --- a/cmd/review_test.go +++ b/cmd/review_test.go @@ -6,11 +6,11 @@ import ( "strings" "testing" - reviewcontracts "github.com/GrayCodeAI/hawk/internal/contracts/review" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/types" + reviewcontracts "github.com/GrayCodeAI/rho/internal/contracts/review" + contracts "github.com/GrayCodeAI/rho/internal/contracts/types" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func setReviewTestDirs(t *testing.T) string { @@ -22,7 +22,7 @@ func setReviewTestDirs(t *testing.T) string { func TestReviewStore_CreateAndGet(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -52,7 +52,7 @@ func TestReviewStore_CreateAndGet(t *testing.T) { func TestReviewStore_Update(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -92,7 +92,7 @@ func TestReviewStore_Update(t *testing.T) { func TestReviewStore_GetBySHA(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -114,7 +114,7 @@ func TestReviewStore_GetBySHA(t *testing.T) { func TestReviewStore_ListOpen(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -141,7 +141,7 @@ func TestReviewStore_ListOpen(t *testing.T) { func TestReviewStore_Summary(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -171,7 +171,7 @@ func TestReviewStore_Summary(t *testing.T) { func TestReviewStore_SetStatus(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -191,7 +191,7 @@ func TestReviewStore_SetStatus(t *testing.T) { func TestReviewStore_ListAll(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -222,7 +222,7 @@ func TestReviewStore_ListAll(t *testing.T) { func TestReviewStore_GetMissing(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -239,7 +239,7 @@ func TestReviewStore_GetMissing(t *testing.T) { func TestReviewStore_EmptyDiffToPassedLifecycle(t *testing.T) { // Mirrors runReviewRun's empty-diff path: create → set running → set passed. dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -278,7 +278,7 @@ func TestReviewStore_EmptyDiffToPassedLifecycle(t *testing.T) { func TestReviewStore_CloseCheckpointsWAL(t *testing.T) { dir := setReviewTestDirs(t) - os.MkdirAll(filepath.Join(dir, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(dir, ".rho"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -356,9 +356,9 @@ func TestStatusIcon(t *testing.T) { } } -func TestHookScript_ContainsHawkReview(t *testing.T) { - if !strings.Contains(hookScript, "hawk review") { - t.Error("hook script should contain 'hawk review'") +func TestHookScript_ContainsRhoReview(t *testing.T) { + if !strings.Contains(hookScript, "rho review") { + t.Error("hook script should contain 'rho review'") } if !strings.Contains(hookScript, "git rev-parse HEAD") { t.Error("hook script should get HEAD sha") diff --git a/cmd/review_tui.go b/cmd/review_tui.go index e25e84920..f51a14f3d 100644 --- a/cmd/review_tui.go +++ b/cmd/review_tui.go @@ -9,7 +9,7 @@ import ( lipgloss "charm.land/lipgloss/v2" "github.com/spf13/cobra" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var reviewTUICmd = &cobra.Command{ @@ -127,11 +127,11 @@ func (m reviewTUIModel) View() tea.View { selected := lipgloss.NewStyle().Bold(true).Background(lipgloss.Color("236")) var b strings.Builder - b.WriteString(header.Render(" hawk review") + dim.Render(" j/k:nav enter:expand c:close f:fix r:refresh q:quit") + "\n") + b.WriteString(header.Render(" rho review") + dim.Render(" j/k:nav enter:expand c:close f:fix r:refresh q:quit") + "\n") b.WriteString(strings.Repeat("─", reviewMin(m.width, 80)) + "\n") if len(m.reviews) == 0 { - b.WriteString("\n No reviews yet. Run 'hawk review init' to get started.\n") + b.WriteString("\n No reviews yet. Run 'rho review init' to get started.\n") v := tea.View{Content: b.String()} v.AltScreen = true return v diff --git a/cmd/hawk/main.go b/cmd/rho/main.go similarity index 86% rename from cmd/hawk/main.go rename to cmd/rho/main.go index 3939bab8e..54d5098c3 100644 --- a/cmd/hawk/main.go +++ b/cmd/rho/main.go @@ -9,12 +9,12 @@ import ( "golang.org/x/term" - "github.com/GrayCodeAI/hawk/cmd" - "github.com/GrayCodeAI/hawk/internal/crash" - "github.com/GrayCodeAI/hawk/internal/hawkerr" - "github.com/GrayCodeAI/hawk/internal/mcp" - "github.com/GrayCodeAI/hawk/internal/observability/otellog" - "github.com/GrayCodeAI/hawk/internal/observability/oteltrace" + "github.com/GrayCodeAI/rho/cmd" + "github.com/GrayCodeAI/rho/internal/crash" + "github.com/GrayCodeAI/rho/internal/mcp" + "github.com/GrayCodeAI/rho/internal/observability/otellog" + "github.com/GrayCodeAI/rho/internal/observability/oteltrace" + "github.com/GrayCodeAI/rho/internal/rhoerr" ) // Version, Commit, and BuildDate are set at build time via ldflags. @@ -43,12 +43,12 @@ func main() { // Handle --version flag immediately if len(os.Args) > 1 && os.Args[1] == "--version" { - fmt.Println("hawk " + Version) + fmt.Println("rho " + Version) return } - // Initialize OpenTelemetry telemetry (opt-in via HAWK_ENABLE_TELEMETRY=1). - // Telemetry failures are non-fatal: hawk continues with in-memory tracing only. + // Initialize OpenTelemetry telemetry (opt-in via RHO_ENABLE_TELEMETRY=1). + // Telemetry failures are non-fatal: rho continues with in-memory tracing only. telemetryProviders, telemetryErr := oteltrace.InitTelemetry(oteltrace.DefaultTelemetryConfig()) if telemetryErr != nil { fmt.Fprintln(os.Stderr, "warning: telemetry initialization failed:", telemetryErr) @@ -62,7 +62,7 @@ func main() { } // OTLP log-record export (DSH session-telemetry-otel port). Opt-in like - // tracing: HAWK_ENABLE_TELEMETRY=1 plus an OTLP logs endpoint. The + // tracing: RHO_ENABLE_TELEMETRY=1 plus an OTLP logs endpoint. The // sharing policy gates emission; failures are non-fatal. logBackend, logBackendErr := otellog.NewBackend(otellog.DefaultConfig()) if logBackendErr != nil { @@ -93,7 +93,7 @@ func main() { if errors.As(err, &exitErr) { os.Exit(exitErr.Code) } - os.Exit(hawkerr.ClassifyExitCode(err)) + os.Exit(rhoerr.ClassifyExitCode(err)) } } diff --git a/cmd/root.go b/cmd/root.go index 4ba2a6645..942e5c6a8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,18 +6,19 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "runtime" "strings" "sync" "time" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/onboarding" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/update" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/onboarding" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/update" "github.com/spf13/cobra" ) @@ -69,6 +70,7 @@ var ( startupProfileFlag bool preflightLiveFlag bool quietFlag bool + cwdFlag string ) var ( @@ -87,36 +89,42 @@ func SetBuildDate(d string) { } func registeredProviderCount() int { - return hawkconfig.RegisteredProviderCount() + return rhoconfig.RegisteredProviderCount() } var rootCmd = &cobra.Command{ - Use: "hawk [prompt]", + Use: "rho [prompt]", Short: "AI coding agent powered by eyrie", - Long: fmt.Sprintf(`hawk is an AI coding agent that reads, writes, and runs code in your terminal. + Long: fmt.Sprintf(`rho is an AI coding agent that reads, writes, and runs code in your terminal. It connects to %d first-class LLM providers through eyrie, executes tools (file I/O, shell, git, web search), and manages sessions — all from a keyboard-driven TUI or headless mode for scripts and CI. Quick orientation: - hawk Start interactive TUI - hawk -p "prompt" One-shot: send prompt, print response, exit - hawk exec "task" Autonomous multi-turn execution - hawk path Check environment readiness - hawk doctor Run diagnostics - hawk config Manage settings and credentials + rho Start interactive TUI + rho -p "prompt" One-shot: send prompt, print response, exit + rho exec "task" Autonomous multi-turn execution + rho path Check environment readiness + rho doctor Run diagnostics + rho config Manage settings and credentials API keys are stored in the OS keychain (macOS Keychain / Linux keyring). -Run hawk and use /config to set up your first provider.`, registeredProviderCount()), - Example: ` hawk - hawk -p "explain this repo" - hawk exec "fix failing tests" - hawk preflight - hawk path`, +Run rho and use /config to set up your first provider.`, registeredProviderCount()), + Example: ` rho + rho -p "explain this repo" + rho exec "fix failing tests" + rho preflight + rho path`, Args: cobra.ArbitraryArgs, SilenceUsage: true, SilenceErrors: true, + // --cwd is a persistent flag, so apply it before any subcommand runs — + // not just the bare root command. This keeps `rho --cwd X ` working + // and validates the path once, early. + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return applyCwdFlag() + }, RunE: func(cmd *cobra.Command, args []string) error { if versionFlag { cmd.Println(versionLine()) @@ -164,7 +172,7 @@ Run hawk and use /config to set up your first provider.`, registeredProviderCoun // the TUI, so gate them identically: untrusted folders block // project automation. if tr := engine.ProjectTrust(""); tr.Blocked { - return fmt.Errorf("cannot start: folder not trusted (%s)\nProject-scoped hooks, MCP servers, and custom specialists are blocked.\nRun 'hawk trust add' to trust this folder before running hawk", tr.Path) + return fmt.Errorf("cannot start: folder not trusted (%s)\nProject-scoped hooks, MCP servers, and custom specialists are blocked.\nRun 'rho trust add' to trust this folder before running rho", tr.Path) } if replFlag { return runRepl() @@ -201,7 +209,7 @@ Run hawk and use /config to set up your first provider.`, registeredProviderCoun // Folder trust check — block starting CLI in an untrusted directory if tr := engine.ProjectTrust(""); tr.Blocked { - return fmt.Errorf("cannot start CLI: folder not trusted (%s)\nProject-scoped hooks, MCP servers, and custom specialists are blocked.\nRun 'hawk trust add' to trust this folder before starting hawk", tr.Path) + return fmt.Errorf("cannot start CLI: folder not trusted (%s)\nProject-scoped hooks, MCP servers, and custom specialists are blocked.\nRun 'rho trust add' to trust this folder before starting rho", tr.Path) } // Launch TUI — use /config to set API keys; eyrie supplies providers and models @@ -213,7 +221,7 @@ func init() { rootCmd.Flags().StringVarP(&model, "model", "m", "", "model to use (from eyrie catalog; see /models)") rootCmd.Flags().BoolVarP(&printMode, "print", "p", false, "print response and exit") rootCmd.Flags().StringVar(&promptFlag, "prompt", "", "send a single prompt and exit (legacy alias for --print)") - rootCmd.Flags().StringVar(&outputFormat, "output-format", "text", `output format for --print: "text", "json", or "stream-json"`) + rootCmd.Flags().StringVar(&outputFormat, "output-format", "text", `output format for --print: "text", "json", "stream-json", or "transcript"`) rootCmd.Flags().BoolVar(&printMarkdown, "markdown", false, `render --print text output as styled markdown (needs color; ignored for json/stream-json)`) rootCmd.Flags().StringVar(&outputFields, "output-fields", "", `comma-separated field whitelist for --output-format json (e.g. "result,session_id")`) rootCmd.Flags().StringVar(&inputFormat, "input-format", "text", `input format for --print: "text" or "stream-json"`) @@ -256,6 +264,7 @@ func init() { rootCmd.Flags().BoolVar(&recoverFlag, "recover", false, "scan for interrupted sessions and offer to resume") rootCmd.Flags().BoolVar(&startupProfileFlag, "startup-profile", false, "print startup performance profile") rootCmd.PersistentFlags().BoolVarP(&quietFlag, "quiet", "q", false, "suppress non-essential output (spinners, progress, decoration); machine-parseable output only") + rootCmd.PersistentFlags().StringVar(&cwdFlag, "cwd", "", "run against this project directory instead of the current one") preflightCmd.Flags().BoolVar(&preflightLiveFlag, "live", false, "verify selected provider connectivity and authentication") preflightCmd.Flags().BoolVar(&preflightJSON, "json", false, "output preflight report as JSON") doctorCmd.Flags().BoolVar(&doctorJSONFlag, "json", false, "output diagnostics as JSON") @@ -422,7 +431,7 @@ func groupRootCommands() { // In a terminal, it requires typing the full confirmation token (not a single // key) so a stray keystroke or terminal-escape trickery cannot confirm it. In // non-interactive mode (CI, scripts), it requires the -// HAWK_DANGEROUSLY_SKIP_PERMISSIONS=1 environment variable. +// RHO_DANGEROUSLY_SKIP_PERMISSIONS=1 environment variable. func confirmDangerousSkipPermissions() error { if isStdinTerminal() { fmt.Fprintf(os.Stderr, "Type %s to confirm skipping permission prompts: ", dangerSkipConfirmToken) @@ -437,8 +446,8 @@ func confirmDangerousSkipPermissions() error { return nil } // Non-interactive: require explicit env var override. - if os.Getenv("HAWK_DANGEROUSLY_SKIP_PERMISSIONS") != "1" { - return fmt.Errorf("--dangerously-skip-permissions requires HAWK_DANGEROUSLY_SKIP_PERMISSIONS=1 in non-interactive mode") + if os.Getenv("RHO_DANGEROUSLY_SKIP_PERMISSIONS") != "1" { + return fmt.Errorf("--dangerously-skip-permissions requires RHO_DANGEROUSLY_SKIP_PERMISSIONS=1 in non-interactive mode") } return nil } @@ -462,31 +471,31 @@ var completionCmd = &cobra.Command{ Long: `To load completions: Bash: - source <(hawk completion bash) + source <(rho completion bash) # To load completions for each session, execute once: # Linux: - hawk completion bash > /etc/bash_completion.d/hawk + rho completion bash > /etc/bash_completion.d/rho # macOS: - hawk completion bash > /usr/local/etc/bash_completion.d/hawk + rho completion bash > /usr/local/etc/bash_completion.d/rho Zsh: - source <(hawk completion zsh) + source <(rho completion zsh) # To load completions for each session, execute once: - hawk completion zsh > "${fpath[1]}/_hawk" + rho completion zsh > "${fpath[1]}/_rho" Fish: - hawk completion fish | source + rho completion fish | source # To load completions for each session, execute once: - hawk completion fish > ~/.config/fish/completions/hawk.fish + rho completion fish > ~/.config/fish/completions/rho.fish PowerShell: - hawk completion powershell | Out-String | Invoke-Expression + rho completion powershell | Out-String | Invoke-Expression # To load completions for every new session, run: - hawk completion powershell > hawk.ps1 + rho completion powershell > rho.ps1 # and source this file from your PowerShell profile. JSON: - hawk completion json + rho completion json # Print a machine-readable command/flag spec for IDE integration. `, DisableFlagsInUseLine: true, @@ -520,17 +529,17 @@ var completionInstallCmd = &cobra.Command{ Long: `Install the shell completion script to the standard location for your OS. Bash: - hawk completion install bash - # Installs to ~/.local/share/bash-completion/completions/hawk (Linux) - # or /opt/homebrew/etc/bash_completion.d/hawk (macOS Homebrew) + rho completion install bash + # Installs to ~/.local/share/bash-completion/completions/rho (Linux) + # or /opt/homebrew/etc/bash_completion.d/rho (macOS Homebrew) Zsh: - hawk completion install zsh - # Installs to the first directory in $fpath (e.g. /usr/local/share/zsh/site-functions/_hawk) + rho completion install zsh + # Installs to the first directory in $fpath (e.g. /usr/local/share/zsh/site-functions/_rho) Fish: - hawk completion install fish - # Installs to ~/.config/fish/completions/hawk.fish`, + rho completion install fish + # Installs to ~/.config/fish/completions/rho.fish`, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish"}, Args: cobra.ExactArgs(1), @@ -572,8 +581,8 @@ Fish: var updateCmd = &cobra.Command{ Use: "update", - Short: "Check for hawk updates", - Long: "Check GitHub for a newer hawk release and print upgrade instructions.", + Short: "Check for rho updates", + Long: "Check GitHub for a newer rho release and print upgrade instructions.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { ver := version @@ -591,7 +600,7 @@ var updateCmd = &cobra.Command{ return nil } if release == nil { - cmd.Println(auditTint("hawk is up to date ("+ver+")", doneGreen)) + cmd.Println(auditTint("rho is up to date ("+ver+")", doneGreen)) return nil } cmd.Println(auditTint("Update available: ", warnAmber) + auditTint(ver+" -> "+release.TagName, textPrimary)) @@ -613,13 +622,13 @@ API keys and secrets are never included.`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { var b strings.Builder - b.WriteString("## hawk bug report\n\n") + b.WriteString("## rho bug report\n\n") b.WriteString(fmt.Sprintf("- **Version:** %s\n", versionLine())) b.WriteString(fmt.Sprintf("- **Platform:** %s\n", update.Platform())) b.WriteString(fmt.Sprintf("- **Go:** %s\n", runtime.Version())) b.WriteString(fmt.Sprintf("- **OS/Arch:** %s/%s\n", runtime.GOOS, runtime.GOARCH)) b.WriteString("\n## Doctor output\n\n```\n") - settings := hawkconfig.LoadSettings() + settings := rhoconfig.LoadSettings() b.WriteString(doctorReport(settings)) b.WriteString("\n```\n") cmd.Print(b.String()) @@ -637,7 +646,7 @@ var versionJSON bool var versionCmd = &cobra.Command{ Use: "version", - Short: "Print hawk version", + Short: "Print rho version", Run: func(cmd *cobra.Command, args []string) { if versionJSON { info := versionInfo{Version: DisplayVersion()} @@ -674,7 +683,7 @@ var setupCmd = &cobra.Command{ var initCmd = &cobra.Command{ Use: "init", Short: "Interactive onboarding wizard for first-time setup", - Long: "Launch the interactive setup wizard to configure credentials, select providers/models, and initialize hawk.", + Long: "Launch the interactive setup wizard to configure credentials, select providers/models, and initialize rho.", RunE: func(cmd *cobra.Command, args []string) error { onboarding.Welcome(version) return onboarding.RunSetup() @@ -738,7 +747,7 @@ var preflightCmd = &cobra.Command{ defer prog.Abort() prog.StartStep(0) } - r := hawkconfig.EnginePreflightReportWithSettings(ctx, settings, hawkconfig.EnginePreflightOptions{VerifyLive: preflightLiveFlag}) + r := rhoconfig.EnginePreflightReportWithSettings(ctx, settings, rhoconfig.EnginePreflightOptions{VerifyLive: preflightLiveFlag}) if prog != nil { prog.CompleteStep(0) prog.Done() @@ -750,14 +759,14 @@ var preflightCmd = &cobra.Command{ } cmd.Println(string(out)) } else { - out := hawkconfig.FormatEnginePreflight(r) + out := rhoconfig.FormatEnginePreflight(r) cmd.Println(out) } if !r.Ready { if preflightLiveFlag { return fmt.Errorf("live preflight failed — check the selected provider credential and network access") } - return fmt.Errorf("preflight failed — run hawk and complete /config") + return fmt.Errorf("preflight failed — run rho and complete /config") } return nil }, @@ -767,10 +776,10 @@ var preflightCmd = &cobra.Command{ // showing a modern old → new transition when the value actually changed. // Settable keys are non-secret (API keys error out before reaching here), // so displaying the prior value cannot leak a secret. -func printConfigSetResult(cmd *cobra.Command, key, newVal string, settings hawkconfig.Settings) { - oldVal, hadOld := hawkconfig.SettingValue(settings, key) +func printConfigSetResult(cmd *cobra.Command, key, newVal string, settings rhoconfig.Settings) { + oldVal, hadOld := rhoconfig.SettingValue(settings, key) if hadOld && oldVal != "" && oldVal != newVal { - cmd.Println(auditTint(key, textPrimary) + auditTint(": ", textMuted) + auditTint(oldVal, textMuted) + auditTint(" → ", hawkColor) + auditTint(newVal, textPrimary) + auditTint(" (updated)", doneGreen)) + cmd.Println(auditTint(key, textPrimary) + auditTint(": ", textMuted) + auditTint(oldVal, textMuted) + auditTint(" → ", rhoColor) + auditTint(newVal, textPrimary) + auditTint(" (updated)", doneGreen)) return } cmd.Println(auditTint("updated ", doneGreen) + auditTint(key, textPrimary)) @@ -784,13 +793,13 @@ var configCmd = &cobra.Command{ switch args[0] { case "get": if len(args) != 2 { - return fmt.Errorf("usage: hawk config get ") + return fmt.Errorf("usage: rho config get ") } settings, err := loadEffectiveSettings() if err != nil { return err } - value, ok := hawkconfig.SettingValue(settings, args[1]) + value, ok := rhoconfig.SettingValue(settings, args[1]) if !ok { return fmt.Errorf("unsupported setting key %q", args[1]) } @@ -802,7 +811,7 @@ var configCmd = &cobra.Command{ return nil case "set": if len(args) < 3 { - return fmt.Errorf("usage: hawk config set ") + return fmt.Errorf("usage: rho config set ") } key := args[1] newVal := strings.Join(args[2:], " ") @@ -810,35 +819,35 @@ var configCmd = &cobra.Command{ if err != nil { return err } - if err := hawkconfig.SetGlobalSetting(key, newVal); err != nil { + if err := rhoconfig.SetGlobalSetting(key, newVal); err != nil { return err } printConfigSetResult(cmd, key, newVal, settings) return nil case "provider": if len(args) < 2 { - return fmt.Errorf("usage: hawk config provider ") + return fmt.Errorf("usage: rho config provider ") } newVal := strings.Join(args[1:], " ") settings, err := loadEffectiveSettings() if err != nil { return err } - if err := hawkconfig.SetGlobalSetting("provider", newVal); err != nil { + if err := rhoconfig.SetGlobalSetting("provider", newVal); err != nil { return err } printConfigSetResult(cmd, "provider", newVal, settings) return nil case "model": if len(args) < 2 { - return fmt.Errorf("usage: hawk config model ") + return fmt.Errorf("usage: rho config model ") } newVal := strings.Join(args[1:], " ") settings, err := loadEffectiveSettings() if err != nil { return err } - if err := hawkconfig.SetGlobalSetting("model", newVal); err != nil { + if err := rhoconfig.SetGlobalSetting("model", newVal); err != nil { return err } printConfigSetResult(cmd, "model", newVal, settings) @@ -848,13 +857,13 @@ var configCmd = &cobra.Command{ return nil case "routing-preview": if len(args) < 2 { - return fmt.Errorf("usage: hawk config routing-preview ") + return fmt.Errorf("usage: rho config routing-preview ") } settings, err := loadEffectiveSettings() if err != nil { return err } - out, err := hawkconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, strings.Join(args[1:], " ")) + out, err := rhoconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, strings.Join(args[1:], " ")) if err != nil { return err } @@ -875,10 +884,10 @@ var configCmd = &cobra.Command{ var mcpCmd = &cobra.Command{ Use: "mcp", - Short: "Show MCP configuration; run or register hawk as an MCP server", - Long: "With no subcommand, summarizes the MCP servers hawk connects to (consumes).\n" + - " hawk mcp serve — run hawk itself as an MCP server over stdio\n" + - " hawk mcp config — print the JSON block to register hawk in Claude Desktop/Cursor/Windsurf", + Short: "Show MCP configuration; run or register rho as an MCP server", + Long: "With no subcommand, summarizes the MCP servers rho connects to (consumes).\n" + + " rho mcp serve — run rho itself as an MCP server over stdio\n" + + " rho mcp config — print the JSON block to register rho in Claude Desktop/Cursor/Windsurf", RunE: func(cmd *cobra.Command, args []string) error { settings, err := loadEffectiveSettings() if err != nil { @@ -960,7 +969,7 @@ var ( var researchCmd = &cobra.Command{ Use: "research [flags] ", Short: "Autonomous research loop (Karpathy autoresearch pattern)", - Long: "hawk research --grep '^val_bpb:' --direction lower 'uv run train.py'", + Long: "rho research --grep '^val_bpb:' --direction lower 'uv run train.py'", RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return fmt.Errorf("metric command is required") @@ -1029,6 +1038,31 @@ func init() { } // Execute runs the root command. +// applyCwdFlag changes the process working directory when --cwd is set, so +// every downstream path (session store, tools, repo map, git) resolves against +// the requested project. It is a no-op when the flag is empty. +func applyCwdFlag() error { + dir := strings.TrimSpace(cwdFlag) + if dir == "" { + return nil + } + abs, err := filepath.Abs(dir) + if err != nil { + return fmt.Errorf("--cwd %q: %w", dir, err) + } + info, err := os.Stat(abs) + if err != nil { + return fmt.Errorf("--cwd %q: %w", dir, err) + } + if !info.IsDir() { + return fmt.Errorf("--cwd %q is not a directory", dir) + } + if err := os.Chdir(abs); err != nil { + return fmt.Errorf("--cwd %q: %w", dir, err) + } + return nil +} + func Execute() error { // Cobra defaults command output to stderr when no writer is configured. // The process entrypoint must make stdout/stderr semantics explicit so @@ -1056,9 +1090,9 @@ var recoverCmd = &cobra.Command{ and offer to resume them. If a session-id is provided, resume that specific session. Examples: - hawk recover # List interrupted sessions - hawk recover abc123 # Resume specific session - hawk --recover # Auto-resume most recent interrupted session`, + rho recover # List interrupted sessions + rho recover abc123 # Resume specific session + rho --recover # Auto-resume most recent interrupted session`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { s, note, err := session.ResumeSession(args[0]) @@ -1075,8 +1109,8 @@ Examples: cmd.Println(session.FormatRecoveryCandidates(candidates)) if len(candidates) > 0 { - cmd.Println(auditTint("Resume with: hawk recover ", textMuted)) - cmd.Println(auditTint("Or launch TUI with: hawk --recover", textMuted)) + cmd.Println(auditTint("Resume with: rho recover ", textMuted)) + cmd.Println(auditTint("Or launch TUI with: rho --recover", textMuted)) } return nil }, diff --git a/cmd/rules.go b/cmd/rules.go index 0c5aeedd7..1a83d1581 100644 --- a/cmd/rules.go +++ b/cmd/rules.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/GrayCodeAI/hawk/internal/rules" + "github.com/GrayCodeAI/rho/internal/rules" "github.com/spf13/cobra" ) @@ -18,7 +18,7 @@ var rulesCmd = &cobra.Command{ Use: "rules", Short: "Detect, import, and export AI coding rules between tool formats", Long: `rules manages AI coding rule files across different tools. -Supported formats: hawk, cursor, claudecode, copilot, gemini. +Supported formats: rho, cursor, claudecode, copilot, gemini. Subcommands: detect Show which AI tool rule files exist in the current directory @@ -56,7 +56,7 @@ var rulesDetectCmd = &cobra.Command{ var rulesImportCmd = &cobra.Command{ Use: "import", - Short: "Import rules from another tool's format into hawk", + Short: "Import rules from another tool's format into rho", RunE: func(cmd *cobra.Command, args []string) error { if rulesImportFrom == "" { return fmt.Errorf("--from flag is required (e.g. --from cursor)") @@ -73,9 +73,9 @@ var rulesImportCmd = &cobra.Command{ return nil } - // Export to hawk format. - if err := rules.Export(".", rules.FormatHawk, imported); err != nil { - return fmt.Errorf("export to hawk format failed: %w", err) + // Export to rho format. + if err := rules.Export(".", rules.FormatRho, imported); err != nil { + return fmt.Errorf("export to rho format failed: %w", err) } cmd.Println(auditTint(fmt.Sprintf("Imported %d rule(s) from %s to .agents/rules/.", len(imported), rulesImportFrom), doneGreen)) @@ -88,30 +88,30 @@ var rulesImportCmd = &cobra.Command{ var rulesExportCmd = &cobra.Command{ Use: "export", - Short: "Export hawk rules to another tool's format", + Short: "Export rho rules to another tool's format", RunE: func(cmd *cobra.Command, args []string) error { if rulesExportTo == "" { return fmt.Errorf("--to flag is required (e.g. --to claudecode)") } - // Read hawk rules. - hawkRules, err := rules.Import(".", rules.FormatHawk) + // Read rho rules. + rhoRules, err := rules.Import(".", rules.FormatRho) if err != nil { - return fmt.Errorf("read hawk rules failed: %w", err) + return fmt.Errorf("read rho rules failed: %w", err) } - if len(hawkRules) == 0 { - cmd.Println(auditTint("No hawk rules found in .agents/rules/. Nothing to export.", textMuted)) + if len(rhoRules) == 0 { + cmd.Println(auditTint("No rho rules found in .agents/rules/. Nothing to export.", textMuted)) return nil } to := rules.Format(rulesExportTo) - if err := rules.Export(".", to, hawkRules); err != nil { + if err := rules.Export(".", to, rhoRules); err != nil { return fmt.Errorf("export to %s format failed: %w", rulesExportTo, err) } - cmd.Println(auditTint(fmt.Sprintf("Exported %d rule(s) to %s format.", len(hawkRules), rulesExportTo), doneGreen)) - for _, r := range hawkRules { + cmd.Println(auditTint(fmt.Sprintf("Exported %d rule(s) to %s format.", len(rhoRules), rulesExportTo), doneGreen)) + for _, r := range rhoRules { cmd.Println(auditTint(" - "+r.Name, textPrimary)) } return nil diff --git a/cmd/sandbox.go b/cmd/sandbox.go index 788dfcc19..84554bde8 100644 --- a/cmd/sandbox.go +++ b/cmd/sandbox.go @@ -4,12 +4,12 @@ import ( "fmt" "sync" - "github.com/GrayCodeAI/hawk/internal/diffsandbox" + "github.com/GrayCodeAI/rho/internal/diffsandbox" "github.com/spf13/cobra" ) // sandboxInstance is a package-level sandbox for the CLI session. -// In a real integration this would be loaded/shared from the hawk engine; +// In a real integration this would be loaded/shared from the rho engine; // for now we create a fresh sandbox rooted at the current directory. var ( sandboxInstance *diffsandbox.Sandbox diff --git a/cmd/search.go b/cmd/search.go index 21bae6d42..50af517b1 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -5,8 +5,8 @@ import ( "os" "strings" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/theme" "github.com/spf13/cobra" ) @@ -18,14 +18,14 @@ var ( var searchCmd = &cobra.Command{ Use: "search ", Short: "Search across saved sessions", - Long: `Full-text search across all saved hawk sessions. + Long: `Full-text search across all saved rho sessions. Searches message content, tool results, and assistant responses. Examples: - hawk search "authentication" - hawk search --limit 5 "database migration" - hawk search "func main"`, + rho search "authentication" + rho search --limit 5 "database migration" + rho search "func main"`, Args: cobra.ExactArgs(1), RunE: runSearch, } diff --git a/cmd/security_verify_governance_cli_test.go b/cmd/security_verify_governance_cli_test.go index 0b539846d..ccdbca9a0 100644 --- a/cmd/security_verify_governance_cli_test.go +++ b/cmd/security_verify_governance_cli_test.go @@ -8,15 +8,15 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/securitylog" + "github.com/GrayCodeAI/rho/internal/securitylog" ) -// withTempState runs a cli test body with HAWK_STATE_DIR pointed at a temp dir +// withTempState runs a cli test body with RHO_STATE_DIR pointed at a temp dir // so the real user state is never touched. func withTempState(t *testing.T, body func(stateDir string)) { t.Helper() dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", dir) + t.Setenv("RHO_STATE_DIR", dir) body(dir) } diff --git a/cmd/securitylog_cmd.go b/cmd/securitylog_cmd.go index e9ba6257a..d971a13ed 100644 --- a/cmd/securitylog_cmd.go +++ b/cmd/securitylog_cmd.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/hawk/internal/securitylog" + "github.com/GrayCodeAI/rho/internal/securitylog" "github.com/spf13/cobra" ) @@ -18,13 +18,13 @@ var ( var securitylogCmd = &cobra.Command{ Use: "securitylog", Short: "Inspect the tamper-evident security event log", - Long: `Hawk records security-relevant events (permission denials, approval + Long: `Rho records security-relevant events (permission denials, approval denials) to an append-only, HMAC-chained log. Entries are linked so that reordering, deletion, or alteration is detectable. - hawk securitylog Show a summary and recent events - hawk securitylog show List logged events - hawk securitylog verify Verify the hash chain has not been tampered with`, + rho securitylog Show a summary and recent events + rho securitylog show List logged events + rho securitylog verify Verify the hash chain has not been tampered with`, RunE: func(cmd *cobra.Command, args []string) error { return runSecuritylogShow(cmd, 20, false) }, diff --git a/cmd/session_export.go b/cmd/session_export.go index bbf3acc23..2f51d2e74 100644 --- a/cmd/session_export.go +++ b/cmd/session_export.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/session" "github.com/spf13/cobra" ) diff --git a/cmd/session_migrate.go b/cmd/session_migrate.go index 94a99cb79..9947017f2 100644 --- a/cmd/session_migrate.go +++ b/cmd/session_migrate.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/session" "github.com/spf13/cobra" ) diff --git a/cmd/session_migrate_test.go b/cmd/session_migrate_test.go index 1596166ca..7978e1bbe 100644 --- a/cmd/session_migrate_test.go +++ b/cmd/session_migrate_test.go @@ -9,7 +9,7 @@ import ( func TestRunSessionMigrate(t *testing.T) { state := t.TempDir() - t.Setenv("HAWK_STATE_DIR", state) + t.Setenv("RHO_STATE_DIR", state) id := "migrate-cmd-test" sessDir := filepath.Join(state, "sessions") if err := os.MkdirAll(sessDir, 0o700); err != nil { @@ -40,7 +40,7 @@ func TestRunSessionMigrate(t *testing.T) { func TestRunSessionMigrateJSON(t *testing.T) { state := t.TempDir() - t.Setenv("HAWK_STATE_DIR", state) + t.Setenv("RHO_STATE_DIR", state) id := "migrate-cmd-json" sessDir := filepath.Join(state, "sessions") _ = os.MkdirAll(sessDir, 0o700) diff --git a/cmd/session_share.go b/cmd/session_share.go index 4bdac9401..da1f597c8 100644 --- a/cmd/session_share.go +++ b/cmd/session_share.go @@ -9,12 +9,12 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/session" "github.com/spf13/cobra" ) // sessionShareCmd shares a session. It uploads the export to a configured -// hosted endpoint (HAWK_SHARE_URL) when one is set, else falls back to the +// hosted endpoint (RHO_SHARE_URL) when one is set, else falls back to the // local content-derived deeplink + export path. var sessionShareCmd = &cobra.Command{ Use: "share [session-id]", @@ -40,7 +40,7 @@ func runSessionShare(_ *cobra.Command, args []string) error { return fmt.Errorf("export session: %w", err) } - host := strings.TrimSpace(os.Getenv("HAWK_SHARE_URL")) + host := strings.TrimSpace(os.Getenv("RHO_SHARE_URL")) if host != "" { url, err := uploadShare(context.Background(), host, s.ID, data) if err != nil { @@ -56,7 +56,7 @@ func runSessionShare(_ *cobra.Command, args []string) error { return fmt.Errorf("could not generate a share link for session %q", s.ID) } fmt.Printf("Share deeplink: %s\n", link) - fmt.Printf("(Set HAWK_SHARE_URL to a Hawk Cloud share endpoint for a hosted URL.)\n") + fmt.Printf("(Set RHO_SHARE_URL to a Rho Cloud share endpoint for a hosted URL.)\n") return nil } diff --git a/cmd/session_sync.go b/cmd/session_sync.go index 2dff22d12..2c6413e81 100644 --- a/cmd/session_sync.go +++ b/cmd/session_sync.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" ) func (m *chatModel) ensureDeferredSystemContext() { @@ -32,12 +32,12 @@ func explicitSelection(ctx context.Context) (provider, model string) { if ctx == nil { ctx = context.Background() } - return strings.TrimSpace(hawkconfig.ActiveGateway(ctx)), strings.TrimSpace(hawkconfig.ActiveModel(ctx)) + return strings.TrimSpace(rhoconfig.ActiveGateway(ctx)), strings.TrimSpace(rhoconfig.ActiveModel(ctx)) } // syncSessionFromPersistedSelection copies explicit eyrie provider.json // selection into the live session when the session fields are empty. -// It intentionally avoids runtime defaults so Hawk can preserve the +// It intentionally avoids runtime defaults so Rho can preserve the // "gateway selected, model still missing" setup state. func syncSessionFromPersistedSelection(sess *engine.Session) { if sess == nil { diff --git a/cmd/session_sync_test.go b/cmd/session_sync_test.go index c78046d46..7f34f9109 100644 --- a/cmd/session_sync_test.go +++ b/cmd/session_sync_test.go @@ -5,26 +5,26 @@ import ( "strings" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestSyncSessionFromPersistedSelection_FillsEmptySessionModel(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.SetActiveProvider(ctx, "openrouter") - _ = hawkconfig.SetActiveModel(ctx, "gpt-4o") + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.SetActiveProvider(ctx, "openrouter") + _ = rhoconfig.SetActiveModel(ctx, "gpt-4o") sess := engine.NewSession("", "", "test", nil) syncSessionFromPersistedSelection(sess) @@ -38,20 +38,20 @@ func TestSyncSessionFromPersistedSelection_FillsEmptySessionModel(t *testing.T) } func TestEnsureSessionReadyForChat_UsesPersistedModel(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.SetActiveProvider(ctx, "openrouter") - _ = hawkconfig.SetActiveModel(ctx, "gpt-4o") + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.SetActiveProvider(ctx, "openrouter") + _ = rhoconfig.SetActiveModel(ctx, "gpt-4o") m := &chatModel{session: engine.NewSession("", "", "test", nil)} if err := m.ensureSessionReadyForChat(); err != nil { @@ -63,18 +63,18 @@ func TestEnsureSessionReadyForChat_UsesPersistedModel(t *testing.T) { } func TestEnsureSessionReadyForChat_NoModel(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.ClearActiveSelection(ctx) + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.ClearActiveSelection(ctx) m := &chatModel{session: engine.NewSession("", "", "test", nil)} if err := m.ensureSessionReadyForChat(); err == nil { @@ -83,20 +83,20 @@ func TestEnsureSessionReadyForChat_NoModel(t *testing.T) { } func TestEnsureSessionReadyForChat_AppliesDeferredSystemContextOnce(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - hawkconfig.InvalidateConfigUICache() - _ = hawkconfig.SetActiveProvider(ctx, "openrouter") - _ = hawkconfig.SetActiveModel(ctx, "gpt-4o") + rhoconfig.InvalidateConfigUICache() + _ = rhoconfig.SetActiveProvider(ctx, "openrouter") + _ = rhoconfig.SetActiveModel(ctx, "gpt-4o") m := &chatModel{ session: engine.NewSession("", "", "base", nil), diff --git a/cmd/skills_cmd.go b/cmd/skills_cmd.go index 6c78bb2e7..4c1f7bc22 100644 --- a/cmd/skills_cmd.go +++ b/cmd/skills_cmd.go @@ -8,8 +8,8 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/tool" "github.com/spf13/cobra" ) diff --git a/cmd/skills_create.go b/cmd/skills_create.go index cbf7ad991..895eac33f 100644 --- a/cmd/skills_create.go +++ b/cmd/skills_create.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/types" "github.com/spf13/cobra" ) @@ -30,7 +30,7 @@ func runSkillsCreate(_ *cobra.Command, args []string) error { return err } model, provider := effectiveModelAndProvider(settings) - sess := newHawkSession(settings, provider, model, "You are a skill author.", tool.NewRegistry()) + sess := newRhoSession(settings, provider, model, "You are a skill author.", tool.NewRegistry()) prompt := plugin.BuildNewSkillPrompt(desc) resp, err := sess.Chat(context.Background(), []types.EyrieMessage{ diff --git a/cmd/skills_curator_cmd.go b/cmd/skills_curator_cmd.go index 4ae40a54a..d5d0ef1d4 100644 --- a/cmd/skills_curator_cmd.go +++ b/cmd/skills_curator_cmd.go @@ -5,14 +5,14 @@ import ( "image/color" "path/filepath" - "github.com/GrayCodeAI/hawk/internal/intelligence/skillcurator" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/intelligence/skillcurator" + "github.com/GrayCodeAI/rho/internal/storage" "github.com/spf13/cobra" ) // skillsCuratorCmd exposes the background skill curator (adopted from Hermes // Agent) as a CLI surface: review/archive/pin/unpin over agent-created skills -// in ~/.hawk/skills. +// in ~/.rho/skills. var skillsCuratorCmd = &cobra.Command{ Use: "curator [command]", Short: "Skill lifecycle curation (status, run, pin, unpin, archive)", diff --git a/cmd/sleep_prevent.go b/cmd/sleep_prevent.go index 41957077c..39cdd1874 100644 --- a/cmd/sleep_prevent.go +++ b/cmd/sleep_prevent.go @@ -43,7 +43,7 @@ func preventSleepLinux() func() { "systemd-inhibit", "--mode=block", "--what=idle:sleep", - "--who=hawk", + "--who=rho", "--why=Agent turn in progress", "sleep", "86400") if err := cmd.Start(); err != nil { diff --git a/cmd/snapshot_cmd.go b/cmd/snapshot_cmd.go index b035d2f7e..7825e31e3 100644 --- a/cmd/snapshot_cmd.go +++ b/cmd/snapshot_cmd.go @@ -7,7 +7,7 @@ import ( "strings" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/snapshot" + "github.com/GrayCodeAI/rho/internal/snapshot" "github.com/spf13/cobra" ) @@ -51,7 +51,7 @@ func (m chatModel) showSnapshotList() (tea.Model, tea.Cmd) { history, err := t.History(15) if err != nil || len(history) == 0 { - m.messages = append(m.messages, displayMsg{role: "system", content: "No snapshots yet. Snapshots are created automatically when hawk modifies files."}) + m.messages = append(m.messages, displayMsg{role: "system", content: "No snapshots yet. Snapshots are created automatically when rho modifies files."}) return m, nil } @@ -123,7 +123,7 @@ func (m chatModel) diffSnapshot(hash string) (tea.Model, tea.Cmd) { var snapshotCmd = &cobra.Command{ Use: "snapshot", Short: "Manage file snapshots (undo any change)", - Long: "View, restore, and diff file snapshots. Hawk automatically snapshots every file modification.", + Long: "View, restore, and diff file snapshots. Rho automatically snapshots every file modification.", } var snapshotListCmd = &cobra.Command{ diff --git a/cmd/spec_picker.go b/cmd/spec_picker.go index 35e233c68..528220a80 100644 --- a/cmd/spec_picker.go +++ b/cmd/spec_picker.go @@ -7,7 +7,7 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" "github.com/mattn/go-runewidth" ) diff --git a/cmd/spec_picker_test.go b/cmd/spec_picker_test.go index 3fe5a8ff1..e0b1fe3df 100644 --- a/cmd/spec_picker_test.go +++ b/cmd/spec_picker_test.go @@ -4,7 +4,7 @@ import ( "testing" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestSpecPicker_HasSevenActions(t *testing.T) { diff --git a/cmd/spinner_wave.go b/cmd/spinner_wave.go index 98d82a3e8..3ea78ee3a 100644 --- a/cmd/spinner_wave.go +++ b/cmd/spinner_wave.go @@ -5,12 +5,12 @@ import ( "strings" "unicode/utf8" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // spinnerWaveColors — 20 distinct hues; the wave flows glyph → verb → ▪▫▫. var spinnerWaveColors = [20][3]int{ - {255, 215, 0}, // Talon Gold — anchor the wave in Hawk's brand color. + {255, 215, 0}, // Talon Gold — anchor the wave in Rho's brand color. {255, 60, 60}, {255, 100, 80}, {255, 150, 60}, @@ -46,7 +46,7 @@ func ansiSpinnerWaveColor(index int) string { // renderSpinnerWaveLine paints glyph + verb + ▪▫▫ as one flowing strip (20 colors). func renderSpinnerWaveLine(glyph, verb string, wavePhase, dotPhase int) string { verbRunes := utf8.RuneCountInString(verb) - total := 1 + verbRunes + 1 + hawkTypingDots + total := 1 + verbRunes + 1 + rhoTypingDots if verb == "" { total = 1 } @@ -66,10 +66,10 @@ func renderSpinnerWaveLine(glyph, verb string, wavePhase, dotPhase int) string { } b.WriteString(renderSpinnerWaveSlot(' ', wavePhase+pos, head == pos, false)) pos++ - for i := 0; i < hawkTypingDots; i++ { + for i := 0; i < rhoTypingDots; i++ { g := icons.CircleOutline() bold := false - if i == dotPhase%hawkTypingDots { + if i == dotPhase%rhoTypingDots { g = icons.CircleFilled() bold = true } diff --git a/cmd/spinner_wave_test.go b/cmd/spinner_wave_test.go index dde12f268..085d8b852 100644 --- a/cmd/spinner_wave_test.go +++ b/cmd/spinner_wave_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // frameContainsSpinnerWave reports whether a rendered frame contains the @@ -17,7 +17,7 @@ func frameContainsSpinnerWave(s string) bool { } func TestSpinnerWave_AdvancesOnTick(t *testing.T) { - s := NewBrailleSpinner(SpinnerHawk, "Hi") + s := NewBrailleSpinner(SpinnerRho, "Hi") f1 := s.Frame() s.Tick() f2 := s.Frame() @@ -57,7 +57,7 @@ func TestSpinnerWave_FlowsThroughGlyphVerbAndDots(t *testing.T) { } func TestSpinnerWave_GlyphUsesWaveColor(t *testing.T) { - s := NewBrailleSpinner(SpinnerHawk, "Go") + s := NewBrailleSpinner(SpinnerRho, "Go") f := s.Frame() if !strings.Contains(f, "◐") && !strings.Contains(f, "◓") && !strings.Contains(f, "◑") && !strings.Contains(f, "◒") { t.Fatalf("expected compass spinner glyph, got %q", f) diff --git a/cmd/stats.go b/cmd/stats.go index 471bffd98..d17c2169a 100644 --- a/cmd/stats.go +++ b/cmd/stats.go @@ -8,7 +8,7 @@ import ( "strings" "time" - analytics "github.com/GrayCodeAI/hawk/internal/observability" + analytics "github.com/GrayCodeAI/rho/internal/observability" "github.com/spf13/cobra" ) @@ -85,7 +85,7 @@ func runStats(cmd *cobra.Command, args []string) error { if len(filtered) == 0 { cmd.Println(auditTint("No session data found for the specified time period.", textMuted)) - cmd.Println(auditTint("Sessions are recorded automatically when you use hawk.", textMuted)) + cmd.Println(auditTint("Sessions are recorded automatically when you use rho.", textMuted)) return nil } @@ -180,7 +180,7 @@ func printStatsText(cmd *cobra.Command, out *statsOutput) { _, _ = fmt.Fprintf(w, "\n") _, _ = fmt.Fprintf(w, "══════════════════════════════════════════════════\n") - _, _ = fmt.Fprintf(w, " %s\n", auditTint(fmt.Sprintf("Hawk Usage Statistics (%s)", out.Period), hawkColor)) + _, _ = fmt.Fprintf(w, " %s\n", auditTint(fmt.Sprintf("Rho Usage Statistics (%s)", out.Period), rhoColor)) _, _ = fmt.Fprintf(w, "══════════════════════════════════════════════════\n") // Overview section diff --git a/cmd/status_snapshot.go b/cmd/status_snapshot.go index eafbaa326..fc1ee6859 100644 --- a/cmd/status_snapshot.go +++ b/cmd/status_snapshot.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/plugin" - "github.com/GrayCodeAI/hawk/internal/status" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/plugin" + "github.com/GrayCodeAI/rho/internal/status" "github.com/spf13/cobra" ) @@ -34,11 +34,11 @@ var statusCmd = &cobra.Command{ func buildStatusSnapshot() status.Snapshot { snapshot := status.New() - snapshot.HawkVersion = version + snapshot.RhoVersion = version snapshot.Workspace = status.Workspace() snapshot.GitBranch = engine.InspectGitBranch("").Branch - settings := hawkconfig.LoadGlobalSettings() - selection := hawkconfig.EffectiveSelection(context.Background(), hawkconfig.SelectionOptions{}) + settings := rhoconfig.LoadGlobalSettings() + selection := rhoconfig.EffectiveSelection(context.Background(), rhoconfig.SelectionOptions{}) snapshot.Model = strings.TrimSpace(selection.Model) snapshot.Provider = strings.TrimSpace(selection.Provider) if snapshot.Model == "" { @@ -77,7 +77,7 @@ func formatStatusSnapshot(s status.Snapshot) string { return fmt.Sprintf("%s: %s\n", auditTint(label, textMuted), auditTint(val, textPrimary)) } var b strings.Builder - b.WriteString(auditTint("Hawk status", hawkColor) + "\n") + b.WriteString(auditTint("Rho status", rhoColor) + "\n") b.WriteString(line("Schema", s.SchemaVersion)) b.WriteString(line("Workspace", s.Workspace)) b.WriteString(line("Git branch", s.GitBranch)) diff --git a/cmd/status_snapshot_test.go b/cmd/status_snapshot_test.go index d7cdcffff..9196fad69 100644 --- a/cmd/status_snapshot_test.go +++ b/cmd/status_snapshot_test.go @@ -8,7 +8,7 @@ import ( func TestFormatStatusSnapshot(t *testing.T) { snapshot := buildStatusSnapshot() formatted := formatStatusSnapshot(snapshot) - for _, expected := range []string{"Hawk status", "Schema: 1", "Secrets redacted: true"} { + for _, expected := range []string{"Rho status", "Schema: 1", "Secrets redacted: true"} { if !strings.Contains(formatted, expected) { t.Errorf("status output missing %q: %s", expected, formatted) } diff --git a/cmd/statusbar.go b/cmd/statusbar.go index 058194861..a00597652 100644 --- a/cmd/statusbar.go +++ b/cmd/statusbar.go @@ -12,9 +12,9 @@ import ( "golang.org/x/text/language" "golang.org/x/text/message" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/engine/git" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine/git" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) var ( diff --git a/cmd/statusbar_test.go b/cmd/statusbar_test.go index 7c87259dc..eab7a32b5 100644 --- a/cmd/statusbar_test.go +++ b/cmd/statusbar_test.go @@ -6,7 +6,7 @@ import ( "time" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) func TestRenderStatusBar_SignatureExists(t *testing.T) { @@ -100,8 +100,8 @@ func TestRenderStatusBarLeft_UsesCachedState(t *testing.T) { func TestShortenHomePath(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) - got := shortenHomePath(home + "/project/hawk") - if got != "~/project/hawk" { + got := shortenHomePath(home + "/project/rho") + if got != "~/project/rho" { t.Fatalf("got %q", got) } } diff --git a/cmd/storage_policy_test.go b/cmd/storage_policy_test.go index 20025a6d8..7bd694163 100644 --- a/cmd/storage_policy_test.go +++ b/cmd/storage_policy_test.go @@ -7,10 +7,10 @@ import ( "testing" ) -func TestStoragePolicyHelpersDoNotCreateProjectHawk(t *testing.T) { +func TestStoragePolicyHelpersDoNotCreateProjectRho(t *testing.T) { project := t.TempDir() - t.Setenv("HAWK_STATE_DIR", filepath.Join(t.TempDir(), "state")) - t.Setenv("HAWK_CACHE_DIR", filepath.Join(t.TempDir(), "cache")) + t.Setenv("RHO_STATE_DIR", filepath.Join(t.TempDir(), "state")) + t.Setenv("RHO_CACHE_DIR", filepath.Join(t.TempDir(), "cache")) oldwd, err := os.Getwd() if err != nil { t.Fatal(err) @@ -21,13 +21,13 @@ func TestStoragePolicyHelpersDoNotCreateProjectHawk(t *testing.T) { } planPath := resolvePlanPath("demo") - if strings.Contains(planPath, filepath.Join(project, ".hawk")) { - t.Fatalf("resolvePlanPath leaked project .hawk: %q", planPath) + if strings.Contains(planPath, filepath.Join(project, ".rho")) { + t.Fatalf("resolvePlanPath leaked project .rho: %q", planPath) } saveInputHistory([]string{"hello"}) recordTipShown("slash-help") - if _, err := os.Stat(filepath.Join(project, ".hawk")); !os.IsNotExist(err) { - t.Fatalf("normal storage helpers created project .hawk, stat err=%v", err) + if _, err := os.Stat(filepath.Join(project, ".rho")); !os.IsNotExist(err) { + t.Fatalf("normal storage helpers created project .rho, stat err=%v", err) } } diff --git a/cmd/tabcomplete.go b/cmd/tabcomplete.go index dcd95fc07..da9b9a6e6 100644 --- a/cmd/tabcomplete.go +++ b/cmd/tabcomplete.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/tool" ) // lruCache is a simple fixed-size LRU cache for directory listing results. diff --git a/cmd/tabcomplete_test.go b/cmd/tabcomplete_test.go index 3f1ba84b0..57a7655c9 100644 --- a/cmd/tabcomplete_test.go +++ b/cmd/tabcomplete_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestFilePathCompletions_CurrentDir(t *testing.T) { diff --git a/cmd/taste.go b/cmd/taste.go index f300f9463..ebcf7fa9b 100644 --- a/cmd/taste.go +++ b/cmd/taste.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/feature/taste" + "github.com/GrayCodeAI/rho/internal/feature/taste" "github.com/spf13/cobra" ) @@ -36,8 +36,8 @@ var tastePushCmd = &cobra.Command{ with teammates or used across machines. Examples: - hawk taste push # Export to stdout - hawk taste push --file team.json # Export to file`, + rho taste push # Export to stdout + rho taste push --file team.json # Export to file`, RunE: runTastePush, } @@ -48,8 +48,8 @@ var tastePullCmd = &cobra.Command{ will be merged with your existing profile (not replaced). Examples: - hawk taste pull team.json - cat team.json | hawk taste pull -`, + rho taste pull team.json + cat team.json | rho taste pull -`, Args: cobra.ExactArgs(1), RunE: runTastePull, } diff --git a/cmd/terminal_notify.go b/cmd/terminal_notify.go index 074f97a3e..7bc213beb 100644 --- a/cmd/terminal_notify.go +++ b/cmd/terminal_notify.go @@ -46,7 +46,7 @@ func sendTerminalNotification(title, body string) { _, _ = fmt.Fprintf(os.Stderr, "\033]9;%s\007", body) case "kitty": // Kitty OSC 99 notification - _, _ = fmt.Fprintf(os.Stderr, "\033]99;i=hawk:d=0;%s\033\\", body) + _, _ = fmt.Fprintf(os.Stderr, "\033]99;i=rho:d=0;%s\033\\", body) case "ghostty": // Ghostty OSC 777 notification _, _ = fmt.Fprintf(os.Stderr, "\033]777;notify;%s;%s\033\\", title, body) diff --git a/cmd/textutil_shim.go b/cmd/textutil_shim.go index 51fcb7356..09229a4df 100644 --- a/cmd/textutil_shim.go +++ b/cmd/textutil_shim.go @@ -1,6 +1,6 @@ package cmd -import "github.com/GrayCodeAI/hawk/internal/textutil" +import "github.com/GrayCodeAI/rho/internal/textutil" // truncateWithEllipsis truncates s to at most max runes, appending "..." when // content is dropped. Rune-safe: never splits a multi-byte character. diff --git a/cmd/theme.go b/cmd/theme.go index fad9defab..241177b75 100644 --- a/cmd/theme.go +++ b/cmd/theme.go @@ -1,11 +1,11 @@ package cmd -// theme.go — the single source of truth for hawk's visual identity. +// theme.go — the single source of truth for rho's visual identity. // // All color constants (24-bit RGB via lipgloss), raw ANSI escape codes // (used by the spinner line), and glyph/icon constants live here. Every // other file in the package references these names instead of repeating -// hex codes or magic strings. To rebrand hawk, edit this file; to audit +// hex codes or magic strings. To rebrand rho, edit this file; to audit // what's used where, grep this file. // // Organization: @@ -24,17 +24,17 @@ import ( lipgloss "charm.land/lipgloss/v2" "charm.land/lipgloss/v2/compat" - internaltheme "github.com/GrayCodeAI/hawk/internal/theme" + internaltheme "github.com/GrayCodeAI/rho/internal/theme" ) // --------------------------------------------------------------------------- // 1. Brand & identity // --------------------------------------------------------------------------- -// hawkColor is Talon Gold (#FFD700). Used for the HAWK wordmark, hawk, +// rhoColor is Talon Gold (#FFD700). Used for the RHO wordmark, rho, // ⛬ assistant prefix, prompt arrow, cursor, exit prompt, and // any place that should "speak" as the brand. -var hawkColor = lipgloss.Color(internaltheme.BrandPrimary) +var rhoColor = lipgloss.Color(internaltheme.BrandPrimary) // --------------------------------------------------------------------------- // 2. UI state @@ -197,7 +197,7 @@ const ( // --------------------------------------------------------------------------- // 10. Icons & glyphs // -// Hawk's icon registry lives in internal/ui/icons. Every call site +// Rho's icon registry lives in internal/ui/icons. Every call site // references icons.ChevronRight() / icons.Robot() / etc. directly; this // file no longer holds any glyph constants. The audit test in // internal/testaudit fails CI if any non-ASCII literal appears in @@ -240,7 +240,7 @@ func ApplyTheme(name string) { p := entry.Palette // 1. Brand — fixed across themes; palette accents remain theme-specific. - hawkColor = lipgloss.Color(internaltheme.BrandPrimary) + rhoColor = lipgloss.Color(internaltheme.BrandPrimary) // 2. Semantic feedback. successTeal = lipgloss.Color(p.Green) @@ -280,7 +280,10 @@ func ApplyTheme(name string) { borderDim = compat.AdaptiveColor{Light: lipgloss.Color("#C6C6C6"), Dark: lipgloss.Color(p.Line2)} bgCode = lipgloss.Color(p.Panel) - // 9. Dark-background flag is now handled via LightDark in v2; no setter needed. + // 9. Minimal chrome: the Tau theme (and any future low-chrome theme) uses + // a hairline separator under the input instead of a full box border. + // refreshThemeStyles rebuilds inputBorderStyle from this flag. + minimalChrome = entry.Name == "tau" // 10. Rebuild package-level styles that snapshotted the old colors at // init time. Without this, markdown/chat/agent-grid styles keep the @@ -288,6 +291,10 @@ func ApplyTheme(name string) { refreshThemeStyles() } +// minimalChrome is set by ApplyTheme when the active theme prefers a +// low-chrome layout (no box borders around the input). +var minimalChrome bool + // refreshThemeStyles rebuilds every package-level lipgloss.Style var that // captures a theme color at package-init time. ApplyTheme mutates the color // vars, but styles constructed from them hold copies — so each one must be @@ -298,12 +305,12 @@ func ApplyTheme(name string) { // unsynchronized from View. func refreshThemeStyles() { // markdown.go - mdH1Style = lipgloss.NewStyle().Foreground(hawkColor).Bold(true).Underline(true) + mdH1Style = lipgloss.NewStyle().Foreground(rhoColor).Bold(true).Underline(true) mdH2Style = lipgloss.NewStyle().Foreground(successTeal).Bold(true) mdH3Style = lipgloss.NewStyle().Foreground(infoSky).Bold(true) mdH4Style = lipgloss.NewStyle().Foreground(costViolet).Bold(true) mdHeaderStyle = lipgloss.NewStyle().Foreground(textPrimary).Bold(true) - mdBoldStyle = lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + mdBoldStyle = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) mdInlineCodeStyle = lipgloss.NewStyle().Foreground(infoSky) mdCodeBlockStyle = lipgloss.NewStyle().Background(bgCode) mdCodeLabelStyle = lipgloss.NewStyle().Foreground(textDisabled).Background(bgCode) @@ -322,13 +329,18 @@ func refreshThemeStyles() { toolDimStyle = lipgloss.NewStyle().Foreground(textDisabled) slashCmdStyle = lipgloss.NewStyle().Foreground(textDisabled) slashDescStyle = lipgloss.NewStyle().Foreground(textDisabled) - slashSelCmdStyle = lipgloss.NewStyle().Foreground(hawkColor).Bold(true) - slashSelDescStyle = lipgloss.NewStyle().Foreground(hawkColor) - inputBorderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false, true, false).BorderForeground(borderDim) + slashSelCmdStyle = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + slashSelDescStyle = lipgloss.NewStyle().Foreground(rhoColor) + if minimalChrome { + // Tau-style: a single hairline rule under the input, no side borders. + inputBorderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, true, false).BorderForeground(borderDim) + } else { + inputBorderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false, true, false).BorderForeground(borderDim) + } dimColor = textDisabled // agent_grid.go - agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(hawkColor).Padding(0, 1) + agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(rhoColor).Padding(0, 1) agentDoneStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(doneGreen).Padding(0, 1) agentFailStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(errorCoral).Padding(0, 1) agentIdleStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(textDisabled).Padding(0, 1) @@ -336,5 +348,5 @@ func refreshThemeStyles() { agentStatusStyle = lipgloss.NewStyle().Foreground(textMuted) // chat_scrollbar.go - scrollbarThumbStyle = lipgloss.NewStyle().Foreground(hawkColor) + scrollbarThumbStyle = lipgloss.NewStyle().Foreground(rhoColor) } diff --git a/cmd/theme_chrome_test.go b/cmd/theme_chrome_test.go new file mode 100644 index 000000000..d627cb880 --- /dev/null +++ b/cmd/theme_chrome_test.go @@ -0,0 +1,52 @@ +package cmd + +import "testing" + +// These tests call ApplyTheme, which mutates package-level color vars. Snapshot +// and restore them with restoreThemeGlobals so a palette swap cannot leak into +// unrelated tests (e.g. TestAdaptiveNeutralsPreserveDarkAppearance). Calling +// ApplyTheme("dark") as cleanup is not equivalent: it writes the dark palette's +// values, which differ from the init-time defaults the adaptive test locks. + +func TestApplyThemeTauEnablesMinimalChrome(t *testing.T) { + restoreThemeGlobals(t) + prev := minimalChrome + t.Cleanup(func() { minimalChrome = prev }) + + ApplyTheme("tau") + if !minimalChrome { + t.Fatal("ApplyTheme(tau) should enable minimal chrome") + } + // Tau keeps only the bottom hairline: no top rule, no side rules. + if inputBorderStyle.GetBorderTop() || inputBorderStyle.GetBorderLeft() || inputBorderStyle.GetBorderRight() { + t.Error("tau input border should draw only the bottom hairline") + } + if !inputBorderStyle.GetBorderBottom() { + t.Error("tau input border should keep the bottom hairline") + } +} + +func TestApplyThemeDarkUsesBoxChrome(t *testing.T) { + restoreThemeGlobals(t) + prev := minimalChrome + t.Cleanup(func() { minimalChrome = prev }) + + ApplyTheme("dark") + if minimalChrome { + t.Fatal("ApplyTheme(dark) should not enable minimal chrome") + } + // The default chrome draws a top and bottom rule around the input. + if !inputBorderStyle.GetBorderTop() || !inputBorderStyle.GetBorderBottom() { + t.Error("dark input border should keep top and bottom rules") + } +} + +func TestApplyThemeUnknownIsNoop(t *testing.T) { + restoreThemeGlobals(t) + ApplyTheme("dark") + before := minimalChrome + ApplyTheme("does-not-exist") + if minimalChrome != before { + t.Error("ApplyTheme with an unknown name must not change state") + } +} diff --git a/cmd/theme_picker.go b/cmd/theme_picker.go index 3e6026a32..b6529213e 100644 --- a/cmd/theme_picker.go +++ b/cmd/theme_picker.go @@ -7,13 +7,13 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - internaltheme "github.com/GrayCodeAI/hawk/internal/theme" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + internaltheme "github.com/GrayCodeAI/rho/internal/theme" ) // detectAutoIsDark returns whether "auto" theme currently resolves to dark. func detectAutoIsDark() bool { - settings := hawkconfig.LoadGlobalSettings() + settings := rhoconfig.LoadGlobalSettings() return settings.Theme == "auto" || settings.Theme == "system" || internaltheme.DetectOSTheme() != "light" } @@ -152,7 +152,7 @@ func (tp *ThemePicker) View() tea.View { } titleStyle := lipgloss.NewStyle(). - Background(hawkColor). + Background(rhoColor). Foreground(lipgloss.Color("#FFFFFF")). Bold(true). Padding(0, 1) @@ -166,9 +166,9 @@ func (tp *ThemePicker) View() tea.View { for i, e := range tp.entries { if i == tp.sel { - rowStyle := lipgloss.NewStyle().Foreground(hawkColor).Bold(true) + rowStyle := lipgloss.NewStyle().Foreground(rhoColor).Bold(true) b.WriteString(fmt.Sprintf(" ▶ %s\n", rowStyle.Render(e.Name))) - b.WriteString(fmt.Sprintf(" %s\n", lipgloss.NewStyle().Foreground(hawkColor).Render(e.Desc))) + b.WriteString(fmt.Sprintf(" %s\n", lipgloss.NewStyle().Foreground(rhoColor).Render(e.Desc))) } else { nameStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#D0D0D0")) b.WriteString(fmt.Sprintf(" %s\n", nameStyle.Render(e.Name))) diff --git a/cmd/tips.go b/cmd/tips.go index 8d45762c4..076b257bd 100644 --- a/cmd/tips.go +++ b/cmd/tips.go @@ -8,10 +8,10 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) -// Tip represents a single hawk usage tip. +// Tip represents a single rho usage tip. type Tip struct { ID string `json:"id"` Text string `json:"text"` @@ -30,7 +30,7 @@ func allTips() []Tip { {ID: "tab-complete", Text: "Press Tab to autocomplete slash commands.", Category: "shortcuts"}, {ID: "history-nav", Text: "Press Up/Down to navigate command history.", Category: "shortcuts"}, {ID: "esc-cancel", Text: "Press Esc to cancel a running query.", Category: "shortcuts"}, - {ID: "ctrl-c-quit", Text: "Press Ctrl+C twice to quit hawk.", Category: "shortcuts"}, + {ID: "ctrl-c-quit", Text: "Press Ctrl+C twice to quit rho.", Category: "shortcuts"}, {ID: "copy-chat", Text: "Ctrl+Shift+C or /copy copies chat; /copy input copies your draft; /mouse off enables click-drag select.", Category: "shortcuts"}, {ID: "vim-mode", Text: "Use /vim to toggle vim-style keybindings.", Category: "editing"}, {ID: "model-switch", Text: "Use /model to switch LLM models on the fly.", Category: "config"}, @@ -51,7 +51,7 @@ func allTips() []Tip { {ID: "slash-start", Text: "Use /start for guided setup: trust, mode, branch, first tasks.", Category: "basics"}, {ID: "slash-mode-plan", Text: "Use /mode plan to research read-only, then /mode act to implement.", Category: "workflow"}, {ID: "slash-trust", Text: "Use /trust add so project hooks and MCP can load (folder trust).", Category: "safety"}, - {ID: "slash-branch-agent", Text: "Use /branch-agent before big edits on main — creates hawk/agent-* branch.", Category: "git"}, + {ID: "slash-branch-agent", Text: "Use /branch-agent before big edits on main — creates rho/agent-* branch.", Category: "git"}, {ID: "tool-search-select", Text: "Use ToolSearch select:Impact (etc.) to unlock optional tools on the lazy surface.", Category: "tools"}, {ID: "slash-auto-commit", Text: "Use /auto-commit on so Write/Edit create git commits automatically.", Category: "git"}, } diff --git a/cmd/tips_test.go b/cmd/tips_test.go index 9248b5807..a9595ac70 100644 --- a/cmd/tips_test.go +++ b/cmd/tips_test.go @@ -34,7 +34,7 @@ func TestNextTip_ReturnsNonEmpty(t *testing.T) { orig := os.Getenv("HOME") tmp := t.TempDir() os.Setenv("HOME", tmp) - os.MkdirAll(filepath.Join(tmp, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(tmp, ".rho"), 0o755) defer os.Setenv("HOME", orig) tip := nextTip("") @@ -47,7 +47,7 @@ func TestRecordTipShown(t *testing.T) { orig := os.Getenv("HOME") tmp := t.TempDir() os.Setenv("HOME", tmp) - os.MkdirAll(filepath.Join(tmp, ".hawk"), 0o755) + os.MkdirAll(filepath.Join(tmp, ".rho"), 0o755) defer os.Setenv("HOME", orig) recordTipShown("slash-help") diff --git a/cmd/toolset_cmd.go b/cmd/toolset_cmd.go index b6b353c44..8376b5d6f 100644 --- a/cmd/toolset_cmd.go +++ b/cmd/toolset_cmd.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/toolset" + "github.com/GrayCodeAI/rho/internal/toolset" "github.com/spf13/cobra" ) @@ -17,8 +17,8 @@ var toolsetCmd = &cobra.Command{ Short: "List or resolve composable tool groups", Long: `Named, composable tool groups for scoping an agent's tool surface. - hawk toolset List available toolsets - hawk toolset research Resolve 'research' to its concrete tool list + rho toolset List available toolsets + rho toolset research Resolve 'research' to its concrete tool list Toolsets compose from other toolsets; resolving expands Requires transitively (cycle-safe) and de-duplicates.`, diff --git a/cmd/trust.go b/cmd/trust.go index 6c9f92e4e..be0b6d713 100644 --- a/cmd/trust.go +++ b/cmd/trust.go @@ -5,10 +5,10 @@ import ( "fmt" "os" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/theme" - "github.com/GrayCodeAI/hawk/internal/flags" - "github.com/GrayCodeAI/hawk/internal/trust" + "github.com/GrayCodeAI/rho/internal/flags" + "github.com/GrayCodeAI/rho/internal/trust" "github.com/spf13/cobra" ) @@ -18,10 +18,10 @@ var trustCmd = &cobra.Command{ Long: `Folder trust controls whether project-scoped hooks, MCP servers, LSP configs, and plugins may load from a repository. -When HAWK_Y0_FOLDER_TRUST is enabled (default after Year 0 PACK-03), +When RHO_Y0_FOLDER_TRUST is enabled (default after Year 0 PACK-03), untrusted projects cannot run project automation (RCE mitigation). -User-global plugins under the Hawk state directory always load.`, +User-global plugins under the Rho state directory always load.`, } var trustAddCmd = &cobra.Command{ @@ -103,7 +103,7 @@ var trustListCmd = &cobra.Command{ fmt.Println("[]") } else { cmd.Println(auditTint("No trusted directories.", textMuted)) - cmd.Printf("%s\n", auditTint(fmt.Sprintf("Folder trust enforcement: %v (HAWK_Y0_FOLDER_TRUST)", flags.FolderTrust()), textMuted)) + cmd.Printf("%s\n", auditTint(fmt.Sprintf("Folder trust enforcement: %v (RHO_Y0_FOLDER_TRUST)", flags.FolderTrust()), textMuted)) } return nil } diff --git a/cmd/usage.go b/cmd/usage.go index 117556632..4ec3429c1 100644 --- a/cmd/usage.go +++ b/cmd/usage.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/GrayCodeAI/hawk/internal/usage" + "github.com/GrayCodeAI/rho/internal/usage" "github.com/spf13/cobra" ) @@ -65,7 +65,7 @@ func runUsage(cmd *cobra.Command, _ []string) error { return nil } - cmd.Println(auditTint(fmt.Sprintf("Usage (last %s)", usagePeriod), hawkColor)) + cmd.Println(auditTint(fmt.Sprintf("Usage (last %s)", usagePeriod), rhoColor)) cmd.Println(auditTint(fmt.Sprintf("%-28s %10s %10s %8s %12s", "model", "in", "out", "gen", "cost"), textMuted)) for _, m := range sum.ByModel { // Pad the cost to its column width first, then colorize, so the diff --git a/cmd/verify_cmd.go b/cmd/verify_cmd.go index 754e4274b..45559f29a 100644 --- a/cmd/verify_cmd.go +++ b/cmd/verify_cmd.go @@ -8,9 +8,9 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/governance" - "github.com/GrayCodeAI/hawk/internal/securitylog" - "github.com/GrayCodeAI/hawk/internal/testrunner" + "github.com/GrayCodeAI/rho/internal/governance" + "github.com/GrayCodeAI/rho/internal/securitylog" + "github.com/GrayCodeAI/rho/internal/testrunner" "github.com/spf13/cobra" ) @@ -19,7 +19,7 @@ import ( var verifyCmd = &cobra.Command{ Use: "verify", Short: "Run local self-verification (security log, governance policy)", - Long: `Run hawk's self-verification checks without a model: + Long: `Run rho's self-verification checks without a model: 1. The tamper-evident security event log hash chain is intact. 2. The managed governance policy (if installed) parses and validates. diff --git a/cmd/version_display.go b/cmd/version_display.go index 2c008d81f..4159c3601 100644 --- a/cmd/version_display.go +++ b/cmd/version_display.go @@ -7,13 +7,13 @@ import ( ) // versionLine is the single user-facing version format shared by -// `hawk --version` and `hawk version`. +// `rho --version` and `rho version`. func versionLine() string { ver := DisplayVersion() if ver != "" && !strings.HasPrefix(ver, "v") && !strings.HasPrefix(ver, "V") { ver = "v" + ver } - line := auditTint("hawk", textPrimary) + " " + auditTint(ver, hawkColor) + line := auditTint("rho", textPrimary) + " " + auditTint(ver, rhoColor) if d := strings.TrimSpace(buildDate); d != "" && d != "unknown" { line += auditTint(" (built "+d+")", textMuted) } diff --git a/cmd/version_display_test.go b/cmd/version_display_test.go index 7047f4b3c..bd96c01bc 100644 --- a/cmd/version_display_test.go +++ b/cmd/version_display_test.go @@ -5,8 +5,8 @@ import ( "path/filepath" "testing" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestDisplayVersion_FromVERSIONFile(t *testing.T) { @@ -29,12 +29,12 @@ func TestDisplayVersion_ReleaseBuild(t *testing.T) { } func TestChatConnectionStatus_NoCredentials(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{session: nil} @@ -45,12 +45,12 @@ func TestChatConnectionStatus_NoCredentials(t *testing.T) { } func TestChatBottomRightStatus_NoCredentials(t *testing.T) { - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - hawkconfig.InvalidateConfigUICache() + rhoconfig.InvalidateConfigUICache() }) m := chatModel{inputIndicator: &InputIndicator{}} diff --git a/cmd/vibe.go b/cmd/vibe.go index 4a159c859..ccafaa365 100644 --- a/cmd/vibe.go +++ b/cmd/vibe.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // VibeConfig controls vibe coding behavior. diff --git a/cmd/visual_diff.go b/cmd/visual_diff.go index 0f77fee31..238553b03 100644 --- a/cmd/visual_diff.go +++ b/cmd/visual_diff.go @@ -35,7 +35,7 @@ type DiffTheme struct { Reset string } -// DefaultDiffTheme returns a DiffTheme using hawk's semantic palette so diff +// DefaultDiffTheme returns a DiffTheme using rho's semantic palette so diff // output reads consistently with the rest of the UI: additions in doneGreen, // deletions in errorCoral, hunk markers in infoSky, file headers in // containerBlue — each color mapped to one meaning, never reused for another. diff --git a/cmd/welcome_banner.go b/cmd/welcome_banner.go index 94cf06425..6a4874e0f 100644 --- a/cmd/welcome_banner.go +++ b/cmd/welcome_banner.go @@ -6,8 +6,8 @@ import ( "github.com/mattn/go-runewidth" ) -// hawkBlockGlyphs — fixed 8-column ██ font used by the welcome gate banners. -var hawkBlockGlyphs = map[rune][5]string{ +// rhoBlockGlyphs — fixed 8-column ██ font used by the welcome gate banners. +var rhoBlockGlyphs = map[rune][5]string{ 'H': {"██ ██ ", "██ ██ ", "███████ ", "██ ██ ", "██ ██ "}, 'A': {" ███ ", " █████ ", "███████ ", "██ ██ ", "██ ██ "}, 'W': {"██ ██ ", "██ ██ ", "██ █ ██ ", "███ ███ ", "██ ██ "}, @@ -20,8 +20,8 @@ var hawkBlockGlyphs = map[rune][5]string{ 'T': {"████████", " ██ ", " ██ ", " ██ ", " ██ "}, } -// hawkLogoArtLines is the canonical HAWK wordmark, with the hawk forming the W. -var hawkLogoArtLines = []string{ +// rhoLogoArtLines is the canonical RHO wordmark, with the rho forming the W. +var rhoLogoArtLines = []string{ " . .", " . . . .", " . | | .", @@ -40,19 +40,19 @@ var hawkLogoArtLines = []string{ } const ( - hawkBlockCellW = 8 - hawkBlockLetterGap = 1 - hawkBlockWordGap = 4 + rhoBlockCellW = 8 + rhoBlockLetterGap = 1 + rhoBlockWordGap = 4 ) // welcomeWordLines — "WELCOME" block (row-aligned, fixed grid). -var welcomeWordLines = composeHawkBlockLines("WELCOME") +var welcomeWordLines = composeRhoBlockLines("WELCOME") // welcomeToWordLines — "TO" block, centered under WELCOME on the gate. -var welcomeToWordLines = composeHawkBlockLines("TO") +var welcomeToWordLines = composeRhoBlockLines("TO") // welcomeToPhraseLines — "WELCOME TO" block for wide welcome gates. -var welcomeToPhraseLines = composeHawkBlockLines("WELCOME TO") +var welcomeToPhraseLines = composeRhoBlockLines("WELCOME TO") // welcomeToBannerMinWidth is the visible width for the WELCOME block. const welcomeToBannerMinWidth = 61 @@ -60,24 +60,24 @@ const welcomeToBannerMinWidth = 61 // welcomeToPhraseMinWidth is the visible width for the combined "WELCOME TO" block. var welcomeToPhraseMinWidth = blockLinesWidth(welcomeToPhraseLines) -func composeHawkBlockLines(text string) []string { +func composeRhoBlockLines(text string) []string { rows := make([]string, 5) words := strings.Fields(text) for wi, word := range words { for ci, ch := range word { - glyph, ok := hawkBlockGlyphs[ch] + glyph, ok := rhoBlockGlyphs[ch] if !ok { continue } for i := range rows { if rows[i] != "" { if ci == 0 && wi > 0 { - rows[i] += strings.Repeat(" ", hawkBlockWordGap) + rows[i] += strings.Repeat(" ", rhoBlockWordGap) } else { - rows[i] += strings.Repeat(" ", hawkBlockLetterGap) + rows[i] += strings.Repeat(" ", rhoBlockLetterGap) } } - cell := padBlockCell(glyph[i], hawkBlockCellW) + cell := padBlockCell(glyph[i], rhoBlockCellW) rows[i] += cell } } diff --git a/cmd/welcome_banner_test.go b/cmd/welcome_banner_test.go index d79ef1e83..d87d359c5 100644 --- a/cmd/welcome_banner_test.go +++ b/cmd/welcome_banner_test.go @@ -8,7 +8,7 @@ import ( ) func TestWelcomeToBanner_TOAligned(t *testing.T) { - to := composeHawkBlockLines("TO") + to := composeRhoBlockLines("TO") if len(to) != 5 { t.Fatalf("expected 5 rows, got %d", len(to)) } @@ -48,10 +48,10 @@ func TestWelcomeToPhraseLinesContainSingleRowPhrase(t *testing.T) { } } -func TestWelcomeWordLines_SameWGlyphAsHAWK(t *testing.T) { - w := strings.TrimRight(hawkBlockGlyphs['W'][0], " ") +func TestWelcomeWordLines_SameWGlyphAsRHO(t *testing.T) { + w := strings.TrimRight(rhoBlockGlyphs['W'][0], " ") if !strings.HasPrefix(welcomeWordLines[0], w) { - t.Fatalf("W row should start with hawk logo glyph:\nwelcome: %q\nhawk W: %q", + t.Fatalf("W row should start with rho logo glyph:\nwelcome: %q\nrho W: %q", welcomeWordLines[0], w) } } diff --git a/cmd/welcome_inline_test.go b/cmd/welcome_inline_test.go index 96f09c5af..e2567c452 100644 --- a/cmd/welcome_inline_test.go +++ b/cmd/welcome_inline_test.go @@ -8,9 +8,9 @@ import ( "charm.land/bubbles/v2/textarea" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) type welcomeMCPStub struct { @@ -26,7 +26,7 @@ func TestWelcomeScreenNerdIconsUnique(t *testing.T) { icons.SetMode(icons.ModeNerd) defer icons.SetMode(icons.ModeASCII) - out := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, false, 100, 24) + out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 100, 24) seen := make(map[rune]struct{}) for _, r := range out { if r < 0xE000 || r > 0xF8FF { @@ -48,7 +48,7 @@ func (s welcomeMCPStub) Execute(context.Context, json.RawMessage) (string, error func (s welcomeMCPStub) MCPServerName() string { return s.server } func TestBuildWelcomeMessage_InlineShowsSetupGuidance(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, false, 100, 24) + out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 100, 24) if !strings.Contains(out, "v") { t.Fatalf("inline welcome should show version, got:\n%s", out) } @@ -58,7 +58,7 @@ func TestBuildWelcomeMessage_InlineShowsSetupGuidance(t *testing.T) { } func TestBuildWelcomeMessage_InlineShowsGuidance(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, false, 100, 24) + out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 100, 24) for _, want := range []string{"Skills (0)", "AGENTS.md", "MCPs (0)"} { if !strings.Contains(out, want) { t.Fatalf("minimal welcome missing %q in:\n%s", want, out) @@ -100,7 +100,7 @@ func TestBuildWelcomeMessage_InlineShowsGuidance(t *testing.T) { } func TestBuildWelcomeMessage_ShortTerminalUsesCompactCopy(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, false, 72, 20) + out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 72, 20) if strings.Contains(out, "PgUp/Dn scroll chat") || strings.Contains(out, "for new session") { t.Fatalf("compact welcome should drop verbose descriptions, got:\n%s", out) } @@ -109,23 +109,23 @@ func TestBuildWelcomeMessage_ShortTerminalUsesCompactCopy(t *testing.T) { } } -func TestBuildWelcomeMessage_WideTerminalUsesHawkWordmark(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, false, 120, 40) +func TestBuildWelcomeMessage_WideTerminalUsesRhoWordmark(t *testing.T) { + out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 120, 40) for _, want := range []string{ "___ ___ _________", "(\\.|\\/|./)", "|0\\/0|", } { if !strings.Contains(out, want) { - t.Fatalf("wide welcome missing hawk wordmark line %q in:\n%s", want, out) + t.Fatalf("wide welcome missing rho wordmark line %q in:\n%s", want, out) } } } -func TestBuildWelcomeMessage_HawkWordmarkBlinks(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, hawkconfig.Settings{}, 0, true, 120, 40) +func TestBuildWelcomeMessage_RhoWordmarkBlinks(t *testing.T) { + out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, true, 120, 40) if !strings.Contains(out, "|-\\/-|") { - t.Fatalf("blinking welcome should close the hawk's eyes, got:\n%s", out) + t.Fatalf("blinking welcome should close the rho's eyes, got:\n%s", out) } } diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index 8c9ffb741..80c4e95a7 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -1,7 +1,7 @@ -name: hawk +name: rho services: - hawk: + rho: build: context: ../../ dockerfile: Dockerfile @@ -11,7 +11,7 @@ services: VERSION: ${VERSION:-dev} COMMIT: ${COMMIT:-none} BUILD_DATE: ${BUILD_DATE:-unknown} - image: ghcr.io/graycodeai/hawk:dev + image: ghcr.io/graycodeai/rho:dev # Override the Dockerfile's default `CMD ["--help"]` so the container # actually runs the HTTP/SSE daemon. `--host 0.0.0.0` is required for # the published port to be reachable from outside the container. @@ -19,9 +19,9 @@ services: ports: - "4590:4590" environment: - - HAWK_DAEMON_API_KEY=${HAWK_DAEMON_API_KEY:-} - - HAWK_DAEMON_PORT=4590 - - HAWK_DAEMON_HOST=0.0.0.0 + - RHO_DAEMON_API_KEY=${RHO_DAEMON_API_KEY:-} + - RHO_DAEMON_PORT=4590 + - RHO_DAEMON_HOST=0.0.0.0 env_file: - path: ../../.env.example required: false diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md index 4ac27f84f..aa24a9210 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -1,6 +1,6 @@ # Benchmarks -Published, reproducible CPU benchmarks for hawk. These are TUI-independent +Published, reproducible CPU benchmarks for rho. These are TUI-independent measurements (no latency theater) run with existing `go test -bench` targets; they exist so performance is documented and regressions are visible, not to make marketing claims. diff --git a/docs/COMPETITIVE.md b/docs/COMPETITIVE.md index 6e739c7ce..22f4fb37a 100644 --- a/docs/COMPETITIVE.md +++ b/docs/COMPETITIVE.md @@ -1,7 +1,7 @@ -# hawk vs Top 20 OSS — Competitive Analysis +# rho vs Top 20 OSS — Competitive Analysis Status: Implemented (branch `feat/competitive-analysis-top20`, 2026-09-09) -Scope: hawk (this repo) vs 10 AI coding CLIs + 6 dev CLIs + 4 terminals (incl. `herdr` multiplexer) +Scope: rho (this repo) vs 10 AI coding CLIs + 6 dev CLIs + 4 terminals (incl. `herdr` multiplexer) Related: `docs/plans/toolbench-comparison-vs-top20.md` (tool-count parity), `docs/SECURITY-DEVELOPER.md` (sandbox model), `docs/plans/pi-adoption-plan.md` (Kitty graphics already proposed), `docs/RESEARCH.md` (top-20 research-paper comparison + implementation record) ## Methodology (no assumptions) @@ -12,13 +12,13 @@ Verified from source in this repo: - Browser/Screenshot: `tool.BrowserTool{}`, `tool.ScreenshotTool{}` in essential set (`cmd/chat_tools.go:75-76`); headless Chrome via chromedp per prior plan. - Sandbox: mandatory Docker, fail-closed, never host fallback — `docs/SECURITY-DEVELOPER.md:71-73`, `internal/sandbox/container.go:72-74`. - Creds: OS secret store only, no `.env`/env read — `docs/SECURITY-DEVELOPER.md:7-12`. -- Share: local deeplink only — `internal/session/export.go:801-819` returns `hawk://share/`, no hosted URL. +- Share: local deeplink only — `internal/session/export.go:801-819` returns `rho://share/`, no hosted URL. - Custom providers: supported — `internal/config/settings.go:50` (`custom_providers`), `internal/config/eyrie_engine.go:32-50`. - Unwired backends: `internal/tool/computer_use.go:67-94` (`SetComputerBackend`, nil default), `internal/tool/media_generation.go:69-71` (`SetMediaEngine`, nil default). - Terminal detect covers kitty/ghostty/wezterm/alacritty names (`internal/ui/icons/detect_test.go:56`); Kitty graphics protocol not implemented (see `docs/plans/pi-adoption-plan.md:25`). - Bench infra exists (`internal/feature/eval/`, `make bench`) but README publishes no numbers. -External star counts below are approximate web-search snapshots (2026-09-08), not repo-verified. Treat as order-of-magnitude traction, not exact rankings. hawk is pre-release (`VERSION`: `0.0.1`, `README.md:40-44` source-build primary) — it competes on architecture, not stars. +External star counts below are approximate web-search snapshots (2026-09-08), not repo-verified. Treat as order-of-magnitude traction, not exact rankings. rho is pre-release (`VERSION`: `0.0.1`, `README.md:40-44` source-build primary) — it competes on architecture, not stars. ## The 20 @@ -41,7 +41,7 @@ External star counts below are approximate web-search snapshots (2026-09-08), no ### B. Dev CLIs (substrate + UX bar) -| # | Repo | Stars~ | Lang / Lic | Lesson for hawk | +| # | Repo | Stars~ | Lang / Lic | Lesson for rho | |---|---|---|---|---| | B1 | `junegunn/fzf` | ~82k | Go, MIT | Pipe-first Unix design; zero-config speed | | B2 | `jesseduffield/lazygit` | ~82k | Go, MIT | Keyboard TUI that makes hard git trivial; closest Go-TUI comp | @@ -61,10 +61,10 @@ External star counts below are approximate web-search snapshots (2026-09-08), no ## Deep dimensions -1. **Traction.** hawk has no star-moat (pre-release). Leaders won via day-1 provider-agnostic + one-liner install + Web/Desktop alongside TUI. hawk already ships script/brew/npm paths (`README.md:48-59`) — keep, don't add Desktop. -2. **Language/distro.** Go+MIT+zero-CGO (`Makefile:54`, `go.mod:3`) matches `gh/fzf/lazygit` enterprise-safe profile. Avoid GPL/EUPL patterns (kitty/eza). Rust wave wins on published benchmarks — hawk has `make bench` but publishes none (Gap-04). -3. **Providers.** hawk routes only via `eyrie/engine` facade (`docs/SECURITY-DEVELOPER.md:51-56`, `ecosystem.yaml:29-30`); custom OpenAI-compat supported (`internal/config/settings.go:50`). Count messaging ("28 first-class" per README) trails OpenCode 75+ / Hermes 300+ — fix by exposing catalog count dynamically, not by forking providers into CLI (ownership lives in router per AGENTS.md). -4. **TUI/UX.** Bubble Tea v2 + vim keys + `/autonomy` + `/spec` + watch `AI!`/`AI?` + visual diff is competitive. Missing vs field: hosted share-link (ours is local `hawk://` deeplink), multi-session grid (we have `mission` worktrees + daemon — unsurfaced like herdr/cmux). Gap-02. +1. **Traction.** rho has no star-moat (pre-release). Leaders won via day-1 provider-agnostic + one-liner install + Web/Desktop alongside TUI. rho already ships script/brew/npm paths (`README.md:48-59`) — keep, don't add Desktop. +2. **Language/distro.** Go+MIT+zero-CGO (`Makefile:54`, `go.mod:3`) matches `gh/fzf/lazygit` enterprise-safe profile. Avoid GPL/EUPL patterns (kitty/eza). Rust wave wins on published benchmarks — rho has `make bench` but publishes none (Gap-04). +3. **Providers.** rho routes only via `eyrie/engine` facade (`docs/SECURITY-DEVELOPER.md:51-56`, `ecosystem.yaml:29-30`); custom OpenAI-compat supported (`internal/config/settings.go:50`). Count messaging ("28 first-class" per README) trails OpenCode 75+ / Hermes 300+ — fix by exposing catalog count dynamically, not by forking providers into CLI (ownership lives in router per AGENTS.md). +4. **TUI/UX.** Bubble Tea v2 + vim keys + `/autonomy` + `/spec` + watch `AI!`/`AI?` + visual diff is competitive. Missing vs field: hosted share-link (ours is local `rho://` deeplink), multi-session grid (we have `mission` worktrees + daemon — unsurfaced like herdr/cmux). Gap-02. 5. **Sandbox.** Docker-only fail-closed is strictest default alongside Codex net-off and Gemini gVisor. Tradeoff is onboarding friction without Docker. Must not add host-exec fallback (violates `docs/SECURITY-DEVELOPER.md:71-73`); fix with preflight/path/doctor messaging + image pull/build guidance. Gap-01. 6. **Memory/context.** AST repomap + Harrier graph + compaction segments + relevance-prune + conversation-arc + 80% tool-result clearing exceeds most. Missing: Hermes-style auto-skill learning loop (we have curator archive + harness — surface it). 7. **Multi-agent.** `mission` worktrees + family messenger + path reservations + budgets + portable `mission-graph.json` + `graph export` (hashes only) is unique verifiable-execution story. Surface it; no new runtime needed. diff --git a/docs/DAEMON-PORT-THREAT-MODEL.md b/docs/DAEMON-PORT-THREAT-MODEL.md index 01b84e668..3c0d74e17 100644 --- a/docs/DAEMON-PORT-THREAT-MODEL.md +++ b/docs/DAEMON-PORT-THREAT-MODEL.md @@ -1,6 +1,6 @@ -# Hawk Daemon — Port 4590 Threat Model +# Rho Daemon — Port 4590 Threat Model -The Hawk daemon (`hawk daemon`) binds an HTTP server on port **4590** (default) +The Rho daemon (`rho daemon`) binds an HTTP server on port **4590** (default) of the **loopback interface only** (`127.0.0.1:4590`). This document describes the threat model, attack surface, and mitigation controls. @@ -33,19 +33,19 @@ A malicious local process (malware, compromised script) can send HTTP requests to `127.0.0.1:4590` without any network privilege. **Mitigation:** -- Set `HAWK_DAEMON_API_KEY` and configure the daemon with `--api-key` to +- Set `RHO_DAEMON_API_KEY` and configure the daemon with `--api-key` to require `Authorization: Bearer ` on all non-readiness endpoints. - The key is validated per-request; the daemon does not issue tokens. #### T3 — Port scanning / fingerprinting -Any local process can detect that port 4590 is open and identify hawk. +Any local process can detect that port 4590 is open and identify rho. **Mitigation:** Low severity — this is unavoidable for a local HTTP service. The daemon does not expose version information on unauthenticated endpoints. #### T4 — Session data exfiltration Session data (conversation history, tool outputs) is stored in SQLite at -`~/.hawk/sessions/hawk.db`. A local attacker with filesystem read access can +`~/.rho/sessions/rho.db`. A local attacker with filesystem read access can read this file directly regardless of the daemon's auth. **Mitigation:** @@ -59,7 +59,7 @@ credits. **Mitigation:** - A global concurrency cap bounds in-flight generations (default **4**, tuned - via `HAWK_DAEMON_MAX_CONCURRENT`). When the cap is hit, new `/v1/chat` + via `RHO_DAEMON_MAX_CONCURRENT`). When the cap is hit, new `/v1/chat` requests are refused with `503` instead of queuing unboundedly. - Per-IP token-bucket rate limiting: `/v1/chat` is limited to ~30 req/min (burst 6) and other authenticated endpoints to ~10 req/min (burst 4). @@ -73,7 +73,7 @@ credits. - **Remote network attackers**: the daemon does not bind to `0.0.0.0`; remote access is architecturally blocked. -- **Process privilege escalation**: hawk does not run as root and does not use +- **Process privilege escalation**: rho does not run as root and does not use `setuid`/`setgid`. --- @@ -84,11 +84,11 @@ Set the daemon API key before starting the daemon: ```bash # Option 1: environment variable (recommended for CI/automation) -export HAWK_DAEMON_API_KEY="your-random-secret-here" -hawk daemon +export RHO_DAEMON_API_KEY="your-random-secret-here" +rho daemon # Option 2: CLI flag -hawk daemon --api-key "your-random-secret-here" +rho daemon --api-key "your-random-secret-here" ``` All clients must then pass: @@ -96,7 +96,7 @@ All clients must then pass: Authorization: Bearer your-random-secret-here ``` -The SDK reads this from `HAWK_DAEMON_API_KEY` automatically if the env var is +The SDK reads this from `RHO_DAEMON_API_KEY` automatically if the env var is set. --- @@ -104,10 +104,10 @@ set. ## Changing the port ```bash -hawk daemon --port 9000 +rho daemon --port 9000 ``` -Or in `~/.hawk/settings.json`: +Or in `~/.rho/settings.json`: ```json { "daemon": { @@ -120,13 +120,13 @@ Or in `~/.hawk/settings.json`: ## Shared machine considerations -If hawk is running on a machine shared by multiple OS users (e.g., a dev +If rho is running on a machine shared by multiple OS users (e.g., a dev server), set the API key **and** use OS firewall rules to restrict which local users can connect to port 4590: ```bash # macOS — pf: allow only current user's processes (requires pf.conf editing) -# Linux — iptables: allow only the hawk-owner UID +# Linux — iptables: allow only the rho-owner UID sudo iptables -A OUTPUT -p tcp --dport 4590 -m owner ! --uid-owner $UID -j REJECT ``` diff --git a/docs/DEVELOPER-PATH.md b/docs/DEVELOPER-PATH.md index efb7090e7..b2fdc2f3d 100644 --- a/docs/DEVELOPER-PATH.md +++ b/docs/DEVELOPER-PATH.md @@ -1,13 +1,13 @@ -# Hawk Developer Path +# Rho Developer Path -This guide explains what `hawk path` checks and how to get a fresh developer machine ready to use Hawk safely. +This guide explains what `rho path` checks and how to get a fresh developer machine ready to use Rho safely. ## What "developer path" means -For Hawk, the developer path is the minimum local setup required to chat, edit code, and keep credentials off disk: +For Rho, the developer path is the minimum local setup required to chat, edit code, and keep credentials off disk: - A provider credential stored in the OS secret store -- A model selected in Hawk settings +- A model selected in Rho settings - A local model catalog available through eyrie - No plaintext API keys left in Eyrie's configured `provider.json` or legacy env files - Safe defaults for Bash execution and filesystem access @@ -16,85 +16,85 @@ For Hawk, the developer path is the minimum local setup required to chat, edit c Run the report at any time: ```bash -hawk path -hawk path --strict -hawk doctor -hawk preflight +rho path +rho path --strict +rho doctor +rho preflight ``` ## Setup checklist ### 1. Build and workspace setup -If you are contributing from source, clone Hawk as the main CLI. The support +If you are contributing from source, clone Rho as the main CLI. The support repositories are independent sibling checkouts when you need the full local -workspace; they are not nested under Hawk: +workspace; they are not nested under Rho: ```bash mkdir graycode-eco && cd graycode-eco -git clone https://github.com/GrayCodeAI/hawk +git clone https://github.com/GrayCodeAI/rho git clone https://github.com/GrayCodeAI/eyrie -cd hawk +cd rho make setup -go build -o hawk ./cmd/hawk +go build -o rho ./cmd/rho ``` `make setup` validates the canonical repository manifest and regenerates the parent `../go.work` from the local Go repositories marked `workspace: true`. -Hawk can also be built as a standalone checkout with `GOWORK=off go build ./cmd/hawk`; +Rho can also be built as a standalone checkout with `GOWORK=off go build ./cmd/rho`; the sibling workspace is only required for cross-repository development and boundary checks. ### 2. Configure credentials -Start Hawk and use `/config` to paste an API key or configure a local provider like Ollama. +Start Rho and use `/config` to paste an API key or configure a local provider like Ollama. -Hawk stores credentials in the macOS Keychain or Linux secret store. It should not rely on shell env vars, `.env`, or plaintext config files for provider secrets. +Rho stores credentials in the macOS Keychain or Linux secret store. It should not rely on shell env vars, `.env`, or plaintext config files for provider secrets. Useful checks: ```bash -hawk credentials status -hawk preflight +rho credentials status +rho preflight ``` -`hawk preflight` is a local-ready check; it does not contact a provider. To +`rho preflight` is a local-ready check; it does not contact a provider. To live-verify the selected provider credential and connectivity, use `/config` -validation or `hawk models list --live`. +validation or `rho models list --live`. ### 3. Select a model -Pick a model in `/config`. Hawk stores the selected model in settings and uses eyrie for provider routing and catalog resolution. +Pick a model in `/config`. Rho stores the selected model in settings and uses eyrie for provider routing and catalog resolution. If the catalog is missing or empty: ```bash -hawk models refresh +rho models refresh ``` ## Security checks -`hawk path` treats these as important security conditions: +`rho path` treats these as important security conditions: - Eyrie's resolved `provider.json` must not contain secret fields - sensitive files like provider config and SSH paths should be blocked from agent reads Eyrie resolves provider state from `EYRIE_CONFIG_DIR` first, then the platform user-config directory. -Hawk protects that resolved path even when it is customized or symlinked. +Rho protects that resolved path even when it is customized or symlinked. -If Hawk detects secret fields in `provider.json`, back up the file, remove those fields manually, and save your keys again through `/config`. +If Rho detects secret fields in `provider.json`, back up the file, remove those fields manually, and save your keys again through `/config`. Read the full credential and isolation model in [SECURITY-DEVELOPER.md](./SECURITY-DEVELOPER.md). ## Execution model -Hawk executes agent commands directly on the host. No Docker daemon or +Rho executes agent commands directly on the host. No Docker daemon or container runtime is required, and there is no container fallback to configure. ## Ecosystem checks -`hawk path` also verifies the provider layer behind Hawk: +`rho path` also verifies the provider layer behind Rho: - `eyrie` for provider routing and local preflight readiness - the embedded token pipeline for estimation and compression @@ -102,19 +102,19 @@ container runtime is required, and there is no container fallback to configure. If you want the broader status summary: ```bash -hawk ecosystem -hawk doctor +rho ecosystem +rho doctor ``` ## Typical recovery path -If `hawk path` says you are not ready, this is the intended order: +If `rho path` says you are not ready, this is the intended order: -1. Run `hawk` +1. Run `rho` 2. Open `/config` 3. Paste an API key or configure Ollama 4. Pick a model -5. Re-run `hawk preflight` -6. Re-run `hawk path` +5. Re-run `rho preflight` +6. Re-run `rho path` If security items still fail, fix those before treating the machine as ready. diff --git a/docs/DYNAMIC-MODELS.md b/docs/DYNAMIC-MODELS.md index 574c8c51f..d409d69cd 100644 --- a/docs/DYNAMIC-MODELS.md +++ b/docs/DYNAMIC-MODELS.md @@ -1,8 +1,8 @@ -# Dynamic Model Discovery — Hawk Developer Guide +# Dynamic Model Discovery — Rho Developer Guide ## Ownership and flow -Hawk is the product face; Eyrie is the provider engine. Hawk owns the CLI/TUI, +Rho is the product face; Eyrie is the provider engine. Rho owns the CLI/TUI, model-picker presentation, user intent, and output compatibility. The `eyrie/engine` facade alone owns provider registry details, credentials, discovery, catalog/cache policy, model aliases, deployment routing, and chat @@ -17,18 +17,18 @@ provider APIs / remote catalog / local cache | stable host DTOs and methods v - Hawk composition and presentation + Rho composition and presentation /config | models | conversation ``` -Production Hawk packages must not import Eyrie packages below +Production Rho packages must not import Eyrie packages below `github.com/GrayCodeAI/eyrie/engine`. Shell and AST guards enforce a zero-exception boundary. -## Hawk composition boundary +## Rho composition boundary -`internal/config` is Hawk's control-plane composition root. It creates an -Eyrie engine and projects engine models into Hawk UI and command contracts: +`internal/config` is Rho's control-plane composition root. It creates an +Eyrie engine and projects engine models into Rho UI and command contracts: ```go models, err := config.ListEngineModels(ctx, "anthropic", false) @@ -36,10 +36,10 @@ live, err := config.ListEngineModels(ctx, "anthropic", true) public, err := config.ListPublicEngineModels(ctx, "xiaomi_mimo_payg") ``` -Conversation construction goes through Hawk's `internal/engine` adapter. The -adapter translates Hawk-owned message, tool, usage, and stream DTOs to the +Conversation construction goes through Rho's `internal/engine` adapter. The +adapter translates Rho-owned message, tool, usage, and stream DTOs to the Eyrie engine facade; conversation history, WAL, resume, approvals, and tool -execution remain Hawk-owned. +execution remain Rho-owned. ## Catalog and live discovery @@ -52,20 +52,20 @@ process-global credentials. Use the normal cache-backed path for repeatable UI and automation: ```bash -hawk models list anthropic -hawk models list anthropic --json +rho models list anthropic +rho models list anthropic --json ``` Use a provider-scoped live request when current connectivity and credentials must be checked: ```bash -hawk models list anthropic --live -hawk models list anthropic --live --json -hawk models list anthropic --live --raw +rho models list anthropic --live +rho models list anthropic --live --json +rho models list anthropic --live --raw ``` -`hawk preflight` reports **local readiness**: usable local state, a selected +`rho preflight` reports **local readiness**: usable local state, a selected model, and presence of the required stored credential. It is intentionally cheap and does not prove that a remote provider accepts that credential. Treat a successful provider-scoped `--live` request (or `/config` live @@ -73,7 +73,7 @@ validation) as **live verified**. ## Stable command output -`hawk models list --json` is a Hawk-owned compatibility contract, not a direct +`rho models list --json` is a Rho-owned compatibility contract, not a direct serialization of Eyrie's evolving `engine.Model` DTO. Its stable fields are: ```text @@ -83,11 +83,11 @@ server_tools, display_name, description, owner, live_metadata New fields must be additive. `--raw` returns provider-native `live_metadata` objects when available; for cache/public rows without native -metadata, it returns the stable Hawk compatibility row instead of `null`. +metadata, it returns the stable Rho compatibility row instead of `null`. ## Custom gateways -Hawk converts effective `custom_providers` settings into +Rho converts effective `custom_providers` settings into `engine.Options.CustomGateways` at its composition root. Custom gateway metadata is snapshotted per Engine instance. Do not register custom gateways in Eyrie process-global state: tests, parallel sessions, and future multi-tenant @@ -100,20 +100,20 @@ hosts must be isolated from one another. 2. Add Eyrie tests for cache and live discovery, credential status, selection, and generation/streaming. 3. Commit and verify standalone Eyrie. -4. Advance Hawk's `../eyrie` sibling checkout to that exact commit, then update - Hawk's module version when the Eyrie revision is published. +4. Advance Rho's `../eyrie` sibling checkout to that exact commit, then update + Rho's module version when the Eyrie revision is published. 5. Verify both the workspace (`go.work`) and published-module (`GOWORK=off`) build modes. -Hawk changes are needed only for a new product behavior or an additive -Hawk-owned presentation field—not for provider-specific mechanics. +Rho changes are needed only for a new product behavior or an additive +Rho-owned presentation field—not for provider-specific mechanics. ## Checks ```bash -hawk models refresh -hawk models status -hawk preflight +rho models refresh +rho models status +rho preflight make eyrie-engine-guard go test ./cmd ./internal/config ./internal/engine -count=1 ``` diff --git a/docs/ECOSYSTEM-CONFIG.md b/docs/ECOSYSTEM-CONFIG.md index d36123462..f029c5c93 100644 --- a/docs/ECOSYSTEM-CONFIG.md +++ b/docs/ECOSYSTEM-CONFIG.md @@ -3,9 +3,9 @@ Status: Draft / historical spec (aspirational; not implemented) Applies to: the wider graycode-eco ecosystem -> Note: Hawk now depends only on eyrie and embeds its own token engine. +> Note: Rho now depends only on eyrie and embeds its own token engine. > References below to harrier, shrike, and swift describe a proposed -> ecosystem-wide schema, not Hawk's current runtime. +> ecosystem-wide schema, not Rho's current runtime. This document specifies a **single, unified configuration schema** for the graycode-eco ecosystem: one declarative file (`graycode-eco.yaml`, with an equivalent @@ -15,7 +15,7 @@ file is the source of truth, version-controlled alongside a project, and each repo reads the slice of the schema it owns. Today each repo configures itself independently through its own env vars, -flags, and config files (hawk: `config.json` + `HAWK_*`/`HAWK_*` env; eyrie: +flags, and config files (rho: `config.json` + `RHO_*`/`RHO_*` env; eyrie: provider env vars; harrier: `~/.harrier/config.toml`; shrike: `TOK_*` env; swift: `SWIFT_*` env). This spec does **not** replace those mechanisms — it defines a superset schema and maps every setting back to the repo + existing env @@ -31,7 +31,7 @@ any runtime behavior. `graycode-eco.yaml` value > repo default. This preserves current behavior where env/flags are authoritative. 3. **Repo-owned sections.** Each top-level section is owned by one repo (with - `model`/`providers` shared by hawk + eyrie). A repo only reads its sections. + `model`/`providers` shared by rho + eyrie). A repo only reads its sections. 4. **Two encodings, one schema.** YAML is canonical for humans; the identical structure is valid JSON for machine generation. (harrier's on-disk format is TOML; its section maps 1:1 to `~/.harrier/config.toml`.) @@ -44,7 +44,7 @@ Search order (first found wins for the file itself; values still follow the runtime precedence above): 1. `--config ` flag (where a repo's CLI supports it) -2. `$HAWK_ECO_CONFIG` +2. `$RHO_ECO_CONFIG` 3. `./graycode-eco.yaml` (project root) 4. `~/.config/graycode-eco/config.yaml` @@ -53,7 +53,7 @@ runtime precedence above): ```yaml version: 1 -# ─── Shared: model + providers (hawk + eyrie) ─────────────────────────────── +# ─── Shared: model + providers (rho + eyrie) ─────────────────────────────── model: default: anthropic/claude-sonnet-4-5 # provider/model the agent uses small_fast: anthropic/claude-haiku # cheap model for trivial steps @@ -72,7 +72,7 @@ providers: # ─── eyrie: gateway / runtime ─────────────────────────────────────────────── gateway: - base_url: http://localhost:8080 # eyrie endpoint hawk talks to + base_url: http://localhost:8080 # eyrie endpoint rho talks to api_key_env: EYRIE_API_KEY allow_insecure_public_api: false deployment_routing: "" # EYRIE_DEPLOYMENT_ROUTING @@ -123,9 +123,9 @@ swift: telemetry: # OTel exporter settings shared by all repos. Span attribute keys follow # docs/OTEL-CONVENTIONS.md. - enabled: false # hawk: HAWK_ENABLE_TELEMETRY + enabled: false # rho: RHO_ENABLE_TELEMETRY otlp_endpoint: "" # OTEL_EXPORTER_OTLP_ENDPOINT - shutdown_timeout_ms: 0 # HAWK_OTEL_SHUTDOWN_TIMEOUT_MS + shutdown_timeout_ms: 0 # RHO_OTEL_SHUTDOWN_TIMEOUT_MS ``` ## Setting → repo → existing mechanism @@ -133,13 +133,13 @@ telemetry: The authoritative mapping. "Mechanism today" is what already implements the setting; the unified key is rendered down to it. -### Shared: model / providers (hawk + eyrie) +### Shared: model / providers (rho + eyrie) | Unified key | Repo | Mechanism today | |---------------------------------|-------------|--------------------------------------------------| -| `model.default` | hawk | `HAWK_MODEL` env / `config.json` | -| `model.small_fast` | hawk | `HAWK_SMALL_FAST_MODEL` env | -| `providers[].api_key_env` | eyrie/hawk | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`, `ZAI_API_KEY`, `CANOPYWAVE_API_KEY`, `FIREWORKS_API_KEY` | +| `model.default` | rho | `RHO_MODEL` env / `config.json` | +| `model.small_fast` | rho | `RHO_SMALL_FAST_MODEL` env | +| `providers[].api_key_env` | eyrie/rho | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`, `ZAI_API_KEY`, `CANOPYWAVE_API_KEY`, `FIREWORKS_API_KEY` | | `providers[].base_url_env` | eyrie | `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL` / `OPENAI_API_BASE`, `OLLAMA_BASE_URL`, `FIREWORKS_BASE_URL` | | `providers[].model` (openai) | eyrie | `OPENAI_MODEL` env | | `providers[].model` (gemini) | eyrie | `GEMINI_MODEL` env | @@ -149,13 +149,13 @@ setting; the unified key is rendered down to it. | Unified key | Mechanism today (eyrie) | |--------------------------------------|--------------------------------------| -| `gateway.base_url` | `EYRIE_BASE_URL` (hawk→eyrie link) | +| `gateway.base_url` | `EYRIE_BASE_URL` (rho→eyrie link) | | `gateway.api_key_env` | `EYRIE_API_KEY` | | `gateway.allow_insecure_public_api` | `EYRIE_ALLOW_INSECURE_PUBLIC_API` | -| `gateway.deployment_routing` | `EYRIE_DEPLOYMENT_ROUTING` (also `HAWK_DEPLOYMENT_ROUTING`) | +| `gateway.deployment_routing` | `EYRIE_DEPLOYMENT_ROUTING` (also `RHO_DEPLOYMENT_ROUTING`) | | `gateway.model_catalog.path_env` | `EYRIE_MODEL_CATALOG_PATH` | | `gateway.model_catalog.url_env` | `EYRIE_MODEL_CATALOG_URL` | -| `gateway.model_catalog.refresh` | `EYRIE_MODEL_CATALOG_REFRESH` / `HAWK_AUTO_REFRESH_CATALOG` / `HAWK_CATALOG_REFRESH_ALWAYS` | +| `gateway.model_catalog.refresh` | `EYRIE_MODEL_CATALOG_REFRESH` / `RHO_AUTO_REFRESH_CATALOG` / `RHO_CATALOG_REFRESH_ALWAYS` | | `gateway` config dir | `EYRIE_CONFIG_DIR` (default `/eyrie`) | ### harrier: memory @@ -197,22 +197,22 @@ few env vars. ### swift + telemetry -| Unified key | Mechanism today (swift / hawk) | +| Unified key | Mechanism today (swift / rho) | |-----------------------------------|-------------------------------------------------| | `swift.search_url` | `SWIFT_SEARCH_URL` env | | `swift.log_level` | `SWIFT_LOG_LEVEL` env | | `swift.telemetry_optout` | `SWIFT_TELEMETRY_OPTOUT` / `SWIFT_NO_TELEMETRY` env | | `swift.posthog.api_key_env` | `POSTHOG_API_KEY` env | | `swift.posthog.endpoint` | `POSTHOG_ENDPOINT` env | -| `telemetry.enabled` | `HAWK_ENABLE_TELEMETRY` env (hawk) | -| `telemetry.shutdown_timeout_ms` | `HAWK_OTEL_SHUTDOWN_TIMEOUT_MS` env (hawk) | +| `telemetry.enabled` | `RHO_ENABLE_TELEMETRY` env (rho) | +| `telemetry.shutdown_timeout_ms` | `RHO_OTEL_SHUTDOWN_TIMEOUT_MS` env (rho) | | `telemetry.otlp_endpoint` | `OTEL_EXPORTER_OTLP_ENDPOINT` (standard OTel) | ## Rendering down to per-repo config The unified file is designed to be **resolved** into the existing mechanisms: -- **env-based repos** (hawk, eyrie, shrike, swift): export the mapped env var for +- **env-based repos** (rho, eyrie, shrike, swift): export the mapped env var for any key set in `graycode-eco.yaml` that is not already present in the process environment (preserving "env wins" precedence). - **file-based repos** (harrier): write/merge the `memory.*` section into diff --git a/docs/ECOSYSTEM-ROADMAP.md b/docs/ECOSYSTEM-ROADMAP.md index 6b9603f1f..61831d4b9 100644 --- a/docs/ECOSYSTEM-ROADMAP.md +++ b/docs/ECOSYSTEM-ROADMAP.md @@ -1,7 +1,7 @@ # GrayCode Ecosystem Roadmap (2026) **Status:** Active · **Last updated:** 2026-09-09 -**Scope:** the 4 in-workspace repos — `hawk`, `eyrie`, +**Scope:** the 4 in-workspace repos — `rho`, `eyrie`, `graycode-skills`, `graycode-platform` — plus the 5 external engine repos they depend on. **Evidence base:** `docs/RESEARCH.md`, `docs/COMPETITIVE.md`, `docs/plans/competitive-gap-*.md` @@ -37,7 +37,7 @@ Claude Code) has proven demand. ### The single most important fact (historical) -`hawk` imports **5 engine modules** — `harrier` (memory graph), +`rho` imports **5 engine modules** — `harrier` (memory graph), `shrike` (token/compress), `kestrel` (code review), `merlin` (site audit), `swift` (session correlation) — that were **absent from this workspace and 404 on GitHub**. They were replaced in `go.mod` by no-op stubs, so the CLI compiled @@ -55,12 +55,12 @@ dropped. | Repo | Role | Build | Tests | Health | |---|---|---|---|---| -| `hawk` | Product face (Go, Bubble Tea v2) | ✅ `go build ./...` | ✅ 179 pkgs green | Engines restored; memory/token/review/audit/correlation live | +| `rho` | Product face (Go, Bubble Tea v2) | ✅ `go build ./...` | ✅ 179 pkgs green | Engines restored; memory/token/review/audit/correlation live | | `eyrie` | Provider runtime (Go) | ✅ | ✅ 37 pkgs green | Healthy; 28 providers; gRPC ChatService wired | | `graycode-skills` | Skill marketplace (Python) | ✅ | ✅ 382/382 | Healthy; single parsed schema | | `graycode-platform` | Web + Cloud control plane (TS) | ✅ | ✅ 320 worker / 33 web | Healthy; worker+bff routes added; migration deduped | -### 2.1 hawk +### 2.1 rho - Builds and vets clean **only because** the 5 engines are stubbed. - **5 failing tests:** `internal/token/shrike_test.go` asserts real token counts @@ -69,10 +69,10 @@ dropped. - `internal/token/shrike.go` — token counting/compression → 0 / identity. - `internal/intelligence/memory/harrier_bridge.go` — `Ready()` is true but nothing persists (stub store returns nil DB). - - `internal/bridge/kestrel/bridge.go` — `hawk review run|analyze` always + - `internal/bridge/kestrel/bridge.go` — `rho review run|analyze` always report "no issues found" / status `Passed`. - `internal/bridge/merlin/bridge.go` — site audits return 0 pages/findings. - - `cmd/swift.go` / `cmd/swift_correlation.go` — `hawk swift` has no + - `cmd/swift.go` / `cmd/swift_correlation.go` — `rho swift` has no working subcommands; correlation silently errors. - `internal/engine/compact.go` etc. — context compaction depends on shrike. - **Orphaned code:** `cmd/merlin_pipeline.go` defines + unit-tests @@ -187,7 +187,7 @@ Make the current state truthful and green. **No new user-facing features.** | # | Repo | Task | Acceptance | Effort | |---|---|---|---|---| | 0.1 | cli | Add `token.ShrikeAvailable()` functional probe; guard the 5 shrike boundary tests to skip when the engine is the stub | `go test ./internal/token/...` green; stub detected as unavailable | 0.5d ✅ | -| 0.2 | cli | Report shrike honestly in `ecosystem` / `doctor` (unavailable when stub, not "pipeline OK (sample=0)") | `hawk ecosystem` flags stub; JSON `shrike.embedded=false` | 0.5d ✅ | +| 0.2 | cli | Report shrike honestly in `ecosystem` / `doctor` (unavailable when stub, not "pipeline OK (sample=0)") | `rho ecosystem` flags stub; JSON `shrike.embedded=false` | 0.5d ✅ | | 0.3 | skills | Make `manifest-schema.toml` the single source of truth; wire a validator into CI that enforces the *enforced* schema (not the aspirational one) | schema parsed by tooling; CI gate matches reality; corpus still 0-warning | 1-2d ✅ | | 0.4 | skills | Add a declared dev/test dependency group so `pytest`/`ruff`/`pytest-cov` install reproducibly | `pip install -e '.[dev]'` then `pytest` green | 0.5d ✅ | | 0.5 | platform | Renumber duplicate migration `0022_identity_ui.sql` → `0023`; dedupe identity schema note | migrations apply in deterministic order; tests green | 0.5d ✅ | @@ -209,14 +209,14 @@ memory, token, review, audit, and correlation features are dead. | 1.5 | `swift` | session correlation / checkpoint linking | `graph correlation` subcommand the CLI shells into | **Acceptance:** `go.mod` `replace` directives removed; `make check-replace` -passes; `hawk path` shows all engines genuinely ready; `go test ./...` +passes; `rho path` shows all engines genuinely ready; `go test ./...` green with real engines. **Effort:** 4-8 weeks total across the 5 repos. ### Phase 2 — Product honesty + verification surface (P1) | # | Repo | Task | Acceptance | |---|---|---|---| -| 2.1 | cli | Wire `RunMerlinPipeline` into a `hawk audit`/`merlin` command (orphaned code) | command runs the pipeline end-to-end with a real merlin | +| 2.1 | cli | Wire `RunMerlinPipeline` into a `rho audit`/`merlin` command (orphaned code) | command runs the pipeline end-to-end with a real merlin | | 2.2 | cli | Wire `BeamSearch` into the live agent loop as scorer/expander | tree-search used for planning with real model outputs | | 2.3 | cli | Publish benchmark numbers in README (Gap-04 follow-through) | `make bench` numbers in README, cited | | 2.4 | router | Wire the gRPC `ChatService` behind the `grpc` build tag | `grpc`-tagged build serves Chat; unit-tested | @@ -270,7 +270,7 @@ green with real engines. **Effort:** 4-8 weeks total across the 5 repos. ## 7. Metrics - `go test ./...` green in all Go repos; `make ci` green. -- `hawk path` / `hawk ecosystem` report engine availability honestly. +- `rho path` / `rho ecosystem` report engine availability honestly. - Skills: 0-warning corpus maintained; single parsed schema. - Platform: worker reachable at a stable domain; migrations deterministic. - Benchmarks published (per Gap-04). diff --git a/docs/ECOSYSTEM-WIRING.md b/docs/ECOSYSTEM-WIRING.md index a35b6346e..6e498d011 100644 --- a/docs/ECOSYSTEM-WIRING.md +++ b/docs/ECOSYSTEM-WIRING.md @@ -1,7 +1,7 @@ # GrayCode ecosystem wiring > NOTE (2026-09-04): the `eagle` repository has been removed; its contracts -> were vendored into hawk's `internal/contracts` and `ecosystem.yaml` +> were vendored into rho'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 @@ -17,23 +17,23 @@ flowchart LR Lists --> Scripts[Workspace and release scripts] Lists --> Owl[Owl catalog] - Hawk[Hawk product] --> Engines[Six engine repositories] + Rho[Rho product] --> Engines[Six engine repositories] Engines --> Eagle[Eagle contracts] - Hawk --> Eagle + Rho --> Eagle - HawkSpec[Hawk OpenAPI] -. manual snapshots .-> Sparrow - HawkSpec -. manual snapshots .-> Robin - HawkSpec -. no snapshot .-> Wren + RhoSpec[Rho OpenAPI] -. manual snapshots .-> Sparrow + RhoSpec -. manual snapshots .-> Robin + RhoSpec -. no snapshot .-> Wren Browser[GrayCode browser] --> Mixed[One mixed Worker] Mixed --> Identity[(Identity tables)] - Mixed --> Cloud[(Hawk Cloud tables)] + Mixed --> Cloud[(Rho Cloud tables)] Mixed -. fire-and-forget .-> Queue[Usage Queue] ``` The runtime engine integrations were mostly sound, but the surrounding wiring could drift: repository names appeared independently in shell scripts and Owl, -SDK contracts were inconsistent, browser identity and the Hawk control plane +SDK contracts were inconsistent, browser identity and the Rho control plane shared a Worker and D1 binding, and a successful usage write did not guarantee a durable Queue-delivery intent. @@ -41,20 +41,20 @@ durable Queue-delivery intent. ```mermaid flowchart TB - Manifest[hawk/ecosystem.yaml\ncanonical repo and contract inventory] + Manifest[rho/ecosystem.yaml\ncanonical repo and contract inventory] Manifest --> Workspace[generated root go.work] Manifest --> Guards[boundary and release parity guards] Manifest --> OwlSnapshot[owl/ecosystem.json] subgraph Local[Local-first runtime] - User[CLI / daemon user] --> Hawk[Hawk composition root] - Hawk --> Eyrie[Eyrie facade] - Hawk --> Harrier[Harrier / Harrier facade] - Hawk --> Shrike[Shrike / Shrike facade] - Hawk --> Swift[Swift / Swift facade] - Hawk --> Kestrel[Kestrel / Kestrel facade] - Hawk --> Merlin[Merlin / Merlin facade] - Hawk --> Eagle[Eagle neutral contracts] + User[CLI / daemon user] --> Rho[Rho composition root] + Rho --> Eyrie[Eyrie facade] + Rho --> Harrier[Harrier / Harrier facade] + Rho --> Shrike[Shrike / Shrike facade] + Rho --> Swift[Swift / Swift facade] + Rho --> Kestrel[Kestrel / Kestrel facade] + Rho --> Merlin[Merlin / Merlin facade] + Rho --> Eagle[Eagle neutral contracts] Eyrie --> Eagle Harrier --> Eagle Shrike --> Eagle @@ -63,7 +63,7 @@ flowchart TB Merlin --> Eagle end - Hawk --> Daemon[Hawk daemon API] + Rho --> Daemon[Rho daemon API] Daemon --> OpenAPI[api/openapi.yaml] OpenAPI --> Sparrow[Sparrow Go SDK snapshot] OpenAPI --> Robin[Robin Python SDK snapshot] @@ -72,7 +72,7 @@ flowchart TB subgraph Hosted[Optional hosted plane] Browser[GrayCode browser] --> BFF[GrayCode identity/BFF Worker] BFF --> Identity[(Identity D1)] - BFF -->|private typed Service Binding| Cloud[Hawk Cloud Worker] + BFF -->|private typed Service Binding| Cloud[Rho Cloud Worker] Cloud --> Control[(Control-plane D1)] Cloud -->|same D1 transaction| Outbox[(Usage outbox)] Outbox -->|immediate + scheduled retry| Queue[Cloudflare Queue] @@ -80,21 +80,21 @@ flowchart TB Queue --> Archive[(R2 archive)] end - Hawk -. explicit opt-in; fail-open .-> Cloud + Rho -. explicit opt-in; fail-open .-> Cloud ``` ## Ownership rules | Boundary | Owner | Rule | |---|---|---| -| Product orchestration | Hawk | Engines never orchestrate Hawk or one another. | -| Provider runtime | Eyrie | Hawk imports its supported `engine` facade. | +| Product orchestration | Rho | Engines never orchestrate Rho or one another. | +| Provider runtime | Eyrie | Rho imports its supported `engine` facade. | | Shared data contracts | Eagle | Neutral types only; no product behavior. | -| Portable local graph | Source engine/Hawk | Emit bounded `hawk.graph/v1` facts without raw secrets or prompts. | -| Daemon HTTP API | Hawk | `hawk/api/openapi.yaml` is authoritative. | +| Portable local graph | Source engine/Rho | Emit bounded `rho.graph/v1` facts without raw secrets or prompts. | +| Daemon HTTP API | Rho | `rho/api/openapi.yaml` is authoritative. | | SDK endpoint support | Each SDK | Exact contract snapshot plus an explicit decision for every path. | | Browser identity | GrayCode BFF | Users, sessions, email, API keys, and UI activity remain in identity D1. | -| Hosted control plane | Hawk Cloud | Organizations, projects, devices, usage, billing, graph ledger, and audit. | +| Hosted control plane | Rho Cloud | Organizations, projects, devices, usage, billing, graph ledger, and audit. | | Cloud delivery | D1 outbox + Queue | Business state and delivery intent commit together; consumers are idempotent. | | Architecture discovery | Owl | Generated projection of the canonical manifest, never a second inventory. | @@ -111,7 +111,7 @@ must use `directory`/`github_repo`; UI copy may use `product_name`. 2. Eagle commit `9d358dde4ad8` is the cross-repository contract revision, pinned through its reachable Go pseudo-version until the next semver tag is published; the parity gate checks every declared consumer. -3. Local graph projections use `hawk.graph/v1`. Hawk Cloud independently +3. Local graph projections use `rho.graph/v1`. Rho Cloud independently validates and privacy-normalizes them into `graycode-cloud.graph/v1`. 4. Usage ingestion commits its idempotency claim, budget counter, event, billing ledger row, daily activity projection, and `usage.recorded.v1` @@ -122,12 +122,12 @@ must use `directory`/`github_repo`; UI copy may use `product_name`. ## Release and development workflow -- Run `make workspace` in Hawk to regenerate the parent `go.work`. +- Run `make workspace` in Rho to regenerate the parent `go.work`. - Run `make boundaries` to validate the inventory, module isolation, Eagle parity, facade locations, and support-repository coupling. - Root `go.mod` files never contain local `replace` directives. Local sibling substitutions exist only in the generated, uncommitted parent workspace. -- SDK CI checks its snapshot byte-for-byte against Hawk and separately checks +- SDK CI checks its snapshot byte-for-byte against Rho and separately checks that every OpenAPI path has a support decision. - Run `owl/scripts/sync-ecosystem.sh` after changing the canonical inventory; Owl CI rejects drift. @@ -137,32 +137,32 @@ must use `directory`/`github_repo`; UI copy may use `product_name`. ### Coordinated Go publication gate The compatible Eagle-migrated Eyrie source is currently ahead of Eyrie's -published `origin/main`. Do not merge or release Hawk against the older Eyrie -pseudo-version: it still exposes the retired `hawk-core-contracts` types and is -not type-compatible with Hawk's Eagle boundary. +published `origin/main`. Do not merge or release Rho against the older Eyrie +pseudo-version: it still exposes the retired `rho-core-contracts` types and is +not type-compatible with Rho's Eagle boundary. 1. Merge and publish the Eyrie ecosystem-wiring branch first. 2. Resolve that final remote commit to its canonical Go pseudo-version with `go list -m github.com/GrayCodeAI/eyrie@`. -3. Update Hawk's Eyrie requirement, run `GOWORK=off go mod tidy`, and remove any +3. Update Rho's Eyrie requirement, run `GOWORK=off go mod tidy`, and remove any transition excludes no longer required by the published engine graphs. -4. Require Hawk's `public-modules` and `release-parity` CI jobs to pass before - merging or tagging Hawk. +4. Require Rho's `public-modules` and `release-parity` CI jobs to pass before + merging or tagging Rho. This publication is intentionally not performed by the source implementation: remote branch merges and tags are externally visible release operations. ## Deployment sequence for the Worker split -1. Create or automatically provision the `hawk-identity` D1 database and +1. Create or automatically provision the `rho-identity` D1 database and confirm its binding in `apps/bff/wrangler.jsonc`. 2. Export the existing identity tables from the legacy mixed database and import them into the identity database; verify row counts and login flows. -3. Deploy `graycode-cloud` with the named `HawkCloudService` entrypoint and +3. Deploy `graycode-cloud` with the named `RhoCloudService` entrypoint and apply migration `0023_usage_outbox.sql`. -4. Deploy `hawk-bff` with its D1 binding and private service binding. -5. Point the web frontend API hostname at the BFF. Hawk/CLI device start and - poll traffic continues to target Hawk Cloud. +4. Deploy `rho-bff` with its D1 binding and private service binding. +5. Point the web frontend API hostname at the BFF. Rho/CLI device start and + poll traffic continues to target Rho Cloud. 6. After a rollback window, remove the legacy identity tables from the old cloud D1 using a separately reviewed data-retirement migration. diff --git a/docs/IMPLEMENTATION-ROADMAP.md b/docs/IMPLEMENTATION-ROADMAP.md index 83a64f175..74cbe8c7f 100644 --- a/docs/IMPLEMENTATION-ROADMAP.md +++ b/docs/IMPLEMENTATION-ROADMAP.md @@ -5,7 +5,7 @@ **Date:** 2026-07-05 **Source:** Historical comparison document; feature and market claims require independent revalidation. Architecture status is tracked in -`docs/architecture/hawk-architecture-baseline.md`. +`docs/architecture/rho-architecture-baseline.md`. --- @@ -27,14 +27,14 @@ Numeric scores are intentionally not used as current architecture evidence. ## Phase 1: High Priority (Immediate - Score Impact: +0.5) -### 1. Add VS Code Extension Integration (hawk repo) +### 1. Add VS Code Extension Integration (rho repo) **Effort:** Large (2-3 weeks) **Priority:** HIGH **What:** -- Create `hawk-vscode` repo for VS Code extension -- Use Hawk SDK for communication +- Create `rho-vscode` repo for VS Code extension +- Use Rho SDK for communication - Add WebSocket transport for real-time updates **Why:** @@ -45,12 +45,12 @@ Numeric scores are intentionally not used as current architecture evidence. **Implementation Plan:** ```bash # Create new repo -mkdir hawk-vscode -cd hawk-vscode +mkdir rho-vscode +cd rho-vscode # Initialize git init -go mod init github.com/GrayCodeAI/hawk-vscode +go mod init github.com/GrayCodeAI/rho-vscode # Add extension scaffold mkdir -p cmd extension/src @@ -64,8 +64,8 @@ go get golang.org/x/net/websocket # Create extension files cat > extension/package.json << 'EOF' { - "name": "hawk", - "displayName": "Hawk", + "name": "rho", + "displayName": "Rho", "description": "AI coding assistant in terminal", "version": "0.1.0", "engines": { "vscode": "^1.90.0" }, @@ -75,14 +75,14 @@ cat > extension/package.json << 'EOF' "contributes": { "commands": [ { - "command": "hawk.activate", - "title": "Activate Hawk" + "command": "rho.activate", + "title": "Activate Rho" } ], "menus": { "commandPalette": [ { - "command": "hawk.activate", + "command": "rho.activate", "when": "editorLangId" } ] @@ -94,22 +94,22 @@ EOF # Add extension scaffold cat > extension/src/extension.ts << 'EOF' import * as vscode from 'vscode'; -import * as hawksdk from '@graycodeai/hawksdk'; +import * as rhosdk from '@graycodeai/rhosdk'; export function activate(context: vscode.ExtensionContext) { - const client = new hawksdk.Client(); + const client = new rhosdk.Client(); // Register completion provider vscode.languages.registerCompletionItemProvider( 'go', - new HawkCompletionProvider(client), + new RhoCompletionProvider(client), 'g', 'h' ); // Register hover provider vscode.languages.registerHoverProvider( 'go', - new HawkHoverProvider(client) + new RhoHoverProvider(client) ); } EOF @@ -123,7 +123,7 @@ vsce publish **Repository Structure:** ``` -hawk-vscode/ +rho-vscode/ ├── extension/ # VS Code extension code │ ├── src/ │ │ ├── extension.ts @@ -140,25 +140,25 @@ hawk-vscode/ --- -### 2. Add Extension Marketplace (hawk repo) +### 2. Add Extension Marketplace (rho repo) **Effort:** Medium (1-2 weeks) **Priority:** HIGH **What:** - Create marketplace for community extensions -- Add extension discovery endpoint to hawk +- Add extension discovery endpoint to rho - Create `starling` integration - Add version compatibility checking **Why:** - Top agents have 100+ extensions -- Build ecosystem around Hawk +- Build ecosystem around Rho - Increase adoption **Implementation Plan:** ```go -// Add to hawk +// Add to rho package marketplace // Extension represents a community extension @@ -305,12 +305,12 @@ cat >> SKILL.md << 'EOF' To discover available extensions, use: ``` -hawk extensions list +rho extensions list ``` To install an extension: ``` -hawk extensions install +rho extensions install ``` EOF ``` @@ -431,7 +431,7 @@ go build forum.go --- -### 5. Add More Extension Points (hawk) +### 5. Add More Extension Points (rho) **Effort:** Small (3-5 days) **Priority:** MEDIUM @@ -514,7 +514,7 @@ func main() { --- -### 7. Add Debugging Support (hawk) +### 7. Add Debugging Support (rho) **Effort:** Large (2-3 weeks) **Priority:** LOW @@ -530,7 +530,7 @@ func main() { --- -### 8. Add Web UI for Monitoring (hawk) +### 8. Add Web UI for Monitoring (rho) **Effort:** Small (3-5 days) **Priority:** LOW @@ -579,7 +579,7 @@ func main() { ## Detailed Repo-Specific Roadmap -### **hawk** (Main Repo) - Primary Product +### **rho** (Main Repo) - Primary Product | Phase | Improvement | Effort | Impact | Status | |-------|--------------|--------|--------|--------| @@ -590,7 +590,7 @@ func main() { | 3 | Add Web UI for monitoring | Small | +0.2 | Planned | | 3 | Add SDK analytics | Small | +0.1 | Planned | -Current architecture status: see `docs/architecture/hawk-architecture-baseline.md`. +Current architecture status: see `docs/architecture/rho-architecture-baseline.md`. --- @@ -657,7 +657,7 @@ Current architecture status: see `docs/architecture/hawk-architecture-baseline.m The roadmap is sequenced by product value and implementation effort. It does not assign target architecture scores. Current architecture status is tracked -in `docs/architecture/hawk-architecture-baseline.md`. +in `docs/architecture/rho-architecture-baseline.md`. --- @@ -667,7 +667,7 @@ in `docs/architecture/hawk-architecture-baseline.md`. |------|--------|------------| | Marketplace competition | High | Focus on quality extensions | | Community building | Medium | Active engagement, good docs | -| Development velocity | Medium | Prioritize hawk SDK first | +| Development velocity | Medium | Prioritize rho SDK first | | Adoption curve | Medium | VS Code extension is key | --- diff --git a/docs/OTEL-CONVENTIONS.md b/docs/OTEL-CONVENTIONS.md index dd3dda5b2..d8187354d 100644 --- a/docs/OTEL-CONVENTIONS.md +++ b/docs/OTEL-CONVENTIONS.md @@ -1,9 +1,9 @@ # graycode-eco OpenTelemetry Semantic Conventions for AI Agent Spans Status: Draft / shared spec -Applies to: hawk, eyrie +Applies to: rho, eyrie -This document defines the OpenTelemetry (OTel) semantic conventions Hawk and +This document defines the OpenTelemetry (OTel) semantic conventions Rho and Eyrie should follow when emitting spans for AI agent and LLM operations. The goal is that a single tracing backend (Jaeger, Tempo, Honeycomb, an OTLP collector, etc.) can correlate model calls, tool invocations, token usage, and @@ -22,7 +22,7 @@ Eyrie owns provider-call instrumentation behind its `eyrie/engine` facade. Its lower provider layer contains the reference OTel decorator for chat and stream calls: it starts a client span, records provider/model/usage attributes, sets status from the result, and ends a streamed span on completion. That -decorator is an Eyrie implementation detail; Hawk must not import or compose it +decorator is an Eyrie implementation detail; Rho must not import or compose it directly. - `eyrie/internal/observability/observability.go` provides a stdlib-only, @@ -33,9 +33,9 @@ directly. of hard-coding strings. A pinning test (`genai_semconv_test.go`) guards the exact key values. -When adding tracing to Hawk, propagate trace context through the Engine call +When adding tracing to Rho, propagate trace context through the Engine call and use the attribute keys in this document. Eyrie wraps provider operations; -Hawk wraps product turns, tools, and the embedded token engine. +Rho wraps product turns, tools, and the embedded token engine. ## Span kinds and names @@ -111,8 +111,8 @@ Mapping: - **eyrie** — owns provider/model/usage spans behind `eyrie/engine`; align attribute keys to `gen_ai.*` over time. -- **hawk** — daemon/orchestrator. Already has OTel hooks - (`HAWK_ENABLE_TELEMETRY`, `HAWK_OTEL_SHUTDOWN_TIMEOUT_MS`). Emit +- **rho** — daemon/orchestrator. Already has OTel hooks + (`RHO_ENABLE_TELEMETRY`, `RHO_OTEL_SHUTDOWN_TIMEOUT_MS`). Emit `agent.id` and `session.id` on agent-turn spans; propagate them downstream to eyrie via context so provider spans inherit the same IDs. - **internal/token** — embedded token engine. Token accounting is its domain; diff --git a/docs/PERMISSION-MODEL-IMPROVEMENTS.md b/docs/PERMISSION-MODEL-IMPROVEMENTS.md index 9f21f8f43..ebcdb4ae9 100644 --- a/docs/PERMISSION-MODEL-IMPROVEMENTS.md +++ b/docs/PERMISSION-MODEL-IMPROVEMENTS.md @@ -1,6 +1,6 @@ -# Hawk Permission Model — Improvements (2026-08-07) +# Rho Permission Model — Improvements (2026-08-07) -This document describes the improvements made to hawk's permission, isolation, +This document describes the improvements made to rho's permission, isolation, and autonomy systems. All changes preserve the existing fail-closed architecture and are backward-compatible. @@ -186,7 +186,7 @@ second within 1.5s lands on Supervised. Prompt auto-expires. **Problem:** Crash could leave `sandbox-*.sb` temp files behind. -**Solution:** `seatbelt.go` `init()` removes orphaned `hawk-seatbelt-*.sb` files from +**Solution:** `seatbelt.go` `init()` removes orphaned `rho-seatbelt-*.sb` files from `os.TempDir()` at process startup. --- diff --git a/docs/RESEARCH.md b/docs/RESEARCH.md index c15642c48..0c1e895b5 100644 --- a/docs/RESEARCH.md +++ b/docs/RESEARCH.md @@ -1,7 +1,7 @@ # Research & Competitive Comparison — graycode-eco Status: Implemented (branch `feat/competitive-analysis-top20`, 2026-09-09) -Scope: 4 repos (`hawk`, `eyrie`, `graycode-platform`, `graycode-skills`) vs +Scope: 4 repos (`rho`, `eyrie`, `graycode-platform`, `graycode-skills`) vs top-20 OSS competitors and top-20 AI-coding-agent research papers. This document maps the field (competitors + research) to our repos, records what @@ -12,14 +12,14 @@ plans in `docs/plans/competitive-gap-*.md` and the extra fixes they surfaced. - Competitors: `docs/COMPETITIVE.md` (20 OSS tools, source-cited). - Research: 20 papers read via arXiv (abstracts) 2026-09-09 — listed below. -- Repo capability inventory: `hawk/README.md`, `eyrie/README.md`, +- Repo capability inventory: `rho/README.md`, `eyrie/README.md`, `graycode-platform/README.md`, `graycode-skills/README.md`, plus source audits. ## Our repos at a glance | Repo | Role | Key strengths | |---|---|---| -| hawk | Product face (Go, Bubble Tea v2) | 120+ tools, Docker fail-closed sandbox, `/autonomy`+`/spec` gates, `mission` multi-agent, execution-graph export, AST repomap + Harrier memory, MCP/LSP | +| rho | Product face (Go, Bubble Tea v2) | 120+ tools, Docker fail-closed sandbox, `/autonomy`+`/spec` gates, `mission` multi-agent, execution-graph export, AST repomap + Harrier memory, MCP/LSP | | eyrie | Provider engine facade | 22 gateways, routing/retry/caching/compaction, OpenAI-compat proxy, model catalog | | graycode-platform | Optional cloud/BFF plane | web + identity BFF + control-plane worker (not a runtime dep) | | graycode-skills | Skill marketplace | 14,015 skills, 27 categories, SKILL.md frontmatter + validation | diff --git a/docs/SECURITY-DEVELOPER.md b/docs/SECURITY-DEVELOPER.md index de1d7f01e..953ded11e 100644 --- a/docs/SECURITY-DEVELOPER.md +++ b/docs/SECURITY-DEVELOPER.md @@ -1,13 +1,13 @@ -# Hawk developer security model +# Rho developer security model -This document describes how hawk and eyrie handle API keys and agent isolation for an individual developer on macOS or Linux (no Vault, no proxy). Teams and enterprise deployment models come later. +This document describes how rho and eyrie handle API keys and agent isolation for an individual developer on macOS or Linux (no Vault, no proxy). Teams and enterprise deployment models come later. ## Goals - API keys live only in the OS secret store (macOS Keychain / Linux GNOME Keyring or KWallet). -- Hawk does not read API keys from `.env`, shell env, or plaintext files. +- Rho does not read API keys from `.env`, shell env, or plaintext files. - Eyrie's `provider.json` holds routing and deployment metadata only — never secrets on disk. -- Hawk talks to eyrie without putting keys in JSON or chat messages. +- Rho talks to eyrie without putting keys in JSON or chat messages. - Agent commands run on the host under the permission engine; file tools cannot read credential paths. @@ -15,12 +15,12 @@ This document describes how hawk and eyrie handle API keys and agent isolation f | Write | Read | Remove | |-------|------|--------| -| `/config` paste flow → `eyrie/engine.Engine.SaveCredential` | `Engine.ResolveCredential` (secret store only) | `/config key remove` or `hawk credentials remove` | +| `/config` paste flow → `eyrie/engine.Engine.SaveCredential` | `Engine.ResolveCredential` (secret store only) | `/config key remove` or `rho credentials remove` | -Hawk does not import keys from legacy `~/.hawk/env` / `~/.hawk/.env` files or +Rho does not import keys from legacy `~/.rho/env` / `~/.rho/.env` files or from secret fields in `provider.json`. Save every key through `/config`. -Check status: `hawk credentials status`, `hawk path`, or `hawk preflight`. +Check status: `rho credentials status`, `rho path`, or `rho preflight`. ## First-run flow (`/config`) @@ -28,7 +28,7 @@ Check status: `hawk credentials status`, `hawk path`, or `hawk preflight`. User pastes API key in /config | v -Hawk /config -> Eyrie engine credential service (OS secret store) +Rho /config -> Eyrie engine credential service (OS secret store) | v Eyrie engine discover/apply (credentials from store, not JSON body) @@ -42,20 +42,20 @@ User picks model -> Eyrie provider.json (canonical id only) Remove a stored key: `/config key remove` (interactive picker). -## Hawk to Eyrie +## Rho to Eyrie -- **Control plane**: Hawk calls only `eyrie/engine`; no lower Eyrie package is a +- **Control plane**: Rho calls only `eyrie/engine`; no lower Eyrie package is a production import. - **Discovery/apply**: credentials are resolved from the Engine's injected secret store; provider state and request bodies remain sanitized. -- **Chat**: Hawk sends model intent, messages, and tool definitions; Eyrie +- **Chat**: Rho sends model intent, messages, and tool definitions; Eyrie resolves the gateway and reads secrets internally. ## Agent execution ``` +------------------+ -| Hawk TUI/host | +| Rho TUI/host | | Keychain access | | /config paste | +------------------+ @@ -65,21 +65,21 @@ Remove a stored key: `/config key remove` (interactive picker). Host command execution ``` -Hawk executes agent commands directly on the host. Every tool call passes +Rho executes agent commands directly on the host. Every tool call passes through the permission engine (autonomy tier, allow/deny rules, approval gate, and dry-run kill switch) before it runs. No Docker daemon or container runtime is required. ### Blocked for agents -- **Read** tool: legacy Hawk env files, Eyrie's configured `provider.json`, +- **Read** tool: legacy Rho env files, Eyrie's configured `provider.json`, `~/.ssh/*`, etc. -- **Bash**: `printenv`, `env`, reading hawk env paths, echoing `*_API_KEY` variables. +- **Bash**: `printenv`, `env`, reading rho env paths, echoing `*_API_KEY` variables. ## Secrets left on disk -- **provider.json secrets**: `hawk path` fails its `provider.json` security - check when the file still holds secret fields. Hawk does not remove them +- **provider.json secrets**: `rho path` fails its `provider.json` security + check when the file still holds secret fields. Rho does not remove them automatically: back up the file, delete the secret fields, and save the keys again through `/config`. - **Provider state writes**: the Eyrie engine sanitizes provider state and @@ -92,24 +92,24 @@ Eyrie owns the provider-state path. Resolution order is: 1. `EYRIE_CONFIG_DIR/provider.json` 2. the platform user-config directory under `eyrie/provider.json` -Hawk's Read/Edit/Write and Bash safety checks protect the resolved path, +Rho's Read/Edit/Write and Bash safety checks protect the resolved path, including a custom or symlinked `EYRIE_CONFIG_DIR`; protection is not limited to the historical default provider-state location. ## Environment variables -Non-secret overrides only (hawk does not load provider API keys from env): +Non-secret overrides only (rho does not load provider API keys from env): | Variable | Meaning | |----------|---------| -| `HAWK_CONFIG_DIR` | Override hawk config directory | +| `RHO_CONFIG_DIR` | Override rho config directory | | `EYRIE_CONFIG_DIR` | Override Eyrie provider-state directory; takes precedence for `provider.json` | | `OPENAI_MODEL` | Override default OpenAI model | | `OLLAMA_BASE_URL` | Ollama server URL (also saved via `/config` for Ollama) | ## Related code -- Hawk: `internal/config/eyrie_engine.go`, `internal/tool/safety.go`, +- Rho: `internal/config/eyrie_engine.go`, `internal/tool/safety.go`, `internal/storage/paths.go`, `cmd/credentials.go` - Eyrie public host boundary: `engine/` - Daemon HTTP surface: [`docs/DAEMON-PORT-THREAT-MODEL.md`](DAEMON-PORT-THREAT-MODEL.md) diff --git a/docs/architecture.md b/docs/architecture.md index 931bf5d67..588d4d9d4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@

-# bird hawk Architecture +# bird rho Architecture **AI Coding Agent for Your Terminal** @@ -14,19 +14,19 @@ ## target Overview -hawk is an AI-powered coding agent for the terminal. It reads codebases, writes and edits files, runs tests, and manages git — all through natural language. Zero CGO, single static binary for linux/darwin/windows on amd64/arm64. +rho is an AI-powered coding agent for the terminal. It reads codebases, writes and edits files, runs tests, and manages git — all through natural language. Zero CGO, single static binary for linux/darwin/windows on amd64/arm64. -Detailed planning docs for the Hawk product architecture live in [`docs/architecture/`](architecture/README.md). +Detailed planning docs for the Rho product architecture live in [`docs/architecture/`](architecture/README.md). --- ## blocks Layered Architecture ``` -hawk/ +rho/ ├── api/openapi.yaml file-text Daemon REST API contract (OpenAPI 3.1) ├── cmd/ terminal Cobra CLI commands (200+ files) -│ ├── hawk/main.go zap Entry point — calls cmd.Execute() +│ ├── rho/main.go zap Entry point — calls cmd.Execute() │ ├── root.go settings Root command, flag definitions │ ├── daemon.go server Daemon start/stop/status │ ├── chat.go message-square Interactive TUI chat @@ -62,8 +62,8 @@ hawk/ | | | |---|---| | **Contract** | [`api/openapi.yaml`](../api/openapi.yaml) | -| **Port** | `:4590` (default). Override: `HAWK_DAEMON_PORT` | -| **Auth** | Bearer token or `X-API-Key`. Set via `HAWK_DAEMON_API_KEY` | +| **Port** | `:4590` (default). Override: `RHO_DAEMON_PORT` | +| **Auth** | Bearer token or `X-API-Key`. Set via `RHO_DAEMON_API_KEY` |
radio Endpoint Summary @@ -100,7 +100,7 @@ hawk/ All three SDKs share types from **eagle** and consume the daemon REST API (:4590). -> lightbulb **hawk never talks to LLM APIs directly** — all calls go through eyrie. +> lightbulb **rho never talks to LLM APIs directly** — all calls go through eyrie. --- @@ -118,9 +118,9 @@ Tool Call → ` — persisted Hawk session; -- `hawk/task-request//` — user task request, content represented by +- `rho/session/` — persisted Rho session; +- `rho/task-request//` — user task request, content represented by SHA-256 only; -- `hawk/task/` — structured task; -- `hawk/runtime-task/` — background agent, shell, or monitor task; -- `hawk/tool-call//` — tool invocation metadata; -- `hawk/policy/` — permission verdict; -- `hawk/verification/` — neutral verification result; +- `rho/task/` — structured task; +- `rho/runtime-task/` — background agent, shell, or monitor task; +- `rho/tool-call//` — tool invocation metadata; +- `rho/policy/` — permission verdict; +- `rho/verification/` — neutral verification result; - `eyrie/route/` and `eyrie/generation/` — model route and normalized generation operations. @@ -120,15 +120,15 @@ The export deliberately excludes: Where correlation is useful, sensitive values are represented by SHA-256 digests. Every node declares `data_classification=metadata_only`. -Hawk writes runtime observations to a per-session append-only JSONL journal. -The journal is stored with mode `0600` under Hawk's state directory and contains +Rho writes runtime observations to a per-session append-only JSONL journal. +The journal is stored with mode `0600` under Rho's state directory and contains only verdict metadata, aggregate verification counts, and SHA-256 digests. A journal failure is logged and never changes a permission, approval, or tool result. Mission-mode graph execution persists a separate `mission-graph.json` artifact beside `mission.json` in the mission directory. The artifact uses the same -portable `hawk.graph/v1` envelope, but it is mission-scoped rather than +portable `rho.graph/v1` envelope, but it is mission-scoped rather than session-scoped: mission node, feature nodes, and wave-join operations nodes are rewritten after each mission run or wave join using only metadata and SHA-256 digests. @@ -136,7 +136,7 @@ digests. ## CLI ```bash -hawk graph export [session-id] +rho graph export [session-id] --repository ``` @@ -155,18 +155,18 @@ GET /v1/sessions/{id}/graph The endpoint uses the daemon's normal Bearer or `X-API-Key` authentication, validates session and repository inputs before projection, and -returns the typed `hawk.graph/v1` envelope. The handler owns transport concerns -only; the Hawk composition root injects the existing graph builder, so CLI, +returns the typed `rho.graph/v1` envelope. The handler owns transport concerns +only; the Rho composition root injects the existing graph builder, so CLI, Cloud sync, and HTTP projections share one construction path. The Go, Python sync/async, and TypeScript SDK clients expose this endpoint and validate the returned graph topology before returning it to callers. -After connecting a project with `hawk cloud login` or `hawk cloud connect`, a +After connecting a project with `rho cloud login` or `rho cloud connect`, a completed session snapshot can be uploaded explicitly: ```bash -hawk cloud graph sync [session-id] +rho cloud graph sync [session-id] --repository ``` @@ -175,18 +175,18 @@ keys to `*_sha256`, enforces the 250-node/500-edge/500-event/900-total-fact and 1 MiB limits, and derives the sync ID from the prepared graph. Repeating the same completed snapshot is therefore idempotent. Upload errors are reported to the explicit command, but cloud availability never affects local execution. -Hawk does not upload prompt bodies, response bodies, tool arguments, tool +Rho does not upload prompt bodies, response bodies, tool arguments, tool results, or other large artifacts. Mission-scoped graph artifacts can use the same explicit path without being -pretended to be a Hawk session: +pretended to be a Rho session: ```bash -hawk graph export --mission-dir -hawk cloud graph sync --mission-dir +rho graph export --mission-dir +rho cloud graph sync --mission-dir ``` -The CLI reads only `mission-graph.json`, validates its `hawk.graph/v1` schema +The CLI reads only `mission-graph.json`, validates its `rho.graph/v1` schema and all edge/event references, then applies the same privacy normalization and Cloud limits. Mission graphs intentionally omit `sessionId`; they are durable mission facts, not Cloud session telemetry. @@ -205,12 +205,12 @@ The central tool-execution seam automatically records: - Eyrie model route and usage reported for persisted-session turns. Mission execution now also persists a portable graph artifact for local mission -runs. Plain `hawk mission` runs emit mission and feature execution nodes; the -graph-driven `hawk mission --from-tasks` path additionally emits operations +runs. Plain `rho mission` runs emit mission and feature execution nodes; the +graph-driven `rho mission --from-tasks` path additionally emits operations nodes for each deterministic wave join, including bounded completion, failure, and blocked-downstream counts. The embedded token-pipeline tracker contributes hourly, daily, session, and cost -limits to the pre-turn guard. Existing Hawk cost accounting and limits remain +limits to the pre-turn guard. Existing Rho cost accounting and limits remain authoritative and are updated first; the tracker observes the same deduplicated request usage and provides the additional token-window decision. diff --git a/docs/architecture/frontend-contract.md b/docs/architecture/frontend-contract.md new file mode 100644 index 000000000..526c1cef6 --- /dev/null +++ b/docs/architecture/frontend-contract.md @@ -0,0 +1,60 @@ +# Frontend contract + +Rho has one agent brain and several frontends. They meet at a single typed +channel: `engine.StreamEvent`. This document defines that boundary so the TUI, +daemon, ACP server, and print mode stay swappable — the discipline Tau calls +"events are the contract." + +## The contract + +`engine.Session.Stream(ctx)` returns `<-chan engine.StreamEvent`. Every +frontend consumes that channel and renders it; nothing else crosses the +boundary. + +```go +type StreamEvent struct { + Type string // content, thinking, tool_use, tool_result, + // usage, compact, compact_start, done, error + Content string + ToolName string + ToolID string + ToolState ToolState + ToolReason ToolTerminalReason + Usage *StreamUsage + TokensBefore int + TokensAfter int +} +``` + +## Rules + +1. **The engine emits, frontends consume.** A frontend must not call into + engine internals to observe agent state; if it needs a new signal, add a + `StreamEvent` type, not a getter. +2. **Events are the only cross-boundary type.** Frontends may read the + public facade types (`engine.Session`, `engine.StreamEvent`, + `engine.StreamUsage`) but must not import engine subpackages + (`internal/engine/agent`, `internal/engine/code`, …). +3. **Terminal event is guaranteed.** Every turn ends with exactly one + `EventDone` (or `done`), on success, error, and cancellation paths, so a + consumer can always know a turn finished. +4. **Frontends own rendering.** Markdown, color, layout, and input handling + live in the frontend. The engine never formats for a specific terminal. + +## Consumers + +| Frontend | Entry point | Role | +|---|---|---| +| TUI | `cmd/chat*.go` | Bubble Tea interactive interface | +| Print | `cmd/chat_print.go` | `-p` one-shot text/JSON output | +| Daemon | `internal/daemon` | HTTP + SSE over the same events | +| ACP | `internal/acp` | Editor (Zed) JSON-RPC bridge | + +## Enforcement + +`internal/testaudit` asserts that `cmd/` does not import engine subpackages +beyond the public facade. Run it with: + +```bash +go test ./internal/testaudit -run TestFrontendContract -count=1 +``` \ No newline at end of file diff --git a/docs/architecture/graycode-ecosystem-summary.md b/docs/architecture/graycode-ecosystem-summary.md index 2f27ba621..700361a29 100644 --- a/docs/architecture/graycode-ecosystem-summary.md +++ b/docs/architecture/graycode-ecosystem-summary.md @@ -1,12 +1,12 @@ # GrayCode Ecosystem Summary > **Historical.** This document describes the pre-2026-09 multi-engine -> ecosystem. Hawk now depends only on `eyrie` and embeds its own token engine; +> ecosystem. Rho now depends only on `eyrie` and embeds its own token engine; > the harrier, kestrel, merlin, shrike, and swift integrations have been > removed. Kept for record. -`graycode-eco` is only a local parent folder. `hawk` is the main CLI and the -only primary Hawk product; the other repositories provide capabilities, +`graycode-eco` is only a local parent folder. `rho` is the main CLI and the +only primary Rho product; the other repositories provide capabilities, contracts, integrations, tooling, or optional platform services. ## Repository layers @@ -15,7 +15,7 @@ contracts, integrations, tooling, or optional platform services. API consumers/extensions sparrow robin wren starling \ | | / - hawk + rho (main CLI and daemon) | Support engines @@ -26,7 +26,7 @@ Foundations Outside the Go runtime graph owl architecture tooling - graycode-platform web, BFF, and Hawk Cloud Worker + graycode-platform web, BFF, and Rho Cloud Worker ``` Product labels map to directories as follows: `harrier`/Harrier, `shrike`/Shrike, @@ -35,20 +35,20 @@ Product labels map to directories as follows: `harrier`/Harrier, `shrike`/Shrike ## Dependency direction ```text -hawk -> eyrie / harrier / shrike / swift / kestrel / merlin / eagle +rho -> eyrie / harrier / shrike / swift / kestrel / merlin / eagle engines -> eagle # when shared contracts are needed harrier / kestrel / merlin -> falcon -sparrow / robin / wren -> Hawk daemon API -starling -> Hawk skill/plugin API +sparrow / robin / wren -> Rho daemon API +starling -> Rho skill/plugin API ``` -Forbidden edges are engine-to-engine, engine-to-Hawk-internal, SDK-to-engine, +Forbidden edges are engine-to-engine, engine-to-Rho-internal, SDK-to-engine, skills-to-engine, and any Go-module dependency on GrayCode Platform. ## Runtime and hosted plane ```text -Hawk main CLI +Rho main CLI ├── Eyrie provider execution ├── Harrier memory ├── Shrike token/context management @@ -56,20 +56,20 @@ Hawk main CLI ├── Kestrel review └── Merlin verification -hawk ── optional authenticated HTTP ──> graycode-platform/apps/worker +rho ── optional authenticated HTTP ──> graycode-platform/apps/worker web ──> graycode-platform/apps/bff ── private Service Binding ──> worker worker ──> control-plane D1 + usage Queue + R2 ``` The Worker is deployed as `graycode-cloud`, but that is an application name, -not a repository. GrayCode Platform remains outside the Hawk Go module graph. -Hawk's cloud usage path is fail-open; graph synchronization is explicit. +not a repository. GrayCode Platform remains outside the Rho Go module graph. +Rho's cloud usage path is fail-open; graph synchronization is explicit. ## Repository roles | Repository | Role | Direct dependency rule | |---|---|---| -| `hawk` | Main CLI, daemon, orchestration, policy | Integrates engines and contracts | +| `rho` | Main CLI, daemon, orchestration, policy | Integrates engines and contracts | | `eyrie` | Provider runtime | Uses Eagle contracts; exposes `engine` | | `harrier` | Harrier memory | Uses Eagle/Falcon where required | | `shrike` | Shrike context engine | Uses Eagle where required | @@ -78,10 +78,10 @@ Hawk's cloud usage path is fail-open; graph synchronization is explicit. | `merlin` | Merlin verification | Uses Eagle and Falcon | | `eagle` | Neutral shared contracts | Leaf module | | `falcon` | MCP kit | Upstream MCP library only | -| `sparrow` | Go SDK | Hawk API contract | -| `robin` | Python SDK | Hawk API contract | -| `wren` | TypeScript SDK | Hawk API contract | -| `starling` | Skills/extensions | Hawk skill surface | +| `sparrow` | Go SDK | Rho API contract | +| `robin` | Python SDK | Rho API contract | +| `wren` | TypeScript SDK | Rho API contract | +| `starling` | Skills/extensions | Rho skill surface | | `owl` | Architecture explorer | Generated read-only projection | | `graycode-platform` | Web/BFF/cloud | HTTP and Service Binding only | @@ -91,5 +91,5 @@ The repository-level target is implemented: independent Git repositories, canonical manifest, generated Owl inventory, sibling Go workspace, Eagle parity, and dependency boundary checks are all present. Remaining work is to publish the Eagle-compatible Eyrie revision, remove the transitional -`hawk-core-contracts` dependency from the standalone graph, and decide whether -Hawk's graph/projection packages need additional engine-owned facades. +`rho-core-contracts` dependency from the standalone graph, and decide whether +Rho's graph/projection packages need additional engine-owned facades. diff --git a/docs/architecture/plan.md b/docs/architecture/plan.md index 09275039a..90980e620 100644 --- a/docs/architecture/plan.md +++ b/docs/architecture/plan.md @@ -1,8 +1,8 @@ -# Hawk Architecture - Technical Plan +# Rho Architecture - Technical Plan ## Overview -This plan defines the technical approach for implementing the hawk architecture specification. The architecture is already largely implemented; this plan documents the existing design decisions and identifies gaps. +This plan defines the technical approach for implementing the rho architecture specification. The architecture is already largely implemented; this plan documents the existing design decisions and identifies gaps. ## Architecture Decisions diff --git a/docs/architecture/hawk-architecture-baseline.md b/docs/architecture/rho-architecture-baseline.md similarity index 83% rename from docs/architecture/hawk-architecture-baseline.md rename to docs/architecture/rho-architecture-baseline.md index 26bf608ad..f727ccb5a 100644 --- a/docs/architecture/hawk-architecture-baseline.md +++ b/docs/architecture/rho-architecture-baseline.md @@ -1,7 +1,7 @@ -# Hawk Architecture Baseline +# Rho Architecture Baseline > **Historical.** This document describes the pre-2026-09 multi-engine -> ecosystem. Hawk now depends only on `eyrie` and embeds its own token engine; +> ecosystem. Rho now depends only on `eyrie` and embeds its own token engine; > the harrier, kestrel, merlin, shrike, and swift integrations have been > removed. Kept for record. @@ -9,7 +9,7 @@ **Date:** 2026-08-04 **Baseline commit:** `69ce83f55f9098623e5a891e8c52be636db89c7c` -This document records the architecture that exists in the Hawk repository at +This document records the architecture that exists in the Rho repository at the beginning of the architecture improvement program. It separates current implementation facts from the intended ecosystem design. It is not a product quality score and does not claim that the migration is complete. @@ -19,16 +19,16 @@ quality score and does not claim that the migration is complete. Use the documents in this order when statements conflict: 1. This document for the dated implementation baseline and migration status. -2. `hawk-current-vs-proposed.md` for the ecosystem repository map. -3. `hawk-product-architecture.md` for ownership and runtime responsibilities. -4. `hawk-dependency-rules.md` for allowed and forbidden dependency edges. +2. `rho-current-vs-proposed.md` for the ecosystem repository map. +3. `rho-product-architecture.md` for ownership and runtime responsibilities. +4. `rho-dependency-rules.md` for allowed and forbidden dependency edges. 5. `spec.md` for behavioral requirements and agent-loop semantics. -Hawk is the main Go CLI/product and workspace entry point in a +Rho is the main Go CLI/product and workspace entry point in a multi-repository ecosystem. `graycode-eco` is only the plain parent folder; the sibling repositories retain independent Git histories and release cadences. Local integration uses the parent `go.work`; standalone builds use -published module pins. There is no current `hawk/external/` vendor tree. +published module pins. There is no current `rho/external/` vendor tree. ## Target product graph @@ -36,7 +36,7 @@ published module pins. There is no current `hawk/external/` vendor tree. users / SDKs / skills / daemon clients | v - hawk + rho / | \ eyrie harrier shrike swift kestrel merlin @@ -47,25 +47,25 @@ users / SDKs / skills / daemon clients The graph is intentionally directional: -- Hawk owns user-facing orchestration, sessions, tools, permissions, +- Rho owns user-facing orchestration, sessions, tools, permissions, composition, and public product surfaces. - Eyrie owns provider protocols, routing, credentials, catalogs, and provider execution behind `eyrie/engine`. - Harrier, Shrike, Swift, Kestrel, and Merlin are support engines and must not import - Hawk internals or one another. + Rho internals or one another. - Core contracts contain stable cross-repository vocabulary and DTOs, not runtime orchestration. -- SDKs and skills consume Hawk surfaces rather than support-engine internals. +- SDKs and skills consume Rho surfaces rather than support-engine internals. ## Current implementation state ### Complete or enforced -- Hawk production code uses Eyrie through the `eyrie/engine` facade. -- Kestrel and Merlin are integrated through Hawk bridge packages. -- Support-engine sibling imports and imports of Hawk internals are guarded. +- Rho production code uses Eyrie through the `eyrie/engine` facade. +- Kestrel and Merlin are integrated through Rho bridge packages. +- Support-engine sibling imports and imports of Rho internals are guarded. - The AST/package-graph guard reports production boundary violations with - file/line diagnostics across Hawk and available support repositories. + file/line diagnostics across Rho and available support repositories. - Persisted tool, review, verification, event, and policy contracts use the implemented portions of `eagle`. - Native-compaction capability contracts use `eagle/llm`; Eyrie @@ -92,7 +92,7 @@ The graph is intentionally directional: At this baseline its top-level production files contain approximately 19,253 lines, its top-level tests approximately 11,855 lines, and the subtree contains compatibility alias/re-export files. -- Hawk's Harrier and Shrike implementation imports are now concentrated in +- Rho's Harrier and Shrike implementation imports are now concentrated in `HarrierBridge` and `internal/token` for the migrated production paths. The graph/projection packages used for capture remain explicit integration surfaces; replaceability is improved, but still not equivalent to the Eyrie @@ -109,7 +109,7 @@ The graph is intentionally directional: - CLI, daemon, and other entry points share substantial construction and orchestration responsibilities instead of depending on one explicit application composition root. -- Non-interactive entry points now share `cmd.newConfiguredHawkSession`; the +- Non-interactive entry points now share `cmd.newConfiguredRhoSession`; the interactive TUI intentionally retains a lightweight startup path followed by deferred heavy configuration to protect first-frame latency. @@ -117,15 +117,15 @@ The graph is intentionally directional: ### ADR-B01 — Keep the multi-repository ecosystem -Do not collapse the support engines into Hawk or force a shared release cycle. -The `graycode-eco` parent workspace and Hawk's published module pins provide +Do not collapse the support engines into Rho or force a shared release cycle. +The `graycode-eco` parent workspace and Rho's published module pins provide local integration without removing independent ownership and release boundaries. ### ADR-B02 — Preserve the Eyrie boundary Provider implementation, catalog metadata, credential mapping, and protocol -adapters remain owned by Eyrie. Hawk may own product policy and user-facing +adapters remain owned by Eyrie. Rho may own product policy and user-facing selection, but production provider access remains through `eyrie/engine`. ### ADR-B03 — Complete internal consolidation before adding new seams @@ -137,7 +137,7 @@ packages should not be added until the current seams are explicit. ### ADR-B04 — Add facades selectively Harrier, Shrike, and Swift require a facade decision based on actual replacement and -release needs. A facade is justified when it isolates Hawk from implementation +release needs. A facade is justified when it isolates Rho from implementation types or enables independent upgrades; it is not justified merely to increase the number of packages. @@ -183,7 +183,7 @@ initialization, cost snapshots, and WAL recovery error reporting are now synchronized and tested. Phase 3 has explicit non-interactive and interactive startup composition boundaries, with heavy TUI configuration remaining deferred for first-frame latency. Phase 4 has consolidated the migrated Harrier -and Shrike implementation imports behind narrow Hawk-owned facades. The next +and Shrike implementation imports behind narrow Rho-owned facades. The next decision is the persistence ADR: document and enforce one durable authority, then define the migration and recovery contract before introducing additional storage backends. diff --git a/docs/architecture/hawk-architecture-v1-definition-of-done.md b/docs/architecture/rho-architecture-v1-definition-of-done.md similarity index 75% rename from docs/architecture/hawk-architecture-v1-definition-of-done.md rename to docs/architecture/rho-architecture-v1-definition-of-done.md index 2a942b786..e0c5d741d 100644 --- a/docs/architecture/hawk-architecture-v1-definition-of-done.md +++ b/docs/architecture/rho-architecture-v1-definition-of-done.md @@ -1,6 +1,6 @@ -# Hawk Architecture v1 — Definition of Done +# Rho Architecture v1 — Definition of Done -This document defines **realistic v1 complete** for the Hawk main CLI and its +This document defines **realistic v1 complete** for the Rho main CLI and its connected ecosystem architecture. `graycode-eco` is only the local parent folder; it is not a repository or product. @@ -10,7 +10,7 @@ It is the shipping bar for the contracts-and-boundaries refactor. It is **not** v1 is done when the ecosystem has: -- one product surface (`hawk`) +- one product surface (`rho`) - six peer support engines with no sibling imports - shared vocabulary only where cross-repo pain is real - automated guards so the old coupling cannot return @@ -27,17 +27,17 @@ Status note: ### Product and dependency graph -- [x] `hawk` is the only primary end-user product in the ecosystem -- [x] Hawk coordinates engines; engines do not import each other -- [x] SDKs and community skills consume Hawk public surfaces, not engines directly -- [x] `graycode-platform` and other company/platform repos stay outside Hawk runtime dependencies +- [x] `rho` is the only primary end-user product in the ecosystem +- [x] Rho coordinates engines; engines do not import each other +- [x] SDKs and community skills consume Rho public surfaces, not engines directly +- [x] `graycode-platform` and other company/platform repos stay outside Rho runtime dependencies ### Forbidden edges stay forbidden -- [x] no support repo imports `hawk/internal/*` +- [x] no support repo imports `rho/internal/*` - [x] no SDK/skills repo references support engines as primary dependencies -- [x] Hawk production code imports Eyrie only through `eyrie/engine` -- [x] Hawk's graph/projection imports are documented as explicit integration +- [x] Rho production code imports Eyrie only through `eyrie/engine` +- [x] Rho's graph/projection imports are documented as explicit integration surfaces and do not create engine-to-engine dependencies ### `eagle` (implemented packages only) @@ -48,7 +48,7 @@ These packages are in scope for v1: - [x] `review/` — neutral review results - [x] `verify/` — neutral verification reports - [x] `tools/` — persisted tool call/result contracts -- [x] `events/` — normalized audit/swift event subset used by Hawk +- [x] `events/` — normalized audit/swift event subset used by Rho - [x] `policy/` — permission and guardian decision contracts Adoption bar: @@ -57,16 +57,16 @@ Adoption bar: - [x] `shrike/types` compatibility shim removed from the local ecosystem - [x] `eyrie`, `harrier`, and `swift` remain contract-free unless they gain a true cross-repo type -### Hawk integration seams +### Rho integration seams - [x] session persistence uses `eagle/tools`, not lower-level provider tool types - [x] review persistence and merlin/review bridge paths use neutral `review` / `verify` contracts -- [x] Hawk owns runtime DTOs in `internal/types` and translates them to `eyrie/engine` in `internal/engine` -- [x] `hawk swift ...` remains a Hawk-mounted subcommand, not a competing product surface +- [x] Rho owns runtime DTOs in `internal/types` and translates them to `eyrie/engine` in `internal/engine` +- [x] `rho swift ...` remains a Rho-mounted subcommand, not a competing product surface ### Enforcement -- [x] Hawk CI runs ecosystem, eyrie-client, and peer-coupling guards +- [x] Rho CI runs ecosystem, eyrie-client, and peer-coupling guards - [x] each support repo runs `check-ecosystem-boundaries.sh` in CI - [x] Go SDK runs consumer boundary guard in CI - [x] Python SDK and community skills run consumer boundary guards in CI @@ -77,15 +77,15 @@ Adoption bar: ### Ship state - [ ] local architecture commits are pushed and merged to upstream default branches -- [ ] open architecture PRs for engines, consumers, and Hawk integration are merged to `main` -- [ ] published module versions used by Hawk match the merged contract changes +- [ ] open architecture PRs for engines, consumers, and Rho integration are merged to `main` +- [ ] published module versions used by Rho match the merged contract changes ## Explicit non-goals for v1 Do not block v1 on any of the following: - `eagle/sessions` or `eagle/engines` -- moving every Hawk internal event struct into `eagle/events` +- moving every Rho internal event struct into `eagle/events` - moving swift timeline/event models out of `swift` - unifying runtime `internal/types` DTOs and persisted contracts into one type - forcing every engine into the same integration depth (library vs subcommand vs service) @@ -107,7 +107,7 @@ Remove compatibility shims only when: ## Verify locally -From `hawk`: +From `rho`: ```bash make ecosystem-guard eyrie-client-guard eyrie-engine-guard peer-guard @@ -124,7 +124,7 @@ go test ./... -count=1 ## Related docs -- `hawk-product-architecture.md` — target shape and phases -- `hawk-dependency-rules.md` — allowed and forbidden edges +- `rho-product-architecture.md` — target shape and phases +- `rho-dependency-rules.md` — allowed and forbidden edges - `eagle-spec.md` — contract inventory and planned packages - `../plans/eagles-migration-backlog.md` — migration history and follow-ups diff --git a/docs/architecture/hawk-capability-seams.md b/docs/architecture/rho-capability-seams.md similarity index 96% rename from docs/architecture/hawk-capability-seams.md rename to docs/architecture/rho-capability-seams.md index e52062913..6fb9f64ac 100644 --- a/docs/architecture/hawk-capability-seams.md +++ b/docs/architecture/rho-capability-seams.md @@ -1,7 +1,7 @@ # Capability seams Deepseek-harness keeps every side effect behind a named, replaceable seam with a -strict owner. Hawk adopts the same discipline without the Cordis dependency +strict owner. Rho adopts the same discipline without the Cordis dependency container: seams are plain Go interfaces or registries wired at the composition root, and every registration that can outlive a request returns a disposer. diff --git a/docs/architecture/hawk-contract-spec.md b/docs/architecture/rho-contract-spec.md similarity index 80% rename from docs/architecture/hawk-contract-spec.md rename to docs/architecture/rho-contract-spec.md index 776c9d95e..c5564843d 100644 --- a/docs/architecture/hawk-contract-spec.md +++ b/docs/architecture/rho-contract-spec.md @@ -1,10 +1,10 @@ -# Hawk Core Contracts Spec +# Rho Core Contracts Spec ## Purpose -`eagle` is the shared language spoken by Hawk and its engines. +`eagle` is the shared language spoken by Rho and its engines. -It exists to remove cross-repo dependency on Hawk internals and to prevent engines from importing each other. +It exists to remove cross-repo dependency on Rho internals and to prevent engines from importing each other. ## Scope @@ -25,7 +25,7 @@ Not allowed: - runtime logic - storage implementations - engine implementations -- Hawk application internals +- Rho application internals ## Package layout @@ -87,12 +87,12 @@ list in sync with the code (it is the inventory the dependency rules assume). ## Migration order -1. inventory cross-repo shared types in `hawk/shared` and `hawk/internal/types` +1. inventory cross-repo shared types in `rho/shared` and `rho/internal/types` 2. move stable shared types into contracts 3. update `kestrel` 4. update `merlin` -5. update other repos that rely on Hawk-exported shared types -6. leave product-only types inside Hawk +5. update other repos that rely on Rho-exported shared types +6. leave product-only types inside Rho ## Current status @@ -103,9 +103,9 @@ list in sync with the code (it is the inventory the dependency rules assume). - event contracts now exist in `eagle/events` - policy contracts now exist in `eagle/policy` - portable graph contracts now exist in `eagle/graph` -- Hawk session persistence has started migrating to provider-neutral tool contracts -- Hawk review storage and merlin/review bridge paths now consume neutral review/verify contracts -- Hawk runtime conversation DTOs and the `ChatClient` port are Hawk-owned and +- Rho session persistence has started migrating to provider-neutral tool contracts +- Rho review storage and merlin/review bridge paths now consume neutral review/verify contracts +- Rho runtime conversation DTOs and the `ChatClient` port are Rho-owned and translated to the stable `eyrie/engine` contract at the integration edge ## Versioning rule diff --git a/docs/architecture/hawk-current-vs-proposed.md b/docs/architecture/rho-current-vs-proposed.md similarity index 75% rename from docs/architecture/hawk-current-vs-proposed.md rename to docs/architecture/rho-current-vs-proposed.md index fd739236d..24679d287 100644 --- a/docs/architecture/hawk-current-vs-proposed.md +++ b/docs/architecture/rho-current-vs-proposed.md @@ -1,16 +1,16 @@ -# Hawk Current vs Proposed Architecture +# Rho Current vs Proposed Architecture ## Purpose This document explains the connected repository design. The canonical -machine-readable inventory is [`hawk/ecosystem.yaml`](../../ecosystem.yaml); +machine-readable inventory is [`rho/ecosystem.yaml`](../../ecosystem.yaml); this document must not maintain a second repository list. The repository directory and product-name distinction is intentional: | Directory/repository | Product or role | |---|---| -| `hawk` | Hawk product and orchestration root | +| `rho` | Rho product and orchestration root | | `eyrie` | Eyrie provider runtime | | `harrier` | Harrier memory engine | | `shrike` | Shrike token/context engine | @@ -24,7 +24,7 @@ The repository directory and product-name distinction is intentional: | `wren` | TypeScript SDK | | `starling` | Community skills and extensions | | `owl` | Architecture visualization tooling | -| `graycode-platform` | GrayCode web, BFF, and Hawk Cloud deployment | +| `graycode-platform` | GrayCode web, BFF, and Rho Cloud deployment | ## Current workspace @@ -35,7 +35,7 @@ marked `workspace: true` in the manifest: ```text graycode-eco/ -├── hawk # product / composition root +├── rho # product / composition root ├── eagle # neutral contracts foundation ├── falcon # MCP foundation ├── eyrie # Eyrie provider engine @@ -49,17 +49,17 @@ graycode-eco/ ├── wren # TypeScript SDK ├── starling # skills/extensions ├── owl # architecture tooling -└── graycode-platform # web/BFF/Hawk Cloud platform +└── graycode-platform # web/BFF/Rho Cloud platform ``` -There is no `hawk/external` vendor tree in the current workspace. Local +There is no `rho/external` vendor tree in the current workspace. Local development uses sibling checkouts and `go.work`; standalone builds use the published versions pinned in each `go.mod`. ## Current connected design ```text -sparrow / robin / wren ── HTTP/OpenAPI ──> hawk <── skill surface ── starling +sparrow / robin / wren ── HTTP/OpenAPI ──> rho <── skill surface ── starling │ ├── eyrie/engine ├── harrier (Harrier) @@ -72,7 +72,7 @@ sparrow / robin / wren ── HTTP/OpenAPI ──> hawk <── skill surface engines ──> eagle harrier / kestrel / merlin ──> falcon ──> mark3labs/mcp-go -hawk ── optional, fail-open HTTP ──> graycode-platform/apps/worker +rho ── optional, fail-open HTTP ──> graycode-platform/apps/worker web ──> graycode-platform/apps/bff ── private Service Binding ──> worker worker ──> D1 + Queue + R2 @@ -81,27 +81,27 @@ owl <── canonical manifest and read-only generated architecture artifacts The compile-time graph is deliberately one-way: -- Hawk is the only orchestrator and product integration root. -- Engines are peers and do not import Hawk internals or one another. +- Rho is the only orchestrator and product integration root. +- Engines are peers and do not import Rho internals or one another. - Eagle contains only neutral, implementation-light contracts. - Falcon contains only reusable MCP transport/handler scaffolding. -- SDKs consume Hawk's daemon API contract; they do not import engines. -- Starling provides content and extension metadata through Hawk's skill surface. -- GrayCode Platform is outside the Hawk runtime module graph. +- SDKs consume Rho's daemon API contract; they do not import engines. +- Starling provides content and extension metadata through Rho's skill surface. +- GrayCode Platform is outside the Rho runtime module graph. ## Current versus proposed At the repository level, the proposed architecture is already implemented: independent repositories, a canonical manifest, sibling Go workspace wiring, -contract parity checks, and Hawk-centered dependency direction all exist. +contract parity checks, and Rho-centered dependency direction all exist. The remaining proposed work is boundary refinement rather than repository reorganization: -1. publish the Eagle-migrated Eyrie revision and update Hawk's standalone pin; -2. remove the transitional `hawk-core-contracts` dependency from the published +1. publish the Eagle-migrated Eyrie revision and update Rho's standalone pin; +2. remove the transitional `rho-core-contracts` dependency from the published module graph; -3. decide whether graph/projection packages should remain explicit Hawk +3. decide whether graph/projection packages should remain explicit Rho integration exceptions or be hidden behind engine-owned facades; 4. keep generated Owl inventory and architecture documents synchronized with `ecosystem.yaml`. @@ -109,12 +109,12 @@ reorganization: ## Allowed dependency edges ```text -hawk -> eyrie / harrier / shrike / swift / kestrel / merlin / eagle +rho -> eyrie / harrier / shrike / swift / kestrel / merlin / eagle engines -> eagle # only for shared contracts harrier / kestrel / merlin -> falcon -sparrow / robin / wren -> Hawk public HTTP/OpenAPI surface -starling -> Hawk skill/plugin surface -graycode-platform <-> Hawk via authenticated HTTP/Service Binding only +sparrow / robin / wren -> Rho public HTTP/OpenAPI surface +starling -> Rho skill/plugin surface +graycode-platform <-> Rho via authenticated HTTP/Service Binding only ``` The product names `Harrier`, `Shrike`, `Swift`, `Kestrel`, and `Merlin` are user-facing @@ -125,15 +125,15 @@ directories and Go module paths. ```text engine -> engine -engine -> hawk/internal/* +engine -> rho/internal/* SDK -> engine skills -> engine -any Hawk engine -> graycode-platform code +any Rho engine -> graycode-platform code any Go module -> graycode-platform code ``` -The sanctioned platform connection is runtime-only and HTTP-based: Hawk may -send explicitly enabled usage or graph requests to the deployed Hawk Cloud +The sanctioned platform connection is runtime-only and HTTP-based: Rho may +send explicitly enabled usage or graph requests to the deployed Rho Cloud Worker, and local execution must remain usable when that request fails. ## Recommendation diff --git a/docs/architecture/hawk-dependency-rules.md b/docs/architecture/rho-dependency-rules.md similarity index 74% rename from docs/architecture/hawk-dependency-rules.md rename to docs/architecture/rho-dependency-rules.md index 22106e491..ac63ea180 100644 --- a/docs/architecture/hawk-dependency-rules.md +++ b/docs/architecture/rho-dependency-rules.md @@ -1,13 +1,13 @@ -# Hawk Dependency Rules +# Rho Dependency Rules -`graycode-eco` is only a local parent folder. `hawk` is the main CLI and the +`graycode-eco` is only a local parent folder. `rho` is the main CLI and the single orchestration root. Repository names below use directory/module names; product labels are shown in parentheses where they differ. ## Required graph ```text -sparrow / robin / wren ── Hawk daemon API ──> hawk <── skill API ── starling +sparrow / robin / wren ── Rho daemon API ──> rho <── skill API ── starling │ ├── eyrie/engine ├── harrier (Harrier) @@ -22,7 +22,7 @@ engines ──> eagle when a shared contract is required ``` The canonical list of repositories is in -[`hawk/ecosystem.yaml`](../../ecosystem.yaml). It is the single source of +[`rho/ecosystem.yaml`](../../ecosystem.yaml). It is the single source of truth; other documents must not maintain a second repository list. ## Contract edges @@ -32,14 +32,14 @@ contract such as a finding, severity, event, or graph fact. Current Go module consumers are tracked by `ecosystem.yaml` and checked for Eagle version parity. Falcon is a separate foundation for shared MCP transport and handler patterns. -It remains upstream-only and must not import Hawk, Eagle, or an engine. +It remains upstream-only and must not import Rho, Eagle, or an engine. ## Forbidden graph ```text engine -> engine -engine -> hawk/internal/* -engine -> hawk/shared/* +engine -> rho/internal/* +engine -> rho/shared/* SDK -> engine skills -> engine any Go module -> graycode-platform code @@ -47,9 +47,9 @@ any Go module -> graycode-platform code ## Rules -### 1. Hawk is the orchestrator +### 1. Rho is the orchestrator -Only Hawk coordinates the support engines and owns the user-facing CLI, +Only Rho coordinates the support engines and owns the user-facing CLI, daemon, sessions, tools, policy, and product workflows. ### 2. Engines are peers @@ -60,18 +60,18 @@ Shrike, `swift` is Swift, `kestrel` is Kestrel, and `merlin` is Merlin. ### 3. Provider logic stays behind Eyrie -Hawk production code reaches provider credentials, catalogs, routing, and +Rho production code reaches provider credentials, catalogs, routing, and transport only through `github.com/GrayCodeAI/eyrie/engine`. This is enforced by shell and AST/package-graph guards. -### 4. Hawk schemas stay Hawk-owned +### 4. Rho schemas stay Rho-owned -Hawk conversation persistence and CLI/JSON output are explicit projections, +Rho conversation persistence and CLI/JSON output are explicit projections, not aliases or direct serialization of Eyrie DTOs. ### 5. Graph integrations are explicit surfaces -Hawk currently imports selected engine-owned graph/projection packages for +Rho currently imports selected engine-owned graph/projection packages for memory, token, review, and verification capture. These are integration exceptions, not peer-engine dependencies. If replaceability requires stronger isolation, move those calls behind engine-owned facades; do not expose more @@ -79,20 +79,20 @@ storage or implementation types. ### 6. Cloud is runtime-only -`graycode-platform` is outside the Hawk Go module graph. Hawk may call the +`graycode-platform` is outside the Rho Go module graph. Rho may call the deployed `graycode-cloud` Worker over authenticated HTTP for optional usage or explicit graph synchronization. Platform failures must not block local CLI execution. ### 7. Engine configuration is instance-scoped -Hawk supplies effective gateway settings while constructing an Eyrie Engine. +Rho supplies effective gateway settings while constructing an Eyrie Engine. Product code must not mutate Eyrie process-global gateway state. ## Enforcement -Hawk CI runs manifest validation, no-local-replace checks, Eagle parity, +Rho CI runs manifest validation, no-local-replace checks, Eagle parity, support-repository coupling, Eyrie facade checks, internal-layer checks, and the AST package-boundary audit. Support repositories run their own boundary -guards. SDK contract tests compare their OpenAPI snapshots with Hawk's public +guards. SDK contract tests compare their OpenAPI snapshots with Rho's public daemon contract. diff --git a/docs/architecture/hawk-harness.md b/docs/architecture/rho-harness.md similarity index 82% rename from docs/architecture/hawk-harness.md rename to docs/architecture/rho-harness.md index 9f0938459..59ef92fed 100644 --- a/docs/architecture/hawk-harness.md +++ b/docs/architecture/rho-harness.md @@ -1,11 +1,11 @@ -# Hawk Harness +# Rho Harness -Hawk treats the agent runtime as a product boundary around provider output. +Rho treats the agent runtime as a product boundary around provider output. ## Tool-Call Path 1. Eyrie normalizes provider protocol responses. -2. Hawk validates and resolves tool metadata. +2. Rho validates and resolves tool metadata. 3. Permission and sandbox policy runs before execution. 4. Tool execution observes timeouts, cancellation, and path boundaries. 5. Results are redacted, persisted, and returned to the model. @@ -23,7 +23,7 @@ interchangeable. The harness is testable without live providers through scripted providers and recorded interactions. Provider protocol and behavioral conformance belongs in -Eyrie's verification package; Hawk owns host-level UX, permissions, persistence, +Eyrie's verification package; Rho owns host-level UX, permissions, persistence, and review contracts. ## Security Rule diff --git a/docs/architecture/hawk-product-architecture.md b/docs/architecture/rho-product-architecture.md similarity index 65% rename from docs/architecture/hawk-product-architecture.md rename to docs/architecture/rho-product-architecture.md index 5aa46e0d7..37be29a8f 100644 --- a/docs/architecture/hawk-product-architecture.md +++ b/docs/architecture/rho-product-architecture.md @@ -1,23 +1,23 @@ -# Hawk Product Architecture +# Rho Product Architecture ## Product statement -Hawk is the model-agnostic main AI coding-agent CLI from GrayCodeAI. +Rho is the model-agnostic main AI coding-agent CLI from GrayCodeAI. `graycode-eco` is only the local parent folder used to develop the independent repositories. It is not a monorepo or a runtime product. -Hawk is the only primary product surface in the graycode-eco ecosystem. The support repos exist to power Hawk, not to compete with it as standalone products. +Rho is the only primary product surface in the graycode-eco ecosystem. The support repos exist to power Rho, not to compete with it as standalone products. -For model execution specifically: **Hawk is the face and composition layer; -Eyrie is the engine.** Hawk owns the conversation and product experience while +For model execution specifically: **Rho is the face and composition layer; +Eyrie is the engine.** Rho owns the conversation and product experience while the `eyrie/engine` facade owns the complete provider path from credential and catalog state through model selection and normalized generation/streaming. ## Goals -- Keep Hawk model agnostic. -- Keep Hawk CLI-first and local-first. +- Keep Rho model agnostic. +- Keep Rho CLI-first and local-first. - Keep provider integration pluggable. - Keep the provider engine isolated behind a stable facade. - Make review and verification first-class. @@ -25,10 +25,10 @@ catalog state through model selection and normalized generation/streaming. ## Target repo set -- `hawk` +- `rho` - `eyrie` - `owl` -- `graycode-platform` (outside the Hawk runtime module graph) +- `graycode-platform` (outside the Rho runtime module graph) Directory names are authoritative for dependencies: `eyrie` is Eyrie. The product label remains useful in CLI and user-facing documentation. @@ -39,7 +39,7 @@ product label remains useful in CLI and user-facing documentation. Users / SDKs / Skills | v - HAWK + RHO | +----------------------+ | | @@ -58,12 +58,12 @@ Users / SDKs / Skills Current implementation in the workspace: ```text -hawk +rho -> eyrie/engine -> internal/token (embedded) -> internal/contracts -x-> Hawk internals from any external module +x-> Rho internals from any external module ``` Proposed steady-state architecture: @@ -72,7 +72,7 @@ Proposed steady-state architecture: SDKs / Skills / future integrations | v - Hawk + Rho | +------------+------------+ | | @@ -87,16 +87,16 @@ SDKs / Skills / future integrations This means: -- Hawk is the product and orchestration boundary +- Rho is the product and orchestration boundary - the provider engine stays behind its stable facade -- shared vocabulary lives in Hawk's vendored contracts -- SDKs and community skills consume Hawk, not the engine directly +- shared vocabulary lives in Rho's vendored contracts +- SDKs and community skills consume Rho, not the engine directly - `graycode-platform` provides the optional hosted plane through HTTP and a - private Service Binding; it is not imported by Hawk + private Service Binding; it is not imported by Rho -## Hawk responsibilities +## Rho responsibilities -Hawk owns: +Rho owns: - CLI entrypoints - session lifecycle @@ -108,7 +108,7 @@ Hawk owns: - engine coordination - public integration surfaces -Hawk does not own: +Rho does not own: - provider-specific implementation details - engine-specific business logic @@ -135,20 +135,20 @@ Hawk does not own: ## Primary runtime flow -1. User invokes `hawk`. -2. Hawk loads product settings, policy, and workspace state, then creates an +1. User invokes `rho`. +2. Rho loads product settings, policy, and workspace state, then creates an Eyrie Engine with effective per-instance custom gateway settings. -3. Hawk creates or resumes a session. -4. Hawk assembles context through the embedded token engine. -5. Hawk recalls relevant local memories. -6. Hawk routes provider execution through `eyrie`. -7. Hawk invokes tools and records graph observations. -8. Hawk persists results and returns output to the user. - -At step 6, Hawk passes intent and Hawk-owned conversation DTOs through its +3. Rho creates or resumes a session. +4. Rho assembles context through the embedded token engine. +5. Rho recalls relevant local memories. +6. Rho routes provider execution through `eyrie`. +7. Rho invokes tools and records graph observations. +8. Rho persists results and returns output to the user. + +At step 6, Rho passes intent and Rho-owned conversation DTOs through its adapter. Eyrie loads provider/catalog/credential state, resolves the gateway, -and returns normalized events. No production Hawk package imports a lower -Eyrie package, and no Eyrie engine DTO is used as Hawk's persistent or CLI +and returns normalized events. No production Rho package imports a lower +Eyrie package, and no Eyrie engine DTO is used as Rho's persistent or CLI schema. ## Implementation phases @@ -160,44 +160,44 @@ schema. ### Phase 2 - add shared contracts -- move shared types out of Hawk internals +- move shared types out of Rho internals Status: - completed - shared contracts now exist for `types`, `contracts/review`, `contracts/verify`, `tools`, `events`, and `policy` ### Phase 3 -- remove engine imports of Hawk internals +- remove engine imports of Rho internals - remove engine-to-engine coupling Status: - completed for current workspace boundaries -- local/CI guards now block support-repo imports of `hawk/internal/*` +- local/CI guards now block support-repo imports of `rho/internal/*` ### Phase 4 -- harden orchestration boundaries in Hawk +- harden orchestration boundaries in Rho - formalize provider, review, and verify integration points Status: - completed for the local runtime boundary -- Hawk owns runtime DTOs and review/verify product-boundary contracts -- Hawk's `ChatClient` anti-corruption port translates only to `eyrie/engine` +- Rho owns runtime DTOs and review/verify product-boundary contracts +- Rho's `ChatClient` anti-corruption port translates only to `eyrie/engine` - all lower-level Eyrie production imports are forbidden by shell guards and meta-audit tests ### Phase 5 -- align SDKs and skills to Hawk public interfaces only +- align SDKs and skills to Rho public interfaces only Status: -- policy is now explicit and guarded in Hawk docs +- policy is now explicit and guarded in Rho docs - broader non-Go consumer enforcement remains future work ## Done criteria The architecture is in good shape when: -- `hawk` is the only product surface +- `rho` is the only product surface - the provider engine is consumed only through its stable facade -- shared types no longer live in Hawk internals as a cross-repo API +- shared types no longer live in Rho internals as a cross-repo API - provider abstraction is stable - review and verification are part of the standard runtime flow - deprecated compatibility surfaces have a documented removal path and active guardrails diff --git a/docs/architecture/hawk-provider-abstraction.md b/docs/architecture/rho-provider-abstraction.md similarity index 73% rename from docs/architecture/hawk-provider-abstraction.md rename to docs/architecture/rho-provider-abstraction.md index 6ac4a47e0..d5daac1bf 100644 --- a/docs/architecture/hawk-provider-abstraction.md +++ b/docs/architecture/rho-provider-abstraction.md @@ -1,17 +1,17 @@ -# Hawk Provider Abstraction +# Rho Provider Abstraction ## Goal -Hawk must remain model agnostic. +Rho must remain model agnostic. -That means Hawk should support multiple providers without leaking provider-specific assumptions across the product. +That means Rho should support multiple providers without leaking provider-specific assumptions across the product. ## Design principle Provider-specific code lives behind Eyrie's stable `eyrie/engine` host facade. -Hawk is the face and composition root; Eyrie is the engine. +Rho is the face and composition root; Eyrie is the engine. -Hawk decides: +Rho decides: - what capability the task needs - the semantic intent (`fast`, `balanced`, `reasoning`, `economical`) @@ -40,9 +40,9 @@ Hawk decides: - error classification - timeout/cancellation support -## Hawk-facing abstraction +## Rho-facing abstraction -Hawk depends on its small `ChatClient` product port and adapts it only to +Rho depends on its small `ChatClient` product port and adapts it only to `eyrie/engine`, never to a vendor-specific or lower-level Eyrie client. Example concerns: @@ -56,16 +56,16 @@ Example concerns: ## Rules -- no direct vendor SDK imports in unrelated Hawk packages +- no direct vendor SDK imports in unrelated Rho packages - no provider-specific branches inside review/verify logic - no model-specific assumptions inside session persistence -- keep task-semantic policy inside Hawk orchestration +- keep task-semantic policy inside Rho orchestration - keep provider/deployment routing, health, retry, and fallback inside Eyrie -- Hawk production integrations use `github.com/GrayCodeAI/eyrie/engine` +- Rho production integrations use `github.com/GrayCodeAI/eyrie/engine` - direct imports of lower Eyrie packages are forbidden and CI-enforced - custom gateway settings enter as `engine.Options.CustomGateways` and are isolated per Engine instance -- Hawk-owned JSON, session, and conversation schemas are explicit projections; +- Rho-owned JSON, session, and conversation schemas are explicit projections; they never become aliases of engine DTOs - local preflight readiness and remote live verification are distinct states diff --git a/docs/architecture/hawk-repo-roles.md b/docs/architecture/rho-repo-roles.md similarity index 64% rename from docs/architecture/hawk-repo-roles.md rename to docs/architecture/rho-repo-roles.md index eb752354c..75b1b8776 100644 --- a/docs/architecture/hawk-repo-roles.md +++ b/docs/architecture/rho-repo-roles.md @@ -1,13 +1,13 @@ -# Hawk Repo Roles +# Rho Repo Roles > **Historical.** This document describes the pre-2026-09 multi-engine -> ecosystem. Hawk now depends only on `eyrie` and embeds its own token engine; +> ecosystem. Rho now depends only on `eyrie` and embeds its own token engine; > the harrier, kestrel, merlin, shrike, and swift integrations have been > removed. Kept for record. ## Product repo -### `hawk` +### `rho` Main CLI and product repository. It is the orchestration root; `graycode-eco` is only the local parent folder for the independent repositories. @@ -22,31 +22,31 @@ Owns: - engine coordination - user-facing model/configuration presentation and stable CLI schemas -Hawk is the only primary product surface. Users interact with Hawk, not with six +Rho is the only primary product surface. Users interact with Rho, not with six separate end-user products. ## Support engines ### `eyrie` -Hawk provider engine. Its public host boundary is `eyrie/engine`, which owns +Rho provider engine. Its public host boundary is `eyrie/engine`, which owns credentials, provider state, catalog discovery, model/deployment selection, -transport, resilience, and normalized generation/streaming. Hawk production +transport, resilience, and normalized generation/streaming. Rho production code has zero imports of Eyrie's lower-level packages. ### `harrier` (Harrier) -Hawk memory engine. +Rho memory engine. ### `shrike` (Shrike) -Hawk context and token engine. +Rho context and token engine. ### `swift` (Swift) -Hawk audit and replay engine. +Rho audit and replay engine. ### `kestrel` (Kestrel) -Hawk review engine. +Rho review engine. ### `merlin` (Merlin) -Hawk verification engine. +Rho verification engine. All six support engines are peers: @@ -57,22 +57,22 @@ All six support engines are peers: - `kestrel` (Kestrel) - `merlin` (Merlin) -They should stay isolated from each other and are coordinated by Hawk; +They should stay isolated from each other and are coordinated by Rho; they may depend on `eagle` where a shared vocabulary is required. ## Ecosystem repos ### `sparrow` -Go integration surface for Hawk public APIs/contracts. +Go integration surface for Rho public APIs/contracts. ### `robin` -Python integration surface for Hawk public APIs/contracts. +Python integration surface for Rho public APIs/contracts. ### `wren` -TypeScript integration surface for Hawk public APIs/contracts. +TypeScript integration surface for Rho public APIs/contracts. ### `starling` -Reusable Hawk skills, recipes, and extension packs. +Reusable Rho skills, recipes, and extension packs. ## Shared foundation @@ -87,23 +87,23 @@ transports, and handler helpers that MCP-serving engines (`kestrel`, `merlin`) would otherwise duplicate. Like `eagle`, it sits below the engines: it must not import -engines, hawk, or graycode-platform. +engines, rho, or graycode-platform. ## Tooling and platform ### `owl` Read-only architecture visualization tooling. It consumes the generated -projection of `hawk/ecosystem.yaml`; it is not a runtime dependency. +projection of `rho/ecosystem.yaml`; it is not a runtime dependency. ### `graycode-platform` -Separate web, browser-BFF, and Hawk Cloud repository. Its Worker deployment is -named `graycode-cloud`, but it connects to Hawk only through authenticated +Separate web, browser-BFF, and Rho Cloud repository. Its Worker deployment is +named `graycode-cloud`, but it connects to Rho only through authenticated runtime HTTP/Service Binding and is never a Go dependency. ## Role rules -- Users should feel they are using `hawk`, not six unrelated tools. +- Users should feel they are using `rho`, not six unrelated tools. - Engines are internal capabilities from a product perspective. - Engines can stay in separate repos for isolation, testing, and replacement. - Engines must not import each other. -- SDKs and skills extend Hawk, but should not bypass Hawk to reach engines directly. +- SDKs and skills extend Rho, but should not bypass Rho to reach engines directly. diff --git a/docs/architecture/hawk-review-verify-lifecycle.md b/docs/architecture/rho-review-verify-lifecycle.md similarity index 82% rename from docs/architecture/hawk-review-verify-lifecycle.md rename to docs/architecture/rho-review-verify-lifecycle.md index 61b0ab9db..e19d9f1f9 100644 --- a/docs/architecture/hawk-review-verify-lifecycle.md +++ b/docs/architecture/rho-review-verify-lifecycle.md @@ -1,13 +1,13 @@ -# Hawk Review and Verify Lifecycle +# Rho Review and Verify Lifecycle > **Historical.** This document describes the pre-2026-09 multi-engine -> ecosystem. Hawk now depends only on `eyrie` and embeds its own token engine; +> ecosystem. Rho now depends only on `eyrie` and embeds its own token engine; > the harrier, kestrel, merlin, shrike, and swift integrations have been > removed. Kept for record. ## Goal -Review and verification should be standard parts of Hawk's workflow, not optional bolt-ons. +Review and verification should be standard parts of Rho's workflow, not optional bolt-ons. ## Roles @@ -59,7 +59,7 @@ Run `merlin` for: ## Decision policy -Hawk should define when review and verification are: +Rho should define when review and verification are: - required - recommended diff --git a/docs/architecture/hawk-swift-event-model.md b/docs/architecture/rho-swift-event-model.md similarity index 91% rename from docs/architecture/hawk-swift-event-model.md rename to docs/architecture/rho-swift-event-model.md index 4a7bbc9e5..bd5863b3b 100644 --- a/docs/architecture/hawk-swift-event-model.md +++ b/docs/architecture/rho-swift-event-model.md @@ -1,7 +1,7 @@ -# Hawk Swift Event Model +# Rho Swift Event Model > **Historical.** This document describes the pre-2026-09 multi-engine -> ecosystem. Hawk now depends only on `eyrie` and embeds its own token engine; +> ecosystem. Rho now depends only on `eyrie` and embeds its own token engine; > the harrier, kestrel, merlin, shrike, and swift integrations have been > removed. Kept for record. @@ -76,7 +76,7 @@ In the local CLI world, swift primarily supports: - debugging - reproduction - replay -- audit of what Hawk changed and why +- audit of what Rho changed and why ## Long-term use diff --git a/docs/architecture/spec.md b/docs/architecture/spec.md index 959158ac0..4c0435123 100644 --- a/docs/architecture/spec.md +++ b/docs/architecture/spec.md @@ -1,8 +1,8 @@ -# Hawk Architecture Specification +# Rho Architecture Specification ## Problem Statement -Hawk is an AI-powered coding agent for the terminal. This specification defines the complete architecture: repository structure, agent loop, agile workflow, feedback loops, and edge case handling. It serves as the authoritative reference for how hawk works. +Rho is an AI-powered coding agent for the terminal. This specification defines the complete architecture: repository structure, agent loop, agile workflow, feedback loops, and edge case handling. It serves as the authoritative reference for how rho works. ## Scope @@ -20,15 +20,15 @@ Hawk is an AI-powered coding agent for the terminal. This specification defines ### REQ-1: Repository Structure -Hawk SHALL be organized as a Go repository and workspace entry point within a -multi-repository ecosystem. The Hawk repository has the following top-level +Rho SHALL be organized as a Go repository and workspace entry point within a +multi-repository ecosystem. The Rho repository has the following top-level layout: | Directory | Purpose | |-----------|---------| | `cmd/` | CLI entry point (Cobra) and TUI (Bubble Tea) | | `internal/` | Private Go packages (not importable by external repos) | -| Parent `go.work` | Resolves the nine local Go siblings: hawk, eagle, falcon, eyrie, harrier (Harrier), shrike (Shrike), swift (Swift), kestrel (Kestrel), and merlin (Merlin) | +| Parent `go.work` | Resolves the nine local Go siblings: rho, eagle, falcon, eyrie, harrier (Harrier), shrike (Shrike), swift (Swift), kestrel (Kestrel), and merlin (Merlin) | | `spec/` | OpenSpec schema consumed by `internal/spec` | | `docs/` | Architecture docs, design docs, plans | | `rules/` | User-defined rules | @@ -74,7 +74,7 @@ The `internal/engine/` package SHALL contain the following sub-systems: |------------|---------| | `stream.go` | The agent loop (agentLoop) - main orchestration | | `session.go` | Session struct and sub-services | -| `chat_service.go` | Hawk ChatClient port, engine adapter coordination, compact | +| `chat_service.go` | Rho ChatClient port, engine adapter coordination, compact | | `safety/` | Permission engine, trust tiers, spec gate | | `compact/` | Context compaction (collapse, micro, smart, truncate) | | `ctxmgr/` | Context providers, packing, visualization | @@ -94,17 +94,17 @@ The `internal/engine/` package SHALL contain the following sub-systems: Provider boundary invariant: ```text -Hawk CLI/TUI + conversation + tools +Rho CLI/TUI + conversation + tools | - Hawk-owned ports/DTOs + Rho-owned ports/DTOs | v eyrie/engine credentials -> catalog -> routing -> generate/stream ``` -No production Hawk package may import a lower Eyrie package. Custom gateways -are supplied per Engine instance, and Eyrie DTOs are not Hawk persistence or +No production Rho package may import a lower Eyrie package. Custom gateways +are supplied per Engine instance, and Eyrie DTOs are not Rho persistence or CLI output schemas. ### REQ-4: Agent Loop Lifecycle diff --git a/docs/architecture/tasks.md b/docs/architecture/tasks.md index 40d608b1e..0a5193e6e 100644 --- a/docs/architecture/tasks.md +++ b/docs/architecture/tasks.md @@ -1,7 +1,7 @@ -# Hawk Architecture - Implementation Tasks +# Rho Architecture - Implementation Tasks > **Historical.** This was the initial architecture implementation checklist. -> It is superseded by `hawk-architecture-v1-definition-of-done.md`, which +> It is superseded by `rho-architecture-v1-definition-of-done.md`, which > reflects the current shipping bar. Kept for record; do not use as a > current TODO list. diff --git a/docs/compatibility.md b/docs/compatibility.md index 8d9ec1cbe..33300ba96 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -12,11 +12,11 @@ Platform/provider capability metadata is separate: [`platform-capabilities.json` ```jsonc { - "components": ["hawk", "eyrie", ...], // canonical eco roster - "dependencies": { "hawk": ["eyrie", ...] }, // who depends on who + "components": ["rho", "eyrie", ...], // canonical eco roster + "dependencies": { "rho": ["eyrie", ...] }, // who depends on who "matrices": [ - { "name": "stable", "components": { "hawk": "0.1.0", "eyrie": "0.1.0", ... } }, - { "name": "next", "components": { "hawk": "main", "eyrie": "main", ... } } + { "name": "stable", "components": { "rho": "0.1.0", "eyrie": "0.1.0", ... } }, + { "name": "next", "components": { "rho": "main", "eyrie": "main", ... } } ] } ``` @@ -48,7 +48,7 @@ existing consumers. `.shared-templates/workflows/compatibility-test.yml.tmpl` is a reusable workflow that: -1. Reads `testdata/compatibility-matrix.json` from the hawk repo. +1. Reads `testdata/compatibility-matrix.json` from the rho repo. 2. Checks out each component at the version listed in the named matrix. 3. Builds + tests the cross-repo integration scenarios. @@ -59,7 +59,7 @@ It runs on: ## Why bother -- **Bug reports become triageable.** "I ran hawk 0.4 with eyrie 0.2" — you +- **Bug reports become triageable.** "I ran rho 0.4 with eyrie 0.2" — you immediately know whether that combination was ever tested. - **Consumers can pin reliably.** Downstream projects that vendor multiple eco packages can pin to a known-good stable matrix instead of guessing. @@ -70,17 +70,17 @@ It runs on: ## Pin freshness (advisory) -Separate from the matrix above: `hawk`'s own `go.mod` directly pins shared +Separate from the matrix above: `rho`'s own `go.mod` directly pins shared leaf dependencies, and sibling-repository consumers can pin the *same* dependencies independently in their own `go.mod`. Go's minimal version -selection means whatever `hawk` pins wins in `hawk`'s own build — but if a +selection means whatever `rho` pins wins in `rho`'s own build — but if a consumer's own pin is older, that consumer's CI has never actually tested the version that ships. This is exactly the kind of drift that let a real bug through in July 2026 (a stale goreleaser pin sat unnoticed until the first tag push exercised it). `make compat-drift` (wired into this workflow as an advisory, non-blocking -step) reports any such mismatch by comparing `hawk/go.mod` against each +step) reports any such mismatch by comparing `rho/go.mod` against each `..//go.mod`: ```bash @@ -89,7 +89,7 @@ make compat-drift This **never fails the build** — it's a signal for humans to notice and re-pin the consumer, not a gate. It checks sibling repositories when they are -present; a Hawk-only checkout simply reports no local consumers to compare. +present; a Rho-only checkout simply reports no local consumers to compare. ## Validating the file diff --git a/docs/design/ECOSYSTEM-MARKETPLACE.md b/docs/design/ECOSYSTEM-MARKETPLACE.md index 82721c324..61087cb62 100644 --- a/docs/design/ECOSYSTEM-MARKETPLACE.md +++ b/docs/design/ECOSYSTEM-MARKETPLACE.md @@ -3,7 +3,7 @@ **Status:** Draft **Author:** Ecosystem / DX working group **Last updated:** 2026-06-06 -**Scope:** Multi-month effort spanning all 5 repos (hawk, eyrie, harrier, shrike, swift) + a new gallery web property + a unified docs site. +**Scope:** Multi-month effort spanning all 5 repos (rho, eyrie, harrier, shrike, swift) + a new gallery web property + a unified docs site. > This is a design a team executes against, not a code-session deliverable. It grounds every claim in the actual graycode-eco codebase (cited as `path:line`) and reuses what already exists rather than greenfielding. @@ -13,7 +13,7 @@ Two adjacent gaps from `TOP20_COMPARISON.md` are addressed together because they share infrastructure (a registry index, a content format, a web property): -1. **Centralized extension gallery / plugin marketplace** — `TOP20_COMPARISON.md:65` (hawk P1) and `TOP20_COMPARISON.md:227` (cross-cutting P1). +1. **Centralized extension gallery / plugin marketplace** — `TOP20_COMPARISON.md:65` (rho P1) and `TOP20_COMPARISON.md:227` (cross-cutting P1). 2. **Documentation site (Docusaurus/Mintlify) for the graycode-eco ecosystem** — `TOP20_COMPARISON.md:232` (cross-cutting P1). ### Who ships this in the Top 20 @@ -29,14 +29,14 @@ Two adjacent gaps from `TOP20_COMPARISON.md` are addressed together because they graycode-eco is **not** starting from zero. A large fraction of the marketplace already exists as working Go and a populated registry: -- **A populated registry already ships.** `starling/registry.json` is a 4.3 MB JSON array of skill entries with `name`, `description`, `category`, `tags`, `path`, `file_count`, `has_scripts` (see file head). It is served raw from GitHub and consumed by hawk at `hawk/internal/plugin/registry.go:18` (`defaultIndexURL = https://raw.githubusercontent.com/GrayCodeAI/starling/main/registry.json`). -- **A registry client already works.** `hawk/internal/plugin/registry.go` defines `SkillEntry`/`SkillIndex` (`:21`, `:36`), `FetchIndex` with a 1-hour cache (`:60`), and `Install/Remove/InstalledSkillInfo` (`:196`, `:289`, `:309`). -- **CLI surface already exists.** `hawk skills {list,search,install,remove,info,trending,audit}` is wired in `hawk/cmd/skills_cmd.go:16-211`. +- **A populated registry already ships.** `starling/registry.json` is a 4.3 MB JSON array of skill entries with `name`, `description`, `category`, `tags`, `path`, `file_count`, `has_scripts` (see file head). It is served raw from GitHub and consumed by rho at `rho/internal/plugin/registry.go:18` (`defaultIndexURL = https://raw.githubusercontent.com/GrayCodeAI/starling/main/registry.json`). +- **A registry client already works.** `rho/internal/plugin/registry.go` defines `SkillEntry`/`SkillIndex` (`:21`, `:36`), `FetchIndex` with a 1-hour cache (`:60`), and `Install/Remove/InstalledSkillInfo` (`:196`, `:289`, `:309`). +- **CLI surface already exists.** `rho skills {list,search,install,remove,info,trending,audit}` is wired in `rho/cmd/skills_cmd.go:16-211`. - **The extension format is partially standardized.** `SKILL.md` files carry YAML frontmatter (`name`, `description`, `license`, `tags`, `version`) — see `starling/api/openapi.yaml` `x-skill-format` and `categories/testing/ab-test-setup/SKILL.md` frontmatter. -- **A V2 manifest already supports MCP-adjacent bundles.** `hawk/internal/plugin/manifest_v2.go:11` (`ManifestV2`) carries `Tools`, `Permissions`, `Hooks` (`:32` `ManifestHook` with `Event`/`Command`/`Priority`), `Config`, `Dependencies`, `Mode` (subprocess/daemon). -- **Trust scaffolding already exists.** `hawk/internal/plugin/malware_check.go:19` blocks `eval()`, pipe-to-shell, reverse shells; `hawk/internal/plugin/audit.go` flags hidden-Unicode / homoglyph attacks (`hawk skills audit`). +- **A V2 manifest already supports MCP-adjacent bundles.** `rho/internal/plugin/manifest_v2.go:11` (`ManifestV2`) carries `Tools`, `Permissions`, `Hooks` (`:32` `ManifestHook` with `Event`/`Command`/`Priority`), `Config`, `Dependencies`, `Mode` (subprocess/daemon). +- **Trust scaffolding already exists.** `rho/internal/plugin/malware_check.go:19` blocks `eval()`, pipe-to-shell, reverse shells; `rho/internal/plugin/audit.go` flags hidden-Unicode / homoglyph attacks (`rho skills audit`). -The gap is therefore **consolidation and elevation**, not invention: (a) a *standardized, multi-asset* extension format (today's registry is skills-only); (b) a *browsable web gallery*; (c) a *trust/signing* story beyond static malware regexes; and (d) a *unified docs site* that today is five disconnected `README.md`/`ARCHITECTURE.md` trees (`hawk/docs/`, `eyrie/docs/`, `harrier/`, `shrike/docs/`, `swift/docs/`). +The gap is therefore **consolidation and elevation**, not invention: (a) a *standardized, multi-asset* extension format (today's registry is skills-only); (b) a *browsable web gallery*; (c) a *trust/signing* story beyond static malware regexes; and (d) a *unified docs site* that today is five disconnected `README.md`/`ARCHITECTURE.md` trees (`rho/docs/`, `eyrie/docs/`, `harrier/`, `shrike/docs/`, `swift/docs/`). --- @@ -44,9 +44,9 @@ The gap is therefore **consolidation and elevation**, not invention: (a) a *stan ### Goals -- **G1.** A single standardized **Hawk Extension** format: a directory with `extension.yaml` (or extended `SKILL.md` frontmatter) that can bundle any subset of: MCP servers, slash commands, prompts, hooks, themes, sub-agents (personas), and skills. -- **G2.** A **browsable gallery** (hawkskills.dev-style) generated from the registry index, with search, categories, per-extension detail pages, and copy-paste install commands. -- **G3.** A versioned, machine-readable **registry index format** (v2) that is a superset of today's `registry.json` and remains backward compatible with `hawk/internal/plugin/registry.go`. +- **G1.** A single standardized **Rho Extension** format: a directory with `extension.yaml` (or extended `SKILL.md` frontmatter) that can bundle any subset of: MCP servers, slash commands, prompts, hooks, themes, sub-agents (personas), and skills. +- **G2.** A **browsable gallery** (rhoskills.dev-style) generated from the registry index, with search, categories, per-extension detail pages, and copy-paste install commands. +- **G3.** A versioned, machine-readable **registry index format** (v2) that is a superset of today's `registry.json` and remains backward compatible with `rho/internal/plugin/registry.go`. - **G4.** An **install / update / remove flow** with **signing & trust verification** (provenance + signature check, not just regex malware scan). - **G5.** A **unified documentation site** spanning all 5 repos: getting-started, per-repo API reference, architecture diagrams, cookbook recipes, and the competitor comparison tables — with the gallery either embedded in or cross-linked from it. - **G6.** Privacy-first throughout: no telemetry-by-default, no account required to browse/install, install is auditable and offline-capable. @@ -56,7 +56,7 @@ The gap is therefore **consolidation and elevation**, not invention: (a) a *stan - **NG1.** A hosted, multi-tenant SaaS backend with accounts, billing, or org RBAC (those are separate P0/P2 gaps at `TOP20_COMPARISON.md:33`, `:72`). The gallery is a **static, build-time-generated site backed by a git repo**, not a dynamic app server. - **NG2.** Paid/commercial extensions, license-key enforcement, or DRM. - **NG3.** Replacing per-repo deep-dive docs; the unified site **aggregates and cross-links**, it does not fork content. -- **NG4.** A new package manager. We reuse `git clone`-based install (`registry.go:215`) and the `~/.hawk/skills` / `.hawk/skills` layout (`registry.go:201-203`). +- **NG4.** A new package manager. We reuse `git clone`-based install (`registry.go:215`) and the `~/.rho/skills` / `.rho/skills` layout (`registry.go:201-203`). - **NG5.** Runtime arbitrary-code execution in the browser gallery (it renders metadata only; nothing executes client-side). --- @@ -78,10 +78,10 @@ The gap is therefore **consolidation and elevation**, not invention: (a) a *stan │ raw.githubusercontent (index + sigs) │ build inputs ▼ ▼ ┌───────────────────────────────┐ ┌──────────────────────────────┐ -│ hawk CLI (Go) │ │ Gallery + Docs site (static) │ +│ rho CLI (Go) │ │ Gallery + Docs site (static) │ │ internal/plugin/registry.go │ │ Docusaurus or Mintlify │ │ + new: trust.go, extman.go │ │ - /extensions (gallery) │ -│ cmd/skills_cmd.go (+ ext cmd)│ │ - /docs/{hawk,eyrie,...} │ +│ cmd/skills_cmd.go (+ ext cmd)│ │ - /docs/{rho,eyrie,...} │ │ internal/plugin/manifest_v2 │ │ - generated from registry.v2 │ └───────────────────────────────┘ └──────────────────────────────┘ ``` @@ -91,22 +91,22 @@ The gap is therefore **consolidation and elevation**, not invention: (a) a *stan Two interoperable representations, chosen so we **do not break** today's skills-only registry: **(a) Minimal — extended `SKILL.md` frontmatter** (for single-asset skills, unchanged path): -Already parsed at `hawk/internal/plugin/skill_loader.go:51` (`parseSkillFrontMatter`) and discovered at `hawk/internal/tool/skill.go:62` (`discoverSkills`). We keep this working verbatim. +Already parsed at `rho/internal/plugin/skill_loader.go:51` (`parseSkillFrontMatter`) and discovered at `rho/internal/tool/skill.go:62` (`discoverSkills`). We keep this working verbatim. **(b) Full — `extension.yaml`** (new, for multi-asset bundles): ```yaml -apiVersion: hawk.extension/v1 +apiVersion: rho.extension/v1 kind: Extension name: terraform-pro version: 1.4.0 description: Terraform authoring — skills, an MCP server, slash commands, and a theme. license: MIT author: { name: GrayCode AI, url: https://github.com/GrayCodeAI } -homepage: https://hawkskills.dev/extensions/terraform-pro +homepage: https://rhoskills.dev/extensions/terraform-pro keywords: [terraform, iac, devops] compat: - minHawkVersion: "1.2.0" # maps to ManifestV2.MinHawkVersion (manifest_v2.go:20) + minRhoVersion: "1.2.0" # maps to ManifestV2.MinRhoVersion (manifest_v2.go:20) provides: skills: [ ./SKILL.md ] commands: [ ./commands/plan.md, ./commands/apply.md ] # slash commands @@ -121,11 +121,11 @@ provides: subagents: [ ./agents/iac-reviewer.md ] # personas (internal/multiagent) permissions: [ run_shell, network ] # ManifestV2.Permissions signing: - publisher: "hawk" + publisher: "rho" signature: "signatures/terraform-pro.sig" ``` -The `provides` block is intentionally a **strict superset of the existing assets hawk already loads**: skills (`tool/skill.go`), hooks (`manifest_v2.go:32`), MCP servers (`internal/mcp/mcp.go`), and personas/sub-agents (`internal/multiagent/agents/`, cited at `TOP20_COMPARISON.md:62`). No new runtime concept is invented — `extension.yaml` is a *bundling manifest* over existing loaders. +The `provides` block is intentionally a **strict superset of the existing assets rho already loads**: skills (`tool/skill.go`), hooks (`manifest_v2.go:32`), MCP servers (`internal/mcp/mcp.go`), and personas/sub-agents (`internal/multiagent/agents/`, cited at `TOP20_COMPARISON.md:62`). No new runtime concept is invented — `extension.yaml` is a *bundling manifest* over existing loaders. ### 3.3 Registry index format v2 (G3) @@ -149,7 +149,7 @@ Today's `SkillEntry` (`registry.go:21`) already has most fields. v2 adds a `kind "repo": "GrayCodeAI/starling", "provides": { "skills":1, "commands":2, "hooks":1, "mcpServers":1, "themes":1, "subagents":1 }, "signed": true, - "publisher": "hawk", + "publisher": "rho", "installs": 0, "updated_at": "..." } @@ -157,16 +157,16 @@ Today's `SkillEntry` (`registry.go:21`) already has most fields. v2 adds a `kind } ``` -Backward compatibility: `registry.go`'s `SkillIndex.Skills` (`:36`) keeps reading `registry.json`. A small shim maps v2 `extensions[]` → `SkillEntry` so older hawk binaries keep working; new binaries prefer `registry.v2.json` and fall back. The existing `tools/update_registry.py` / `tools/registry_schema.py` generators (in `starling/tools/`) are extended to emit both. +Backward compatibility: `registry.go`'s `SkillIndex.Skills` (`:36`) keeps reading `registry.json`. A small shim maps v2 `extensions[]` → `SkillEntry` so older rho binaries keep working; new binaries prefer `registry.v2.json` and fall back. The existing `tools/update_registry.py` / `tools/registry_schema.py` generators (in `starling/tools/`) are extended to emit both. ### 3.4 API surface The marketplace has **no HTTP API server** (per NG1). Its "API" is: 1. **The static index files** over `raw.githubusercontent.com` (already the contract — `registry.go:18`). -2. **The hawk CLI**, extended: - - Existing: `hawk skills {list,search,install,remove,info,trending,audit}` (`cmd/skills_cmd.go`). - - New: `hawk ext {search,install,update,verify,info,publish}` for multi-asset extensions, plus `hawk ext install --verify` (signature-checked by default). +2. **The rho CLI**, extended: + - Existing: `rho skills {list,search,install,remove,info,trending,audit}` (`cmd/skills_cmd.go`). + - New: `rho ext {search,install,update,verify,info,publish}` for multi-asset extensions, plus `rho ext install --verify` (signature-checked by default). 3. **The gallery's generated JSON** (`/extensions/index.json`) for the site's client-side search (build-time, no server). ### 3.5 Key flows (sequences) @@ -174,7 +174,7 @@ The marketplace has **no HTTP API server** (per NG1). Its "API" is: **Flow A — Install with trust verification** ``` -user: hawk ext install GrayCodeAI/starling terraform-pro +user: rho ext install GrayCodeAI/starling terraform-pro → RegistryClient.FetchIndex() registry.go:60 (cache <1h) → resolve entry, get repo+path+publisher+signature → git clone --depth 1 (existing) registry.go:215 @@ -184,7 +184,7 @@ user: hawk ext install GrayCodeAI/starling terraform-pro → audit.go (hidden-unicode) + malware_check.go (regex) on every file blocked pattern → abort malware_check.go:19 → parse extension.yaml → ManifestV2 manifest_v2.go:42 - → copy assets to ~/.hawk/skills/ (and commands/hooks/mcp dirs) + → copy assets to ~/.rho/skills/ (and commands/hooks/mcp dirs) scope user|project registry.go:201-203 → inject source-tracking metadata registry.go (injectSourceMetadata) → print: installed N assets (skills, 1 mcp, 1 theme, ...) @@ -192,18 +192,18 @@ user: hawk ext install GrayCodeAI/starling terraform-pro **Flow B — Gallery browse & install (no account, no server)** ``` -browser → hawkskills.dev/extensions (static Docusaurus/Mintlify page) +browser → rhoskills.dev/extensions (static Docusaurus/Mintlify page) → client loads /extensions/index.json (generated from registry.v2.json) → user filters by category/tag/capability badge (skills, MCP, hooks, theme) → detail page renders extension.yaml metadata + README + capability list - → "Install" shows: hawk ext install (copy button) + → "Install" shows: rho ext install (copy button) → nothing executes in browser; install happens locally via CLI ``` **Flow C — Docs build (unified site, all 5 repos)** ``` CI (docs repo) → - pull README.md / ARCHITECTURE.md / docs/** from each of hawk, eyrie, + pull README.md / ARCHITECTURE.md / docs/** from each of rho, eyrie, harrier (Harrier), shrike (Shrike), swift (Swift) (independent checkouts or sparse checkout) → transform: inject sidebars, rewrite intra-repo links → generate /extensions/* from registry.v2.json @@ -219,36 +219,36 @@ Concrete reuse map — what is *already there* and what each piece becomes: | New capability | Reuse today | File:line | |---|---|---| -| Registry fetch + 1h cache | `RegistryClient.FetchIndex` | `hawk/internal/plugin/registry.go:60` | +| Registry fetch + 1h cache | `RegistryClient.FetchIndex` | `rho/internal/plugin/registry.go:60` | | Index URL contract | `defaultIndexURL` | `registry.go:18` | | Entry/index schema | `SkillEntry`, `SkillIndex` | `registry.go:21`, `:36` | | git-clone install + scopes | `RegistryClient.Install` | `registry.go:196-215`, `:201-203` | | Remove / info | `Remove`, `InstalledSkillInfo` | `registry.go:289`, `:309` | -| CLI command tree | `skillsCmd` + subcommands | `hawk/cmd/skills_cmd.go:16-211` | -| Skill discovery roots | `discoverSkills`, `skillRoots` | `hawk/internal/tool/skill.go:62`, `:91` | -| Frontmatter parsing | `parseSkillFrontMatter` | `hawk/internal/plugin/skill_loader.go:51` | -| Multi-asset manifest | `ManifestV2` (+`Hooks`,`Config`,`Dependencies`,`Mode`) | `hawk/internal/plugin/manifest_v2.go:11`, `:32` | +| CLI command tree | `skillsCmd` + subcommands | `rho/cmd/skills_cmd.go:16-211` | +| Skill discovery roots | `discoverSkills`, `skillRoots` | `rho/internal/tool/skill.go:62`, `:91` | +| Frontmatter parsing | `parseSkillFrontMatter` | `rho/internal/plugin/skill_loader.go:51` | +| Multi-asset manifest | `ManifestV2` (+`Hooks`,`Config`,`Dependencies`,`Mode`) | `rho/internal/plugin/manifest_v2.go:11`, `:32` | | Hook bundling | `ManifestHook{Event,Command,Priority}` | `manifest_v2.go:32` | -| MCP server bundling | MCP client/loader | `hawk/internal/mcp/mcp.go`, `hawk/internal/tool/mcp_tool.go` | -| Sub-agent bundling | Persona system (YAML frontmatter MD) | `hawk/internal/multiagent/agents/` (cf. `TOP20_COMPARISON.md:62`) | -| Rule/skill discovery precedence | `DefaultRuleSources`, `RuleDiscoverer` | `hawk/internal/plugin/` rule discovery | -| Trust — static scan | `malware_check.go` (blocked/suspicious regex) | `hawk/internal/plugin/malware_check.go:19` | -| Trust — unicode/homoglyph | `audit.go` (`AuditFinding`, severities) | `hawk/internal/plugin/audit.go:12-22` | +| MCP server bundling | MCP client/loader | `rho/internal/mcp/mcp.go`, `rho/internal/tool/mcp_tool.go` | +| Sub-agent bundling | Persona system (YAML frontmatter MD) | `rho/internal/multiagent/agents/` (cf. `TOP20_COMPARISON.md:62`) | +| Rule/skill discovery precedence | `DefaultRuleSources`, `RuleDiscoverer` | `rho/internal/plugin/` rule discovery | +| Trust — static scan | `malware_check.go` (blocked/suspicious regex) | `rho/internal/plugin/malware_check.go:19` | +| Trust — unicode/homoglyph | `audit.go` (`AuditFinding`, severities) | `rho/internal/plugin/audit.go:12-22` | | Registry generators/validators | Python tooling | `starling/tools/{update_registry,registry_schema,validate_skill,package_skill,sync_marketplace}.py` | | Format reference | OpenAPI skill-format spec | `starling/api/openapi.yaml` (`x-skill-format`) | | IDE/agent plugin manifests | `.claude-plugin/{plugin,marketplace}.json` | `starling/.claude-plugin/` | **Net-new Go (small, additive):** -- `hawk/internal/plugin/trust.go` — signature verification (Flow A) and a `trustdb` of publisher public keys. -- `hawk/internal/plugin/extension.go` — parse `extension.yaml`, expand `provides` into the existing loaders. -- `hawk/cmd/ext_cmd.go` — `hawk ext` command tree mirroring `skills_cmd.go`. +- `rho/internal/plugin/trust.go` — signature verification (Flow A) and a `trustdb` of publisher public keys. +- `rho/internal/plugin/extension.go` — parse `extension.yaml`, expand `provides` into the existing loaders. +- `rho/cmd/ext_cmd.go` — `rho ext` command tree mirroring `skills_cmd.go`. - Extend `manifest_v2.go` with a `Signing` struct and a `Provides` map (or a thin adapter from `extension.yaml`). **Net-new outside Go:** - `registry.v2.json` generator (extend `tools/update_registry.py`). -- The gallery + docs site (new repo, e.g. `hawk-docs`). +- The gallery + docs site (new repo, e.g. `rho-docs`). -The discovery-precedence engine (`rules.go:20` `DefaultRuleSources`, walk-up + local/global/distance/priority sort at `rules.go:97-108`) is reused unchanged for *where* installed extension assets are found — installed extensions land in `.hawk/skills` / `~/.hawk/skills` (project then user), exactly the precedence `RuleDiscoverer` already honors. +The discovery-precedence engine (`rules.go:20` `DefaultRuleSources`, walk-up + local/global/distance/priority sort at `rules.go:97-108`) is reused unchanged for *where* installed extension assets are found — installed extensions land in `.rho/skills` / `~/.rho/skills` (project then user), exactly the precedence `RuleDiscoverer` already honors. --- @@ -257,20 +257,20 @@ The discovery-precedence engine (`rules.go:20` `DefaultRuleSources`, walk-up + l ### P0 — Foundation (format + index + trust core) - **M0.1** Specify and freeze the `extension.yaml` schema; publish as `x-extension-format` alongside the existing `x-skill-format` in `starling/api/openapi.yaml`. - **M0.2** `registry.v2.json` generator in `tools/update_registry.py`; emit both v1 and v2; v1 stays the default `defaultIndexURL` consumer for older binaries. -- **M0.3** `extension.go` parser + adapter into `ManifestV2`; `hawk ext install` reuses `registry.go:196` clone path; multi-asset copy (skills/commands/hooks/mcp/themes/subagents). -- **M0.4** Trust v1: detached **minisign/cosign** signatures (`signatures/.sig`), publisher pubkey bundled in hawk; `--allow-unsigned` escape hatch; wire `malware_check.go` + `audit.go` into the install path as hard gates. -- **Exit:** `hawk ext install` works for at least 5 multi-asset extensions, signature-verified, on a v2 index. +- **M0.3** `extension.go` parser + adapter into `ManifestV2`; `rho ext install` reuses `registry.go:196` clone path; multi-asset copy (skills/commands/hooks/mcp/themes/subagents). +- **M0.4** Trust v1: detached **minisign/cosign** signatures (`signatures/.sig`), publisher pubkey bundled in rho; `--allow-unsigned` escape hatch; wire `malware_check.go` + `audit.go` into the install path as hard gates. +- **Exit:** `rho ext install` works for at least 5 multi-asset extensions, signature-verified, on a v2 index. ### P1 — Gallery + Docs site (the visible surface) -- **M1.1** Stand up the static site (decision in §6). Pages: `/extensions` (gallery), per-extension detail, `/docs/{hawk,eyrie,harrier,shrike,swift}` (getting-started + API ref + architecture), `/comparison` (rendered from `TOP20_COMPARISON.md`). +- **M1.1** Stand up the static site (decision in §6). Pages: `/extensions` (gallery), per-extension detail, `/docs/{rho,eyrie,harrier,shrike,swift}` (getting-started + API ref + architecture), `/comparison` (rendered from `TOP20_COMPARISON.md`). - **M1.2** Build-time generation of `/extensions/index.json` from `registry.v2.json`; client-side fuzzy search, category + capability-badge filters. - **M1.3** Aggregate the 5 independent repositories' docs via checkout or sparse checkout in CI (Flow C); link-rewriting + unified sidebar. -- **M1.4** `hawk ext update` (diff installed version vs index, re-verify, atomic replace) and `hawk ext verify ` (re-check signature/audit of an installed extension). -- **Exit:** hawkskills.dev (or chosen domain) live; gallery browseable; one-line install copy works; docs cover all 5 repos with getting-started + API ref. +- **M1.4** `rho ext update` (diff installed version vs index, re-verify, atomic replace) and `rho ext verify ` (re-check signature/audit of an installed extension). +- **Exit:** rhoskills.dev (or chosen domain) live; gallery browseable; one-line install copy works; docs cover all 5 repos with getting-started + API ref. ### P2 — Ecosystem maturity & publisher self-service -- **M2.1** `hawk ext publish` — scaffolds `extension.yaml`, runs validators (`tools/validate_skill.py` extended), signs locally, opens a PR to `starling`. -- **M2.2** Publisher trust tiers: verified-publisher badge (key in hawk's bundled trust DB) vs community (signed but unverified) vs unsigned. +- **M2.1** `rho ext publish` — scaffolds `extension.yaml`, runs validators (`tools/validate_skill.py` extended), signs locally, opens a PR to `starling`. +- **M2.2** Publisher trust tiers: verified-publisher badge (key in rho's bundled trust DB) vs community (signed but unverified) vs unsigned. - **M2.3** Trending/installs analytics — privacy-preserving (aggregate counts from PR-based opt-in pings or GitHub stars only; **no per-user tracking**, see §7). - **M2.4** Cross-repo extension kinds: eyrie provider plug-ins, shrike compression profiles (`TOP20_COMPARISON.md:181` team profiles), swift exporters — registered through the same `extension.yaml` `provides` mechanism with new `kind`s. - **Exit:** External contributors can publish a verified, multi-asset extension end-to-end; gallery shows mixed-kind extensions across the ecosystem. @@ -297,13 +297,13 @@ The discovery-precedence engine (`rules.go:20` `DefaultRuleSources`, walk-up + l minisign is the lighter P0 choice (single keypair, no external infra). cosign/sigstore is the P2 upgrade if we want keyless, transparency-log-backed provenance (which also dovetails with the SBOM/Cosign work at `TOP20_COMPARISON.md:240`). -### New runtime dependencies for hawk +### New runtime dependencies for rho - A minisign/Ed25519 verify path: Go stdlib `crypto/ed25519` is sufficient — **no new dependency** for verification; only key management is new. - No new dependency for install: `git` is already required (`registry.go:215`). ### Licensing implications - Every extension's `extension.yaml` **must** carry a `license` field (already required for skills — `openapi.yaml` `required_frontmatter`). The gallery surfaces it; the validator rejects missing/unknown licenses. -- The community repo is **MIT** (`starling/LICENSE`). Contributed extensions retain their own license but must be OSI-approved; a CI check (extend `tools/validate_skill.py`) flags GPL/AGPL bundled binaries that would conflict with hawk's distribution model (mirrors `TOP20_COMPARISON.md:241`). +- The community repo is **MIT** (`starling/LICENSE`). Contributed extensions retain their own license but must be OSI-approved; a CI check (extend `tools/validate_skill.py`) flags GPL/AGPL bundled binaries that would conflict with rho's distribution model (mirrors `TOP20_COMPARISON.md:241`). - Docusaurus (MIT) and minisign (ISC) impose no copyleft obligations. --- @@ -316,24 +316,24 @@ These repos are privacy-first; the marketplace must not regress that. - **Supply-chain trust (the core new risk).** Installing an extension means importing executable assets (hooks `manifest_v2.go:32`, MCP server binaries, scripts). Mitigations, in order of enforcement at install time (Flow A): 1. **Signature verification** (`trust.go`): detached signature over the extension tree hash, verified against a bundled/known publisher key. Unsigned → blocked unless `--allow-unsigned`. 2. **Static malware scan** (`malware_check.go:19`): blocks `eval(`, pipe-to-shell, base64-to-shell, reverse shells, netcat `-e`. - 3. **Hidden-Unicode / homoglyph audit** (`audit.go`): catches prompt-injection-via-invisible-characters in `SKILL.md`/prompts (already `hawk skills audit`). + 3. **Hidden-Unicode / homoglyph audit** (`audit.go`): catches prompt-injection-via-invisible-characters in `SKILL.md`/prompts (already `rho skills audit`). 4. **Permission disclosure:** `extension.yaml` `permissions` (mapped to `ManifestV2.Permissions`, `manifest_v2.go:18`) are shown to the user before install; `run_shell`/`network` require explicit confirmation. - **MCP server execution is the highest-risk asset.** Bundled MCP servers run as subprocesses. P0 ships them as **opt-in** (the extension installs the *definition*; the user must explicitly enable the server), and prefers `stdio` transport with no inbound network surface. - **Prompt-injection through extension content.** Skills/prompts are injected into the system prompt via the same path as rules (`rules.go`). The audit (3) plus the existing HTML-comment-stripping concern (`TOP20_COMPARISON.md:74`) apply; the validator strips/flags suspicious frontmatter. - **Offline/air-gapped install.** Because install is `git clone` + local copy with local signature verification, an org can mirror `starling` internally and point `defaultIndexURL`/`--index` at it — no call to GitHub required. - **No third-party doc analytics.** Self-hosted Docusaurus (§6) avoids shipping user reading data to a SaaS. Any "installs" metric (P2) is aggregate-only and opt-in. -- **Signing key custody.** Verified-publisher keys ship in hawk's binary; rotation requires a hawk release. P2 cosign/sigstore would move trust to a transparency log, reducing reliance on bundled keys. +- **Signing key custody.** Verified-publisher keys ship in rho's binary; rotation requires a rho release. P2 cosign/sigstore would move trust to a transparency log, reducing reliance on bundled keys. --- ## 8. Open Questions 1. **Signing backend:** minisign (simple, key-in-binary) for P0, or go straight to sigstore/cosign keyless (transparency log, no key custody) and align with the SBOM effort (`TOP20_COMPARISON.md:240`)? Tradeoff: operational simplicity now vs. provenance rigor later. -2. **One repo or two?** Keep gallery + docs in `starling`, or split docs into a new `hawk-docs` repo with the 5 repos as submodules? (Submodules complicate contributor flow but isolate doc build from skill content.) +2. **One repo or two?** Keep gallery + docs in `starling`, or split docs into a new `rho-docs` repo with the 5 repos as submodules? (Submodules complicate contributor flow but isolate doc build from skill content.) 3. **Cross-repo extension kinds (P2):** do eyrie/shrike/swift assets (provider plugins, compression profiles, exporters) belong in the *same* `starling` registry, or a per-repo registry federated into one gallery index? -4. **Versioning & compat matrix:** `compat.minHawkVersion` exists (`manifest_v2.go:20`), but do we also need per-asset compat (e.g., an MCP server needing a specific transport hawk supports)? Tie-in to the tri-modal MCP transport gap (`TOP20_COMPARISON.md:83`). -5. **Install integrity for non-skill assets:** today install only copies `SKILL.md` trees (`registry.go` discovers `SKILL.md`); multi-asset copy (commands/hooks/mcp/themes/subagents) needs a defined on-disk layout under `~/.hawk/`. What are the canonical install dirs for each asset kind? -6. **Domain & hosting:** confirm `hawkskills.dev` (referenced `TOP20_COMPARISON.md:65`) ownership and Pages target (GitHub vs Cloudflare). +4. **Versioning & compat matrix:** `compat.minRhoVersion` exists (`manifest_v2.go:20`), but do we also need per-asset compat (e.g., an MCP server needing a specific transport rho supports)? Tie-in to the tri-modal MCP transport gap (`TOP20_COMPARISON.md:83`). +5. **Install integrity for non-skill assets:** today install only copies `SKILL.md` trees (`registry.go` discovers `SKILL.md`); multi-asset copy (commands/hooks/mcp/themes/subagents) needs a defined on-disk layout under `~/.rho/`. What are the canonical install dirs for each asset kind? +6. **Domain & hosting:** confirm `rhoskills.dev` (referenced `TOP20_COMPARISON.md:65`) ownership and Pages target (GitHub vs Cloudflare). 7. **Docs source of truth:** auto-aggregate from each repo's `README.md`/`ARCHITECTURE.md` (drift-free but messy formatting) vs. hand-curated landing pages that link into per-repo docs (cleaner but duplicative)? --- @@ -342,12 +342,12 @@ These repos are privacy-first; the marketplace must not regress that. | Workstream | Scope | Est. | |---|---|---| -| **P0 — format + index v2** | `extension.yaml` spec, v2 generator in `tools/`, `extension.go` parser + `ManifestV2` adapter, multi-asset install layout, `hawk ext` command tree | **4–5 ew** | +| **P0 — format + index v2** | `extension.yaml` spec, v2 generator in `tools/`, `extension.go` parser + `ManifestV2` adapter, multi-asset install layout, `rho ext` command tree | **4–5 ew** | | **P0 — trust core** | `trust.go` (Ed25519/minisign verify), publisher key bundling, wire `malware_check`/`audit` as install gates, permission-disclosure UX | **3–4 ew** | | **P1 — gallery** | Docusaurus setup, `/extensions` React route, build-time `index.json`, search + filters + detail pages | **4–5 ew** | | **P1 — unified docs** | Aggregate 5 independent repositories (checkout/sparse-checkout CI), sidebars + link rewrite, getting-started + API-ref scaffolding per repo, comparison tables from `TOP20_COMPARISON.md` | **5–7 ew** | -| **P1 — CLI update/verify** | `hawk ext update`, `hawk ext verify`, atomic replace | **2 ew** | -| **P2 — publisher self-service** | `hawk ext publish` scaffolder + validators, PR automation | **3 ew** | +| **P1 — CLI update/verify** | `rho ext update`, `rho ext verify`, atomic replace | **2 ew** | +| **P2 — publisher self-service** | `rho ext publish` scaffolder + validators, PR automation | **3 ew** | | **P2 — trust tiers + sigstore upgrade** | verified-publisher badges, optional cosign/sigstore keyless + transparency log | **3–4 ew** | | **P2 — cross-repo kinds** | eyrie/shrike/swift extension kinds + federated index | **3–4 ew** | | **Cross-cutting** | docs writing (content, not framework), security review, CI, design iteration | **4–6 ew** | @@ -360,13 +360,13 @@ The estimate is bounded on the low side because the hardest plumbing — the reg ### Appendix: grounding index (real files cited) - `starling/registry.json`, `registry.json` head (schema), `tools/*.py`, `api/openapi.yaml`, `.claude-plugin/{plugin,marketplace}.json`, `categories/testing/ab-test-setup/SKILL.md`, `LICENSE` -- `hawk/internal/plugin/registry.go:18,21,36,60,196,201,215,289,309` -- `hawk/internal/plugin/manifest_v2.go:11,18,20,32,42` -- `hawk/internal/plugin/skill_loader.go:51` -- `hawk/internal/plugin/malware_check.go:19` -- `hawk/internal/plugin/audit.go:12` -- `hawk/internal/tool/skill.go:62,91` -- `hawk/internal/plugin/` rule discovery -- `hawk/cmd/skills_cmd.go:16-211` -- `hawk/internal/mcp/mcp.go`, `hawk/internal/tool/mcp_tool.go` +- `rho/internal/plugin/registry.go:18,21,36,60,196,201,215,289,309` +- `rho/internal/plugin/manifest_v2.go:11,18,20,32,42` +- `rho/internal/plugin/skill_loader.go:51` +- `rho/internal/plugin/malware_check.go:19` +- `rho/internal/plugin/audit.go:12` +- `rho/internal/tool/skill.go:62,91` +- `rho/internal/plugin/` rule discovery +- `rho/cmd/skills_cmd.go:16-211` +- `rho/internal/mcp/mcp.go`, `rho/internal/tool/mcp_tool.go` - `TOP20_COMPARISON.md:65,74,83,181,227,232,240,241` diff --git a/docs/design/HAWK-CLOUD-SAAS.md b/docs/design/RHO-CLOUD-SAAS.md similarity index 92% rename from docs/design/HAWK-CLOUD-SAAS.md rename to docs/design/RHO-CLOUD-SAAS.md index 9322d328a..532cd5629 100644 --- a/docs/design/HAWK-CLOUD-SAAS.md +++ b/docs/design/RHO-CLOUD-SAAS.md @@ -1,4 +1,4 @@ -# Hawk Cloud — Hosted Execution Plane (Design Doc) +# Rho Cloud — Hosted Execution Plane (Design Doc) **Status:** Draft / Proposal **Author:** Platform team @@ -9,17 +9,17 @@ ## 1. Overview & Competitive Context -Hawk today is a terminal-native, single-user, privacy-first Go binary. The daemon -(`hawk/internal/daemon/daemon.go`) exposes a small HTTP API — `POST /v1/chat` +Rho today is a terminal-native, single-user, privacy-first Go binary. The daemon +(`rho/internal/daemon/daemon.go`) exposes a small HTTP API — `POST /v1/chat` with optional SSE streaming, plus session CRUD — bound to loopback (`netutil.LoopbackHost`, port `4590` by default) and protected by a single optional shared API key (`Server.apiKey`, compared in `daemon.go:185-205`). There is no notion of a user, an org, a tenant, a credit balance, or remote isolated execution. Sandboxed execution is **local Docker** only -(`hawk/internal/sandbox/container.go`). +(`rho/internal/sandbox/container.go`). -This doc designs **Hawk Cloud**: a hosted, multi-tenant execution plane that runs -the hawk agent as a managed service, authenticated by OAuth and API keys, metered +This doc designs **Rho Cloud**: a hosted, multi-tenant execution plane that runs +the rho agent as a managed service, authenticated by OAuth and API keys, metered and billed by credits, organized into team workspaces with org RBAC, and backed by cloud sandboxed execution (E2B-/Daytona-style microVMs) instead of the user's local Docker. It also covers the two surfaces that depend on this plane: a @@ -28,7 +28,7 @@ browser/web chat-editing UI served from the daemon, and IDE extensions ### Which Top-20 repos ship this -From `TOP20_COMPARISON.md`, the hawk P0 table (lines 27-37) and P2 table +From `TOP20_COMPARISON.md`, the rho P0 table (lines 27-37) and P2 table (lines 70-86) name these directly: | Capability | Top-20 repos that ship it | Comparison ref | @@ -43,11 +43,11 @@ From `TOP20_COMPARISON.md`, the hawk P0 table (lines 27-37) and P2 table The comparison doc is blunt that this is a **fundamental product-tier gap** (`TOP20_COMPARISON.md:33`) — the single largest item in the report — and that org RBAC "requires a multi-tenant SaaS backend ... beyond the current single-user -token store at `/hawk/internal/auth/auth.go`" (`TOP20_COMPARISON.md:72`). +token store at `/rho/internal/auth/auth.go`" (`TOP20_COMPARISON.md:72`). The eyrie side of the house has the parallel gaps: a LiteLLM-compatible proxy endpoint and "multi-tenant team/project management with SSO/RBAC" with per-key -budgets (`TOP20_COMPARISON.md:95-96`). Hawk Cloud and eyrie multi-tenancy share +budgets (`TOP20_COMPARISON.md:95-96`). Rho Cloud and eyrie multi-tenancy share the same identity, billing, and RBAC substrate; this doc designs that substrate once and shows how both consume it. @@ -68,27 +68,27 @@ once and shows how both consume it. `sync.Map` (`daemon.go:35`). Net-new: a routing layer that maps tenant → session → execution worker. 4. **Billing / credit system.** Meter token spend and sandbox-minutes per tenant, - price normalized usage with Hawk Cloud's versioned server catalog, enforce + price normalized usage with Rho Cloud's versioned server catalog, enforce hard limits, and bill from the authoritative cloud ledger. 5. **Team workspaces & conversation sharing.** Shared, permissioned session state; read-only and continue-able shares. 6. **Org RBAC** with Member / Admin / Owner tiers, invitations, and org-scoped credit pools and provider config. 7. **Cloud sandboxed execution.** Replace local Docker with on-demand isolated - microVMs (E2B/Daytona/Firecracker) behind hawk's existing sandbox interface. + microVMs (E2B/Daytona/Firecracker) behind rho's existing sandbox interface. 8. **Browser/web UI** for chat-driven editing, served from the plane, consuming the existing SSE stream. 9. **IDE extensions** (VS Code, JetBrains) over ACP with IDE-native diff review. ### Non-Goals -- **Not** replacing the local/offline single-binary mode. Local hawk remains +- **Not** replacing the local/offline single-binary mode. Local rho remains fully functional with zero cloud dependency; cloud is strictly additive. The privacy-first posture (`TOP20_COMPARISON.md:20`) is preserved — cloud is opt-in. - **Not** building our own microVM hypervisor. We integrate E2B/Daytona (build-vs-buy in §6), not write Firecracker orchestration from scratch. - **Not** an LLM gateway rewrite. Eyrie remains the provider engine behind - Hawk's `eyrie/engine` integration; the plane calls a Hawk agent worker, which + Rho's `eyrie/engine` integration; the plane calls a Rho agent worker, which composes Eyrie. The plane does not re-implement routing, caching, or provider adapters. - **Not** SSO/SAML in P0 (deferred to P2; OIDC social login + API keys first). @@ -103,7 +103,7 @@ once and shows how both consume it. ``` ┌────────────────────────────────────────┐ - Browser UI ──┐ │ Hawk Cloud │ + Browser UI ──┐ │ Rho Cloud │ IDE (ACP) ──┼──TLS──▶│ │ CLI (token)──┘ │ ┌──────────────┐ ┌────────────────┐ │ │ │ Edge / API │ │ Identity svc │ │ @@ -144,8 +144,8 @@ middleware (`daemon.go:185`) and `routes()` table (`daemon.go:174-183`). Net-new. Wraps/extends `internal/auth` (see §4). **Billing service** — credit ledger, metering ingestion, server-side pricing, -hard-limit enforcement, and invoicing. Hawk Cloud owns this authority; Eyrie -supplies normalized model and token usage through Hawk's Engine boundary. +hard-limit enforcement, and invoicing. Rho Cloud owns this authority; Eyrie +supplies normalized model and token usage through Rho's Engine boundary. **Workspace service** — workspace membership, shared session state, share links, permission checks for view/continue. @@ -156,7 +156,7 @@ of the daemon's `sessions sync.Map` (`daemon.go:35`). **Agent Worker** — a process (or pod) running `engine.NewSessionWithClient(...)` (`internal/engine/session.go:137`) and driving `Session.Stream(ctx)` -(`internal/engine/stream.go:20`). This is the existing hawk agent loop, unchanged, +(`internal/engine/stream.go:20`). This is the existing rho agent loop, unchanged, running server-side. One worker handles one active session at a time (worktree-per- session isolation), matching the existing single-user model — we scale by running many workers, not by making one worker multi-tenant. @@ -196,11 +196,11 @@ credit_ledger(id, org_id, ts, delta_credits, reason, ref_session_id, ref_meter_i meter_events(id, org_id, workspace_id, session_id, ts, kind, tokens_in, tokens_out, model, sandbox_ms, cost_usd, credits, pricing_catalog_version) - -- kind ∈ {llm, sandbox}; cost_usd is priced by Hawk Cloud + -- kind ∈ {llm, sandbox}; cost_usd is priced by Rho Cloud ``` `meter_events.cost_usd` is computed from normalized model/token dimensions by -Hawk Cloud's versioned pricing catalog. Client-side estimates may be displayed +Rho Cloud's versioned pricing catalog. Client-side estimates may be displayed or audited, but never affect credits or invoices. ### 3.3 API Surface @@ -267,9 +267,9 @@ Billing: append credit_ledger debit; if balance ≤ 0 → signal Worker **B. OAuth login (browser UI / IDE)** PKCE authorization-code grant for the web UI; **device grant for the CLI/IDE** — -hawk already implements RFC 8628 device flow end-to-end +rho already implements RFC 8628 device flow end-to-end (`internal/auth/device_flow.go`: `RequestCode`, `PollForToken`, -`exchangeCode`). Hawk Cloud stands up the *server* side of these grants; the +`exchangeCode`). Rho Cloud stands up the *server* side of these grants; the client side is largely present. **C. Share a conversation** @@ -303,7 +303,7 @@ This is the crux: **what is reusable today vs. net-new.** ### Reusable today (high leverage) -| Existing asset | File | How Hawk Cloud reuses it | +| Existing asset | File | How Rho Cloud reuses it | |---|---|---| | Engine session + agent loop | `internal/engine/session.go:42,132,137`, `internal/engine/stream.go:20` | The Agent Worker *is* `engine.Session.Stream`. No change to the agent loop — it already runs headless with a `SessionFactory` (`daemon.go:27`). | | HTTP daemon + routes + SSE | `internal/daemon/daemon.go:174-183, 315-337` | The Edge/API gateway is the daemon's `routes()`/`handleChat` generalized. SSE framing (per-line `data:` escaping, `daemon.go:326-329`) is correct and reused verbatim. | @@ -311,10 +311,10 @@ This is the crux: **what is reusable today vs. net-new.** | Autonomy / permission gating | `daemon.go:286-298` (`PresetConfig`, `NeedsPermission`) | Server-side auto-approval policy already exists for non-interactive runs; cloud reuses it per-workspace policy. | | Auth primitives | `internal/auth/auth.go` (`TokenStore`, `SecureStorage`), `device_flow.go` (full RFC 8628) | Device-grant **client** is done. `SecureStorage` (macOS keychain + file fallback, `auth.go:54-121`) stays for local credential caching of cloud tokens. `GenerateNonce` (`auth.go:124`) for OAuth state/PKCE. | | Constant-time key compare | `daemon.go:207-224` | API-key verification logic carries over (but keys move to hashed storage; see net-new). | -| Eyrie normalized usage | `eyrie/engine` response and stream usage DTOs through Hawk's adapter | Reuse model/token dimensions as metering input. Do not import lower Eyrie packages or trust client-computed prices for billing. | +| Eyrie normalized usage | `eyrie/engine` response and stream usage DTOs through Rho's adapter | Reuse model/token dimensions as metering input. Do not import lower Eyrie packages or trust client-computed prices for billing. | | Sandbox executor interface | `internal/sandbox/container.go:18-21` (`containerExecutor`: `Exec`, `Running`) | `CloudSandbox` implements the same interface → drop-in. Callers don't know if they're on local Docker or a cloud microVM. | | Sandbox lifecycle manager | `internal/sandbox/snapshot_sandbox.go:52-228` (`Create/Pause/Resume/Snapshot/Restore/List/Cleanup`) | Existing pause/resume/snapshot semantics map cleanly onto E2B/Daytona pause+snapshot APIs; the manager abstraction guides the `CloudSandbox` API shape. | -| Messaging gateways | `internal/daemon/gateway.go`, `telegram.go`, `discord.go`, `slack.go` | Already forward to `/v1/chat` via `forwardToHawk` (`gateway.go:17`) with bearer auth. In cloud they forward to the tenant-scoped chat endpoint with the org's key — minimal change. | +| Messaging gateways | `internal/daemon/gateway.go`, `telegram.go`, `discord.go`, `slack.go` | Already forward to `/v1/chat` via `forwardToRho` (`gateway.go:17`) with bearer auth. In cloud they forward to the tenant-scoped chat endpoint with the org's key — minimal change. | | Cron engine | (not yet implemented) | Cloud scheduled runs (Routines) are planned, per `TOP20_COMPARISON.md:48`. Out of scope here but shares the worker plane. | ### Net-new (must build) @@ -362,11 +362,11 @@ This is the crux: **what is reusable today vs. net-new.** - **M0.5** Single-region Agent Worker pool (1 session/worker, worktree isolation). - **M0.6** `CloudSandbox` (E2B *or* Daytona — pick one) implementing `containerExecutor` (`container.go:18`). - **M0.7** Metering: ingest normalized model/token dimensions, price them with - the versioned Hawk Cloud catalog, persist the Postgres credit ledger, and + the versioned Rho Cloud catalog, persist the Postgres credit ledger, and return HTTP 402 at the hard credit floor. - **M0.8** Minimal web UI (single `go:embed` page) for chat + diff view. -**Exit criteria:** a paying single team can log in via OAuth, run hawk against a +**Exit criteria:** a paying single team can log in via OAuth, run rho against a cloud sandbox, see streamed output and diffs in the browser, and get cut off when credits hit zero. @@ -399,7 +399,7 @@ credits hit zero. | Identity / OAuth server | **Buy** (Ory Hydra/Kratos, Auth0, or WorkOS) for P0; revisit P2 | Writing a compliant OAuth2/OIDC AS is risky. Ory is Apache-2.0 (self-hostable, privacy-aligned). WorkOS/Auth0 are SaaS — faster but introduce a third party in the auth path (weigh against privacy posture). Prefer **Ory self-hosted** to keep the privacy-first promise. | | Billing / payments | **Buy** Stripe for payment + invoicing; **build** the credit ledger | Never build a card processor. The ledger and metering are ours (and partly exist in eyrie). Stripe SDK is permissive. | | Database | **Buy** managed Postgres | Standard. | -| Metering enforcement | **Build in Hawk Cloud** on normalized Engine usage | Billing authority must be server-owned and versioned; lower Eyrie packages remain engine implementation details. | +| Metering enforcement | **Build in Rho Cloud** on normalized Engine usage | Billing authority must be server-owned and versioned; lower Eyrie packages remain engine implementation details. | | Web UI framework | **Build** small React SPA, `go:embed` served (Aider/OpenHands pattern) | Keeps deployment a single binary; matches `TOP20_COMPARISON.md:31`. | | IDE protocol | **Adopt** ACP (Agent Client Protocol) | Industry trajectory (Gemini CLI, Zed, JetBrains), `TOP20_COMPARISON.md:32,36`. Avoid bespoke per-IDE protocols. | @@ -415,12 +415,12 @@ service, so AGPL deps would create source-disclosure obligations — avoid them. These repos are privacy-first (`TOP20_COMPARISON.md:20`); the cloud plane must not erode that. -1. **Cloud is opt-in and isolated from local mode.** Local hawk never phones home; +1. **Cloud is opt-in and isolated from local mode.** Local rho never phones home; nothing in `internal/engine` or `internal/sandbox` gains a cloud dependency. The plane is a separate deployable. 2. **Tenant isolation by construction.** One session ↔ one Agent Worker ↔ one microVM; no shared filesystem or process between tenants. Worktree-per-session - (already hawk's isolation model) carries over. Sandboxes default to + (already rho's isolation model) carries over. Sandboxes default to `--network none` semantics like `ContainerSandbox` (`container.go:81`) unless a workspace explicitly grants egress, gated by the existing net-proxy allowlisting (`internal/sandbox/netproxy.go`). @@ -454,7 +454,7 @@ erode that. single-user — `daemon.go` keys one `apiKey`, sessions share a process). Leaning process/pod-per-session for isolation; needs a cost model. 2. **Sandbox provider:** E2B vs. Daytona for P0. E2B = fastest microVM cold start; - Daytona = richer dev-environment model. Which maps better onto hawk's + Daytona = richer dev-environment model. Which maps better onto rho's pause/resume/snapshot (`snapshot_sandbox.go`)? 3. **Credit unit:** bill in $-equivalent credits (1 credit = $0.01?) covering both server-priced LLM usage and sandbox-minutes — what's the blended margin? diff --git a/docs/design/three-repo-architecture.md b/docs/design/three-repo-architecture.md index 484ecbd48..70909dabd 100644 --- a/docs/design/three-repo-architecture.md +++ b/docs/design/three-repo-architecture.md @@ -8,7 +8,7 @@ proposal. See also `gateway-architecture.md` (human-readable version), ```mermaid flowchart LR - subgraph CLI["hawk"] + subgraph CLI["rho"] TUI["chat · exec · review\nagent loop · sessions"] ENG["eyrie engine\n(compiled in)"] end @@ -34,14 +34,14 @@ Wires, with evidence: |---|---|---| | CLI → Router | Go module dep, in-process construction | `go.mod` requires `github.com/GrayCodeAI/eyrie`; `internal/provider/gateway/gateway.go` is the sole importer | | CLI → Platform | Opt-in HTTPS (login, usage, graph sync) | `internal/platform/cloud/client.go`; `Enabled()` requires endpoint + token; no default URL | -| Router → CLI | none | router `go.mod`/`go.sum` contain zero hawk refs | +| Router → CLI | none | router `go.mod`/`go.sum` contain zero rho refs | | Router ↔ Platform | none | no imports, package deps, or API calls either direction | ## Target state (proposal) ```mermaid flowchart LR - CLI2["hawk\n(default: in-process)"] -. "opt-in --model-gateway" .-> GW + CLI2["rho\n(default: in-process)"] -. "opt-in --model-gateway" .-> GW WEB2["platform web/BFF"] -->|per-user keys| GW subgraph GW["eyrie: common service"] API["OpenAI-compatible API\n/v1/chat/completions + SSE"] diff --git a/docs/ecosystem-message-flow.md b/docs/ecosystem-message-flow.md index ac74c889f..8b98528d7 100644 --- a/docs/ecosystem-message-flow.md +++ b/docs/ecosystem-message-flow.md @@ -1,11 +1,11 @@ # Message flow (eyrie + embedded token engine) -How one user message travels through hawk. +How one user message travels through rho. ## Overview ``` -User prompt (TUI or hawk exec) +User prompt (TUI or rho exec) │ ▼ ┌───────────────────┐ @@ -14,7 +14,7 @@ User prompt (TUI or hawk exec) │ ▼ ┌───────────────────┐ ┌─────────────┐ -│ local memory │◄────│ ~/.hawk/ │ conventions, decisions, lessons +│ local memory │◄────│ ~/.rho/ │ conventions, decisions, lessons │ recall │ │ memories │ └─────────┬─────────┘ └─────────────┘ │ @@ -26,7 +26,7 @@ User prompt (TUI or hawk exec) │ ▼ ┌───────────────────┐ ┌─────────────┐ -│ Hawk ChatClient │────►│ eyrie/engine│ catalog, credentials, routing +│ Rho ChatClient │────►│ eyrie/engine│ catalog, credentials, routing │ port + adapter │ │ generate/ │────► provider API └─────────┬─────────┘ │ stream │ └─────────────┘ @@ -46,21 +46,21 @@ User prompt (TUI or hawk exec) ## Step by step -### 1. Session start (`hawk` or `hawk exec`) +### 1. Session start (`rho` or `rho exec`) -- **eyrie**: The Hawk composition root creates an `eyrie/engine.Engine` with +- **eyrie**: The Rho composition root creates an `eyrie/engine.Engine` with Eyrie-owned state paths, an injected secret store, and per-engine custom gateway metadata. The engine loads provider state and the model catalog, then - builds transport behind Hawk's `ChatClient` port. + builds transport behind Rho's `ChatClient` port. - **memory**: `configureSession` initializes the local memory manager (core, auto, evolving, zen, retrieval metrics, continuity). If the state - directory is unavailable, hawk runs without persistent memory. + directory is unavailable, rho runs without persistent memory. - **token**: No startup step — embedded at compile time. ### 2. System prompt assembly -- Hawk templates (`internal/prompts/templates/*.md`) define behavior, tools, and practices. -- Project `AGENTS.md` is appended via `hawkconfig.BuildContextWithDirs`. +- Rho templates (`internal/prompts/templates/*.md`) define behavior, tools, and practices. +- Project `AGENTS.md` is appended via `rhoconfig.BuildContextWithDirs`. - **memory**: `Memory.Recall` injects relevant stored memories into the system prompt. ### 3. User message → agent loop (`internal/engine/stream.go`) @@ -68,7 +68,7 @@ User prompt (TUI or hawk exec) Each turn: 1. **memory** — recall memories matching the latest user message (token budget ~2000). -2. **eyrie** — Hawk's adapter calls engine generate/stream with Hawk-owned tool +2. **eyrie** — Rho's adapter calls engine generate/stream with Rho-owned tool definitions; Eyrie normalizes provider events and tool requests. 3. Tools run with the session's memory service in context. 4. **memory** — post-session bookkeeping records the session goal and outcome. @@ -78,7 +78,7 @@ Each turn: When messages exceed limits (`internal/engine/compact.go`): 1. **token** — `token.Compress()` tries a fast compression path for summaries. -2. **eyrie** — if token reduction is insufficient, hawk calls the LLM to summarize, then keeps recent messages. +2. **eyrie** — if token reduction is insufficient, rho calls the LLM to summarize, then keeps recent messages. ### 5. Token accounting @@ -88,13 +88,13 @@ When messages exceed limits (`internal/engine/compact.go`): ## Verify locally ```bash -hawk doctor # ecosystem panel + eyrie preflight -./scripts/smoke-hawk.sh # build + quick tests +rho doctor # ecosystem panel + eyrie preflight +./scripts/smoke-rho.sh # build + quick tests ``` ## Module layout -| Module | Role in hawk | Required? | +| Module | Role in rho | Required? | |--------|----------------|-----------| | **eyrie** | LLM APIs, catalog, credentials, routing | Yes | | **internal/token** | Token estimate + context compression + secrets + usage | Yes (embedded, no config) | @@ -102,7 +102,7 @@ hawk doctor # ecosystem panel + eyrie preflight `eyrie` is an independent sibling checkout; the parent `go.work` wires the local Go workspace. -Production Hawk code imports Eyrie only through `eyrie/engine`. Conversation -history, WAL/resume, permissions, tool execution, and memory remain in Hawk; +Production Rho code imports Eyrie only through `eyrie/engine`. Conversation +history, WAL/resume, permissions, tool execution, and memory remain in Rho; provider credentials, discovery, selection, transport, resilience, and normalized streaming remain in Eyrie. \ No newline at end of file diff --git a/docs/ecosystem-remediation-plan.md b/docs/ecosystem-remediation-plan.md index eb01e3f7b..e594bc66f 100644 --- a/docs/ecosystem-remediation-plan.md +++ b/docs/ecosystem-remediation-plan.md @@ -4,21 +4,21 @@ This document captures the improvement recommendations from the 2026-07-11 full-ecosystem audit that require significant refactoring and are documented here for scheduled execution rather than immediate implementation. -## 1. charmbracelet v1/v2 Dependency Duplication (hawk + swift) +## 1. charmbracelet v1/v2 Dependency Duplication (rho + swift) **Status:** ✅ Done (2026-07-11) -**Impact:** High — contributed ~20-30MB to hawk binary size +**Impact:** High — contributed ~20-30MB to rho binary size ### Outcome -- Hawk and Swift imports migrated to `charm.land/*/v2` (`bubbles`, `bubbletea`, +- Rho and Swift imports migrated to `charm.land/*/v2` (`bubbles`, `bubbletea`, `lipgloss`, `huh`, `glamour`). - Direct `github.com/charmbracelet/{bubbles,bubbletea,lipgloss}` requires removed - from hawk/swift `go.mod`. + from rho/swift `go.mod`. - Binary size gate tightened: **110MB → 80MB** (`make size-check`). -- Verified build: hawk binary **~75 MB** (under gate) after migration. +- Verified build: rho binary **~75 MB** (under gate) after migration. -Commits (hawk): `2890c74` (migrate), `ccfa286` (API fixups), `7a42c1e` (size gate). +Commits (rho): `2890c74` (migrate), `ccfa286` (API fixups), `7a42c1e` (size gate). ### Residual notes @@ -38,13 +38,13 @@ Commits (hawk): `2890c74` (migrate), `ccfa286` (API fixups), `7a42c1e` (size gat (`replace github.com/GrayCodeAI/harrier => ../..`). - Core `github.com/GrayCodeAI/harrier` no longer requires `charmbracelet/{bubbles,bubbletea,lipgloss}`. -- Hawk embeds only the library packages (`engine`, `storage`, `graph`, …), so - the default hawk binary does not pull the demo TUI module. +- Rho embeds only the library packages (`engine`, `storage`, `graph`, …), so + the default rho binary does not pull the demo TUI module. - Verify: `cd harrier && go test ./...`; `cd harrier/cmd/harrier-tui && go test ./...`. ### Residual notes -- The local `harrier` (Harrier) checkout and Hawk's `go.mod` must resolve to a +- The local `harrier` (Harrier) checkout and Rho's `go.mod` must resolve to a published matching commit. **Publish Harrier before** relying on `GOWORK=off` / module-release parity (the Go proxy must see the commit for sumdb download). @@ -98,13 +98,13 @@ large diffs and bloats the repo. `.gitignore`. 2. **Generate at build/publish time:** Run `python tools/update_registry.py` in CI before publishing artifacts. -3. **Runtime fetch:** Have `hawk` fetch the registry from a CDN or GitHub +3. **Runtime fetch:** Have `rho` fetch the registry from a CDN or GitHub raw URL rather than embedding it. ### Risk -If `registry.json` is read offline by hawk, removing it requires adding a -fetch-or-cache mechanism. Verify how hawk consumes the registry before +If `registry.json` is read offline by rho, removing it requires adding a +fetch-or-cache mechanism. Verify how rho consumes the registry before removing from git. --- diff --git a/docs/intelligent-cli.md b/docs/intelligent-cli.md index b17274e94..bbd37203b 100644 --- a/docs/intelligent-cli.md +++ b/docs/intelligent-cli.md @@ -1,6 +1,6 @@ # Intelligent CLI capabilities -Hawk keeps the startup tool schema small while making the full capability +Rho keeps the startup tool schema small while making the full capability surface discoverable on demand. The registry currently contains core tools, lazy tools, and MCP tools; intent routing promotes only the tools relevant to the current request. @@ -37,8 +37,8 @@ Dependency and GitHub operations are network-gated and read-only by default. ## Inspecting the registry ```bash -hawk tools -hawk tools --json +rho tools +rho tools --json ``` The JSON form includes risk level, read-only status, aliases, and intent diff --git a/docs/mcp-servers.md b/docs/mcp-servers.md index b845efdcb..18f47b390 100644 --- a/docs/mcp-servers.md +++ b/docs/mcp-servers.md @@ -1,10 +1,10 @@ # MCP Server Configuration -hawk supports connecting to external MCP (Model Context Protocol) servers to extend its capabilities with additional tools, resources, and prompts. +rho supports connecting to external MCP (Model Context Protocol) servers to extend its capabilities with additional tools, resources, and prompts. ## Configuration -MCP servers are configured in `settings.json` (global: `~/.hawk/settings.json`, project: `.hawk/settings.json`). +MCP servers are configured in `settings.json` (global: `~/.rho/settings.json`, project: `.rho/settings.json`). ```json { @@ -42,7 +42,7 @@ My-Jogyo provides 12 MCP tools for scientific research workflows, including Pyth # Install My-Jogyo npm install -g my-jogyo -# Add to hawk settings +# Add to rho settings ``` **settings.json:** @@ -78,16 +78,16 @@ npm install -g my-jogyo ```bash # Add an MCP server -hawk mcp add [args...] +rho mcp add [args...] # List configured servers -hawk mcp list +rho mcp list # Remove a server -hawk mcp remove +rho mcp remove # Test a server connection -hawk mcp test +rho mcp test ``` ## Troubleshooting @@ -98,8 +98,8 @@ hawk mcp test - Run the command manually to check for errors **Tools not appearing:** -- Restart hawk after adding a new server -- Check `hawk mcp test ` for connection errors +- Restart rho after adding a new server +- Check `rho mcp test ` for connection errors - Verify the server's tools/list response is valid JSON-RPC **Timeout errors:** diff --git a/docs/monitoring-guide.md b/docs/monitoring-guide.md index c2998a354..e560631f6 100644 --- a/docs/monitoring-guide.md +++ b/docs/monitoring-guide.md @@ -1,6 +1,6 @@ # Monitoring Guide -This guide covers how to monitor hawk's daemon and CLI for production health, +This guide covers how to monitor rho's daemon and CLI for production health, performance, and security. ## 1. Daemon Health & Readiness @@ -31,7 +31,7 @@ Returns aggregated usage statistics (sessions, messages, tool calls, cost) for the last N days (default 30, `?days=30`). ```bash -curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" \ +curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ http://localhost:4590/v1/stats ``` @@ -43,7 +43,7 @@ The daemon exposes metrics in Prometheus text exposition format at `GET /v1/metrics`. This endpoint requires authentication. ```bash -curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" \ +curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ http://localhost:4590/v1/metrics ``` @@ -51,9 +51,9 @@ Available metrics: | Metric | Type | Description | |--------|------|-------------| -| `hawk_daemon_active_sessions` | gauge | Number of active daemon sessions | -| `hawk_daemon_chat_concurrency_used` | gauge | Number of in-use chat concurrency slots | -| `hawk_daemon_uptime_seconds` | gauge | Daemon uptime in seconds | +| `rho_daemon_active_sessions` | gauge | Number of active daemon sessions | +| `rho_daemon_chat_concurrency_used` | gauge | Number of in-use chat concurrency slots | +| `rho_daemon_uptime_seconds` | gauge | Daemon uptime in seconds | | `http_requests_total` | counter | Total HTTP requests received | | `http_request_duration_ms` | histogram | HTTP request duration in milliseconds | | `http_rate_limited_total` | counter | Number of requests rejected by rate limiter | @@ -70,9 +70,9 @@ text format. Telemetry is **opt-in**. Enable it by setting: ```bash -export HAWK_ENABLE_TELEMETRY=1 +export RHO_ENABLE_TELEMETRY=1 export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 -hawk daemon start +rho daemon start ``` The daemon will automatically: @@ -80,31 +80,31 @@ The daemon will automatically: - Initialize the OTel SDK with a batch span processor (5s batch interval). - Export traces to the OTLP endpoint (`OTEL_EXPORTER_OTLP_ENDPOINT`). - Send trace headers from `OTEL_EXPORTER_OTLP_HEADERS`. -- Set the service name (default: `hawk`) and version. +- Set the service name (default: `rho`) and version. ### Configuration | Environment Variable | Default | Description | |---------------------|---------|-------------| -| `HAWK_ENABLE_TELEMETRY` | `0` | Set to `1` to enable OTP telemetry | +| `RHO_ENABLE_TELEMETRY` | `0` | Set to `1` to enable OTP telemetry | | `OTEL_EXPORTER_OTLP_ENDPOINT` | _(empty)_ | OTLP collector endpoint | | `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` | OTLP transport protocol | | `OTEL_EXPORTER_OTLP_HEADERS` | _(empty)_ | Comma-separated `key=value` headers | -| `HAWK_OTEL_SHUTDOWN_TIMEOUT_MS` | `2000` | Shutdown timeout in milliseconds | +| `RHO_OTEL_SHUTDOWN_TIMEOUT_MS` | `2000` | Shutdown timeout in milliseconds | ### OTel Conventions -hawk follows the OpenTelemetry semantic conventions for traces. See +rho follows the OpenTelemetry semantic conventions for traces. See [docs/OTEL-CONVENTIONS.md](OTEL-CONVENTIONS.md) for span naming and attribute details. ## 4. Structured Logging -The daemon writes structured SLOG logs to `~/.hawk/state/daemon.log` by +The daemon writes structured SLOG logs to `~/.rho/state/daemon.log` by default. Control the log level via: ```bash -hawk daemon start --log-level DEBUG +rho daemon start --log-level DEBUG ``` Or via environment variable: @@ -127,13 +127,13 @@ All daemon log entries include: ### Audit log Security-relevant events (auth failures, tool executions) are written to a -tamper-evident log at `~/.hawk/state/securitylog/security_events.jsonl`. +tamper-evident log at `~/.rho/state/securitylog/security_events.jsonl`. Verify the audit log integrity: ```bash # The securitylog package provides a verify command -go run ./cmd/hawk securitylog verify +go run ./cmd/rho securitylog verify ``` ## 5. Prometheus Scraping @@ -142,12 +142,12 @@ go run ./cmd/hawk securitylog verify ```yaml services: - hawk: - image: ghcr.io/graycodeai/hawk-daemon:latest + rho: + image: ghcr.io/graycodeai/rho-daemon:latest ports: - "4590:4590" environment: - - HAWK_DAEMON_API_KEY=secret + - RHO_DAEMON_API_KEY=secret labels: - "prometheus.io/scrape=true" - "prometheus.io/port=4590" @@ -160,14 +160,14 @@ services: apiVersion: v1 kind: Service metadata: - name: hawk-daemon + name: rho-daemon annotations: prometheus.io/scrape: "true" prometheus.io/port: "4590" prometheus.io/path: "/v1/metrics" spec: selector: - app: hawk-daemon + app: rho-daemon ports: - port: 4590 targetPort: 4590 @@ -175,18 +175,18 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: hawk-daemon + name: rho-daemon spec: template: spec: containers: - - name: hawk - image: ghcr.io/graycodeai/hawk-daemon:latest + - name: rho + image: ghcr.io/graycodeai/rho-daemon:latest env: - - name: HAWK_DAEMON_API_KEY + - name: RHO_DAEMON_API_KEY valueFrom: secretKeyRef: - name: hawk-secret + name: rho-secret key: api-key ports: - containerPort: 4590 @@ -208,26 +208,26 @@ spec: | Alert | Condition | Severity | |-------|-----------|----------| -| Daemon down | `hawk_daemon_uptime_seconds` does not increase for 2+ minutes | critical | +| Daemon down | `rho_daemon_uptime_seconds` does not increase for 2+ minutes | critical | | High request latency | `histogram_quantile(0.95, http_request_duration_ms)` > 10000ms for 5 minutes | warning | | Rate limit saturation | `rate(http_rate_limited_total[5m])` > 10/s | warning | | Auth failures | `rate(auth_denied_total[5m])` > 5/s | critical (possible brute force) | -| High concurrency | `hawk_daemon_chat_concurrency_used` sustained at max for 5+ minutes | warning | -| No active sessions | `hawk_daemon_active_sessions` = 0 during business hours | warning | +| High concurrency | `rho_daemon_chat_concurrency_used` sustained at max for 5+ minutes | warning | +| No active sessions | `rho_daemon_active_sessions` = 0 during business hours | warning | ## 7. Systemd Logging When running under systemd, logs from stderr/stdout are captured by journald: ```bash -journalctl -u hawk-daemon -f +journalctl -u rho-daemon -f ``` The daemon also writes its own structured log to -`~/.hawk/state/daemon.log`: +`~/.rho/state/daemon.log`: ```bash -tail -f ~/.hawk/state/daemon.log +tail -f ~/.rho/state/daemon.log ``` ## 8. Feature Flags @@ -236,7 +236,7 @@ Feature flags allow runtime configuration without restarts. They are controlled via environment variables: ```bash -export HAWK_FEATURE_=1 +export RHO_FEATURE_=1 ``` | Flag | Default | Description | @@ -250,5 +250,5 @@ export HAWK_FEATURE_=1 List all registered flags: ```bash -hawk features +rho features ``` diff --git a/docs/operations-checklist.md b/docs/operations-checklist.md index 361349284..1e257dca9 100644 --- a/docs/operations-checklist.md +++ b/docs/operations-checklist.md @@ -1,22 +1,22 @@ # Production Operations Checklist -Use this checklist when deploying or upgrading hawk in a production +Use this checklist when deploying or upgrading rho in a production environment. Each item links to the relevant configuration option or documentation section. ## Pre-Deployment -- [ ] **API key is set** — `HAWK_DAEMON_API_KEY` environment variable is +- [ ] **API key is set** — `RHO_DAEMON_API_KEY` environment variable is configured to a cryptographically random value (≥ 32 bytes). Do **not** rely on the auto-generated key for production. ```bash - export HAWK_DAEMON_API_KEY=$(openssl rand -base64 32) + export RHO_DAEMON_API_KEY=$(openssl rand -base64 32) ``` - [ ] **Bind address** — Daemon binds to `0.0.0.0` (not just loopback) only when remote access is needed. A non-loopback bind requires an API key and native TLS; otherwise startup fails closed. - [ ] **TLS configured** — Enable native TLS with `--tls-cert` / `--tls-key` - flags. If a reverse proxy terminates TLS, keep Hawk bound to loopback or + flags. If a reverse proxy terminates TLS, keep Rho bound to loopback or an internal-only interface and restrict that network path at the firewall; the daemon does not treat `X-Forwarded-Proto` as transport encryption. - [ ] **CORS configured** — If serving browser-based clients, set @@ -24,14 +24,14 @@ documentation section. trusted origins only. Use `--cors '*'` only for development. - [ ] **Rate limits reviewed** — Default: 10 req/min for general API, 30 req/min for chat, 4 concurrent chat sessions. Tune via - `HAWK_DAEMON_MAX_CONCURRENT`. + `RHO_DAEMON_MAX_CONCURRENT`. - [ ] **Resource limits set** — Configure CPU/memory limits in systemd (`MemoryMax`, `CPUQuota`) or Kubernetes. Defaults in the systemd unit file: `MemoryMax=4G`, `CPUQuota=200%`. -- [ ] **Log retention** — Daemon logs at `~/.hawk/state/daemon.log`. +- [ ] **Log retention** — Daemon logs at `~/.rho/state/daemon.log`. Configure log rotation (logrotate, journald retention) to prevent disk exhaustion. -- [ ] **State directory backed up** — The `~/.hawk/state/` directory contains +- [ ] **State directory backed up** — The `~/.rho/state/` directory contains the PID file, API key pin file, audit log, and session state. Back up the audit log key (`securitylog/sel.key`) — **losing it makes all historical audit entries unverifiable**. @@ -40,17 +40,17 @@ documentation section. ## Observability -- [ ] **Telemetry enabled (optional)** — Set `HAWK_ENABLE_TELEMETRY=1` +- [ ] **Telemetry enabled (optional)** — Set `RHO_ENABLE_TELEMETRY=1` and configure `OTEL_EXPORTER_OTLP_ENDPOINT` to send traces to your OTLP collector. Telemetry is opt-in by default. - [ ] **Prometheus scraping** — If using Prometheus, configure a scrape target for `http://:4590/v1/metrics` with authentication: ```yaml scrape_configs: - - job_name: 'hawk-daemon' - bearer_token: '' + - job_name: 'rho-daemon' + bearer_token: '' static_configs: - - targets: ['hawk-daemon:4590'] + - targets: ['rho-daemon:4590'] metrics_path: '/v1/metrics' ``` - [ ] **Health/readiness probes** — Configure in your orchestrator: @@ -62,13 +62,13 @@ documentation section. ## Security Hardening - [ ] **API key rotated** — The API key is written to a pinned file at - `~/.hawk/state/daemon.key` for convenience. **Remove this file in + `~/.rho/state/daemon.key` for convenience. **Remove this file in production** or ensure it has `0600` permissions and is not world-readable. - [ ] **Audit log verification** — Periodically verify the audit log integrity: ```bash # The Verify function checks the HMAC chain - go run ./cmd/hawk securitylog verify + go run ./cmd/rho securitylog verify ``` - [ ] **Security headers** — Verify `X-Content-Type-Options`, `X-Frame-Options`, and `Content-Security-Policy` headers are present @@ -79,7 +79,7 @@ documentation section. - [ ] **No CGO** — The binary is built with `CGO_ENABLED=0` for a static binary. Verify the deployed binary has no dynamic library dependencies: ```bash - ldd /usr/local/bin/hawk # should say "not a dynamic executable" + ldd /usr/local/bin/rho # should say "not a dynamic executable" ``` ## Post-Deployment @@ -97,7 +97,7 @@ documentation section. ``` - [ ] **Metrics test** — Verify the metrics endpoint: ```bash - curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" http://localhost:4590/v1/metrics + curl -H "X-API-Key: $RHO_DAEMON_API_KEY" http://localhost:4590/v1/metrics ``` - [ ] **Version check** — Verify the deployed version matches expectations: ```bash @@ -106,10 +106,10 @@ documentation section. ## Upgrade Procedure -1. **Back up state** — Copy `~/.hawk/state/` to a safe location. +1. **Back up state** — Copy `~/.rho/state/` to a safe location. 2. **Drain traffic** — Remove the daemon from load balancer rotation or stop sending new requests. -3. **Install new binary** — Replace the binary and run `hawk daemon start` +3. **Install new binary** — Replace the binary and run `rho daemon start` with the same configuration. 4. **Verify health** — Check `GET /v1/health` and `GET /v1/ready`. 5. **Verify metrics** — Check `GET /v1/metrics` for expected counters. diff --git a/docs/plans/FULL-GROK-ECO-TO-HAWK-ECO-PORT-PLAN.md b/docs/plans/FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md similarity index 77% rename from docs/plans/FULL-GROK-ECO-TO-HAWK-ECO-PORT-PLAN.md rename to docs/plans/FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md index fd5292865..55848590f 100644 --- a/docs/plans/FULL-GROK-ECO-TO-HAWK-ECO-PORT-PLAN.md +++ b/docs/plans/FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md @@ -5,7 +5,7 @@ **Active execution:** [YEAR-0-ACTIVE.md](./YEAR-0-ACTIVE.md) (Year 0 control-plane track) **ADR:** [ADR-0003](../architecture/adr/ADR-0003-grok-behavioral-port-go-multirepo.md) **Meaning of “port”:** Reimplement **every Grok Build capability** in idiomatic **Go** across graycode-eco repos. -**Not meaning:** Copy Rust crates, depend on Grok binaries, or collapse hawk into a Rust monorepo. +**Not meaning:** Copy Rust crates, depend on Grok binaries, or collapse rho into a Rust monorepo. **Source tree:** `grok-eco/grok-build` (~1.35M LOC Rust, 1 product monorepo) **Target tree:** `graycode-eco/*` (multi-repo Go platform + cloud/TS/Python) @@ -18,8 +18,8 @@ ## 0. Port principles 1. **Behavior parity over code structure** — same user-visible contracts, not same file tree. -2. **Go multi-repo stays** — map Grok crates into hawk / engines / contracts / cloud / skills. -3. **Prefer wire-first** — Hawk already has partial types; complete wiring before greenfield. +2. **Go multi-repo stays** — map Grok crates into rho / engines / contracts / cloud / skills. +3. **Prefer wire-first** — Rho already has partial types; complete wiring before greenfield. 4. **Privacy-first defaults** — Grok Mixpanel/Sentry defaults become **opt-in** OTEL/privacy-safe telemetry. 5. **Multi-provider stays** — Grok sampler/auth maps to **eyrie**, not a single-vendor clone. 6. **Memory stays harrier** — Grok markdown memory maps to **harrier APIs + UX**, not a second store. @@ -67,7 +67,7 @@ Critical path remains **months**; **complete parity** including TUI polish and e ```text grok-eco/grok-build (one Rust workspace) │ - ├─► hawk product CLI/TUI/engine/tools/hooks/plugins/ACP + ├─► rho product CLI/TUI/engine/tools/hooks/plugins/ACP ├─► eagle pure DTOs (tool/spawn/hooks/policy) ├─► eyrie LLM routing/stream/retry/catalog/auth credentials ├─► harrier memory graph + dream UX @@ -92,87 +92,87 @@ grok-eco/grok-build (one Rust workspace) | Grok crate | ~LOC | Capability | Target repo | Status | Effort | Notes | |------------|-----:|------------|-------------|--------|--------|-------| -| `xai-grok-pager` | 415k | Full TUI | hawk `cmd/` | Partial | XXL | Bubble Tea already; parity of panes/modals/slash is multi-year | -| `xai-grok-pager-render` | 35k | Render pipeline | hawk | Partial | L | Streaming render, blocks, media | -| `xai-grok-pager-minimal` | 5.6k | `--minimal` native scrollback | hawk | Partial | M | `hawk --repl` / print modes exist | -| `xai-grok-pager-bin` | 2.9k | Binary composition root | hawk `cmd/hawk` | Done | — | | -| `xai-grok-pager-pty-harness` | 10k | PTY test harness | hawk tests | Partial | M | Test infra | -| `xai-grok-shell` | 336k | Agent runtime host | hawk `internal/engine` | Partial | XXL | Largest runtime port | -| `xai-grok-shell-base` | 2.7k | Shared shell foundation | hawk | Partial | S | Absorb | -| `xai-grok-shell-session-support` | 1.5k | Session support extract | hawk `session` | Partial | S | Absorb | -| `xai-grok-tools` | 112k | Tool implementations | hawk `internal/tool` | Partial | XL | See tool matrix §3 | -| `xai-grok-tools-api` | 0.7k | Tool API / slash wording | hawk + contracts | Partial | S | | -| `xai-grok-workspace` | 77k | FS/VCS/permissions/hub | hawk + sandbox + session | Partial | XL | | +| `xai-grok-pager` | 415k | Full TUI | rho `cmd/` | Partial | XXL | Bubble Tea already; parity of panes/modals/slash is multi-year | +| `xai-grok-pager-render` | 35k | Render pipeline | rho | Partial | L | Streaming render, blocks, media | +| `xai-grok-pager-minimal` | 5.6k | `--minimal` native scrollback | rho | Partial | M | `rho --repl` / print modes exist | +| `xai-grok-pager-bin` | 2.9k | Binary composition root | rho `cmd/rho` | Done | — | | +| `xai-grok-pager-pty-harness` | 10k | PTY test harness | rho tests | Partial | M | Test infra | +| `xai-grok-shell` | 336k | Agent runtime host | rho `internal/engine` | Partial | XXL | Largest runtime port | +| `xai-grok-shell-base` | 2.7k | Shared shell foundation | rho | Partial | S | Absorb | +| `xai-grok-shell-session-support` | 1.5k | Session support extract | rho `session` | Partial | S | Absorb | +| `xai-grok-tools` | 112k | Tool implementations | rho `internal/tool` | Partial | XL | See tool matrix §3 | +| `xai-grok-tools-api` | 0.7k | Tool API / slash wording | rho + contracts | Partial | S | | +| `xai-grok-workspace` | 77k | FS/VCS/permissions/hub | rho + sandbox + session | Partial | XL | | | `xai-grok-workspace-types` | 9.2k | Workspace wire types | contracts | Partial | M | | -| `xai-grok-workspace-client` | 0.8k | Workspace RPC client | hawk (if hub) | Port | M | Only if computer-hub ported | -| `xai-grok-agent` | 21k | Agent defs + system prompt | hawk agent/personas | Partial | L | | -| `xai-grok-subagent-resolution` | 2.6k | Capability/isolation resolve | hawk + contracts | Port | M | Critical | -| `xai-chat-state` | 13k | Chat state actor | hawk session/engine | Partial | L | | -| `xai-grok-markdown` | 22k | Streaming MD TUI | hawk markdown | Partial | L | | -| `xai-grok-markdown-core` | 1.1k | Headless MD analysis | hawk | Partial | S | | -| `xai-ratatui-textarea` | 12k | Input widget | hawk (Bubble Tea) | N/A | — | Different UI stack | -| `xai-ratatui-inline` | 3.7k | Inline render | hawk | N/A | — | UI stack | -| `xai-fast-worktree` | 19k | CoW worktree speed | hawk worktree | Port | L | Perf enhancement | -| `xai-file-utils` | 15k | Event tracking / upload | hawk observability | Partial | M | Privacy opt-in | -| `xai-grok-telemetry` | 14k | Events + OTEL + Mixpanel + Sentry | hawk observability | Partial | L | **Skip Mixpanel default**; keep OTEL | -| `xai-hunk-tracker` | 13k | Agent vs external hunks | hawk | Port | L | | +| `xai-grok-workspace-client` | 0.8k | Workspace RPC client | rho (if hub) | Port | M | Only if computer-hub ported | +| `xai-grok-agent` | 21k | Agent defs + system prompt | rho agent/personas | Partial | L | | +| `xai-grok-subagent-resolution` | 2.6k | Capability/isolation resolve | rho + contracts | Port | M | Critical | +| `xai-chat-state` | 13k | Chat state actor | rho session/engine | Partial | L | | +| `xai-grok-markdown` | 22k | Streaming MD TUI | rho markdown | Partial | L | | +| `xai-grok-markdown-core` | 1.1k | Headless MD analysis | rho | Partial | S | | +| `xai-ratatui-textarea` | 12k | Input widget | rho (Bubble Tea) | N/A | — | Different UI stack | +| `xai-ratatui-inline` | 3.7k | Inline render | rho | N/A | — | UI stack | +| `xai-fast-worktree` | 19k | CoW worktree speed | rho worktree | Port | L | Perf enhancement | +| `xai-file-utils` | 15k | Event tracking / upload | rho observability | Partial | M | Privacy opt-in | +| `xai-grok-telemetry` | 14k | Events + OTEL + Mixpanel + Sentry | rho observability | Partial | L | **Skip Mixpanel default**; keep OTEL | +| `xai-hunk-tracker` | 13k | Agent vs external hunks | rho | Port | L | | | `xai-grok-sampling-types` | 13k | Chat API types | **eyrie** | Done* | — | Different shape; eyrie owns | | `xai-grok-sampler` | 11k | HTTP stream + retry | **eyrie** | Done* | — | Do not replace eyrie | -| `xai-grok-update` | 11k | Auto-update | hawk | Partial | M | | -| `xai-grok-mcp` | 10k | MCP client (oauth, wire) | hawk `mcp` | Partial | L | | -| `xai-codebase-graph` | 9.7k | Tree-sitter graph | hawk codegraph/repomap | Partial | L | | +| `xai-grok-update` | 11k | Auto-update | rho | Partial | M | | +| `xai-grok-mcp` | 10k | MCP client (oauth, wire) | rho `mcp` | Partial | L | | +| `xai-codebase-graph` | 9.7k | Tree-sitter graph | rho codegraph/repomap | Partial | L | | | `xai-grok-memory` | 9.7k | Cross-session memory | **harrier** | Done* | M | Port UX only | -| `xai-grok-hooks` | 8.3k | File/HTTP hooks | hawk hooks | Port | L | | -| `xai-fsnotify` | 6.7k | FS events | hawk (fsnotify) | Partial | S | | -| `xai-grok-config` | 6k | Config layers + managed | hawk config + cloud | Port | L | | +| `xai-grok-hooks` | 8.3k | File/HTTP hooks | rho hooks | Port | L | | +| `xai-fsnotify` | 6.7k | FS events | rho (fsnotify) | Partial | S | | +| `xai-grok-config` | 6k | Config layers + managed | rho config + cloud | Port | L | | | `xai-grok-config-types` | 2.7k | Config DTOs | contracts/config | Partial | S | | -| `xai-grok-plugin-marketplace` | 5.3k | Marketplace | hawk + community-skills | Port | L | | -| `xai-grok-shared` | 5.2k | Shared utils | hawk | N/A | — | Absorb | -| `xai-grok-test-support` | 4.6k | Test harness | hawk testutil | Partial | M | | -| `xai-grok-sandbox` | 3.9k | OS sandbox profiles | hawk sandbox | Port | L | | -| `xai-grok-voice` | 2.7k | Streaming STT | hawk | Partial | M | whisper path exists | -| `xai-acp-lib` | 2.3k | ACP protocol | hawk acp | Port | L | | -| `xai-grok-mermaid` | 2.2k | Mermaid→PNG | hawk | Port | M | | -| `xai-crash-handler` | 1.9k | Crash + startup detect | hawk | Port | S | | -| `ptyctl` | 2.3k | Headless PTY control | hawk | Port | M | | -| `ptyctl-cli` | 0.8k | PTY CLI | hawk tests/tools | Optional | S | | -| `xai-tty-utils` | 1.2k | TTY-safe spawn | hawk | Port | S | | +| `xai-grok-plugin-marketplace` | 5.3k | Marketplace | rho + community-skills | Port | L | | +| `xai-grok-shared` | 5.2k | Shared utils | rho | N/A | — | Absorb | +| `xai-grok-test-support` | 4.6k | Test harness | rho testutil | Partial | M | | +| `xai-grok-sandbox` | 3.9k | OS sandbox profiles | rho sandbox | Port | L | | +| `xai-grok-voice` | 2.7k | Streaming STT | rho | Partial | M | whisper path exists | +| `xai-acp-lib` | 2.3k | ACP protocol | rho acp | Port | L | | +| `xai-grok-mermaid` | 2.2k | Mermaid→PNG | rho | Port | M | | +| `xai-crash-handler` | 1.9k | Crash + startup detect | rho | Port | S | | +| `ptyctl` | 2.3k | Headless PTY control | rho | Port | M | | +| `ptyctl-cli` | 0.8k | PTY CLI | rho tests/tools | Optional | S | | +| `xai-tty-utils` | 1.2k | TTY-safe spawn | rho | Port | S | | | `xai-hooks-plugins-types` | 1.2k | Hooks/plugins ACP DTOs | contracts | Port | S | | -| `xai-sqlite-journal` | 0.8k | SQLite journal mode | hawk/harrier/shrike | Partial | XS | | -| `xai-system-power` | 0.7k | Sleep/wake notify | hawk sleep_prevent | Partial | S | | -| `xai-grok-http` | 0.6k | Shared HTTP client | hawk/netutil | Partial | XS | | -| `xai-agent-lifecycle` | 0.6k | Lifecycle hooks data | hawk hooks/engine | Partial | S | | -| `xai-gix-status` | 0.6k | Fast git status | hawk git tools | Partial | S | | -| `xai-grok-paths` | 0.6k | AbsPath types | hawk | N/A | XS | Go path.Clean enough | +| `xai-sqlite-journal` | 0.8k | SQLite journal mode | rho/harrier/shrike | Partial | XS | | +| `xai-system-power` | 0.7k | Sleep/wake notify | rho sleep_prevent | Partial | S | | +| `xai-grok-http` | 0.6k | Shared HTTP client | rho/netutil | Partial | XS | | +| `xai-agent-lifecycle` | 0.6k | Lifecycle hooks data | rho hooks/engine | Partial | S | | +| `xai-gix-status` | 0.6k | Fast git status | rho git tools | Partial | S | | +| `xai-grok-paths` | 0.6k | AbsPath types | rho | N/A | XS | Go path.Clean enough | | `xai-grok-secrets` | 0.6k | Secrets helpers | shrike + eyrie | Partial | S | | -| `xai-grok-announcements` | 0.4k | Release announcements | hawk tips/notify | Port | S | | -| `xai-grok-auth` | 0.4k | Auth seam | eyrie + hawk auth | Partial | M | Browser OAuth optional | +| `xai-grok-announcements` | 0.4k | Release announcements | rho tips/notify | Port | S | | +| `xai-grok-auth` | 0.4k | Auth seam | eyrie + rho auth | Partial | M | Browser OAuth optional | | `xai-token-estimation` | 0.3k | Bytes/4 heuristic | **shrike** | Skip | — | shrike superior | -| `xai-tracing-macros` | 0.2k | Log macros | hawk observability | N/A | XS | | -| `xai-grok-env` | 0.2k | Backend env presets | eyrie/hawk | Partial | XS | | -| `xai-prompt-queue` | 0.2k | Prompt queue types | hawk | Port | S | | +| `xai-tracing-macros` | 0.2k | Log macros | rho observability | N/A | XS | | +| `xai-grok-env` | 0.2k | Backend env presets | eyrie/rho | Partial | XS | | +| `xai-prompt-queue` | 0.2k | Prompt queue types | rho | Port | S | | | `xai-mixpanel` | 0.1k | Mixpanel client | — | **Skip** | — | Privacy; OTEL opt-in | -| `xai-grok-version` | 0.1k | Version | hawk VERSION | Done | — | | +| `xai-grok-version` | 0.1k | Version | rho VERSION | Done | — | | | `xai-grok-models` | 0.1k | Default model IDs | eyrie catalog | Done* | — | | ### 2.2 Common crates (tool protocol / hub / compaction) | Grok crate | ~LOC | Capability | Target | Status | Effort | Notes | |------------|-----:|------------|--------|--------|--------|-------| -| `xai-computer-hub-sdk` | 14k | Remote tool server SDK | new or hawk | Port (Y2+) | XL | Only if multi-host tools wanted | +| `xai-computer-hub-sdk` | 14k | Remote tool server SDK | new or rho | Port (Y2+) | XL | Only if multi-host tools wanted | | `xai-computer-hub-core` | 4.2k | Transport/registry | same | Port (Y2+) | L | | | `xai-computer-hub-mcp-adapter` | 1k | MCP into hub | falcon | Port (Y2+) | M | | -| `xai-tool-protocol` | 6.6k | Wire protocol | contracts + hawk | Port (Y2) | L | | -| `xai-tool-runtime` | 5.4k | Tool trait runtime | hawk tool | Partial | L | | +| `xai-tool-protocol` | 6.6k | Wire protocol | contracts + rho | Port (Y2) | L | | +| `xai-tool-runtime` | 5.4k | Tool trait runtime | rho tool | Partial | L | | | `xai-tool-types` | 3.6k | Spawn/task types | **contracts** | Port | M | **P0** | -| `xai-grok-compaction` | 6.8k | Compaction engine | hawk engine + shrike | Partial | L | | +| `xai-grok-compaction` | 6.8k | Compaction engine | rho engine + shrike | Partial | L | | | `xai-circuit-breaker` | 2.2k | HTTP breaker | eyrie/resilience | Partial | S | | -| `xai-tracing` | 0.8k | Tracing | hawk OTEL | Partial | S | | -| `xai-test-utils` | 0.4k | Hermetic git tests | hawk testutil | Partial | S | | -| `xai-interjection-core` | 0.3k | Interjection messaging | hawk | Port | S | Mid-turn user inject | +| `xai-tracing` | 0.8k | Tracing | rho OTEL | Partial | S | | +| `xai-test-utils` | 0.4k | Hermetic git tests | rho testutil | Partial | S | | +| `xai-interjection-core` | 0.3k | Interjection messaging | rho | Port | S | Mid-turn user inject | | `xai-proto-build` | — | Build tooling | — | Skip | — | Rust build | -\*Done* = domain covered by hawk engine with different API. +\*Done* = domain covered by rho engine with different API. --- @@ -180,7 +180,7 @@ grok-eco/grok-build (one Rust workspace) ### 3.1 Grok Build native tools -| Grok tool | Hawk equivalent | Status | Port work | +| Grok tool | Rho equivalent | Status | Port work | |-----------|-----------------|--------|-----------| | `bash` / `run_terminal_command` | `Bash` | Partial | background flag, timeout, safe-bash, kill | | `read_file` | `Read` | Partial | media/PDF page ranges parity | @@ -210,20 +210,20 @@ grok-eco/grok-build (one Rust workspace) Grok vendors codex/opencode tool implementations for compatibility profiles. -| Compat surface | Hawk | Action | +| Compat surface | Rho | Action | |----------------|------|--------| -| Codex apply_patch / read / list / grep | hawk tools | Optional compat profile | -| OpenCode read/write/edit/bash/glob/grep/todo/skill | hawk | Optional `compat.opencode` | -| Claude import | missing | **Port** into swift/hawk | +| Codex apply_patch / read / list / grep | rho tools | Optional compat profile | +| OpenCode read/write/edit/bash/glob/grep/todo/skill | rho | Optional `compat.opencode` | +| Claude import | missing | **Port** into swift/rho | | Cursor skills scan | missing | **Port** | --- ## 4. Slash commands & UX (pager) -Grok slash modules (port checklist → hawk `/` commands or CLI): +Grok slash modules (port checklist → rho `/` commands or CLI): -| Grok slash | Status in hawk | Action | +| Grok slash | Status in rho | Action | |------------|----------------|--------| | help | Partial | Port completeness | | model / effort | Partial | Port effort levels | @@ -259,17 +259,17 @@ Grok slash modules (port checklist → hawk `/` commands or CLI): --- -## 5. User-guide docs (port as hawk user-guide) +## 5. User-guide docs (port as rho user-guide) | Grok doc | Port to | |----------|---------| -| 01–05 essentials | `hawk/docs/user-guide/` | +| 01–05 essentials | `rho/docs/user-guide/` | | 06 theming | same | | 07 MCP | same + mcp-servers.md merge | | 08 skills | same | | 09 plugins | same | | 10 hooks | same | -| 11 custom models | eyrie + hawk | +| 11 custom models | eyrie + rho | | 12 project rules AGENTS.md | exists Partial | | 13 memory | harrier UX | | 14 headless | CLI flags doc | @@ -292,7 +292,7 @@ Grok slash modules (port checklist → hawk `/` commands or CLI): These are “small” crates/modules but required for **full** port claims: -| Item | Grok home | Hawk action | Effort | +| Item | Grok home | Rho action | Effort | |------|-----------|-------------|--------| | Folder trust | `workspace/folder_trust.rs` | **Port** `internal/trust` | M | | envrc / direnv load | `workspace/envrc.rs` | **Port** | S | @@ -332,7 +332,7 @@ These are “small” crates/modules but required for **full** port claims: | Hyperlink routing | pager | Port | S | | Config TOML live edit | pager | Port | S | | Goal classifier | shell session | Port | S | -| Swift classifier | shell | map to hawk swift | S | +| Swift classifier | shell | map to rho swift | S | | Repo changes tracking | shell session | Partial | M | | Active sessions multi | shell | Partial | M | | MCP doctor | shell mcp_doctor | Port | S | @@ -349,10 +349,10 @@ These are “small” crates/modules but required for **full** port claims: | Quarter | Deliverables | Repos | |---------|--------------|-------| -| **Q1** | Contracts spawn DTOs; wire explore/plan/general; stop hardcoded explore; explore bash hard gate; unify taskruntime; structured SpawnResult | contracts, hawk | -| **Q2** | sandbox.toml; folder trust; safe-bash; permission pipeline hooks-first; PreToolUse deny | hawk | -| **Q3** | File+HTTP hooks; vendor aliases; multi-harness skills; multi-component plugins; marketplace MVP | hawk, community-skills | -| **Q4** | Monitor/Wait/Kill; /loop; structured AskUser; plan/spec alignment; user-guide 01–12; crash handler; announcements; prompt queue; interjection | hawk | +| **Q1** | Contracts spawn DTOs; wire explore/plan/general; stop hardcoded explore; explore bash hard gate; unify taskruntime; structured SpawnResult | contracts, rho | +| **Q2** | sandbox.toml; folder trust; safe-bash; permission pipeline hooks-first; PreToolUse deny | rho | +| **Q3** | File+HTTP hooks; vendor aliases; multi-harness skills; multi-component plugins; marketplace MVP | rho, community-skills | +| **Q4** | Monitor/Wait/Kill; /loop; structured AskUser; plan/spec alignment; user-guide 01–12; crash handler; announcements; prompt queue; interjection | rho | **Exit Year 0:** Contributor-ready “Grok-class agent controls” without claiming full TUI parity. @@ -361,7 +361,7 @@ These are “small” crates/modules but required for **full** port claims: | Quarter | Deliverables | |---------|--------------| | **Q5** | ACP session/load/resume; richer updates; OpenAPI; sdk-go/python fields | -| **Q6** | Managed policy (signed) graycode-cloud → hawk; IT tier; config layer order | +| **Q6** | Managed policy (signed) graycode-cloud → rho; IT tier; config layer order | | **Q7** | Foreign session import (Claude/Codex); envrc; hunk tracker; fast worktree; mermaid render | | **Q8** | Voice streaming upgrade; update checker parity; MCP doctor; slash parity batch 1; user-guide 13–24 | @@ -378,7 +378,7 @@ These are “small” crates/modules but required for **full** port claims: ### Year 3–5 — Continuous parity -- Diff audit bot: scan Grok public releases → open hawk issues +- Diff audit bot: scan Grok public releases → open rho issues - Enterprise: MDM, SCIM (cloud), fleet policies - Full editor suite (VS Code/Zed via ACP) - Hardening, fuzz, SLSA releases @@ -406,11 +406,11 @@ Each pack is a shippable program of work with Definition of Done. - [ ] Tests for aliases and validation - [ ] Version release -**DoD:** hawk can import types; no engine deps. +**DoD:** rho can import types; no engine deps. ### PACK-02: Spawn control plane (6–8 weeks) -**Repo:** `hawk` +**Repo:** `rho` - [ ] Change `AgentSpawnFn` to SpawnRequest/Result - [ ] Agent tool full schema (type, capability, isolation, resume, cwd, model, thoroughness, description, background) @@ -440,7 +440,7 @@ Each pack is a shippable program of work with Definition of Done. - [ ] File discovery paths - [ ] HTTP runner - [ ] Inject into PermissionEngine before autonomy -- [ ] Plugin env HAWK_PLUGIN_ROOT/DATA +- [ ] Plugin env RHO_PLUGIN_ROOT/DATA ### PACK-05: Extensions (8–10 weeks) @@ -484,7 +484,7 @@ Each pack is a shippable program of work with Definition of Done. ### PACK-10: Enterprise policy (6 weeks) - [ ] Signed managed policy schema (cloud) -- [ ] hawk apply layers +- [ ] rho apply layers - [ ] fail-open vs fail-closed - [ ] IT non-excludable tier end-to-end @@ -510,7 +510,7 @@ Each pack is a shippable program of work with Definition of Done. ### PACK-14: TUI parity program (ongoing XL) -Track Grok pager features against hawk Bubble Tea: +Track Grok pager features against rho Bubble Tea: - [ ] Dashboard view parity - [ ] Agents/personas modal @@ -545,7 +545,7 @@ Only if product requires remote tool hosts: ## 9. Per graycode-eco repo full ownership checklist -### `hawk` (majority) +### `rho` (majority) Port/finish: spawn, tools, sandbox, trust, hooks, plugins, marketplace client, TUI, ACP, headless, slash, taskruntime, hunks, mermaid, voice, crash, announcements, queue, interjection, plan, ask user, update, PTY harness, envrc, permissions pipeline, user-guide. @@ -571,7 +571,7 @@ Foreign session import; share/export; session indexing parity with Grok session ### `kestrel` / `merlin` -No direct Grok crates; keep peer engines; ensure hawk composition matches Grok “review/merlin” product moments if any. +No direct Grok crates; keep peer engines; ensure rho composition matches Grok “review/merlin” product moments if any. ### `falcon` @@ -608,7 +608,7 @@ Dashboard/usage/marketplace web UI only. | Replace eyrie with xai-grok-sampler | Multi-provider better | | ratatui widgets | Bubble Tea stack | | `xai-proto-build` | Rust build | -| Closed contribution policy | Hawk is open | +| Closed contribution policy | Rho is open | Skipping is **documented completion**, not unfinished work. @@ -692,7 +692,7 @@ Execute shorter plan as **Year 0** of this master plan. ## 16. One-line summary -**Port all of grok-eco into graycode-eco = reimplement Grok Build’s full capability surface in Go across graycode-eco repos, map each crate to an owner engine, skip vendor/privacy conflicts, wire Hawk’s existing partial systems first, and run a multi-year program ending in behavioral parity—not a Rust code transplant.** +**Port all of grok-eco into graycode-eco = reimplement Grok Build’s full capability surface in Go across graycode-eco repos, map each crate to an owner engine, skip vendor/privacy conflicts, wire Rho’s existing partial systems first, and run a multi-year program ending in behavioral parity—not a Rust code transplant.** --- diff --git a/docs/plans/GROK-CLASS-CAPABILITY-LONG-HORIZON-PLAN.md b/docs/plans/GROK-CLASS-CAPABILITY-LONG-HORIZON-PLAN.md index ba36435b0..2e6f73b3b 100644 --- a/docs/plans/GROK-CLASS-CAPABILITY-LONG-HORIZON-PLAN.md +++ b/docs/plans/GROK-CLASS-CAPABILITY-LONG-HORIZON-PLAN.md @@ -4,8 +4,8 @@ **Date:** 2026-07-16 **Horizon:** ~12–18 months (phased; can compress with parallel teams) **Language:** Go only (no Rust ports; reimplement contracts and behavior) -**Primary product:** `hawk` -**Supporting repositories:** `eagle`, `eyrie`, `harrier` (Harrier), `shrike` (Shrike), `swift` (Swift), `kestrel` (Kestrel), `merlin` (Merlin), `falcon`, `starling`, `sparrow`, `robin`, `wren`, `owl`, and `graycode-platform` (web/BFF/Hawk Cloud; outside the Hawk Go runtime graph) +**Primary product:** `rho` +**Supporting repositories:** `eagle`, `eyrie`, `harrier` (Harrier), `shrike` (Shrike), `swift` (Swift), `kestrel` (Kestrel), `merlin` (Merlin), `falcon`, `starling`, `sparrow`, `robin`, `wren`, `owl`, and `graycode-platform` (web/BFF/Rho Cloud; outside the Rho Go runtime graph) --- @@ -13,21 +13,21 @@ ### Goal -Bring Hawk to **Grok-class agent control-plane quality** (typed subagents, sandbox profiles, folder trust, hooks, plugins/marketplace, ACP depth, monitor/scheduler, enterprise managed policy) **without** abandoning Hawk’s multi-repo Go platform advantages (eyrie multi-provider, harrier graph memory, shrike compression, mission mode, contracts, cloud ledger). +Bring Rho to **Grok-class agent control-plane quality** (typed subagents, sandbox profiles, folder trust, hooks, plugins/marketplace, ACP depth, monitor/scheduler, enterprise managed policy) **without** abandoning Rho’s multi-repo Go platform advantages (eyrie multi-provider, harrier graph memory, shrike compression, mission mode, contracts, cloud ledger). ### Non-goals -- Rewrite Hawk in Rust or monorepo-collapse engines into hawk. +- Rewrite Rho in Rust or monorepo-collapse engines into rho. - Vendor lock-in to a single LLM host auth model. - Replace eyrie/harrier/shrike/swift with Grok-shaped internal crates. - Default opt-in product telemetry that violates privacy-first posture. ### Strategy -1. **Close wiring gaps first** — Hawk already has many *types* and *partial systems* that are not exposed on the Agent tool or unified. +1. **Close wiring gaps first** — Rho already has many *types* and *partial systems* that are not exposed on the Agent tool or unified. 2. **Stabilize contracts** in `eagle` before multi-repo consumers. 3. **Ship vertical slices** (end-to-end user value) every quarter. -4. **Keep engines peer-independent**; hawk remains the only product orchestrator. +4. **Keep engines peer-independent**; rho remains the only product orchestrator. --- @@ -110,7 +110,7 @@ This is the single highest-ROI fix: **wire what already exists**, then extend. ```text ┌──────────────────────────────┐ - │ hawk TUI / CLI / daemon/ACP │ + │ rho TUI / CLI / daemon/ACP │ └──────────────┬───────────────┘ │ ┌─────────────────────────┼─────────────────────────┐ @@ -133,7 +133,7 @@ This is the single highest-ROI fix: **wire what already exists**, then extend. eyrie harrier shrike swift kestrel/merlin ``` -**New core package (proposed):** `hawk/internal/spawn` (or expand `engine/agent`) owning: +**New core package (proposed):** `rho/internal/spawn` (or expand `engine/agent`) owning: - `SpawnRequest` / `SpawnResult` (Go structs aligned with contracts) - capability filter, isolation worktree lifecycle @@ -186,7 +186,7 @@ Legend: **Done** | **Partial** | **Missing** | **N/A (keep engine)** | C2 | Vendor event aliases | Missing | Full | | C3 | File-discovered hooks | Partial (in-process) | Full | | C4 | HTTP hooks | Missing | Full | -| C5 | Plugin env HAWK_PLUGIN_ROOT/DATA | Missing | Full | +| C5 | Plugin env RHO_PLUGIN_ROOT/DATA | Missing | Full | | C6 | PreToolUse can deny in CheckTool path | Missing | Full | ### Wave D — Plugins & marketplace (P3) @@ -261,7 +261,7 @@ Legend: **Done** | **Partial** | **Missing** | **N/A (keep engine)** ## 4. Multi-quarter roadmap (long horizon) -Assumes ~1–2 senior Go engineers on hawk core + fractional engine/cloud; scale parallelizes quarters. +Assumes ~1–2 senior Go engineers on rho core + fractional engine/cloud; scale parallelizes quarters. ```text Q1 Foundation: contracts + spawn wiring + background unify @@ -370,13 +370,13 @@ type SpawnResult struct { **Exit criteria** -- `go test ./...` green; hawk can depend on new pseudo-version without engine imports. +- `go test ./...` green; rho can depend on new pseudo-version without engine imports. --- ### Phase 2 — Spawn controller & Agent tool (4–6 weeks) ★ highest ROI -**Repo:** `hawk` +**Repo:** `rho` **Packages:** `internal/engine/agent`, `internal/engine`, `internal/tool` #### 2.1 Change `AgentSpawnFn` signature @@ -403,8 +403,8 @@ Migration: temporary adapter for one release if needed; prefer single breaking c | 2 | Map `subagent_type` → `SubAgentMode` (+ thoroughness) | | 3 | Default capability from mode; allow override | | 4 | Apply `FilterToolsForMode` **and** capability filter | -| 5 | Isolation worktree: create under `.hawk/worktrees/` using shared helper from mission worker | -| 6 | Persist child session transcript under `~/.hawk/subagents//` | +| 5 | Isolation worktree: create under `.rho/worktrees/` using shared helper from mission worker | +| 6 | Persist child session transcript under `~/.rho/subagents//` | | 7 | `resume_from`: load transcript, append prompt, re-spawn with same type | | 8 | Return structured `SpawnResult` JSON to model | @@ -449,7 +449,7 @@ Deprecate: #### 2.6 PR stack (Graphite-friendly) -1. Contracts bump in hawk +1. Contracts bump in rho 2. `SpawnRequest` internal + adapter 3. Agent tool schema + parse 4. Wire modes plan/general/explore @@ -474,13 +474,13 @@ Deprecate: ### Phase 3 — Sandbox profiles + folder trust (4–5 weeks) -**Repo:** `hawk` +**Repo:** `rho` **Packages:** `internal/sandbox`, `internal/trust` (new), `internal/mcp`, `internal/hooks`, `internal/plugin` #### 3.1 sandbox.toml ```toml -# ~/.hawk/sandbox.toml +# ~/.rho/sandbox.toml [profiles.ci] extends = "strict" restrict_network = true @@ -490,7 +490,7 @@ deny = ["**/.env", "**/*.pem", "**/*credentials*"] **Loader rules (security-critical):** 1. Load user global. -2. Load project `.hawk/sandbox.toml` **additive only** (new names only). +2. Load project `.rho/sandbox.toml` **additive only** (new names only). 3. Warn on conflicting redefinition; ignore project redefinition. 4. Deny globs applied on top of backend (seatbelt/landlock/bwrap-equivalent). 5. Fail closed when deny requested but OS cannot enforce. @@ -508,7 +508,7 @@ deny = ["**/.env", "**/*.pem", "**/*credentials*"] #### 3.3 Folder trust -**Store:** `~/.hawk/trusted_folders.toml` +**Store:** `~/.rho/trusted_folders.toml` ```toml [[folders]] @@ -538,7 +538,7 @@ trusted_at = "2026-07-16T00:00:00Z" ### Phase 4 — Hooks pipeline completion (3–4 weeks) -**Repo:** `hawk` +**Repo:** `rho` **Package:** `internal/hooks` #### 4.1 Event model expansion @@ -550,7 +550,7 @@ Keep existing; alias map for Claude/Cursor names. | Type | Behavior | |------|----------| -| command | shell with timeout, env HAWK_* | +| command | shell with timeout, env RHO_* | | http | POST JSON, timeout, optional deny on non-2xx for pre_tool | #### 4.3 Integration into PermissionEngine @@ -566,8 +566,8 @@ CheckTool: #### 4.4 File discovery -- `~/.hawk/hooks/*.json` -- `.hawk/hooks/*.json` (trust required) +- `~/.rho/hooks/*.json` +- `.rho/hooks/*.json` (trust required) - Compat: `.claude/settings.json` hooks, `.cursor/hooks.json` behind flags **Exit criteria** @@ -582,7 +582,7 @@ CheckTool: ### Phase 5 — Plugins multi-component + marketplace MVP (6–8 weeks) -**Repos:** `hawk`, `starling` +**Repos:** `rho`, `starling` #### 5.1 Plugin layout (convention) @@ -603,19 +603,19 @@ Loader merges components; tools optional. 1. Session meta / SDK 2. `--plugin-dir` -3. Project `.hawk/plugins` (trust) -4. User `~/.hawk/plugins` +3. Project `.rho/plugins` (trust) +4. User `~/.rho/plugins` 5. Config extra paths #### 5.3 Marketplace | Component | Owner | |-----------|--------| -| Source list config | hawk | -| Index schema | community-skills + hawk | -| Install resolve (git) | hawk | -| Audit | hawk plugin malware_check (extend) | -| CLI `hawk plugins` / TUI tab | hawk | +| Source list config | rho | +| Index schema | community-skills + rho | +| Install resolve (git) | rho | +| Audit | rho plugin malware_check (extend) | +| CLI `rho plugins` / TUI tab | rho | | Optional web gallery | graycode-platform later | #### 5.4 Multi-harness skills @@ -661,7 +661,7 @@ Depends on Phase 2 unified `taskruntime`. | Work | Detail | |------|--------| -| Plan subagent | Default tool for “design only”; writes plan file under `.hawk/plans/` or specs | +| Plan subagent | Default tool for “design only”; writes plan file under `.rho/plans/` or specs | | Spec workflow | Document mapping: plan agent → `/spec` stages; avoid double systems | | AskUserQuestion | questions[], options, multi_select, other, cancel message | | TUI | Reuse autonomy/spec pickers | @@ -677,7 +677,7 @@ Depends on Phase 2 unified `taskruntime`. ### Phase 8 — ACP phase-2 + SDKs + OpenAPI (5–7 weeks) -**Repos:** `hawk`, `sparrow`, `robin` +**Repos:** `rho`, `sparrow`, `robin` | Milestone | Scope | |-----------|--------| @@ -686,11 +686,11 @@ Depends on Phase 2 unified `taskruntime`. | ACP-3 | permission round-trip hardened | | API | OpenAPI fields: spawn options, sandbox, autonomy, plugins meta | | SDK | AgentConfig + ChatRequest fields; plugin dirs | -| Optional | `hawk agent serve --bind` WS | +| Optional | `rho agent serve --bind` WS | **Exit criteria** -- Zed/VS Code can drive hawk ACP for multi-turn with permissions. +- Zed/VS Code can drive rho ACP for multi-turn with permissions. - SDK e2e against daemon contract snapshot tests. **Effort:** ~7 eng-weeks (+ extension work separate). @@ -699,13 +699,13 @@ Depends on Phase 2 unified `taskruntime`. ### Phase 9 — Enterprise managed policy (4–6 weeks) -**Repos:** `graycode-platform/apps/worker` (deployed as `graycode-cloud`), `hawk`, optional `eyrie` +**Repos:** `graycode-platform/apps/worker` (deployed as `graycode-cloud`), `rho`, optional `eyrie` | Piece | Detail | |-------|--------| | Policy document | models allow/deny, capabilities, sandbox deny, max budget, tool denylist | -| Signing | Ed25519 envelope; hawk verifies before apply | -| Apply path | `~/.hawk/managed_policy.json` layers under user config | +| Signing | Ed25519 envelope; rho verifies before apply | +| Apply path | `~/.rho/managed_policy.json` layers under user config | | Default | fail-open for individual; org can require fail-closed | | IT tier | non-excludable rules (already sketched in product — finish) | | Cloud UI | graycode-platform admin later | @@ -726,10 +726,10 @@ Build on existing enterprise policyInput (model/capability lists). | Item | Repo | Detail | |------|------|--------| -| Foreign import | swift + hawk CLI | Claude/Codex session metadata → index | -| Hunk attribution | hawk | agent vs external edits via fsnotify | -| Memory UX | harrier + hawk | toggle priority, `/dream` consolidate | -| Mermaid optional | hawk | sandbox render path | +| Foreign import | swift + rho CLI | Claude/Codex session metadata → index | +| Hunk attribution | rho | agent vs external edits via fsnotify | +| Memory UX | harrier + rho | toggle priority, `/dream` consolidate | +| Mermaid optional | rho | sandbox render path | **Exit criteria** @@ -756,20 +756,20 @@ Build on existing enterprise policyInput (model/capability lists). | Concept family | Primary | Secondary | Contracts? | |----------------|---------|-----------|------------| -| Spawn / capability / isolation | hawk | — | yes | -| Sandbox profiles | hawk | — | optional DTO | -| Folder trust | hawk | — | no | -| Hooks | hawk | — | event names yes | -| Plugins / marketplace | hawk | community-skills | manifest schema | -| Multi-harness skills | hawk | community-skills | no | -| Monitor/tasks | hawk | — | optional | -| ACP | hawk | sdk-go/python | OpenAPI | -| Managed policy | graycode-platform/apps/worker (deployed as `graycode-cloud`) | hawk, graycode-platform/apps/bff | yes DTO | -| Foreign import | swift | hawk | optional | -| Memory dream UX | harrier | hawk | no | +| Spawn / capability / isolation | rho | — | yes | +| Sandbox profiles | rho | — | optional DTO | +| Folder trust | rho | — | no | +| Hooks | rho | — | event names yes | +| Plugins / marketplace | rho | community-skills | manifest schema | +| Multi-harness skills | rho | community-skills | no | +| Monitor/tasks | rho | — | optional | +| ACP | rho | sdk-go/python | OpenAPI | +| Managed policy | graycode-platform/apps/worker (deployed as `graycode-cloud`) | rho, graycode-platform/apps/bff | yes DTO | +| Foreign import | swift | rho | optional | +| Memory dream UX | harrier | rho | no | | Token | shrike | — | no change | -| Providers | eyrie | hawk | no change | -| Review engines | kestrel/merlin | hawk | findings already | +| Providers | eyrie | rho | no change | +| Review engines | kestrel/merlin | rho | findings already | --- @@ -835,7 +835,7 @@ Prefer **secure defaults** (folder trust on) even if noisy. | Risk | Impact | Mitigation | |------|--------|------------| -| Signature change of AgentSpawnFn breaks plugins | High | Adapter release; version min_hawk | +| Signature change of AgentSpawnFn breaks plugins | High | Adapter release; version min_rho | | Worktree disk bloat | Med | GC old worktrees; symlink shared dirs (already pattern) | | Explore still escapes via Bash | High | AST allowlist + sandbox | | Marketplace supply chain | High | audit, pin commit SHAs, signatures later | @@ -886,7 +886,7 @@ docs(user-guide): 01-getting-started … | Role | Q1–Q2 | Q3–Q4 | Q5–Q8 | |------|-------|-------|-------| -| Hawk core (Go) | 1.5 FTE | 1.5 FTE | 1 FTE | +| Rho core (Go) | 1.5 FTE | 1.5 FTE | 1 FTE | | Security-minded sandbox | 0.5 FTE | 0.5 FTE | 0.25 FTE | | Community-skills / marketplace content | 0.25 | 0.75 | 0.5 | | Cloud (TS) | 0 | 0.25 | 0.75 | @@ -920,21 +920,21 @@ Background: --- -## 14. Appendix B — Mapping Grok names → Hawk names +## 14. Appendix B — Mapping Grok names → Rho names -| Grok | Hawk | +| Grok | Rho | |------|------| | `task` tool | `Agent` / `Task` alias | | `general-purpose` | `general` / `general-purpose` | | `capability_mode` | same | | `isolation: worktree` | same | | `resume_from` | same (replace weak `agent_id` semantics) | -| `sandbox.toml` | `~/.hawk/sandbox.toml` | -| folder trust | `~/.hawk/trusted_folders.toml` | +| `sandbox.toml` | `~/.rho/sandbox.toml` | +| folder trust | `~/.rho/trusted_folders.toml` | | `/loop` | `/loop` over CronScheduler | -| plugin marketplace | `hawk plugins` + community registry | +| plugin marketplace | `rho plugins` + community registry | | managed_config | managed policy via graycode-cloud | -| ACP | `hawk acp` | +| ACP | `rho acp` | --- diff --git a/docs/plans/SPEC_DRIVEN_PHASE2_PLAN.md b/docs/plans/SPEC_DRIVEN_PHASE2_PLAN.md index 10f6aafa8..29e1dffa3 100644 --- a/docs/plans/SPEC_DRIVEN_PHASE2_PLAN.md +++ b/docs/plans/SPEC_DRIVEN_PHASE2_PLAN.md @@ -69,7 +69,7 @@ **Changes:** 1. Create `internal/tool/spec_version.go` — SpecVersionTool - - Stage .hawk/specs/ changes + - Stage .rho/specs/ changes - Generate commit message referencing REQ IDs - Link code commits to spec requirements diff --git a/docs/plans/YEAR-0-ACTIVE.md b/docs/plans/YEAR-0-ACTIVE.md index 5bc336cd1..1ff11547f 100644 --- a/docs/plans/YEAR-0-ACTIVE.md +++ b/docs/plans/YEAR-0-ACTIVE.md @@ -1,13 +1,13 @@ -# Year 0 Active Track (Grok → Hawk) +# Year 0 Active Track (Grok → Rho) > **Historical.** This track predates the removal of the harrier, kestrel, -> merlin, shrike, and swift engines. Hawk now depends only on `eyrie` and +> merlin, shrike, and swift engines. Rho now depends only on `eyrie` and > embeds its own token engine. Kept for record. **Status:** Superseded **Date:** 2026-07-16 **ADR:** [ADR-0003](../architecture/adr/ADR-0003-grok-behavioral-port-go-multirepo.md) -**Full matrices:** [FULL-GROK-ECO-TO-HAWK-ECO-PORT-PLAN.md](./FULL-GROK-ECO-TO-HAWK-ECO-PORT-PLAN.md), +**Full matrices:** [FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md](./FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md), [GROK-CLASS-CAPABILITY-LONG-HORIZON-PLAN.md](./GROK-CLASS-CAPABILITY-LONG-HORIZON-PLAN.md) This is the **executable Year 0 program**. It does not replace the full @@ -18,7 +18,7 @@ port matrices; it freezes what “Year 0 done” means and tracks pack status. | Do | Do not | |----|--------| | Reimplement Grok **behavior** in Go | Copy Rust crates or depend on Grok | -| Map capabilities to graycode-eco repos | Collapse engines into hawk monorepo | +| Map capabilities to graycode-eco repos | Collapse engines into rho monorepo | | Wire existing modes/budgets first | Rebuild eyrie/harrier/shrike as Grok clones | | Privacy-first telemetry (OTEL opt-in) | Port Mixpanel defaults | @@ -42,13 +42,13 @@ port matrices; it freezes what “Year 0 done” means and tracks pack status. - [x] Model can spawn `explore` \| `plan` \| `general-purpose` via Agent tool schema (isolation=worktree works; resume still stub) - [x] Explore bash cannot mutate (`ExploreBashAllowed` segment allowlist + `ReadOnlyBash` on subagents) - [x] Unified agent taskruntime (`internal/taskruntime`; shell TaskOutput merge is PACK-06) -- [x] Folder trust gates project hooks / plugins (`.hawk/plugins`, `.hawk/hooks`; MCP/LSP follow same AllowLoadPath) +- [x] Folder trust gates project hooks / plugins (`.rho/plugins`, `.rho/hooks`; MCP/LSP follow same AllowLoadPath) - [x] `sandbox.toml` profiles + project additive merge; deny globs fail-closed - [x] PreToolUse hooks can deny inside `PermissionEngine` before autonomy - [x] Multi-component plugins + marketplace MVP install path - [x] Monitor + Wait/Kill + `/loop` tools implemented and working - [~] Crash handler, announcements, prompt queue, interjection — crash handler exists in cmd/errors.go; `/btw` (interjection) implemented; announcements and prompt queue still missing -- [x] User-guide docs `01`–`24` under `hawk/docs/user-guide/` (completed July 2026) +- [x] User-guide docs `01`–`24` under `rho/docs/user-guide/` (completed July 2026) - [x] ADR-0003 published - [x] PACK-00 inventory + flags + spawn matrix template complete @@ -62,9 +62,9 @@ computer hub, full slash/TUI pixel parity, Mixpanel. | Env | Default | Purpose | |-----|---------|---------| -| `HAWK_Y0_SPAWN_V2` | `1` once PACK-02 ships; `0` during dual path | Typed SpawnRequest path | -| `HAWK_Y0_FOLDER_TRUST` | `1` recommended after PACK-03 | Gate project automation | -| `HAWK_Y0_MARKETPLACE` | `0` until PACK-05 + trust | Marketplace install path | +| `RHO_Y0_SPAWN_V2` | `1` once PACK-02 ships; `0` during dual path | Typed SpawnRequest path | +| `RHO_Y0_FOLDER_TRUST` | `1` recommended after PACK-03 | Gate project automation | +| `RHO_Y0_MARKETPLACE` | `0` until PACK-05 + trust | Marketplace install path | Implementation: `internal/flags/y0.go`. diff --git a/docs/plans/codex-adoption-plan.md b/docs/plans/codex-adoption-plan.md index 9e7bb9224..b1f666c60 100644 --- a/docs/plans/codex-adoption-plan.md +++ b/docs/plans/codex-adoption-plan.md @@ -1,6 +1,6 @@ # OpenAI Codex CLI Adoption Plan -Status: Audited. Core ideas already implemented natively in hawk; remaining +Status: Audited. Core ideas already implemented natively in rho; remaining deltas recorded as future RFCs. Source: `https://github.com/openai/codex` (Apache-2.0, Rust workspace @@ -8,9 +8,9 @@ Source: `https://github.com/openai/codex` (Apache-2.0, Rust workspace ## Executive Decision -The audit found that every codex-rs capability relevant to hawk's security and +The audit found that every codex-rs capability relevant to rho's security and runtime model already has a native Go implementation, several of them deeper -than codex's equivalents because they build on Hawk's independent ecosystem +than codex's equivalents because they build on Rho's independent ecosystem repositories. No second runtime, sandbox layer, or policy engine was created. @@ -23,18 +23,18 @@ Three codex ideas are deliberately deferred as future RFCs; see ## Capability Audit -| codex-rs crate/concept | hawk implementation | Decision | +| codex-rs crate/concept | rho implementation | Decision | |---|---|---| -| `core` agent loop | `internal/engine` | Keep hawk | -| `tui`, `ansi-escape`, `terminal-detection` | Bubble Tea/Lipgloss TUI | Keep hawk | -| `rollout`, `thread-store`, `history` JSONL sessions with resume/fork | `internal/session` JSONL + WAL + named checkpoints + fork + recovery + handover | Keep hawk (richer) | -| `app-server-daemon`, `app-server-protocol` (JSON-RPC for IDE/desktop) | `internal/daemon` HTTP/SSE on 4590 + `internal/acp` | Keep hawk | -| `mcp-server`, `codex-mcp`, `rmcp-client`, `connectors` | `internal/mcp` client+server, sibling `falcon` scaffolding | Keep hawk | -| `skills`, `plugin`, `hooks` | community skill registry + structural validator, plugins, expanded lifecycle hook events | Keep hawk | -| `login`, `keyring-store`, `aws-auth` | eyrie credential store in OS keychain across 28 providers | Keep hawk (broader) | -| `model-provider(-info)`, `models-manager`, `ollama`, `lmstudio` | sibling `eyrie` adapters, catalog, cascade routing | Keep hawk (much broader) | -| `memories`, `agent-graph-store`, `context-fragments` | sibling `harrier` (Harrier) graph memory; eventlog/graphjournal projections | Keep hawk | -| `apply-patch`, `file-search`, `file-watcher`, `git-utils` | edit tools, codegraph, git tooling, watcher hooks | Keep hawk | +| `core` agent loop | `internal/engine` | Keep rho | +| `tui`, `ansi-escape`, `terminal-detection` | Bubble Tea/Lipgloss TUI | Keep rho | +| `rollout`, `thread-store`, `history` JSONL sessions with resume/fork | `internal/session` JSONL + WAL + named checkpoints + fork + recovery + handover | Keep rho (richer) | +| `app-server-daemon`, `app-server-protocol` (JSON-RPC for IDE/desktop) | `internal/daemon` HTTP/SSE on 4590 + `internal/acp` | Keep rho | +| `mcp-server`, `codex-mcp`, `rmcp-client`, `connectors` | `internal/mcp` client+server, sibling `falcon` scaffolding | Keep rho | +| `skills`, `plugin`, `hooks` | community skill registry + structural validator, plugins, expanded lifecycle hook events | Keep rho | +| `login`, `keyring-store`, `aws-auth` | eyrie credential store in OS keychain across 28 providers | Keep rho (broader) | +| `model-provider(-info)`, `models-manager`, `ollama`, `lmstudio` | sibling `eyrie` adapters, catalog, cascade routing | Keep rho (much broader) | +| `memories`, `agent-graph-store`, `context-fragments` | sibling `harrier` (Harrier) graph memory; eventlog/graphjournal projections | Keep rho | +| `apply-patch`, `file-search`, `file-watcher`, `git-utils` | edit tools, codegraph, git tooling, watcher hooks | Keep rho | | `external-agent-migration` | swift reads Claude Code / Codex / Gemini CLI / OpenCode / Cursor sessions | Parity | | **`linux-sandbox`** (Landlock + seccomp-bpf) | `internal/sandbox/landlock.go`, `seccomp.go` — raw syscalls and BPF filter, no external tools | Already implemented | | **macOS Seatbelt** | `internal/sandbox/seatbelt.go` — SBPL profile generator with per-policy read/write/process/network rules | Already implemented | @@ -47,7 +47,7 @@ Three codex ideas are deliberately deferred as future RFCs; see ### Adopted in this change -- Status transparency: `hawk status` (text and `--json`) now resolves the +- Status transparency: `rho status` (text and `--json`) now resolves the effective sandbox backend via `sandbox.SelectSandbox` and reports it as `permission.sandbox_backend`, so operators can confirm real kernel-level isolation (seatbelt on macOS, landlock/seccomp on Linux, ACL on Windows, @@ -67,19 +67,19 @@ Three codex ideas are deliberately deferred as future RFCs; see the safe read-only fan-out case. Arbitrary-script execution still requires an embedded runtime, capabilities model, and output-trust threat model; track as a standalone RFC. -- **Agent identity signing** (`agent-identity`): hawk already provides a +- **Agent identity signing** (`agent-identity`): rho already provides a per-harness anonymous user identity (`internal/identity`) and a tamper-evident HMAC-chained security log with session-scoped events (`internal/securitylog`). Signed subagent delegation chains are worth a focused design once multi-org delegation exists. - **Cloud tasks client** (`cloud-tasks*`): remote task queue integration. - Hawk Cloud already provides sync/review surfaces; a queue protocol would + Rho Cloud already provides sync/review surfaces; a queue protocol would duplicate that until a concrete consumer exists. ## Verification - `go test ./...` full suite green. -- `make vet`, `make lint`, `hawk verify` green. +- `make vet`, `make lint`, `rho verify` green. - Repo-owned markdown passes `markdownlint-cli2 '**/*.md'` (CI scope); findings under sibling repositories belong to those repositories and follow their own contribution flow. diff --git a/docs/plans/commandcodeai-adoption-plan.md b/docs/plans/commandcodeai-adoption-plan.md index 07e58ebd7..295866257 100644 --- a/docs/plans/commandcodeai-adoption-plan.md +++ b/docs/plans/commandcodeai-adoption-plan.md @@ -1,6 +1,6 @@ # CommandCodeAI Adoption Plan -Status: Implemented in the Hawk working tree where the existing architecture +Status: Implemented in the Rho working tree where the existing architecture supports a safe, native implementation. ## Source Review @@ -13,7 +13,7 @@ The reviewed CommandCodeAI organization contains four relevant categories: | `cmd-old-public` | Archived placeholder/documentation repository | No implementation to adopt | | `BaseAI` | Archived TypeScript pipe SDK/local provider server; licensing metadata is inconsistent | Reimplement narrow ideas only; do not add as a dependency | | `agent-skills` | MIT skill collection with progressive-disclosure guidance | Adopt authoring/process ideas; preserve individual asset licenses | -| `awesome-agents` | Apache-2.0 example applications | Reference only; do not merge into Hawk skills | +| `awesome-agents` | Apache-2.0 example applications | Reference only; do not merge into Rho skills | `starling` remains the canonical public skill registry. Its validator and registry tooling are more complete than the CommandCodeAI @@ -23,7 +23,7 @@ repositories and should remain authoritative. ### 5. Kimi Code workflow parity -The Kimi Code comparison confirmed that Hawk already provides native equivalents +The Kimi Code comparison confirmed that Rho already provides native equivalents for most of its useful workflow ideas. The remaining gaps were addressed without adding a second agent runtime: @@ -34,20 +34,20 @@ adding a second agent runtime: `UserPromptQueued`, `TurnStarted`, `PostToolFailure`, `PermissionResult`, `SessionHeartbeat`, `TaskStarted`, `StopFailure`, `Interrupt`, and `Notification`. -- Hawk's existing permission engine already supports ordered allow/deny rules +- Rho's existing permission engine already supports ordered allow/deny rules such as `Bash(git status*)` and `Write(*.env)`, pre-tool denial hooks, scoped policy snapshots, and destructive-command hard blocks. -- Hawk's existing goal tracker already provides durable objective state, +- Rho's existing goal tracker already provides durable objective state, dependencies, progress, token budgets, continuation prompts, and lifecycle events. A second `GOAL.md` state machine would duplicate this implementation. The comparison also found no reason to adopt Kimi Code's two-engine split or -replace Hawk's stronger Harrier memory, Shrike token controls, Swift replay, Kestrel +replace Rho's stronger Harrier memory, Shrike token controls, Swift replay, Kestrel review, Merlin auditing, or Eyrie provider runtime. ### 1. Skill metadata interoperability -Hawk's smart-skill parser accepts both hyphenated and community-schema +Rho's smart-skill parser accepts both hyphenated and community-schema snake_case keys: - `auto-invoke` / `auto_invoke` @@ -66,7 +66,7 @@ metadata. ### 2. Local skill validation -Hawk's existing Unicode audit remains the runtime security scanner. A new +Rho's existing Unicode audit remains the runtime security scanner. A new structural validator complements it by checking: - required `name` and `description` @@ -77,13 +77,13 @@ structural validator complements it by checking: - `SKILL.md` size - `@ref(...)` path containment -`hawk skills audit` now reports both Unicode and structural findings. This is a +`rho skills audit` now reports both Unicode and structural findings. This is a small Go-native subset of the community repository's broader validation model; it does not duplicate the registry's Python implementation. ### 3. Transparent preference model -Hawk already has `internal/feature/taste` with confidence, sample count, +Rho already has `internal/feature/taste` with confidence, sample count, decay, project identity, merge, reset, prompt projection, and accept/edit signals. No second preference database was created. The user-facing model and policy are documented in `docs/user-guide/26-learned-preferences.md`: @@ -96,12 +96,12 @@ policy are documented in `docs/user-guide/26-learned-preferences.md`: ### 4. Workflow and harness documentation -CommandCodeAI's strongest product contribution is discoverability. Hawk now +CommandCodeAI's strongest product contribution is discoverability. Rho now documents its existing capabilities in: - `docs/user-guide/26-learned-preferences.md` - `docs/user-guide/27-workflows.md` -- `docs/architecture/hawk-harness.md` +- `docs/architecture/rho-harness.md` - `docs/user-guide/28-workflow-budgets.md` These cover slash/shell/file-context input, headless review, MCP-backed @@ -112,21 +112,21 @@ tool, depth, time, token, and cost budgets. ## Deliberately Not Adopted - CommandCodeAI provider adapters: Eyrie owns provider protocols and routing. -- BaseAI remote pipes: incompatible with Hawk's local authority and durable +- BaseAI remote pipes: incompatible with Rho's local authority and durable event model. -- BaseAI `lowdb` JSON memory: weaker than Harrier and Hawk persistence. +- BaseAI `lowdb` JSON memory: weaker than Harrier and Rho persistence. - Unconditional parallel tool execution: unsafe for mutations and approvals. - Historical `gpt3-agent`: no permissions, sandbox, path guard, audit, or tests. -- Media/UI/status repositories: outside Hawk's code-intelligence boundary. +- Media/UI/status repositories: outside Rho's code-intelligence boundary. - CommandCodeAI branding, proprietary model claims, and undocumented services. ## Verification Plan -1. Run formatting and static checks on all changed Hawk Go files. +1. Run formatting and static checks on all changed Rho Go files. 2. Run focused parser, validator, taste, engine, and command tests. -3. Run the full Hawk test suite and vet. +3. Run the full Rho test suite and vet. 4. Repeat the focused and full checks independently. 5. Merlin the final diff, worktree, and sibling-repository status. The Eyrie repository remains a separate repository change and must be published -through its own feature branch and PR before updating Hawk's module pin. +through its own feature branch and PR before updating Rho's module pin. diff --git a/docs/plans/competitive-gap-01-docker-onboarding.md b/docs/plans/competitive-gap-01-docker-onboarding.md index 2c3f464eb..49ce97900 100644 --- a/docs/plans/competitive-gap-01-docker-onboarding.md +++ b/docs/plans/competitive-gap-01-docker-onboarding.md @@ -7,11 +7,11 @@ Constraint (non-negotiable): mandatory Docker isolation, fail-closed, never host See `docs/SECURITY-DEVELOPER.md:71-73` and `internal/sandbox/container.go:72-74`. This plan adds zero execution paths. It only improves messaging/docs. -## Existing hawk capabilities (verified) +## Existing rho capabilities (verified) -- `hawk path` / `preflight` / `doctor` / `ecosystem` commands (`README.md:346-356`). -- `scripts/verify-developer-path.sh` (`make path`) and `scripts/smoke-hawk.sh` (`make smoke`). -- Sandbox image auto-pull (`graycodeai/hawk-sandbox`) with local Dockerfile build fallback (`docs/SECURITY-DEVELOPER.md:75-79`). +- `rho path` / `preflight` / `doctor` / `ecosystem` commands (`README.md:346-356`). +- `scripts/verify-developer-path.sh` (`make path`) and `scripts/smoke-rho.sh` (`make smoke`). +- Sandbox image auto-pull (`graycodeai/rho-sandbox`) with local Dockerfile build fallback (`docs/SECURITY-DEVELOPER.md:75-79`). ## Decision diff --git a/docs/plans/competitive-gap-02-share-multisession.md b/docs/plans/competitive-gap-02-share-multisession.md index 1e452e5e0..543c1b782 100644 --- a/docs/plans/competitive-gap-02-share-multisession.md +++ b/docs/plans/competitive-gap-02-share-multisession.md @@ -6,9 +6,9 @@ Source: field comparison vs OpenCode share-links/multi-session, herdr multiplexe Constraint: developer-first, local by default, no cloud account required (per `.github/ISSUE_TEMPLATE/feature_request.yml:60-63`). -## Existing hawk capabilities (verified) +## Existing rho capabilities (verified) -- `GenerateShareLink` returns local deeplink `hawk://share/` (`internal/session/export.go:801-819`); deterministic, no hosted URL. +- `GenerateShareLink` returns local deeplink `rho://share/` (`internal/session/export.go:801-819`); deterministic, no hosted URL. - Session export (`session_export.go`), mission graph export (`cmd/execution_graph.go`, `mission-graph.json`), daemon sessions, `mission` worktrees. - Completion list includes `exec`, `daemon`, `mission`, `sessions`, `tools`, `skills` (`cmd/completions_test.go:50`). @@ -21,7 +21,7 @@ Do not adopt: hosted share URLs, cloud account,/Desktop app. ## Priority model - P0: `sessions` list shows id/model/updated + export path; document `graph export` bundle as the share unit. -- P1: TUI session picker surfaces the `hawk://share/` deeplink + export file path for copy-paste. +- P1: TUI session picker surfaces the `rho://share/` deeplink + export file path for copy-paste. - P2: Mission watchdog read-only overview (already in HUD panel) exposed via `mission --dry-run`/status; no new runtime. ## Steps diff --git a/docs/plans/competitive-gap-03-kitty-graphics.md b/docs/plans/competitive-gap-03-kitty-graphics.md index ac8f92dc3..6293be2f3 100644 --- a/docs/plans/competitive-gap-03-kitty-graphics.md +++ b/docs/plans/competitive-gap-03-kitty-graphics.md @@ -3,7 +3,7 @@ Status: Implemented (2026-09-09) Source: `https://github.com/kovidgoyal/kitty` (GPL-3.0; protocol only, no code copy), Ghostty compat; extends `docs/plans/pi-adoption-plan.md:25` (already proposed there — this file scopes the TUI work, it does not re-propose). -## Existing hawk capabilities (verified) +## Existing rho capabilities (verified) - Vision input path: `internal/engine/vision.go`; image command: `cmd/image.go`. - Terminal detection covers kitty/ghostty/wezterm/alacritty names (`internal/ui/icons/detect_test.go:56`). diff --git a/docs/plans/competitive-gap-04-published-benchmarks.md b/docs/plans/competitive-gap-04-published-benchmarks.md index aafb6bbf7..7303588ea 100644 --- a/docs/plans/competitive-gap-04-published-benchmarks.md +++ b/docs/plans/competitive-gap-04-published-benchmarks.md @@ -5,7 +5,7 @@ Source: field comparison vs ripgrep/fzf/alacritty (numbers in README), Aider Rep Constraint: docs-only. No new benchmark framework; infra already exists. -## Existing hawk capabilities (verified) +## Existing rho capabilities (verified) - `make bench` (`go test -bench=. -benchmem -count=3`) in `Makefile:100-101`. - `internal/bench/suite.go` (eco suite runner + report formatter). @@ -21,7 +21,7 @@ Do not adopt: new eval harness, SWE-bench claims, provider-funded comparisons. ## Priority model - P0: fixed command + env (`make bench` subset: session save/load 100/1000, repomap size/tokens) recorded with machine + commit. -- P1: `docs/BENCHMARKS.md` table (TUI-independent, no latency theater): session save/load, repomap tokens, tool-catalog size before/after `HAWK_TOOL_SHRINK=1`. +- P1: `docs/BENCHMARKS.md` table (TUI-independent, no latency theater): session save/load, repomap tokens, tool-catalog size before/after `RHO_TOOL_SHRINK=1`. - P2: CI artifact (optional): nightly `bench` JSON upload; never gate releases on it. ## Steps diff --git a/docs/plans/competitive-gap-05-backend-wiring.md b/docs/plans/competitive-gap-05-backend-wiring.md index d5fd5025a..04f5b2be2 100644 --- a/docs/plans/competitive-gap-05-backend-wiring.md +++ b/docs/plans/competitive-gap-05-backend-wiring.md @@ -5,11 +5,11 @@ Source: field comparison vs Qwen `computer_use`, Codex browser/screenshot, Goose Constraints (non-negotiable): -- Provider ownership lives in `../eyrie`; hawk consumes only the stable engine facade (`AGENTS.md`, `docs/SECURITY-DEVELOPER.md:51-56`). +- Provider ownership lives in `../eyrie`; rho consumes only the stable engine facade (`AGENTS.md`, `docs/SECURITY-DEVELOPER.md:51-56`). - Boundary guards must stay green: `make boundaries` (incl. `eyrie-client-guard`, `eyrie-engine-guard`). - Tools fail safe today with explicit errors when unwired — preserve that behavior when disabled. -## Existing hawk capabilities (verified) +## Existing rho capabilities (verified) - `ComputerUseTool` reports "no computer backend installed" without `SetComputerBackend` (`internal/tool/computer_use.go:67-94`). - `GenerateMediaTool` backend nil by default via `SetMediaEngine` (`internal/tool/media_generation.go:69-71`). @@ -25,13 +25,13 @@ Do not adopt: direct `eyrie/client` production imports, new secrets paths, alway ## Priority model - P0: design note mapping each tool seam → facade method (media/image, STT/audio, computer backend) with guard-safe import path. -- P1: env-gated wiring (e.g. `HAWK_MEDIA=1`) + docs; unwired default error text unchanged. -- P2: `hawk doctor` reports backend status (wired/unwired) without leaking secrets. +- P1: env-gated wiring (e.g. `RHO_MEDIA=1`) + docs; unwired default error text unchanged. +- P2: `rho doctor` reports backend status (wired/unwired) without leaking secrets. ## Steps 1. Confirm facade methods exist in sibling `../eyrie/engine`; if missing, file the change there first (router repo owns providers). -2. Implement wiring in hawk behind env gates; keep `Set*` seams for tests. +2. Implement wiring in rho behind env gates; keep `Set*` seams for tests. 3. Run `make boundaries` + `make vet` + targeted `go test ./internal/tool/ -run 'TestComputer|TestMedia'`. ## Verification diff --git a/docs/plans/dsh-harness-gap-port-plan.md b/docs/plans/dsh-harness-gap-port-plan.md index 60bf5b5a0..81c172863 100644 --- a/docs/plans/dsh-harness-gap-port-plan.md +++ b/docs/plans/dsh-harness-gap-port-plan.md @@ -24,7 +24,7 @@ Same as the parent plan: ## Gap inventory (from the comparison audit) -| # | DSH package(s) | Hawk gap | Port status | +| # | DSH package(s) | Rho gap | Port status | | --- | --- | --- | --- | | 13 | `identity/anonymous-user-id` | no per-home anonymous telemetry identity | **Delivered** | | 14 | `web/web-search-deepseek`, `-exa`, `-perplexity` | web search only has Brave/SearXNG/DDG | **Delivered** | @@ -37,7 +37,7 @@ Same as the parent plan: | 21 | `session/session-persistence-sqlite`, `session-query/*sqlite`, `storage/storage-sqlite` | JSONL-only session persistence | deferred | | 22 | `e2b/*` | no cloud Linux sandbox | deferred | | 23 | `code-runtime/*` | no sandboxed model-written program execution | deferred | -| 24 | `sdk/client`, `sdk/protocol`, `sdk/server`, `python/sdk` | hawk exposes ACP server instead of DSH JSON-RPC SDK | deferred (keep ACP) | +| 24 | `sdk/client`, `sdk/protocol`, `sdk/server`, `python/sdk` | rho exposes ACP server instead of DSH JSON-RPC SDK | deferred (keep ACP) | | 25 | `terminal/*`, `tool-terminal` | no PTY terminal tool | deferred | | 26 | `client/*`, `web/*`, `website`, `bundle/*-web-app` | web UI layer | out of scope (CLI/TUI) | @@ -46,7 +46,7 @@ Same as the parent plan: Port of DSH `identity/anonymous-user-id/src/index.ts`. - `identity.Identity` — per-harness-home anonymous user id. -- Resolved once per process (memoized): `$HAWK_HOME`-style home (`~/.hawk`), +- Resolved once per process (memoized): `$RHO_HOME`-style home (`~/.rho`), `.anonymous-user-id` file containing a bare random UUID. - Never derived from hostname, network address, git remote, or env. - Sync read/write; deleting the file mints a fresh identity. @@ -98,7 +98,7 @@ Port of DSH `attachment/attachment` version-one image path. `attachment.Limits` (maxImageBytes, maxImagesPerMessage, maxMessageImageBytes, maxImagePixels, mediaTypes), `attachment.SaveImage`, `attachment.Stored` (ref + data), `attachment.Store` interface + - filesystem `Store` under the hawk home data dir. + filesystem `Store` under the rho home data dir. - Validation: declared media type checked against decoded bytes; byte and pixel limits enforced; duplicate writes rejected; ID opaque. @@ -118,7 +118,7 @@ Port of DSH `session/session-projection-cache` semantics. ## Phase 18 — OTLP log-record export (`internal/observability/otellog`) Port of DSH `session/session-telemetry-otel` backend semantics (the capture -coordinator stays out of scope — hawk has no Cordis bus; records reach the +coordinator stays out of scope — rho has no Cordis bus; records reach the backend via `Emit`/`EmitFeedback`). - `Record{Channel, Time, Severity, Attributes, Body}` / `Sink` seam — @@ -139,8 +139,8 @@ backend via `Emit`/`EmitFeedback`). - Shutdown races the DSH deadline (default 3s); exporter-shutdown goroutine stays observed after the deadline; `Emit` is a non-blocking enqueue. - `DefaultConfig()` mirrors oteltrace env conventions - (`HAWK_ENABLE_TELEMETRY=1` + `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`); - wired in `cmd/hawk/main.go` and `cmd/daemon.go`. + (`RHO_ENABLE_TELEMETRY=1` + `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`); + wired in `cmd/rho/main.go` and `cmd/daemon.go`. - Tests: mode resolution, load-time validation, severity mapping, full/ops/ ledger emission (in-memory exporter), feedback-only direct-drop, disabled-drop, idempotent shutdown, value conversion, attribute filtering. @@ -148,7 +148,7 @@ backend via `Emit`/`EmitFeedback`). ## Gates Each phase: `gofmt`/`go vet`/`go test` on the touched packages, then `make lint` -and `hawk verify` — run **twice** (the second pass re-runs the full touched +and `rho verify` — run **twice** (the second pass re-runs the full touched suite after any fixes). No direct commits to `main`. ### Verification record (2026-08-17, branch `feat/dsh-harness-port-p0-eventlog`) @@ -162,14 +162,14 @@ Pass 1: - `make lint`: **0 issues** (fixed pre-existing errcheck `check-type-assertions` debt + one unused helper in `internal/session/preparations.go`, and an S1000 single-case select in `internal/jobs/jobs.go`) -- `hawk verify`: exit 0 +- `rho verify`: exit 0 - `scripts/check-internal-layer-imports.sh`: passed Pass 2 (after lint fixes): - `gofmt -l`: clean; `go vet`: clean; `go build ./...`: clean - `go test -race` (identity, jobs, attachment): ok; `go test` (session, tool): ok -- `make lint`: 0 issues; `hawk verify`: exit 0 +- `make lint`: 0 issues; `rho verify`: exit 0 New dependency: `golang.org/x/image v0.45.0` (direct) — webp decode validation for `internal/attachment` (stdlib has no webp decoder). @@ -178,18 +178,18 @@ for `internal/attachment` (stdlib has no webp decoder). Pass 1: -- `gofmt -l` (cmd/hawk/main.go, cmd/daemon.go, internal/observability/otellog): clean +- `gofmt -l` (cmd/rho/main.go, cmd/daemon.go, internal/observability/otellog): clean - `go vet` (otellog, cmd): clean - `go test -race` (otellog): ok; `go build ./...`: ok - `make lint`: **0 issues** -- `hawk verify`: exit 0 +- `rho verify`: exit 0 - `scripts/check-internal-layer-imports.sh`: passed Pass 2 (uncached re-run, no fixes were needed): - `gofmt -l`: clean; `go vet`: clean; `go build ./...`: clean - `go test -count=1 -race` (otellog): ok -- `make lint`: 0 issues; `hawk verify`: exit 0 +- `make lint`: 0 issues; `rho verify`: exit 0 New dependencies (all direct): `go.opentelemetry.io/otel/log v0.20.0`, `go.opentelemetry.io/otel/sdk/log v0.20.0`, diff --git a/docs/plans/dsh-harness-port-plan.md b/docs/plans/dsh-harness-port-plan.md index 5a1c0d47f..8e8b868c1 100644 --- a/docs/plans/dsh-harness-port-plan.md +++ b/docs/plans/dsh-harness-port-plan.md @@ -1,4 +1,4 @@ -# Adopt durable-design ideas from deepseek-harness in hawk +# Adopt durable-design ideas from deepseek-harness in rho Status: Phase 0 scaffolded on `feat/dsh-harness-port-p0-eventlog`. @@ -11,14 +11,14 @@ Status: Phase 0 scaffolded on `feat/dsh-harness-port-p0-eventlog`. ## Reference sources -| dsh file | Extract | Maps to hawk | +| dsh file | Extract | Maps to rho | | --- | --- | --- | | `packages/core/session/src/index.ts` | append-only `SessionEvent` log + `session/event` emit | `internal/eventlog` | | `docs/architecture.md` "Turn flow" | `deriveMessages`; "model-visible ⟺ logged" | `Session.Persistence()` projection | | `packages/core/tools/src/index.ts` | `tools/pre-execute` / `tools/execute` / `tools/post-execute` waterfall | `internal/tool/interceptor.go` (Phase 1) | | `packages/interaction/user-approval/src/index.ts:30` | `approval/request` waterfall, fail-closed | `internal/engine/approval_gate.go` (Phase 1) | | `docs/cordis-primer.md` "Waterfall Semantics" | `next()` delegate / short-circuit | interceptor contract | -| `docs/capability-seams.md` | owner / impl / consumer roles | `docs/architecture/hawk-capability-seams.md` (Phase 2) | +| `docs/capability-seams.md` | owner / impl / consumer roles | `docs/architecture/rho-capability-seams.md` (Phase 2) | ## Phase 0 — Event-sourced session log + "model-visible ⟺ logged" @@ -93,7 +93,7 @@ concrete tool, carry it through `toolExecResult` in a later PR. ## Phase 2 — Seam discipline (docs + disposers) -- `docs/architecture/hawk-capability-seams.md`: owner / impl / consumer table. +- `docs/architecture/rho-capability-seams.md`: owner / impl / consumer table. - Registrations return disposers (tool registry, hooks, MCP client). - "Where new behavior goes" table. @@ -114,16 +114,16 @@ concrete tool, carry it through `toolExecResult` in a later PR. ## Upstream parity matrix (honest status) -These numbers compare Hawk against the real deepseek-harness source, not the +These numbers compare Rho against the real deepseek-harness source, not the plan's own scoped promises. The clone inspected was `deepseek-ai/deepseek-harness` at `dsh-0.1.0-rc.7`. -Hawk is a **deliberate subset**. It ports the session-log spine, the fail-closed +Rho is a **deliberate subset**. It ports the session-log spine, the fail-closed approval waterfall, and the interceptor/disposer seams; it does not port the -product-wide plugin catalogue. Measured head-to-head, Hawk has roughly **20–30%** +product-wide plugin catalogue. Measured head-to-head, Rho has roughly **20–30%** of upstream by feature surface, and roughly **80–90%** of the plan's stated scope. -| Surface | DSH | Hawk today | Gap | +| Surface | DSH | Rho today | Gap | | --- | --- | --- | --- | | Known event types | 44 (dsh known-event-types.ts) | **44** (internal/eventlog/event.go) | **Closed** — all 26 new DSH event types added with typed payloads, wire decode, and Append helpers | | Session spine code | ~3,156 non-test TS lines | ~868 non-test Go lines | Focused core, not full parity | @@ -429,4 +429,4 @@ UI additions (Safari support, outside pointer handling) or ACP content admission ## Gates -Each phase: `make ci` + `hawk verify`; no direct commits to `main`. +Each phase: `make ci` + `rho verify`; no direct commits to `main`. diff --git a/docs/plans/dsh-harness-rfc-port-plan.md b/docs/plans/dsh-harness-rfc-port-plan.md index eb8e7858b..eea4fe805 100644 --- a/docs/plans/dsh-harness-rfc-port-plan.md +++ b/docs/plans/dsh-harness-rfc-port-plan.md @@ -1,4 +1,4 @@ -# Wave 2 — high-value deepseek-harness RFC ports for hawk +# Wave 2 — high-value deepseek-harness RFC ports for rho Status: **Proposed** (RFC specs only; no code on this branch yet). Each phase below is a self-contained feature-branch spec in the style of the Wave 1 docs, ready to @@ -31,7 +31,7 @@ Reference source: `deepseek-ai/deepseek-harness` at `dsh-v0.1.0-rc.7` ## Wave 2 gap inventory -| # | DSH source | Hawk gap | Status | +| # | DSH source | Rho gap | Status | | --- | --- | --- | --- | | 2.1 | `guard/timeout-policy` | no per-tool declared timeout enforced at dispatch | **implemented** on `feat/dsh-harness-rfc-2.1-timeout-policy` | | 2.2 | `compaction/compaction` tool-pairing helpers | compact strategies can cut across open tool call/result pairs | proposed | @@ -59,7 +59,7 @@ Port of DSH `guard/timeout-policy` (`dsh-tool-call-timeout-policy`) + the the tool's own declaration, not from a policy table. - `tool.Tool` gains an optional `TimeoutProvider` interface (`Timeout() - time.Duration`, 0 = no declaration) — the hawk-native form of DSH's + time.Duration`, 0 = no declaration) — the rho-native form of DSH's `ToolDefinition.timeoutMs`, consistent with the existing optional-provider pattern (`RiskLevelProvider`, `RetryPolicyProvider`, `SchemaProvider`). `tool.TimeoutOf(t Tool) time.Duration` reads it. @@ -89,7 +89,7 @@ TOOL_TIMEOUT). ## Phase 2.2 — Compaction tool-pairing boundaries (`internal/engine/compact` + `internal/eventlog`) Port of DSH `compaction/compaction` surface contract + the -`toolPairingBalancedBefore` / `toolPairingBalancedAfter` helpers. Hawk's +`toolPairingBalancedBefore` / `toolPairingBalancedAfter` helpers. Rho's `internal/engine/compact/` already does LLM summarization (`strategy.go`, `micro.go`, `session_memory.go`) and `internal/eventlog` already journals `tool.call` / `tool.result` / `session.compacted` (Wave 1 Phase 0). The @@ -122,7 +122,7 @@ detection and adoption on load; deterministic replay after replace. Port of DSH `sandbox/sandbox-policy` semantics (`SandboxMode` read-only / workspace-write / danger-full-access, `ctx.sandboxPolicy.resolve()`, `setSandboxMode`, the `sandbox:policy` context -contribution). Hawk already has the vocabulary — `internal/sandbox/mode.go` +contribution). Rho already has the vocabulary — `internal/sandbox/mode.go` `Mode` strict / workspace / off — so this phase ports the **policy resolution and durability** around it. @@ -143,7 +143,7 @@ and durability** around it. durable context message on the first request and on each effective policy change; unchanged requests add nothing. `workspace` carries only the canonical workspace path (no host-dependent temp paths — summarize them). Rendered by - the prompt assembler exactly like DSH's three templates, but with hawk's + the prompt assembler exactly like DSH's three templates, but with rho's vocabulary: - `strict` — read-only; do not refuse a required modification from this policy alone; try the tool and follow denial/escalation guidance. @@ -224,7 +224,7 @@ discovery; runtime provider registration/disposal. Revives gap `#21` in **query-only** scope (no migration of the JSONL store; the JSONL file stays the source of truth). Port of DSH -`session-query/session-query-sqlite` semantics over hawk's session logs. +`session-query/session-query-sqlite` semantics over rho's session logs. - New `internal/sessionquery` package: - FTS5 index (external content or contentless) over the session store, @@ -272,9 +272,9 @@ resume of overdue work, no external channel, create/list/delete round-trip. ## Phase 2.8 — ACP client + external-agent subagent providers (`internal/acp` + `internal/multiagent`) Port of DSH `acp/` (client side) + `subagent/subagent-acp`, `subagent-claude-code`, -`subagent-codex`. Hawk has the ACP **server** (Wave 1 `internal/acp/server.go` — +`subagent-codex`. Rho has the ACP **server** (Wave 1 `internal/acp/server.go` — initialize, session/new, session/prompt, streamed updates, -session/request_permission); this phase adds the client so hawk's mission mode +session/request_permission); this phase adds the client so rho's mission mode can **delegate to** other ACP agents, Claude Code, and Codex. - `internal/acp/client.go`: @@ -317,7 +317,7 @@ tool calls and supports interactive stdin. - Local backend over the sandbox executor: PTY allocation with zero-CGO on Linux (`syscall`-based pty or a maintained pure-Go pty; verify `github.com/creack/pty`'s CGO status before adoption — zero-CGO is a hard - hawk constraint); Windows via `ConPTY` later, outside this phase. + rho constraint); Windows via `ConPTY` later, outside this phase. - Sandbox enforcement: the backend applies the resolved Phase 2.3 policy (mode + workspace root) before exec, fail-closed. - Bounded reads: per-read byte cap; no unbounded buffering in model-facing @@ -368,7 +368,7 @@ pre-publication failure. Port of DSH `sandbox/sandbox-windows-acl`. `internal/sandbox/landlock_other.go` is currently a stub on non-Linux (`Apply` always errors; `Available` false), so -on Windows hawk is Docker-only and fails closed without Docker. This phase gives +on Windows rho is Docker-only and fails closed without Docker. This phase gives Windows a native, unprivileged confinement backend matching the Landlock philosophy already stated in `landlock.go` ("works without root, without Docker, without external tools"). @@ -398,7 +398,7 @@ Port of DSH `lsp/lsp-stdio` operational semantics. `internal/lsp/` currently has `client.go`/`manager.go`/`config.go`; this phase audits and aligns the client lifecycle with DSH's: - **Transient-open sequence**: per query — resolve and byte-bound the source - through hawk's fs, `textDocument/didOpen` (version 1, full text), the + through rho's fs, `textDocument/didOpen` (version 1, full text), the requested request, then `textDocument/didClose` in `finally`. No document cache, no LRU, no `didChange`; a failed/canceled `didOpen` write terminates the server instance before the pool can reuse it. @@ -428,7 +428,7 @@ scrubbing (KEY/PASSWORD/SECRET/TOKEN absent from child env); processId null. ## Gates (each phase) `gofmt`/`go vet`/`go test -race` on the touched packages, then `make lint` and -`hawk verify`, run **twice** (second pass re-runs the full touched suite after +`rho verify`, run **twice** (second pass re-runs the full touched suite after any fixes). `scripts/check-internal-layer-imports.sh` must stay green. No direct commits to `main` — each phase is its own feature branch (`feat/dsh-harness-rfc-`), opened from `main`, with a PR and green CI. diff --git a/docs/plans/engine-refactor-plan.md b/docs/plans/engine-refactor-plan.md index f74dec386..ea4631074 100644 --- a/docs/plans/engine-refactor-plan.md +++ b/docs/plans/engine-refactor-plan.md @@ -1,20 +1,20 @@ -# hawk/engine sub-package split — analysis and migration plan +# rho/engine sub-package split — analysis and migration plan > Status: **analysis only**. No code is moved by this document. The actual -> split is multi-PR work that should land incrementally to keep hawk's +> split is multi-PR work that should land incrementally to keep rho's > build green at every step. ## The problem ``` -hawk/engine/ +rho/engine/ ├── *.go 161 source files, 66,682 lines └── *_test.go 141 test files, 65,907 lines ───── total: ~133K lines, 302 files, ONE package ``` For comparison: the entire `kubernetes/kubectl` (a non-trivial CLI) is -~110K lines split across **dozens** of internal packages. hawk's engine +~110K lines split across **dozens** of internal packages. rho's engine is bigger than that and lives in a single `package engine`. Concrete pain that creates today: @@ -40,7 +40,7 @@ splitting into ~15 sub-packages. The line counts are file-name-based estimates; the real numbers will shift during the split. ``` -hawk/engine/ +rho/engine/ ├── engine.go # top-level Engine type, public API only ├── lifecycle.go # session start/stop, hooks, graceful shutdown ├── stream.go # the main response stream loop (large; consider further split) @@ -193,18 +193,18 @@ code from earlier experiments). ## Migration strategy -The split is high-risk because it touches every other package in hawk -that imports `engine.Foo`. To keep hawk green at every commit: +The split is high-risk because it touches every other package in rho +that imports `engine.Foo`. To keep rho green at every commit: 1. **Stage 1 — alias-only.** For each proposed sub-package, create `engine//.go` containing only re-exports: ```go package compact - import "github.com/GrayCodeAI/hawk/engine" + import "github.com/GrayCodeAI/rho/engine" type Strategy = engine.CompactStrategy var Default = engine.DefaultCompactStrategy ``` - Hawk's external callers can start migrating to the new import paths. + Rho's external callers can start migrating to the new import paths. Old code keeps working unchanged. **Land this first.** 2. **Stage 2 — move bodies.** For one sub-package at a time: @@ -223,7 +223,7 @@ that imports `engine.Foo`. To keep hawk green at every commit: coordinator package only. This approach scales: each PR is small, reviewable, and individually -revertible. No "big bang" merge that paralyses hawk for a week. +revertible. No "big bang" merge that paralyses rho for a week. ## Estimated effort @@ -266,7 +266,7 @@ The `compact/` sub-package is the cleanest extraction candidate: | `git/` | `engine/git/aliases.go` | 9 types, 4 funcs | | `prompt/` | `engine/prompt/aliases.go` | 6 types, 3 funcs | -New code should import `github.com/GrayCodeAI/hawk/engine/` +New code should import `github.com/GrayCodeAI/rho/engine/` instead of reaching into `engine` for these names. The remaining 17 clusters follow the same pattern: @@ -274,7 +274,7 @@ clusters follow the same pattern: // engine//aliases.go package -import "github.com/GrayCodeAI/hawk/engine" +import "github.com/GrayCodeAI/rho/engine" // Re-export the cluster's public symbols from engine as type and var // aliases. Implementation stays in engine until Stage 2 of this plan. diff --git a/docs/plans/fix-critical-and-high-review.md b/docs/plans/fix-critical-and-high-review.md index dbf828021..a4aa5e820 100644 --- a/docs/plans/fix-critical-and-high-review.md +++ b/docs/plans/fix-critical-and-high-review.md @@ -1,7 +1,7 @@ -# Plan: Fix Critical + High-Impact Review Findings — hawk +# Plan: Fix Critical + High-Impact Review Findings — rho > Branch: `fix/critical-and-high-review-2026-06` -> PR: +> PR: > Status: **✅ COMPLETE — all 9 items + extensive follow-up committed.** > Constraint: **no new go.mod / go.sum dependencies** for any item in this plan. @@ -77,12 +77,12 @@ These were documented in the original plan as out-of-scope-for-this-PR. They rem ## Context -A deep code review of `eyrie` and `hawk` (companion plan at +A deep code review of `eyrie` and `rho` (companion plan at `../eyrie/docs/plans/fix-critical-and-high-review.md`) surfaced 7 critical -and 9 high items. This plan covers **all hawk items** (C3, C4, C5, H5, +and 9 high items. This plan covers **all rho items** (C3, C4, C5, H5, H6, H7, H8, H9) broken into a sequence of small, reviewable PRs. -## Scope (hawk) +## Scope (rho) | ID | Severity | Title | File(s) | Effort | |----|----------|-------|---------|--------| @@ -99,7 +99,7 @@ H6, H7, H8, H9) broken into a sequence of small, reviewable PRs. - H10 from eyrie: `//nolint:errcheck` on type-assertion that can panic. - M1–M20 medium items. -- L-tier quick wins (e.g. `cosmenticFlags` typo, `cmd/.hawk/` leaked state). +- L-tier quick wins (e.g. `cosmenticFlags` typo, `cmd/.rho/` leaked state). - `internal/intelligence/repomap/` documentation (large, separate effort). - Anything that requires a new dependency. @@ -127,7 +127,7 @@ PRs can be merged individually; the branch is a namespace. ## PR 1 — Surface silent migration error (C4) **What**: `cmd/root.go:114` calls `MigrateProviderSecrets()` and discards -the error. If migration fails, secrets may remain in `~/.hawk/.env` while +the error. If migration fails, secrets may remain in `~/.rho/.env` while the agent is told to ignore that file. **Fix**: @@ -236,7 +236,7 @@ core. Mitigation: keep both code paths behind a feature flag for one release; metric for "wait latency" before/after. **Rollback**: feature flag. If regressions appear, set -`HAWK_MULTIAGENT_POLLING=1` to revert. +`RHO_MULTIAGENT_POLLING=1` to revert. --- @@ -360,7 +360,7 @@ legitimate-input fix, not a security regression. ## PR 7 — Sandbox default-deny (H9) -**Bug**: `internal/sandbox/seatbelt.go:70-108` `DefaultHawkPolicy` defaults +**Bug**: `internal/sandbox/seatbelt.go:70-108` `DefaultRhoPolicy` defaults to `AllowWrite: true` and `AllowProcess: true`. A sandboxed bash can write and spawn processes out of the box. @@ -464,7 +464,7 @@ behavioral changes, only structural. ```bash go mod verify -go build ./cmd/hawk +go build ./cmd/rho go test -race -count=1 -shuffle=on ./... go vet ./... golangci-lint run @@ -492,10 +492,10 @@ Coverage target: maintained at 60%+ (CI gate). ## Cross-repo coordination -- **eyrie PR 4 (C2 — Vertex fix)** and **hawk PR 4 (H6 — Session +- **eyrie PR 4 (C2 — Vertex fix)** and **rho PR 4 (H6 — Session decomposition)** are independent. - **eyrie PR 8 (H4 — EyrieError)** is a prerequisite for any future - hawk-side `errors.As(err, &eyrieErr)` use (currently none). No + rho-side `errors.As(err, &eyrieErr)` use (currently none). No ordering dependency. - The two repos' branches are independent and can be merged in any order. diff --git a/docs/plans/fx-adoption-plan.md b/docs/plans/fx-adoption-plan.md index 7bd70b9bd..9b57a98f3 100644 --- a/docs/plans/fx-adoption-plan.md +++ b/docs/plans/fx-adoption-plan.md @@ -1,18 +1,18 @@ # Vercel fx Adoption Plan -Status: Implemented in Hawk's native Go architecture +Status: Implemented in Rho's native Go architecture Source: `https://github.com/vercel-labs/fx` This plan records the useful ideas identified while comparing Vercel Labs' -`fx` Unix-like coding agent with Hawk and its independent ecosystem repositories. It is an -adoption plan, not a code-porting plan. Hawk should reimplement compatible +`fx` Unix-like coding agent with Rho and its independent ecosystem repositories. It is an +adoption plan, not a code-porting plan. Rho should reimplement compatible behavior in Go and preserve its existing provider, memory, review, audit, session, and security boundaries. ## Executive Decision -Hawk should adopt the following `fx` ideas: +Rho should adopt the following `fx` ideas: 1. Stable identities and operational commands for persisted permission rules. 2. A unified machine-readable runtime status snapshot. @@ -21,21 +21,21 @@ Hawk should adopt the following `fx` ideas: user authority. 5. Compatibility aliases for common permission modes in CLI and ACP surfaces. 6. Stronger subagent lifecycle and permission observability. -7. A narrow embeddable host API over Hawk's existing daemon and ACP surfaces. +7. A narrow embeddable host API over Rho's existing daemon and ACP surfaces. -Hawk should not copy `fx` source code, replace its Go runtime with Zig, create a +Rho should not copy `fx` source code, replace its Go runtime with Zig, create a second permission system, or reduce its code-intelligence tool surface merely to match `fx`'s smaller binary. -## Existing Hawk Capabilities +## Existing Rho Capabilities -The comparison found that Hawk already provides the foundation for most `fx` +The comparison found that Rho already provides the foundation for most `fx` features: -| `fx` capability | Hawk implementation | Current decision | +| `fx` capability | Rho implementation | Current decision | |---|---|---| -| Native single binary | Go static binary and cross-platform release builds | Keep Hawk implementation | -| Agent loop | `internal/engine` | Keep Hawk implementation | +| Native single binary | Go static binary and cross-platform release builds | Keep Rho implementation | +| Agent loop | `internal/engine` | Keep Rho implementation | | Permission and sandbox split | `internal/engine/safety`, `internal/sandbox` | Harden and document | | `ask`/automatic approval behavior | Autonomy profiles, governance, grants, hooks | Add compatibility aliases only | | Child agents | `internal/multiagent`, continuable children, cold resume | Add observability and configuration polish | @@ -45,7 +45,7 @@ features: | ACP | `internal/acp` | Extend status/config parity where useful | | Swift and replay | sibling `swift` (Swift), `internal/session/replay` | Add terminal-level tape capability | | Persistent memory | sibling `harrier` (Harrier) | Do not replace with flat JSON state | -| Provider runtime | sibling `eyrie` | Do not add provider logic to Hawk | +| Provider runtime | sibling `eyrie` | Do not add provider logic to Rho | ## Priority Model @@ -63,7 +63,7 @@ when their original command, path, or workspace has changed. ### Scope and ownership -- Primary implementation: Hawk `internal/permissions` and +- Primary implementation: Rho `internal/permissions` and `internal/engine/safety`. - Shared contract changes, if needed: sibling `eagle/policy`. - User-facing commands: `cmd` and the existing slash-command surface. @@ -88,12 +88,12 @@ when their original command, path, or workspace has changed. ### Proposed interfaces ```text -hawk permissions list [--json] -hawk permissions revoke -hawk permissions reset [--scope user|project] +rho permissions list [--json] +rho permissions revoke +rho permissions reset [--scope user|project] ``` -The exact command names may follow existing Hawk conventions, but the typed +The exact command names may follow existing Rho conventions, but the typed operation should be shared by CLI, TUI, daemon, and ACP. ### Acceptance criteria @@ -155,7 +155,7 @@ the daemon, ACP clients, and the interactive UI. ### Ownership -- Snapshot contract: Hawk root or `eagle` if consumed cross-repo. +- Snapshot contract: Rho root or `eagle` if consumed cross-repo. - Assembly: `internal/engine`, `internal/session`, `internal/mcp`, `internal/permissions`, `internal/multiagent`. - Rendering: CLI/TUI and daemon adapters. @@ -164,7 +164,7 @@ the daemon, ACP clients, and the interactive UI. ```text schema_version -hawk_version +rho_version session_id workspace git_branch @@ -193,8 +193,8 @@ identifiers rather than raw secrets. ### Required interfaces ```text -hawk status -hawk status --json +rho status +rho status --json ``` The daemon and ACP should expose the same snapshot schema, with transport @@ -212,14 +212,14 @@ metadata kept outside the product snapshot. ### Goal -Add `fx`-style terminal byte and resize recording to complement Hawk's existing +Add `fx`-style terminal byte and resize recording to complement Rho's existing agent-session swift and replay features. ### Ownership - Preferred home: sibling `swift` (Swift) if the capability is intended for reuse by other agents. -- Hawk integration: `internal/swift` or the TUI composition layer. +- Rho integration: `internal/swift` or the TUI composition layer. - Do not put terminal tape parsing in the agent engine. ### Required behavior @@ -237,10 +237,10 @@ agent-session swift and replay features. ### Proposed interfaces ```text -hawk swift record --output -hawk swift replay -hawk swift replay --frames -hawk swift replay --json +rho swift record --output +rho swift replay +rho swift replay --frames +rho swift replay --json ``` Existing Swift session capture remains the source of prompts, tool calls, git @@ -260,35 +260,35 @@ ID. ### Goal -Expose familiar `fx` permission names without weakening Hawk's richer policy +Expose familiar `fx` permission names without weakening Rho's richer policy engine. ### Mapping -| Compatibility name | Hawk behavior | +| Compatibility name | Rho behavior | |---|---| | `ask` | Supervised or equivalent prompt-required policy | | `auto` | Automatic review/approval behavior where configured | | `yolo` | Explicit high-autonomy mode, still subject to governance, sandbox, and destructive-command hard blocks | -These are aliases or presentation-layer modes, not a replacement for Hawk's +These are aliases or presentation-layer modes, not a replacement for Rho's autonomy tiers, governance ceiling, spec gates, and sandbox policy. ### Required behavior 1. Accept names in configuration, CLI, and ACP only where the surface supports them. -2. Display the effective Hawk tier and hard safety constraints. +2. Display the effective Rho tier and hard safety constraints. 3. Never let `yolo` bypass governance, hard-deny rules, destructive-command blocks, or mandatory spec approval. -4. Persist the canonical Hawk representation, not an ambiguous alias. +4. Persist the canonical Rho representation, not an ambiguous alias. 5. Add migration and invalid-value diagnostics. ## P1: Subagent Lifecycle and Authority Observability ### Goal -Adopt `fx`'s useful child-agent visibility while retaining Hawk's continuable +Adopt `fx`'s useful child-agent visibility while retaining Rho's continuable child sessions, worktree isolation, model routing, and delegated policy inheritance. @@ -319,7 +319,7 @@ inheritance. ### Goal Improve discoverability and trust reporting using `fx`'s clear status model, -without changing Hawk's existing MCP and skills architecture. +without changing Rho's existing MCP and skills architecture. ### Required behavior @@ -344,7 +344,7 @@ without changing Hawk's existing MCP and skills architecture. ### Goal Offer a supported embedding boundary inspired by `fx`'s ACP and WASM surfaces, -without committing Hawk to a WASM rewrite. +without committing Rho to a WASM rewrite. ### First implementation @@ -417,11 +417,11 @@ make test-race make vet make lint make security -hawk verify +rho verify ``` For sibling-repository changes, run that repository's own tests and boundary checks before -updating the Hawk pointer. Do not modify provider protocols or adapters in Hawk; +updating the Rho pointer. Do not modify provider protocols or adapters in Rho; those belong in the Eyrie repository. ## Delivery Sequence @@ -456,7 +456,7 @@ those belong in the Eyrie repository. - Design and review the versioned swift artifact format in sibling `swift` (Swift). - Implement recording, replay, redaction, and deterministic golden tests. -- Integrate recording controls into Hawk without coupling tape parsing to the +- Integrate recording controls into Rho without coupling tape parsing to the agent loop. ### Milestone 5: Host integration and trust UX @@ -469,18 +469,18 @@ those belong in the Eyrie repository. ## Deliberately Rejected - Copying Zig implementation code from `vercel-labs/fx`. -- Replacing Hawk's Go runtime or independent ecosystem repositories. +- Replacing Rho's Go runtime or independent ecosystem repositories. - Adding a second permission evaluator. - Replacing Harrier with flat JSON memory. - Replacing Swift session capture with terminal tapes. -- Removing Hawk's code intelligence, review, audit, token, or provider features +- Removing Rho's code intelligence, review, audit, token, or provider features to target `fx`'s binary size. - Automatically executing repository-local MCP servers or hooks. - Adding WASM before daemon and ACP contracts are stable and proven. ## Success Criteria -The adoption is successful when Hawk can provide the operational clarity of +The adoption is successful when Rho can provide the operational clarity of `fx` while retaining its stronger ecosystem architecture: - Every persistent permission rule can be listed and revoked by stable ID. @@ -488,7 +488,7 @@ The adoption is successful when Hawk can provide the operational clarity of - TUI failures can be reproduced from a credential-free terminal tape. - Project configuration cannot silently grant private authority. - Child-agent authority, lifecycle, model, and budget state are inspectable. -- Existing Hawk memory, provider, review, audit, MCP, ACP, and session behavior +- Existing Rho memory, provider, review, audit, MCP, ACP, and session behavior remains intact. ## Implemented Scope @@ -504,7 +504,7 @@ The adoption is successful when Hawk can provide the operational clarity of continuable children, and cold resume retained as the authoritative runtime. The remaining items in this document are future refinements to the already -implemented surfaces, not missing baseline features. Hawk's subprocess LSP +implemented surfaces, not missing baseline features. Rho's subprocess LSP manager remains a separate integration task: the current main `LSP` tool uses the codegraph path, while `internal/lsp` provides the richer language-server client and is not yet attached to the primary tool registry. diff --git a/docs/plans/goose-adoption-plan.md b/docs/plans/goose-adoption-plan.md index 6744fb2dc..f0959f353 100644 --- a/docs/plans/goose-adoption-plan.md +++ b/docs/plans/goose-adoption-plan.md @@ -7,23 +7,23 @@ Linux Foundation / Agentic AI Foundation) ## Executive Decision -Goose's audit against hawk found that most of its runtime concepts already have -a native hawk implementation (providers via eyrie, sessions, MCP, ACP, skills, -native sandboxing, permissions). The genuinely novel, hawk-relevant ideas are -adopted here in Go, without copying Rust code or weakening hawk's native +Goose's audit against rho found that most of its runtime concepts already have +a native rho implementation (providers via eyrie, sessions, MCP, ACP, skills, +native sandboxing, permissions). The genuinely novel, rho-relevant ideas are +adopted here in Go, without copying Rust code or weakening rho's native sandboxing model. -## Existing Hawk Capabilities +## Existing Rho Capabilities -| Goose package/concept | Hawk implementation | Decision | +| Goose package/concept | Rho implementation | Decision | |---|---|---| -| Provider abstraction (~36) | sibling `eyrie` (28 built-in + 75+ live) | Keep hawk | -| Sessions (SQLite WAL) | `internal/session` JSONL+WAL+zstd + sibling `swift` (Swift) | Keep hawk | -| MCP (client+server) | `internal/mcp` + sibling `falcon` | Keep hawk | -| ACP | `internal/acp` | Keep hawk | -| Extensions/skills | `internal/plugin`, skills registry | Keep hawk | -| OS sandboxing | `internal/sandbox` seatbelt/landlock/seccomp/ACL | **hawk ahead** (goose has none) | -| OSV malware gate | `internal/permissions/osv_checker.go` (`CheckCommand`/`CheckPackage`) | Keep hawk | +| Provider abstraction (~36) | sibling `eyrie` (28 built-in + 75+ live) | Keep rho | +| Sessions (SQLite WAL) | `internal/session` JSONL+WAL+zstd + sibling `swift` (Swift) | Keep rho | +| MCP (client+server) | `internal/mcp` + sibling `falcon` | Keep rho | +| ACP | `internal/acp` | Keep rho | +| Extensions/skills | `internal/plugin`, skills registry | Keep rho | +| OS sandboxing | `internal/sandbox` seatbelt/landlock/seccomp/ACL | **rho ahead** (goose has none) | +| OSV malware gate | `internal/permissions/osv_checker.go` (`CheckCommand`/`CheckPackage`) | Keep rho | | Hints / AGENTS.md | `internal/config` AGENTS.md loader | **Adopt** @file references + subdir hints | | Context compaction | sibling `shrike` (Shrike) + `internal/engine/compaction` | **Adopt** structured-summary retry ladder | | Extension env safety | none (only OSV gate) | **Adopt** disallowed-env-var filter | @@ -31,7 +31,7 @@ sandboxing model. ## Priority Model -- **P0:** Security-relevant, bounded, hawk-native adoptions. +- **P0:** Security-relevant, bounded, rho-native adoptions. - **P1:** High-value product improvements. - **Defer:** Larger or cross-cutting changes needing an RFC. @@ -114,7 +114,7 @@ strict size/depth budgets — matching goose `hints/import_files.rs`. ### Goal -Upgrade hawk's context compaction to a structured summary with a progressive +Upgrade rho's context compaction to a structured summary with a progressive tool-response-dropping retry ladder on overflow, and token-estimator-backed accounting, matching `goose-context-management`. @@ -142,18 +142,18 @@ accounting, matching `goose-context-management`. ## Deliberately Deferred -- Goose's SQLite session store (`usage_ledger`, token/cost schema): hawk's +- Goose's SQLite session store (`usage_ledger`, token/cost schema): rho's JSONL/WAL + swift + cost tracker cover it; a schema migration is a larger change and is tracked separately. - MCP Apps / agent-provided HTML UIs: novel but requires UI-layer design. - ACP-as-provider wrapping other CLIs: larger provider abstraction change. - Local-inference tool emulation / toolshim: depends on eyrie's local-model path. -- Recipe security scanner / cron recipes: hawk already has schedule/cron. +- Recipe security scanner / cron recipes: rho already has schedule/cron. ## Verification - `go test ./...` full suite. -- `make vet`, `make lint`, `hawk verify`. +- `make vet`, `make lint`, `rho verify`. - Focused tests for the env filter, AGENTS.md references, and compaction retry. - markdownlint on this document. diff --git a/docs/plans/minimax-adoption-plan.md b/docs/plans/minimax-adoption-plan.md index f433c1185..09bbb0624 100644 --- a/docs/plans/minimax-adoption-plan.md +++ b/docs/plans/minimax-adoption-plan.md @@ -1,14 +1,14 @@ -# MiniMax-AI → hawk Adoption Plan +# MiniMax-AI → rho Adoption Plan Status: Implemented in working tree; sibling-repository PRs and skills curation remain follow-ups Date: 2026-08-21 Scope: Adopt the high-value, verified concepts from MiniMax-AI's open-source -repos into the Hawk ecosystem (Hawk plus independent repositories: Eyrie, Eagle, +repos into the Rho ecosystem (Rho plus independent repositories: Eyrie, Eagle, Falcon, Kestrel, and Merlin). ## Findings summary -Deep code review of 6 MiniMax-AI repos against Hawk's existing sibling +Deep code review of 6 MiniMax-AI repos against Rho's existing sibling repositories and internals produced these adoptions, ordered by value: @@ -16,9 +16,9 @@ internals produced these adoptions, ordered by value: |---|---|---|---| | 1 | MiniMax-Provider-Verifier | sibling `eyrie` | Add provider-conformance metrics + wire the orphaned `verify` harness into CI | | 2 | MiniMax/skills | `starling` | Curate high-quality skill content (content, not mechanism) | -| 3 | MiniMax-Coding-Plan-MCP | hawk / `falcon` | MCP v2 no-network test pattern + image-source normalization (patterns only) | -| 4 | minimax_search | hawk tooling | Jina page→Markdown browse extractor + evidence-extraction prompt (pattern) | -| 5 | Mini-Agent | hawk engine | Cooperative-cancellation-with-cleanup + token-aware lossy summarization (patterns) | +| 3 | MiniMax-Coding-Plan-MCP | rho / `falcon` | MCP v2 no-network test pattern + image-source normalization (patterns only) | +| 4 | minimax_search | rho tooling | Jina page→Markdown browse extractor + evidence-extraction prompt (pattern) | +| 5 | Mini-Agent | rho engine | Cooperative-cancellation-with-cleanup + token-aware lossy summarization (patterns) | | 6 | MiniMax-MCP / JS | — | Not adoptable (media generation, out of scope) | ## 1. Eyrie provider-conformance metrics + CI wiring (highest value) @@ -93,17 +93,17 @@ This makes the orphaned harness operational and enables CI gating. ## 2. Curate MiniMax/skills content into starling -### Verified current state (hawk) -- hawk's skills system (`internal/plugin`) reads markdown+YAML-frontmatter skills - from dirs (`~/.hawk/skills`, `.claude/skills`, `.zero/skills`, `skills`) with a - registry (`starling` repo, `hawk skills search/install/list/remove`). +### Verified current state (rho) +- rho's skills system (`internal/plugin`) reads markdown+YAML-frontmatter skills + from dirs (`~/.rho/skills`, `.claude/skills`, `.zero/skills`, `skills`) with a + registry (`starling` repo, `rho skills search/install/list/remove`). - MiniMax/skills (13.4k★) has 18 high-quality skills in the same format (frontmatter `name`/`description`/`license`/`metadata`), especially `frontend-dev`, `fullstack-dev`, `shader-dev`, mobile guides, `vision-analysis`. ### What to implement - Port the best MiniMax skills into `GrayCodeAI/starling` (separate - repo), adapting frontmatter to hawk's convention (`globs`, `alwaysApply`). + repo), adapting frontmatter to rho's convention (`globs`, `alwaysApply`). - This is a content curation task in a separate repo; tracked here for completeness but implemented as a follow-up PR in `starling`. @@ -113,10 +113,10 @@ This makes the orphaned harness operational and enables CI gating. ## 3. MCP v2 no-network test pattern + image-source normalization -### Verified current state (hawk) +### Verified current state (rho) - `internal/mcp` implements its own JSON-RPC client + server and does NOT use the shared `falcon` scaffolding (architectural divergence). -- hawk has `internal/attachment/image.go` for image decode, and `ScreenshotTool` +- rho has `internal/attachment/image.go` for image decode, and `ScreenshotTool` / `BrowserTool`. No image-source (URL/file/data-URL) normalization helper. - MiniMax-Coding-Plan-MCP shows: MCP v2 tool registration + no-network test harness (in-process `Client` + stdio `ClientSession` asserting exact payloads) @@ -135,8 +135,8 @@ This makes the orphaned harness operational and enables CI gating. ## 4. Jina page→Markdown browse extractor -### Verified current state (hawk) -- hawk has `WebSearchTool` (6-provider cascade: Brave/SearXNG/DeepSeek/Exa/ +### Verified current state (rho) +- rho has `WebSearchTool` (6-provider cascade: Brave/SearXNG/DeepSeek/Exa/ Perplexity/DDG), `AgenticFetchTool`, `WebFetchTool`, `DownloadTool`, `engine/search/url_scraper.go`. - No lightweight "URL → Markdown" extractor (Jina Reader pattern). @@ -155,14 +155,14 @@ This makes the orphaned harness operational and enables CI gating. ## 5. Agent-loop robustness patterns (reference) -### Verified current state (hawk) -- hawk's `Session.agentLoop` (`internal/engine/stream.go:133`) is a complete +### Verified current state (rho) +- rho's `Session.agentLoop` (`internal/engine/stream.go:133`) is a complete think→act→observe loop with memory, 70+ tools, sub-agents, multi-agent. - No cooperative-cancellation-with-history-cleanup, and long-session compaction uses truncation rather than "summarize between user turns". ### What to implement (small, low-risk) -- Add `_cleanup_incomplete_messages` equivalent to hawk's loop: on cancellation +- Add `_cleanup_incomplete_messages` equivalent to rho's loop: on cancellation at a safe checkpoint, trim the partial assistant message + orphaned tool results so message history stays valid. - Add a token-limit-driven lossy summarization option (keep user intents, @@ -175,16 +175,16 @@ This makes the orphaned harness operational and enables CI gating. ## Out of scope (not adopted) - MiniMax-MCP / MiniMax-MCP-JS: media generation (TTS/image/video) — no - relevance to code intelligence; hawk has no TTS and that is a product decision. -- minimax_search's search layer: redundant (hawk has more providers). -- Mini-Agent's core loop: outclassed by hawk. + relevance to code intelligence; rho has no TTS and that is a product decision. +- minimax_search's search layer: redundant (rho has more providers). +- Mini-Agent's core loop: outclassed by rho. - The `verify` harness's live-token path: optional, off by default. ## Execution order 1. Eyrie verify metrics + schema validation + tests (highest value) 2. Eyrie verify CLI + Makefile + CI wiring -3. hawk image-source normalization helper + tests -4. hawk Jina browse extractor + tests +3. rho image-source normalization helper + tests +4. rho Jina browse extractor + tests 5. Agent-loop cancellation/summarization patterns 6. starling content curation (follow-up PR in separate repo) @@ -192,10 +192,10 @@ This makes the orphaned harness operational and enables CI gating. - [x] Eyrie schema-accuracy validation and tool-call match-rate metrics. - [x] Eyrie verification CLI, deterministic Makefile target, and CI job. -- [x] Hawk image-source normalization for data URIs, URLs, local files, and raw base64. -- [x] Hawk Jina Reader page-to-Markdown client with opt-in configuration and tests. -- [x] Hawk cancellation cleanup for incomplete assistant tool-use/tool-result turns. -- [x] Confirmed hawk's existing `internal/engine/compact` already provides token-triggered +- [x] Rho image-source normalization for data URIs, URLs, local files, and raw base64. +- [x] Rho Jina Reader page-to-Markdown client with opt-in configuration and tests. +- [x] Rho cancellation cleanup for incomplete assistant tool-use/tool-result turns. +- [x] Confirmed rho's existing `internal/engine/compact` already provides token-triggered compaction; no duplicate summarizer was added. - [ ] Publish the Eyrie repository changes through its own feature branch and PR. - [ ] Curate MiniMax skills into `starling` through its own feature branch and PR. diff --git a/docs/plans/pi-adoption-plan.md b/docs/plans/pi-adoption-plan.md index c1a755bb8..145b4caad 100644 --- a/docs/plans/pi-adoption-plan.md +++ b/docs/plans/pi-adoption-plan.md @@ -4,16 +4,16 @@ Status: Proposed Source: `https://github.com/earendil-works/pi` (MIT, TypeScript/Bun monorepo) -This plan records the features identified as genuinely missing from hawk while -auditing the Pi agent harness against Hawk and its independent ecosystem +This plan records the features identified as genuinely missing from rho while +auditing the Pi agent harness against Rho and its independent ecosystem repositories. It is -an adoption plan, not a code-porting plan: hawk reimplements compatible behavior +an adoption plan, not a code-porting plan: rho reimplements compatible behavior in Go and preserves its existing provider, session, sandbox, permission, observability, and protocol boundaries. ## Executive Decision -Hawk should adopt the following Pi features: +Rho should adopt the following Pi features: 1. A Go telemetry conformance suite that verifies emitted OpenTelemetry spans and attributes match the documented schema. @@ -25,21 +25,21 @@ Hawk should adopt the following Pi features: 5. Kitty graphics protocol support for terminal images. 6. Session lease/ownership semantics in the daemon protocol. -Hawk should not copy Pi's TypeScript code, replace its Go runtime, adopt Pi's +Rho should not copy Pi's TypeScript code, replace its Go runtime, adopt Pi's custom CBOR RPC protocol, or remove its native permission/sandboxing model. -## Existing Hawk Capabilities +## Existing Rho Capabilities -The audit found that hawk already provides the foundation for most Pi features: +The audit found that rho already provides the foundation for most Pi features: -| Pi package | Hawk implementation | Current decision | +| Pi package | Rho implementation | Current decision | |---|---|---| -| `pi-ai` (multi-provider) | sibling `eyrie` client + engine facade + catalog + router + credentials | Keep hawk (broader) | -| `pi-agent-core` (agent loop) | `internal/engine` | Keep hawk | -| `pi-coding-agent` (CLI) | `cmd` TUI + CLI + daemon | Keep hawk | -| `pi-session-backends` (storage) | `internal/session` JSONL + zstd + WAL + SQLite index, sibling `swift` (Swift) | Keep hawk | -| `pi-server` (RPC) | `internal/daemon` + `internal/acp` + `internal/mcp` | Keep hawk (broader) | -| Permissions/sandbox | `internal/engine/safety` + `internal/sandbox` (seatbelt/landlock/seccomp/ACL/netproxy) | Keep hawk (native, ahead) | +| `pi-ai` (multi-provider) | sibling `eyrie` client + engine facade + catalog + router + credentials | Keep rho (broader) | +| `pi-agent-core` (agent loop) | `internal/engine` | Keep rho | +| `pi-coding-agent` (CLI) | `cmd` TUI + CLI + daemon | Keep rho | +| `pi-session-backends` (storage) | `internal/session` JSONL + zstd + WAL + SQLite index, sibling `swift` (Swift) | Keep rho | +| `pi-server` (RPC) | `internal/daemon` + `internal/acp` + `internal/mcp` | Keep rho (broader) | +| Permissions/sandbox | `internal/engine/safety` + `internal/sandbox` (seatbelt/landlock/seccomp/ACL/netproxy) | Keep rho (native, ahead) | | `pi-tui` differential rendering | Bubble Tea v2 full-frame redraw | Adopt line-diff engine | | `pi-telemetry` conformance | `docs/OTEL-CONVENTIONS.md`, eyrie `genai_semconv` pinning | Adopt conformance suite | | `pi-evals` agent-level eval | `internal/feature/eval` (model benchmark only) | Adopt agent-runtime eval | @@ -57,7 +57,7 @@ The audit found that hawk already provides the foundation for most Pi features: Guarantee the emitted OpenTelemetry spans and attributes always match the documented `gen_ai.*` / `cost.usd` / `tool.name` / `session.id` / `agent.id` -contract, so the schema cannot silently drift across Hawk and its independent +contract, so the schema cannot silently drift across Rho and its independent ecosystem repositories. ### Scope and ownership @@ -82,24 +82,24 @@ ecosystem repositories. - asserts sensitive attributes never carry raw prompt/response text, - asserts parent/child span relationships and settlement semantics, - is runner-independent (usable from any Go test harness). -4. Wire the conformance harness into the hawk and eyrie test suites so CI +4. Wire the conformance harness into the rho and eyrie test suites so CI enforces the contract. 5. Keep the conformance layer passive and non-throwing: malformed or unreadable telemetry payloads must not break agent execution. ### Acceptance criteria -- Every span hawk emits is covered by the schema. +- Every span rho emits is covered by the schema. - A deliberate schema drift (adding a span or attribute) fails the conformance test until the schema is updated. - No raw prompt/response text appears in recorded attributes. -- The conformance harness runs green in hawk and eyrie CI. +- The conformance harness runs green in rho and eyrie CI. ## P0: Agent-Runtime Eval Harness ### Goal -Evaluate the full hawk agent end-to-end (real session, tool loop, planning, +Evaluate the full rho agent end-to-end (real session, tool loop, planning, sandbox) against tasks, and snapshot session data as artifacts — not just a model-level benchmark. @@ -273,7 +273,7 @@ Every implementation phase must preserve: - Eval runs the real tool loop end-to-end in an isolated dir. - Daemon and ACP parity for leased sessions. - fxtape recording/replay across the new renderer. -- Cross-repo telemetry conformance in hawk and eyrie CI. +- Cross-repo telemetry conformance in rho and eyrie CI. ### Security tests @@ -291,11 +291,11 @@ make test-race make vet make lint make security -hawk verify +rho verify ``` For sibling-repository changes, run that repository's own tests and boundary checks before -updating the Hawk pointer. +updating the Rho pointer. ## Delivery Sequence @@ -310,7 +310,7 @@ updating the Hawk pointer. - [x] Define the typed span/attribute schema. - [x] Implement the conformance harness. -- [x] Wire hawk and eyrie CI. +- [x] Wire rho and eyrie CI. - [x] Add schema-drift regression tests. ### Milestone 2: Agent-runtime eval (P0) @@ -318,7 +318,7 @@ updating the Hawk pointer. - [x] Implement the loop runner over the real Session/tool loop. - [x] Add isolated execution and session-JSONL artifacts. - [ ] Add comparative and reproducibility reporting. -- [x] Extend `hawk eval` with a loop mode. +- [x] Extend `rho eval` with a loop mode. ### Milestone 3: Differential renderer (P1) @@ -344,19 +344,19 @@ updating the Hawk pointer. ## Deliberately Deferred - Copying Pi's TypeScript code or adopting its custom CBOR RPC protocol in place - of hawk's ACP/MCP/daemon stack. -- Replacing hawk's native permission/sandbox model with Pi's container-only + of rho's ACP/MCP/daemon stack. +- Replacing rho's native permission/sandbox model with Pi's container-only approach. -- Porting Pi's extension system verbatim; hawk's plugin/hook/skills model already +- Porting Pi's extension system verbatim; rho's plugin/hook/skills model already covers it. - Adding a full agent-swarm/graph model beyond current subagent support. ## Success Criteria -The adoption is successful when hawk closes each confirmed gap while retaining +The adoption is successful when rho closes each confirmed gap while retaining its stronger architecture: -- Telemetry spans always conform to the documented schema across hawk and its +- Telemetry spans always conform to the documented schema across rho and its sibling repositories. - The agent can be evaluated end-to-end through its real tool loop. - The TUI re-renders only changed lines with synchronized output. diff --git a/docs/plans/pi-renderer-and-eval-reporting.md b/docs/plans/pi-renderer-and-eval-reporting.md index ab392916d..18ca71492 100644 --- a/docs/plans/pi-renderer-and-eval-reporting.md +++ b/docs/plans/pi-renderer-and-eval-reporting.md @@ -8,7 +8,7 @@ the three merged Pi PRs (#226, #227, #228). ## Executive Decision Two of the three remaining sub-items are **blocked by the rendering stack** and -cannot be adopted cleanly in hawk's current UI framework: +cannot be adopted cleanly in rho's current UI framework: - Bubble Tea v2 exposes **no public `Renderer` interface** (its renderer is an unexported type with internal methods; the only option is `WithoutRenderer`). @@ -28,7 +28,7 @@ reporting** — is fully feasible and safe (non-TUI) and is adopted here. config) so identical runs can be cached and compared. - Add a comparative report across runs/models: pass rate, token/latency/cost deltas, and per-run reproducibility hash. -- Wire it into the existing `evalloop` package and `hawk eval loop` output. +- Wire it into the existing `evalloop` package and `rho eval loop` output. ### Scope and ownership @@ -43,7 +43,7 @@ reporting** — is fully feasible and safe (non-TUI) and is adopted here. result transcript. 3. A `Compare` helper aggregates multiple results and reports pass-rate and per-metric deltas (tokens, cost, duration). -4. `hawk eval loop --report` prints the comparative summary. +4. `rho eval loop --report` prints the comparative summary. ### Acceptance criteria @@ -64,6 +64,6 @@ reporting** — is fully feasible and safe (non-TUI) and is adopted here. ## Verification - `go test ./...` full suite. -- `make vet`, `make lint`, `hawk verify`. +- `make vet`, `make lint`, `rho verify`. - Focused `internal/feature/evalloop` tests. - markdownlint on this document. diff --git a/docs/plans/qwen-code-adoption-plan.md b/docs/plans/qwen-code-adoption-plan.md index 04a02e52c..35452b044 100644 --- a/docs/plans/qwen-code-adoption-plan.md +++ b/docs/plans/qwen-code-adoption-plan.md @@ -1,17 +1,17 @@ # Qwen Code Adoption Plan -Status: Implemented selectively in the current Hawk feature branch. +Status: Implemented selectively in the current Rho feature branch. ## Guardrails -Qwen Code is Apache-2.0 TypeScript software with a Gemini-shaped core. Hawk +Qwen Code is Apache-2.0 TypeScript software with a Gemini-shaped core. Rho will independently reimplement behavioral contracts in Go, preserve the Eyrie -provider boundary, and retain Hawk's event-sourced sessions and OS sandbox. +provider boundary, and retain Rho's event-sourced sessions and OS sandbox. No Qwen source or dependency is vendored. -## Existing Hawk Capabilities +## Existing Rho Capabilities -Hawk already has durable sessions, event logging, WAL/recovery, branching, +Rho already has durable sessions, event logging, WAL/recovery, branching, review contracts, provider routing in Eyrie, MCP integration, skills, memory, context compaction, policy snapshots, subagents, daemon security, and filesystem/process sandboxing. These systems will not be duplicated. @@ -24,7 +24,7 @@ filesystem/process sandboxing. These systems will not be duplicated. 2. Added terminal reasons for permission denial, approval denial, unknown tool, pipeline failure, timeout, cancellation, execution failure, and success. 3. Added regression coverage for the lifecycle contract. -4. Preserved Hawk's existing cancellation transcript cleanup and compaction. +4. Preserved Rho's existing cancellation transcript cleanup and compaction. 5. Preserved policy-snapshot inheritance and subagent cleanup already present. 6. Added explicit `StreamEvent` tool lifecycle state and terminal-reason fields, with execution-path transitions and regression coverage. @@ -47,7 +47,7 @@ filesystem/process sandboxing. These systems will not be duplicated. ### P2: Declarative extensibility -- Map Markdown subagent frontmatter to Hawk's typed SpawnRequest. +- Map Markdown subagent frontmatter to Rho's typed SpawnRequest. - Add path-conditional skill activation and parse-error diagnostics. - Add hot reload with bounded activation listeners. - Scope child hooks and MCP resources by session/agent ID. @@ -61,6 +61,6 @@ filesystem/process sandboxing. These systems will not be duplicated. ## Verification - Focused lifecycle, skills, engine, and command tests. -- Full Hawk build, vet, and test suite. +- Full Rho build, vet, and test suite. - Independent second verification pass. - Final diff and sibling-repository status inspection. diff --git a/docs/plans/hawk-contracts-migration-backlog.md b/docs/plans/rho-contracts-migration-backlog.md similarity index 67% rename from docs/plans/hawk-contracts-migration-backlog.md rename to docs/plans/rho-contracts-migration-backlog.md index 4edbc9e1b..6889ad869 100644 --- a/docs/plans/hawk-contracts-migration-backlog.md +++ b/docs/plans/rho-contracts-migration-backlog.md @@ -1,8 +1,8 @@ -# Plan: Hawk Contracts Migration Backlog +# Plan: Rho Contracts Migration Backlog > Status: locally complete > Scope: graycode-eco ecosystem architecture cleanup after introducing `eagle` -> Goal: keep `hawk` as the product while moving stable cross-repo contracts out of Hawk internals +> Goal: keep `rho` as the product while moving stable cross-repo contracts out of Rho internals External follow-up still outside the scope of this local workspace audit: @@ -22,8 +22,8 @@ These items are already completed in the current workspace. - added `eagle/types` - moved severity and finding definitions into contracts - migrated `kestrel` and `merlin` to import contracts -- switched Hawk `internal/types/severity.go` to re-export from contracts -- removed `hawk/shared/types` after local ecosystem migration +- switched Rho `internal/types/severity.go` to re-export from contracts +- removed `rho/shared/types` after local ecosystem migration - removed the duplicate severity/finding definitions in `shrike/types` (shrike was the original shared-types host) - removed the `shrike/types` compatibility shim after verifying no in-workspace @@ -31,14 +31,14 @@ These items are already completed in the current workspace. ### Tool contract migration - added `eagle/tools` -- switched Hawk session persistence to provider-neutral tool contracts +- switched Rho session persistence to provider-neutral tool contracts - added runtime/session conversion helpers - added `eagle/tools/tool_test.go` -- centralized message slice conversion in `hawk/internal/session` -- removed direct lower-level provider message reconstruction from Hawk +- centralized message slice conversion in `rho/internal/session` +- removed direct lower-level provider message reconstruction from Rho cmd/session restore paths -- replaced the provider-owned message alias with a Hawk-owned runtime DTO -- added explicit Hawk-owned runtime DTOs; the final boundary translates them +- replaced the provider-owned message alias with a Rho-owned runtime DTO +- added explicit Rho-owned runtime DTOs; the final boundary translates them through `eyrie/engine` ### Event contract migration @@ -61,7 +61,7 @@ These items are already completed in the current workspace. - wired the Eyrie engine-boundary guards into `Makefile` and CI - added a legacy import guard so the removed `shared/types` path cannot return - extended the ecosystem boundary guard to scan sibling engine repos when present locally -- updated docs across Hawk, kestrel, merlin, and external workspace copies +- updated docs across Rho, kestrel, merlin, and external workspace copies - added standalone boundary guards in `kestrel` and `merlin` - added standalone boundary guards in `shrike`, `eyrie`, `harrier`, and `swift` - updated support repo READMEs with ecosystem boundary rules @@ -72,8 +72,8 @@ These items are already completed in the current workspace. - added shared review/verification contract tests - added kestrel -> review contract adapters - added merlin -> verify contract adapters -- switched Hawk review persistence to neutral review contracts -- switched Hawk review/merlin bridge paths to return neutral review/verify contracts +- switched Rho review persistence to neutral review contracts +- switched Rho review/merlin bridge paths to return neutral review/verify contracts ## Remaining external follow-up @@ -91,26 +91,26 @@ Still external: ### 2. Confirm release/publication convergence Local state: -- Hawk local integration snapshot points at architecture-aligned support-repo revisions +- Rho local integration snapshot points at architecture-aligned support-repo revisions Still external: -- confirm released module versions used by Hawk match the merged contract changes +- confirm released module versions used by Rho match the merged contract changes -### 3. Remove Hawk production dependency on lower Eyrie packages +### 3. Remove Rho production dependency on lower Eyrie packages Current state: - session persistence uses neutral tool contracts -- Hawk now owns the runtime message DTO in `internal/types.EyrieMessage` -- Hawk now owns runtime tool call/result DTOs in `internal/types` -- Hawk now owns runtime response/usage/stream DTOs in `internal/types` -- Hawk now owns runtime chat options, response format, tool choice, continuation config, and tool definition DTOs in `internal/types` -- Hawk now owns the transport-provider seam in `internal/types.ChatProvider` -- Hawk session, review, setup, catalog, diagnostics, and custom-provider paths +- Rho now owns the runtime message DTO in `internal/types.EyrieMessage` +- Rho now owns runtime tool call/result DTOs in `internal/types` +- Rho now owns runtime response/usage/stream DTOs in `internal/types` +- Rho now owns runtime chat options, response format, tool choice, continuation config, and tool definition DTOs in `internal/types` +- Rho now owns the transport-provider seam in `internal/types.ChatProvider` +- Rho session, review, setup, catalog, diagnostics, and custom-provider paths now enter through `eyrie/engine` - production imports of every lower Eyrie package are zero - cmd/session restore paths now go through centralized `session.ToRuntimeMessages` and `session.FromRuntimeMessages` Decision: -- completed: Hawk owns the product DTO/port seam and Eyrie owns the engine, +- completed: Rho owns the product DTO/port seam and Eyrie owns the engine, provider, credential, catalog, routing, resilience, and normalization layers - keep the zero-exception boundary enforced; lower packages are test-fixture-only @@ -133,7 +133,7 @@ Do this only if another repo truly needs them. Current state: - normalized review result contracts now live in `eagle/review` - normalized verification report contracts now live in `eagle/verify` -- Hawk consumes neutral review/verification contracts at persistence and bridge boundaries +- Rho consumes neutral review/verification contracts at persistence and bridge boundaries Possible later additions: - review lifecycle status enums if another repo needs them @@ -151,12 +151,12 @@ Possible later additions: Do not move every internal event shape by default. -### 6. Remove `hawk/shared/types` +### 6. Remove `rho/shared/types` Completed for the local ecosystem. Current status: -- Hawk no longer ships `hawk/shared/types` +- Rho no longer ships `rho/shared/types` - local import guards prevent the old path from returning ## Non-goals @@ -164,36 +164,36 @@ Current status: Do not do these without a separate decision: - move provider runtime types into contracts -- move Hawk orchestration logic into contracts +- move Rho orchestration logic into contracts - move sandbox manager internals into contracts -- move every event struct in Hawk into contracts -- force Lark/Gitant architecture into Hawk contracts +- move every event struct in Rho into contracts +- force Lark/Gitant architecture into Rho contracts ## Recommended PR order ### PR 1 -- completed: moved chat options/request DTOs behind Hawk-owned runtime adapters +- completed: moved chat options/request DTOs behind Rho-owned runtime adapters ### PR 2 -- completed: moved provider/config interfaces behind Hawk-owned transport adapters +- completed: moved provider/config interfaces behind Rho-owned transport adapters ### PR 3 -- completed: removed all lower-level Eyrie imports from Hawk production code and +- completed: removed all lower-level Eyrie imports from Rho production code and replaced the compatibility allowlist with a zero-exception guard ### PR 4 -- completed: added neutral review/verification result contracts and wired Hawk bridge/persistence edges +- completed: added neutral review/verification result contracts and wired Rho bridge/persistence edges ### PR 5 -- completed: removed `hawk/shared/types` from the local ecosystem and kept a legacy import guard +- completed: removed `rho/shared/types` from the local ecosystem and kept a legacy import guard ## Success criteria The migration is in a good long-term state when: - `eagle` is the only source of truth for shared contracts -- support repos do not import `hawk/internal/*` -- support repos do not import `hawk/shared/types` +- support repos do not import `rho/internal/*` +- support repos do not import `rho/shared/types` - removed compatibility shims do not return - CI prevents regressions - new shared contracts are added deliberately, not by habit diff --git a/docs/plans/rho-tau-alignment.md b/docs/plans/rho-tau-alignment.md new file mode 100644 index 000000000..e38d4492b --- /dev/null +++ b/docs/plans/rho-tau-alignment.md @@ -0,0 +1,72 @@ +# Plan: Rho — Tau-style TUI restyle + architecture tightening + +Status: in progress +Branch: `feat/rename-rho` + +## Context + +Rho (formerly Hawk) is a Go terminal coding agent (Bubble Tea + Lip Gloss, +~17.7k LOC TUI across 107 files, 138 tools, 64 commands). Tau is a small +Python/Textual teaching agent with a strict 3-layer split +(`tau_ai → tau_agent → tau_coding`). + +"Make Rho like Tau" cannot mean a language port or a Textual clone. This plan +does the achievable parts: + +1. **Tau-style visual restyle** — adopt Tau's restrained, low-chrome look in + Rho's existing Bubble Tea TUI. +2. **Tau-style architecture discipline** — make `engine.StreamEvent` the hard + frontend contract; document and enforce the boundary. +3. **Verify twice** — targeted tests + lint, then a second independent pass. + +## Phase 0 — Rename completion (done) + +- [x] Content rename hawk → rho (0 refs remain except intentional shims) +- [x] File/dir renames (`cmd/rho`, `internal/rhoerr`, npm packages) +- [x] Module path `github.com/GrayCodeAI/rho` +- [x] Compatibility shims: `env.GetenvRho`, `storage.resolveAppDir`, + legacy `HAWK_*_DIR` overrides +- [x] Committed + pushed to `feat/rename-rho` + +## Phase 1 — Tau-style theme (visual) + +Tau's palette is a restrained dark theme: near-black panel, single muted +accent, generous dim text, no heavy borders. + +- [ ] Add a `tau` palette to `internal/theme/theme_palettes.go` + (panel `#0d1117`, ink `#e6edf3`, muted `#8b949e`, accent `#7aa2f7`, + subtle line `#21262d`). +- [ ] Register it in `themeRegistry`; make it selectable via `/theme tau`. +- [ ] Reduce chrome: prefer single-line separators over box borders where the + theme is `tau`. +- [ ] Keep every existing palette intact (no regressions). + +## Phase 2 — Tau-style layout (visual) + +- [ ] Flatten the footer: one status line + one input line (Tau shows a + minimal bottom bar, not a multi-row panel). +- [ ] Dim the input border; show the mode as a short prefix rather than a + separate row. +- [ ] Tighten the welcome block: wordmark + one hint line. +- [ ] Preserve all existing information (model, ctx, cost) in the compact row. + +## Phase 3 — Architecture tightening (contract) + +- [ ] Document the frontend contract in `docs/architecture/frontend-contract.md`: + `engine.StreamEvent` is the only channel; TUI/daemon/ACP/print are + consumers and must not reach into engine internals. +- [ ] Add a testaudit guard asserting `cmd/` TUI files do not import + `internal/engine/*` subpackages beyond the public facade types. + +## Phase 4 — Verification (twice) + +- [ ] Pass 1: `gofumpt v0.10.0`, `goimports`, `go build ./...`, `go vet ./...`, + `golangci-lint`, targeted tests for touched packages, boundary guards. +- [ ] Pass 2: independent re-run of the same, plus a fresh-eyes grep for + stale references and a manual TUI render check. + +## Out of scope (explicitly) + +- Rewriting Rho in Python / cloning Textual. +- Deleting Rho's tool/command surface (separate decision). +- Changing the daemon/ACP wire protocols. \ No newline at end of file diff --git a/docs/plans/tau-feature-parity.md b/docs/plans/tau-feature-parity.md new file mode 100644 index 000000000..30d4d3202 --- /dev/null +++ b/docs/plans/tau-feature-parity.md @@ -0,0 +1,54 @@ +# Plan: bring Tau's feature set into Rho (Go) + +Status: in progress +Branch: `feat/rename-rho` + +## Principle + +Tau's features must exist in Rho **without leaving Go**. Rho already has a +superset of Tau's agent capabilities; this plan closes the specific gaps where +Tau offers a documented feature Rho does not. + +## Gap analysis (Tau feature → Rho status) + +| Tau feature | Rho status | Action | +|---|---|---| +| Interactive TUI | ✅ Bubble Tea TUI | none | +| `-p` print mode | ✅ `-p` | none | +| `--cwd ` run against another project | ❌ missing | **add** | +| Built-in tools (read/write/edit/bash) | ✅ 138 tools (superset) | none | +| Durable JSONL sessions + resume + branching | ✅ JSONL + DAG branching | none | +| Slash commands (login, model, sessions, compaction, export, theme) | ⚠️ `/model`, `/compact`, `/export`, `/theme` yes; `/login` no | **add `/login`** | +| `AGENTS.md` project instructions | ✅ | none | +| `.tau/` project resources | ✅ `.rho/` | none | +| `.agents/` shared resources | ⚠️ partial (skills/providers/recipes) | **extend to instructions** | +| User skills, prompt templates, themes | ✅ | none | +| Context accounting + manual/auto compaction | ✅ | none | +| Provider-neutral output: text, JSON, transcript | ⚠️ text + json + stream-json; no transcript | **add transcript** | +| `~/.tau/catalog.toml` custom providers/models, no code changes | ❌ missing | **add `~/.rho/catalog.toml`** | +| `tau update` | ✅ `rho update` | none | +| Installers | ✅ `install.sh` | none | + +## Work items + +1. **`--cwd` global flag** — change directory before session start; applies to + interactive, `-p`, and `exec`. +2. **`/login` slash command** — Tau's auth entry point; route to Rho's existing + credential/config flow (no new secret handling). +3. **`transcript` output format** — add to `--output-format`; render the turn + as a plain, timestamped transcript. +4. **User catalog `~/.rho/catalog.toml`** — let users add providers/models + without code changes, merged over the eyrie catalog. +5. **`.agents/` instruction resources** — load `AGENTS.md`-style instructions + from `.agents/` in addition to the existing skill/provider/recipe dirs. + +## Verification (twice) + +- Pass 1: gofumpt v0.10.0, goimports, `go build ./...`, `go vet ./...`, + golangci-lint, targeted tests per touched package, boundary guards. +- Pass 2: independent re-check of each feature via CLI surface + targeted tests. + +## Out of scope + +- Python/Textual port. Rho stays Go. +- Removing Rho's larger feature surface. \ No newline at end of file diff --git a/docs/plans/toolbench-comparison-vs-top20.md b/docs/plans/toolbench-comparison-vs-top20.md index 5b58ae8ff..0463dce79 100644 --- a/docs/plans/toolbench-comparison-vs-top20.md +++ b/docs/plans/toolbench-comparison-vs-top20.md @@ -1,17 +1,17 @@ -# Toolbench Comparison: hawk vs the Top-20 OSS Coding Agents (2026) +# Toolbench Comparison: rho vs the Top-20 OSS Coding Agents (2026) > Status: analysis complete -> Scope: built-in tool inventory of hawk and the leading open-source coding agents +> Scope: built-in tool inventory of rho and the leading open-source coding agents > Related issue: browser/screenshot automation gap ## Executive summary -hawk ships **69 built-in agent tools** — more than any top-20 OSS rival — and covers every capability category the leader board demands (file I/O, shell, web, search, memory, planning, MCP, sub-agents). The only genuine gaps versus the field are: +rho ships **69 built-in agent tools** — more than any top-20 OSS rival — and covers every capability category the leader board demands (file I/O, shell, web, search, memory, planning, MCP, sub-agents). The only genuine gaps versus the field are: 1. **Browser / computer-use automation** — now closed (see this plan's outcome). -2. **IDE surfaces** — Cline/Continue/Zed are editor-native; hawk remains terminal-first by design. +2. **IDE surfaces** — Cline/Continue/Zed are editor-native; rho remains terminal-first by design. -hawk's tool count exceeds the field (OpenCode ~10, Claude Code 18, Qwen Code ~26, Codex ~9+), but count alone is not the point: hawk matches **Qwen Code** tool-for-tool *and* adds a spec/planning suite, persistent core memory, impact/codegraph/git tools, and an in-tree linter toolchain (`nilaway`, `revive`) that the others leave to MCP. +rho's tool count exceeds the field (OpenCode ~10, Claude Code 18, Qwen Code ~26, Codex ~9+), but count alone is not the point: rho matches **Qwen Code** tool-for-tool *and* adds a spec/planning suite, persistent core memory, impact/codegraph/git tools, and an in-tree linter toolchain (`nilaway`, `revive`) that the others leave to MCP. ## Top-20 OSS coding agents by tool surface (2026 star snapshots) @@ -19,7 +19,7 @@ Star counts are approximate (GitHub, ~July 2026): OpenCode ~172k, Gemini CLI ~99 | Agent | Built-in tools | Categories covered | |---|---|---| -| hawk | **69** (+Power, +MCP) | all | +| rho | **69** (+Power, +MCP) | all | | Qwen Code | 26 + computer_use | all; computer use, cron, worktree | | Claude Code | 18 | all; notebook_edit, skills, hooks | | Kilo Code | ~15 + Playwright MCP | all + browser via MCP | @@ -36,7 +36,7 @@ Star counts are approximate (GitHub, ~July 2026): OpenCode ~172k, Gemini CLI ~99 | Continue | ~10 | editor | | Plandex | ~5 | planning + git | -## hawk's current tool set +## rho's current tool set See `cmd/chat_tools.go` for the registry construction. @@ -48,23 +48,23 @@ Spec/planning suite (`plan`, `spec_*`, `approve_implementation`, `clarify`, `con ## The browser gap and how it was closed -Every browser-native competitor drives a real browser: Cline bundles Puppeteer, Kilo bundles the Playwright MCP server, Codex CLI ships a Chrome extension + in-app browser, Qwen Code exposes `computer_use_*`, OpenManus embeds browser/crawl4ai. hawk previously had only file/shell/web tools. +Every browser-native competitor drives a real browser: Cline bundles Puppeteer, Kilo bundles the Playwright MCP server, Codex CLI ships a Chrome extension + in-app browser, Qwen Code exposes `computer_use_*`, OpenManus embeds browser/crawl4ai. rho previously had only file/shell/web tools. -**Outcome:** added `internal/tool/browser.go` and `internal/tool/screenshot.go`, exposed through hawk's existing `tool.Tool` interface: +**Outcome:** added `internal/tool/browser.go` and `internal/tool/screenshot.go`, exposed through rho's existing `tool.Tool` interface: - `BrowserTool` — headless-Chrome CDP driver with actions `navigate`, `content` (text/HTML, selector-scoped), `screenshot`, `click`, `type` (with clear), `title`, `location`, `close`. Risk level `high`; URL scheme validation restricts to `http(s)` (localhost/LAN included). - `ScreenshotTool` — single-shot full-page PNG capture to a path (defaults to a temp file). -Implementation uses `github.com/chromedp/chromedp` v0.16.0, the canonical pure-Go Chrome DevTools client, and reuses hawk's existing `validatePathAllowed` guard for the output path. A lazily-allocated, mutex-guarded browser process is shared across calls (closed via `Browser … action:"close"` or `releaseBrowser()` in tests). +Implementation uses `github.com/chromedp/chromedp` v0.16.0, the canonical pure-Go Chrome DevTools client, and reuses rho's existing `validatePathAllowed` guard for the output path. A lazily-allocated, mutex-guarded browser process is shared across calls (closed via `Browser … action:"close"` or `releaseBrowser()` in tests). -A live end-to-end test (`TestBrowserLive`, opt-in via `HAWK_LIVE_BROWSER=1`) navigates `example.com`, captures a screenshot, and reads the page title against a real Chrome install — it passes. +A live end-to-end test (`TestBrowserLive`, opt-in via `RHO_LIVE_BROWSER=1`) navigates `example.com`, captures a screenshot, and reads the page title against a real Chrome install — it passes. ## Category parity vs the field -| Category | hawk | Field leaders | +| Category | rho | Field leaders | |---|---|---| -| File ops | read/write/edit/structured/multi/notebook (Go) | Claude Code/Qwen have notebook_edit; hawk has it | -| Search | grep/glob/ls + LSP + code_search/code_graph | OpenCode LSP; hawk code_graph exceeds | +| File ops | read/write/edit/structured/multi/notebook (Go) | Claude Code/Qwen have notebook_edit; rho has it | +| Search | grep/glob/ls + LSP + code_search/code_graph | OpenCode LSP; rho code_graph exceeds | | Shell | bash (+PowerShell on Windows) + monitor/kill/wait | all | | Web | web_fetch/web_search/agentic_fetch/download | all | | Memory | **persistent core_memory_\* (Harrier)** | only Qwen/Claude have persistence | @@ -77,10 +77,10 @@ A live end-to-end test (`TestBrowserLive`, opt-in via `HAWK_LIVE_BROWSER=1`) nav ## Remaining deltas (out of scope here) - VS Code / JetBrains extension (planned in `docs/IMPLEMENTATION-ROADMAP.md`). -- Auto-commit-per-edit discipline à la Aider (hawk uses AGENTS.md branch rules instead). +- Auto-commit-per-edit discipline à la Aider (rho uses AGENTS.md branch rules instead). ## Verification - `go build ./internal/tool/... ./cmd/...` — clean after adding the tools. -- `go test ./internal/tool/` — unit tests pass; live browser test passes with `HAWK_LIVE_BROWSER=1`. +- `go test ./internal/tool/` — unit tests pass; live browser test passes with `RHO_LIVE_BROWSER=1`. - `/tools` REPL command now reports essential/optional breakdown (enhanced in this change; see `cmd/diagnostics.go`). diff --git a/docs/platform-capabilities.json b/docs/platform-capabilities.json index 0d057f61f..792cc49b1 100644 --- a/docs/platform-capabilities.json +++ b/docs/platform-capabilities.json @@ -136,6 +136,6 @@ ] }, "ecosystem_compatibility": { - "hawk+eyrie": "full" + "rho+eyrie": "full" } } diff --git a/docs/plugin-development.md b/docs/plugin-development.md index 4f209f699..99b2d1886 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -2,10 +2,10 @@ ## Overview -Hawk supports three plugin types: +Rho supports three plugin types: 1. **Simple plugins** — shell scripts with a `plugin.json` manifest. Tools are executed as one-shot subprocesses via stdin/stdout. -2. **Managed subprocess plugins** — binary executables managed by `PluginManager`. Supports security scanning, timeout enforcement, and auto-discovery from `~/.hawk/plugins/`. +2. **Managed subprocess plugins** — binary executables managed by `PluginManager`. Supports security scanning, timeout enforcement, and auto-discovery from `~/.rho/plugins/`. 3. **Daemon plugins** — long-lived processes communicating via JSON-RPC 2.0 over stdin/stdout. Managed by `DynamicPluginManager` with full lifecycle (Discover → Load → Activate → Failed/Disabled). ## Plugin Manifest @@ -51,7 +51,7 @@ For hooks, daemon mode, dependencies, and configuration: #### Hook priority -Hooks can specify `priority` (higher runs first). Event data is passed as environment variables (`HAWK_EVENT=file.write`, `HAWK_FILE_PATH=/path/to/file`). +Hooks can specify `priority` (higher runs first). Event data is passed as environment variables (`RHO_EVENT=file.write`, `RHO_FILE_PATH=/path/to/file`). #### Daemon mode @@ -59,7 +59,7 @@ Set `"mode": "daemon"` with `"entrypoint": "./my-daemon"`. The daemon receives J ## Skills -Skills are Markdown files with YAML frontmatter placed in `.hawk/skills//SKILL.md`: +Skills are Markdown files with YAML frontmatter placed in `.rho/skills//SKILL.md`: ```yaml --- @@ -81,7 +81,7 @@ Skill instructions here using markdown... - **Auto-invoke**: Skills with `auto-invoke: true` and matching path/context globs are automatically activated. - **Chaining**: Skills can declare `chain.before` and `chain.after` for ordered execution. - **References**: Use `@ref(path/to/doc.md)` to reference supporting documents within a skill. -- **Cross-agent compatibility**: Skills follow the Agent Skills spec (agentskills.io) and work with hawk, Claude Code, and Codex. +- **Cross-agent compatibility**: Skills follow the Agent Skills spec (agentskills.io) and work with rho, Claude Code, and Codex. ## Event Hooks @@ -89,27 +89,27 @@ Available events for plugin and skill hooks: | Event | Data | Description | |-------|------|-------------| -| `session.start` | `HAWK_SESSION_ID` | Session began | -| `session.end` | `HAWK_SESSION_ID`, `HAWK_DURATION_MS` | Session ended | -| `turn.start` | `HAWK_TURN_NUM` | Agent turn started | -| `turn.end` | `HAWK_TURN_NUM`, `HAWK_TOOL_COUNT` | Agent turn ended | -| `tool_call.start` | `HAWK_TOOL_NAME`, `HAWK_TOOL_INPUT` | Tool execution started | -| `tool_call.end` | `HAWK_TOOL_NAME`, `HAWK_TOOL_RESULT` | Tool execution finished | -| `tool_call.error` | `HAWK_TOOL_NAME`, `HAWK_ERROR` | Tool execution failed | -| `file.read` | `HAWK_FILE_PATH` | File was read | -| `file.write` | `HAWK_FILE_PATH` | File was written | -| `file.edit` | `HAWK_FILE_PATH` | File was edited | -| `file.delete` | `HAWK_FILE_PATH` | File was deleted | -| `compaction.start` | `HAWK_COMPACTION_REASON` | Context compaction began | -| `compaction.end` | `HAWK_COMPACTION_NEW_LENGTH` | Context compaction finished | -| `budget.warning` | `HAWK_BUDGET_USAGE_PCT` | Token/cost budget warning | -| `budget.exceeded` | `HAWK_BUDGET_TYPE` | Budget exceeded (hard stop) | -| `error.occurred` | `HAWK_ERROR_TYPE` | Error occurred | -| `error.recovered` | `HAWK_RECOVERY_STRATEGY` | Error was recovered | -| `model.switch` | `HAWK_NEW_MODEL` | Active model changed | -| `provider.switch` | `HAWK_NEW_PROVIDER` | Active provider changed | -| `user.input` | `HAWK_USER_MESSAGE` | User sent a message | -| `agent.response` | `HAWK_RESPONSE_LENGTH` | Agent generated a response | +| `session.start` | `RHO_SESSION_ID` | Session began | +| `session.end` | `RHO_SESSION_ID`, `RHO_DURATION_MS` | Session ended | +| `turn.start` | `RHO_TURN_NUM` | Agent turn started | +| `turn.end` | `RHO_TURN_NUM`, `RHO_TOOL_COUNT` | Agent turn ended | +| `tool_call.start` | `RHO_TOOL_NAME`, `RHO_TOOL_INPUT` | Tool execution started | +| `tool_call.end` | `RHO_TOOL_NAME`, `RHO_TOOL_RESULT` | Tool execution finished | +| `tool_call.error` | `RHO_TOOL_NAME`, `RHO_ERROR` | Tool execution failed | +| `file.read` | `RHO_FILE_PATH` | File was read | +| `file.write` | `RHO_FILE_PATH` | File was written | +| `file.edit` | `RHO_FILE_PATH` | File was edited | +| `file.delete` | `RHO_FILE_PATH` | File was deleted | +| `compaction.start` | `RHO_COMPACTION_REASON` | Context compaction began | +| `compaction.end` | `RHO_COMPACTION_NEW_LENGTH` | Context compaction finished | +| `budget.warning` | `RHO_BUDGET_USAGE_PCT` | Token/cost budget warning | +| `budget.exceeded` | `RHO_BUDGET_TYPE` | Budget exceeded (hard stop) | +| `error.occurred` | `RHO_ERROR_TYPE` | Error occurred | +| `error.recovered` | `RHO_RECOVERY_STRATEGY` | Error was recovered | +| `model.switch` | `RHO_NEW_MODEL` | Active model changed | +| `provider.switch` | `RHO_NEW_PROVIDER` | Active provider changed | +| `user.input` | `RHO_USER_MESSAGE` | User sent a message | +| `agent.response` | `RHO_RESPONSE_LENGTH` | Agent generated a response | ## Security @@ -123,16 +123,16 @@ All plugins are scanned on install for: 1. Push your plugin to a public GitHub repository. 2. The registry index (maintained at `starling/registry.json`) is periodically refreshed. -3. Users discover plugins via `hawk plugin search `. -4. Users install via `hawk plugin install `. +3. Users discover plugins via `rho plugin search `. +4. Users install via `rho plugin install `. 5. Registry installation includes automatic audit and security scanning. ## Local Development ```bash # Create a plugin directory -mkdir -p ~/.hawk/plugins/my-plugin -cd ~/.hawk/plugins/my-plugin +mkdir -p ~/.rho/plugins/my-plugin +cd ~/.rho/plugins/my-plugin # Create a manifest cat > plugin.json << 'EOF' @@ -147,14 +147,14 @@ cat > plugin.json << 'EOF' EOF # List installed plugins -hawk plugin list +rho plugin list # Run a plugin command -hawk plugin exec my-plugin hello +rho plugin exec my-plugin hello # Create a skill (project-local) -mkdir -p .hawk/skills/my-skill -cat > .hawk/skills/my-skill/SKILL.md << 'EOF' +mkdir -p .rho/skills/my-skill +cat > .rho/skills/my-skill/SKILL.md << 'EOF' --- name: my-skill description: My first skill @@ -165,4 +165,4 @@ EOF ## Feedback and Learning -Users can rate skills (1–5 stars) using `/skill rate `. Ratings are stored in `~/.hawk/feedback.json` and used by the `/learn` command to recommend skills based on project signals. +Users can rate skills (1–5 stars) using `/skill rate `. Ratings are stored in `~/.rho/feedback.json` and used by the `/learn` command to recommend skills based on project signals. diff --git a/docs/session-decomposition.md b/docs/session-decomposition.md index 62546677f..9be9d48c6 100644 --- a/docs/session-decomposition.md +++ b/docs/session-decomposition.md @@ -6,11 +6,11 @@ > into their owning services. > Author: opencode session > Date: 2026-06-12 -> Scope: `hawk/internal/engine/session.go` (the 35-collaborator `Session` struct) +> Scope: `rho/internal/engine/session.go` (the 35-collaborator `Session` struct) ## Problem -`Session` at `hawk/internal/engine/session.go:42-141` is a god object: +`Session` at `rho/internal/engine/session.go:42-141` is a god object: - 35 fields, ~30 of them `*T` pointer collaborators with optional behavior - Every new feature adds a field (Beliefs, Critic, Trajectory, Steer­ing, etc.) @@ -90,7 +90,7 @@ backends without an explicit migration and recovery decision. - `RecordSuccess(latency time.Duration)` / `RecordFailure(err error)` - `BuildContinuationConfig() types.ContinuationConfig` -**File:** `hawk/internal/engine/chat_service.go` (~150 LOC) +**File:** `rho/internal/engine/chat_service.go` (~150 LOC) ### 2. `MemoryService` — owns memory recall/remember @@ -104,7 +104,7 @@ backends without an explicit migration and recovery decision. - `RecordFeedback(ctx, content string, action string)` — wraps EnhancedMemory feedback - `ShouldRemember(text string) bool` — heuristic for auto-remember -**File:** `hawk/internal/engine/memory_service.go` (~200 LOC) +**File:** `rho/internal/engine/memory_service.go` (~200 LOC) ### 3. `ToolService` — owns the registry + tool execution @@ -117,7 +117,7 @@ backends without an explicit migration and recovery decision. - `SpawnBackgroundAgent(ctx, prompt string) (id string, err error)` - `PollBackgroundAgent(id string) (output string, done bool, err error)` -**File:** `hawk/internal/engine/tool_service.go` (~400 LOC — biggest, owns the 15-stage pipeline) +**File:** `rho/internal/engine/tool_service.go` (~400 LOC — biggest, owns the 15-stage pipeline) ### 4. `PermissionService` — owns the safety layer @@ -131,7 +131,7 @@ backends without an explicit migration and recovery decision. - `SetMode(mode string) error` — validates + applies - `SetMaxTurns(n int)`, `SetMaxBudgetUSD(usd float64)` -**File:** `hawk/internal/engine/permission_service.go` (~150 LOC) +**File:** `rho/internal/engine/permission_service.go` (~150 LOC) ### 5. `LifecycleService` — owns the self-improvement loop @@ -148,7 +148,7 @@ backends without an explicit migration and recovery decision. - `DetectDoomLoop(steps []ToolStep) bool` - `InjectSteering(messages []types.EyrieMessage) []types.EyrieMessage` -**File:** `hawk/internal/engine/lifecycle_service.go` (~250 LOC) +**File:** `rho/internal/engine/lifecycle_service.go` (~250 LOC) ### 6. `PersistenceService` — owns checkpoint and session state @@ -167,7 +167,7 @@ backends without an explicit migration and recovery decision. - `CheckpointOnCompaction(strategy string, before, after int, manual bool)` - `TokenTracking() (prompt, completion int)`, `RecordAPIUsage(prompt, completion int)` -**File:** `hawk/internal/engine/persistence_service.go` (~300 LOC) +**File:** `rho/internal/engine/persistence_service.go` (~300 LOC) ## What stays on `Session` diff --git a/docs/terminal-icons.md b/docs/terminal-icons.md index 24be39fad..4b76ab924 100644 --- a/docs/terminal-icons.md +++ b/docs/terminal-icons.md @@ -1,6 +1,6 @@ # Terminal icons -Hawk uses current Nerd Font Codicon glyphs for interactive terminal output. +Rho uses current Nerd Font Codicon glyphs for interactive terminal output. The application does not try to infer the installed font from `TERM`: terminal names do not report the active font, and guessing can produce tiny fallback boxes or missing glyphs. @@ -10,14 +10,14 @@ Interactive TTYs use Nerd Font icons by default. Captured output, CI, and ```bash # Real icons (requires a Nerd Font configured in the terminal profile) -HAWK_ICONS=nerd ./bin/hawk +RHO_ICONS=nerd ./bin/rho # Portable text-only output -HAWK_ICONS=ascii ./bin/hawk +RHO_ICONS=ascii ./bin/rho ``` -For the real icons, configure the terminal profile—not Hawk's Go code—with a +For the real icons, configure the terminal profile—not Rho's Go code—with a patched font such as `JetBrainsMono Nerd Font` or `Symbols Nerd Font Mono`. -Font size and glyph scale are controlled by that profile. Hawk applies bold +Font size and glyph scale are controlled by that profile. Rho applies bold weight to status icons for contrast, but there is no portable ANSI escape that can resize one glyph independently of the surrounding text. diff --git a/docs/troubleshooting-guide.md b/docs/troubleshooting-guide.md index babcc6481..f03aa5216 100644 --- a/docs/troubleshooting-guide.md +++ b/docs/troubleshooting-guide.md @@ -1,6 +1,6 @@ # Troubleshooting Guide -A practical guide for diagnosing common hawk daemon and CLI issues. +A practical guide for diagnosing common rho daemon and CLI issues. ## Table of Contents @@ -30,27 +30,27 @@ because the auth middleware would be open to the network. **Fix:** Set an API key: ```bash -export HAWK_DAEMON_API_KEY=$(openssl rand -base64 32) -hawk daemon start --host 0.0.0.0 --port 4590 +export RHO_DAEMON_API_KEY=$(openssl rand -base64 32) +rho daemon start --host 0.0.0.0 --port 4590 ``` Or bind to loopback only (no API key required, but not remotely accessible): ```bash -hawk daemon start --host 127.0.0.1 --port 4590 +rho daemon start --host 127.0.0.1 --port 4590 ``` ### "permission denied" on state directory -The daemon writes logs, PID files, and the audit log to `~/.hawk/state/`. +The daemon writes logs, PID files, and the audit log to `~/.rho/state/`. **Fix:** ```bash -mkdir -p ~/.hawk/state -chmod 750 ~/.hawk/state -# If running under systemd as user 'hawk', ensure ownership: -chown -R hawk:hawk ~/.hawk +mkdir -p ~/.rho/state +chmod 750 ~/.rho/state +# If running under systemd as user 'rho', ensure ownership: +chown -R rho:rho ~/.rho ``` ### "port already in use" @@ -64,7 +64,7 @@ Another process is using port 4590. lsof -i :4590 # Or use a different port -hawk daemon start --port 4591 +rho daemon start --port 4591 ``` --- @@ -84,9 +84,9 @@ curl -v http://localhost:4590/v1/ready Check the response body for the specific failed check. Common causes: -- No model configured — set `HAWK_MODEL` or provider credentials. +- No model configured — set `RHO_MODEL` or provider credentials. - Eyrie catalog not initialized — the Go modules are independent sibling - repositories; from a full parent workspace, run `make setup` in hawk to + repositories; from a full parent workspace, run `make setup` in rho to regenerate the parent `go.work`: ```bash make setup @@ -103,11 +103,11 @@ failed to start. ```bash # Check if the process is running -ps aux | grep hawk +ps aux | grep rho # Check logs -journalctl -u hawk-daemon -n 50 -tail -50 ~/.hawk/state/daemon.log +journalctl -u rho-daemon -n 50 +tail -50 ~/.rho/state/daemon.log ``` --- @@ -121,7 +121,7 @@ The daemon requires `Authorization: Bearer ` or `X-API-Key: `. **Fix:** ```bash -curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" http://localhost:4590/v1/stats +curl -H "X-API-Key: $RHO_DAEMON_API_KEY" http://localhost:4590/v1/stats ``` ### "constant time comparison" errors in logs @@ -132,16 +132,16 @@ No action needed. ### Forgotten API key -The API key is written to `~/.hawk/state/daemon.key` (permissions 0600). +The API key is written to `~/.rho/state/daemon.key` (permissions 0600). ```bash -cat ~/.hawk/state/daemon.key +cat ~/.rho/state/daemon.key ``` **Security note:** Remove this file in production after initial testing: ```bash -rm ~/.hawk/state/daemon.key +rm ~/.rho/state/daemon.key ``` --- @@ -153,7 +153,7 @@ your request. The default limits are: - **General API**: 10 req/min, burst 4 - **Chat**: 30 req/min, burst 6 -- **Concurrent chat sessions**: 4 (configurable via `HAWK_DAEMON_MAX_CONCURRENT`) +- **Concurrent chat sessions**: 4 (configurable via `RHO_DAEMON_MAX_CONCURRENT`) **Fix:** @@ -171,7 +171,7 @@ your request. The default limits are: CORS is **disabled by default**. Enable it when serving browser-based clients: ```bash -hawk daemon start --cors https://app.example.com --cors https://admin.example.com +rho daemon start --cors https://app.example.com --cors https://admin.example.com ``` Use `--cors '*'` only for development — it allows any origin. @@ -182,7 +182,7 @@ The CORS middleware handles OPTIONS preflight automatically when the `cors` feature flag is enabled. If you're getting 405, ensure CORS is enabled: ```bash -export HAWK_FEATURE_CORS=1 +export RHO_FEATURE_CORS=1 ``` --- @@ -211,7 +211,7 @@ The metrics endpoint is protected by the same API key authentication as other endpoints. ```bash -curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" http://localhost:4590/v1/metrics +curl -H "X-API-Key: $RHO_DAEMON_API_KEY" http://localhost:4590/v1/metrics ``` ### Metrics output is empty @@ -227,7 +227,7 @@ Prometheus version supports this format (Prometheus 2.20+). For troubleshooting, try the JSON format: ```bash -curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" \ +curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ "http://localhost:4590/v1/metrics?format=json" ``` @@ -240,13 +240,13 @@ curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" \ Telemetry is **opt-in**. You must explicitly enable it: ```bash -export HAWK_ENABLE_TELEMETRY=1 +export RHO_ENABLE_TELEMETRY=1 export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 -hawk daemon start +rho daemon start ``` **Important**: Setting `OTEL_EXPORTER_OTLP_ENDPOINT` alone does **not** -enable telemetry. The `HAWK_ENABLE_TELEMETRY=1` flag is required. +enable telemetry. The `RHO_ENABLE_TELEMETRY=1` flag is required. ### "telemetry initialization failed" warning in logs @@ -265,7 +265,7 @@ curl -v http://localhost:4318/v1/traces The OTel SDK uses a batch span processor with a 5-second flush interval. On shutdown, the daemon waits up to 2 seconds (configurable via -`HAWK_OTEL_SHUTDOWN_TIMEOUT_MS`) to flush pending spans. +`RHO_OTEL_SHUTDOWN_TIMEOUT_MS`) to flush pending spans. To force a flush, send SIGTERM to the daemon — it will flush telemetry before shutting down. @@ -276,25 +276,25 @@ before shutting down. ### Security log won't open -The audit log is stored in `~/.hawk/state/securitylog/`. If the directory +The audit log is stored in `~/.rho/state/securitylog/`. If the directory doesn't exist or isn't writable: ```bash -mkdir -p ~/.hawk/state/securitylog -chmod 700 ~/.hawk/state/securitylog +mkdir -p ~/.rho/state/securitylog +chmod 700 ~/.rho/state/securitylog ``` ### "log tail does not match head pointer (truncated or tampered)" This error means the security log has been modified or truncated. The tamper-evident design detected an inconsistency. Restore from a backup -of the `~/.hawk/state/securitylog/` directory. +of the `~/.rho/state/securitylog/` directory. ### Lost the HMAC key The HMAC key (`sel.key`) is required to verify the audit log. If it's lost, all entries become unverifiable. **Always back up the entire -`~/.hawk/state/securitylog/` directory.** +`~/.rho/state/securitylog/` directory.** --- @@ -306,13 +306,13 @@ Tools that require sandboxing are disabled until the sandbox container is running. Check the sandbox status: ```bash -hawk sandbox status +rho sandbox status ``` Start the sandbox: ```bash -hawk sandbox start +rho sandbox start ``` ### Permission denied for a tool @@ -328,13 +328,13 @@ audit log for the `denied` event type. 1. Check the metrics endpoint for request duration: ```bash - curl -H "X-API-Key: $HAWK_DAEMON_API_KEY" \ + curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ "http://localhost:4590/v1/metrics?format=json" ``` -2. Check concurrent sessions: `hawk_daemon_chat_concurrency_used` +2. Check concurrent sessions: `rho_daemon_chat_concurrency_used` 3. If at capacity, increase the concurrency limit: ```bash - export HAWK_DAEMON_MAX_CONCURRENT=8 + export RHO_DAEMON_MAX_CONCURRENT=8 ``` ### High memory usage @@ -352,7 +352,7 @@ large contexts can consume significant memory. Consider: ### Daemon exits immediately -The default entrypoint runs `hawk daemon start --host 0.0.0.0 --port 4590`. +The default entrypoint runs `rho daemon start --host 0.0.0.0 --port 4590`. Non-loopback binds require both an API key and native TLS; an API key alone does not protect credentials or conversation data from plaintext interception. @@ -360,9 +360,9 @@ does not protect credentials or conversation data from plaintext interception. ```bash docker run -p 4590:4590 \ - -e HAWK_DAEMON_API_KEY=$(openssl rand -base64 32) \ + -e RHO_DAEMON_API_KEY=$(openssl rand -base64 32) \ -v "$PWD/certs:/certs:ro" \ - ghcr.io/graycodeai/hawk-daemon:latest \ + ghcr.io/graycodeai/rho-daemon:latest \ --tls-cert /certs/server.crt --tls-key /certs/server.key ``` @@ -377,35 +377,35 @@ or start-period if needed. ## Systemd Issues -### "Failed to start hawk-daemon.service: Unit not found" +### "Failed to start rho-daemon.service: Unit not found" Install the unit file: ```bash -sudo cp packaging/systemd/hawk-daemon.service /etc/systemd/system/ +sudo cp packaging/systemd/rho-daemon.service /etc/systemd/system/ sudo systemctl daemon-reload -sudo systemctl enable --now hawk-daemon +sudo systemctl enable --now rho-daemon ``` ### "Permission denied" accessing state directory -The systemd unit runs as user `hawk` with `ProtectSystem=strict` and -`ReadWritePaths=%h/.hawk/state`. Ensure the user's home directory has +The systemd unit runs as user `rho` with `ProtectSystem=strict` and +`ReadWritePaths=%h/.rho/state`. Ensure the user's home directory has the correct state: ```bash -sudo -u hawk mkdir -p /home/hawk/.hawk/state -sudo -u hawk chmod 750 /home/hawk/.hawk/state +sudo -u rho mkdir -p /home/rho/.rho/state +sudo -u rho chmod 750 /home/rho/.rho/state ``` ### Daemon not logging to journald If using the systemd unit, logs go to both journald (stdout/stderr) and -`~/.hawk/state/daemon.log`. Check: +`~/.rho/state/daemon.log`. Check: ```bash -journalctl -u hawk-daemon -f -tail -f ~/.hawk/state/daemon.log +journalctl -u rho-daemon -f +tail -f ~/.rho/state/daemon.log ``` If journald logs are missing, verify that stdout/stderr are not being diff --git a/docs/user-guide/01-getting-started.md b/docs/user-guide/01-getting-started.md index 3392fda31..df787591c 100644 --- a/docs/user-guide/01-getting-started.md +++ b/docs/user-guide/01-getting-started.md @@ -1,25 +1,25 @@ # Getting Started -Hawk is an AI-powered coding agent for your terminal, built for developers by GrayCode AI. It understands your codebase, executes shell commands, edits files, searches the web, and manages tasks — all through natural language. +Rho is an AI-powered coding agent for your terminal, built for developers by GrayCode AI. It understands your codebase, executes shell commands, edits files, searches the web, and manages tasks — all through natural language. -You can use Hawk interactively as a full-screen TUI, run it headlessly for scripting and CI/CD, or integrate it into editors via the Agent Client Protocol (ACP). +You can use Rho interactively as a full-screen TUI, run it headlessly for scripting and CI/CD, or integrate it into editors via the Agent Client Protocol (ACP). --- ## Installation -Hawk is currently in active development. Contributor source builds are the primary path while we harden the product in the open. +Rho is currently in active development. Contributor source builds are the primary path while we harden the product in the open. ### From Source (Recommended for Contributors) ```bash -git clone https://github.com/GrayCodeAI/hawk && cd hawk -GOWORK=off go build -o hawk ./cmd/hawk -./hawk +git clone https://github.com/GrayCodeAI/rho && cd rho +GOWORK=off go build -o rho ./cmd/rho +./rho ``` For full cross-repository development, place the nine Go repositories beside -Hawk in a parent workspace and run `make setup` from `hawk`; `graycode-eco` is +Rho in a parent workspace and run `make setup` from `rho`; `graycode-eco` is only a local folder name, not a repository containing those modules. ### Verification @@ -27,7 +27,7 @@ only a local folder name, not a repository containing those modules. Run the developer path check to verify your setup: ```bash -./hawk path +./rho path ``` This checks setup and security readiness. @@ -36,15 +36,15 @@ This checks setup and security readiness. ## First Launch -Start Hawk by running: +Start Rho by running: ```bash -hawk +rho ``` -On first launch, Hawk opens a TUI where you can configure credentials. Press `/config` (or `/autonomy`) to open the configuration picker. Your API keys are stored in your OS keychain (macOS Keychain or Linux keyring), never in plain text. +On first launch, Rho opens a TUI where you can configure credentials. Press `/config` (or `/autonomy`) to open the configuration picker. Your API keys are stored in your OS keychain (macOS Keychain or Linux keyring), never in plain text. -Hawk supports multiple providers: +Rho supports multiple providers: - **xAI Grok** — `XAI_API_KEY` - **Anthropic Claude** — `ANTHROPIC_API_KEY` @@ -58,12 +58,12 @@ See [Authentication](02-authentication.md) for the full set of auth options incl ## Basic Interaction -Once authenticated, Hawk presents a full-screen TUI powered by Bubble Tea with two main areas: +Once authenticated, Rho presents a full-screen TUI powered by Bubble Tea with two main areas: -- **Scrollback** — the conversation history showing your prompts, Hawk's responses, tool calls, file edits, and more +- **Scrollback** — the conversation history showing your prompts, Rho's responses, tool calls, file edits, and more - **Prompt** — the input area at the bottom where you type messages -Type a message and press `Enter` to send it. Hawk reads files, runs commands, and edits code as needed. Each tool run streams into the scrollback in real time. +Type a message and press `Enter` to send it. Rho reads files, runs commands, and edits code as needed. Each tool run streams into the scrollback in real time. Press `Tab` to move focus between the prompt and the scrollback. While a turn is running, `Ctrl+C` cancels it. In Vim mode, use `j`/`k` to navigate and `h`/`l` to collapse/expand entries. @@ -81,7 +81,7 @@ The `@` operator opens a fuzzy file picker. By default it respects `.gitignore` ### Permissions -Hawk exposes two independent control surfaces: +Rho exposes two independent control surfaces: - **`/autonomy`** — Controls the trust tier (Always Ask, Scout, Builder, Operator, Autonomous) - **`/spec`** — A workflow gate that blocks Write/Edit/Bash until you approve implementation @@ -105,21 +105,21 @@ Every conversation is a **session**. Sessions are automatically saved and can be - Start a new session: `Ctrl+N` or `/new` - Resume a previous session: `/resume` in the TUI, or `--resume ` from the CLI -- Continue the most recent session: `hawk -c` +- Continue the most recent session: `rho -c` ### Scrollback The scrollback shows: - **User prompts** — your messages, rendered as sticky headers -- **Agent messages** — Hawk's responses with markdown rendering -- **Thinking blocks** — Hawk's reasoning process (collapsible) +- **Agent messages** — Rho's responses with markdown rendering +- **Thinking blocks** — Rho's reasoning process (collapsible) - **Tool calls** — file edits, command executions, search results - **Task lists** — TODO items tracking progress ### Tools -Hawk has built-in tools for: +Rho has built-in tools for: | Tool | Description | |------|-------------| @@ -152,44 +152,44 @@ See [Slash Commands](04-slash-commands.md) for the complete reference. ```bash # Start the interactive TUI -hawk +rho # Submit an initial prompt as the first turn -hawk "fix the failing auth test and run it" +rho "fix the failing auth test and run it" # Start in a specific project directory -hawk --cwd ~/projects/my-app +rho --cwd ~/projects/my-app # Resume a previous session -hawk -r abc123 +rho -r abc123 # Continue the most recent session -hawk -c +rho -c # Use a specific provider/model -hawk --provider openai --model gpt-4o +rho --provider openai --model gpt-4o # Isolated worktree for changes -hawk --worktree "refactor module X" +rho --worktree "refactor module X" # Non-interactive (headless) mode -hawk -p "Explain this codebase" +rho -p "Explain this codebase" # Full auto mode -hawk exec --auto full "add error handling" +rho exec --auto full "add error handling" # Dry-run mode (denies all tools) -hawk exec --autonomy dry-run "What would this do?" +rho exec --autonomy dry-run "What would this do?" ``` --- ## Headless Mode -Run Hawk non-interactively for scripting, CI/CD, and automation: +Run Rho non-interactively for scripting, CI/CD, and automation: ```bash -hawk -p "Your prompt here" +rho -p "Your prompt here" ``` Output formats: @@ -204,15 +204,15 @@ Output formats: ## Project Rules (AGENTS.md) -Add per-project instructions by creating an `AGENTS.md` file in your repository. Hawk reads these files and injects their contents as a project-instructions message at the start of the conversation: +Add per-project instructions by creating an `AGENTS.md` file in your repository. Rho reads these files and injects their contents as a project-instructions message at the start of the conversation: ``` -~/.hawk/AGENTS.md # Global rules (apply to all projects) +~/.rho/AGENTS.md # Global rules (apply to all projects) /AGENTS.md # Repository-level rules /AGENTS.md # Directory-level rules (highest priority) ``` -Deeper files take precedence. Hawk also reads `CLAUDE.md` files for compatibility. +Deeper files take precedence. Rho also reads `CLAUDE.md` files for compatibility. --- diff --git a/docs/user-guide/02-authentication.md b/docs/user-guide/02-authentication.md index c6b03f171..9db643e79 100644 --- a/docs/user-guide/02-authentication.md +++ b/docs/user-guide/02-authentication.md @@ -1,14 +1,14 @@ # Authentication -Hawk supports several authentication methods, including API key configuration through the TUI and multi-provider support via Eyrie. +Rho supports several authentication methods, including API key configuration through the TUI and multi-provider support via Eyrie. --- ## API Key Configuration -On first launch, Hawk opens the TUI where you can configure credentials. Press `/config` or `/autonomy` to open the configuration picker. Your API keys are stored in your OS keychain (macOS Keychain or Linux keyring), never in plain text or environment variables. +On first launch, Rho opens the TUI where you can configure credentials. Press `/config` or `/autonomy` to open the configuration picker. Your API keys are stored in your OS keychain (macOS Keychain or Linux keyring), never in plain text or environment variables. -Hawk supports multiple providers: +Rho supports multiple providers: | Provider | ID | Key | |----------|----|-----| @@ -24,10 +24,10 @@ Hawk supports multiple providers: ```bash # Verify credential status -hawk credentials status +rho credentials status # Run the TUI and use /config to set keys interactively -hawk +rho ``` ### Environment Variables (Fallback) @@ -36,7 +36,7 @@ For CI/CD or headless environments, you can set API keys as environment variable ```bash export XAI_API_KEY="xai-..." -hawk +rho ``` Fireworks uses its OpenAI-compatible API. Set `FIREWORKS_API_KEY`; the default @@ -49,19 +49,19 @@ base URL is `https://api.fireworks.ai/inference/v1`. See the official ## Provider Configuration -Hawk uses Eyrie for provider routing, health checks, and retry logic. To configure providers: +Rho uses Eyrie for provider routing, health checks, and retry logic. To configure providers: ```bash # In the TUI, press /config to open provider settings -hawk +rho # Or validate readiness -hawk path +rho path ``` ### Deployment-Aware Routing -For deployment-aware routing, set in `.hawk/settings.json`: +For deployment-aware routing, set in `.rho/settings.json`: ```json { @@ -72,10 +72,10 @@ For deployment-aware routing, set in `.hawk/settings.json`: Or export: ```bash -export HAWK_DEPLOYMENT_ROUTING=true +export RHO_DEPLOYMENT_ROUTING=true ``` -Hawk will route canonical model IDs through Eyrie's deployment catalog. Refresh the catalog with: +Rho will route canonical model IDs through Eyrie's deployment catalog. Refresh the catalog with: ``` /refresh-model-catalog @@ -90,7 +90,7 @@ Authenticate developers through your own Identity Provider (IdP) — such as Okt ### Configure via Settings ```json -// .hawk/settings.json +// .rho/settings.json { "oidc": { "issuer": "https://acme.okta.com", @@ -99,7 +99,7 @@ Authenticate developers through your own Identity Provider (IdP) — such as Okt } ``` -Hawk discovers endpoints via `{issuer}/.well-known/openid-configuration`, opens the IdP login page, and stores tokens in the keychain. Tokens auto-refresh silently via the stored `refresh_token`. +Rho discovers endpoints via `{issuer}/.well-known/openid-configuration`, opens the IdP login page, and stores tokens in the keychain. Tokens auto-refresh silently via the stored `refresh_token`. ### Required Scopes @@ -116,7 +116,7 @@ When browser-based login isn't possible — for example, on sandboxed VMs, CI ru ### How It Works -1. Hawk runs your command via `sh -c ""` +1. Rho runs your command via `sh -c ""` 2. Your binary runs whatever auth flow it needs 3. **stdout** is captured and parsed as an access token 4. **stderr** carries human-readable output surfaced to the user @@ -139,7 +139,7 @@ JSON with optional refresh token: ### Configuration ```json -// .hawk/settings.json +// .rho/settings.json { "auth_provider_command": "/usr/local/bin/my-auth-provider", "auth_provider_label": "Acme Corp" @@ -149,17 +149,17 @@ JSON with optional refresh token: Or via environment variables: ```bash -export HAWK_AUTH_PROVIDER_COMMAND="/usr/local/bin/my-auth-provider" -export HAWK_AUTH_PROVIDER_LABEL="Acme Corp" +export RHO_AUTH_PROVIDER_COMMAND="/usr/local/bin/my-auth-provider" +export RHO_AUTH_PROVIDER_LABEL="Acme Corp" ``` ### Token Refresh -When Hawk needs to refresh an expired token, it re-runs your binary with `HAWK_AUTH_EXPIRED=1` set in the environment: +When Rho needs to refresh an expired token, it re-runs your binary with `RHO_AUTH_EXPIRED=1` set in the environment: ```bash #!/bin/sh -if [ "$HAWK_AUTH_EXPIRED" = "1" ]; then +if [ "$RHO_AUTH_EXPIRED" = "1" ]; then echo "Refreshing token..." >&2 TOKEN=$(my-company-auth --refresh --silent) else @@ -182,7 +182,7 @@ echo "{\"access_token\": \"$TOKEN\", \"expires_in\": 3600}" Check credential status at any time: ```bash -hawk credentials status +rho credentials status ``` This verifies keychain entries and validates Eyrie's provider status. @@ -191,7 +191,7 @@ This verifies keychain entries and validates Eyrie's provider status. ## Credential Precedence -Hawk resolves credentials in this order: +Rho resolves credentials in this order: 1. **Per-model configuration** — set via `/config` or settings 2. **Keychain entry** — obtained through TUI configuration @@ -203,7 +203,7 @@ During a session, the active method handles all refreshes. ## Multi-Provider Support -Hawk works with any LLM provider through Eyrie's adapter system: +Rho works with any LLM provider through Eyrie's adapter system: | Provider | Status | |----------|--------| diff --git a/docs/user-guide/03-keyboard-shortcuts.md b/docs/user-guide/03-keyboard-shortcuts.md index a040cfd71..6af07dd38 100644 --- a/docs/user-guide/03-keyboard-shortcuts.md +++ b/docs/user-guide/03-keyboard-shortcuts.md @@ -1,12 +1,12 @@ # Keyboard Shortcuts -Reference for key bindings in the Hawk TUI. Bindings are built-in and cannot currently be remapped. +Reference for key bindings in the Rho TUI. Bindings are built-in and cannot currently be remapped. --- ## Input Modes -Hawk has two input modes that control how you navigate the scrollback: +Rho has two input modes that control how you navigate the scrollback: - **Simple mode** (default): Arrow keys for navigation, `Shift+Arrow` for turn navigation, `Space` to focus the prompt - **Vim mode** (opt-in): `j`/`k` for navigation, `H`/`L` for turn navigation, `h`/`l` for fold, `Tab` to focus the prompt @@ -17,7 +17,7 @@ Simple mode is active by default. To switch to Vim mode: /vim-mode ``` -Or set `vim_mode = true` under `[ui]` in `~/.hawk/settings.json`: +Or set `vim_mode = true` under `[ui]` in `~/.rho/settings.json`: ```json { diff --git a/docs/user-guide/04-slash-commands.md b/docs/user-guide/04-slash-commands.md index a761d27c9..178de2cf0 100644 --- a/docs/user-guide/04-slash-commands.md +++ b/docs/user-guide/04-slash-commands.md @@ -186,7 +186,7 @@ Install a skill from a source. ``` /skills install go-review -hawk skills install go-review +rho skills install go-review ``` ### `/skills audit` @@ -195,7 +195,7 @@ Security scan installed skills. ``` /skills audit -hawk skills audit +rho skills audit ``` --- diff --git a/docs/user-guide/05-configuration.md b/docs/user-guide/05-configuration.md index 735433bf1..aed869a04 100644 --- a/docs/user-guide/05-configuration.md +++ b/docs/user-guide/05-configuration.md @@ -1,6 +1,6 @@ # Configuration -Hawk reads configuration from settings files, environment variables, and has defaults for all options. This document covers the common configuration options. +Rho reads configuration from settings files, environment variables, and has defaults for all options. This document covers the common configuration options. --- @@ -11,18 +11,18 @@ Configuration is resolved in this order (highest priority first): 1. **CLI `--settings` JSON override** (`LoadSettingsWithOverride`) 2. **Per-command CLI flags** (e.g., `--provider`, `--model`) 3. **Environment variables** (only where explicitly read; there is no global env layer) -4. **Project settings** (`.hawk/settings.json`, repository-safe subset only — +4. **Project settings** (`.rho/settings.json`, repository-safe subset only — `model`, `provider`, permissions, MCP servers and providers are stripped) -5. **User settings** (`~/.hawk/settings.json`) +5. **User settings** (`~/.rho/settings.json`) 6. **Built-in defaults** --- ## Settings File -Location: `~/.hawk/settings.json` +Location: `~/.rho/settings.json` -This is the main configuration file. Hawk writes to it when you save changes via `/config` or `/autonomy save`. +This is the main configuration file. Rho writes to it when you save changes via `/config` or `/autonomy save`. ### Basic Settings @@ -106,34 +106,34 @@ Key environment variables for configuration. | Variable | Description | |----------|-------------| -| `HAWK_Y0_FOLDER_TRUST` | Folder trust feature flag (default: `1`) | -| `HAWK_Y0_MARKETPLACE` | Marketplace feature flag (default: `1`, set `0` to disable remote installs) | -| `HAWK_DEPLOYMENT_ROUTING` | Not an environment variable: set `deployment_routing` in `settings.json` | +| `RHO_Y0_FOLDER_TRUST` | Folder trust feature flag (default: `1`) | +| `RHO_Y0_MARKETPLACE` | Marketplace feature flag (default: `1`, set `0` to disable remote installs) | +| `RHO_DEPLOYMENT_ROUTING` | Not an environment variable: set `deployment_routing` in `settings.json` | ### Paths | Variable | Description | |----------|-------------| -| `HAWK_HOME` | Harness home override used by identity only (default: `~/.hawk`); most config paths honor `HAWK_CONFIG_DIR` / `HAWK_STATE_DIR` / `HAWK_CACHE_DIR` instead | +| `RHO_HOME` | Harness home override used by identity only (default: `~/.rho`); most config paths honor `RHO_CONFIG_DIR` / `RHO_STATE_DIR` / `RHO_CACHE_DIR` instead | --- ## Project Configuration -Place configuration in `.hawk/` within your repository: +Place configuration in `.rho/` within your repository: | File | Purpose | |------|---------| -| `.hawk/settings.json` | Project settings (autonomy, rules) | -| `.hawk/lsp.json` | LSP server configuration | +| `.rho/settings.json` | Project settings (autonomy, rules) | +| `.rho/lsp.json` | LSP server configuration | | `AGENTS.md` | Project instructions | --- ## MCP Servers -Configure MCP servers in global `~/.hawk/settings.json` only — project -`.hawk/settings.json` cannot register MCP servers (stripped by +Configure MCP servers in global `~/.rho/settings.json` only — project +`.rho/settings.json` cannot register MCP servers (stripped by `projectSafeSettings`; project automation additionally requires folder trust, see below): @@ -169,7 +169,7 @@ Folder trust controls whether project automation (hooks, plugins, MCP, LSP) can ### Trust Store -Location: `~/.hawk/trusted_folders.toml` +Location: `~/.rho/trusted_folders.toml` ```toml [[folders]] diff --git a/docs/user-guide/06-theming.md b/docs/user-guide/06-theming.md index cf1ede1b2..10533c6a5 100644 --- a/docs/user-guide/06-theming.md +++ b/docs/user-guide/06-theming.md @@ -1,16 +1,16 @@ # Theming and Appearance Customization -Hawk draws all TUI colors from a central theme. You can switch themes while running, follow your operating system's light or dark appearance, and adjust scroll speed and compact mode through slash commands or settings. +Rho draws all TUI colors from a central theme. You can switch themes while running, follow your operating system's light or dark appearance, and adjust scroll speed and compact mode through slash commands or settings. --- ## Available Themes -Hawk includes 17 built-in themes, plus an `auto` option that follows your system appearance: +Rho includes 17 built-in themes, plus an `auto` option that follows your system appearance: | Theme | Description | |-------|-------------| -| **dark** | Neutral dark base with Hawk's Talon Gold accent. Default theme. | +| **dark** | Neutral dark base with Rho's Talon Gold accent. Default theme. | | **dracula** | The Dracula color scheme with muted violet surfaces. | | **nord** | Arctic cold blue palette. | | **gruvbox** | Warm retro browns with olive-green accent. | @@ -37,7 +37,7 @@ Theme names are case-insensitive. ### In the TUI -Run the `/theme` slash command to open the theme picker. As you move through the list with the arrow keys, Hawk previews each theme in real time: +Run the `/theme` slash command to open the theme picker. As you move through the list with the arrow keys, Rho previews each theme in real time: ``` /theme @@ -54,7 +54,7 @@ Submitting `/theme` on its own opens the picker. ### Via Settings -Set the theme in `~/.hawk/settings.json`: +Set the theme in `~/.rho/settings.json`: ```json { @@ -66,7 +66,7 @@ Set the theme in `~/.hawk/settings.json`: ## Auto Theme (System Appearance) -Set `theme: "auto"` to have Hawk follow your operating system's light/dark appearance and switch themes automatically: +Set `theme: "auto"` to have Rho follow your operating system's light/dark appearance and switch themes automatically: ```json { @@ -93,13 +93,13 @@ By default, dark mode maps to the **dark** theme and light mode maps to the **li | **Windows** | Reads the system personalization registry via PowerShell. | | **SSH / headless** | Defaults to dark theme when detection fails. | -Once running, Hawk checks for appearance changes when you explicitly switch themes (no continuous polling to avoid resource drain). +Once running, Rho checks for appearance changes when you explicitly switch themes (no continuous polling to avoid resource drain). --- ## Color Support Detection -On startup, Hawk detects your terminal's color capability: +On startup, Rho detects your terminal's color capability: | Level | Description | |-------|-------------| @@ -107,7 +107,7 @@ On startup, Hawk detects your terminal's color capability: | **256-color** | Indexed palette. Colors are mapped to the nearest index. | | **16-color** | ANSI names only. Colors map to the closest ANSI color. | -When `COLORTERM=truecolor` is set, Hawk uses truecolor mode. When `TERM` contains `256color`, it uses 256-color mode. When `NO_COLOR` is set, Hawk renders in monochrome. +When `COLORTERM=truecolor` is set, Rho uses truecolor mode. When `TERM` contains `256color`, it uses 256-color mode. When `NO_COLOR` is set, Rho renders in monochrome. --- @@ -192,7 +192,7 @@ View terminal configuration recommendations and current capabilities: /terminal-setup ``` -This shows detected color support, current settings, and tips for optimal Hawk experience. +This shows detected color support, current settings, and tips for optimal Rho experience. --- diff --git a/docs/user-guide/07-mcp-servers.md b/docs/user-guide/07-mcp-servers.md index 45dae6e38..bf919ed1a 100644 --- a/docs/user-guide/07-mcp-servers.md +++ b/docs/user-guide/07-mcp-servers.md @@ -1,12 +1,12 @@ # MCP Servers -MCP (Model Context Protocol) servers extend Hawk with external tool integrations. They let Hawk interact with any service that implements the MCP standard. +MCP (Model Context Protocol) servers extend Rho with external tool integrations. They let Rho interact with any service that implements the MCP standard. --- ## What Are MCP Servers? -An MCP server is a process that exposes tools to Hawk over a standardized protocol. When you configure an MCP server, its tools become available to the model alongside Hawk's built-in tools. The model can discover and call these tools during a session. +An MCP server is a process that exposes tools to Rho over a standardized protocol. When you configure an MCP server, its tools become available to the model alongside Rho's built-in tools. The model can discover and call these tools during a session. For example, a GitHub MCP server might expose tools like `create_issue`, `list_pull_requests`, and `search_code`. A database server might expose `query`, `list_tables`, and `describe_schema`. @@ -16,11 +16,11 @@ See the [MCP specification](https://modelcontextprotocol.io) for protocol detail ## Configuration -MCP servers are configured in `.hawk/settings.json` under the `mcp_servers` key. +MCP servers are configured in `.rho/settings.json` under the `mcp_servers` key. ### stdio Transport (Local Process) -Hawk spawns a local process and communicates over stdin/stdout: +Rho spawns a local process and communicates over stdin/stdout: ```json { @@ -64,40 +64,40 @@ Manage MCP servers from the command line: ```bash # List configured MCP servers -hawk mcp list +rho mcp list # Add a stdio server -hawk mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir +rho mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir # Add a remote HTTP server -hawk mcp add linear --transport http https://mcp.linear.app/mcp +rho mcp add linear --transport http https://mcp.linear.app/mcp # Remove a server -hawk mcp remove github +rho mcp remove github # Diagnose server configuration -hawk mcp doctor -hawk mcp doctor +rho mcp doctor +rho mcp doctor ``` -Use `--scope project` to write to `.hawk/settings.json` in the current directory instead of the user config. +Use `--scope project` to write to `.rho/settings.json` in the current directory instead of the user config. --- ## Project-Scoped MCP Servers -MCP servers can be configured per-project in `.hawk/settings.json`: +MCP servers can be configured per-project in `.rho/settings.json`: ``` my-project/ - .hawk/ + .rho/ settings.json src/ ... ``` ```json -// .hawk/settings.json +// .rho/settings.json { "mcp_servers": { "linear": { @@ -125,7 +125,7 @@ MCP tools are namespaced with the server name to avoid collisions: ## Discovering and Using Tools -Hawk provides built-in tools to work with MCP servers: +Rho provides built-in tools to work with MCP servers: - **`search_tool`** — Discover available integration tools across all enabled MCP servers - **`use_tool`** — Call an integration tool discovered via `search_tool` diff --git a/docs/user-guide/08-skills.md b/docs/user-guide/08-skills.md index b5e14d201..519c5f1a4 100644 --- a/docs/user-guide/08-skills.md +++ b/docs/user-guide/08-skills.md @@ -1,26 +1,26 @@ # Skills -Skills are reusable prompt packages that extend Hawk with task-specific instructions. They let you capture a repeatable procedure once, instead of re-explaining it each session. +Skills are reusable prompt packages that extend Rho with task-specific instructions. They let you capture a repeatable procedure once, instead of re-explaining it each session. --- ## What Are Skills? -A skill is a directory containing a `SKILL.md` file. Its markdown body tells Hawk how to handle a specific type of task: step-by-step instructions, conventions, and tool-usage patterns. +A skill is a directory containing a `SKILL.md` file. Its markdown body tells Rho how to handle a specific type of task: step-by-step instructions, conventions, and tool-usage patterns. -Use a skill for a repeatable procedure that's too specific for AGENTS.md but too long to retype each time. Hawk activates a skill when it applies to your current task. +Use a skill for a repeatable procedure that's too specific for AGENTS.md but too long to retype each time. Rho activates a skill when it applies to your current task. --- ## Skill Locations -Hawk discovers skills from these directories, in priority order: +Rho discovers skills from these directories, in priority order: | Location | Scope | Priority | |----------|-------|----------| -| `.hawk/skills/`, `.hawk/commands/` | Local (CWD) | Highest | -| `/.hawk/skills/` | Repo | Medium | -| `~/.hawk/skills/` | User | Lowest | +| `.rho/skills/`, `.rho/commands/` | Local (CWD) | Highest | +| `/.rho/skills/` | Repo | Medium | +| `~/.rho/skills/` | User | Lowest | Higher-priority locations override skills with the same name. @@ -33,7 +33,7 @@ Higher-priority locations override skills with the same name. Each skill lives in its own directory with a `SKILL.md` file: ``` -~/.hawk/skills/ +~/.rho/skills/ commit/ SKILL.md review-pr/ @@ -103,13 +103,13 @@ Pass arguments after the name: When names collide, use qualified forms: ``` -/local:commit # From ./.hawk/skills/ -/user:commit # From ~/.hawk/skills/ +/local:commit # From ./.rho/skills/ +/user:commit # From ~/.rho/skills/ ``` ### Automatic Invocation -Hawk can invoke a skill automatically when it recognizes a relevant task. Write specific descriptions in the `description` and `when-to-use` fields. +Rho can invoke a skill automatically when it recognizes a relevant task. Write specific descriptions in the `description` and `when-to-use` fields. --- @@ -119,26 +119,26 @@ Manage skills from the command line: ```bash # Search the community registry -hawk skills search go +rho skills search go # Install a skill from a source -hawk skills install go-review +rho skills install go-review # Audit installed skills for security -hawk skills audit +rho skills audit ``` --- ## Installing Skills -Hawk ships **no bundled skills** by default. Skills are installed on demand +Rho ships **no bundled skills** by default. Skills are installed on demand from the separate `GrayCodeAI/starling` repo (or any GitHub repo): ```bash -hawk skills search # find a skill in the registry -hawk skills install [skill] # install after user approval -hawk skills audit # security-scan installed skills +rho skills search # find a skill in the registry +rho skills install [skill] # install after user approval +rho skills audit # security-scan installed skills ``` Once installed, skills are discovered from the locations listed above. diff --git a/docs/user-guide/09-plugins.md b/docs/user-guide/09-plugins.md index 773b065a2..bdcbdec16 100644 --- a/docs/user-guide/09-plugins.md +++ b/docs/user-guide/09-plugins.md @@ -20,12 +20,12 @@ For example, a `team-tools` plugin might include a deploy skill, a code-review a ## Plugin Locations -Hawk discovers plugins from these locations, in priority order: +Rho discovers plugins from these locations, in priority order: | Location | Scope | Trust Required | |----------|-------|----------------| -| `.hawk/plugins/` | Project | Yes | -| `~/.hawk/plugins/` | User | No | +| `.rho/plugins/` | Project | Yes | +| `~/.rho/plugins/` | User | No | | `--plugin-dir` | Process | No | When two plugins share a name, the higher-priority location wins. @@ -38,21 +38,21 @@ Manage plugins from the command line: ```bash # List installed plugins -hawk plugins list +rho plugins list # Install a plugin -hawk plugins install user/repo -hawk plugins install user/repo@v1.0 -hawk plugins install /path/to/local/plugin +rho plugins install user/repo +rho plugins install user/repo@v1.0 +rho plugins install /path/to/local/plugin # Install with trust (required for project plugins) -hawk plugins install user/repo --trust +rho plugins install user/repo --trust # Uninstall a plugin -hawk plugins uninstall my-plugin +rho plugins uninstall my-plugin # Update plugins -hawk plugins update +rho plugins update ``` --- @@ -63,15 +63,15 @@ Browse and install plugins from the community marketplace: ```bash # Search marketplace -hawk plugins search go +rho plugins search go # Install from marketplace -hawk plugins install go-review --trust +rho plugins install go-review --trust ``` ### Adding Marketplace Sources -Configure additional marketplaces in `~/.hawk/settings.json`: +Configure additional marketplaces in `~/.rho/settings.json`: ```json { @@ -90,13 +90,13 @@ Configure additional marketplaces in `~/.hawk/settings.json`: ## Trust Model -- **User plugins** (`~/.hawk/plugins/`) — Trusted automatically -- **Project plugins** (`.hawk/plugins/`) — Require explicit trust before activation +- **User plugins** (`~/.rho/plugins/`) — Trusted automatically +- **Project plugins** (`.rho/plugins/`) — Require explicit trust before activation Trust is required because plugins can execute code via hooks and MCP servers. To trust a plugin: ```bash -hawk plugins install --trust +rho plugins install --trust ``` --- diff --git a/docs/user-guide/10-hooks.md b/docs/user-guide/10-hooks.md index 8553ea89e..f7430a1c6 100644 --- a/docs/user-guide/10-hooks.md +++ b/docs/user-guide/10-hooks.md @@ -1,12 +1,12 @@ # Hooks -Hooks let you run code at key moments in a Hawk session. Use them to automate tasks, enforce safety checks, log activity, and integrate custom tools. +Hooks let you run code at key moments in a Rho session. Use them to automate tasks, enforce safety checks, log activity, and integrate custom tools. --- ## What Are Hooks? -A hook is a shell command or HTTP endpoint that Hawk calls when a specific lifecycle event occurs. Hooks can: +A hook is a shell command or HTTP endpoint that Rho calls when a specific lifecycle event occurs. Hooks can: - **Block actions** — A `PreToolUse` hook can deny a dangerous command before it runs - **React to events** — A `PostToolUse` hook can log every tool execution @@ -36,7 +36,7 @@ Only `PreToolUse` can block a tool call. All other events are passive. ## Hook Configuration -Create hooks in `.hawk/hooks/hooks.json`: +Create hooks in `.rho/hooks/hooks.json`: ```json { @@ -100,8 +100,8 @@ Exit code `2` also signals denial. Any other exit code is fail-open (tool is all ## Trust Model -- **User hooks** (`~/.hawk/hooks/`) — Always trusted -- **Project hooks** (`.hawk/hooks/`) — Require folder trust +- **User hooks** (`~/.rho/hooks/`) — Always trusted +- **Project hooks** (`.rho/hooks/`) — Require folder trust Project hooks are blocked until you trust the folder: @@ -114,15 +114,15 @@ Project hooks are blocked until you trust the folder: ## Environment Variables -Hawk sets these variables for every hook: +Rho sets these variables for every hook: | Variable | Description | |----------|-------------| -| `HAWK_HOOK_EVENT` | Event name (e.g., `PreToolUse`) | -| `HAWK_SESSION_ID` | Current session ID | -| `HAWK_WORKSPACE_ROOT` | Project root path | -| `HAWK_PLUGIN_ROOT` | Plugin directory (for plugin hooks) | -| `HAWK_PLUGIN_DATA` | Plugin data directory | +| `RHO_HOOK_EVENT` | Event name (e.g., `PreToolUse`) | +| `RHO_SESSION_ID` | Current session ID | +| `RHO_WORKSPACE_ROOT` | Project root path | +| `RHO_PLUGIN_ROOT` | Plugin directory (for plugin hooks) | +| `RHO_PLUGIN_DATA` | Plugin data directory | --- @@ -130,20 +130,20 @@ Hawk sets these variables for every hook: ```bash # List hooks -hawk hooks list +rho hooks list # Trust project hooks -hawk hooks trust +rho hooks trust # Untrust project hooks -hawk hooks untrust +rho hooks untrust ``` --- ## Example: Safe Shell Guard -Create `.hawk/hooks/safety.json`: +Create `.rho/hooks/safety.json`: ```json { diff --git a/docs/user-guide/11-custom-models.md b/docs/user-guide/11-custom-models.md index 99368cfde..aee6b7596 100644 --- a/docs/user-guide/11-custom-models.md +++ b/docs/user-guide/11-custom-models.md @@ -1,12 +1,12 @@ # Custom Models -Hawk connects to custom model endpoints through Eyrie for alternative providers, self-hosted models, and overriding built-in settings. +Rho connects to custom model endpoints through Eyrie for alternative providers, self-hosted models, and overriding built-in settings. --- ## Supported Providers -Hawk works with any LLM provider. Built-in support includes: +Rho works with any LLM provider. Built-in support includes: | Provider | ID | Key | |----------|-----|-----| @@ -24,7 +24,7 @@ Hawk works with any LLM provider. Built-in support includes: ### CLI Flag ```bash -hawk -m gpt-4o -p "Hello" +rho -m gpt-4o -p "Hello" ``` ### Slash Command @@ -39,7 +39,7 @@ In the TUI: ### Config Default ```json -// ~/.hawk/settings.json +// ~/.rho/settings.json { "default_provider": "openai", "default_model": "gpt-4o" @@ -50,7 +50,7 @@ In the TUI: ## Configuring Custom Models -Add custom models in `~/.hawk/settings.json`: +Add custom models in `~/.rho/settings.json`: ```json { @@ -73,7 +73,7 @@ Add custom models in `~/.hawk/settings.json`: ### Credential Resolution -Hawk resolves credentials in this order: +Rho resolves credentials in this order: 1. Per-model `api_key` field 2. Environment variable (`env_key`) @@ -130,8 +130,8 @@ ollama pull llama-3.1-70b Enable deployment-aware routing to use Eyrie's model catalog: ```bash -export HAWK_DEPLOYMENT_ROUTING=true -hawk +export RHO_DEPLOYMENT_ROUTING=true +rho ``` Or in settings: diff --git a/docs/user-guide/12-project-rules.md b/docs/user-guide/12-project-rules.md index f8c189241..32dc93fad 100644 --- a/docs/user-guide/12-project-rules.md +++ b/docs/user-guide/12-project-rules.md @@ -1,12 +1,12 @@ # Project Rules (AGENTS.md) -Project rules let you configure Hawk per project or directory. By placing an AGENTS.md file in your repository, you can set coding conventions, build instructions, and style guides that Hawk follows automatically. +Project rules let you configure Rho per project or directory. By placing an AGENTS.md file in your repository, you can set coding conventions, build instructions, and style guides that Rho follows automatically. --- ## Supported File Names -Hawk checks for these filenames in each directory (from repo root to CWD): +Rho checks for these filenames in each directory (from repo root to CWD): - `AGENTS.md` - `Agents.md` @@ -14,26 +14,26 @@ Hawk checks for these filenames in each directory (from repo root to CWD): - `Claude.md` - `AGENT.md` -Hawk loads all matching files. Deeper files take precedence when instructions conflict. +Rho loads all matching files. Deeper files take precedence when instructions conflict. --- ## Rules Directories -Hawk also scans for `*.md` files in rules directories: +Rho also scans for `*.md` files in rules directories: | Location | Purpose | |----------|---------| -| `/.hawk/rules/` | Always scanned | +| `/.rho/rules/` | Always scanned | | `/.claude/rules/` | Claude compatibility | --- ## How Discovery Works -Hawk scans rules in this order: +Rho scans rules in this order: -1. **Global rules**: `~/.hawk/AGENTS.md` (applies to all projects) +1. **Global rules**: `~/.rho/AGENTS.md` (applies to all projects) 2. **Repo rules**: Every directory from git root to CWD (inclusive) 3. **CWD-only**: If not in a git repo, only current directory @@ -49,7 +49,7 @@ my-monorepo/ AGENTS.md # "Use Express. Follow REST conventions." ``` -When Hawk runs in `packages/frontend/`, it loads all instructions. The frontend-specific rules appear later and take precedence. +When Rho runs in `packages/frontend/`, it loads all instructions. The frontend-specific rules appear later and take precedence. --- @@ -108,7 +108,7 @@ my-monorepo/ For one-off rules without editing files: ```bash -hawk --rules "Always use TypeScript" -p "Implement feature" +rho --rules "Always use TypeScript" -p "Implement feature" ``` --- diff --git a/docs/user-guide/13-memory.md b/docs/user-guide/13-memory.md index 0db9003fc..b3d8ad9b6 100644 --- a/docs/user-guide/13-memory.md +++ b/docs/user-guide/13-memory.md @@ -1,20 +1,20 @@ # Memory -Memory lets Hawk recall facts, decisions, and patterns from earlier sessions. Hawk indexes saved information and searches it automatically, so new sessions can reuse relevant context. +Memory lets Rho recall facts, decisions, and patterns from earlier sessions. Rho indexes saved information and searches it automatically, so new sessions can reuse relevant context. --- ## What Is Memory? -Without memory, each Hawk session starts fresh. When memory is enabled, Hawk can: +Without memory, each Rho session starts fresh. When memory is enabled, Rho can: - Recall project conventions you explained before - Reuse debugging steps that worked - Carry architectural decisions forward across sessions - Avoid re-asking questions it already has answers to -Memory is a local subsystem built into Hawk. It stores memories as files under -Hawk's state directory; no external service is required. +Memory is a local subsystem built into Rho. It stores memories as files under +Rho's state directory; no external service is required. --- @@ -29,7 +29,7 @@ Memory is on by default. To inspect what has been stored: ### Settings ```json -// ~/.hawk/settings.json +// ~/.rho/settings.json { "memory": { "enabled": true @@ -41,14 +41,14 @@ Memory is on by default. To inspect what has been stored: ## How Memory Is Stored -Memory is stored under Hawk's state directory (`~/.hawk/` by default). +Memory is stored under Rho's state directory (`~/.rho/` by default). | Location | Scope | Description | |----------|-------|-------------| -| `~/.hawk/memories/` | Global | Core memories and preferences | -| `~/.hawk/` state files | Workspace | Project-specific memory and lessons | +| `~/.rho/memories/` | Global | Core memories and preferences | +| `~/.rho/` state files | Workspace | Project-specific memory and lessons | -Hawk combines core memories, automatic captures, evolving guidelines, and +Rho combines core memories, automatic captures, evolving guidelines, and retrieval metrics to rank what is relevant to the current turn. --- @@ -57,17 +57,17 @@ retrieval metrics to rank what is relevant to the current turn. ### Remember -Ask Hawk to remember something: +Ask Rho to remember something: ``` /remember always open PR links after pushing ``` -Hawk records entries as durable statements organized by topic. +Rho records entries as durable statements organized by topic. ### Forget -Ask what Hawk should forget: +Ask what Rho should forget: ``` /forget the snake_case convention @@ -77,13 +77,13 @@ Forget is best-effort. ### Recall -Ask what Hawk remembers: +Ask what Rho remembers: ``` /what do you remember about auth? ``` -Hawk searches across all memory sources and summarizes. +Rho searches across all memory sources and summarizes. --- @@ -99,7 +99,7 @@ Lessons learned are surfaced through: ## First-Turn Injection -On the first turn of each session, Hawk automatically searches memory for content relevant to the current project and injects it as context. +On the first turn of each session, Rho automatically searches memory for content relevant to the current project and injects it as context. Configure injection: diff --git a/docs/user-guide/14-headless-mode.md b/docs/user-guide/14-headless-mode.md index 12ce192cd..8d7d43a38 100644 --- a/docs/user-guide/14-headless-mode.md +++ b/docs/user-guide/14-headless-mode.md @@ -1,6 +1,6 @@ # Headless Mode and Scripting -Headless mode runs Hawk non-interactively from the command line. It accepts a prompt, executes tools, and returns results — ideal for automation and CI/CD. +Headless mode runs Rho non-interactively from the command line. It accepts a prompt, executes tools, and returns results — ideal for automation and CI/CD. --- @@ -9,10 +9,10 @@ Headless mode runs Hawk non-interactively from the command line. It accepts a pr Pass a prompt to run headless: ```bash -hawk -p "Your prompt here" +rho -p "Your prompt here" ``` -Hawk processes the prompt and prints the result to stdout. +Rho processes the prompt and prints the result to stdout. --- @@ -23,7 +23,7 @@ Hawk processes the prompt and prints the result to stdout. Human-readable text: ```bash -hawk -p "Summarize this codebase" +rho -p "Summarize this codebase" ``` ### json @@ -31,7 +31,7 @@ hawk -p "Summarize this codebase" Single JSON object after completion: ```bash -hawk -p "Summarize this codebase" --output-format json | jq -r '.response' +rho -p "Summarize this codebase" --output-format json | jq -r '.response' ``` Output includes: @@ -44,7 +44,7 @@ Output includes: NDJSON events in real time: ```bash -hawk -p "Summarize" --output-format stream-json | jq -r 'select(.type=="content") | .content' +rho -p "Summarize" --output-format stream-json | jq -r 'select(.type=="content") | .content' ``` Event types: @@ -59,20 +59,20 @@ Event types: ### Named Session -Each `hawk -p` creates a fresh session by default. To continue a session: +Each `rho -p` creates a fresh session by default. To continue a session: ```bash # Get session ID -hawk -p "Initial prompt" --output-format json | jq -r '.sessionId' +rho -p "Initial prompt" --output-format json | jq -r '.sessionId' # Resume the session -hawk -p "Follow-up" --resume +rho -p "Follow-up" --resume ``` ### Continue Most Recent ```bash -hawk -p "Continue" --continue +rho -p "Continue" --continue ``` --- @@ -83,10 +83,10 @@ Restrict available tools: ```bash # Allow only read tools -hawk -p "Explain this" --tools "Read,Grep,LS" +rho -p "Explain this" --tools "Read,Grep,LS" # Deny specific tools -hawk -p "Review" --disallowed-tools "Bash,WebSearch" +rho -p "Review" --disallowed-tools "Bash,WebSearch" ``` --- @@ -97,10 +97,10 @@ Control tool permissions: ```bash # Allow shell commands through the explicit tool policy flag -hawk -p "Build" --allowed-tools "Bash(git:*) Bash(npm:*)" +rho -p "Build" --allowed-tools "Bash(git:*) Bash(npm:*)" # Deny dangerous commands -hawk -p "Clean" --disallowed-tools "Bash(rm:*) Bash(sudo:*)" +rho -p "Clean" --disallowed-tools "Bash(rm:*) Bash(sudo:*)" ``` --- @@ -110,8 +110,8 @@ hawk -p "Clean" --disallowed-tools "Bash(rm:*) Bash(sudo:*)" Use `--auto` for fully automated runs: ```bash -hawk -p "Format all files" --dangerously-skip-permissions -hawk exec --auto full "Add error handling" +rho -p "Format all files" --dangerously-skip-permissions +rho exec --auto full "Add error handling" ``` **Warning:** This grants full autonomy. Use only in trusted environments. @@ -124,14 +124,14 @@ hawk exec --auto full "Add error handling" ```bash #!/bin/bash -hawk -p "Review staged changes for bugs. Reply OK if fine." \ +rho -p "Review staged changes for bugs. Reply OK if fine." \ --dangerously-skip-permissions --output-format json | jq -r '.response' | grep -q "^OK" || exit 1 ``` ### Code Review ```bash -hawk -p "Review PR for security issues" \ +rho -p "Review PR for security issues" \ --output-format json --dangerously-skip-permissions | jq -r '.response' > review.md ``` @@ -139,7 +139,7 @@ hawk -p "Review PR for security issues" \ ```bash for file in src/*.go; do - hawk -p "Format $file" --auto + rho -p "Format $file" --auto done ``` @@ -149,8 +149,8 @@ done ```bash export XAI_API_KEY="xai-..." # API key -export HAWK_HOME="/path" # Custom config location -export HAWK_LOG_FILE="/tmp/hawk.log" # Log file +export RHO_HOME="/path" # Custom config location +export RHO_LOG_FILE="/tmp/rho.log" # Log file ``` --- diff --git a/docs/user-guide/15-agent-mode.md b/docs/user-guide/15-agent-mode.md index c4133d8cf..6837cdb96 100644 --- a/docs/user-guide/15-agent-mode.md +++ b/docs/user-guide/15-agent-mode.md @@ -1,6 +1,6 @@ # Agent Mode (ACP) and IDE Integration -Agent mode runs Hawk as an ACP (Agent Client Protocol) server for IDE integration and custom tooling. +Agent mode runs Rho as an ACP (Agent Client Protocol) server for IDE integration and custom tooling. --- @@ -17,10 +17,10 @@ The [Agent Client Protocol (ACP)](https://agentclientprotocol.com) is a standard ## stdio Transport -Run Hawk as an ACP server over stdio: +Run Rho as an ACP server over stdio: ```bash -hawk agent stdio +rho agent stdio ``` Clients include: @@ -31,9 +31,9 @@ Clients include: ### Options ```bash -hawk agent --model gpt-4o stdio -hawk agent --auto stdio -hawk agent --agent-profile path/to/profile.yaml stdio +rho agent --model gpt-4o stdio +rho agent --auto stdio +rho agent --agent-profile path/to/profile.yaml stdio ``` | Flag | Description | @@ -49,7 +49,7 @@ hawk agent --agent-profile path/to/profile.yaml stdio ### WebSocket Server ```bash -hawk agent serve --bind 127.0.0.1:2419 --secret +rho agent serve --bind 127.0.0.1:2419 --secret ``` Connect clients over WebSocket using the secret token for authentication. @@ -81,7 +81,7 @@ ACP streams structured events with `sessionUpdate` types: ## Extension Methods -Hawk provides `x.ai/*` extension methods: +Rho provides `x.ai/*` extension methods: | Category | Methods | |----------|---------| @@ -110,7 +110,7 @@ Hawk provides `x.ai/*` extension methods: import { spawn } from "child_process"; // Start ACP server -const proc = spawn("hawk", ["agent", "stdio"]); +const proc = spawn("rho", ["agent", "stdio"]); // Initialize proc.stdin.write(JSON.stringify({ diff --git a/docs/user-guide/16-subagents.md b/docs/user-guide/16-subagents.md index c7ebd5830..2790aa8df 100644 --- a/docs/user-guide/16-subagents.md +++ b/docs/user-guide/16-subagents.md @@ -8,7 +8,7 @@ Subagents are enabled by default. ## How Subagents Work -When Hawk needs to delegate work, it spawns a child session using the `Agent` tool. The child runs with: +When Rho needs to delegate work, it spawns a child session using the `Agent` tool. The child runs with: - Its own context window (isolated from parent) - A toolset determined by its type and capability mode @@ -74,7 +74,7 @@ This creates an isolated git worktree, preventing conflicts with the parent sess ## Personas -Personas are behavioral overlays applied to subagents. Define them in `~/.hawk/settings.json`: +Personas are behavioral overlays applied to subagents. Define them in `~/.rho/settings.json`: ```json { diff --git a/docs/user-guide/17-sessions.md b/docs/user-guide/17-sessions.md index 77d650bec..639923568 100644 --- a/docs/user-guide/17-sessions.md +++ b/docs/user-guide/17-sessions.md @@ -1,6 +1,6 @@ # Sessions -Hawk saves every conversation to disk automatically. Whether you work in the TUI, in headless mode, or over ACP, Hawk records the exchange as a session. +Rho saves every conversation to disk automatically. Whether you work in the TUI, in headless mode, or over ACP, Rho records the exchange as a session. --- @@ -14,7 +14,7 @@ A session is a persistent conversation with full history: - Token usage and turn counts - Subagent sessions -Sessions are identified by a unique session ID and stored under `~/.hawk/sessions/`. +Sessions are identified by a unique session ID and stored under `~/.rho/sessions/`. --- @@ -36,7 +36,7 @@ This clears the current context and starts fresh. Alias: `/exit` -To leave the session but stay in Hawk: +To leave the session but stay in Rho: ``` /home @@ -58,13 +58,13 @@ Opens a session picker. Select a session to resume. ```bash # Resume specific session -hawk --resume +rho --resume # Continue most recent -hawk --continue +rho --continue # New session with specific ID -hawk --session-id -p "prompt" +rho --session-id -p "prompt" ``` --- @@ -122,10 +122,10 @@ Maintain context across headless calls: ```bash # Start and capture ID -ID=$(hawk -p "First" --output-format json | jq -r '.sessionId') +ID=$(rho -p "First" --output-format json | jq -r '.sessionId') # Continue -hawk -p "Second" --resume "$ID" +rho -p "Second" --resume "$ID" ``` --- diff --git a/docs/user-guide/18-execution-and-permissions.md b/docs/user-guide/18-execution-and-permissions.md index 243bbc3a2..4f0e65ab5 100644 --- a/docs/user-guide/18-execution-and-permissions.md +++ b/docs/user-guide/18-execution-and-permissions.md @@ -1,6 +1,6 @@ # Execution & Permissions -Hawk executes agent commands directly on the host. There is no container or +Rho executes agent commands directly on the host. There is no container or OS-level sandbox layer; the safety boundary is the permission engine plus the tool-level guards described below. @@ -9,7 +9,7 @@ tool-level guards described below. ## Quick Start ```bash -# Set the autonomy tier (how much Hawk may do without asking) +# Set the autonomy tier (how much Rho may do without asking) /autonomy tier builder # Allow or deny specific tool rules @@ -39,14 +39,14 @@ for the full model. ## Tool-Level Guards -Independent of the autonomy tier, Hawk always enforces: +Independent of the autonomy tier, Rho always enforces: - **Destructive-command blocking** — patterns like `rm -rf /` are hard-blocked in Bash and PowerShell. - **Path guard** — file tools are restricted to the working directory and any directories added with `--add-dir` / `/add-dir`. - **Sensitive-path protection** — credential files (`~/.ssh/*`, provider state, - Hawk env files) are blocked for Read/Edit/Write. + Rho env files) are blocked for Read/Edit/Write. - **Environment scrubbing** — provider API keys are removed from child-process environments. diff --git a/docs/user-guide/19-plan-mode.md b/docs/user-guide/19-plan-mode.md index 39b8155e4..bb1bdc2c8 100644 --- a/docs/user-guide/19-plan-mode.md +++ b/docs/user-guide/19-plan-mode.md @@ -1,16 +1,16 @@ # Plan Mode -Plan mode is a structured planning phase where Hawk explores the codebase and designs an implementation before writing code. Use it when tasks have genuine ambiguity about the right approach. +Plan mode is a structured planning phase where Rho explores the codebase and designs an implementation before writing code. Use it when tasks have genuine ambiguity about the right approach. --- ## What Plan Mode Does -When plan mode is active, Hawk: +When plan mode is active, Rho: 1. Reads and searches the codebase to understand patterns 2. Designs an implementation approach -3. Writes the plan to a file under `.hawk/specs/` +3. Writes the plan to a file under `.rho/specs/` 4. May ask clarifying questions via `/ask` 5. Calls `exit_plan_mode` to request approval @@ -45,7 +45,7 @@ Or press **Ctrl+Shift+P** to cycle modes. ## The Plan File -Plans are written to `.hawk/specs//plan.md`: +Plans are written to `.rho/specs//plan.md`: - **Context** — Why the change is needed - **Approach** — Recommended implementation strategy @@ -56,7 +56,7 @@ Plans are written to `.hawk/specs//plan.md`: ## Plan Approval -When planning completes, Hawk opens a preview with action bar: +When planning completes, Rho opens a preview with action bar: | Key | Action | |-----|--------| diff --git a/docs/user-guide/20-background-tasks.md b/docs/user-guide/20-background-tasks.md index c96e06285..57d97792f 100644 --- a/docs/user-guide/20-background-tasks.md +++ b/docs/user-guide/20-background-tasks.md @@ -1,6 +1,6 @@ # Background Tasks and Monitoring -Hawk runs long-lived processes without blocking the conversation. This covers background commands, `/loop`, and the `monitor` tool. +Rho runs long-lived processes without blocking the conversation. This covers background commands, `/loop`, and the `monitor` tool. --- diff --git a/docs/user-guide/21-terminal-support.md b/docs/user-guide/21-terminal-support.md index 7ef8ee0ec..a268d926f 100644 --- a/docs/user-guide/21-terminal-support.md +++ b/docs/user-guide/21-terminal-support.md @@ -1,6 +1,6 @@ # Terminal Support and Troubleshooting -Hawk runs as a full-screen TUI powered by Bubble Tea. This covers terminal compatibility and common fixes. +Rho runs as a full-screen TUI powered by Bubble Tea. This covers terminal compatibility and common fixes. --- @@ -24,7 +24,7 @@ set -as terminal-features ",*:RGB" ## Terminal Detection -Hawk detects these terminals: +Rho detects these terminals: - Apple Terminal - iTerm2 @@ -61,7 +61,7 @@ Run `/terminal-setup` for diagnostics. **Cause**: Zellij, tmux control mode, or config. -**Fix**: Set in `~/.hawk/settings.json`: +**Fix**: Set in `~/.rho/settings.json`: ```json { "terminal": { "alt_screen": "always" } } @@ -128,7 +128,7 @@ set -g set-clipboard on ## Diagnostics -Run in Hawk: +Run in Rho: ``` /terminal-setup diff --git a/docs/user-guide/22-permissions-and-safety.md b/docs/user-guide/22-permissions-and-safety.md index 18a062f24..c87e29dc2 100644 --- a/docs/user-guide/22-permissions-and-safety.md +++ b/docs/user-guide/22-permissions-and-safety.md @@ -1,6 +1,6 @@ # Permissions and Safety Controls -Hawk can read files, edit code, and run shell commands. The permission system controls what the agent is allowed to do. +Rho can read files, edit code, and run shell commands. The permission system controls what the agent is allowed to do. --- diff --git a/docs/user-guide/23-dashboard.md b/docs/user-guide/23-dashboard.md index 98ae715b5..ec392b0ac 100644 --- a/docs/user-guide/23-dashboard.md +++ b/docs/user-guide/23-dashboard.md @@ -1,6 +1,6 @@ # Dashboard and HUD -The Hawk dashboard provides system status and monitoring information. +The Rho dashboard provides system status and monitoring information. --- @@ -18,7 +18,7 @@ In the TUI: ## Ecosystem Status -Shows the status of all Hawk components: +Shows the status of all Rho components: | Component | Status | |-----------|--------| @@ -33,7 +33,7 @@ Shows the status of all Hawk components: Check readiness to chat: ```bash -hawk path +rho path ``` This verifies: diff --git a/docs/user-guide/24-monitoring-usage.md b/docs/user-guide/24-monitoring-usage.md index 40f3e29ba..eee140720 100644 --- a/docs/user-guide/24-monitoring-usage.md +++ b/docs/user-guide/24-monitoring-usage.md @@ -1,6 +1,6 @@ # Monitoring and Usage -Hawk tracks token usage, costs, and session activity for monitoring and debugging. +Rho tracks token usage, costs, and session activity for monitoring and debugging. --- @@ -11,8 +11,8 @@ Token usage is tracked per session and aggregated. ### View Usage ```bash -hawk usage -hawk doctor # Full health report +rho usage +rho doctor # Full health report ``` Usage shows: @@ -24,12 +24,12 @@ Usage shows: ## Telemetry -Hawk can send anonymous usage telemetry. +Rho can send anonymous usage telemetry. ### Enable/Disable ```json -// ~/.hawk/settings.json +// ~/.rho/settings.json { "telemetry": { "enabled": false @@ -40,7 +40,7 @@ Hawk can send anonymous usage telemetry. Or: ```bash -hawk --telemetry # Enable +rho --telemetry # Enable haw --no-telemetry # Disable ``` @@ -77,7 +77,7 @@ Accessible via `/ecosystem` in TUI. ## Cloud Integration -Hawk Cloud provides managed usage tracking: +Rho Cloud provides managed usage tracking: - Organization-level metrics - Budget alerts diff --git a/docs/user-guide/25-shell-completions.md b/docs/user-guide/25-shell-completions.md index f40480c70..d13c8e0ed 100644 --- a/docs/user-guide/25-shell-completions.md +++ b/docs/user-guide/25-shell-completions.md @@ -1,15 +1,15 @@ # Shell Completions -hawk ships completion scripts for **bash**, **zsh**, **fish**, and **PowerShell**, +rho ships completion scripts for **bash**, **zsh**, **fish**, and **PowerShell**, plus a machine-readable **JSON** spec for IDE integration. ## Quick Install ```bash # Auto-install to the standard location for your shell and OS: -hawk completion install bash -hawk completion install zsh -hawk completion install fish +rho completion install bash +rho completion install zsh +rho completion install fish ``` ## Manual Setup @@ -18,50 +18,50 @@ hawk completion install fish ```bash # Load for current session: -source <(hawk completion bash) +source <(rho completion bash) # Persist (Linux): -hawk completion bash > ~/.local/share/bash-completion/completions/hawk +rho completion bash > ~/.local/share/bash-completion/completions/rho # Persist (macOS with Homebrew): -hawk completion bash > /opt/homebrew/etc/bash_completion.d/hawk +rho completion bash > /opt/homebrew/etc/bash_completion.d/rho ``` ### Zsh ```bash # Load for current session: -source <(hawk completion zsh) +source <(rho completion zsh) # Persist: -hawk completion zsh > "${fpath[1]}/_hawk" +rho completion zsh > "${fpath[1]}/_rho" ``` ### Fish ```bash # Load for current session: -hawk completion fish | source +rho completion fish | source # Persist: -hawk completion fish > ~/.config/fish/completions/hawk.fish +rho completion fish > ~/.config/fish/completions/rho.fish ``` ### PowerShell ```powershell # Load for current session: -hawk completion powershell | Out-String | Invoke-Expression +rho completion powershell | Out-String | Invoke-Expression # Persist: add to your $PROFILE -hawk completion powershell > hawk.ps1 -. ./hawk.ps1 +rho completion powershell > rho.ps1 +. ./rho.ps1 ``` ## JSON Spec (IDE Integration) ```bash -hawk completion json +rho completion json ``` Prints a machine-readable command/flag spec that IDEs and editor plugins can @@ -69,10 +69,10 @@ consume for inline completions without shell integration. ## Install Paths -`hawk completion install` resolves the correct path automatically: +`rho completion install` resolves the correct path automatically: | Shell | Linux | macOS (Homebrew) | |-------|-------|-------------------| -| bash | `~/.local/share/bash-completion/completions/hawk` | `/opt/homebrew/etc/bash_completion.d/hawk` | -| zsh | First `$fpath` entry (e.g. `/usr/local/share/zsh/site-functions/_hawk`) | Same | -| fish | `~/.config/fish/completions/hawk.fish` | Same | +| bash | `~/.local/share/bash-completion/completions/rho` | `/opt/homebrew/etc/bash_completion.d/rho` | +| zsh | First `$fpath` entry (e.g. `/usr/local/share/zsh/site-functions/_rho`) | Same | +| fish | `~/.config/fish/completions/rho.fish` | Same | diff --git a/docs/user-guide/26-learned-preferences.md b/docs/user-guide/26-learned-preferences.md index 1962fcdc2..10ca692f4 100644 --- a/docs/user-guide/26-learned-preferences.md +++ b/docs/user-guide/26-learned-preferences.md @@ -1,6 +1,6 @@ # Learned Preferences -Hawk can learn coding-style tendencies from feedback through its taste system. +Rho can learn coding-style tendencies from feedback through its taste system. These preferences are advisory context, not policy. ## Policy Boundaries diff --git a/docs/user-guide/27-workflows.md b/docs/user-guide/27-workflows.md index 76e7a7a86..1ee5b0742 100644 --- a/docs/user-guide/27-workflows.md +++ b/docs/user-guide/27-workflows.md @@ -2,7 +2,7 @@ ## Interactive Input -- `/command` invokes a Hawk slash command. +- `/command` invokes a Rho slash command. - `!command` runs a direct shell command when the current permission mode allows it. - `@path` adds file or directory context. - `Esc` interrupts the current operation. @@ -13,7 +13,7 @@ Use a bounded, machine-readable review in CI: ```bash -hawk review run HEAD --output-format json --max-turns 8 +rho review run HEAD --output-format json --max-turns 8 ``` Review findings remain structured and severity-based. Provider retries, @@ -22,7 +22,7 @@ permissions, and tool timeouts are still enforced in headless mode. ## MCP-Backed Analysis 1. Establish project trust. -2. Merlin configured MCP servers with `hawk mcp`. +2. Merlin configured MCP servers with `rho mcp`. 3. Run the review or scan command. 4. Check the persisted findings and event output. @@ -31,7 +31,7 @@ Do not bypass trust or permission controls to make an MCP tool convenient. ## Session Recovery Use `/session`, `/resume`, `/continue`, `/checkpoint`, and `/rewind` to recover -from interruptions. Hawk trims incomplete tool turns before a cancelled session +from interruptions. Rho trims incomplete tool turns before a cancelled session is reused, preserving provider transcript invariants. ## Recording and Replay diff --git a/docs/user-guide/28-workflow-budgets.md b/docs/user-guide/28-workflow-budgets.md index 1f2ab15ef..8197a23e7 100644 --- a/docs/user-guide/28-workflow-budgets.md +++ b/docs/user-guide/28-workflow-budgets.md @@ -1,6 +1,6 @@ # Workflow Budgets -Hawk exposes several independent limits. Configure the smallest useful scope +Rho exposes several independent limits. Configure the smallest useful scope for automation and distinguish them when diagnosing termination. | Budget | Limits | Purpose | diff --git a/docs/versioning.md b/docs/versioning.md index 49c9708bd..fe7d944d2 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -12,7 +12,7 @@ repository that follows this layout. Adopted 2026-05-14. ## Pattern by repo type -### Go binaries (`hawk`) +### Go binaries (`rho`) - `VERSION` at the repo root. - A version package (`internal/version` for binaries, or `main` itself) declares @@ -90,7 +90,7 @@ func init() { pattern = "^(?P[^\\s]+)" [tool.hatch.build.targets.wheel] - force-include = { "VERSION" = "hawk/VERSION" } + force-include = { "VERSION" = "rho/VERSION" } ``` - `_version.py` reads the same `VERSION` file at runtime via `pathlib`, so `__version__` matches the package metadata both in source checkouts and in diff --git a/ecosystem.yaml b/ecosystem.yaml index b48e65d98..9a8146a85 100644 --- a/ecosystem.yaml +++ b/ecosystem.yaml @@ -6,17 +6,17 @@ schema_version: 1 # file instead of carrying its own repo-name list. Only repositories that # exist are listed: removed siblings stay out until they are restored. contracts: - portable_graph_schema: hawk.graph/v1 + portable_graph_schema: rho.graph/v1 cloud_graph_schema: graycode-cloud.graph/v1 usage_event: usage.recorded.v1 repositories: - - directory: hawk - github_repo: hawk - product_name: Hawk + - directory: rho + github_repo: rho + product_name: Rho kind: product language: go - module: github.com/GrayCodeAI/hawk + module: github.com/GrayCodeAI/rho workspace: true - directory: eyrie @@ -30,7 +30,7 @@ repositories: - directory: graycode-skills github_repo: graycode-skills - product_name: Hawk Community Skills + product_name: Rho Community Skills kind: extension language: python workspace: false diff --git a/examples/README.md b/examples/README.md index bc809e223..4c045ac78 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,26 +1,26 @@ -# Hawk Examples +# Rho Examples -Hawk is an AI coding agent that understands your codebase. +Rho is an AI coding agent that understands your codebase. ## Basic Usage ### Start a chat session ```bash -hawk chat +rho chat > Explain how the authentication system works ``` ### Generate code ```bash -hawk "Add input validation to the user registration endpoint" +rho "Add input validation to the user registration endpoint" ``` ### Review changes ```bash -hawk review +rho review ``` ## Advanced Examples @@ -28,52 +28,52 @@ hawk review ### Use with skills ```bash -hawk --skill code-review "Review my latest changes" +rho --skill code-review "Review my latest changes" ``` ### Analyze codebase ```bash -hawk analyze --depth full +rho analyze --depth full ``` ### Fix issues ```bash -hawk fix --auto +rho fix --auto ``` ### Headless agent in CI -Copy [hawk-ci-exec.yml](github/hawk-ci-exec.yml) to run `hawk exec --ephemeral --json` +Copy [rho-ci-exec.yml](github/rho-ci-exec.yml) to run `rho exec --ephemeral --json` on pull requests (summarize diff, risk list, or your own prompt). Pin your -hawk install step and provider secrets before enabling the job. +rho install step and provider secrets before enabling the job. ### Report CI delivery context -Copy [hawk-delivery-context.yml](github/hawk-delivery-context.yml) to your -repository to report GitHub Actions runs to Hawk Cloud. Create a dedicated, +Copy [rho-delivery-context.yml](github/rho-delivery-context.yml) to your +repository to report GitHub Actions runs to Rho Cloud. Create a dedicated, revocable device token for CI and keep its endpoint, device ID, project ID, and token in GitHub Actions secrets. ## MCP Integration -Hawk can use MCP servers for extended capabilities: +Rho can use MCP servers for extended capabilities: ```bash # With harrier for persistent memory harrier setup -hawk chat +rho chat # With swift for session capture swift start -hawk "refactor the API layer" +rho "refactor the API layer" swift stop ``` ## Configuration -Create `.hawk/config.json`: +Create `.rho/config.json`: ```json { diff --git a/examples/github/hawk-delivery-context.yml b/examples/github/hawk-delivery-context.yml deleted file mode 100644 index 343218d50..000000000 --- a/examples/github/hawk-delivery-context.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Hawk Delivery Context -# -# Copy to .github/workflows/hawk-delivery-context.yml. This workflow assumes -# `hawk` is available on PATH (install it using your team's normal release -# mechanism before this reporting step). -# -# Create a dedicated, revocable Hawk Cloud device token for CI. Do not reuse a -# developer's local device token. Store all three values as repository secrets. -name: Hawk Delivery Context - -on: - push: - branches: [main] - -permissions: - contents: read - -jobs: - report-delivery: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - # Install Hawk here using the release package your organization pins. - # The remaining steps are intentionally independent of the installer. - - name: Connect CI device to Hawk Cloud - run: | - hawk cloud connect \ - --endpoint "$HAWK_CLOUD_URL" \ - --device-id "$HAWK_CLOUD_DEVICE_ID" \ - --project-id "$HAWK_CLOUD_PROJECT_ID" \ - --token "$HAWK_CLOUD_DEVICE_TOKEN" - env: - HAWK_CLOUD_URL: ${{ secrets.HAWK_CLOUD_URL }} - HAWK_CLOUD_DEVICE_ID: ${{ secrets.HAWK_CLOUD_DEVICE_ID }} - HAWK_CLOUD_PROJECT_ID: ${{ secrets.HAWK_CLOUD_PROJECT_ID }} - HAWK_CLOUD_DEVICE_TOKEN: ${{ secrets.HAWK_CLOUD_DEVICE_TOKEN }} - - - name: Report successful CI run - if: ${{ success() }} - run: hawk cloud context --ci-status succeeded - - - name: Report failed CI run - if: ${{ failure() }} - run: hawk cloud context --ci-status failed diff --git a/examples/github/hawk-ci-exec.yml b/examples/github/rho-ci-exec.yml similarity index 58% rename from examples/github/hawk-ci-exec.yml rename to examples/github/rho-ci-exec.yml index e15e35101..0006b726b 100644 --- a/examples/github/hawk-ci-exec.yml +++ b/examples/github/rho-ci-exec.yml @@ -1,15 +1,15 @@ -# Hawk CI agent (headless) +# Rho CI agent (headless) # -# Copy to .github/workflows/hawk-ci-exec.yml. +# Copy to .github/workflows/rho-ci-exec.yml. # Runs a non-interactive agent turn with JSON output — suitable for # "fix failing tests", "summarize this PR", or similar CI jobs. # # Prerequisites: -# - hawk binary on PATH (pin a release or install from your org's package) +# - rho binary on PATH (pin a release or install from your org's package) # - LLM credentials via secrets (never commit keys) -# - Docker available on the runner if your hawk build requires container isolation +# - Docker available on the runner if your rho build requires container isolation # -name: Hawk CI Exec +name: Rho CI Exec on: pull_request: @@ -26,27 +26,27 @@ permissions: pull-requests: read jobs: - hawk-exec: + rho-exec: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - # Install hawk with your org's preferred mechanism, for example: + # Install rho with your org's preferred mechanism, for example: # - curl -fsSL https://…/install.sh | sh - # - go install github.com/GrayCodeAI/hawk/cmd/hawk@vX.Y.Z + # - go install github.com/GrayCodeAI/rho/cmd/rho@vX.Y.Z # - download a release asset into $GITHUB_PATH - - name: Ensure hawk is available + - name: Ensure rho is available run: | - if ! command -v hawk >/dev/null 2>&1; then - echo "Install hawk before this step (see comments in this workflow)." + if ! command -v rho >/dev/null 2>&1; then + echo "Install rho before this step (see comments in this workflow)." exit 1 fi - hawk version || hawk --version || true + rho version || rho --version || true - name: Trust workspace (folder trust) - run: hawk trust add --reason "github-actions ci" || true + run: rho trust add --reason "github-actions ci" || true - name: Run agent (ephemeral + JSON) id: agent @@ -54,20 +54,20 @@ jobs: # Map your provider key(s) here, e.g. ANTHROPIC_API_KEY / OPENAI_API_KEY. ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - HAWK_PROMPT: ${{ github.event.inputs.prompt || 'Summarize the changes in this PR (git log/diff) and list risks.' }} + RHO_PROMPT: ${{ github.event.inputs.prompt || 'Summarize the changes in this PR (git log/diff) and list risks.' }} run: | set -euo pipefail - PROMPT="${HAWK_PROMPT}" - hawk exec \ + PROMPT="${RHO_PROMPT}" + rho exec \ --ephemeral \ --json \ --auto full \ --max-turns 20 \ - "$PROMPT" | tee hawk-result.json + "$PROMPT" | tee rho-result.json - name: Upload agent result if: always() uses: actions/upload-artifact@v4 with: - name: hawk-result - path: hawk-result.json + name: rho-result + path: rho-result.json diff --git a/examples/github/rho-delivery-context.yml b/examples/github/rho-delivery-context.yml new file mode 100644 index 000000000..dfcd1b864 --- /dev/null +++ b/examples/github/rho-delivery-context.yml @@ -0,0 +1,47 @@ +# Rho Delivery Context +# +# Copy to .github/workflows/rho-delivery-context.yml. This workflow assumes +# `rho` is available on PATH (install it using your team's normal release +# mechanism before this reporting step). +# +# Create a dedicated, revocable Rho Cloud device token for CI. Do not reuse a +# developer's local device token. Store all three values as repository secrets. +name: Rho Delivery Context + +on: + push: + branches: [main] + +permissions: + contents: read + +jobs: + report-delivery: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + # Install Rho here using the release package your organization pins. + # The remaining steps are intentionally independent of the installer. + - name: Connect CI device to Rho Cloud + run: | + rho cloud connect \ + --endpoint "$RHO_CLOUD_URL" \ + --device-id "$RHO_CLOUD_DEVICE_ID" \ + --project-id "$RHO_CLOUD_PROJECT_ID" \ + --token "$RHO_CLOUD_DEVICE_TOKEN" + env: + RHO_CLOUD_URL: ${{ secrets.RHO_CLOUD_URL }} + RHO_CLOUD_DEVICE_ID: ${{ secrets.RHO_CLOUD_DEVICE_ID }} + RHO_CLOUD_PROJECT_ID: ${{ secrets.RHO_CLOUD_PROJECT_ID }} + RHO_CLOUD_DEVICE_TOKEN: ${{ secrets.RHO_CLOUD_DEVICE_TOKEN }} + + - name: Report successful CI run + if: ${{ success() }} + run: rho cloud context --ci-status succeeded + + - name: Report failed CI run + if: ${{ failure() }} + run: rho cloud context --ci-status failed diff --git a/examples/github/hawk-issue-resolver.yml b/examples/github/rho-issue-resolver.yml similarity index 57% rename from examples/github/hawk-issue-resolver.yml rename to examples/github/rho-issue-resolver.yml index 9a1724de0..4466ae0e3 100644 --- a/examples/github/hawk-issue-resolver.yml +++ b/examples/github/rho-issue-resolver.yml @@ -1,16 +1,16 @@ -# Hawk Issue Resolver +# Rho Issue Resolver # -# Copy this file to .github/workflows/hawk-issue-resolver.yml in your repository. +# Copy this file to .github/workflows/rho-issue-resolver.yml in your repository. # -# It runs the Hawk agent in two scenarios: -# 1. Automation mode - when an issue is labeled `hawk-agent`, Hawk reads the +# It runs the Rho agent in two scenarios: +# 1. Automation mode - when an issue is labeled `rho-agent`, Rho reads the # issue title + body and attempts to resolve it autonomously. -# 2. Interactive mode - when someone comments "@hawk ..." on an issue or PR, -# Hawk replies conversationally to the request. +# 2. Interactive mode - when someone comments "@rho ..." on an issue or PR, +# Rho replies conversationally to the request. # -# The mode is auto-detected by `hawk exec` from the GitHub event payload, so a +# The mode is auto-detected by `rho exec` from the GitHub event payload, so a # single composite action handles both paths. -name: Hawk Issue Resolver +name: Rho Issue Resolver on: issues: @@ -26,12 +26,12 @@ permissions: pull-requests: write jobs: - hawk: - # Run on the `hawk-agent` label, or on any comment mentioning @hawk. + rho: + # Run on the `rho-agent` label, or on any comment mentioning @rho. if: >- - (github.event_name == 'issues' && github.event.label.name == 'hawk-agent') || - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@hawk')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@hawk')) + (github.event_name == 'issues' && github.event.label.name == 'rho-agent') || + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@rho')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@rho')) runs-on: ubuntu-latest steps: - name: Checkout @@ -39,11 +39,11 @@ jobs: with: fetch-depth: 0 - - name: Run Hawk - id: hawk - uses: GrayCodeAI/hawk/.github/actions/hawk@main + - name: Run Rho + id: rho + uses: GrayCodeAI/rho/.github/actions/rho@main with: - # Leave prompt empty so Hawk derives it from the triggering event. + # Leave prompt empty so Rho derives it from the triggering event. # Set a "/skill-name args" prompt here to dispatch a skill instead. prompt: "" auto: semi @@ -52,12 +52,12 @@ jobs: # Provide your model provider credentials as repository secrets. ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - - name: Post Hawk response as a comment - if: ${{ steps.hawk.outputs.response != '' }} + - name: Post Rho response as a comment + if: ${{ steps.rho.outputs.response != '' }} uses: actions/github-script@v7 with: script: | - const body = `🦅 **Hawk**\n\n${{ toJSON(steps.hawk.outputs.response) }}`; + const body = `🦅 **Rho**\n\n${{ toJSON(steps.rho.outputs.response) }}`; const issueNumber = context.issue.number; if (issueNumber) { await github.rest.issues.createComment({ diff --git a/flake.nix b/flake.nix index 79207cb5b..de0af69e1 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "Hawk - AI coding agent powered by eyrie"; + description = "Rho - AI coding agent powered by eyrie"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -53,8 +53,8 @@ ) siblings)} ''; - hawk = pkgs.buildGoModule rec { - pname = "hawk"; + rho = pkgs.buildGoModule rec { + pname = "rho"; version = "0.0.1"; src = ./.; @@ -96,7 +96,7 @@ meta = with lib; { description = "AI coding agent that reads, writes, and runs code in your terminal"; - homepage = "https://github.com/GrayCodeAI/hawk"; + homepage = "https://github.com/GrayCodeAI/rho"; license = licenses.mit; maintainers = [ ]; }; @@ -104,8 +104,8 @@ in { packages = { - default = hawk; - inherit hawk; + default = rho; + inherit rho; }; devShells.default = pkgs.mkShell { @@ -119,14 +119,14 @@ ]; shellHook = '' - echo "Hawk development shell" + echo "Rho development shell" echo "Go version: $(go version)" ''; }; apps.default = { type = "app"; - program = "${hawk}/bin/hawk"; + program = "${rho}/bin/rho"; }; }); } diff --git a/go.mod b/go.mod index 7e78759b5..71604f466 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/GrayCodeAI/hawk +module github.com/GrayCodeAI/rho go 1.26.6 diff --git a/install.sh b/install.sh index 2649d5ee3..cc1d01aa8 100755 --- a/install.sh +++ b/install.sh @@ -2,16 +2,16 @@ set -e # Versioned install target. -# By default the binary is installed under $HAWK_HOME/bin (default ~/.hawk/bin). -# Override with HAWK_HOME env var, or pass --prefix as the first flag. -HAWK_HOME="${HAWK_HOME:-$HOME/.hawk}" +# By default the binary is installed under $RHO_HOME/bin (default ~/.rho/bin). +# Override with RHO_HOME env var, or pass --prefix as the first flag. +RHO_HOME="${RHO_HOME:-$HOME/.rho}" if [ "$1" = "--prefix" ]; then - HAWK_HOME="$2" + RHO_HOME="$2" shift 2 fi -REPO="GrayCodeAI/hawk" -BINARY="hawk" +REPO="GrayCodeAI/rho" +BINARY="rho" OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) @@ -39,7 +39,7 @@ fi ARCHIVE_NAME="${BINARY}_${LATEST}_${OS}_${ARCH}.${ARCHIVE_EXT}" URL="https://github.com/$REPO/releases/download/v${LATEST}/${ARCHIVE_NAME}" -echo "Downloading hawk v${LATEST} for ${OS}/${ARCH}..." +echo "Downloading rho v${LATEST} for ${OS}/${ARCH}..." TMP=$(mktemp -d) ARCHIVE="$TMP/${ARCHIVE_NAME}" @@ -146,25 +146,25 @@ VERSION=$(printf '%s' "$LATEST" | sed 's/^v//') # falling back to checksum-only verification otherwise. Versioned install is a # prerequisite for safe in-place self-update tooling: once installs land at a # stable versioned path + symlink, a future updater can swap the link without -# ever replacing a binary a running hawk has mmap'd (same SIGKILL rationale). +# ever replacing a binary a running rho has mmap'd (same SIGKILL rationale). # # Windows lacks reliable non-admin symlinks, so the launcher is a plain copy. -BINDIR="$HAWK_HOME/bin" +BINDIR="$RHO_HOME/bin" mkdir -p "$BINDIR" if [ "$OS" = "windows" ]; then - mv -f "$TMP/$BIN_NAME" "$BINDIR/hawk-$VERSION.exe" - cp -f "$BINDIR/hawk-$VERSION.exe" "$BINDIR/hawk.exe" + mv -f "$TMP/$BIN_NAME" "$BINDIR/rho-$VERSION.exe" + cp -f "$BINDIR/rho-$VERSION.exe" "$BINDIR/rho.exe" echo "" - echo "Installed hawk v$VERSION to $BINDIR/hawk-$VERSION.exe" - echo "Linked launcher: $BINDIR/hawk.exe" + echo "Installed rho v$VERSION to $BINDIR/rho-$VERSION.exe" + echo "Linked launcher: $BINDIR/rho.exe" else - mv -f "$TMP/$BIN_NAME" "$BINDIR/hawk-$VERSION" - ln -sf "hawk-$VERSION" "$BINDIR/hawk.tmp" \ - && mv -f "$BINDIR/hawk.tmp" "$BINDIR/hawk" + mv -f "$TMP/$BIN_NAME" "$BINDIR/rho-$VERSION" + ln -sf "rho-$VERSION" "$BINDIR/rho.tmp" \ + && mv -f "$BINDIR/rho.tmp" "$BINDIR/rho" echo "" - echo "Installed hawk v$VERSION to $BINDIR/hawk-$VERSION (linked: $BINDIR/hawk)" + echo "Installed rho v$VERSION to $BINDIR/rho-$VERSION (linked: $BINDIR/rho)" fi rm -rf "$TMP" @@ -172,5 +172,5 @@ echo "" echo "Add $BINDIR to your PATH if it is not already, e.g." echo " export PATH=\"\$PATH:$BINDIR\"" echo "" -echo "Restart any running hawk sessions to pick up the new binary — the old" +echo "Restart any running rho sessions to pick up the new binary — the old" echo "process keeps running the previous version until it is restarted." diff --git a/internal/acp/acp_extra_test.go b/internal/acp/acp_extra_test.go index c5a15a8b1..bf0452e73 100644 --- a/internal/acp/acp_extra_test.go +++ b/internal/acp/acp_extra_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // --- Error factory for testing --- diff --git a/internal/acp/client_test.go b/internal/acp/client_test.go index a128b6f24..79f9d3ed9 100644 --- a/internal/acp/client_test.go +++ b/internal/acp/client_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/engine" + "github.com/GrayCodeAI/rho/internal/engine" ) // mockSession creates a minimal engine session for testing. diff --git a/internal/acp/content.go b/internal/acp/content.go index ce2bd2db4..2f3969e37 100644 --- a/internal/acp/content.go +++ b/internal/acp/content.go @@ -17,7 +17,7 @@ import ( "regexp" "strings" - "github.com/GrayCodeAI/hawk/internal/attachment" + "github.com/GrayCodeAI/rho/internal/attachment" ) // Raster formats shared by ACP image blocks and the core attachment diff --git a/internal/acp/content_test.go b/internal/acp/content_test.go index 4806f769a..2e317ec76 100644 --- a/internal/acp/content_test.go +++ b/internal/acp/content_test.go @@ -16,7 +16,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/attachment" + "github.com/GrayCodeAI/rho/internal/attachment" ) // pngFixture encodes a 4x3 NRGBA raster as PNG bytes. diff --git a/internal/acp/server.go b/internal/acp/server.go index 259a8b100..eb7c2b1c0 100644 --- a/internal/acp/server.go +++ b/internal/acp/server.go @@ -1,4 +1,4 @@ -// Package acp implements an Agent Client Protocol (ACP) server for hawk, exposing +// Package acp implements an Agent Client Protocol (ACP) server for rho, exposing // the agent over newline-delimited JSON-RPC 2.0 on stdio so editors (e.g. Zed) // can drive it. It mirrors the framing of internal/mcp and the session-driving // pattern of internal/daemon. @@ -6,7 +6,7 @@ // Scope (first cut): the core agent-side methods (initialize, session/new, // session/prompt, session/cancel), streamed session/update notifications, and // client-routed tool approvals via session/request_permission. File reads/writes -// use hawk's local tools; client fs routing is intentionally out of scope. +// use rho's local tools; client fs routing is intentionally out of scope. package acp import ( @@ -20,10 +20,10 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/attachment" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/session" - statussnapshot "github.com/GrayCodeAI/hawk/internal/status" + "github.com/GrayCodeAI/rho/internal/attachment" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/session" + statussnapshot "github.com/GrayCodeAI/rho/internal/status" ) // ProtocolVersion is the ACP protocol version this server implements. @@ -186,8 +186,8 @@ func (s *Server) handle(ctx context.Context, msg rpcMessage) { "audio": false, }, }, - // Hawk control-plane metadata for IDE clients that want it. - "hawkCapabilities": map[string]any{ + // Rho control-plane metadata for IDE clients that want it. + "rhoCapabilities": map[string]any{ "workModes": []string{"plan", "act", "review"}, "isolation": []string{"dev", "workspace", "strict", "container"}, "folderTrust": true, @@ -324,8 +324,8 @@ func (s *Server) handleSessionNew(msg rpcMessage) { s.reply(msg.ID, map[string]any{ "sessionId": id, - // Hawk extensions (ignored by clients that only read sessionId). - "hawk": map[string]any{ + // Rho extensions (ignored by clients that only read sessionId). + "rho": map[string]any{ "workMode": string(sess.WorkMode()), "autoCommit": sess.AutoCommit(), }, diff --git a/internal/acp/server_test.go b/internal/acp/server_test.go index d8de3add8..e0cd16334 100644 --- a/internal/acp/server_test.go +++ b/internal/acp/server_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/tool" ) // testFactory builds a session backed by the engine's canned mock chat client, @@ -138,7 +138,7 @@ func TestACP_ParseError(t *testing.T) { func TestACP_SessionLoad(t *testing.T) { tempDir := t.TempDir() - t.Setenv("HAWK_SESSIONS_DIR", tempDir) + t.Setenv("RHO_SESSIONS_DIR", tempDir) // Create and persist a session sessID := "acp-load-test-1" @@ -189,7 +189,7 @@ func TestACP_SessionLoad(t *testing.T) { func TestACP_SessionList(t *testing.T) { tempDir := t.TempDir() - t.Setenv("HAWK_SESSIONS_DIR", tempDir) + t.Setenv("RHO_SESSIONS_DIR", tempDir) prior := &session.Session{ ID: "acp-list-test-1", diff --git a/internal/appverify/appverify_test.go b/internal/appverify/appverify_test.go index 0428df7cf..38651531d 100644 --- a/internal/appverify/appverify_test.go +++ b/internal/appverify/appverify_test.go @@ -116,7 +116,7 @@ func TestManifestRoundTripAndPriority(t *testing.T) { func TestLoadManifestCorruptIsError(t *testing.T) { root := writeProject(t, map[string]string{ - ".hawk/verify/environment.json": "{not json", + ".rho/verify/environment.json": "{not json", }) if _, err := LoadManifest(root); err == nil { t.Fatal("expected error for corrupt manifest") diff --git a/internal/appverify/manifest.go b/internal/appverify/manifest.go index 576be01b2..840b87889 100644 --- a/internal/appverify/manifest.go +++ b/internal/appverify/manifest.go @@ -6,13 +6,13 @@ import ( "os" "path/filepath" - "github.com/GrayCodeAI/hawk/internal/safewrite" + "github.com/GrayCodeAI/rho/internal/safewrite" ) // ManifestPath returns the location of the persisted verify environment -// manifest for a project: /.hawk/verify/environment.json. +// manifest for a project: /.rho/verify/environment.json. func ManifestPath(root string) string { - return filepath.Join(root, ".hawk", "verify", "environment.json") + return filepath.Join(root, ".rho", "verify", "environment.json") } // Manifest is the on-disk contract between recipe detection and execution. diff --git a/internal/appverify/prompt.go b/internal/appverify/prompt.go index 19b17fc58..377ffc286 100644 --- a/internal/appverify/prompt.go +++ b/internal/appverify/prompt.go @@ -7,7 +7,7 @@ import ( // EvidenceDir is the stable, workspace-relative directory for verification // artifacts. Stable paths let reports and downstream tooling rely on them. -const EvidenceDir = ".hawk/verify/artifacts" +const EvidenceDir = ".rho/verify/artifacts" // BuildVerifyPrompt renders the phased QA-engineer prompt for the recipe. The // discipline it encodes (adopted from grok-cli) is that build/test passing diff --git a/internal/auth/auth.go b/internal/auth/auth.go index dda15e824..a74a2a05e 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -13,8 +13,8 @@ import ( "runtime" "strings" - "github.com/GrayCodeAI/hawk/internal/safewrite" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/safewrite" + "github.com/GrayCodeAI/rho/internal/storage" ) // TokenStore manages authentication tokens. @@ -217,6 +217,20 @@ func (s *SecureStorage) setWindows(account, token string) error { func (s *SecureStorage) getFile(account string) (string, error) { path := filepath.Join(storage.ConfigDir(), ".tokens") + info, err := os.Lstat(path) + if err != nil { + return "", err + } + // Refuse to follow a symlink: the token store is a fixed, private file and + // a symlink here would let another process redirect reads/writes. + if info.Mode()&os.ModeSymlink != 0 { + return "", fmt.Errorf("refusing to read token store: %s is a symlink", path) + } + // Tighten overly-permissive modes rather than failing, so existing installs + // are repaired instead of locked out. + if info.Mode().Perm()&0o077 != 0 { + _ = os.Chmod(path, 0o600) + } data, err := os.ReadFile(path) // #nosec G304 -- path is the fixed internal token store location, not external input if err != nil { return "", err diff --git a/internal/auth/auth_extra_test.go b/internal/auth/auth_extra_test.go index ecb86d082..b5f29023e 100644 --- a/internal/auth/auth_extra_test.go +++ b/internal/auth/auth_extra_test.go @@ -73,7 +73,7 @@ func TestNewSecureStorage(t *testing.T) { func TestSecureStorage_GetFile_NonExistent(t *testing.T) { // Override the config directory to a temp dir so .tokens definitely doesn't exist - t.Setenv("HAWK_CONFIG_DIR", t.TempDir()) + t.Setenv("RHO_CONFIG_DIR", t.TempDir()) ss := &SecureStorage{service: "test"} // getFile will fail because the file doesn't exist in the temp dir diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index b020ad48e..67b401578 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) func TestTokenStore(t *testing.T) { @@ -119,12 +119,12 @@ func TestGenerateNonce(t *testing.T) { func TestSecureStorage(t *testing.T) { t.Run("new secure storage", func(t *testing.T) { t.Parallel() - ss := NewSecureStorage("hawk-test") + ss := NewSecureStorage("rho-test") if ss == nil { t.Fatal("NewSecureStorage returned nil") } - if ss.service != "hawk-test" { - t.Errorf("service = %q, want %q", ss.service, "hawk-test") + if ss.service != "rho-test" { + t.Errorf("service = %q, want %q", ss.service, "rho-test") } }) @@ -132,7 +132,7 @@ func TestSecureStorage(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) - ss := NewSecureStorage("hawk-test") + ss := NewSecureStorage("rho-test") _, err := ss.getFile("nonexistent") if err == nil { t.Error("getFile() should return error for missing file") @@ -143,7 +143,7 @@ func TestSecureStorage(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) - ss := NewSecureStorage("hawk-test") + ss := NewSecureStorage("rho-test") if err := ss.setFile("anthropic", "sk-test-token"); err != nil { t.Fatalf("setFile() error = %v", err) } @@ -161,7 +161,7 @@ func TestSecureStorage(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) - ss := NewSecureStorage("hawk-test") + ss := NewSecureStorage("rho-test") if err := ss.setFile("provider", "old-token"); err != nil { t.Fatal(err) } @@ -182,7 +182,7 @@ func TestSecureStorage(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) - ss := NewSecureStorage("hawk-test") + ss := NewSecureStorage("rho-test") if err := ss.setFile("test", "secret"); err != nil { t.Fatal(err) } @@ -202,7 +202,7 @@ func TestSecureStorage(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) - ss := NewSecureStorage("hawk-test") + ss := NewSecureStorage("rho-test") if err := ss.setFile("provider1", "token1"); err != nil { t.Fatal(err) } diff --git a/internal/auth/storage_extra_test.go b/internal/auth/storage_extra_test.go index b8b05d804..8283a7a93 100644 --- a/internal/auth/storage_extra_test.go +++ b/internal/auth/storage_extra_test.go @@ -11,7 +11,7 @@ import ( func TestSecureStorage_GetSet_Fallback(t *testing.T) { // Override config dir to use a temp directory tmpDir := t.TempDir() - t.Setenv("HAWK_CONFIG_DIR", tmpDir) + t.Setenv("RHO_CONFIG_DIR", tmpDir) ss := &SecureStorage{service: "test-service"} @@ -34,7 +34,7 @@ func TestSecureStorage_GetSet_Fallback(t *testing.T) { // TestSecureStorage_GetSet_MultipleAccounts tests storing multiple accounts. func TestSecureStorage_GetSet_MultipleAccounts(t *testing.T) { tmpDir := t.TempDir() - t.Setenv("HAWK_CONFIG_DIR", tmpDir) + t.Setenv("RHO_CONFIG_DIR", tmpDir) ss := &SecureStorage{service: "test-service"} @@ -67,7 +67,7 @@ func TestSecureStorage_GetSet_MultipleAccounts(t *testing.T) { // TestSecureStorage_Get_NonExistent tests getting a token that doesn't exist. func TestSecureStorage_Get_NonExistent(t *testing.T) { tmpDir := t.TempDir() - t.Setenv("HAWK_CONFIG_DIR", tmpDir) + t.Setenv("RHO_CONFIG_DIR", tmpDir) ss := &SecureStorage{service: "test-service"} @@ -81,7 +81,7 @@ func TestSecureStorage_Get_NonExistent(t *testing.T) { // TestSecureStorage_Set_OverwritesExisting tests that Set overwrites existing tokens. func TestSecureStorage_Set_OverwritesExisting(t *testing.T) { tmpDir := t.TempDir() - t.Setenv("HAWK_CONFIG_DIR", tmpDir) + t.Setenv("RHO_CONFIG_DIR", tmpDir) ss := &SecureStorage{service: "test-service"} @@ -108,7 +108,7 @@ func TestSecureStorage_Set_OverwritesExisting(t *testing.T) { // TestSecureStorage_GetFile_CorruptJSON tests that corrupt JSON returns an error. func TestSecureStorage_GetFile_CorruptJSON(t *testing.T) { tmpDir := t.TempDir() - t.Setenv("HAWK_CONFIG_DIR", tmpDir) + t.Setenv("RHO_CONFIG_DIR", tmpDir) // Write corrupt JSON to the token file tokenFile := filepath.Join(tmpDir, ".tokens") diff --git a/internal/autoinit/autoinit.go b/internal/autoinit/autoinit.go index 9100e372f..6f9ccf311 100644 --- a/internal/autoinit/autoinit.go +++ b/internal/autoinit/autoinit.go @@ -1,13 +1,13 @@ // Package autoinit performs a one-time, automatic codebase-analysis pass the -// first time hawk runs in a project that has no context files (AGENTS.md / -// HAWK.md / CLAUDE.md). It mirrors the behaviour of the `init-deep` skill but +// first time rho runs in a project that has no context files (AGENTS.md / +// RHO.md / CLAUDE.md). It mirrors the behaviour of the `init-deep` skill but // is gated so it runs at most once per project and can be disabled entirely. // // The package is intentionally additive and self-contained: it only inspects // the filesystem to decide whether to run, writes a marker file once a run has // been attempted, and delegates the actual analysis to a caller-supplied // runner. Callers (the cmd layer) wire the runner to whatever drives the -// init analysis (the init-deep skill / `hawk init`). When no runner is wired, +// init analysis (the init-deep skill / `rho init`). When no runner is wired, // MaybeRun is a no-op beyond gating, so importing the package never changes // behaviour on its own. package autoinit @@ -18,8 +18,8 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/config" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/rho/internal/storage" ) // markerName is the file written under the project's user-state directory once an @@ -30,12 +30,12 @@ const markerName = "auto-init.done" // disableEnv, when set to a truthy value ("1", "true", "yes", "on"), disables // auto-init globally. This is the kill switch for users/CI that never want the // behaviour. -const disableEnv = "HAWK_DISABLE_AUTO_INIT" +const disableEnv = "RHO_DISABLE_AUTO_INIT" // contextFiles are the project-level context files whose presence means the // project already has context and auto-init should be skipped. This matches -// the convention files recognized elsewhere in hawk. -var contextFiles = []string{"AGENTS.md", "HAWK.md", "CLAUDE.md", "CONTEXT.md"} +// the convention files recognized elsewhere in rho. +var contextFiles = []string{"AGENTS.md", "RHO.md", "CLAUDE.md", "CONTEXT.md"} // Runner performs the actual codebase analysis for a project rooted at root. // It is supplied by the caller so this package carries no dependency on the @@ -93,7 +93,7 @@ func HasRun(root string) bool { // MaybeRun runs the auto-init analysis at most once for opts.Root, subject to // the gating rules: // -// 1. Disabled via HAWK_DISABLE_AUTO_INIT -> skip. +// 1. Disabled via RHO_DISABLE_AUTO_INIT -> skip. // 2. Marker file already present -> skip. // 3. Project already has a context file -> mark + skip (unless Force). // 4. Otherwise -> run, then mark. diff --git a/internal/autoinit/autoinit_test.go b/internal/autoinit/autoinit_test.go index 60d920222..efd2feaf3 100644 --- a/internal/autoinit/autoinit_test.go +++ b/internal/autoinit/autoinit_test.go @@ -97,7 +97,7 @@ func TestMaybeRun_Disabled(t *testing.T) { func TestMaybeRun_ForceIgnoresExistingContext(t *testing.T) { setTestStateDir(t) root := t.TempDir() - if err := os.WriteFile(filepath.Join(root, "HAWK.md"), []byte("# x"), 0o644); err != nil { + if err := os.WriteFile(filepath.Join(root, "RHO.md"), []byte("# x"), 0o644); err != nil { t.Fatal(err) } calls := 0 @@ -150,7 +150,7 @@ func TestMaybeRun_ReturnsMarkerWriteError(t *testing.T) { if err := os.WriteFile(stateFile, []byte("not a directory"), 0o644); err != nil { t.Fatal(err) } - t.Setenv("HAWK_STATE_DIR", stateFile) + t.Setenv("RHO_STATE_DIR", stateFile) calls := 0 run := func(context.Context, string) error { calls++ @@ -184,5 +184,5 @@ func TestHasContext(t *testing.T) { func setTestStateDir(t *testing.T) { t.Helper() - t.Setenv("HAWK_STATE_DIR", filepath.Join(t.TempDir(), "state")) + t.Setenv("RHO_STATE_DIR", filepath.Join(t.TempDir(), "state")) } diff --git a/internal/catalogtest/install.go b/internal/catalogtest/install.go index d069d5e63..5de29a6b4 100644 --- a/internal/catalogtest/install.go +++ b/internal/catalogtest/install.go @@ -20,7 +20,7 @@ var ( // Call from TestMain; returns cleanup to unset env. func InstallGlobal() (cleanup func()) { globalOnce.Do(func() { - dir, err := os.MkdirTemp("", "hawk-catalog-*") + dir, err := os.MkdirTemp("", "rho-catalog-*") if err != nil { // In TestMain, we can't use t.Fatal, so log and exit. // This is a test helper, so panicking is acceptable but we'll use a clearer message. diff --git a/internal/circuitbreaker/circuitbreaker.go b/internal/circuitbreaker/circuitbreaker.go index cd342e057..bd264f89a 100644 --- a/internal/circuitbreaker/circuitbreaker.go +++ b/internal/circuitbreaker/circuitbreaker.go @@ -3,7 +3,7 @@ // OpenClaude's auto-compact circuit breaker: after a threshold of consecutive // failures, the breaker "opens" and skips the operation for a cooldown window, // then re-arms in a half-open state so a single success closes it and a single -// failure re-opens it. Hawk uses it to stop runaway auto-compaction (an +// failure re-opens it. Rho uses it to stop runaway auto-compaction (an // irrecoverable prompt_too_long would otherwise retry thousands of times). package circuitbreaker diff --git a/internal/codegraph/algorithms_cgo_more.go b/internal/codegraph/algorithms_cgo_more.go index 93ee3bd89..a9e448b40 100644 --- a/internal/codegraph/algorithms_cgo_more.go +++ b/internal/codegraph/algorithms_cgo_more.go @@ -255,7 +255,7 @@ func (cg *CodeGraph) AnalyzeCoupling(topN int) ([]CouplingMetric, error) { } // CrossRepoQuery queries across multiple codegraph databases. -// Useful for finding relationships between hawk, eyrie, and other packages. +// Useful for finding relationships between rho, eyrie, and other packages. func CrossRepoQuery(repos []string, query string, limit int) (map[string][]Node, error) { results := make(map[string][]Node) @@ -278,7 +278,7 @@ func CrossRepoQuery(repos []string, query string, limit int) (map[string][]Node, } // CrossRepoImpact finds the impact of changing a symbol across multiple repos. -// If a symbol in hawk calls a symbol in eyrie, this traces that cross-repo dependency. +// If a symbol in rho calls a symbol in eyrie, this traces that cross-repo dependency. func CrossRepoImpact(repos []string, symbol string, maxDepth int) (map[string]*ImpactResult, error) { results := make(map[string]*ImpactResult) @@ -311,7 +311,7 @@ func CrossRepoImpact(repos []string, symbol string, maxDepth int) (map[string]*I } // FindCrossRepoCalls finds function calls that cross repo boundaries. -// For example, hawk calling eyrie functions. +// For example, rho calling eyrie functions. func FindCrossRepoCalls(repos []string) ([]CrossRepoCall, error) { type repoSymbol struct { repo string diff --git a/internal/config/catalog_api.go b/internal/config/catalog_api.go index af9f58316..1968c83b8 100644 --- a/internal/config/catalog_api.go +++ b/internal/config/catalog_api.go @@ -6,7 +6,7 @@ import ( "strings" llm "github.com/GrayCodeAI/eyrie/llm" - gw "github.com/GrayCodeAI/hawk/internal/provider/gateway" + gw "github.com/GrayCodeAI/rho/internal/provider/gateway" ) type GatewayStatus struct { diff --git a/internal/config/catalog_compat_test.go b/internal/config/catalog_compat_test.go index a7909d1c9..cd75b2df1 100644 --- a/internal/config/catalog_compat_test.go +++ b/internal/config/catalog_compat_test.go @@ -9,7 +9,7 @@ import ( ) // CompiledCatalogV1 is retained only for lower-level migration/security tests. -// Production Hawk code consumes engine DTOs exclusively. +// Production Rho code consumes engine DTOs exclusively. func CompiledCatalogV1() *catalog.CompiledCatalog { compiled, err := catalog.LoadCatalog(context.Background(), catalog.LoadCatalogOptions{CachePath: catalog.DefaultCachePath()}) if err == nil && compiled != nil { diff --git a/internal/config/catalog_gateways_test.go b/internal/config/catalog_gateways_test.go index 5cf36e7d8..465e43d60 100644 --- a/internal/config/catalog_gateways_test.go +++ b/internal/config/catalog_gateways_test.go @@ -7,7 +7,7 @@ import ( "github.com/GrayCodeAI/eyrie/catalog" "github.com/GrayCodeAI/eyrie/credentials" - "github.com/GrayCodeAI/hawk/internal/catalogtest" + "github.com/GrayCodeAI/rho/internal/catalogtest" ) func TestIsCatalogCacheRequired(t *testing.T) { diff --git a/internal/config/catalog_health.go b/internal/config/catalog_health.go index 87e3b5201..b3ef178c4 100644 --- a/internal/config/catalog_health.go +++ b/internal/config/catalog_health.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/theme" ) var ( @@ -73,12 +73,12 @@ func catalogHealthReportUncached(ctx context.Context) CatalogHealth { Source: status.Source, Error: status.Error, } if !h.Exists && h.Error == "" { - h.Error = "cache missing — hawk will discover automatically on start" + h.Error = "cache missing — rho will discover automatically on start" } return h } -// FormatCatalogHealth returns human-readable catalog status for hawk doctor. +// FormatCatalogHealth returns human-readable catalog status for rho doctor. func FormatCatalogHealth(h CatalogHealth) string { var b strings.Builder b.WriteString(theme.Tint("Model catalog (eyrie):", theme.ReportInfo) + "\n") @@ -93,7 +93,7 @@ func FormatCatalogHealth(h CatalogHealth) string { } b.WriteString(" " + theme.Tint("models:", theme.ReportMuted) + " " + theme.Tint(fmt.Sprintf("%d", h.Models), theme.ReportInfo) + " " + theme.Tint("deployments:", theme.ReportMuted) + " " + theme.Tint(fmt.Sprintf("%d", h.Deployments), theme.ReportInfo) + " " + theme.Tint("offerings:", theme.ReportMuted) + " " + theme.Tint(fmt.Sprintf("%d", h.Offerings), theme.ReportInfo) + "\n") if h.Stale { - b.WriteString(" " + theme.Tint("stale:", theme.ReportMuted) + " " + theme.Tint("yes", theme.ReportWarn) + fmt.Sprintf(" (after %s) — hawk refreshes automatically on start\n", h.StaleAfter.UTC().Format(time.RFC3339))) + b.WriteString(" " + theme.Tint("stale:", theme.ReportMuted) + " " + theme.Tint("yes", theme.ReportWarn) + fmt.Sprintf(" (after %s) — rho refreshes automatically on start\n", h.StaleAfter.UTC().Format(time.RFC3339))) } else if !h.StaleAfter.IsZero() { b.WriteString(" " + theme.Tint("stale:", theme.ReportMuted) + " " + theme.Tint("no", theme.ReportSuccess) + fmt.Sprintf(" (until %s)\n", h.StaleAfter.UTC().Format(time.RFC3339))) } @@ -108,7 +108,7 @@ func CatalogEmptyHint(ctx context.Context) string { if !HasConfiguredDeploymentCached(ctx) { return "run /config to paste an API key or set up Ollama (local, no key)" } - return "check network access, then hawk preflight or /config — hawk refreshes the catalog automatically" + return "check network access, then rho preflight or /config — rho refreshes the catalog automatically" } // EnsureCatalogAvailable returns an error when the production catalog cache is missing or empty. diff --git a/internal/config/catalog_health_test.go b/internal/config/catalog_health_test.go index 48b638d6f..8e03452bf 100644 --- a/internal/config/catalog_health_test.go +++ b/internal/config/catalog_health_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestCatalogEmptyHint_NoCredentials(t *testing.T) { diff --git a/internal/config/catalog_startup.go b/internal/config/catalog_startup.go index 506521a2e..651fcce06 100644 --- a/internal/config/catalog_startup.go +++ b/internal/config/catalog_startup.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/env" + "github.com/GrayCodeAI/rho/internal/env" ) type gatewayModelCount struct { @@ -78,7 +78,7 @@ func CatalogReady(ctx context.Context) bool { return h.Error == "" && h.Models > 0 && !h.Stale } -// CatalogStartupOptions controls automatic catalog refresh at hawk startup. +// CatalogStartupOptions controls automatic catalog refresh at rho startup. type CatalogStartupOptions struct { ForceRefresh bool SkipAutoRefresh bool @@ -86,7 +86,7 @@ type CatalogStartupOptions struct { } // PrepareCatalogForSession ensures a usable, fresh catalog before chat/print. -// By default hawk auto-discovers when the cache is missing, empty, or stale. +// By default rho auto-discovers when the cache is missing, empty, or stale. func PrepareCatalogForSession(ctx context.Context, out io.Writer, opts CatalogStartupOptions) error { h := CatalogHealthReport(ctx) if !catalogNeedsAutoRefresh(h, opts) { @@ -180,7 +180,7 @@ func AutoRefreshCatalog(ctx context.Context, out io.Writer, verbose bool) error // TryAutoRefreshCatalog refreshes once when the cache cannot be read (e.g. mid-session). func TryAutoRefreshCatalog(ctx context.Context) error { if !autoRefreshCatalogEnabled() { - return fmt.Errorf("automatic catalog refresh is disabled (HAWK_AUTO_REFRESH_CATALOG=0)") + return fmt.Errorf("automatic catalog refresh is disabled (RHO_AUTO_REFRESH_CATALOG=0)") } return AutoRefreshCatalog(ctx, nil, false) } @@ -209,7 +209,7 @@ func StartupCatalogPrefetch(ctx context.Context) { }() } -// DiscoverCatalogAfterSetup runs during optional hawk setup after API keys are saved. +// DiscoverCatalogAfterSetup runs during optional rho setup after API keys are saved. func DiscoverCatalogAfterSetup(ctx context.Context, out io.Writer) { if out == nil { out = os.Stdout @@ -225,11 +225,11 @@ func catalogRefreshFailureHint(ctx context.Context) string { if !HasConfiguredDeployment(ctx) { return "No API keys in " + CredentialStoreName() + ". Run /config to paste a key or set up Ollama." } - return "Check network access and stored keys (" + CredentialStoreName() + "). Run hawk preflight or /config." + return "Check network access and stored keys (" + CredentialStoreName() + "). Run rho preflight or /config." } func autoRefreshCatalogEnabled() bool { - switch strings.ToLower(strings.TrimSpace(env.Getenv("HAWK_AUTO_REFRESH_CATALOG"))) { + switch strings.ToLower(strings.TrimSpace(env.Getenv("RHO_AUTO_REFRESH_CATALOG"))) { case "0", "false", "no", "off": return false default: @@ -238,7 +238,7 @@ func autoRefreshCatalogEnabled() bool { } func catalogRefreshAlways() bool { - switch strings.ToLower(strings.TrimSpace(env.Getenv("HAWK_CATALOG_REFRESH_ALWAYS"))) { + switch strings.ToLower(strings.TrimSpace(env.Getenv("RHO_CATALOG_REFRESH_ALWAYS"))) { case "1", "true", "yes", "on": return true default: diff --git a/internal/config/catalog_startup_robust_test.go b/internal/config/catalog_startup_robust_test.go index fa3863c38..a53115074 100644 --- a/internal/config/catalog_startup_robust_test.go +++ b/internal/config/catalog_startup_robust_test.go @@ -6,16 +6,16 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/catalogtest" - hawkconfig "github.com/GrayCodeAI/hawk/internal/config" + "github.com/GrayCodeAI/rho/internal/catalogtest" + rhoconfig "github.com/GrayCodeAI/rho/internal/config" ) func TestPrepareCatalogForSession_StaleCacheRefreshFailureContinues(t *testing.T) { catalogtest.Install(t) // Force stale so refresh is attempted; remote may fail offline — should not block if cache has models. - h := hawkconfig.CatalogHealthReport(context.Background()) + h := rhoconfig.CatalogHealthReport(context.Background()) var buf bytes.Buffer - err := hawkconfig.PrepareCatalogForSession(context.Background(), &buf, hawkconfig.CatalogStartupOptions{ + err := rhoconfig.PrepareCatalogForSession(context.Background(), &buf, rhoconfig.CatalogStartupOptions{ ForceRefresh: true, }) // With ForceRefresh, remote may fail; if we had models before, we tolerate failure. @@ -28,7 +28,7 @@ func TestPrepareCatalogForSession_StaleCacheRefreshFailureContinues(t *testing.T func TestCatalogCachePathForDisplay_RespectsEnv(t *testing.T) { custom := filepath.Join(t.TempDir(), "custom.json") t.Setenv("EYRIE_MODEL_CATALOG_PATH", custom) - if got := hawkconfig.CatalogCachePathForDisplay(); got != custom { + if got := rhoconfig.CatalogCachePathForDisplay(); got != custom { t.Fatalf("path = %q want %q", got, custom) } } diff --git a/internal/config/catalog_startup_test.go b/internal/config/catalog_startup_test.go index 6c98d153d..cba7f95ee 100644 --- a/internal/config/catalog_startup_test.go +++ b/internal/config/catalog_startup_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/catalogtest" + "github.com/GrayCodeAI/rho/internal/catalogtest" ) func TestCatalogReady_MissingCache(t *testing.T) { @@ -50,11 +50,11 @@ func TestCatalogNeedsAutoRefresh_Fresh(t *testing.T) { } func TestAutoRefreshCatalogEnabled(t *testing.T) { - t.Setenv("HAWK_AUTO_REFRESH_CATALOG", "false") + t.Setenv("RHO_AUTO_REFRESH_CATALOG", "false") if autoRefreshCatalogEnabled() { t.Fatal("expected disabled") } - t.Setenv("HAWK_AUTO_REFRESH_CATALOG", "") + t.Setenv("RHO_AUTO_REFRESH_CATALOG", "") if !autoRefreshCatalogEnabled() { t.Fatal("expected enabled by default") } diff --git a/internal/config/config.go b/internal/config/config.go index 210a08b13..a19352a46 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,14 @@ var agentFiles = []string{ "AGENTS.md", } +// sharedAgentFiles lists instruction files under a shared resource directory. +// Tau reads `.agents/` resources; Rho honors the same layout in addition to +// the root-level AGENTS.md, so a repo can keep shared agent instructions in +// `.agents/AGENTS.md`. +var sharedAgentFiles = []string{ + filepath.Join(".agents", "AGENTS.md"), +} + // LoadAgentsMDFrom reads AGENTS.md from start or its parents. func LoadAgentsMDFrom(start string) string { dir := start @@ -33,7 +41,9 @@ func LoadAgentsMDFrom(start string) string { dir = abs } for { - for _, name := range agentFiles { + // Root-level instruction files win over shared-resource ones in the + // same directory. + for _, name := range append(append([]string{}, agentFiles...), sharedAgentFiles...) { path := filepath.Join(dir, name) data, err := os.ReadFile(path) // #nosec G304 -- dir is the working directory or an ancestor of it; name is a fixed constant if err == nil { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d53d0fa27..3e4eb2470 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestLoadAgentsMD(t *testing.T) { @@ -169,7 +169,7 @@ func TestLoadSettingsUsesUserConfigOnly(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) configDir := filepath.Join(home, "config") - t.Setenv("HAWK_CONFIG_DIR", configDir) + t.Setenv("RHO_CONFIG_DIR", configDir) t.Setenv("EYRIE_CONFIG_DIR", filepath.Join(home, "eyrie")) if err := os.MkdirAll(configDir, 0o755); err != nil { t.Fatal(err) @@ -204,7 +204,7 @@ func TestSetGlobalSettingAndSettingValue(t *testing.T) { if err := SetGlobalSetting("maxBudgetUSD", "2.5"); err != nil { t.Fatal(err) } - // Hawk: API keys rejected from settings file + // Rho: API keys rejected from settings file if err := SetGlobalSetting("apiKey.openai", "sk-test"); err == nil { t.Fatal("expected error setting api key in settings") } diff --git a/internal/config/credentials_store.go b/internal/config/credentials_store.go index c314cce89..d41c43452 100644 --- a/internal/config/credentials_store.go +++ b/internal/config/credentials_store.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/theme" ) // PersistAPIKey saves a provider API key via eyrie (OS secret store). @@ -137,7 +137,7 @@ func ConfiguredCredentialProviders() []string { return configuredCredentialProvidersCached(context.Background()) } -// FormatCredentialCLIStatus returns hawk credentials status output (providers, not raw env names). +// FormatCredentialCLIStatus returns rho credentials status output (providers, not raw env names). func FormatCredentialCLIStatus(ctx context.Context) string { if ctx == nil { ctx = context.Background() diff --git a/internal/config/credentials_store_test.go b/internal/config/credentials_store_test.go index d13b5b70c..b8a8759d4 100644 --- a/internal/config/credentials_store_test.go +++ b/internal/config/credentials_store_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestRemoveStoredCredential_ByProvider(t *testing.T) { diff --git a/internal/config/deployment_status.go b/internal/config/deployment_status.go index 620ae7bb9..8fdb061c9 100644 --- a/internal/config/deployment_status.go +++ b/internal/config/deployment_status.go @@ -10,7 +10,7 @@ func ResolveCanonicalModel(model string) string { return CanonicalModelID(context.Background(), strings.TrimSpace(model)) } -// DeploymentStatusReport returns hawk deployment routing diagnostics. +// DeploymentStatusReport returns rho deployment routing diagnostics. func DeploymentStatusReport(ctx context.Context, activeModel string) (string, error) { engine, err := newEyrieEngine() if err != nil { diff --git a/internal/config/deployments_ui_test.go b/internal/config/deployments_ui_test.go index 77338e48c..e61e731b1 100644 --- a/internal/config/deployments_ui_test.go +++ b/internal/config/deployments_ui_test.go @@ -3,7 +3,7 @@ package config import "testing" func TestDeploymentRoutingLabel(t *testing.T) { - t.Setenv("HAWK_DEPLOYMENT_ROUTING", "") + t.Setenv("RHO_DEPLOYMENT_ROUTING", "") enabled := true if DeploymentRoutingLabel(Settings{DeploymentRouting: &enabled}) != "on" { t.Fatal("expected on") diff --git a/internal/config/developer_path.go b/internal/config/developer_path.go index 7b9723b2c..d98540a4f 100644 --- a/internal/config/developer_path.go +++ b/internal/config/developer_path.go @@ -7,13 +7,13 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/home" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/theme" - "github.com/GrayCodeAI/hawk/internal/token" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/home" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/rho/internal/token" + "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // PathCheckStatus is pass, warn, or fail for one readiness row. @@ -61,7 +61,7 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { checks = append(checks, PathCheck{ Section: "Setup", Name: "credentials", Status: PathFail, Detail: "No provider credentials configured", - FixHint: "Run hawk and /config to paste an API key (or configure Ollama)", + FixHint: "Run rho and /config to paste an API key (or configure Ollama)", Blocking: true, }) } @@ -92,13 +92,13 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { checks = append(checks, PathCheck{ Section: "Setup", Name: "catalog", Status: PathWarn, Detail: "Catalog file present but empty", - FixHint: "Run hawk models refresh after adding credentials", + FixHint: "Run rho models refresh after adding credentials", }) default: checks = append(checks, PathCheck{ Section: "Setup", Name: "catalog", Status: PathWarn, Detail: CatalogEmptyHint(ctx), - FixHint: "Add credentials then run hawk models refresh", + FixHint: "Add credentials then run rho models refresh", }) } @@ -133,10 +133,10 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { }) } - hawkDir := home.MustDir() + rhoDir := home.MustDir() provPath := ProviderStateSecurityStatus().Path if provPath == "" { - provPath = filepath.Join(hawkDir, ".hawk", "provider.json") + provPath = filepath.Join(rhoDir, ".rho", "provider.json") } if reason := tool.IsSensitivePath(provPath); reason != "" { checks = append(checks, PathCheck{ @@ -169,13 +169,13 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { } checks = append(checks, PathCheck{ Section: "Ecosystem", Name: "eyrie", Status: status, - Detail: "Preflight not ready — see hawk preflight", + Detail: "Preflight not ready — see rho preflight", FixHint: "Complete /config (credentials + model)", Blocking: true, }) } - sample := token.CountTokensFast("hawk developer path readiness") + sample := token.CountTokensFast("rho developer path readiness") checks = append(checks, PathCheck{ Section: "Ecosystem", Name: "token", Status: PathPass, Detail: fmt.Sprintf("Embedded token/compress pipeline OK (sample=%d tokens)", sample), @@ -200,10 +200,10 @@ func anyBlockingFail(checks []PathCheck, section string) bool { func developerPathNextStep(r DeveloperPathReport, setup SetupState) string { if r.Ready { - return "Ready — run hawk and start chatting" + return "Ready — run rho and start chatting" } if !setup.HasCredentials { - return "Run hawk → /config → paste API key (or Ollama local)" + return "Run rho → /config → paste API key (or Ollama local)" } if !setup.HasModel { return "Run /config → pick a model from the catalog" @@ -211,7 +211,7 @@ func developerPathNextStep(r DeveloperPathReport, setup SetupState) string { if !r.SecureReady { return "Fix security items above (provider.json secrets, read guard)" } - return "Run hawk preflight for details, then /config if needed" + return "Run rho preflight for details, then /config if needed" } // pathStatusColor maps a readiness status to a semantic report color. @@ -232,7 +232,7 @@ func pathStatusColor(s PathCheckStatus) color.Color { func FormatDeveloperPathReport(ctx context.Context) string { r := EvaluateDeveloperPath(ctx) var b strings.Builder - b.WriteString(theme.Tint("Developer path (hawk · eyrie · token engine)", theme.ReportInfo) + "\n\n") + b.WriteString(theme.Tint("Developer path (rho · eyrie · token engine)", theme.ReportInfo) + "\n\n") status := "NEEDS SETUP" statusColor := theme.ReportWarn @@ -268,7 +268,7 @@ func FormatDeveloperPathReport(ctx context.Context) string { } b.WriteString(theme.Tint("Next:", theme.ReportMuted) + " " + r.NextStep + "\n") - b.WriteString("\n" + theme.Tint("Docs: docs/DEVELOPER-PATH.md · docs/SECURITY-DEVELOPER.md · hawk doctor · hawk preflight", theme.ReportMuted) + "\n") + b.WriteString("\n" + theme.Tint("Docs: docs/DEVELOPER-PATH.md · docs/SECURITY-DEVELOPER.md · rho doctor · rho preflight", theme.ReportMuted) + "\n") return strings.TrimRight(b.String(), "\n") } diff --git a/internal/config/developer_path_test.go b/internal/config/developer_path_test.go index ffb6d49c1..d0a25da24 100644 --- a/internal/config/developer_path_test.go +++ b/internal/config/developer_path_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestEvaluateDeveloperPath_FreshInstall(t *testing.T) { diff --git a/internal/config/ecosystem_report.go b/internal/config/ecosystem_report.go index 296b055be..149473932 100644 --- a/internal/config/ecosystem_report.go +++ b/internal/config/ecosystem_report.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/theme" - "github.com/GrayCodeAI/hawk/internal/token" + "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/rho/internal/token" ) // EcosystemReport is the structured view of the ecosystem panel. @@ -49,7 +49,7 @@ func BuildEcosystemReport(ctx context.Context, provider, model string) Ecosystem // embedded token engine r.Token.Embedded = token.ShrikeAvailable() - r.Token.SampleTokens = token.CountTokensFast("hawk context compression pipeline") + r.Token.SampleTokens = token.CountTokensFast("rho context compression pipeline") return r } @@ -65,7 +65,7 @@ func FormatEcosystemPanel(ctx context.Context, provider, model string) string { if cat.Exists { eyrieLine += theme.Tint(fmt.Sprintf("catalog %d models", cat.Models), theme.ReportInfo) } else { - eyrieLine += theme.Tint("catalog missing (run hawk models refresh)", theme.ReportWarn) + eyrieLine += theme.Tint("catalog missing (run rho models refresh)", theme.ReportWarn) } pre := EnginePreflightReport(ctx) if pre.Ready { @@ -86,7 +86,7 @@ func FormatEcosystemPanel(ctx context.Context, provider, model string) string { b.WriteString(eyrieLine + "\n") // Embedded token engine — token counting and context compression. - sample := token.CountTokensFast("hawk context compression pipeline") + sample := token.CountTokensFast("rho context compression pipeline") if token.ShrikeAvailable() { b.WriteString(" " + theme.Tint("token:", theme.ReportMuted) + " " + theme.Tint("embedded", theme.ReportInfo) + " · " + theme.Tint("token/compress pipeline OK", theme.ReportSuccess) + fmt.Sprintf(" (sample=%d tokens)", sample) + "\n") } else { diff --git a/internal/config/envmanager.go b/internal/config/envmanager.go index ce3290a7b..a320d922e 100644 --- a/internal/config/envmanager.go +++ b/internal/config/envmanager.go @@ -11,7 +11,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/env" + "github.com/GrayCodeAI/rho/internal/env" ) // EnvVar represents a single environment variable with metadata. diff --git a/internal/config/envmanager_test.go b/internal/config/envmanager_test.go index 3749c5196..1de9b4f78 100644 --- a/internal/config/envmanager_test.go +++ b/internal/config/envmanager_test.go @@ -174,8 +174,8 @@ func TestListForDisplay(t *testing.T) { Source: "env", Secret: true, } - em.Vars["HAWK_MODEL"] = &EnvVar{ - Key: "HAWK_MODEL", + em.Vars["RHO_MODEL"] = &EnvVar{ + Key: "RHO_MODEL", Value: "claude-sonnet-4-6", Source: ".env.local", Secret: false, @@ -195,8 +195,8 @@ func TestListForDisplay(t *testing.T) { if !strings.Contains(output, "from: env") { t.Fatal("output should show source") } - if !strings.Contains(output, "HAWK_MODEL") { - t.Fatal("output should contain HAWK_MODEL") + if !strings.Contains(output, "RHO_MODEL") { + t.Fatal("output should contain RHO_MODEL") } if !strings.Contains(output, "claude-sonnet-4-6") { t.Fatal("output should show non-secret value in full") diff --git a/internal/config/eyrie_apply.go b/internal/config/eyrie_apply.go index 6ed88efcf..348a0038e 100644 --- a/internal/config/eyrie_apply.go +++ b/internal/config/eyrie_apply.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) -// ApplyCredentialsResult is Hawk's UI-safe view of an Eyrie catalog/routing +// ApplyCredentialsResult is Rho's UI-safe view of an Eyrie catalog/routing // application. It intentionally excludes Eyrie setup/config implementation // types from the product boundary. type ApplyCredentialsResult struct { diff --git a/internal/config/eyrie_engine.go b/internal/config/eyrie_engine.go index f5e7fd623..8d62d9635 100644 --- a/internal/config/eyrie_engine.go +++ b/internal/config/eyrie_engine.go @@ -3,7 +3,7 @@ package config import ( "context" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) type ( @@ -19,7 +19,7 @@ func newEyrieEngine() (*gateway.Gateway, error) { func NewEyrieEngine() (*gateway.Gateway, error) { return newEyrieEngine() } // NewEyrieEngineForSettings composes a fresh gateway for one effective -// Hawk settings snapshot. It performs no package-global registration and does +// Rho settings snapshot. It performs no package-global registration and does // not mutate provider environment variables. func NewEyrieEngineForSettings(settings Settings) (*gateway.Gateway, error) { return gateway.New(context.Background(), gatewayCustomGateways(settings.CustomProviders)) @@ -128,7 +128,7 @@ func EngineDeploymentSummary(ctx context.Context, model string) (gateway.Deploym return gw.DeploymentSummary(ctx, model) } -// newEyrieEngine is Hawk's default composition root for Eyrie's stable host +// newEyrieEngine is Rho's default composition root for Eyrie's stable host // facade. Command paths that support --settings must use // NewEyrieEngineForSettings instead of relying on this global-settings default. diff --git a/internal/config/eyrie_engine_test.go b/internal/config/eyrie_engine_test.go index 4d7c79b7e..3815f664b 100644 --- a/internal/config/eyrie_engine_test.go +++ b/internal/config/eyrie_engine_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestNewEyrieEngineForSettingsIsInvocationScoped(t *testing.T) { diff --git a/internal/config/eyrie_selection.go b/internal/config/eyrie_selection.go index b707a4b68..c4c506c96 100644 --- a/internal/config/eyrie_selection.go +++ b/internal/config/eyrie_selection.go @@ -3,7 +3,7 @@ package config import ( "context" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) type ( @@ -29,7 +29,7 @@ func EffectiveSelectionWithSettings(ctx context.Context, settings Settings, opts return engine.EffectiveSelection(ctx, opts) } -// ActiveModel returns the selected model from eyrie provider.json (not hawk settings). +// ActiveModel returns the selected model from eyrie provider.json (not rho settings). func ActiveModel(ctx context.Context) string { if ctx == nil { ctx = context.Background() diff --git a/internal/config/main_test.go b/internal/config/main_test.go index e3567512a..a6f047e96 100644 --- a/internal/config/main_test.go +++ b/internal/config/main_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" - "github.com/GrayCodeAI/hawk/internal/catalogtest" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/catalogtest" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestMain(m *testing.M) { diff --git a/internal/config/milestone_verify_test.go b/internal/config/milestone_verify_test.go index f3606fc02..59b3f9366 100644 --- a/internal/config/milestone_verify_test.go +++ b/internal/config/milestone_verify_test.go @@ -13,18 +13,18 @@ import ( "github.com/GrayCodeAI/eyrie/credentials" ) -// isolateMilestoneTest uses a temp HOME and HAWK_CONFIG_DIR so verification does not touch the user machine. +// isolateMilestoneTest uses a temp HOME and RHO_CONFIG_DIR so verification does not touch the user machine. func isolateMilestoneTest(t *testing.T) string { t.Helper() home := t.TempDir() - hawkDir := filepath.Join(home, ".hawk") - if err := os.MkdirAll(hawkDir, 0o700); err != nil { + rhoDir := filepath.Join(home, ".rho") + if err := os.MkdirAll(rhoDir, 0o700); err != nil { t.Fatal(err) } t.Setenv("HOME", home) - t.Setenv("HAWK_CONFIG_DIR", hawkDir) - t.Setenv("EYRIE_CONFIG_DIR", hawkDir) - return hawkDir + t.Setenv("RHO_CONFIG_DIR", rhoDir) + t.Setenv("EYRIE_CONFIG_DIR", rhoDir) + return rhoDir } func TestVerify_ProviderJSONOnDiskHasNoSecrets(t *testing.T) { @@ -46,7 +46,7 @@ func TestVerify_ProviderJSONOnDiskHasNoSecrets(t *testing.T) { } func TestVerify_PersistAPIKeyDoesNotWriteProviderJSON(t *testing.T) { - hawkDir := isolateMilestoneTest(t) + rhoDir := isolateMilestoneTest(t) credentials.SetDefaultStore(emptyCredentialStore{}) t.Cleanup(func() { credentials.SetDefaultStore(nil) }) @@ -54,7 +54,7 @@ func TestVerify_PersistAPIKeyDoesNotWriteProviderJSON(t *testing.T) { if err := PersistAPIKey(context.Background(), "ANTHROPIC_API_KEY", secret); err != nil { t.Fatal(err) } - path := filepath.Join(hawkDir, "provider.json") + path := filepath.Join(rhoDir, "provider.json") if _, err := os.Stat(path); err == nil { data, _ := os.ReadFile(path) if strings.Contains(string(data), secret) { @@ -99,7 +99,7 @@ func TestVerify_EvaluateSetupFlow(t *testing.T) { t.Fatal("expected setup still needed until model selected") } - providerPath := filepath.Join(os.Getenv("HOME"), ".hawk", "provider.json") + providerPath := filepath.Join(os.Getenv("HOME"), ".rho", "provider.json") cfg := &eyriecfg.ProviderConfig{ ActiveProvider: "anthropic", ActiveModel: "claude-sonnet-4-20250514", diff --git a/internal/config/security_test.go b/internal/config/security_test.go index cb6881686..b6ffc44fc 100644 --- a/internal/config/security_test.go +++ b/internal/config/security_test.go @@ -87,7 +87,7 @@ func TestMergeSettings_AllowedToolsAppend(t *testing.T) { func TestValidateSettings_ValidConfig(t *testing.T) { // This test uses the global catalog test setup from main_test.go dir := t.TempDir() - t.Setenv("HAWK_CONFIG_DIR", dir) + t.Setenv("RHO_CONFIG_DIR", dir) t.Setenv("EYRIE_CONFIG_DIR", dir) s := Settings{ MaxBudgetUSD: 10.0, diff --git a/internal/config/settings.go b/internal/config/settings.go index 9b085cef1..b06dd7200 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -13,21 +13,21 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/provider/routing" - "github.com/GrayCodeAI/hawk/internal/safewrite" - "github.com/GrayCodeAI/hawk/internal/smartrouting" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/safewrite" + "github.com/GrayCodeAI/rho/internal/smartrouting" + "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func fetchModelsViaRuntime(ctx context.Context, provider string) ([]EngineModel, error) { return ListEngineModels(ctx, provider, false) } -// Settings holds hawk configuration. -// Hawk: no API keys stored here. Secrets come from the OS secret store via eyrie. +// Settings holds rho configuration. +// Rho: no API keys stored here. Secrets come from the OS secret store via eyrie. type Settings struct { // PolicySchemaVersion versions permission/autonomy/sandbox fields. Zero is // the legacy format and is migrated to CurrentPolicySchemaVersion on load. @@ -105,7 +105,7 @@ func ToolPresetByName(name string) (ToolPreset, bool) { return p, ok } -// Attribution controls how hawk identifies itself in git commits. +// Attribution controls how rho identifies itself in git commits. type Attribution = types.Attribution // CustomProviderConfig defines a user-specified OpenAI-compatible provider. @@ -190,7 +190,7 @@ func readSettingsFileCached(path string) ([]byte, error) { settingsCache.modTime.Equal(fi.ModTime()) && settingsCache.size == fi.Size() { return settingsCache.data, nil } - data, err := os.ReadFile(path) // #nosec G304 -- path is the hawk global settings path from internal storage config, not external input + data, err := os.ReadFile(path) // #nosec G304 -- path is the rho global settings path from internal storage config, not external input if err == nil && statErr == nil { settingsCache.valid = true settingsCache.path = path @@ -212,7 +212,7 @@ func invalidateSettingsCache() { settingsCache.Unlock() } -// LoadGlobalSettings loads only Hawk's user config settings.json. +// LoadGlobalSettings loads only Rho's user config settings.json. func LoadGlobalSettings() Settings { var s Settings path := globalSettingsPath() @@ -232,7 +232,7 @@ func LoadGlobalSettings() Settings { // LoadSettings loads settings from user config, overlaid with any // project-scoped settings discovered by walking up from the current working -// directory looking for .hawk/settings.json. +// directory looking for .rho/settings.json. func LoadSettings() Settings { s := LoadGlobalSettings() if project := findProjectSettings(); project != nil { @@ -266,7 +266,7 @@ func projectSafeSettings(project Settings) Settings { } // findProjectSettings walks up from the current working directory looking for -// a .hawk/settings.json file. Returns nil if none is found. The nearest +// a .rho/settings.json file. Returns nil if none is found. The nearest // ancestor wins (no recursive merge — a project settings file fully shadows // global settings for its fields). func findProjectSettings() *Settings { @@ -276,7 +276,7 @@ func findProjectSettings() *Settings { } dir := cwd for { - candidate := filepath.Join(dir, ".hawk", "settings.json") + candidate := filepath.Join(dir, ".rho", "settings.json") if data, err := os.ReadFile(candidate); err == nil { // #nosec G304 -- path derived from cwd walk-up var s Settings if err := json.Unmarshal(data, &s); err == nil { @@ -441,7 +441,7 @@ func SaveGlobal(s Settings) error { // SettingValue returns a display-safe value for a supported setting key. func SettingValue(s Settings, key string) (string, bool) { normalized := normalizeSettingKey(key) - // Hawk: API key status comes from OS secret store, not settings file + // Rho: API key status comes from OS secret store, not settings file if provider, ok := apiKeyProviderFromSettingKey(normalized); ok { return EnvKeyStatus(provider), true } @@ -508,12 +508,12 @@ func SettingValue(s Settings, key string) (string, bool) { } } -// SetGlobalSetting updates a supported scalar/list setting in Hawk user config. -// Hawk: API keys are NOT stored in settings.json. Use /config and the OS secret store. +// SetGlobalSetting updates a supported scalar/list setting in Rho user config. +// Rho: API keys are NOT stored in settings.json. Use /config and the OS secret store. func SetGlobalSetting(key, value string) error { s := LoadGlobalSettings() normalized := normalizeSettingKey(key) - // Hawk: reject API key persistence to disk + // Rho: reject API key persistence to disk if _, ok := apiKeyProviderFromSettingKey(normalized); ok { return fmt.Errorf("API keys are not stored in settings.json. Save via /config (%s)", CredentialStoreName()) } @@ -687,7 +687,7 @@ func splitSettingList(value string) []string { func BoolPtr(b bool) *bool { return &b } // ───────────────────────────────────────────────────────────── -// Hawk: API keys from OS secret store only (no .env) +// Rho: API keys from OS secret store only (no .env) // ───────────────────────────────────────────────────────────── // ProviderAPIKeyEnv returns the API key env var for a provider (registry first for setup gateways). @@ -756,7 +756,7 @@ func providerCredentialEnvAliases(provider string) []string { // Live model catalog fetch from eyrie // ───────────────────────────────────────────────────────────── -// FetchModelsForProvider returns models from the eyrie catalog (dynamic; no hawk hardcoded lists). +// FetchModelsForProvider returns models from the eyrie catalog (dynamic; no rho hardcoded lists). // RefreshModelCatalogV1 is the explicit network refresh boundary. func FetchModelsForProvider(provider string) ([]EngineModel, error) { provider = gateway.NormalizeProviderID(provider) @@ -774,7 +774,7 @@ func FetchModelsForProvider(provider string) ([]EngineModel, error) { if err != nil { return nil, err } - // Custom OpenAI-compatible providers: single model from settings, not hawk catalog data. + // Custom OpenAI-compatible providers: single model from settings, not rho catalog data. for _, cp := range LoadSettings().CustomProviders { if gateway.NormalizeProviderID(cp.Name) != provider { continue @@ -786,7 +786,7 @@ func FetchModelsForProvider(provider string) ([]EngineModel, error) { }}, nil } } - return nil, fmt.Errorf("no models found for provider %s in eyrie catalog (check API keys; hawk will refresh automatically on next start)", provider) + return nil, fmt.Errorf("no models found for provider %s in eyrie catalog (check API keys; rho will refresh automatically on next start)", provider) } // FetchModelsForProviderWithSettings resolves cached models using one diff --git a/internal/config/settings_status_test.go b/internal/config/settings_status_test.go index 2fc290e6f..6dc70b411 100644 --- a/internal/config/settings_status_test.go +++ b/internal/config/settings_status_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestEnvKeyStatusUsesEyrieCredentialStatus(t *testing.T) { diff --git a/internal/config/settings_test.go b/internal/config/settings_test.go index b0bd78cfb..dab923e31 100644 --- a/internal/config/settings_test.go +++ b/internal/config/settings_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) func TestMergeSettings_ModelOverride(t *testing.T) { diff --git a/internal/config/setup_status_test.go b/internal/config/setup_status_test.go index 3a5223c4f..f0b4e52b4 100644 --- a/internal/config/setup_status_test.go +++ b/internal/config/setup_status_test.go @@ -7,7 +7,7 @@ import ( "github.com/GrayCodeAI/eyrie/catalog" "github.com/GrayCodeAI/eyrie/credentials" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestHasConfiguredDeployment_FromStore(t *testing.T) { diff --git a/internal/config/validator.go b/internal/config/validator.go index 696d36e3a..c75db7fba 100644 --- a/internal/config/validator.go +++ b/internal/config/validator.go @@ -62,7 +62,7 @@ func ValidateSettings(s Settings) ValidationResult { if activeProvider == "" { activeProvider = ActiveProvider(context.Background()) } - // Hawk: validate API key is in the OS secret store (not in settings) + // Rho: validate API key is in the OS secret store (not in settings) if activeProvider != "" { envKey := ProviderAPIKeyEnv(activeProvider) if envKey != "" && EnvKeyStatus(activeProvider) != "set" { diff --git a/internal/config/validator_test.go b/internal/config/validator_test.go index ab281a327..ce7d7161a 100644 --- a/internal/config/validator_test.go +++ b/internal/config/validator_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestValidateSettingsValid(t *testing.T) { diff --git a/internal/config/xiaomi_setup_test.go b/internal/config/xiaomi_setup_test.go index 300070753..0022ec872 100644 --- a/internal/config/xiaomi_setup_test.go +++ b/internal/config/xiaomi_setup_test.go @@ -10,7 +10,7 @@ import ( func TestSetGatewayRegion_XiaomiClearsStaleBaseHost(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", t.TempDir()) - t.Setenv("HAWK_CONFIG_DIR", dir) + t.Setenv("RHO_CONFIG_DIR", dir) t.Setenv("EYRIE_CONFIG_DIR", dir) t.Setenv("XIAOMI_MIMO_TOKEN_PLAN_BASE_URL", "https://caller-owned.example.test/v1") cfg := &eyriecfg.ProviderConfig{ @@ -36,7 +36,7 @@ func TestSetGatewayRegion_XiaomiClearsStaleBaseHost(t *testing.T) { func TestNeedsGatewayRegion_XiaomiInvalidAndMissing(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", t.TempDir()) - t.Setenv("HAWK_CONFIG_DIR", dir) + t.Setenv("RHO_CONFIG_DIR", dir) t.Setenv("EYRIE_CONFIG_DIR", dir) if !NeedsGatewayRegion(ProviderXiaomiTokenPlan) { diff --git a/internal/context/repomap/repomap.go b/internal/context/repomap/repomap.go index 4638166fc..c8132ef6a 100644 --- a/internal/context/repomap/repomap.go +++ b/internal/context/repomap/repomap.go @@ -1,12 +1,12 @@ // Package repomap is the prompt-injection shim that produces a token-budgeted -// repository overview for hawk's context layer. It builds an import/refer +// repository overview for rho's context layer. It builds an import/refer // graph over the source files in root, ranks the nodes with a PageRank // pass, and renders the highest-ranked files (and their top symbols) as a // compact text block capped at Options.Budget tokens. // // # Relationship to internal/intelligence/repomap // -// hawk ships a second package, internal/intelligence/repomap, that exposes +// rho ships a second package, internal/intelligence/repomap, that exposes // a much larger surface: call graphs, search, quality signals, API // scanning, incremental indexing, and so on. That package is the deep // analysis engine. THIS package is intentionally narrow: one entry point diff --git a/internal/context/repomap/scan.go b/internal/context/repomap/scan.go index 1f9411f13..0c54d53c7 100644 --- a/internal/context/repomap/scan.go +++ b/internal/context/repomap/scan.go @@ -12,7 +12,7 @@ var skipDirs = map[string]struct{}{ ".git": {}, "node_modules": {}, "vendor": {}, - ".hawk": {}, + ".rho": {}, "dist": {}, "build": {}, "target": {}, diff --git a/internal/contracts/agent/hooks.go b/internal/contracts/agent/hooks.go index 3ebc9ed40..b8e717dd3 100644 --- a/internal/contracts/agent/hooks.go +++ b/internal/contracts/agent/hooks.go @@ -1,13 +1,13 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package agent import "strings" // Hook event names for lifecycle and tool gates. // -// Hawk may accept vendor aliases (Claude/Cursor); normalize to these -// canonical names at the boundary. Constants are shared so hawk, plugins, +// Rho may accept vendor aliases (Claude/Cursor); normalize to these +// canonical names at the boundary. Constants are shared so rho, plugins, // and SDKs agree on wire/event vocabulary without importing engines. const ( HookPreToolUse = "PreToolUse" @@ -24,7 +24,7 @@ const ( HookFailure = "Failure" ) -// VendorHookAliases maps common third-party hook names to canonical Hawk names. +// VendorHookAliases maps common third-party hook names to canonical Rho names. // Keys are lower-case for case-insensitive lookup. var VendorHookAliases = map[string]string{ "pretooluse": HookPreToolUse, @@ -52,7 +52,7 @@ var VendorHookAliases = map[string]string{ "on_error": HookFailure, } -// CanonicalHookEvent returns the Hawk canonical event name for s, or "" if unknown. +// CanonicalHookEvent returns the Rho canonical event name for s, or "" if unknown. func CanonicalHookEvent(s string) string { if s == "" { return "" diff --git a/internal/contracts/agent/spawn.go b/internal/contracts/agent/spawn.go index 6f4baac94..53c6c40d5 100644 --- a/internal/contracts/agent/spawn.go +++ b/internal/contracts/agent/spawn.go @@ -1,5 +1,5 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. // Package agent defines shared DTOs for typed subagent spawn across graycode-eco. // // Stdlib only. No engine, CLI, or storage imports. diff --git a/internal/contracts/events/events.go b/internal/contracts/events/events.go index a22b20461..427af9fd6 100644 --- a/internal/contracts/events/events.go +++ b/internal/contracts/events/events.go @@ -1,10 +1,10 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package events import "time" -// ToolEvent represents a normalized tool event emitted by Hawk workflows. +// ToolEvent represents a normalized tool event emitted by Rho workflows. type ToolEvent struct { ToolName string `json:"tool_name"` ToolInput map[string]interface{} `json:"tool_input,omitempty"` diff --git a/internal/contracts/graph/graph.go b/internal/contracts/graph/graph.go index f9d5451e3..8bc3aef5b 100644 --- a/internal/contracts/graph/graph.go +++ b/internal/contracts/graph/graph.go @@ -1,5 +1,5 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. // Package graph defines the portable graph vocabulary shared across graycode-eco. // // The package contains data contracts only. Individual repositories retain diff --git a/internal/contracts/harness/harness.go b/internal/contracts/harness/harness.go index 0c1665556..09a589577 100644 --- a/internal/contracts/harness/harness.go +++ b/internal/contracts/harness/harness.go @@ -1,11 +1,11 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package harness import ( "time" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/types" + contracts "github.com/GrayCodeAI/rho/internal/contracts/types" ) // Dimension represents one of the 5 core Agent Work Loop dimensions. @@ -67,7 +67,7 @@ type AssetsDetected struct { AutonomyTier string `json:"autonomy_tier"` } -// Report is the neutral cross-repo contract for Hawk Agent Harness evaluations. +// Report is the neutral cross-repo contract for Rho Agent Harness evaluations. type Report struct { TargetPath string `json:"target_path"` GeneratedAt time.Time `json:"generated_at"` diff --git a/internal/contracts/policy/policy.go b/internal/contracts/policy/policy.go index 6ce810c9c..193653a58 100644 --- a/internal/contracts/policy/policy.go +++ b/internal/contracts/policy/policy.go @@ -1,5 +1,5 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package policy import ( diff --git a/internal/contracts/review/review.go b/internal/contracts/review/review.go index 06802dfed..9d35500a9 100644 --- a/internal/contracts/review/review.go +++ b/internal/contracts/review/review.go @@ -1,5 +1,5 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package review import ( @@ -7,10 +7,10 @@ import ( "strings" "time" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/types" + contracts "github.com/GrayCodeAI/rho/internal/contracts/types" ) -// Finding is the neutral review finding contract shared across Hawk and review engines. +// Finding is the neutral review finding contract shared across Rho and review engines. type Finding struct { Concern string `json:"concern"` Severity contracts.Severity `json:"severity"` diff --git a/internal/contracts/types/finding.go b/internal/contracts/types/finding.go index eed0aa2b4..3c5d9fe0c 100644 --- a/internal/contracts/types/finding.go +++ b/internal/contracts/types/finding.go @@ -1,5 +1,5 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package types import ( @@ -8,7 +8,7 @@ import ( "time" ) -// Finding represents a unified analysis concern sourced from Hawk support engines. +// Finding represents a unified analysis concern sourced from Rho support engines. type Finding struct { ID string `json:"id"` Source string `json:"source"` diff --git a/internal/contracts/types/severity.go b/internal/contracts/types/severity.go index 76813ba8f..ce772fed1 100644 --- a/internal/contracts/types/severity.go +++ b/internal/contracts/types/severity.go @@ -1,5 +1,5 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package types import ( diff --git a/internal/contracts/verify/verify.go b/internal/contracts/verify/verify.go index 7acc406ca..9650d35b4 100644 --- a/internal/contracts/verify/verify.go +++ b/internal/contracts/verify/verify.go @@ -1,14 +1,14 @@ // 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 Hawk as its contract surface. +// The upstream repository no longer exists; this copy is owned by Rho as its contract surface. package verify import ( "time" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/types" + contracts "github.com/GrayCodeAI/rho/internal/contracts/types" ) -// Finding is the neutral verification finding contract shared across Hawk and verification engines. +// Finding is the neutral verification finding contract shared across Rho and verification engines. type Finding struct { Check string `json:"check"` Severity contracts.Severity `json:"severity"` diff --git a/internal/crash/crash.go b/internal/crash/crash.go index ab67d9623..06957d9a8 100644 --- a/internal/crash/crash.go +++ b/internal/crash/crash.go @@ -31,7 +31,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) const ( @@ -151,7 +151,7 @@ func WriteReport(recovered any, stack []byte) (string, error) { func formatReport(r CrashReport) string { var b strings.Builder - b.WriteString("hawk crash report\n") + b.WriteString("rho crash report\n") fmt.Fprintf(&b, "timestamp: %s\n", r.Timestamp.Format(time.RFC3339Nano)) if r.Version != "" { fmt.Fprintf(&b, "version: %s\n", r.Version) diff --git a/internal/crash/crash_extra_test.go b/internal/crash/crash_extra_test.go index 23ecf8827..34b5d88a5 100644 --- a/internal/crash/crash_extra_test.go +++ b/internal/crash/crash_extra_test.go @@ -9,7 +9,7 @@ import ( "time" ) -const testEnvStateDir = "HAWK_STATE_DIR" +const testEnvStateDir = "RHO_STATE_DIR" // --- WriteReport tests --- @@ -112,7 +112,7 @@ func TestFormatReport_FullFields(t *testing.T) { } result := formatReport(r) - if !strings.Contains(result, "hawk crash report") { + if !strings.Contains(result, "rho crash report") { t.Error("report should have header") } if !strings.Contains(result, "1.2.3") { diff --git a/internal/crash/crash_test.go b/internal/crash/crash_test.go index 62d1a7a00..c913ae015 100644 --- a/internal/crash/crash_test.go +++ b/internal/crash/crash_test.go @@ -5,14 +5,14 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/crash" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/crash" + "github.com/GrayCodeAI/rho/internal/storage" ) // reportRoot returns the crash report dir by appending the subdir name to the // state dir, matching the layout crash.WriteReport uses. We go through the // public storage.StateDir so the test stays in-package-friendly and inherits -// the same HAWK_STATE_DIR override production uses. +// the same RHO_STATE_DIR override production uses. func reportRoot(t *testing.T) string { t.Helper() base := storage.StateDir() @@ -23,10 +23,10 @@ func reportRoot(t *testing.T) string { } func TestReportPath_InStateDir(t *testing.T) { - // HAWK_STATE_DIR must be set before Install/crash writes anything; mirror + // RHO_STATE_DIR must be set before Install/crash writes anything; mirror // the convention storage's own tests use. dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", dir) + t.Setenv("RHO_STATE_DIR", dir) got, err := crash.WriteReport("boom", []byte("goroutine 1 [running]:\nmain.main()\n")) if err != nil { @@ -43,7 +43,7 @@ func TestReportPath_InStateDir(t *testing.T) { func TestInstall_DoesNotPanic(t *testing.T) { dir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", dir) + t.Setenv("RHO_STATE_DIR", dir) // Install must be safe and idempotent. crash.Install() diff --git a/internal/crash/crash_unix.go b/internal/crash/crash_unix.go index f3bcd7c56..6e0d4c37c 100644 --- a/internal/crash/crash_unix.go +++ b/internal/crash/crash_unix.go @@ -53,7 +53,7 @@ func dumpSignal(sig syscall.Signal, reason string) { timestamp := now().UTC().Format("20060102T150405.000Z") filename := fmt.Sprintf("crash-signal-%s-%s.txt", sig, timestamp) path := filepath.Join(dir, filename) - content := fmt.Sprintf("hawk signal report\nsignal: %s\nreason: %s\ntimestamp: %s\n\n%s\n", + content := fmt.Sprintf("rho signal report\nsignal: %s\nreason: %s\ntimestamp: %s\n\n%s\n", sig, reason, timestamp, stacks) _ = os.WriteFile(path, []byte(content), 0o600) } diff --git a/internal/daemon/contract_parity_test.go b/internal/daemon/contract_parity_test.go index dc89e0a5a..a520d62e3 100644 --- a/internal/daemon/contract_parity_test.go +++ b/internal/daemon/contract_parity_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/hawk/internal/contracts/graph" - "github.com/GrayCodeAI/hawk/internal/testutil" + graphcontracts "github.com/GrayCodeAI/rho/internal/contracts/graph" + "github.com/GrayCodeAI/rho/internal/testutil" ) // TestDaemon_GraphSync_ContractMatrix locks the /v1/graph/sync parity contract diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 38276ed36..e86a4bdf9 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -22,12 +22,12 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/netutil" - "github.com/GrayCodeAI/hawk/internal/observability/metrics" - "github.com/GrayCodeAI/hawk/internal/securitylog" - hawksession "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/netutil" + "github.com/GrayCodeAI/rho/internal/observability/metrics" + "github.com/GrayCodeAI/rho/internal/securitylog" + rhosession "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" ) const maxRequestBodyBytes = 1 << 20 @@ -74,10 +74,10 @@ func (e *InvalidChatRequestError) Unwrap() error { return e.Err } // version is set via SetVersion from main.go at startup. var version = "0.0.0" -// SetVersion propagates the canonical hawk version into the daemon. +// SetVersion propagates the canonical rho version into the daemon. func SetVersion(v string) { version = v } -// Server is the hawk daemon HTTP server for programmatic/CI access. +// Server is the rho daemon HTTP server for programmatic/CI access. type Server struct { addr string mux *http.ServeMux @@ -99,7 +99,7 @@ type Server struct { readyMu sync.RWMutex readyFn func() (bool, string) - // graphFactory projects a persisted Hawk session into the portable, + // graphFactory projects a persisted Rho session into the portable, // privacy-safe execution graph. The composition root supplies the builder; // the daemon owns only HTTP validation, authentication, and encoding. graphMu sync.RWMutex @@ -125,6 +125,9 @@ type Server struct { // POST /v1/cancel to abort an in-flight generation (H10). cancelMu sync.Mutex cancels map[string]*cancelEntry + // reviewWG tracks in-flight `rho review run` subprocesses spawned by + // POST /v1/review so Stop can wait for them. + reviewWG sync.WaitGroup // General per-IP token bucket for non-chat API routes. apiLimiter *ipLimiter // Per-IP token bucket for /v1/chat generations (heavier, so lower rate). @@ -343,7 +346,7 @@ func (s *Server) Start() (string, error) { s.gateways.Start(context.Background()) } - slog.Info("hawk daemon started", "addr", actualAddr) + slog.Info("rho daemon started", "addr", actualAddr) return actualAddr, nil } @@ -382,7 +385,7 @@ func (s *Server) warnInsecureAuthConfig() { return } slog.Warn( - "hawk daemon started without API key authentication; only loopback access allowed", + "rho daemon started without API key authentication; only loopback access allowed", "addr", s.addr, "hint", "Set Config.APIKey to enable authentication, or keep the default loopback bind.", ) @@ -414,6 +417,16 @@ func (s *Server) Stop(ctx context.Context) error { if s.securityLog != nil { _ = s.securityLog.Close() } + // Wait for in-flight review subprocesses, bounded by the caller's context. + reviewsDone := make(chan struct{}) + go func() { + s.reviewWG.Wait() + close(reviewsDone) + }() + select { + case <-reviewsDone: + case <-ctx.Done(): + } _ = s.removePIDFile() return s.server.Shutdown(ctx) } @@ -540,11 +553,11 @@ func (s *Server) cancelSession(sessionID string) bool { return true } -// maxConcurrentFromEnv reads HAWK_DAEMON_MAX_CONCURRENT (clamped to >= 1) so +// maxConcurrentFromEnv reads RHO_DAEMON_MAX_CONCURRENT (clamped to >= 1) so // operators can tune the global chat concurrency cap without a rebuild. func maxConcurrentFromEnv() int { n := defaultMaxConcurrentChat - if raw := os.Getenv("HAWK_DAEMON_MAX_CONCURRENT"); raw != "" { + if raw := os.Getenv("RHO_DAEMON_MAX_CONCURRENT"); raw != "" { if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 { n = parsed } @@ -706,12 +719,12 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { lock.Lock() defer lock.Unlock() - var saved *hawksession.Session + var saved *rhosession.Session if requestedID != "" { var loadErr error - saved, loadErr = hawksession.Load(sessionID) + saved, loadErr = rhosession.Load(sessionID) if loadErr != nil { - if !errors.Is(loadErr, hawksession.ErrNotFound) { + if !errors.Is(loadErr, rhosession.ErrNotFound) { slog.Error("load continuation session failed", "err", loadErr, "session_id", sessionID) writeJSON(w, http.StatusInternalServerError, ErrorResponse{ Error: "session load failed", @@ -783,7 +796,7 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { return } if saved != nil { - sess.LoadMessages(hawksession.ToRuntimeMessages(saved.Messages)) + sess.LoadMessages(rhosession.ToRuntimeMessages(saved.Messages)) if err := sess.ReplayJournal(saved.Events); err != nil { slog.Error("replay session event journal failed", "err", err, "session_id", sessionID) writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "session event journal replay failed"}) @@ -807,7 +820,7 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { } if requested > max { writeJSON(w, http.StatusBadRequest, ErrorResponse{ - Error: fmt.Sprintf("autonomy %q exceeds the daemon's configured maximum (%s); the daemon is non-interactive and cannot approve escalated permissions. Raise Config.MaxAutonomy (HAWK_DAEMON_AUTONOMY) to allow it", requested.String(), max.String()), + Error: fmt.Sprintf("autonomy %q exceeds the daemon's configured maximum (%s); the daemon is non-interactive and cannot approve escalated permissions. Raise Config.MaxAutonomy (RHO_DAEMON_AUTONOMY) to allow it", requested.String(), max.String()), Code: "autonomy_denied", }) return @@ -867,11 +880,11 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { // streamSSE writes a streaming response as SSE events, observing client // disconnect via r.Context().Done() so the handler does not keep pushing // events to a dead connection. -func streamSSE(s *Server, w http.ResponseWriter, r *http.Request, events <-chan engine.StreamEvent, sessionID string, req ChatRequest, sess *engine.Session, saved *hawksession.Session, start time.Time) { +func streamSSE(s *Server, w http.ResponseWriter, r *http.Request, events <-chan engine.StreamEvent, sessionID string, req ChatRequest, sess *engine.Session, saved *rhosession.Session, start time.Time) { w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") - w.Header().Set("X-Hawk-Session-ID", sessionID) + w.Header().Set("X-Rho-Session-ID", sessionID) w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") flusher, _ := w.(http.Flusher) @@ -962,7 +975,7 @@ func (s *Server) abortStreamedSession(sessionID string) { } // writeJSONResponse accumulates events and writes a single JSON response. -func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.StreamEvent, sessionID string, req ChatRequest, sess *engine.Session, saved *hawksession.Session, start time.Time) { +func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.StreamEvent, sessionID string, req ChatRequest, sess *engine.Session, saved *rhosession.Session, start time.Time) { var response strings.Builder var totalIn, totalOut, turns int @@ -985,7 +998,7 @@ func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.St return } s.trackSession(sessionID, req, saved, start, turns) - w.Header().Set("X-Hawk-Session-ID", sessionID) + w.Header().Set("X-Rho-Session-ID", sessionID) writeJSON(w, http.StatusOK, ChatResponse{ SessionID: sessionID, @@ -998,7 +1011,7 @@ func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.St } func validSessionID(id string) bool { - return hawksession.ValidID(id) + return rhosession.ValidID(id) } // CancelRequest is the JSON body for POST /v1/cancel. @@ -1077,7 +1090,7 @@ func (s *Server) sessionLock(id string) *sync.Mutex { return &s.sessionLocks[hash%uint64(len(s.sessionLocks))] } -func persistDaemonSession(id string, req ChatRequest, sess *engine.Session, previous *hawksession.Session, startedAt time.Time) error { +func persistDaemonSession(id string, req ChatRequest, sess *engine.Session, previous *rhosession.Session, startedAt time.Time) error { createdAt := startedAt name := "" if previous != nil { @@ -1092,20 +1105,20 @@ func persistDaemonSession(id string, req ChatRequest, sess *engine.Session, prev if j := sess.Persistence().Journal(); j != nil { j.AppendSessionTitle(name) } - return hawksession.Save(&hawksession.Session{ + return rhosession.Save(&rhosession.Session{ ID: id, Model: sess.Model(), Provider: sess.Provider(), Agent: req.Agent, CWD: req.CWD, Name: name, - Messages: hawksession.FromRuntimeMessages(sess.RawMessages()), + Messages: rhosession.FromRuntimeMessages(sess.RawMessages()), Events: sess.JournalWire(), CreatedAt: createdAt, }) } -func (s *Server) trackSession(id string, req ChatRequest, previous *hawksession.Session, startedAt time.Time, turns int) { +func (s *Server) trackSession(id string, req ChatRequest, previous *rhosession.Session, startedAt time.Time, turns int) { createdAt := startedAt if previous != nil && !previous.CreatedAt.IsZero() { createdAt = previous.CreatedAt diff --git a/internal/daemon/daemon_test.go b/internal/daemon/daemon_test.go index 3dac3ca5d..fb0eef3ac 100644 --- a/internal/daemon/daemon_test.go +++ b/internal/daemon/daemon_test.go @@ -14,10 +14,10 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/engine" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/testutil" ) func startTestDaemon(t *testing.T, srv *Server) string { @@ -238,7 +238,7 @@ func TestDaemon_RejectsUnknownFields(t *testing.T) { } func TestDaemon_Chat_WithEngine(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) factory := func(req ChatRequest) (*engine.Session, error) { sess := engine.NewSession("", "test-model", "you are helpful", nil) if err := sess.SetMaxTurns(1); err != nil { @@ -315,7 +315,7 @@ func postDaemonChat(t *testing.T, addr string, request ChatRequest, accept strin } func TestDaemon_ChatPersistsRetrievableSessionAndRequestMetadata(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) requestedCWD := t.TempDir() canonicalCWD, err := canonicalSessionCWD(requestedCWD) if err != nil { @@ -336,8 +336,8 @@ func TestDaemon_ChatPersistsRetrievableSessionAndRequestMetadata(t *testing.T) { if resp.StatusCode != http.StatusOK { t.Fatalf("POST /v1/chat status = %d, want 200", resp.StatusCode) } - if chat.SessionID == "" || resp.Header.Get("X-Hawk-Session-ID") != chat.SessionID { - t.Fatalf("session ID response/header mismatch: body=%q header=%q", chat.SessionID, resp.Header.Get("X-Hawk-Session-ID")) + if chat.SessionID == "" || resp.Header.Get("X-Rho-Session-ID") != chat.SessionID { + t.Fatalf("session ID response/header mismatch: body=%q header=%q", chat.SessionID, resp.Header.Get("X-Rho-Session-ID")) } factoryReq := <-seen @@ -371,7 +371,7 @@ func TestDaemon_ChatPersistsRetrievableSessionAndRequestMetadata(t *testing.T) { } func TestDaemon_ChatContinuationReusesDurableSession(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) requestedCWD := t.TempDir() seen := make(chan ChatRequest, 2) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, daemonTestSessionFactory(seen)) @@ -419,7 +419,7 @@ func TestDaemon_ChatContinuationReusesDurableSession(t *testing.T) { } func TestDaemon_ChatRejectsMissingContinuation(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, daemonTestSessionFactory(nil)) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) @@ -432,7 +432,7 @@ func TestDaemon_ChatRejectsMissingContinuation(t *testing.T) { } func TestDaemon_ChatDoesNotMisreportCorruptContinuationAsMissing(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) if err := os.MkdirAll(storage.SessionsDir(), 0o750); err != nil { t.Fatal(err) } @@ -460,7 +460,7 @@ func TestDaemon_ChatDoesNotMisreportCorruptContinuationAsMissing(t *testing.T) { } func TestDaemon_ChatRejectsUnsafeSessionIDAndInvalidCWD(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, daemonTestSessionFactory(nil)) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) @@ -479,7 +479,7 @@ func TestDaemon_ChatRejectsUnsafeSessionIDAndInvalidCWD(t *testing.T) { } func TestDaemon_ChatSSEExposesRetrievableSessionID(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, daemonTestSessionFactory(nil)) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) @@ -490,7 +490,7 @@ func TestDaemon_ChatSSEExposesRetrievableSessionID(t *testing.T) { if err != nil { t.Fatalf("read SSE response: %v", err) } - id := resp.Header.Get("X-Hawk-Session-ID") + id := resp.Header.Get("X-Rho-Session-ID") if resp.StatusCode != http.StatusOK || id == "" || !strings.Contains(string(body), `"session_id":"`+id+`"`) { t.Fatalf("SSE status=%d id=%q body=%q", resp.StatusCode, id, body) } diff --git a/internal/daemon/discord.go b/internal/daemon/discord.go index 18e679803..ce6ebf87a 100644 --- a/internal/daemon/discord.go +++ b/internal/daemon/discord.go @@ -11,7 +11,7 @@ import ( "github.com/bwmarrin/discordgo" ) -// DiscordGateway bridges hawk to Discord via the official Gateway (WebSocket), +// DiscordGateway bridges rho to Discord via the official Gateway (WebSocket), // using bwmarrin/discordgo. Unlike a REST-poll bridge it receives message events // in real time — guild @mentions and direct messages — with reconnection, // heartbeats, and rate limiting handled by the library. Authorized prompts are @@ -21,7 +21,7 @@ type DiscordGateway struct { cfg DiscordConfig daemonAddr string apiKey string - client *http.Client // for forwardToHawk + client *http.Client // for forwardToRho auth *authorizer dispatch *asyncDispatcher @@ -140,7 +140,7 @@ func (g *DiscordGateway) handleMessage(ctx context.Context, senderID, channelID, if isPair, ok := g.auth.tryPair(senderID, text); isPair { if ok { - reply("Paired. You can now chat with hawk.") + reply("Paired. You can now chat with rho.") } else { reply("Pairing failed: invalid code.") } @@ -151,7 +151,7 @@ func (g *DiscordGateway) handleMessage(ctx context.Context, senderID, channelID, return } - resp, err := forwardToHawk(ctx, g.client, g.daemonAddr, g.apiKey, text) + resp, err := forwardToRho(ctx, g.client, g.daemonAddr, g.apiKey, text) if err != nil { resp = fmt.Sprintf("Error: %v", err) } diff --git a/internal/daemon/e2e_test.go b/internal/daemon/e2e_test.go index 46b966573..1381ed27a 100644 --- a/internal/daemon/e2e_test.go +++ b/internal/daemon/e2e_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/feature" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/feature" + "github.com/GrayCodeAI/rho/internal/testutil" ) // TestE2E_HealthEndpoint verifies the health endpoint returns the expected @@ -83,11 +83,11 @@ func TestE2E_MetricsEndpoint(t *testing.T) { if err != nil { t.Fatal(err) } - if !strings.Contains(string(body), "hawk_daemon_active_sessions") { - t.Error("expected hawk_daemon_active_sessions metric in output") + if !strings.Contains(string(body), "rho_daemon_active_sessions") { + t.Error("expected rho_daemon_active_sessions metric in output") } - if !strings.Contains(string(body), "hawk_daemon_uptime_seconds") { - t.Error("expected hawk_daemon_uptime_seconds metric in output") + if !strings.Contains(string(body), "rho_daemon_uptime_seconds") { + t.Error("expected rho_daemon_uptime_seconds metric in output") } // Test JSON format too. diff --git a/internal/daemon/gateway.go b/internal/daemon/gateway.go index 739537a1d..ef282e83b 100644 --- a/internal/daemon/gateway.go +++ b/internal/daemon/gateway.go @@ -11,11 +11,11 @@ import ( "sync" ) -// forwardToHawk posts a prompt to the daemon's /v1/chat endpoint and returns the +// forwardToRho posts a prompt to the daemon's /v1/chat endpoint and returns the // assistant reply. daemonAddr must include a scheme (e.g. "http://127.0.0.1:4590"). // apiKey, when non-empty, is sent as a Bearer token. Shared by the Discord and // Slack gateways; Telegram keeps its own method for backwards compatibility. -func forwardToHawk(ctx context.Context, client *http.Client, daemonAddr, apiKey, prompt string) (string, error) { +func forwardToRho(ctx context.Context, client *http.Client, daemonAddr, apiKey, prompt string) (string, error) { payload, _ := json.Marshal(map[string]string{"prompt": prompt}) req, err := http.NewRequestWithContext(ctx, http.MethodPost, daemonAddr+"/v1/chat", strings.NewReader(string(payload))) if err != nil { @@ -41,7 +41,7 @@ func forwardToHawk(ctx context.Context, client *http.Client, daemonAddr, apiKey, } // Gateway is a bidirectional messaging bridge between an external chat platform -// (Telegram, Discord, Slack, ...) and the hawk daemon. Implementations forward +// (Telegram, Discord, Slack, ...) and the rho daemon. Implementations forward // inbound messages to the daemon's /v1/chat endpoint and relay the reply back. type Gateway interface { // Name returns a short identifier for the gateway (e.g. "telegram"). diff --git a/internal/daemon/gateway_test.go b/internal/daemon/gateway_test.go index ffe340b7d..04ffcd500 100644 --- a/internal/daemon/gateway_test.go +++ b/internal/daemon/gateway_test.go @@ -211,7 +211,7 @@ func TestAuthorizer_PairingAndAllowlist(t *testing.T) { } } -func TestForwardToHawk(t *testing.T) { +func TestForwardToRho(t *testing.T) { var gotAuth, gotPrompt string ts := newIPv4GatewayServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuth = r.Header.Get("Authorization") @@ -224,9 +224,9 @@ func TestForwardToHawk(t *testing.T) { })) defer ts.Close() - reply, err := forwardToHawk(context.Background(), ts.Client(), ts.URL, "key123", "ping") + reply, err := forwardToRho(context.Background(), ts.Client(), ts.URL, "key123", "ping") if err != nil { - t.Fatalf("forwardToHawk: %v", err) + t.Fatalf("forwardToRho: %v", err) } if reply != "pong" { t.Errorf("reply=%q want pong", reply) @@ -367,22 +367,22 @@ func TestSlackGateway_RejectsBadSignature(t *testing.T) { } func TestDiscordGateway_HandleMessage_FlowsThroughAllowlist(t *testing.T) { - hawk := newIPv4GatewayServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, ChatResponse{Response: "hawk-reply"}) + rho := newIPv4GatewayServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, ChatResponse{Response: "rho-reply"}) })) - defer hawk.Close() + defer rho.Close() - g := newDiscordGateway(DiscordConfig{Token: "bot", PairingCode: "code"}, hawk.URL, "") + g := newDiscordGateway(DiscordConfig{Token: "bot", PairingCode: "code"}, rho.URL, "") var sent []string send := func(s string) error { sent = append(sent, s); return nil } - // Unauthorized non-pair message -> "Unauthorized", no hawk call. + // Unauthorized non-pair message -> "Unauthorized", no rho call. g.handleMessage(context.Background(), "u1", "C", "hello", send) // Wrong pairing code -> failure. g.handleMessage(context.Background(), "u1", "C", "/pair nope", send) // Correct pairing code -> paired. g.handleMessage(context.Background(), "u1", "C", "/pair code", send) - // Authorized -> forwarded to hawk. + // Authorized -> forwarded to rho. g.handleMessage(context.Background(), "u1", "C", "do it", send) if len(sent) != 4 { @@ -397,8 +397,8 @@ func TestDiscordGateway_HandleMessage_FlowsThroughAllowlist(t *testing.T) { if !strings.Contains(sent[2], "Paired") { t.Errorf("sent[2]=%q want Paired", sent[2]) } - if sent[3] != "hawk-reply" { - t.Errorf("sent[3]=%q want hawk-reply", sent[3]) + if sent[3] != "rho-reply" { + t.Errorf("sent[3]=%q want rho-reply", sent[3]) } if !g.auth.allowed("u1") { t.Errorf("u1 should be allowed after pairing") diff --git a/internal/daemon/graph_ledger_sqlite_test.go b/internal/daemon/graph_ledger_sqlite_test.go index dfb9b493f..afa37e2a5 100644 --- a/internal/daemon/graph_ledger_sqlite_test.go +++ b/internal/daemon/graph_ledger_sqlite_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestSQLiteGraphLedger_PersistsAcrossReopen(t *testing.T) { @@ -17,10 +17,10 @@ func TestSQLiteGraphLedger_PersistsAcrossReopen(t *testing.T) { SyncID: "sync-1", ProjectID: "proj", SessionID: "sess", - SchemaVersion: "hawk.graph/v1", + SchemaVersion: "rho.graph/v1", Digest: "abc123", Facts: 4, - GraphJSON: `{"schema_version":"hawk.graph/v1","nodes":[]}`, + GraphJSON: `{"schema_version":"rho.graph/v1","nodes":[]}`, ReceivedAt: time.Now().UTC(), } @@ -55,7 +55,7 @@ func TestSQLiteGraphLedger_PersistsAcrossReopen(t *testing.T) { if got.Digest != "abc123" || got.Facts != 4 || got.GraphJSON != rec.GraphJSON { t.Fatalf("retained record mismatch: %+v", got) } - if got.ProjectID != "proj" || got.SessionID != "sess" || got.SchemaVersion != "hawk.graph/v1" { + if got.ProjectID != "proj" || got.SessionID != "sess" || got.SchemaVersion != "rho.graph/v1" { t.Fatalf("retained metadata mismatch: %+v", got) } if got.ReceivedAt.IsZero() { diff --git a/internal/daemon/h9_h10_test.go b/internal/daemon/h9_h10_test.go index 3782dba3d..a9398cb48 100644 --- a/internal/daemon/h9_h10_test.go +++ b/internal/daemon/h9_h10_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/testutil" ) // TestIPLimiter_BurstAndPerIPIsolation verifies the token bucket allows up to @@ -111,7 +111,7 @@ func TestDaemon_CancelEndpoint(t *testing.T) { // TestDaemon_GlobalConcurrencyCap verifies handleChat returns 503 when the // global concurrency semaphore is saturated (H9). func TestDaemon_GlobalConcurrencyCap(t *testing.T) { - t.Setenv("HAWK_DAEMON_MAX_CONCURRENT", "1") + t.Setenv("RHO_DAEMON_MAX_CONCURRENT", "1") srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, daemonTestSessionFactory(nil)) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) diff --git a/internal/daemon/middleware.go b/internal/daemon/middleware.go index 70a5b5a97..52c3e9502 100644 --- a/internal/daemon/middleware.go +++ b/internal/daemon/middleware.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/feature" + "github.com/GrayCodeAI/rho/internal/feature" ) // requestIDKey is the context key for the request ID. diff --git a/internal/daemon/middleware_test.go b/internal/daemon/middleware_test.go index 2c29af36c..92286ba08 100644 --- a/internal/daemon/middleware_test.go +++ b/internal/daemon/middleware_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/feature" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/feature" + "github.com/GrayCodeAI/rho/internal/testutil" ) // --- Pure helper unit tests --- diff --git a/internal/daemon/routes_agent_status_test.go b/internal/daemon/routes_agent_status_test.go index e6c1d7a51..df3d62078 100644 --- a/internal/daemon/routes_agent_status_test.go +++ b/internal/daemon/routes_agent_status_test.go @@ -13,7 +13,7 @@ func TestAgentStatusStates(t *testing.T) { now := time.Now() // working: in-flight cancel registered. - s.sessions.Store("working-1", &Session{ID: "working-1", Agent: "hawk", Turns: 3, LastUsed: now}) + s.sessions.Store("working-1", &Session{ID: "working-1", Agent: "rho", Turns: 3, LastUsed: now}) s.cancelMu.Lock() s.cancels["working-1"] = &cancelEntry{cancel: func() {}} s.cancelMu.Unlock() diff --git a/internal/daemon/routes_graph.go b/internal/daemon/routes_graph.go index 2eee3f19f..4f78b4d9b 100644 --- a/internal/daemon/routes_graph.go +++ b/internal/daemon/routes_graph.go @@ -9,8 +9,8 @@ import ( "time" "unicode" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/session" ) const ( @@ -26,7 +26,7 @@ type GraphRequest struct { GeneratedAt time.Time } -// GraphFactory projects one persisted session into Hawk's portable graph. +// GraphFactory projects one persisted session into Rho's portable graph. type GraphFactory func(context.Context, GraphRequest) (executiongraph.Export, error) // handleGetSessionGraph handles GET /v1/sessions/{id}/graph. diff --git a/internal/daemon/routes_graph_read.go b/internal/daemon/routes_graph_read.go index c7d30d58b..0cc93f865 100644 --- a/internal/daemon/routes_graph_read.go +++ b/internal/daemon/routes_graph_read.go @@ -17,7 +17,7 @@ import ( const ( defaultGraphReadLimit = 100 maxGraphReadLimit = 250 - graphReadSchemaVersion = "hawk-local.graph/v1" + graphReadSchemaVersion = "rho-local.graph/v1" ) // GraphReadResponse is the JSON response for GET /v1/projects/:projectId/graph. diff --git a/internal/daemon/routes_graph_read_test.go b/internal/daemon/routes_graph_read_test.go index 12fdff547..93735227a 100644 --- a/internal/daemon/routes_graph_read_test.go +++ b/internal/daemon/routes_graph_read_test.go @@ -5,7 +5,7 @@ import ( "net/http" "testing" - "github.com/GrayCodeAI/hawk/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/executiongraph" ) func getGraphRead(t *testing.T, addr, path string) *http.Response { @@ -49,8 +49,8 @@ func TestDaemon_GraphRead_Empty(t *testing.T) { t.Fatalf("expected 200, got %d", resp.StatusCode) } out := decodeGraphRead(t, resp) - if out.Graph.SchemaVersion != "hawk-local.graph/v1" { - t.Errorf("schema_version = %q, want hawk-local.graph/v1", out.Graph.SchemaVersion) + if out.Graph.SchemaVersion != "rho-local.graph/v1" { + t.Errorf("schema_version = %q, want rho-local.graph/v1", out.Graph.SchemaVersion) } if out.Graph.Scope["project_id"] != "proj" { t.Errorf("scope.project_id = %q, want proj", out.Graph.Scope["project_id"]) diff --git a/internal/daemon/routes_graph_sync.go b/internal/daemon/routes_graph_sync.go index 02ebb9387..d45b36215 100644 --- a/internal/daemon/routes_graph_sync.go +++ b/internal/daemon/routes_graph_sync.go @@ -12,12 +12,12 @@ import ( "strings" "time" - graphcontracts "github.com/GrayCodeAI/hawk/internal/contracts/graph" - "github.com/GrayCodeAI/hawk/internal/executiongraph" + graphcontracts "github.com/GrayCodeAI/rho/internal/contracts/graph" + "github.com/GrayCodeAI/rho/internal/executiongraph" ) // POST /v1/graph/sync lets a producer (a future ecosystem repo) push portable -// `*.graph/v1` facts into Hawk. It mirrors the cloud plane's /v1/graph/sync +// `*.graph/v1` facts into Rho. It mirrors the cloud plane's /v1/graph/sync // contract so a producer can target either surface with the same payload. // The daemon is a localhost consumer surface: it validates the graph, rejects // malformed or non-portable facts, and acknowledges with an idempotency digest. diff --git a/internal/daemon/routes_graph_sync_test.go b/internal/daemon/routes_graph_sync_test.go index 41e28538e..cbb32c2da 100644 --- a/internal/daemon/routes_graph_sync_test.go +++ b/internal/daemon/routes_graph_sync_test.go @@ -11,9 +11,9 @@ import ( "testing" "time" - graphcontracts "github.com/GrayCodeAI/hawk/internal/contracts/graph" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - "github.com/GrayCodeAI/hawk/internal/testutil" + graphcontracts "github.com/GrayCodeAI/rho/internal/contracts/graph" + "github.com/GrayCodeAI/rho/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/testutil" ) func newGraphSyncTestServer(t *testing.T) string { diff --git a/internal/daemon/routes_graph_test.go b/internal/daemon/routes_graph_test.go index 3794ee624..3d29958ed 100644 --- a/internal/daemon/routes_graph_test.go +++ b/internal/daemon/routes_graph_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/executiongraph" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/executiongraph" + "github.com/GrayCodeAI/rho/internal/session" ) func TestGetSessionGraphProjectsBoundedRequest(t *testing.T) { @@ -27,7 +27,7 @@ func TestGetSessionGraphProjectsBoundedRequest(t *testing.T) { request := httptest.NewRequest( http.MethodGet, - "/v1/sessions/session-1/graph?repository=hawk&swift_checkpoint=012345abcdef&swift_checkpoint=fedcba987654", + "/v1/sessions/session-1/graph?repository=rho&swift_checkpoint=012345abcdef&swift_checkpoint=fedcba987654", nil, ) request.Header.Set("Authorization", "Bearer secret") @@ -37,7 +37,7 @@ func TestGetSessionGraphProjectsBoundedRequest(t *testing.T) { if response.Code != http.StatusOK { t.Fatalf("status = %d, want 200: %s", response.Code, response.Body.String()) } - if captured.SessionID != "session-1" || captured.RepositoryID != "hawk" { + if captured.SessionID != "session-1" || captured.RepositoryID != "rho" { t.Fatalf("captured request = %#v", captured) } wantCheckpoints := []string{"012345abcdef", "fedcba987654"} @@ -92,7 +92,7 @@ func TestGetSessionGraphRejectsInvalidInputBeforeFactory(t *testing.T) { }, { name: "repository control character", - url: "/v1/sessions/session-1/graph?repository=hawk%0Aother", + url: "/v1/sessions/session-1/graph?repository=rho%0Aother", code: "invalid_repository", }, { diff --git a/internal/daemon/routes_lease.go b/internal/daemon/routes_lease.go index 60d60eb38..19e10877e 100644 --- a/internal/daemon/routes_lease.go +++ b/internal/daemon/routes_lease.go @@ -6,7 +6,7 @@ import ( "errors" "net/http" - "github.com/GrayCodeAI/hawk/internal/session" + "github.com/GrayCodeAI/rho/internal/session" ) // handleAcquireLease creates or refreshes a single-owner lease on a session, diff --git a/internal/daemon/routes_lease_test.go b/internal/daemon/routes_lease_test.go index 6738c866f..8898e48b5 100644 --- a/internal/daemon/routes_lease_test.go +++ b/internal/daemon/routes_lease_test.go @@ -6,8 +6,8 @@ import ( "net/http" "testing" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/testutil" ) func httpDo(t *testing.T, method, url string) *http.Response { @@ -24,7 +24,7 @@ func httpDo(t *testing.T, method, url string) *http.Response { } func TestAcquireAndReleaseLease(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, nil) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) diff --git a/internal/daemon/routes_metrics.go b/internal/daemon/routes_metrics.go index e25d35641..a408e1f1c 100644 --- a/internal/daemon/routes_metrics.go +++ b/internal/daemon/routes_metrics.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/observability/metrics" + "github.com/GrayCodeAI/rho/internal/observability/metrics" ) // handleMetrics handles GET /v1/metrics. It exposes daemon-level metrics in @@ -76,16 +76,16 @@ func (s *Server) emitRuntimeMetrics(sb *strings.Builder) { return true }) - sb.WriteString("# TYPE hawk_daemon_active_sessions gauge\n") - sb.WriteString(fmt.Sprintf("hawk_daemon_active_sessions %d\n", activeSessions)) + sb.WriteString("# TYPE rho_daemon_active_sessions gauge\n") + sb.WriteString(fmt.Sprintf("rho_daemon_active_sessions %d\n", activeSessions)) // Concurrency slots used - sb.WriteString("# TYPE hawk_daemon_chat_concurrency_used gauge\n") - sb.WriteString(fmt.Sprintf("hawk_daemon_chat_concurrency_used %d\n", len(s.concurrencySem))) + sb.WriteString("# TYPE rho_daemon_chat_concurrency_used gauge\n") + sb.WriteString(fmt.Sprintf("rho_daemon_chat_concurrency_used %d\n", len(s.concurrencySem))) // Uptime - sb.WriteString("# TYPE hawk_daemon_uptime_seconds gauge\n") - sb.WriteString(fmt.Sprintf("hawk_daemon_uptime_seconds %.0f\n", time.Since(s.startedAt).Seconds())) + sb.WriteString("# TYPE rho_daemon_uptime_seconds gauge\n") + sb.WriteString(fmt.Sprintf("rho_daemon_uptime_seconds %.0f\n", time.Since(s.startedAt).Seconds())) } // sanitizeMetricName converts a dotted metric name to Prometheus naming diff --git a/internal/daemon/routes_review.go b/internal/daemon/routes_review.go index 1a1874d48..4468433fc 100644 --- a/internal/daemon/routes_review.go +++ b/internal/daemon/routes_review.go @@ -7,6 +7,7 @@ import ( "os/exec" "regexp" "strings" + "time" ) var validSHA = regexp.MustCompile(`^[0-9a-f]{7,40}$`) @@ -19,13 +20,17 @@ const reviewArgMaxLen = 4096 var reviewArgCharset = regexp.MustCompile(`^[^\x00-\x1f\x7f]*$`) -// reviewSem bounds the number of concurrent `hawk review run` subprocesses +// reviewSem bounds the number of concurrent `rho review run` subprocesses // spawned by POST /v1/review so an authenticated caller cannot exhaust CPU // or memory by firing unbounded review jobs. var reviewSem = make(chan struct{}, maxConcurrentReviews) const maxConcurrentReviews = 4 +// reviewTimeout bounds a spawned `rho review run` subprocess so a hung review +// cannot run forever and cannot block daemon shutdown. +const reviewTimeout = 30 * time.Minute + // ReviewRequest is the JSON body for POST /v1/review. type ReviewRequest struct { SHA string `json:"sha"` @@ -82,17 +87,22 @@ func (s *Server) handleReview(w http.ResponseWriter, r *http.Request) { } // Bound concurrent review spawns: refuse (503) rather than queue when the - // limit is reached so a burst cannot pile up unbounded subprocesses. + // limit is reached so a burst cannot pile up unbounded subprocesses. The + // slot is released when the review subprocess finishes (in the goroutine + // below), not when this handler returns, so it bounds running reviews. select { case reviewSem <- struct{}{}: - defer func() { <-reviewSem }() default: writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "server busy: too many reviews in flight"}) return } - // Trigger review asynchronously via hawk review run. + // Trigger review asynchronously via rho review run. Track it in the + // server lifecycle so Stop can wait for in-flight reviews. + s.reviewWG.Add(1) go func() { + defer s.reviewWG.Done() + defer func() { <-reviewSem }() args := []string{"review", "run", req.SHA, "--background"} if req.Model != "" { args = append(args, "--model", req.Model) @@ -100,7 +110,9 @@ func (s *Server) handleReview(w http.ResponseWriter, r *http.Request) { if req.Concerns != "" { args = append(args, "--concerns", req.Concerns) } - _ = exec.CommandContext(context.Background(), "hawk", args...).Run() // #nosec G204 -- binary is fixed "hawk"; args are validated (SHA regex, no "--" prefix, printable charset) + ctx, cancel := context.WithTimeout(context.Background(), reviewTimeout) + defer cancel() + _ = exec.CommandContext(ctx, "rho", args...).Run() // #nosec G204 -- binary is fixed "rho"; args are validated (SHA regex, no "--" prefix, printable charset) }() resp := ReviewResponse{ @@ -112,8 +124,8 @@ func (s *Server) handleReview(w http.ResponseWriter, r *http.Request) { } func (s *Server) handleReviewStatus(w http.ResponseWriter, _ *http.Request) { - // Run hawk review status and return output. - out, err := exec.CommandContext(context.Background(), "hawk", "review", "status").Output() + // Run rho review status and return output. + out, err := exec.CommandContext(context.Background(), "rho", "review", "status").Output() if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return diff --git a/internal/daemon/routes_review_test.go b/internal/daemon/routes_review_test.go index e63635685..a51728eb5 100644 --- a/internal/daemon/routes_review_test.go +++ b/internal/daemon/routes_review_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/testutil" ) // newReviewTestServer starts a daemon. RegisterReviewRoutes() is already @@ -119,7 +119,7 @@ func TestDaemon_ReviewStatus(t *testing.T) { } defer resp.Body.Close() - // `hawk review status` shells out to the hawk binary; in a sandboxed + // `rho review status` shells out to the rho binary; in a sandboxed // test environment that command may not resolve, so the handler's own // 500 branch is just as valid an outcome as a real 200 — both are // well-defined, deterministic behavior we can assert on. diff --git a/internal/daemon/routes_sessions.go b/internal/daemon/routes_sessions.go index 40a72d216..d80c1ed54 100644 --- a/internal/daemon/routes_sessions.go +++ b/internal/daemon/routes_sessions.go @@ -8,8 +8,8 @@ import ( "path/filepath" "strconv" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" ) // handleGetSession handles GET /v1/sessions/{id} — get session detail. diff --git a/internal/daemon/routes_sessions_test.go b/internal/daemon/routes_sessions_test.go index 9b9fa8392..551b8ccf1 100644 --- a/internal/daemon/routes_sessions_test.go +++ b/internal/daemon/routes_sessions_test.go @@ -7,15 +7,15 @@ import ( "testing" contracts "github.com/GrayCodeAI/eyrie/tools" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/testutil" ) // saveTestSession isolates session storage to a temp dir and persists a // session with the given messages, returning its ID. func saveTestSession(t *testing.T, id string, messages []session.Message) { t.Helper() - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) sess := &session.Session{ ID: id, @@ -144,7 +144,7 @@ func TestDaemon_GetMessages_NoPaginationParams(t *testing.T) { } func TestDaemon_GetMessages_MissingSession(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, nil) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) @@ -190,7 +190,7 @@ func TestDaemon_DeleteSession_Success(t *testing.T) { } func TestDaemon_DeleteSession_InvalidID(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, nil) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) @@ -211,7 +211,7 @@ func TestDaemon_DeleteSession_InvalidID(t *testing.T) { } func TestDaemon_DeleteSession_NotFound(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, nil) addr := startTestDaemon(t, srv) defer srv.Stop(context.Background()) diff --git a/internal/daemon/routes_stats.go b/internal/daemon/routes_stats.go index 5adacfcc2..585bd73e8 100644 --- a/internal/daemon/routes_stats.go +++ b/internal/daemon/routes_stats.go @@ -5,7 +5,7 @@ import ( "strconv" "time" - analytics "github.com/GrayCodeAI/hawk/internal/observability" + analytics "github.com/GrayCodeAI/rho/internal/observability" ) // handleStats handles GET /v1/stats — get aggregated usage statistics. diff --git a/internal/daemon/routes_stats_test.go b/internal/daemon/routes_stats_test.go index 6b154d7ed..06e8958d9 100644 --- a/internal/daemon/routes_stats_test.go +++ b/internal/daemon/routes_stats_test.go @@ -7,12 +7,12 @@ import ( "testing" "time" - analytics "github.com/GrayCodeAI/hawk/internal/observability" - "github.com/GrayCodeAI/hawk/internal/testutil" + analytics "github.com/GrayCodeAI/rho/internal/observability" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestDaemon_Stats_Aggregation(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) now := time.Now() traces := []*analytics.SessionTrace{ @@ -79,7 +79,7 @@ func TestDaemon_Stats_Aggregation(t *testing.T) { } func TestDaemon_Stats_DaysParam(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) old := &analytics.SessionTrace{ SessionID: "old", StartTime: time.Now().AddDate(0, 0, -10), Model: "m", MessageCount: 1, @@ -120,7 +120,7 @@ func TestDaemon_Stats_DaysParam(t *testing.T) { } func TestDaemon_Stats_Empty(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) srv := New(Config{Port: 0, Host: testutil.LoopbackHost}, nil) addr := startTestDaemon(t, srv) diff --git a/internal/daemon/routes_status.go b/internal/daemon/routes_status.go index 9d435b15f..7c089a957 100644 --- a/internal/daemon/routes_status.go +++ b/internal/daemon/routes_status.go @@ -3,14 +3,14 @@ package daemon import ( "net/http" - "github.com/GrayCodeAI/hawk/internal/status" + "github.com/GrayCodeAI/rho/internal/status" ) // handleStatus returns a process-local, redacted daemon snapshot. It does not // initialize providers or MCP servers and is safe to call during startup. func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { snapshot := status.New() - snapshot.HawkVersion = version + snapshot.RhoVersion = version snapshot.Workspace = status.Workspace() if s.startedAt.IsZero() { snapshot.Recovery = "not_started" diff --git a/internal/daemon/slack.go b/internal/daemon/slack.go index 1ac7993d5..9580acaab 100644 --- a/internal/daemon/slack.go +++ b/internal/daemon/slack.go @@ -81,7 +81,7 @@ func newSlackGateway(cfg SlackConfig, daemonAddr, apiKey string, s *Server) *Sla func (g *SlackGateway) Name() string { return "slack" } // setDaemonURL implements daemonURLSetter. Slack forwards to the daemon via -// forwardToHawk, so it needs the resolved address even though it replies via the +// forwardToRho, so it needs the resolved address even though it replies via the // Slack Web API. func (g *SlackGateway) setDaemonURL(url string) { g.daemonAddr = url } @@ -167,7 +167,7 @@ func (g *SlackGateway) handleMention(ctx context.Context, ev slackEventInner) { if isPair, ok := g.auth.tryPair(ev.User, text); isPair { if ok { - g.reply(ctx, ev.Channel, threadTS, "Paired. You can now chat with hawk.") + g.reply(ctx, ev.Channel, threadTS, "Paired. You can now chat with rho.") } else { g.reply(ctx, ev.Channel, threadTS, "Pairing failed: invalid code.") } @@ -178,7 +178,7 @@ func (g *SlackGateway) handleMention(ctx context.Context, ev slackEventInner) { return } - reply, err := forwardToHawk(ctx, g.client, g.daemonAddr, g.apiKey, text) + reply, err := forwardToRho(ctx, g.client, g.daemonAddr, g.apiKey, text) if err != nil { reply = fmt.Sprintf("Error: %v", err) } diff --git a/internal/daemon/telegram.go b/internal/daemon/telegram.go index a816de47b..f7898da0f 100644 --- a/internal/daemon/telegram.go +++ b/internal/daemon/telegram.go @@ -1,5 +1,5 @@ -// Package daemon provides a Telegram gateway for hawk. -// Allows users to interact with hawk via Telegram bot messages. +// Package daemon provides a Telegram gateway for rho. +// Allows users to interact with rho via Telegram bot messages. package daemon import ( @@ -15,13 +15,13 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/stt" + "github.com/GrayCodeAI/rho/internal/stt" ) -// TelegramGateway connects hawk to a Telegram bot. +// TelegramGateway connects rho to a Telegram bot. type TelegramGateway struct { Token string - DaemonAddr string // hawk daemon address to forward messages to + DaemonAddr string // rho daemon address to forward messages to client *http.Client offset int @@ -188,7 +188,7 @@ func (tg *TelegramGateway) handleMessage(ctx context.Context, msg *TelegramMessa sender := telegramSenderID(msg) if isPair, ok := tg.auth.tryPair(sender, msg.Text); isPair { if ok { - tg.reply(ctx, msg.Chat.ID, "Paired. You can now chat with hawk.") + tg.reply(ctx, msg.Chat.ID, "Paired. You can now chat with rho.") } else { tg.reply(ctx, msg.Chat.ID, "Pairing failed: invalid code.") } @@ -213,8 +213,8 @@ func (tg *TelegramGateway) handleMessage(ctx context.Context, msg *TelegramMessa } } - // Forward to hawk daemon - response, err := tg.forwardToHawk(ctx, prompt) + // Forward to rho daemon + response, err := tg.forwardToRho(ctx, prompt) if err != nil { response = fmt.Sprintf("Error: %v", err) } @@ -311,7 +311,7 @@ func (tg *TelegramGateway) reply(ctx context.Context, chatID int64, text string) } } -func (tg *TelegramGateway) forwardToHawk(ctx context.Context, prompt string) (string, error) { +func (tg *TelegramGateway) forwardToRho(ctx context.Context, prompt string) (string, error) { // Use json.Marshal for safe JSON encoding instead of fmt.Sprintf // with %q, which does not handle all JSON edge cases (e.g., control // characters, surrogate pairs). @@ -337,7 +337,7 @@ func (tg *TelegramGateway) forwardToHawk(ctx context.Context, prompt string) (st // Limit response body to 1 MiB to prevent memory exhaustion. body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { - slog.Warn("partial read in forwardToHawk response", "error", err) + slog.Warn("partial read in forwardToRho response", "error", err) } var chatResp struct { Response string `json:"response"` diff --git a/internal/daemon/telegram_test.go b/internal/daemon/telegram_test.go index 3697c050e..ab6e6acce 100644 --- a/internal/daemon/telegram_test.go +++ b/internal/daemon/telegram_test.go @@ -28,22 +28,22 @@ func newIPv4TelegramServer(t *testing.T, h http.Handler) *httptest.Server { return srv } -// telegramMockAPI captures sendMessage calls and serves a fake Telegram + hawk +// telegramMockAPI captures sendMessage calls and serves a fake Telegram + rho // endpoint. The Telegram bot API base is hardcoded in telegram.go, so we instead // drive handleMessage directly and observe outbound sends via a mock daemon. func TestTelegram_HandleMessage_Authorization(t *testing.T) { - // Mock hawk daemon /v1/chat. - hawk := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, ChatResponse{Response: "hawk-reply"}) + // Mock rho daemon /v1/chat. + rho := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, ChatResponse{Response: "rho-reply"}) })) - defer hawk.Close() + defer rho.Close() // Mock Telegram sendMessage endpoint by intercepting via a custom transport. var mu sync.Mutex var sends []string tg := newTelegramGatewayFromConfig( TelegramConfig{Token: "tok", PairingCode: "open"}, - hawk.URL, "apikey", + rho.URL, "apikey", ) tg.client = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { if strings.Contains(req.URL.Path, "/sendMessage") { @@ -53,7 +53,7 @@ func TestTelegram_HandleMessage_Authorization(t *testing.T) { mu.Unlock() return jsonResp(`{"ok":true}`), nil } - // forward-to-hawk goes to the real mock server; let it through. + // forward-to-rho goes to the real mock server; let it through. return http.DefaultTransport.RoundTrip(req) })} @@ -64,13 +64,13 @@ func TestTelegram_HandleMessage_Authorization(t *testing.T) { return m } - // 1. Unauthorized non-pair message -> "Unauthorized" reply, no hawk call. + // 1. Unauthorized non-pair message -> "Unauthorized" reply, no rho call. tg.handleMessage(context.Background(), mkMsg("hello")) // 2. Wrong pairing code -> failure reply. tg.handleMessage(context.Background(), mkMsg("/pair wrong")) // 3. Correct pairing code -> paired reply. tg.handleMessage(context.Background(), mkMsg("/pair open")) - // 4. Now authorized -> hawk reply forwarded. + // 4. Now authorized -> rho reply forwarded. tg.handleMessage(context.Background(), mkMsg("do something")) mu.Lock() @@ -87,8 +87,8 @@ func TestTelegram_HandleMessage_Authorization(t *testing.T) { if !strings.Contains(sends[2], "Paired") { t.Errorf("send[2]=%q want Paired", sends[2]) } - if sends[3] != "hawk-reply" { - t.Errorf("send[3]=%q want hawk-reply", sends[3]) + if sends[3] != "rho-reply" { + t.Errorf("send[3]=%q want rho-reply", sends[3]) } if !tg.auth.allowed("alice") { t.Errorf("alice should be allowed after pairing") @@ -112,16 +112,16 @@ func TestTelegramSenderID(t *testing.T) { func TestTelegram_BareConstructorFailsClosed(t *testing.T) { // The bare constructor seeds an empty authorizer, so it must refuse all - // senders (no pairing code / allowlist) rather than forwarding to hawk. - hawkCalled := false - hawk := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - hawkCalled = true + // senders (no pairing code / allowlist) rather than forwarding to rho. + rhoCalled := false + rho := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rhoCalled = true writeJSON(w, http.StatusOK, ChatResponse{Response: "ok"}) })) - defer hawk.Close() + defer rho.Close() var sends []string - tg := NewTelegramGateway("tok", hawk.URL) + tg := NewTelegramGateway("tok", rho.URL) tg.client = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { if strings.Contains(req.URL.Path, "/sendMessage") { _ = req.ParseForm() @@ -137,8 +137,8 @@ func TestTelegram_BareConstructorFailsClosed(t *testing.T) { if len(sends) != 1 || !strings.Contains(sends[0], "Unauthorized") { t.Fatalf("expected an Unauthorized reply, got %v", sends) } - if hawkCalled { - t.Fatal("bare constructor must not forward unauthorized messages to hawk") + if rhoCalled { + t.Fatal("bare constructor must not forward unauthorized messages to rho") } } @@ -166,14 +166,14 @@ func TestTelegramSafeFileName(t *testing.T) { } func TestTelegramVoiceWithoutTranscriberFallsBackToText(t *testing.T) { - hawk := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, ChatResponse{Response: "hawk-reply"}) + rho := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, ChatResponse{Response: "rho-reply"}) })) - defer hawk.Close() + defer rho.Close() // No STT transcriber installed: a voice message must fall back to the // existing text path (transcribeAudio returns "" with no reply). - tg := newTelegramGatewayFromConfig(TelegramConfig{Token: "tok", AllowList: []string{"user"}}, hawk.URL, "k") + tg := newTelegramGatewayFromConfig(TelegramConfig{Token: "tok", AllowList: []string{"user"}}, rho.URL, "k") tg.handleMessage(context.Background(), &TelegramMessage{ Text: "hello", Chat: struct { diff --git a/internal/diffsandbox/sandbox.go b/internal/diffsandbox/sandbox.go index 42485880c..b6ad72467 100644 --- a/internal/diffsandbox/sandbox.go +++ b/internal/diffsandbox/sandbox.go @@ -11,7 +11,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/theme" + "github.com/GrayCodeAI/rho/internal/theme" ) // ChangeType identifies the kind of file modification. diff --git a/internal/engine/adaptive_prompt.go b/internal/engine/adaptive_prompt.go index 4d54dcdf4..81edd807a 100644 --- a/internal/engine/adaptive_prompt.go +++ b/internal/engine/adaptive_prompt.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/textutil" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/textutil" ) // AdaptivePrompt adjusts system prompt sections based on user corrections. @@ -32,7 +32,7 @@ type PromptAdjustment struct { LastUsed time.Time `json:"last_used"` } -// NewAdaptivePrompt creates an adaptive prompt backed by Hawk user state. +// NewAdaptivePrompt creates an adaptive prompt backed by Rho user state. func NewAdaptivePrompt() *AdaptivePrompt { ap := &AdaptivePrompt{ path: filepath.Join(storage.StateDir(), "adaptive_prompt.json"), diff --git a/internal/engine/adaptive_prompt_test.go b/internal/engine/adaptive_prompt_test.go index bd0e8da3a..48fa7302f 100644 --- a/internal/engine/adaptive_prompt_test.go +++ b/internal/engine/adaptive_prompt_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/testutil" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestAdaptivePrompt_New(t *testing.T) { diff --git a/internal/engine/adaptive_system_prompt.go b/internal/engine/adaptive_system_prompt.go index bc21fc519..294fd26d4 100644 --- a/internal/engine/adaptive_system_prompt.go +++ b/internal/engine/adaptive_system_prompt.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) // PromptBuildContext provides situational context for building a system prompt. @@ -266,12 +266,12 @@ func DiffPrompts(old, new string) string { return strings.TrimRight(result, "\n") } -// DefaultSections returns the built-in prompt sections that hawk uses. +// DefaultSections returns the built-in prompt sections that rho uses. func DefaultSections(ctx PromptBuildContext) []PromptSection { sections := []PromptSection{ { Name: "identity", - Content: "You are hawk, an AI coding agent. You help developers write, debug, review, and refactor code. You operate inside the user's repository with access to tools for file manipulation, shell commands, and code search.", + Content: "You are rho, an AI coding agent. You help developers write, debug, review, and refactor code. You operate inside the user's repository with access to tools for file manipulation, shell commands, and code search.", Priority: 1, }, { diff --git a/internal/engine/adaptive_system_prompt_test.go b/internal/engine/adaptive_system_prompt_test.go index 11a5fd876..27109e5bc 100644 --- a/internal/engine/adaptive_system_prompt_test.go +++ b/internal/engine/adaptive_system_prompt_test.go @@ -5,7 +5,7 @@ import ( "sync" "testing" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) func TestNewSystemPromptBuilder(t *testing.T) { @@ -24,7 +24,7 @@ func TestNewSystemPromptBuilder(t *testing.T) { func TestAddSection(t *testing.T) { b := NewSystemPromptBuilder("", 5000) - b.AddSection(PromptSection{Name: "identity", Content: "You are hawk.", Priority: 1}) + b.AddSection(PromptSection{Name: "identity", Content: "You are rho.", Priority: 1}) b.AddSection(PromptSection{Name: "safety", Content: "Be safe.", Priority: 1}) if len(b.Sections) != 2 { @@ -32,11 +32,11 @@ func TestAddSection(t *testing.T) { } // Adding same name replaces - b.AddSection(PromptSection{Name: "identity", Content: "You are hawk v2.", Priority: 1}) + b.AddSection(PromptSection{Name: "identity", Content: "You are rho v2.", Priority: 1}) if len(b.Sections) != 2 { t.Fatalf("expected 2 sections after replace, got %d", len(b.Sections)) } - if b.Sections[0].Content != "You are hawk v2." { + if b.Sections[0].Content != "You are rho v2." { t.Errorf("expected updated content, got %q", b.Sections[0].Content) } } @@ -302,7 +302,7 @@ func TestEstimateStringTokens(t *testing.T) { func TestFormatPrompt(t *testing.T) { sections := []PromptSection{ - {Name: "identity", Content: "You are hawk."}, + {Name: "identity", Content: "You are rho."}, {Name: "safety", Content: "Be safe."}, } @@ -317,7 +317,7 @@ func TestFormatPrompt(t *testing.T) { if !strings.Contains(result, "## safety") { t.Error("expected safety section header") } - if !strings.Contains(result, "You are hawk.") { + if !strings.Contains(result, "You are rho.") { t.Error("expected identity content") } if !strings.Contains(result, "Be safe.") { diff --git a/internal/engine/agent/background_agent.go b/internal/engine/agent/background_agent.go index a34dc537b..e9203d120 100644 --- a/internal/engine/agent/background_agent.go +++ b/internal/engine/agent/background_agent.go @@ -6,9 +6,9 @@ import ( "sync" "time" - agentcontracts "github.com/GrayCodeAI/hawk/internal/contracts/agent" + agentcontracts "github.com/GrayCodeAI/rho/internal/contracts/agent" - "github.com/GrayCodeAI/hawk/internal/taskruntime" + "github.com/GrayCodeAI/rho/internal/taskruntime" ) // BackgroundAgentPool manages async sub-agents that run in the background. diff --git a/internal/engine/agent_reexports.go b/internal/engine/agent_reexports.go index a0903fa48..adee48e39 100644 --- a/internal/engine/agent_reexports.go +++ b/internal/engine/agent_reexports.go @@ -4,7 +4,7 @@ package engine import ( - "github.com/GrayCodeAI/hawk/internal/engine/agent" + "github.com/GrayCodeAI/rho/internal/engine/agent" ) type ( diff --git a/internal/engine/agent_resume_test.go b/internal/engine/agent_resume_test.go index 549ad438d..78ade5b31 100644 --- a/internal/engine/agent_resume_test.go +++ b/internal/engine/agent_resume_test.go @@ -3,10 +3,10 @@ package engine import ( "testing" - agentcontracts "github.com/GrayCodeAI/hawk/internal/contracts/agent" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/tool" + agentcontracts "github.com/GrayCodeAI/rho/internal/contracts/agent" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/tool" ) func TestSubAgentResume_ReplaysTranscriptMessages(t *testing.T) { diff --git a/internal/engine/agent_session_tool.go b/internal/engine/agent_session_tool.go index b43be4dae..c1b0f13aa 100644 --- a/internal/engine/agent_session_tool.go +++ b/internal/engine/agent_session_tool.go @@ -7,15 +7,15 @@ import ( "strings" "time" - agentcontracts "github.com/GrayCodeAI/hawk/internal/contracts/agent" + agentcontracts "github.com/GrayCodeAI/rho/internal/contracts/agent" - engagent "github.com/GrayCodeAI/hawk/internal/engine/agent" - "github.com/GrayCodeAI/hawk/internal/eventlog" - "github.com/GrayCodeAI/hawk/internal/gitworktree" - "github.com/GrayCodeAI/hawk/internal/hooks" - "github.com/GrayCodeAI/hawk/internal/prompts" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/tool" + engagent "github.com/GrayCodeAI/rho/internal/engine/agent" + "github.com/GrayCodeAI/rho/internal/eventlog" + "github.com/GrayCodeAI/rho/internal/gitworktree" + "github.com/GrayCodeAI/rho/internal/hooks" + "github.com/GrayCodeAI/rho/internal/prompts" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/tool" ) // WireAgentTool sets up typed sub-agent spawning via SpawnController. @@ -54,7 +54,7 @@ func (s *Session) spawnSubAgentRequest(ctx context.Context, req agentcontracts.S Agent: string(mode), Depth: depth, Mode: "one-shot", - Provider: "hawk", + Provider: "rho", Label: truncateSummary(norm.Prompt, 200), AgentProvider: s.ChatLLM().Provider(), AgentModel: s.ChatLLM().Model(), diff --git a/internal/engine/arc_test.go b/internal/engine/arc_test.go index 90a74418a..178027138 100644 --- a/internal/engine/arc_test.go +++ b/internal/engine/arc_test.go @@ -3,7 +3,7 @@ package engine import ( "testing" - "github.com/GrayCodeAI/hawk/internal/conversationarc" + "github.com/GrayCodeAI/rho/internal/conversationarc" ) func TestSessionArcAccessors(t *testing.T) { diff --git a/internal/engine/architect.go b/internal/engine/architect.go index 347d3c864..cfa6fc42f 100644 --- a/internal/engine/architect.go +++ b/internal/engine/architect.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/planning" - "github.com/GrayCodeAI/hawk/internal/provider/routing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/planning" + "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/types" ) // ArchitectConfig configures the two-model architect/editor pipeline. diff --git a/internal/engine/assumptions.go b/internal/engine/assumptions.go index 1c1f7bdab..ea8f62415 100644 --- a/internal/engine/assumptions.go +++ b/internal/engine/assumptions.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // AssumptionStatus tracks whether an assumption has been verified. diff --git a/internal/engine/auto_commit.go b/internal/engine/auto_commit.go index 1f813ccb6..48edefa69 100644 --- a/internal/engine/auto_commit.go +++ b/internal/engine/auto_commit.go @@ -81,7 +81,7 @@ func (ac *AutoCommitter) generateMessage(description string) string { if len(description) > 72 { description = description[:69] + "..." } - return "hawk: " + description + return "rho: " + description } - return fmt.Sprintf("hawk: auto-commit %s", time.Now().Format("15:04:05")) + return fmt.Sprintf("rho: auto-commit %s", time.Now().Format("15:04:05")) } diff --git a/internal/engine/auto_commit_wire_test.go b/internal/engine/auto_commit_wire_test.go index 0a4765ba0..bf5e8ab4d 100644 --- a/internal/engine/auto_commit_wire_test.go +++ b/internal/engine/auto_commit_wire_test.go @@ -7,8 +7,8 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/types" ) // autoCommitCaptureTool records ToolContext.AutoCommit for the wire path. diff --git a/internal/engine/background_runner.go b/internal/engine/background_runner.go index 23587b770..ad15b23d6 100644 --- a/internal/engine/background_runner.go +++ b/internal/engine/background_runner.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - agentcontracts "github.com/GrayCodeAI/hawk/internal/contracts/agent" + agentcontracts "github.com/GrayCodeAI/rho/internal/contracts/agent" - "github.com/GrayCodeAI/hawk/internal/taskruntime" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/taskruntime" + "github.com/GrayCodeAI/rho/internal/tool" ) // BackgroundTask represents an async subagent task running in the background. diff --git a/internal/engine/branching/cascade.go b/internal/engine/branching/cascade.go index d6e5c0fd7..f5d34c4d7 100644 --- a/internal/engine/branching/cascade.go +++ b/internal/engine/branching/cascade.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/engine/cost" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/engine/cost" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) // CascadeRouter selects the optimal model for each request based on task complexity. diff --git a/internal/engine/branching/cascade_test.go b/internal/engine/branching/cascade_test.go index 5602674e9..de940ab00 100644 --- a/internal/engine/branching/cascade_test.go +++ b/internal/engine/branching/cascade_test.go @@ -3,7 +3,7 @@ package branching import ( "testing" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) const testProvider = "anthropic" diff --git a/internal/engine/branching/shadow.go b/internal/engine/branching/shadow.go index fad7c38fa..af710200d 100644 --- a/internal/engine/branching/shadow.go +++ b/internal/engine/branching/shadow.go @@ -28,7 +28,7 @@ type ShadowWorkspace struct { // NewShadowWorkspace creates a new temporary directory for shadow validation. func NewShadowWorkspace() (*ShadowWorkspace, error) { - dir, err := os.MkdirTemp("", "hawk-shadow-*") + dir, err := os.MkdirTemp("", "rho-shadow-*") if err != nil { return nil, fmt.Errorf("shadow workspace: create temp dir: %w", err) } diff --git a/internal/engine/cache_gate_test.go b/internal/engine/cache_gate_test.go index 96ff6ea95..5ad208791 100644 --- a/internal/engine/cache_gate_test.go +++ b/internal/engine/cache_gate_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestCacheDecisionNonAnthropicOff(t *testing.T) { diff --git a/internal/engine/cache_planner.go b/internal/engine/cache_planner.go index f9640ace4..ff8c0487b 100644 --- a/internal/engine/cache_planner.go +++ b/internal/engine/cache_planner.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // Prompt-cache segment planning, extending the caveman-style break-even gate diff --git a/internal/engine/cache_planner_test.go b/internal/engine/cache_planner_test.go index 65316cedf..555bfc59b 100644 --- a/internal/engine/cache_planner_test.go +++ b/internal/engine/cache_planner_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func bigSys(n int) string { return strings.Repeat("s", n) } diff --git a/internal/engine/chat_provider.go b/internal/engine/chat_provider.go index 8e5af4b79..993892ea3 100644 --- a/internal/engine/chat_provider.go +++ b/internal/engine/chat_provider.go @@ -5,11 +5,11 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/types" ) -// BuildChatProvider adapts Hawk's engine-backed session client to the smaller +// BuildChatProvider adapts Rho's engine-backed session client to the smaller // provider contract used by host integrations such as Kestrel. Model resolution, // credentials, routing, and transport remain owned by Eyrie's engine facade. func BuildChatProvider(ctx context.Context, selection gateway.Selection, legacyProvider string) (types.ChatProvider, string, error) { @@ -28,7 +28,7 @@ func BuildChatProvider(ctx context.Context, selection gateway.Selection, legacyP }, provider, nil } -// engineChatProvider keeps compatibility-only provider behavior at Hawk's +// engineChatProvider keeps compatibility-only provider behavior at Rho's // integration edge while all generation goes through the engine ChatClient. type engineChatProvider struct { client ChatClient diff --git a/internal/engine/chat_provider_test.go b/internal/engine/chat_provider_test.go index 2fcfc67cd..77a6c0bfc 100644 --- a/internal/engine/chat_provider_test.go +++ b/internal/engine/chat_provider_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/GrayCodeAI/eyrie/llm" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) type recordingChatClient struct { diff --git a/internal/engine/chat_replay.go b/internal/engine/chat_replay.go index c0331a6cb..3f44a9ac5 100644 --- a/internal/engine/chat_replay.go +++ b/internal/engine/chat_replay.go @@ -4,19 +4,19 @@ import ( "context" "os" - "github.com/GrayCodeAI/hawk/internal/replaycache" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/replaycache" + "github.com/GrayCodeAI/rho/internal/types" ) // replayCacheDirEnv opts a run into the disk-persisted replay cache: when set, // every non-streaming completion is looked up by its canonicalized request and // replayed from disk on a hit, giving deterministic, offline regression runs. // Unset (the default) leaves the chat path untouched. -const replayCacheDirEnv = "HAWK_REPLAY_CACHE_DIR" +const replayCacheDirEnv = "RHO_REPLAY_CACHE_DIR" // replayFingerprintEnv optionally folds an extra string (e.g. a fixture // version) into replay cache keys so whole suites can be invalidated at once. -const replayFingerprintEnv = "HAWK_REPLAY_FINGERPRINT" +const replayFingerprintEnv = "RHO_REPLAY_FINGERPRINT" // replayKey builds the cache key for one completion request. func replayKey(opts types.ChatOptions, messages []types.EyrieMessage) string { @@ -25,7 +25,7 @@ func replayKey(opts types.ChatOptions, messages []types.EyrieMessage) string { } // chatWithReplay wraps client.Chat with the replay cache when -// HAWK_REPLAY_CACHE_DIR is set; otherwise it calls straight through. +// RHO_REPLAY_CACHE_DIR is set; otherwise it calls straight through. func chatWithReplay(ctx context.Context, client ChatClient, messages []types.EyrieMessage, opts types.ChatOptions) (*types.EyrieResponse, error) { dir := os.Getenv(replayCacheDirEnv) if dir == "" { diff --git a/internal/engine/chat_replay_test.go b/internal/engine/chat_replay_test.go index 308e6d366..694364538 100644 --- a/internal/engine/chat_replay_test.go +++ b/internal/engine/chat_replay_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestChatWithReplayDisabledPassesThrough(t *testing.T) { diff --git a/internal/engine/chat_service.go b/internal/engine/chat_service.go index 25392ceeb..1bf51acec 100644 --- a/internal/engine/chat_service.go +++ b/internal/engine/chat_service.go @@ -8,10 +8,10 @@ import ( "time" "github.com/GrayCodeAI/eyrie/engine" - "github.com/GrayCodeAI/hawk/internal/observability/metrics" - "github.com/GrayCodeAI/hawk/internal/resilience/ratelimit" - "github.com/GrayCodeAI/hawk/internal/resilience/retry" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/observability/metrics" + "github.com/GrayCodeAI/rho/internal/resilience/ratelimit" + "github.com/GrayCodeAI/rho/internal/resilience/retry" + "github.com/GrayCodeAI/rho/internal/types" ) // ChatService is the Session's view of the LLM transport. It owns the @@ -207,7 +207,7 @@ func (c *ChatService) BuildOptions(systemPrompt, activeModel string, maxTokens i if outputSchema != "" { opts.ResponseFormat = &types.ResponseFormat{Type: "json_schema", Schema: outputSchema} } - // Opt-in tool-catalog compression (HAWK_TOOL_SHRINK=1): fail-open, so the + // Opt-in tool-catalog compression (RHO_TOOL_SHRINK=1): fail-open, so the // returned tools equal the input whenever anything is off or drifts. opts.Tools = shrinkEyrieTools(opts.Tools) return opts @@ -224,7 +224,7 @@ func (c *ChatService) BuildOptions(systemPrompt, activeModel string, maxTokens i // // Eyrie facade clients advertise that they manage provider resilience. For // those clients this service records the product metric and delegates exactly -// once; injected legacy clients retain Hawk's compatibility retry/rate layer. +// once; injected legacy clients retain Rho's compatibility retry/rate layer. func (c *ChatService) Stream(ctx context.Context, messages []types.EyrieMessage, opts types.ChatOptions) (*types.StreamResult, error) { c.mu.RLock() client := c.client diff --git a/internal/engine/chat_service_test.go b/internal/engine/chat_service_test.go index e37de4fad..1fadd95d9 100644 --- a/internal/engine/chat_service_test.go +++ b/internal/engine/chat_service_test.go @@ -8,8 +8,8 @@ import ( "time" "github.com/GrayCodeAI/eyrie/llm" - "github.com/GrayCodeAI/hawk/internal/resilience/retry" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/resilience/retry" + "github.com/GrayCodeAI/rho/internal/types" ) // TestChatService_BuildOptions checks that BuildOptions correctly @@ -19,8 +19,8 @@ func TestChatService_BuildOptions(t *testing.T) { Provider: "anthropic", Model: "claude-opus-4", }) - opts := svc.BuildOptions(strings.Repeat("you are hawk. ", cacheMinPrefixBytes/7+64), "claude-opus-4", 4096, nil) - opts.System = "you are hawk" // restore exact assertion target + opts := svc.BuildOptions(strings.Repeat("you are rho. ", cacheMinPrefixBytes/7+64), "claude-opus-4", 4096, nil) + opts.System = "you are rho" // restore exact assertion target if opts.Provider != "anthropic" { t.Errorf("expected provider=anthropic, got %q", opts.Provider) } @@ -35,7 +35,7 @@ func TestChatService_BuildOptions(t *testing.T) { if !opts.EnableCaching { t.Error("expected EnableCaching=true for anthropic with a prefix at/above break-even") } - if opts.System != "you are hawk" { + if opts.System != "you are rho" { t.Errorf("expected system prompt to be set, got %q", opts.System) } } diff --git a/internal/engine/client_interface.go b/internal/engine/client_interface.go index 981ff8d7a..700e6106d 100644 --- a/internal/engine/client_interface.go +++ b/internal/engine/client_interface.go @@ -4,7 +4,7 @@ import ( "context" "github.com/GrayCodeAI/eyrie/llm" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // ChatClient abstracts the LLM client methods used by Session. diff --git a/internal/engine/coding_soul.go b/internal/engine/coding_soul.go index e11bc42f2..f85d58f0b 100644 --- a/internal/engine/coding_soul.go +++ b/internal/engine/coding_soul.go @@ -5,11 +5,11 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) // CodingSoul defines the persistent coding personality and style preferences. -// Loaded from Hawk user state. +// Loaded from Rho user state. type CodingSoul struct { Style string // communication style Preferences string // coding preferences @@ -61,7 +61,7 @@ func (s *CodingSoul) ForPrompt() string { // InitSoulPrompt returns a prompt to generate an initial soul.md. func InitSoulPrompt() string { - return `Generate a coding soul profile for Hawk user state based on my coding patterns. Analyze my recent code and infer: + return `Generate a coding soul profile for Rho user state based on my coding patterns. Analyze my recent code and infer: ## Style - How I communicate (terse vs verbose, formal vs casual) diff --git a/internal/engine/compact.go b/internal/engine/compact.go index 99dc6fc4f..aecacd855 100644 --- a/internal/engine/compact.go +++ b/internal/engine/compact.go @@ -7,11 +7,11 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/engine/compact" - "github.com/GrayCodeAI/hawk/internal/engine/token" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/rho/internal/types" - modelPkg "github.com/GrayCodeAI/hawk/internal/provider/routing" + modelPkg "github.com/GrayCodeAI/rho/internal/provider/routing" ) // ShouldAutoCompact returns true if the conversation is approaching context limits. @@ -104,7 +104,7 @@ func (s *Session) smartCompactBody(ctx context.Context) { // segment before they leave the live context. Best-effort: a persistence // failure must never block or corrupt compaction itself. if sessionID := s.executionGraphSessionID(); sessionID != "" && len(compactedMsgs) > 0 { - detail, _ := compact.ParseCompactionDetail(os.Getenv("HAWK_COMPACTION_SEGMENT_DETAIL")) + detail, _ := compact.ParseCompactionDetail(os.Getenv("RHO_COMPACTION_SEGMENT_DETAIL")) if _, err := compact.WriteCompactionSegment(sessionID, compactedMsgs, detail); err != nil { slog.Debug("compaction segment persistence skipped", "error", err) } diff --git a/internal/engine/compact/aliases_extra_test.go b/internal/engine/compact/aliases_extra_test.go index 49c8724b9..7fc6e04fc 100644 --- a/internal/engine/compact/aliases_extra_test.go +++ b/internal/engine/compact/aliases_extra_test.go @@ -3,7 +3,7 @@ package compact import ( "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestDefaultConfig(t *testing.T) { diff --git a/internal/engine/compact/api.go b/internal/engine/compact/api.go index 1605a39c1..8137f7f78 100644 --- a/internal/engine/compact/api.go +++ b/internal/engine/compact/api.go @@ -1,9 +1,9 @@ package compact import ( - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/engine/token" + "github.com/GrayCodeAI/rho/internal/engine/token" ) type APICompactConfig struct { diff --git a/internal/engine/compact/files.go b/internal/engine/compact/files.go index a5b5641c6..f494ea6fc 100644 --- a/internal/engine/compact/files.go +++ b/internal/engine/compact/files.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) type FileTracker struct { diff --git a/internal/engine/compact/files_test.go b/internal/engine/compact/files_test.go index 421560a26..a0a3fc59f 100644 --- a/internal/engine/compact/files_test.go +++ b/internal/engine/compact/files_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestFileTracker_NewFileTracker(t *testing.T) { diff --git a/internal/engine/compact/incremental_test.go b/internal/engine/compact/incremental_test.go index ffb89fe2a..d34bfbeb1 100644 --- a/internal/engine/compact/incremental_test.go +++ b/internal/engine/compact/incremental_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestBuildIncrementalCompactPromptIncludesPriorSummary(t *testing.T) { diff --git a/internal/engine/compact/micro.go b/internal/engine/compact/micro.go index 4b6cec735..3769fa894 100644 --- a/internal/engine/compact/micro.go +++ b/internal/engine/compact/micro.go @@ -3,7 +3,7 @@ package compact import ( "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) type MicroCompactConfig struct { diff --git a/internal/engine/compact/prompt.go b/internal/engine/compact/prompt.go index 6a5eefcff..498860251 100644 --- a/internal/engine/compact/prompt.go +++ b/internal/engine/compact/prompt.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) const noToolsPreamble = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools. @@ -121,7 +121,7 @@ func BuildIncrementalCompactPrompt(priorSummary string) string { fmt.Sprintf(incrementalUpdateTemplate, priorSummary) } -// PriorSummaryPrefix is the marker prefix hawk prepends to a persisted +// PriorSummaryPrefix is the marker prefix rho prepends to a persisted // conversation summary message. const PriorSummaryPrefix = "[Conversation summary]" diff --git a/internal/engine/compact/session_memory.go b/internal/engine/compact/session_memory.go index d3adafb49..2c80b3e0d 100644 --- a/internal/engine/compact/session_memory.go +++ b/internal/engine/compact/session_memory.go @@ -5,10 +5,10 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/engine/token" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/rho/internal/storage" ) type SessionMemoryConfig struct { diff --git a/internal/engine/compact/split_test.go b/internal/engine/compact/split_test.go index a95dfcc30..a1268b7e0 100644 --- a/internal/engine/compact/split_test.go +++ b/internal/engine/compact/split_test.go @@ -3,7 +3,7 @@ package compact import ( "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // --------------------------------------------------------------------------- diff --git a/internal/engine/compact/strategy.go b/internal/engine/compact/strategy.go index 0e95a8adb..2c1466a43 100644 --- a/internal/engine/compact/strategy.go +++ b/internal/engine/compact/strategy.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) type CompactResult struct { diff --git a/internal/engine/compact/strategy_test.go b/internal/engine/compact/strategy_test.go index 77d998ecf..5d5274112 100644 --- a/internal/engine/compact/strategy_test.go +++ b/internal/engine/compact/strategy_test.go @@ -4,9 +4,9 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/engine/token" + "github.com/GrayCodeAI/rho/internal/engine/token" ) func TestCompactEstimateTokens(t *testing.T) { diff --git a/internal/engine/compact/transcript_segments.go b/internal/engine/compact/transcript_segments.go index db6ea471d..bdadf4a6f 100644 --- a/internal/engine/compact/transcript_segments.go +++ b/internal/engine/compact/transcript_segments.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/types" ) // Compaction transcript segments, adopted from grok-build's diff --git a/internal/engine/compact/transcript_segments_test.go b/internal/engine/compact/transcript_segments_test.go index c558a9df8..ca2a0aae3 100644 --- a/internal/engine/compact/transcript_segments_test.go +++ b/internal/engine/compact/transcript_segments_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func segTestMessages() []types.EyrieMessage { @@ -74,7 +74,7 @@ func TestParseCompactionDetail(t *testing.T) { func TestWriteCompactionSegmentAndIndex(t *testing.T) { stateDir := t.TempDir() - t.Setenv("HAWK_STATE_DIR", stateDir) + t.Setenv("RHO_STATE_DIR", stateDir) sessionID := "seg-test-session" path, err := WriteCompactionSegment(sessionID, segTestMessages(), SegmentVerbose) if err != nil { diff --git a/internal/engine/compact_auto.go b/internal/engine/compact_auto.go index aea5e67fe..7ffabac2d 100644 --- a/internal/engine/compact_auto.go +++ b/internal/engine/compact_auto.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/circuitbreaker" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/circuitbreaker" + "github.com/GrayCodeAI/rho/internal/types" ) // AutoCompactor orchestrates compaction with circuit breaker protection. diff --git a/internal/engine/compact_clear.go b/internal/engine/compact_clear.go index 7d4fee3f5..837a2cf78 100644 --- a/internal/engine/compact_clear.go +++ b/internal/engine/compact_clear.go @@ -4,7 +4,7 @@ import ( "context" "sort" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // Two-tier context management (adopted from herm): before compacting, "clear" diff --git a/internal/engine/compact_clear_test.go b/internal/engine/compact_clear_test.go index 86b91cc38..0ac810405 100644 --- a/internal/engine/compact_clear_test.go +++ b/internal/engine/compact_clear_test.go @@ -3,7 +3,7 @@ package engine import ( "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func mkMsg(role string, trs []types.ToolResult) types.EyrieMessage { diff --git a/internal/engine/compact_micro_engine.go b/internal/engine/compact_micro_engine.go index d57190567..f9307b5a2 100644 --- a/internal/engine/compact_micro_engine.go +++ b/internal/engine/compact_micro_engine.go @@ -4,9 +4,9 @@ import ( "context" "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/engine/compact" + "github.com/GrayCodeAI/rho/internal/engine/compact" ) type MicroCompactStrategy struct{} diff --git a/internal/engine/compact_provider_native.go b/internal/engine/compact_provider_native.go index bcdad611d..3f0a172eb 100644 --- a/internal/engine/compact_provider_native.go +++ b/internal/engine/compact_provider_native.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/GrayCodeAI/eyrie/llm" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // ProviderNativeCompactStrategy delegates provider-specific compaction to -// Eyrie. Hawk owns when conversation state is compacted and how the resulting +// Eyrie. Rho owns when conversation state is compacted and how the resulting // summary is inserted; Eyrie owns credentials and provider transport details. type ProviderNativeCompactStrategy struct{} diff --git a/internal/engine/compact_reexports.go b/internal/engine/compact_reexports.go index e964ba1bf..c851c857e 100644 --- a/internal/engine/compact_reexports.go +++ b/internal/engine/compact_reexports.go @@ -4,9 +4,9 @@ package engine import ( - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/engine/compact" + "github.com/GrayCodeAI/rho/internal/engine/compact" ) type CompactVariant = compact.CompactVariant @@ -46,7 +46,7 @@ func ExtractPriorSummary(msgs []types.EyrieMessage) string { return compact.ExtractPriorSummary(msgs) } -// PriorSummaryPrefix is the marker prefix hawk prepends to a persisted +// PriorSummaryPrefix is the marker prefix rho prepends to a persisted // conversation summary message. const PriorSummaryPrefix = compact.PriorSummaryPrefix diff --git a/internal/engine/compact_regression_test.go b/internal/engine/compact_regression_test.go index 3c274372b..62b31376e 100644 --- a/internal/engine/compact_regression_test.go +++ b/internal/engine/compact_regression_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // Regression tests for compaction fixes. Each test pins a specific bug so it diff --git a/internal/engine/compact_relevance.go b/internal/engine/compact_relevance.go index 6e7ff84bd..e4bccc4cd 100644 --- a/internal/engine/compact_relevance.go +++ b/internal/engine/compact_relevance.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/GrayCodeAI/hawk/internal/relevanceprune" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/relevanceprune" + "github.com/GrayCodeAI/rho/internal/types" ) // RelevancePruneStrategy prunes context by relevance: it scores older messages diff --git a/internal/engine/compact_relevance_test.go b/internal/engine/compact_relevance_test.go index 24295ba22..c17fcf01a 100644 --- a/internal/engine/compact_relevance_test.go +++ b/internal/engine/compact_relevance_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestRelevancePruneStrategy_ShouldTrigger(t *testing.T) { diff --git a/internal/engine/compact_session_memory_engine.go b/internal/engine/compact_session_memory_engine.go index 2c48a58f8..a2cddefe5 100644 --- a/internal/engine/compact_session_memory_engine.go +++ b/internal/engine/compact_session_memory_engine.go @@ -6,9 +6,9 @@ import ( "os" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/engine/compact" + "github.com/GrayCodeAI/rho/internal/engine/compact" ) type SessionMemoryStrategy struct{} diff --git a/internal/engine/compact_split.go b/internal/engine/compact_split.go index 2c24bc232..9f4abfc47 100644 --- a/internal/engine/compact_split.go +++ b/internal/engine/compact_split.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // splitTurnCompact handles the edge case where a single turn's messages diff --git a/internal/engine/compact_strategy_engine.go b/internal/engine/compact_strategy_engine.go index ace86f8d5..5bc9d93df 100644 --- a/internal/engine/compact_strategy_engine.go +++ b/internal/engine/compact_strategy_engine.go @@ -3,7 +3,7 @@ package engine import ( "context" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) type CompactStrategy interface { diff --git a/internal/engine/compact_strategy_test.go b/internal/engine/compact_strategy_test.go index 7b5c6f50f..f39052ff6 100644 --- a/internal/engine/compact_strategy_test.go +++ b/internal/engine/compact_strategy_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestSessionMemoryStrategy_ShouldTrigger(t *testing.T) { diff --git a/internal/engine/compression/cross_session.go b/internal/engine/compression/cross_session.go index 0180fc633..2c47fdd79 100644 --- a/internal/engine/compression/cross_session.go +++ b/internal/engine/compression/cross_session.go @@ -10,8 +10,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/mathutil" - "github.com/GrayCodeAI/hawk/internal/safewrite" + "github.com/GrayCodeAI/rho/internal/mathutil" + "github.com/GrayCodeAI/rho/internal/safewrite" ) // Insight represents a learned insight from a previous session. diff --git a/internal/engine/compression/session_timeline.go b/internal/engine/compression/session_timeline.go index bedea5767..2a50dd485 100644 --- a/internal/engine/compression/session_timeline.go +++ b/internal/engine/compression/session_timeline.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // TimelineEvent represents a single event in a session timeline. diff --git a/internal/engine/compression/session_timeline_test.go b/internal/engine/compression/session_timeline_test.go index 5500cede5..1a5a09447 100644 --- a/internal/engine/compression/session_timeline_test.go +++ b/internal/engine/compression/session_timeline_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestNewTimeline(t *testing.T) { diff --git a/internal/engine/context_compaction.go b/internal/engine/context_compaction.go index 6b3a8cbea..4e5482e64 100644 --- a/internal/engine/context_compaction.go +++ b/internal/engine/context_compaction.go @@ -3,10 +3,10 @@ package engine import ( "path/filepath" - "github.com/GrayCodeAI/hawk/internal/eventlog" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/storage" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/eventlog" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/rho/internal/types" ) // CompactionEvent describes a completed context compaction. @@ -20,7 +20,7 @@ type CompactionEvent struct { // OnCompaction is invoked after compaction (TUI, logging, etc.). type OnCompaction func(CompactionEvent) -// PersistID is the on-disk session id under Hawk's user state sessions dir. +// PersistID is the on-disk session id under Rho's user state sessions dir. // Set from the TUI when a chat session is created or resumed. func (s *Session) SetPersistID(id string) { if s == nil { diff --git a/internal/engine/context_compaction_test.go b/internal/engine/context_compaction_test.go index bdb89ea50..78d7d06f8 100644 --- a/internal/engine/context_compaction_test.go +++ b/internal/engine/context_compaction_test.go @@ -6,7 +6,7 @@ import ( "github.com/GrayCodeAI/eyrie/credentials" eyrieengine "github.com/GrayCodeAI/eyrie/engine" - "github.com/GrayCodeAI/hawk/internal/provider/gateway" + "github.com/GrayCodeAI/rho/internal/provider/gateway" ) func TestContextUsedTokens_PrefersAPI(t *testing.T) { diff --git a/internal/engine/context_governor.go b/internal/engine/context_governor.go index 017a9546d..9ba2e339e 100644 --- a/internal/engine/context_governor.go +++ b/internal/engine/context_governor.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/GrayCodeAI/hawk/internal/engine/ctxmgr" - modelPkg "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/engine/ctxmgr" + modelPkg "github.com/GrayCodeAI/rho/internal/provider/routing" ) const ( diff --git a/internal/engine/context_governor_test.go b/internal/engine/context_governor_test.go index 596f4df44..4c1d1bfd5 100644 --- a/internal/engine/context_governor_test.go +++ b/internal/engine/context_governor_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestResolveModelContextWindow_Fallback(t *testing.T) { @@ -38,7 +38,7 @@ func TestMaybeSpillToolOutput_SmallUnchanged(t *testing.T) { } func TestMaybeSpillToolOutput_LargeSpills(t *testing.T) { - t.Setenv("HAWK_CACHE_DIR", t.TempDir()) + t.Setenv("RHO_CACHE_DIR", t.TempDir()) in := strings.Repeat("x", toolOutputSpillMinChars+100) got := maybeSpillToolOutput(in, "Bash", "call-1") if !strings.Contains(got, "/scratch/") { diff --git a/internal/engine/control/backtrack.go b/internal/engine/control/backtrack.go index 4fa78b0dd..b92ab9df6 100644 --- a/internal/engine/control/backtrack.go +++ b/internal/engine/control/backtrack.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // DecisionPoint captures a point in the conversation where the agent made a diff --git a/internal/engine/control/backtrack_test.go b/internal/engine/control/backtrack_test.go index f262363c1..aa9d748a0 100644 --- a/internal/engine/control/backtrack_test.go +++ b/internal/engine/control/backtrack_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestNewBacktrackEngine(t *testing.T) { diff --git a/internal/engine/control/stall_detector.go b/internal/engine/control/stall_detector.go index 11ee29962..8115fdf45 100644 --- a/internal/engine/control/stall_detector.go +++ b/internal/engine/control/stall_detector.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // StallEntry represents a single recorded tool invocation in the stall detection window. diff --git a/internal/engine/control/stall_detector_test.go b/internal/engine/control/stall_detector_test.go index b05c62855..714918435 100644 --- a/internal/engine/control/stall_detector_test.go +++ b/internal/engine/control/stall_detector_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestNewStallDetector(t *testing.T) { diff --git a/internal/engine/control_plane_test.go b/internal/engine/control_plane_test.go index 5d1a46a5a..5ee76c06e 100644 --- a/internal/engine/control_plane_test.go +++ b/internal/engine/control_plane_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/types" ) func TestWorkModePlanFiltersToolsAndBash(t *testing.T) { diff --git a/internal/engine/control_reexports.go b/internal/engine/control_reexports.go index 9fdd82497..5fb300099 100644 --- a/internal/engine/control_reexports.go +++ b/internal/engine/control_reexports.go @@ -1,6 +1,6 @@ package engine -import "github.com/GrayCodeAI/hawk/internal/engine/control" +import "github.com/GrayCodeAI/rho/internal/engine/control" type ( LoopDetector = control.LoopDetector diff --git a/internal/engine/conversation_adapter.go b/internal/engine/conversation_adapter.go index 2046361aa..7200adc3b 100644 --- a/internal/engine/conversation_adapter.go +++ b/internal/engine/conversation_adapter.go @@ -29,7 +29,7 @@ type ConversationState struct { } // ConversationManager manages a conversation lifecycle, bridging eyrie's -// conversation management into hawk's chat session flow. It is safe for +// conversation management into rho's chat session flow. It is safe for // concurrent use. type ConversationManager struct { mu sync.RWMutex diff --git a/internal/engine/cost/aliases.go b/internal/engine/cost/aliases.go index ca0f62bb3..678857bab 100644 --- a/internal/engine/cost/aliases.go +++ b/internal/engine/cost/aliases.go @@ -1,3 +1,3 @@ // Package cost provides cost tracking, optimisation, and display -// for the hawk engine. See ../../docs/plans/engine-refactor-plan.md. +// for the rho engine. See ../../docs/plans/engine-refactor-plan.md. package cost diff --git a/internal/engine/cost/cost_optimizer.go b/internal/engine/cost/cost_optimizer.go index 439bbcbcd..d38653b3a 100644 --- a/internal/engine/cost/cost_optimizer.go +++ b/internal/engine/cost/cost_optimizer.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) type CostOptimizer struct { diff --git a/internal/engine/cost/cost_optimizer_test.go b/internal/engine/cost/cost_optimizer_test.go index 260107c15..a920c7883 100644 --- a/internal/engine/cost/cost_optimizer_test.go +++ b/internal/engine/cost/cost_optimizer_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) const testProvider = "anthropic" diff --git a/internal/engine/cost/cost_table.go b/internal/engine/cost/cost_table.go index 9ca6791cc..cd9cf9988 100644 --- a/internal/engine/cost/cost_table.go +++ b/internal/engine/cost/cost_table.go @@ -4,7 +4,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) // Tier-based default pricing when catalog data is unavailable. diff --git a/internal/engine/cost/cost_tracker.go b/internal/engine/cost/cost_tracker.go index 36fd45484..46ee3ba7f 100644 --- a/internal/engine/cost/cost_tracker.go +++ b/internal/engine/cost/cost_tracker.go @@ -8,8 +8,8 @@ import ( "sync" "time" - analytics "github.com/GrayCodeAI/hawk/internal/observability" - "github.com/GrayCodeAI/hawk/internal/storage" + analytics "github.com/GrayCodeAI/rho/internal/observability" + "github.com/GrayCodeAI/rho/internal/storage" ) type CostTracker struct { diff --git a/internal/engine/cost/cost_tracker_test.go b/internal/engine/cost/cost_tracker_test.go index aca7ffdee..6e9b595be 100644 --- a/internal/engine/cost/cost_tracker_test.go +++ b/internal/engine/cost/cost_tracker_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - analytics "github.com/GrayCodeAI/hawk/internal/observability" - "github.com/GrayCodeAI/hawk/internal/testutil" + analytics "github.com/GrayCodeAI/rho/internal/observability" + "github.com/GrayCodeAI/rho/internal/testutil" ) func TestCostTracker_NewAndRecord(t *testing.T) { diff --git a/internal/engine/cost_reexports.go b/internal/engine/cost_reexports.go index e0a1bab5d..c42adfd91 100644 --- a/internal/engine/cost_reexports.go +++ b/internal/engine/cost_reexports.go @@ -1,8 +1,8 @@ package engine import ( - "github.com/GrayCodeAI/hawk/internal/engine/cost" - analytics "github.com/GrayCodeAI/hawk/internal/observability" + "github.com/GrayCodeAI/rho/internal/engine/cost" + analytics "github.com/GrayCodeAI/rho/internal/observability" ) type ( diff --git a/internal/engine/council.go b/internal/engine/council.go index 9fc3052ae..27f2ae91f 100644 --- a/internal/engine/council.go +++ b/internal/engine/council.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - routing "github.com/GrayCodeAI/hawk/internal/provider/routing" + routing "github.com/GrayCodeAI/rho/internal/provider/routing" ) // CouncilConfig controls the Karpathy LLM Council pattern. diff --git a/internal/engine/ctxmgr/context_budget.go b/internal/engine/ctxmgr/context_budget.go index b2d104553..aa8610e4c 100644 --- a/internal/engine/ctxmgr/context_budget.go +++ b/internal/engine/ctxmgr/context_budget.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/mathutil" + "github.com/GrayCodeAI/rho/internal/mathutil" ) // ContextBudget allocates the model's context window across different content categories. diff --git a/internal/engine/ctxmgr/context_collapse.go b/internal/engine/ctxmgr/context_collapse.go index b7c139024..2191e1098 100644 --- a/internal/engine/ctxmgr/context_collapse.go +++ b/internal/engine/ctxmgr/context_collapse.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // CollapseRepeatedMessages finds and collapses similar consecutive messages to diff --git a/internal/engine/ctxmgr/context_collapse_test.go b/internal/engine/ctxmgr/context_collapse_test.go index 812b21c93..0caa47c16 100644 --- a/internal/engine/ctxmgr/context_collapse_test.go +++ b/internal/engine/ctxmgr/context_collapse_test.go @@ -3,7 +3,7 @@ package ctxmgr import ( "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestCollapseRepeatedMessages_NoCollapse(t *testing.T) { diff --git a/internal/engine/ctxmgr/context_decay.go b/internal/engine/ctxmgr/context_decay.go index 34eb60a53..685cdfe3e 100644 --- a/internal/engine/ctxmgr/context_decay.go +++ b/internal/engine/ctxmgr/context_decay.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // ContextDecay manages context entries with time-based importance decay. diff --git a/internal/engine/ctxmgr/context_decay_test.go b/internal/engine/ctxmgr/context_decay_test.go index 099554c69..f81712966 100644 --- a/internal/engine/ctxmgr/context_decay_test.go +++ b/internal/engine/ctxmgr/context_decay_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestNewContextDecay(t *testing.T) { diff --git a/internal/engine/ctxmgr/context_viz.go b/internal/engine/ctxmgr/context_viz.go index 319463c53..e0708eb4b 100644 --- a/internal/engine/ctxmgr/context_viz.go +++ b/internal/engine/ctxmgr/context_viz.go @@ -5,7 +5,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // ContextVisualizer provides a real-time view of context window usage, diff --git a/internal/engine/ctxmgr/context_viz_test.go b/internal/engine/ctxmgr/context_viz_test.go index df4d139a2..4fa3d9770 100644 --- a/internal/engine/ctxmgr/context_viz_test.go +++ b/internal/engine/ctxmgr/context_viz_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestNewContextVisualizer(t *testing.T) { diff --git a/internal/engine/ctxmgr/incremental.go b/internal/engine/ctxmgr/incremental.go index e6a0834cf..b8caef574 100644 --- a/internal/engine/ctxmgr/incremental.go +++ b/internal/engine/ctxmgr/incremental.go @@ -4,11 +4,11 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/hawk/internal/systemcontext" + "github.com/GrayCodeAI/rho/internal/systemcontext" ) // Section defines a dynamic, incrementally-rendered system-prompt section. It -// is the hawk-host integration of the systemcontext package: each section is a +// is the rho-host integration of the systemcontext package: each section is a // typed context source whose value is loaded on demand, and only sections that // actually change are re-rendered as a mid-conversation update rather than // rebuilding the entire system prompt. @@ -146,7 +146,7 @@ func renderSection(header, value string) string { func keyScope(key string) string { // A section key like "memories" or "scope/name" maps to a valid // namespaced source key. - return "hawk" + return "rho" } func keyName(key string) string { @@ -154,7 +154,7 @@ func keyName(key string) string { } // DefaultIncrementalSections returns a reasonable set of dynamic sections for -// a session, wired to the same loaders hawk already uses for each. It is a +// a session, wired to the same loaders rho already uses for each. It is a // convenience for hosts that want to enable incremental context without // hand-assembling sections. Loaders may be nil-able wrappers; the returned // sections must be configured with their Load funcs by the caller. diff --git a/internal/engine/diff/diff_staging.go b/internal/engine/diff/diff_staging.go index a477322f9..e36301f32 100644 --- a/internal/engine/diff/diff_staging.go +++ b/internal/engine/diff/diff_staging.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // StagingArea provides a local staging area for agent edits, allowing review diff --git a/internal/engine/diff_reexports.go b/internal/engine/diff_reexports.go index d1bd3706b..acc6934e5 100644 --- a/internal/engine/diff_reexports.go +++ b/internal/engine/diff_reexports.go @@ -1,6 +1,6 @@ package engine -import "github.com/GrayCodeAI/hawk/internal/engine/diff" +import "github.com/GrayCodeAI/rho/internal/engine/diff" // Types from diff sub-package. diff --git a/internal/engine/directive_scanner.go b/internal/engine/directive_scanner.go index 703e51046..7fe0eed33 100644 --- a/internal/engine/directive_scanner.go +++ b/internal/engine/directive_scanner.go @@ -9,7 +9,7 @@ import ( "strings" ) -// Directive is a parsed hawk: comment from source code. +// Directive is a parsed rho: comment from source code. type Directive struct { File string Line int @@ -17,9 +17,9 @@ type Directive struct { Context string } -var hawkDirectivePattern = regexp.MustCompile(`(?i)(?://|#|--|/\*)\s*hawk:\s*(.+?)(?:\s*\*/)?$`) +var rhoDirectivePattern = regexp.MustCompile(`(?i)(?://|#|--|/\*)\s*rho:\s*(.+?)(?:\s*\*/)?$`) -// ScanDirectives finds all `// hawk: ` comments in source files. +// ScanDirectives finds all `// rho: ` comments in source files. func ScanDirectives(dir string) []Directive { var directives []Directive _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { @@ -28,7 +28,7 @@ func ScanDirectives(dir string) []Directive { } if d.IsDir() { name := d.Name() - if name == ".git" || name == "node_modules" || name == "vendor" || name == ".hawk" { + if name == ".git" || name == "node_modules" || name == "vendor" || name == ".rho" { return filepath.SkipDir } return nil @@ -45,7 +45,7 @@ func ScanDirectives(dir string) []Directive { } lines := strings.Split(string(data), "\n") for i, line := range lines { - matches := hawkDirectivePattern.FindStringSubmatch(line) + matches := rhoDirectivePattern.FindStringSubmatch(line) if len(matches) > 1 { start := i - 3 if start < 0 { @@ -73,5 +73,5 @@ func DirectivePrompt(d Directive) string { return "File: " + d.File + " (line " + fmt.Sprintf("%d", d.Line) + ")\n" + "Directive: " + d.Command + "\n" + "Context:\n```\n" + d.Context + "\n```\n\n" + - "Implement what the hawk: comment asks for. Remove the hawk: comment after implementing." + "Implement what the rho: comment asks for. Remove the rho: comment after implementing." } diff --git a/internal/engine/elision.go b/internal/engine/elision.go index 11470af47..d2701c7b1 100644 --- a/internal/engine/elision.go +++ b/internal/engine/elision.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/hawk/internal/engine/token" + "github.com/GrayCodeAI/rho/internal/engine/token" ) // elisionNotice computes a verified-facts suffix for a truncation marker from diff --git a/internal/engine/engine.go b/internal/engine/engine.go index bd699affb..6267dd8ef 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -4,7 +4,7 @@ import ( "context" "os" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) const ( diff --git a/internal/engine/engine_full_loop_test.go b/internal/engine/engine_full_loop_test.go index e83d0b252..9910b987c 100644 --- a/internal/engine/engine_full_loop_test.go +++ b/internal/engine/engine_full_loop_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func TestEngine_FullLoop_TextOnly(t *testing.T) { diff --git a/internal/engine/engine_integration_test.go b/internal/engine/engine_integration_test.go index 419204fae..3d9decce9 100644 --- a/internal/engine/engine_integration_test.go +++ b/internal/engine/engine_integration_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" - contracts "github.com/GrayCodeAI/hawk/internal/contracts/policy" - "github.com/GrayCodeAI/hawk/internal/types" + contracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + "github.com/GrayCodeAI/rho/internal/types" - "github.com/GrayCodeAI/hawk/internal/session" - "github.com/GrayCodeAI/hawk/internal/tool" + "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/rho/internal/tool" ) // ────────────────────────────────────────────────────────────────────────────── diff --git a/internal/engine/engine_stage2_test_helpers.go b/internal/engine/engine_stage2_test_helpers.go index 00ed92802..68c49d029 100644 --- a/internal/engine/engine_stage2_test_helpers.go +++ b/internal/engine/engine_stage2_test_helpers.go @@ -3,7 +3,7 @@ package engine import ( "testing" - "github.com/GrayCodeAI/hawk/internal/provider/routing" + "github.com/GrayCodeAI/rho/internal/provider/routing" ) const testProvider = "anthropic" diff --git a/internal/engine/engine_token_helpers.go b/internal/engine/engine_token_helpers.go index 5602c027b..5e5ce4944 100644 --- a/internal/engine/engine_token_helpers.go +++ b/internal/engine/engine_token_helpers.go @@ -3,7 +3,7 @@ package engine import ( "strings" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) func isRecentToolHeavy(messages []types.EyrieMessage) bool { diff --git a/internal/engine/errs/aliases.go b/internal/engine/errs/aliases.go index 286f88746..f5e9e5d17 100644 --- a/internal/engine/errs/aliases.go +++ b/internal/engine/errs/aliases.go @@ -1,5 +1,5 @@ // Package errs provides error context enrichment, grouping, learning, -// patterns, and recovery for the hawk engine. +// patterns, and recovery for the rho engine. // // Named "errs" (not "error") to avoid shadowing the builtin error type. package errs diff --git a/internal/engine/errs/error_context.go b/internal/engine/errs/error_context.go index 3e8ab0fbb..f11031596 100644 --- a/internal/engine/errs/error_context.go +++ b/internal/engine/errs/error_context.go @@ -410,7 +410,7 @@ func NewErrorContext() *ErrorContext { }, } - ec.Patterns["hawk_old_str_not_found"] = &ErrorHelp{ + ec.Patterns["rho_old_str_not_found"] = &ErrorHelp{ Pattern: regexp.MustCompile(`old_str not found`), Title: "Edit target string not found", Explanation: "The text specified in old_str does not exist in the file. The file may have been modified since it was last read, or the string may contain whitespace or encoding differences.", @@ -427,7 +427,7 @@ func NewErrorContext() *ErrorContext { AutoFix: "Re-read the target file and retry with the exact current content", } - ec.Patterns["hawk_file_too_large"] = &ErrorHelp{ + ec.Patterns["rho_file_too_large"] = &ErrorHelp{ Pattern: regexp.MustCompile(`file too large`), Title: "File exceeds size limit", Explanation: "The file is too large to be processed in a single operation. This protects against accidentally loading very large files into memory.", @@ -444,7 +444,7 @@ func NewErrorContext() *ErrorContext { AutoFix: "Use head_tail or line-range reads to process the file in parts", } - ec.Patterns["hawk_budget_exceeded"] = &ErrorHelp{ + ec.Patterns["rho_budget_exceeded"] = &ErrorHelp{ Pattern: regexp.MustCompile(`budget exceeded`), Title: "Token or cost budget exceeded", Explanation: "The session has consumed more tokens or cost than the configured budget allows. This is a safety limit to prevent runaway costs.", @@ -455,12 +455,12 @@ func NewErrorContext() *ErrorContext { "Check for loops that may be inflating token usage", }, Examples: []string{ - "hawk --budget 10.00 # set a higher budget", + "rho --budget 10.00 # set a higher budget", "/compact # reduce context size", }, } - ec.Patterns["hawk_tool_not_found"] = &ErrorHelp{ + ec.Patterns["rho_tool_not_found"] = &ErrorHelp{ Pattern: regexp.MustCompile(`(tool not found|unknown tool)`), Title: "Tool not found", Explanation: "The requested tool does not exist in the current tool registry. It may be misspelled or not available in this configuration.", @@ -475,7 +475,7 @@ func NewErrorContext() *ErrorContext { }, } - ec.Patterns["hawk_sandbox_violation"] = &ErrorHelp{ + ec.Patterns["rho_sandbox_violation"] = &ErrorHelp{ Pattern: regexp.MustCompile(`(sandbox violation|operation not permitted by sandbox)`), Title: "Sandbox security violation", Explanation: "The operation was blocked by the sandbox security policy. The command attempted to access a resource outside the allowed scope.", @@ -483,7 +483,7 @@ func NewErrorContext() *ErrorContext { "Check which paths are allowed by the sandbox configuration", "Request permission for the specific operation", "Verify the file is within the project directory", - "Check sandbox settings in Hawk user settings", + "Check sandbox settings in Rho user settings", }, Examples: []string{ "// Ensure operations target files within the project root", diff --git a/internal/engine/errs/error_context_test.go b/internal/engine/errs/error_context_test.go index 1aeafee9a..88d4bace4 100644 --- a/internal/engine/errs/error_context_test.go +++ b/internal/engine/errs/error_context_test.go @@ -277,7 +277,7 @@ func TestEnrich_SysConnectionRefused(t *testing.T) { } } -func TestEnrich_HawkOldStrNotFound(t *testing.T) { +func TestEnrich_RhoOldStrNotFound(t *testing.T) { ec := NewErrorContext() enriched := ec.Enrich("edit failed: old_str not found in file.go") if enriched == nil { @@ -291,7 +291,7 @@ func TestEnrich_HawkOldStrNotFound(t *testing.T) { } } -func TestEnrich_HawkFileTooLarge(t *testing.T) { +func TestEnrich_RhoFileTooLarge(t *testing.T) { ec := NewErrorContext() enriched := ec.Enrich("cannot read: file too large (50MB)") if enriched == nil { @@ -302,7 +302,7 @@ func TestEnrich_HawkFileTooLarge(t *testing.T) { } } -func TestEnrich_HawkBudgetExceeded(t *testing.T) { +func TestEnrich_RhoBudgetExceeded(t *testing.T) { ec := NewErrorContext() enriched := ec.Enrich("session terminated: budget exceeded ($5.00 limit)") if enriched == nil { diff --git a/internal/engine/errs/error_patterns.go b/internal/engine/errs/error_patterns.go index 8b7a9bab0..b6bff566f 100644 --- a/internal/engine/errs/error_patterns.go +++ b/internal/engine/errs/error_patterns.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/storage" + "github.com/GrayCodeAI/rho/internal/storage" ) type ErrorPattern struct { diff --git a/internal/engine/errs_reexports.go b/internal/engine/errs_reexports.go index 6ae27a910..6eb21669d 100644 --- a/internal/engine/errs_reexports.go +++ b/internal/engine/errs_reexports.go @@ -1,6 +1,6 @@ package engine -import "github.com/GrayCodeAI/hawk/internal/engine/errs" +import "github.com/GrayCodeAI/rho/internal/engine/errs" type ( ErrorContext = errs.ErrorContext diff --git a/internal/engine/event_bus.go b/internal/engine/event_bus.go index 260eef33c..38a6631f0 100644 --- a/internal/engine/event_bus.go +++ b/internal/engine/event_bus.go @@ -24,7 +24,7 @@ type Event struct { Payload interface{} } -// EventBus is a lightweight pub/sub system for decoupling hawk components. +// EventBus is a lightweight pub/sub system for decoupling rho components. type EventBus struct { mu sync.RWMutex subs map[EventType][]chan Event diff --git a/internal/engine/execution_graph_observations.go b/internal/engine/execution_graph_observations.go index 31a851f9f..4a5ca368a 100644 --- a/internal/engine/execution_graph_observations.go +++ b/internal/engine/execution_graph_observations.go @@ -9,11 +9,11 @@ import ( eyrieengine "github.com/GrayCodeAI/eyrie/engine" eyriegraph "github.com/GrayCodeAI/eyrie/graph" - graphcontracts "github.com/GrayCodeAI/hawk/internal/contracts/graph" - policycontracts "github.com/GrayCodeAI/hawk/internal/contracts/policy" - "github.com/GrayCodeAI/hawk/internal/engine/token" - "github.com/GrayCodeAI/hawk/internal/graphjournal" - "github.com/GrayCodeAI/hawk/internal/types" + graphcontracts "github.com/GrayCodeAI/rho/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/rho/internal/graphjournal" + "github.com/GrayCodeAI/rho/internal/types" ) func (s *Session) recordPolicyObservation(tc types.ToolCall, stage string, allowed bool, reason string) { @@ -29,10 +29,10 @@ func (s *Session) recordPolicyObservation(tc types.ToolCall, stage string, allow } verdict := policycontracts.Allow(reason) verdict.Rule = strings.TrimSpace(stage) - verdict.Source = "hawk." + strings.TrimSpace(stage) + verdict.Source = "rho." + strings.TrimSpace(stage) if !allowed { verdict = policycontracts.Deny(reason, strings.TrimSpace(stage)) - verdict.Source = "hawk." + strings.TrimSpace(stage) + verdict.Source = "rho." + strings.TrimSpace(stage) } if err := graphjournal.AppendPolicy(sessionID, tc.ID, stage, verdict, time.Now()); err != nil { s.Logger().Warn("graph observation append failed", map[string]interface{}{ @@ -315,7 +315,7 @@ func (s *Session) recordEyrieOperationObservation( } // The following helpers convert Eyrie's vendored graph contract types into -// Hawk's contracts/graph contract types. The definitions are byte-identical, so +// Rho's contracts/graph contract types. The definitions are byte-identical, so // conversion is a field-by-field copy at the sibling boundary. func toContractNodes(nodes []eyriegraph.Node) []graphcontracts.Node { diff --git a/internal/engine/execution_graph_observations_test.go b/internal/engine/execution_graph_observations_test.go index 6920aa64c..53f5694ef 100644 --- a/internal/engine/execution_graph_observations_test.go +++ b/internal/engine/execution_graph_observations_test.go @@ -6,10 +6,10 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/hawk/internal/graphjournal" - "github.com/GrayCodeAI/hawk/internal/token" - "github.com/GrayCodeAI/hawk/internal/tool" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/graphjournal" + "github.com/GrayCodeAI/rho/internal/token" + "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/rho/internal/types" ) type graphVerifyTool struct{} @@ -25,7 +25,7 @@ func (graphVerifyTool) Execute(context.Context, json.RawMessage) (string, error) } func TestToolExecutionAutomaticallyRecordsPolicyAndVerification(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry(graphVerifyTool{})) sess.SetPersistID("graph-runtime-session") @@ -62,7 +62,7 @@ func TestToolExecutionAutomaticallyRecordsPolicyAndVerification(t *testing.T) { } func TestShrikeCompressionObservationIsPrivacySafe(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("token-runtime-session") sess.recordCompressionObservation( @@ -89,7 +89,7 @@ func TestShrikeCompressionObservationIsPrivacySafe(t *testing.T) { } func TestShrikeRedactionObservationIsPrivacySafe(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("token-redaction-session") sess.recordRedactionObservation( @@ -116,7 +116,7 @@ func TestShrikeRedactionObservationIsPrivacySafe(t *testing.T) { } func TestShrikeUsageBudgetObservationTracksAndProjectsAuthoritativeUsage(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("shrike-usage-session") if err := sess.SetMaxBudgetUSD(1); err != nil { @@ -216,7 +216,7 @@ func TestDrainAlertsSurfacesHourlyWarning(t *testing.T) { } func TestEyrieOperationObservationIsPrivacySafe(t *testing.T) { - t.Setenv("HAWK_STATE_DIR", t.TempDir()) + t.Setenv("RHO_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("eyrie-runtime-session") sess.recordEyrieOperationObservation( diff --git a/internal/engine/experiment_loop.go b/internal/engine/experiment_loop.go index 651bb69ea..7d3227774 100644 --- a/internal/engine/experiment_loop.go +++ b/internal/engine/experiment_loop.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // gitRollbackTimeout bounds rollback git operations in the experiment loop. diff --git a/internal/engine/external_content.go b/internal/engine/external_content.go new file mode 100644 index 000000000..54745be53 --- /dev/null +++ b/internal/engine/external_content.go @@ -0,0 +1,30 @@ +package engine + +import "github.com/GrayCodeAI/rho/internal/permissions" + +// externalContentSource maps a tool name to the untrusted content source used +// to wrap its output before it enters the model context. +func externalContentSource(toolName string) (permissions.ContentSource, bool) { + switch toolName { + case "WebFetch", "web_fetch", "webfetch": + return permissions.SourceWebFetch, true + case "WebSearch", "web_search", "websearch": + return permissions.SourceWebSearch, true + case "Browser", "browser": + return permissions.SourceBrowser, true + default: + return "", false + } +} + +// wrapExternalToolResult marks output from network-facing tools as untrusted +// so the model treats it as data rather than instructions. Without this, a +// fetched page containing "ignore previous instructions…" enters the context +// as ordinary text with no boundary marker. +func wrapExternalToolResult(toolName, content string) string { + src, ok := externalContentSource(toolName) + if !ok { + return content + } + return permissions.WrapWebContent(content, src) +} diff --git a/internal/engine/extract_targets_test.go b/internal/engine/extract_targets_test.go index b0df1d255..9210fa3cf 100644 --- a/internal/engine/extract_targets_test.go +++ b/internal/engine/extract_targets_test.go @@ -5,7 +5,7 @@ import ( "encoding/json" "testing" - "github.com/GrayCodeAI/hawk/internal/types" + "github.com/GrayCodeAI/rho/internal/types" ) // fakeToolForSchema is a minimal tool.Tool implementation that returns a diff --git a/internal/engine/git/git_context.go b/internal/engine/git/git_context.go index e2ba16a59..d626ab5d1 100644 --- a/internal/engine/git/git_context.go +++ b/internal/engine/git/git_context.go @@ -3,13 +3,14 @@ package git import ( "context" "fmt" - "os/exec" "regexp" "sort" "strconv" "strings" "sync" "time" + + "github.com/GrayCodeAI/rho/internal/gitcmd" ) // GitContext provides git-aware context enrichment for files and sessions. @@ -58,7 +59,7 @@ func NewGitContext(repoDir string) *GitContext { // runGit executes a git command in the repo directory and returns its output. func (gc *GitContext) runGit(args ...string) (string, error) { - cmd := exec.CommandContext(context.Background(), "git", args...) // #nosec G204 -- fixed git executable + cmd := gitcmd.Command(context.Background(), args...) // #nosec G204 -- fixed git executable cmd.Dir = gc.RepoDir out, err := cmd.Output() if err != nil { diff --git a/internal/engine/git/git_provider.go b/internal/engine/git/git_provider.go index 29ee0b790..9f36dec68 100644 --- a/internal/engine/git/git_provider.go +++ b/internal/engine/git/git_provider.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) // GitProvider integrates with GitHub/GitLab/Bitbucket APIs for issue management, diff --git a/internal/engine/git/git_provider_test.go b/internal/engine/git/git_provider_test.go index 0d132ca77..ee5fcee2e 100644 --- a/internal/engine/git/git_provider_test.go +++ b/internal/engine/git/git_provider_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/hawk/internal/ui/icons" + "github.com/GrayCodeAI/rho/internal/ui/icons" ) func TestParsePRNumbersJSON(t *testing.T) { diff --git a/internal/engine/git/provider_extra_test.go b/internal/engine/git/provider_extra_test.go index e36fb6606..0c2cad011 100644 --- a/internal/engine/git/provider_extra_test.go +++ b/internal/engine/git/provider_extra_test.go @@ -5,13 +5,13 @@ import ( ) func TestParseGHRepoView_Github(t *testing.T) { - jsonStr := `{"owner":{"login":"GrayCodeAI"},"name":"hawk","url":"https://github.com/GrayCodeAI/hawk"}` + jsonStr := `{"owner":{"login":"GrayCodeAI"},"name":"rho","url":"https://github.com/GrayCodeAI/rho"}` owner, repo, provider := parseGHRepoView(jsonStr) if owner != "GrayCodeAI" { t.Errorf("owner = %q, want %q", owner, "GrayCodeAI") } - if repo != "hawk" { - t.Errorf("repo = %q, want %q", repo, "hawk") + if repo != "rho" { + t.Errorf("repo = %q, want %q", repo, "rho") } if provider != "github" { t.Errorf("provider = %q, want %q", provider, "github") @@ -50,7 +50,7 @@ func TestParseGitConfig_Github(t *testing.T) { ignorecase = true precomposeunicode = true [remote "origin"] - url = git@github.com:GrayCodeAI/hawk.git + url = git@github.com:GrayCodeAI/rho.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "main"] remote = origin @@ -59,8 +59,8 @@ func TestParseGitConfig_Github(t *testing.T) { if owner != "GrayCodeAI" { t.Errorf("owner = %q, want %q", owner, "GrayCodeAI") } - if repo != "hawk" { - t.Errorf("repo = %q, want %q", repo, "hawk") + if repo != "rho" { + t.Errorf("repo = %q, want %q", repo, "rho") } if provider != "github" { t.Errorf("provider = %q, want %q", provider, "github") diff --git a/internal/engine/git_safety.go b/internal/engine/git_safety.go index 7df4780e7..5f84d186f 100644 --- a/internal/engine/git_safety.go +++ b/internal/engine/git_safety.go @@ -25,7 +25,7 @@ type GitBranchInfo struct { Detached bool HasRepo bool Dirty bool - Suggested string // hawk/agent- when OnDefault + Suggested string // rho/agent- when OnDefault } // InspectGitBranch reads branch and dirty state for repoDir ("" = cwd). @@ -62,7 +62,7 @@ func InspectGitBranch(repoDir string) GitBranchInfo { } info.OnDefault = !info.Detached && defaultBranchNames[info.Branch] if info.OnDefault { - info.Suggested = fmt.Sprintf("hawk/agent-%s", time.Now().Format("20060102-150405")) + info.Suggested = fmt.Sprintf("rho/agent-%s", time.Now().Format("20060102-150405")) } st := exec.CommandContext(ctx, "git", "status", "--porcelain") @@ -73,7 +73,7 @@ func InspectGitBranch(repoDir string) GitBranchInfo { return info } -// EnsureAgentBranch creates and checks out a hawk/agent-* branch when currently +// EnsureAgentBranch creates and checks out a rho/agent-* branch when currently // on a default branch. No-op if already on a feature branch or not a git repo. // Returns the branch name after the operation. func EnsureAgentBranch(repoDir string) (string, error) { @@ -89,11 +89,11 @@ func EnsureAgentBranch(repoDir string) (string, error) { } name := info.Suggested if name == "" { - name = fmt.Sprintf("hawk/agent-%d", time.Now().Unix()) + name = fmt.Sprintf("rho/agent-%d", time.Now().Unix()) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - // #nosec G204 -- fixed git subcommand; branch name is generated internally (hawk/agent-*) + // #nosec G204 -- fixed git subcommand; branch name is generated internally (rho/agent-*) cmd := exec.CommandContext(ctx, "git", "checkout", "-b", name) cmd.Dir = info.RepoDir if out, err := cmd.CombinedOutput(); err != nil { diff --git a/internal/engine/git_safety_test.go b/internal/engine/git_safety_test.go index 8913cc694..74eccd56b 100644 --- a/internal/engine/git_safety_test.go +++ b/internal/engine/git_safety_test.go @@ -53,7 +53,7 @@ func TestEnsureAgentBranch_FromMain(t *testing.T) { if err != nil { t.Fatal(err) } - if !strings.HasPrefix(name, "hawk/agent-") { + if !strings.HasPrefix(name, "rho/agent-") { t.Fatalf("branch = %q", name) } info2 := InspectGitBranch(dir) diff --git a/internal/engine/goose_extras_test.go b/internal/engine/goose_extras_test.go index 68fe65577..d6af9da45 100644 --- a/internal/engine/goose_extras_test.go +++ b/internal/engine/goose_extras_test.go @@ -9,7 +9,7 @@ import ( func TestHintsLoader_LoadHints(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, ".hawkhints"), []byte("Use Go idioms\nPrefer table-driven tests"), 0o644) + os.WriteFile(filepath.Join(dir, ".rhohints"), []byte("Use Go idioms\nPrefer table-driven tests"), 0o644) h := NewHintsLoader() hints := h.LoadHints(dir) @@ -29,7 +29,7 @@ func TestHintsLoader_LoadHints(t *testing.T) { func TestHintsLoader_Reset(t *testing.T) { dir := t.TempDir() - os.WriteFile(filepath.Join(dir, ".hawkhints"), []byte("hint"), 0o644) + os.WriteFile(filepath.Join(dir, ".rhohints"), []byte("hint"), 0o644) h := NewHintsLoader() h.LoadHints(dir) diff --git a/internal/engine/hints_loader.go b/internal/engine/hints_loader.go index 5974f58bb..53023991d 100644 --- a/internal/engine/hints_loader.go +++ b/internal/engine/hints_loader.go @@ -6,8 +6,8 @@ import ( "strings" ) -// HintsFilenames are the files hawk auto-loads for project context. -var HintsFilenames = []string{".hawkhints", "AGENTS.md"} +// HintsFilenames are the files rho auto-loads for project context. +var HintsFilenames = []string{".rhohints", "AGENTS.md"} // HintsLoader discovers and loads project-specific hint files from the // working directory and subdirectories the agent explores. diff --git a/internal/engine/history/annotations.go b/internal/engine/history/annotations.go index d8d397253..44827dcf6 100644 --- a/internal/engine/history/annotations.go +++ b/internal/engine/history/annotations.go @@ -154,7 +154,7 @@ func (am *AnnotationManager) InjectAnnotations(file, content string) string { if a.Resolved { continue } - commentLine := fmt.Sprintf("%s [hawk:%s] %s", commentPrefix, a.Type, a.Content) + commentLine := fmt.Sprintf("%s [rho:%s] %s", commentPrefix, a.Type, a.Content) idx := a.Line - 1 // convert 1-based to 0-based if idx < 0 { idx = 0 @@ -188,29 +188,29 @@ func annotationCommentPrefix(file string) string { } } -// hawkAnnotationRe matches hawk annotation comment lines. -var hawkAnnotationRe = regexp.MustCompile(`^\s*(//|#|/\*|