diff --git a/.env.example b/.env.example index ddfbef9c2..f6299fb56 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,10 @@ -# rho daemon environment variables — copy to .env and fill in. -# Only RHO_DAEMON_API_KEY is consumed from the environment. -RHO_DAEMON_API_KEY= +# decoder daemon environment variables — copy to .env and fill in. +# Only DECODER_DAEMON_API_KEY is consumed from the environment. +DECODER_DAEMON_API_KEY= -# 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`) -# FLUX_API_KEY / FLUX_BASE_URL, HARRIER_API_KEY / HARRIER_ADDR — +# The following are NOT read by decoder — setting them has no effect: +# DECODER_DAEMON_PORT / DECODER_DAEMON_HOST — daemon binds 127.0.0.1:4590; +# override with the --host / --port flags (see `decoder daemon start --help`) +# MUX_API_KEY / MUX_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 ddef946bb..4b863a150 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ -# CODEOWNERS for rho +# CODEOWNERS for decoder # # 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 bf5955dd4..19eef71bf 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/rho/issues) to avoid duplicates. + - Search [existing issues](https://github.com/GrayCodeAI/decoder/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 `rho ...`, I expected X but got Y. + placeholder: When I run `decoder ...`, 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 `rho ...` + 1. Run `decoder ...` 2. Type `...` 3. See error `...` validations: @@ -44,10 +44,10 @@ body: required: true - type: input - id: rho-version + id: decoder-version attributes: - label: rho version - description: Output of `rho version` (or `rho --version`). + label: decoder version + description: Output of `decoder version` (or `decoder --version`). placeholder: "0.1.0" validations: required: true @@ -66,7 +66,7 @@ body: attributes: label: Go version (if building from source) description: Output of `go version`. Skip if you installed a pre-built binary. - placeholder: "go version go1.26.1 darwin/arm64" + placeholder: "go version go1.27.2 darwin/arm64" - type: textarea id: logs diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a6fdeb91e..132cd351d 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/rho/security/advisories/new + url: https://github.com/GrayCodeAI/decoder/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/rho/discussions + url: https://github.com/GrayCodeAI/decoder/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 992f0f913..b0bad6f34 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 rho. +description: Suggest an improvement or new capability for decoder. title: "feat: " labels: ["enhancement", "triage"] @@ -7,13 +7,13 @@ body: - type: markdown attributes: value: | - Thanks for proposing a feature. rho is built **for developers first** + Thanks for proposing a feature. decoder 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/rho/issues) to avoid duplicates. + - Search [existing issues](https://github.com/GrayCodeAI/decoder/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, rho makes me do Y, which is painful because Z. + placeholder: When I'm doing X, decoder 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 rho to behave? CLI flags, output, config, etc. + description: How would you like decoder to behave? CLI flags, output, config, etc. validations: required: true @@ -55,7 +55,7 @@ body: id: principles attributes: label: Developer fit - description: rho avoids enterprise scope for now. Confirm this feature respects that. + description: decoder 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-flux/action.yml b/.github/actions/checkout-mux/action.yml similarity index 96% rename from .github/actions/checkout-flux/action.yml rename to .github/actions/checkout-mux/action.yml index 2b1fb2c56..d47124a95 100644 --- a/.github/actions/checkout-flux/action.yml +++ b/.github/actions/checkout-mux/action.yml @@ -1,5 +1,5 @@ name: Checkout ecosystem -description: Clone graycode-eco sibling repos into the workspace parent for rho's go.work +description: Clone graycode-eco sibling repos into the workspace parent for decoder'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" = rho ] && continue + [ "$repo" = decoder ] && continue dest="${ws_parent}/${repo}" if [ -d "$dest/.git" ]; then echo "$repo already present at $dest" diff --git a/.github/actions/rho/action.yml b/.github/actions/decoder/action.yml similarity index 58% rename from .github/actions/rho/action.yml rename to .github/actions/decoder/action.yml index c43ebff01..ac70519a7 100644 --- a/.github/actions/rho/action.yml +++ b/.github/actions/decoder/action.yml @@ -1,7 +1,7 @@ -name: Rho +name: Decoder description: >- - Run the Rho coding agent headlessly in a GitHub workflow. Rho is + Run the Decoder coding agent headlessly in a GitHub workflow. Decoder 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 Rho to execute. Mutually exclusive with prompt-file. + description: The instruction for Decoder 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 Rho step and never written to the log. + it is exported only for the Decoder 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 Rho step. Leave the action + api-key, that variable is exported only for the Decoder 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 `rho exec --model`. + description: Model id to use for the run. Passed to `decoder exec --model`. required: false default: "" auto: @@ -54,19 +54,19 @@ inputs: default: supervised agent: description: >- - Agent persona to use (from Rho user state). Maps to `rho exec --agent`. + Agent persona to use (from Decoder user state). Maps to `decoder exec --agent`. required: false default: "" cwd: description: >- - Working directory override. Maps to `rho exec --cwd`. + Working directory override. Maps to `decoder 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 `rho exec --ephemeral`. + Maps to `decoder exec --ephemeral`. required: false default: "false" worktree: @@ -80,7 +80,7 @@ inputs: default: text post-to: description: >- - Optional destination for a run summary after Rho finishes: `pr-comment` + Optional destination for a run summary after Decoder finishes: `pr-comment` posts to the triggering pull request, `slack` posts to slack-webhook-url, or `none` (default) posts nowhere. required: false @@ -93,35 +93,35 @@ inputs: default: "" github-token: description: >- - Token used to look up the latest Rho release and, when post-to is + Token used to look up the latest Decoder release and, when post-to is pr-comment, to post the PR comment. Defaults to ${{ github.token }}. required: false default: ${{ github.token }} working-directory: - description: Directory to run Rho in. Defaults to ${{ github.workspace }}. + description: Directory to run Decoder in. Defaults to ${{ github.workspace }}. required: false default: ${{ github.workspace }} - rho-version: + decoder-version: description: >- - Rho release to install (e.g. 0.3.0 or v0.3.0), or latest. Defaults to - the release tag this action is pinned to (uses: ...@v0.3.0); a branch or - commit-SHA pin installs the latest release. Requires Rho v0.3.0 or later: - earlier tags predate the rename and have no rho_* archives. + Decoder release to install (e.g. X.Y.Z or vX.Y.Z), or latest. Defaults to + the release tag this action is pinned to (uses: ...@vX.Y.Z); a branch or + commit-SHA pin installs the latest release. Requires a release containing Decoder archives: + earlier tags predate the rename and have no decoder_* archives. required: false default: "" - rho-repo: + decoder-repo: description: >- - Repository to install Rho from (owner/repo). The release archive's + Repository to install Decoder from (owner/repo). The release archive's cosign signature must come from that repository's release workflow. required: false - default: GrayCodeAI/rho + default: GrayCodeAI/decoder outputs: exit-code: - description: The exit code Rho returned (0 success, 2 usage, 3 provider, non-zero otherwise). + description: The exit code Decoder returned (0 success, 2 usage, 3 provider, non-zero otherwise). value: ${{ steps.run.outputs.exit-code }} output-file: - description: Path to the captured Rho stdout (the raw output-format stream). + description: Path to the captured Decoder 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. @@ -131,16 +131,16 @@ runs: using: composite steps: # cosign verifies the release signature; install.sh runs with - # RHO_REQUIRE_COSIGN=1 so an unsigned or tampered release fails the step. + # DECODER_REQUIRE_COSIGN=1 so an unsigned or tampered release fails the step. - name: Install cosign uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - - name: Install Rho + - name: Install Decoder id: install shell: bash env: - RHO_REPO: ${{ inputs.rho-repo }} - RHO_VERSION_INPUT: ${{ inputs.rho-version }} + DECODER_REPO: ${{ inputs.decoder-repo }} + DECODER_VERSION_INPUT: ${{ inputs.decoder-version }} ACTION_REF: ${{ github.action_ref }} ACTION_PATH: ${{ github.action_path }} GH_TOKEN: ${{ inputs.github-token }} @@ -148,26 +148,26 @@ runs: run: | set -euo pipefail if [ "${RUNNER_OS}" = "Windows" ]; then - echo "::error::The Rho action currently supports Linux and macOS runners only." >&2 + echo "::error::The Decoder action currently supports Linux and macOS runners only." >&2 exit 1 fi - if ! [[ "${RHO_REPO}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then - echo "::error::rho-repo must look like owner/repo." >&2 + if ! [[ "${DECODER_REPO}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then + echo "::error::decoder-repo must look like owner/repo." >&2 exit 2 fi release_re='^v?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$' # Resolve the version: explicit input wins; otherwise the release tag # this action is pinned to; otherwise (branch or SHA pin) the latest - # release of rho-repo. - version="${RHO_VERSION_INPUT}" + # release of decoder-repo. + version="${DECODER_VERSION_INPUT}" if [ -z "${version}" ] && [[ "${ACTION_REF}" =~ ${release_re} ]]; then version="${ACTION_REF}" fi if [ -z "${version}" ] || [ "${version}" = "latest" ]; then - version="$(gh release view --repo "${RHO_REPO}" --json tagName --jq .tagName)" + version="$(gh release view --repo "${DECODER_REPO}" --json tagName --jq .tagName)" fi if ! [[ "${version}" =~ ${release_re} ]]; then - echo "::error::rho-version must be a release version such as 0.3.0, or 'latest'." >&2 + echo "::error::decoder-version must be a release version such as 1.2.3, or 'latest'." >&2 exit 2 fi version="${version#v}" @@ -175,31 +175,31 @@ runs: # release archive and checksums.txt, verifies the cosign signature # (required here) and the SHA-256, and installs under the prefix. installer="$(cd "${ACTION_PATH}/../../.." && pwd)/install.sh" - prefix="${RUNNER_TEMP}/rho" - RHO_INSTALL_REPO="${RHO_REPO}" RHO_REQUIRE_COSIGN=1 \ + prefix="${RUNNER_TEMP}/decoder" + DECODER_INSTALL_REPO="${DECODER_REPO}" DECODER_REQUIRE_COSIGN=1 \ sh "${installer}" --version "${version}" --prefix "${prefix}" echo "${prefix}/bin" >> "${GITHUB_PATH}" - echo "Installed Rho ${version} from ${RHO_REPO}" + echo "Installed Decoder ${version} from ${DECODER_REPO}" - - name: Run Rho + - name: Run Decoder 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. - 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 }} + DECODER_INPUT_API_KEY: ${{ inputs.api-key }} + DECODER_INPUT_API_KEY_ENV: ${{ inputs.api-key-env }} + DECODER_INPUT_PROVIDER: ${{ inputs.provider }} + DECODER_INPUT_PROMPT: ${{ inputs.prompt }} + DECODER_INPUT_PROMPT_FILE: ${{ inputs.prompt-file }} + DECODER_INPUT_MODEL: ${{ inputs.model }} + DECODER_INPUT_AUTO: ${{ inputs.auto }} + DECODER_INPUT_AGENT: ${{ inputs.agent }} + DECODER_INPUT_CWD: ${{ inputs.cwd }} + DECODER_INPUT_EPHEMERAL: ${{ inputs.ephemeral }} + DECODER_INPUT_WORKTREE: ${{ inputs.worktree }} + DECODER_INPUT_OUTPUT_FORMAT: ${{ inputs.output-format }} ACTION_PATH: ${{ github.action_path }} RUNNER_TEMP: ${{ runner.temp }} run: | @@ -208,50 +208,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 "${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 + if [ -n "${DECODER_INPUT_API_KEY}" ] && [ -n "${DECODER_INPUT_API_KEY_ENV}" ]; then + if ! printf '%s' "${DECODER_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 "${RHO_INPUT_API_KEY_ENV}=${RHO_INPUT_API_KEY}" + export "${DECODER_INPUT_API_KEY_ENV}=${DECODER_INPUT_API_KEY}" fi - if [ -n "${RHO_INPUT_PROVIDER}" ]; then - export RHO_PROVIDER="${RHO_INPUT_PROVIDER}" + if [ -n "${DECODER_INPUT_PROVIDER}" ]; then + export DECODER_PROVIDER="${DECODER_INPUT_PROVIDER}" fi args=(exec) - if [ -n "${RHO_INPUT_PROMPT_FILE}" ] && [ -n "${RHO_INPUT_PROMPT}" ]; then + if [ -n "${DECODER_INPUT_PROMPT_FILE}" ] && [ -n "${DECODER_INPUT_PROMPT}" ]; then echo "::error::The 'prompt' and 'prompt-file' inputs are mutually exclusive." >&2 exit 2 fi - if [ -n "${RHO_INPUT_PROMPT_FILE}" ]; then - args+=(--file "${RHO_INPUT_PROMPT_FILE}") - elif [ -n "${RHO_INPUT_PROMPT}" ]; then - args+=("${RHO_INPUT_PROMPT}") + if [ -n "${DECODER_INPUT_PROMPT_FILE}" ]; then + args+=(--file "${DECODER_INPUT_PROMPT_FILE}") + elif [ -n "${DECODER_INPUT_PROMPT}" ]; then + args+=("${DECODER_INPUT_PROMPT}") else echo "::error::Provide either the 'prompt' or 'prompt-file' input." >&2 exit 2 fi - [ -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 + [ -n "${DECODER_INPUT_MODEL}" ] && args+=(--model "${DECODER_INPUT_MODEL}") + [ -n "${DECODER_INPUT_AUTO}" ] && args+=(--auto "${DECODER_INPUT_AUTO}") + [ -n "${DECODER_INPUT_AGENT}" ] && args+=(--agent "${DECODER_INPUT_AGENT}") + [ -n "${DECODER_INPUT_CWD}" ] && args+=(--cwd "${DECODER_INPUT_CWD}") + if [ "${DECODER_INPUT_EPHEMERAL}" = "true" ]; then args+=(--ephemeral) fi - if [ "${RHO_INPUT_WORKTREE}" = "true" ]; then + if [ "${DECODER_INPUT_WORKTREE}" = "true" ]; then args+=(--worktree) fi - [ -n "${RHO_INPUT_OUTPUT_FORMAT}" ] && args+=(--output-format "${RHO_INPUT_OUTPUT_FORMAT}") + [ -n "${DECODER_INPUT_OUTPUT_FORMAT}" ] && args+=(--output-format "${DECODER_INPUT_OUTPUT_FORMAT}") - output_file="${RUNNER_TEMP}/rho-output.txt" + output_file="${RUNNER_TEMP}/decoder-output.txt" - # Run Rho. Capture stdout to a file AND echo it to the log. Surface the + # Run Decoder. Capture stdout to a file AND echo it to the log. Surface the # real exit code as the step's status. set +e - rho "${args[@]}" | tee "${output_file}" + decoder "${args[@]}" | tee "${output_file}" code=${PIPESTATUS[0]} set -e @@ -260,14 +260,14 @@ runs: # Best-effort one-line summary if [ "${code}" = "0" ]; then - summary="Rho run succeeded" + summary="Decoder run succeeded" else - summary="Rho run failed (exit ${code})" + summary="Decoder run failed (exit ${code})" fi { - echo "summary<> "${GITHUB_OUTPUT}" exit "${code}" @@ -278,13 +278,13 @@ runs: env: GH_TOKEN: ${{ inputs.github-token }} PR_NUMBER: ${{ github.event.pull_request.number }} - RHO_EXIT: ${{ steps.run.outputs.exit-code }} - RHO_SUMMARY: ${{ steps.run.outputs.summary }} + DECODER_EXIT: ${{ steps.run.outputs.exit-code }} + DECODER_SUMMARY: ${{ steps.run.outputs.summary }} run: | set -euo pipefail status="succeeded" - [ "${RHO_EXIT}" != "0" ] && status="failed (exit ${RHO_EXIT})" - body="$(printf 'Rho run %s.\n\n%s' "${status}" "${RHO_SUMMARY}")" + [ "${DECODER_EXIT}" != "0" ] && status="failed (exit ${DECODER_EXIT})" + body="$(printf 'Decoder run %s.\n\n%s' "${status}" "${DECODER_SUMMARY}")" gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ -f body="${body}" >/dev/null @@ -293,14 +293,14 @@ runs: shell: bash env: SLACK_WEBHOOK_URL: ${{ inputs.slack-webhook-url }} - RHO_EXIT: ${{ steps.run.outputs.exit-code }} - RHO_SUMMARY: ${{ steps.run.outputs.summary }} + DECODER_EXIT: ${{ steps.run.outputs.exit-code }} + DECODER_SUMMARY: ${{ steps.run.outputs.summary }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | set -euo pipefail status="succeeded" - [ "${RHO_EXIT}" != "0" ] && status="failed (exit ${RHO_EXIT})" - text="Rho run ${status} for ${GITHUB_REPOSITORY}@${GITHUB_REF_NAME}. ${RHO_SUMMARY} ${RUN_URL}" + [ "${DECODER_EXIT}" != "0" ] && status="failed (exit ${DECODER_EXIT})" + text="Decoder run ${status} for ${GITHUB_REPOSITORY}@${GITHUB_REF_NAME}. ${DECODER_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 53a187601..64abd1a9c 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -4,7 +4,7 @@ description: Clone ecosystem sibling repos with retry, setup Go, sync workspace inputs: go-version: description: Go version to install - default: '1.26.1' + default: '1.27.2' token: description: GitHub token for authenticated clones required: true @@ -28,7 +28,7 @@ runs: ws_parent="$(cd "${GITHUB_WORKSPACE}/.." && pwd)" mkdir -p "$ws_parent" while IFS= read -r repo; do - [ "$repo" = rho ] && continue + [ "$repo" = decoder ] && 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 9c2ca8196..1cf03c15e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,8 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: "1.26.6" - # Module resolution: flux is the only GrayCodeAI module dependency and it is + GO_VERSION: "1.27.2" + # Module resolution: mux is the only GrayCodeAI module dependency and it is # published on the public proxy. Direct-VCS fallback is retained for anything # else. GOPROXY: "https://proxy.golang.org,direct" @@ -29,7 +29,7 @@ env: # Go's reference is explicit that a committed go.work "may cause a continuous # integration (CI) system to select and thus test the wrong versions of a # module's dependencies" and that "CI systems should generally not be allowed to - # use the go.work file". Because ./.github/actions/checkout-flux clones the + # use the go.work file". Because ./.github/actions/checkout-mux clones the # siblings at branch HEAD into the workspace parent, workspace mode would silently # hide version skew (e.g. a shared pin that no longer matches). # The `module` job opts back in (GOWORK: "auto") since `go work sync` and the @@ -37,6 +37,13 @@ env: GOWORK: "off" jobs: + version: + name: Canonical version metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - run: python3 scripts/sync-version.py --check + # ------------------------------------------------------------------------- # 1. Format — gofumpt + goimports must be clean (zero tolerance). # ------------------------------------------------------------------------- @@ -62,7 +69,7 @@ jobs: fi - name: goimports run: | - go install golang.org/x/tools/cmd/goimports@v0.30.0 + go install golang.org/x/tools/cmd/goimports@v0.52.0 out=$(git ls-files -- '*.go' | xargs goimports -l) if [ -n "$out" ]; then echo "::error::goimports would reformat:" @@ -73,13 +80,13 @@ jobs: run: bash ./scripts/check-ecosystem-boundaries.sh - name: internal layer boundary guard run: bash ./scripts/check-internal-layer-imports.sh - - name: flux client boundary guard - run: bash ./scripts/check-flux-client-imports.sh - - name: flux engine facade boundary guard - run: bash ./scripts/check-flux-engine-boundary.sh + - name: mux client boundary guard + run: bash ./scripts/check-mux-client-imports.sh + - name: mux engine facade boundary guard + run: bash ./scripts/check-mux-engine-boundary.sh # ------------------------------------------------------------------------- - # 2. Module hygiene — tidy, verify Rho plus the sibling Go modules via go.work. + # 2. Module hygiene — tidy, verify Decoder plus the sibling Go modules via go.work. # ------------------------------------------------------------------------- module: name: module hygiene @@ -94,7 +101,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - name: Generate ecosystem workspace @@ -109,7 +116,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/rho + go build -mod=readonly -o /dev/null ./cmd/decoder 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 +128,7 @@ jobs: run: | workspace_file="${GITHUB_WORKSPACE}/../go.work" while IFS= read -r module; do - [ "$module" = rho ] && continue + [ "$module" = decoder ] && continue if ! grep -q "./${module}" "$workspace_file"; then echo "::error::go.work must include ./${module}." cat "$workspace_file" @@ -145,7 +152,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -158,7 +165,7 @@ jobs: run: | go mod download go mod verify - go build -mod=readonly ./cmd/rho + go build -mod=readonly ./cmd/decoder go test ./... -count=1 -timeout=300s release-parity: @@ -168,7 +175,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -187,7 +194,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -196,8 +203,8 @@ jobs: cache: true - name: go vet run: go vet ./... - - name: rho harness evaluation audit - run: go run ./cmd/rho harness --out-dir .rho/harness + - name: decoder harness evaluation audit + run: go run ./cmd/decoder harness --out-dir .decoder/harness - name: support repo coupling guard run: bash ./scripts/check-support-repo-coupling.sh @@ -212,7 +219,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -221,7 +228,7 @@ jobs: cache: true - name: Run golangci-lint run: | - go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.0 + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 golangci-lint run --timeout=5m # ------------------------------------------------------------------------- @@ -232,7 +239,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 rho PR #155, where + # historically masked real test regressions (see decoder 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. @@ -241,7 +248,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -313,7 +320,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/rho/issues/123" + echo " // TODO: https://github.com/GrayCodeAI/decoder/issues/123" echo " t.Skip(\"not yet implemented\")" fi @@ -328,7 +335,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -337,41 +344,22 @@ jobs: cache: true - name: govulncheck run: | - go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 - # Fail only on vulnerabilities reachable from our code (findings with a - # non-empty call trace). Transitive advisories that our code does not - # call are surfaced but do not block CI — they cannot be remediated - # without an upstream fix and are not in our attack surface. - govulncheck -json ./... > /tmp/vuln.json || true - reachable=$(python3 - <<'PY' - import json, sys - n = 0 - for line in open("/tmp/vuln.json"): - line = line.strip() - if not line: - continue - try: - o = json.loads(line) - except json.JSONDecodeError: - continue - if not isinstance(o, dict): - continue - if o.get("module") == "finding" and o.get("trace"): - n += 1 - print(f"REACHABLE: {o.get('osv', {}).get('id')} in {o.get('package', {}).get('path')}", file=sys.stderr) - print(n) - PY - ) - echo "Reachable vulnerabilities: $reachable" - if [ "$reachable" -gt 0 ]; then - echo "::error::govulncheck found $reachable reachable vulnerability(ies) in our code" - exit 1 - fi - echo "No reachable vulnerabilities in our code." + go install golang.org/x/vuln/cmd/govulncheck@v1.8.0 + # The default symbol scan exits nonzero for reachable vulnerabilities + # and scanner errors. Verbose output also reports uncalled advisories. + govulncheck -show=verbose ./... - name: gosec continue-on-error: true run: | - go install github.com/securego/gosec/v2/cmd/gosec@v2.22.4 + # gosec's release still pins an older Go export-data reader. + # Build it with Go 1.27-compatible x/tools in an isolated module. + tool_dir=$(mktemp -d) + ( + cd "$tool_dir" + go mod init graycodeai.local/gosec-tool + go get github.com/securego/gosec/v2/cmd/gosec@v2.29.0 golang.org/x/tools@v0.52.0 + go install github.com/securego/gosec/v2/cmd/gosec + ) # Advisory scan. The blocking gosec gate lives in the `lint` job # (see .golangci.yml), which applies the repo's G301/G304 # exclusions for intentional 0755 dirs and user-supplied file @@ -436,7 +424,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c @@ -445,7 +433,7 @@ jobs: cache: true - name: deadcode run: | - go install golang.org/x/tools/cmd/deadcode@v0.30.0 + go install golang.org/x/tools/cmd/deadcode@v0.52.0 output=$(mktemp) trap 'rm -f "$output"' EXIT deadcode ./... >"$output" @@ -491,7 +479,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -506,7 +494,7 @@ jobs: run: go build -trimpath -v ./... - name: Binary size budget (linux/amd64 only) if: matrix.goos == 'linux' && matrix.goarch == 'amd64' - # Builds bin/rho with the release ldflags (-s -w) and fails when it + # Builds bin/decoder with the release ldflags (-s -w) and fails when it # exceeds SIZE_LIMIT_MB in the Makefile — the single source of the budget. run: make size-check @@ -521,7 +509,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 @@ -544,7 +532,7 @@ jobs: go test -run='^$' -fuzz=FuzzParseToolCallResult -fuzztime=60s ./internal/mcp # ------------------------------------------------------------------------- - # 10. Smoke — build rho and verify ecosystem CLI wiring. + # 10. Smoke — build decoder and verify ecosystem CLI wiring. # ------------------------------------------------------------------------- smoke: name: smoke @@ -554,12 +542,12 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: true - - name: smoke-rho.sh - run: ./scripts/smoke-rho.sh + - name: smoke-decoder.sh + run: ./scripts/smoke-decoder.sh diff --git a/.github/workflows/compatibility-matrix.yml b/.github/workflows/compatibility-matrix.yml index ac80831db..3cac8b8cd 100644 --- a/.github/workflows/compatibility-matrix.yml +++ b/.github/workflows/compatibility-matrix.yml @@ -23,12 +23,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: ./.github/actions/checkout-flux + - uses: ./.github/actions/checkout-mux with: ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: "1.26.6" + go-version: "1.27.2" cache: true - name: Structural validation (schema + version pins) run: make compat-check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa0b179ee..f2da60aee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -# Release workflow for rho. A maintainer pushes a v tag after the +# Release workflow for decoder. A maintainer pushes a v tag after the # release PR (VERSION + CHANGELOG) has merged; creating v* tags is restricted by # the repository ruleset "protect-release-tags". See docs/RELEASING.md. # Source of truth: .shared-templates/workflows/go-release.yml.tmpl @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest env: GOPROXY: "https://proxy.golang.org,direct" - # Release builds resolve every dependency (including flux) from + # Release builds resolve every dependency (including mux) from # go.mod/go.sum through the public proxy — never from sibling checkouts. GOWORK: "off" steps: @@ -32,6 +32,9 @@ jobs: fetch-depth: 0 # goreleaser needs full history for changelog persist-credentials: false + - name: Verify canonical version metadata + run: python3 scripts/sync-version.py --check + # Refuses a tag that disagrees with VERSION or has no CHANGELOG section, # and extracts that section as the release notes. - name: Check tag against VERSION and CHANGELOG @@ -40,7 +43,7 @@ jobs: - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: "1.26.6" + go-version: "1.27.2" cache: true - name: Refuse local replace directives diff --git a/.gitignore b/.gitignore index 637ffaa90..7903bccc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ # Binaries -/rho +/decoder # Ignore build output in bin/, but track committed source scripts like bin/buf. bin/* !bin/buf # GoReleaser output (goreleaser release --snapshot / make release). /dist/ -rho_bin +decoder_bin *.exe *.dll *.so @@ -14,7 +14,7 @@ rho_bin .claude/ .codegraph/ .commandcode/ -.rho/ +.decoder/ .harrier/ .openclaude/ .pi/ @@ -49,11 +49,11 @@ go.work.sum # Lefthook-generated git hook wrappers .githooks/ __pycache__/ -rho_bin +decoder_bin # Logs *.log -rho-sec105b.log +decoder-sec105b.log # Compiled Go test binaries *.test @@ -63,7 +63,7 @@ rho-sec105b.log # for tools that prefer it; keep the regenerated copy out of git. /CLAUDE.md -# Local module overlay for building against a sibling checkout (e.g. flux). +# Local module overlay for building against a sibling checkout (e.g. mux). # go.local.mod carries a `replace` directive pointing at ../; committing # it would break CI, which builds against the pinned published module. /go.local.mod diff --git a/.golangci.yml b/.golangci.yml index 2f7923bbb..4d11074b1 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 — rho reads user-supplied file paths by design (CLI agent) + # G304 — decoder reads user-supplied file paths by design (CLI agent) includes: - G101 # hardcoded credentials - G102 # bind to all interfaces @@ -94,6 +94,6 @@ linters: # Tests conventionally compare errors for exact identity (err != sentinel) # or type-assert with a concrete type; enforcing errors.Is/As there is noise. - errorlint - issues: +issues: max-issues-per-linter: 0 max-same-issues: 0 diff --git a/.goreleaser.yml b/.goreleaser.yml index 783e89c25..d11598581 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,5 +1,5 @@ -# GoReleaser config for rho (the terminal AI coding agent). -# Derived from .shared-templates/.goreleaser.yml.tmpl; rho-specific deltas are +# GoReleaser config for decoder (the terminal AI coding agent). +# Derived from .shared-templates/.goreleaser.yml.tmpl; decoder-specific deltas are # commented inline. Run by .github/workflows/release.yml on a v* tag; see # docs/RELEASING.md for the release process. # @@ -8,7 +8,7 @@ # goreleaser release --snapshot --clean --skip=publish,sign version: 2 -project_name: rho +project_name: decoder # --------------------------------------------------------------------------- # Pre-build hooks — verify module checksums before building anything. @@ -23,9 +23,9 @@ before: # rather than the build host's clock. # --------------------------------------------------------------------------- builds: - - id: rho - main: ./cmd/rho - binary: rho + - id: decoder + main: ./cmd/decoder + binary: decoder env: - CGO_ENABLED=0 # Build strictly from go.mod/go.sum, never from a developer go.work. @@ -48,7 +48,7 @@ builds: # --------------------------------------------------------------------------- # Archives — tar.gz on Unix, zip on Windows. Names are -# rho___.; install.sh and .github/actions/rho depend +# decoder___.; install.sh and .github/actions/decoder depend # on this exact shape. # --------------------------------------------------------------------------- archives: @@ -94,7 +94,7 @@ checksum: # uploaded with the release as checksums.txt.sigstore.json. Verify with: # # cosign verify-blob --bundle checksums.txt.sigstore.json \ -# --certificate-identity "https://github.com/GrayCodeAI/rho/.github/workflows/release.yml@refs/tags/vX.Y.Z" \ +# --certificate-identity "https://github.com/GrayCodeAI/decoder/.github/workflows/release.yml@refs/tags/vX.Y.Z" \ # --certificate-oidc-issuer https://token.actions.githubusercontent.com \ # checksums.txt # --------------------------------------------------------------------------- @@ -164,9 +164,9 @@ release: prerelease: auto name_template: "v{{ .Version }}" header: | - ## rho v{{ .Version }} + ## decoder v{{ .Version }} - Rho is the terminal AI coding agent from GrayCode (pre-1.0). + Decoder is the terminal AI coding agent from GrayCode (pre-1.0). footer: | ### Verify this release @@ -176,10 +176,10 @@ release: ```sh cosign verify-blob --bundle checksums.txt.sigstore.json \ - --certificate-identity "https://github.com/GrayCodeAI/rho/.github/workflows/release.yml@refs/tags/{{ .Tag }}" \ + --certificate-identity "https://github.com/GrayCodeAI/decoder/.github/workflows/release.yml@refs/tags/{{ .Tag }}" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ checksums.txt sha256sum --ignore-missing -c checksums.txt ``` - {{ if .PreviousTag }}**Full changelog:** https://github.com/GrayCodeAI/rho/compare/{{ .PreviousTag }}...{{ .Tag }}{{ end }} + {{ if .PreviousTag }}**Full changelog:** https://github.com/GrayCodeAI/decoder/compare/{{ .PreviousTag }}...{{ .Tag }}{{ end }} diff --git a/.shared-templates/.goreleaser.yml.tmpl b/.shared-templates/.goreleaser.yml.tmpl index eec7aedf3..ae9ad4bd1 100644 --- a/.shared-templates/.goreleaser.yml.tmpl +++ b/.shared-templates/.goreleaser.yml.tmpl @@ -1,11 +1,11 @@ # Canonical GrayCodeAI goreleaser config for Go binary repos. -# Source of truth: .shared-templates/.goreleaser.yml.tmpl (rho's +# Source of truth: .shared-templates/.goreleaser.yml.tmpl (decoder's # .goreleaser.yml is the reference rendering). # # 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. rho) -# {{MAIN_PKG}} — main package path (e.g. ./ or ./cmd/rho) +# {{NAME}} — short repo name (e.g. decoder) +# {{MAIN_PKG}} — main package path (e.g. ./ or ./cmd/decoder) # {{DESCRIPTION}} — short single-line description for the release header # {{VERSION_PKG}} — Go package path holding Version vars # (e.g. main or github.com/GrayCodeAI//internal/version) diff --git a/.shared-templates/Makefile.binary.tmpl b/.shared-templates/Makefile.binary.tmpl index 1646cc356..3f06b178d 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}}. # -# rho is currently the only Go binary repo in the ecosystem, and its real -# Makefile extends this core with rho-specific targets (workspace `setup`, +# decoder is currently the only Go binary repo in the ecosystem, and its real +# Makefile extends this core with decoder-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 -# rho/Makefile. +# decoder/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/rho/blob/main/docs/versioning.md. +# See https://github.com/GrayCodeAI/decoder/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") @@ -139,5 +139,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 rho/Makefile for the real, extended reference implementation. +# See decoder/Makefile for the real, extended reference implementation. # --------------------------------------------------------------------------- diff --git a/.shared-templates/Makefile.library.tmpl b/.shared-templates/Makefile.library.tmpl index d2b16e25a..7a8643ca7 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/rho/blob/main/VERSIONING.md. +# See https://github.com/GrayCodeAI/decoder/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 24b1a6720..4a53beb63 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 Rho private packages. +boundary-guard: ## Fail if the SDK references support engines or Decoder 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 bcfb91eb4..826bc41d8 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 `rho`, because -`rho` is the one repo every consumer already depends on or references — +This directory is that source of truth. It lives here, in `decoder`, because +`decoder` 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 rho itself.** It is a template +**This directory is not built or run by decoder 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 `rho`) +- `Makefile.binary.tmpl` — Go binary repos (currently only `decoder`) - `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 `rho`'s `external/` submodule pins (rho-specific, not templated elsewhere) +- `scripts/sync-external.sh` — read-only drift report for `decoder`'s `external/` submodule pins (decoder-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. `rho`'s own `Makefile`/`lefthook.yml`/CI predate this directory +per repo. `decoder`'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 `rho`'s +`Makefile.binary.tmpl`, which documents the real deltas against `decoder`'s Makefile). ## Boundary rule diff --git a/.shared-templates/docs/coverage-matrix.md b/.shared-templates/docs/coverage-matrix.md index ed283de1f..d33414b69 100644 --- a/.shared-templates/docs/coverage-matrix.md +++ b/.shared-templates/docs/coverage-matrix.md @@ -8,8 +8,8 @@ repo's threshold, update both the CI file and this table in the same PR.** | Repo | Threshold | Mechanism | |---|---|---| -| `rho` | 65% | inline `bc` check in `ci.yml` | -| `flux` | 60% | inline `bc` check in `ci.yml` | +| `decoder` | 65% | inline `bc` check in `ci.yml` | +| `mux` | 60% | inline `bc` check in `ci.yml` | | `graycode-skills` | n/a | no Go/Python test suite (skill/content registry) | ## Why thresholds differ per repo diff --git a/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl b/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl index 2dc77a715..5beca785f 100644 --- a/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl +++ b/.shared-templates/scripts/check-ecosystem-boundaries.sh.tmpl @@ -12,29 +12,29 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" # ============================================================================= -# VARIANT 1 — Support engine (flux, harrier, shrike, swift, kestrel, merlin). +# VARIANT 1 — Support engine (mux, harrier, shrike, swift, kestrel, merlin). # Engines are peers: they may depend on falcon, -# but never on rho/internal/* or another engine. +# but never on decoder/internal/* or another engine. # ============================================================================= -FORBIDDEN_RHO='github\.com/GrayCodeAI/rho/internal/' +FORBIDDEN_DECODER='github\.com/GrayCodeAI/decoder/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_RHO" --glob '*.go' . || true)" + violations="$(rg -n "$FORBIDDEN_DECODER" --glob '*.go' . || true)" engine_violations="$(rg -n "$FORBIDDEN_ENGINES" --glob '*.go' . || true)" else - violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_RHO" . || true)" + violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_DECODER" . || true)" engine_violations="$(grep -rn --include='*.go' -E "$FORBIDDEN_ENGINES" . || true)" fi if [[ -n "${violations}" ]]; then - echo "forbidden Rho imports found:" + echo "forbidden Decoder imports found:" echo "${violations}" echo - echo "support repos must use local contracts, not rho/internal" + echo "support repos must use local contracts, not decoder/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 rho, engines, or any other GrayCodeAI/* package" +# echo "{{OWN_MODULE}} is a foundation repo — it must not depend on decoder, 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 rho's public surfaces -# only — never on a support engine directly, and never on rho/internal. +# VARIANT 3 — SDK / skills consumer (graycode-skills). Consumers may depend on decoder's public surfaces +# only — never on a support engine directly, and never on decoder/internal. # ============================================================================= # -# FORBIDDEN_ENGINES='github\.com/GrayCodeAI/(flux|harrier|shrike|swift|kestrel|merlin)(/|")' -# FORBIDDEN_INTERNAL='github\.com/GrayCodeAI/rho/internal' +# FORBIDDEN_ENGINES='github\.com/GrayCodeAI/(mux|harrier|shrike|swift|kestrel|merlin)(/|")' +# FORBIDDEN_INTERNAL='github\.com/GrayCodeAI/decoder/internal' # # (same rg/grep + report pattern as Variant 1, naming "SDKs/skills must go -# through rho's public API, not engines directly" as the violation message) +# through decoder'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 a911365bf..f22a0ce6b 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 rho to bump the external/tok pin. This +# sync-submodules` + a commit in decoder 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 rho after confirming the sibling repos are what you expect, then commit the updated external/ pins" + echo "drift detected — run 'make sync-submodules' in decoder 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 d53d0abbd..6df35c95b 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 rho/docs/compatibility.md. +# Cross-repo compatibility matrix check, referenced from decoder/docs/compatibility.md. # Source of truth: .shared-templates/workflows/compatibility-test.yml.tmpl # -# Lives (and runs) in rho only — rho owns testdata/compatibility-matrix.json +# Lives (and runs) in decoder only — decoder 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. @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: "1.26.5" + go-version: "1.27.2" cache: true - name: Structural validation (schema + version pins) run: make compat-check diff --git a/.shared-templates/workflows/go-ci.yml.tmpl b/.shared-templates/workflows/go-ci.yml.tmpl index f36742492..a3bd46d1e 100644 --- a/.shared-templates/workflows/go-ci.yml.tmpl +++ b/.shared-templates/workflows/go-ci.yml.tmpl @@ -37,7 +37,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: "1.26.5" + GO_VERSION: "1.27.2" GOPROXY: "https://proxy.golang.org,direct" GOPRIVATE: "github.com/GrayCodeAI/*" GONOSUMDB: "github.com/GrayCodeAI/*" @@ -86,7 +86,7 @@ jobs: run: bash ./scripts/check-ecosystem-boundaries.sh - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v7.0.0 with: - version: v2.1.0 + version: v2.14.0 install-mode: goinstall verify: false args: --timeout=5m @@ -146,11 +146,19 @@ jobs: cache: true - name: govulncheck run: | - go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 + go install golang.org/x/vuln/cmd/govulncheck@v1.8.0 govulncheck ./... - name: gosec (advisory) run: | - go install github.com/securego/gosec/v2/cmd/gosec@v2.22.4 + # gosec's release still pins an older Go export-data reader. + # Build it with Go 1.27-compatible x/tools in an isolated module. + tool_dir=$(mktemp -d) + ( + cd "$tool_dir" + go mod init graycodeai.local/gosec-tool + go get github.com/securego/gosec/v2/cmd/gosec@v2.29.0 golang.org/x/tools@v0.52.0 + go install github.com/securego/gosec/v2/cmd/gosec + ) gosec -exclude=G104,G301,G302,G304,G306 ./... # ------------------------------------------------------------------------- diff --git a/.shared-templates/workflows/go-release.yml.tmpl b/.shared-templates/workflows/go-release.yml.tmpl index d97a444b6..2e84b29e3 100644 --- a/.shared-templates/workflows/go-release.yml.tmpl +++ b/.shared-templates/workflows/go-release.yml.tmpl @@ -2,7 +2,7 @@ # A maintainer pushes a v tag after the release PR (VERSION + # CHANGELOG) merges; protect v* tags with a repository ruleset. # Source of truth: .shared-templates/workflows/go-release.yml.tmpl -# (rho's .github/workflows/release.yml is the reference rendering). +# (decoder's .github/workflows/release.yml is the reference rendering). name: release @@ -35,14 +35,14 @@ jobs: # Refuses a tag that disagrees with VERSION or has no CHANGELOG section, # and extracts that section as the release notes. Copy - # scripts/check-release-tag.sh from rho. + # scripts/check-release-tag.sh from decoder. - name: Check tag against VERSION and CHANGELOG run: bash ./scripts/check-release-tag.sh --notes "${RUNNER_TEMP}/release-notes.md" "${GITHUB_REF_NAME}" - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: "1.26.6" + go-version: "1.27.2" cache: true # GoReleaser shells out to syft for the `sboms:` stanza; the runner image diff --git a/.shared-templates/workflows/python-ci.yml.tmpl b/.shared-templates/workflows/python-ci.yml.tmpl index ead5a693b..2af8c8254 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. rho) +# {{PACKAGE}} — importable package name for --cov (e.g. decoder) # {{THRESHOLD}} — minimum coverage percentage, kept in sync with # docs/coverage-matrix.md diff --git a/AGENTS.md b/AGENTS.md index ead53ba9b..6258d9016 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,15 +1,15 @@ --- -description: Extending rho — how to write AGENTS.md files, custom specialists, skills, hooks, MCP servers, and plugins. +description: Extending decoder — 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 rho +# Extending decoder -rho is an open-source code intelligence platform. It lives in the `graycode-eco` -workspace alongside the ecosystem repos that power it (`flux`, +decoder is an open-source code intelligence platform. It lives in the `graycode-eco` +workspace alongside the ecosystem repos that power it (`mux`, `shrike`, `harrier`, `swift`, `kestrel`, `merlin`). This document describes how to extend -rho with custom tools, skills, hooks, and integrations. +decoder 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 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)`). +When decoder 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. rho 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. decoder 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. rho caps the **total** across all matched files at 32 KiB; everything past the cap is dropped. +- Keep each file under ~8 KiB. decoder 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`). rho 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`). decoder 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: `~/.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. +For preferences that follow *you*, not a specific repo (tone, tooling habits, workflow), drop a `ZERO.md` in your user config directory: `~/.decoder/ZERO.md` on Linux/macOS, `%AppData%\decoder\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 rho's sub-agents. Three scopes, in priority order: +Specialists are decoder's sub-agents. Three scopes, in priority order: | Scope | Path | Shared? | | --- | --- | --- | -| Built-in | compiled into rho | yes | -| User | `~/.rho/specialists/*.md` | no — your machine only | +| Built-in | compiled into decoder | yes | +| User | `~/.decoder/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 -rho specialist list -rho specialist show api-reviewer -rho specialist create api-reviewer \ +decoder specialist list +decoder specialist show api-reviewer +decoder specialist create api-reviewer \ --project \ --description "Reviews API changes" \ --tools read-only,plan \ --prompt "$(cat api-reviewer.md)" -rho specialist edit api-reviewer --project -rho specialist delete api-reviewer --project -rho specialist path # prints the resolved specialists directory +decoder specialist edit api-reviewer --project +decoder specialist delete api-reviewer --project +decoder specialist path # prints the resolved specialists directory ``` ## 3. Skills -rho ships **no bundled skills** by default. Skills are markdown instruction +decoder 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 -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 +decoder skills search # find skills in graycode-skills +decoder skills install [skill-name] # install after user approval +decoder skills list # list installed skills +decoder skills remove ``` Installed skills live in user or project scope: -- User-scoped: `~/.rho/skills/` +- User-scoped: `~/.decoder/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 -rho hook add beforeReview --command "lint-check" -rho hook remove beforeReview -rho hook list +decoder hook add beforeReview --command "lint-check" +decoder hook remove beforeReview +decoder hook list ``` ## 5. MCP integration -MCP (Model Context Protocol) servers can expose tools to rho: +MCP (Model Context Protocol) servers can expose tools to decoder: ```bash -rho mcp add --name server --url http://localhost:8080 -rho mcp remove server -rho mcp list +decoder mcp add --name server --url http://localhost:8080 +decoder mcp remove server +decoder mcp list ``` ## 6. Plugins -Plugins extend rho with custom tools and capabilities: +Plugins extend decoder with custom tools and capabilities: ```bash -rho plugin add --name my-plugin --path ./my-plugin -rho plugin remove my-plugin -rho plugin list +decoder plugin add --name my-plugin --path ./my-plugin +decoder plugin remove my-plugin +decoder plugin list ``` ## 7. Verification -rho includes a self-verification system to validate local changes before contributing: +decoder includes a self-verification system to validate local changes before contributing: ```bash -rho verify -rho verify --fix +decoder verify +decoder verify --fix ``` ## Development ```bash make lint -rho verify +decoder verify ``` ### Architecture note: cross-repo contracts -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. +Cross-repo severity and finding contracts now live in decoder's `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) — extensions and support repos must vendor the needed DTOs instead of Decoder internals until a published contracts module exists. ### Architecture note: provider ownership Implement provider protocols, adapters, catalog metadata, credential mappings, and -provider contract tests in `../flux` (the flux engine's repo) first. Rho consumes providers only -through Flux's stable engine facade; Rho changes should be limited to host UX +provider contract tests in `../mux` (the mux engine's repo) first. Decoder consumes providers only +through Mux's stable engine facade; Decoder 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 **rho** (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 **decoder** (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 **rho** (97743 symbols, 322940 relationsh | Resource | Use for | |----------|---------| -| `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 | +| `gitnexus://repo/decoder/context` | Codebase overview, check index freshness | +| `gitnexus://repo/decoder/clusters` | All functional areas | +| `gitnexus://repo/decoder/processes` | All execution flows | +| `gitnexus://repo/decoder/process/{name}` | Step-by-step execution trace | ## CLI @@ -245,10 +245,10 @@ This project is indexed by GitNexus as **rho** (97743 symbols, 322940 relationsh ### Workspace workflow (sibling repos) -rho depends on ecosystem repos (`flux`, 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. +decoder depends on ecosystem repos (`mux`, etc.) as independent sibling repos in the `graycode-eco` workspace. Decoder's `go.work` lists them as `../`, so local changes in any sibling are automatically picked up by decoder. Each sibling is its own git repo, versioned and released independently. -1. Edit + test in `../` — run its tests, run `make test` in rho +1. Edit + test in `../` — run its tests, run `make test` in decoder 2. Push from the sibling: `git push origin ` 3. Open a PR in the sibling repo → merge to `main` -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) +4. Ensure decoder'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: decoder 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 7c4578dfd..7d37afc5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASING.md). -> **Version history.** The next release, **v0.3.0**, is the first published -> under the `rho` name and module path `github.com/GrayCodeAI/rho`. Sections +> **Version history.** The next release, versioned by [`VERSION`](./VERSION), is the first published +> under the `decoder` name and module path `github.com/GrayCodeAI/decoder`. Sections > labelled `hawk` below are kept as the historical record of the project > before the rename: `hawk 0.2.0` is the `v0.2.0` tag, which (like `v0.1.0` > and `v0.1.1`) was built from the `github.com/GrayCodeAI/hawk` module and > ships `hawk_*` archives; the sections marked "untagged" used an earlier > numbering that was never tagged. Names in those sections are the names of -> the time (for example Flux was called eyrie, and several companion repos +> the time (for example Mux was called eyrie, and several companion repos > have since been retired). ## [Unreleased] @@ -23,25 +23,26 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI - **windows/arm64 release archives**: GoReleaser and the CI build matrix now cover all six linux/darwin/windows × amd64/arm64 targets. ### Changed -- **Version 0.3.0**: `VERSION` is `0.3.0`, the first release under the rho name (the Go proxy already holds the hawk-era v0.1.0–v0.2.0), and the compatibility matrix `stable` entry pins rho 0.3.0 with flux 0.0.1. +- The module, CLI, environment variables, state paths, integrations and packaging now use Decoder, backed by the renamed Mux module; Go 1.27.2 is required. +- The project version resets to the value in `VERSION`. Runtime defaults embed it, and package/API metadata is generated from it with CI drift checks. Historical hawk tags remain intact; explicit tags are required for the reset series. - **Release workflow can publish**: it installs syft for the SBOMs, signs `checksums.txt` inside GoReleaser with cosign keyless signing (`checksums.txt.sigstore.json`), re-verifies the published signature, takes the release notes from this file, builds with `GOWORK=off` and `-trimpath`, and drops the unused `packages: write` permission and sibling checkout. - **Binary size budget enforced**: `make size-check` fails above `SIZE_LIMIT_MB` (70 MiB; linux/amd64 is 59.6 MiB today) and CI runs it instead of only warning above 98 MB. - **`make release` no longer publishes** from a workstation; releases come only from the tag-triggered workflow. - **npm scaffolding kept unpublished**: packages are marked `"private": true` with the correct MIT license, and assembly stamps the meta package and its `optionalDependencies` and reads GoReleaser v2's output paths. Nothing is published to npm. -- **Dependency renamed eyrie → flux**: rho now depends on - `github.com/GrayCodeAI/flux v0.0.1` (the provider runtime was renamed). - All imports, env vars (`EYRIE_CONFIG_DIR` → `FLUX_CONFIG_DIR`, - `EYRIE_MODEL_CATALOG_PATH` → `FLUX_MODEL_CATALOG_PATH`), boundary-guard +- **Dependency renamed eyrie → mux**: decoder now depends on + `github.com/GrayCodeAI/mux v0.0.1` (the provider runtime was renamed). + All imports, env vars (`EYRIE_CONFIG_DIR` → `MUX_CONFIG_DIR`, + `EYRIE_MODEL_CATALOG_PATH` → `MUX_MODEL_CATALOG_PATH`), boundary-guard scripts, ecosystem manifest entries, docs, and internal identifiers were renamed to match. User state paths move from `~/.../eyrie/provider.json` - to `~/.../flux/provider.json`. + to `~/.../mux/provider.json`. - **Makefile lint pin matches CI**: `make lint`/`lint-fix`/`setup` install `golangci-lint@v2.1.0` (was `@latest`), the same version CI enforces. - **Docs truth and housekeeping**: SECURITY.md/CONTRIBUTING.md now describe the actual Go toolchain (golangci-lint, go vet, govulncheck) instead of the polyglot template's ruff/mypy/pip-audit/pnpm-lock language, CONTRIBUTING documents `make setup`/`boundaries`/`test-10x`/`smoke`, and the planning docs (`SPEC_DRIVEN_PLAN.md`, `SPEC_DRIVEN_PHASE2_PLAN.md`, `internal/engine/REFACTOR_PLAN.md`) moved to `docs/plans/`. ### Security -- **`install.sh` verifies releases fail-closed**: with cosign installed it verifies `checksums.txt.sigstore.json` against the exact release-workflow identity and aborts on a missing or invalid signature; without cosign it warns and reports the signature as not verified, and `RHO_REQUIRE_COSIGN=1` makes cosign mandatory. The SHA-256 check always runs, flags are parsed strictly (unknown flags exit 2), and releases older than v0.3.0 (hawk-era archives) are refused. -- **GitHub Action installs signed release archives** through `install.sh` with cosign required, instead of building an unverified `curl | tar` source tarball; `rho-version` accepts `0.3.0`, `v0.3.0` or `latest`, and Go is no longer needed. -- **systemd unit**: runs as a dedicated `rho` user with state in `StateDirectory`/`CacheDirectory` (the old `%h` paths were hidden by `ProtectHome=true`), reads the daemon API key from an `EnvironmentFile` instead of the command line, and links the troubleshooting guide instead of a dead domain. +- **`install.sh` verifies releases fail-closed**: with cosign installed it verifies `checksums.txt.sigstore.json` against the exact release-workflow identity and aborts on a missing or invalid signature; without cosign it warns and reports the signature as not verified, and `DECODER_REQUIRE_COSIGN=1` makes cosign mandatory. The SHA-256 check always runs, flags are parsed strictly (unknown flags exit 2), and known hawk-era tags are refused. +- **GitHub Action installs signed release archives** through `install.sh` with cosign required, instead of building an unverified `curl | tar` source tarball; `decoder-version` accepts a release version with or without `v`, or `latest`, and Go is no longer needed. +- **systemd unit**: runs as a dedicated `decoder` user with state in `StateDirectory`/`CacheDirectory` (the old `%h` paths were hidden by `ProtectHome=true`), reads the daemon API key from an `EnvironmentFile` instead of the command line, and links the troubleshooting guide instead of a dead domain. - **Session lock TOCTOU eliminated**: `AcquireLock`'s stat → stale-if->5min → remove → O_EXCL dance could delete a live lock on misjudged staleness and let two instances open the same session. Mutual exclusion now uses an OS advisory lock (`gofrs/flock`, promoted to a direct dependency); a crashed holder's lock is reclaimed instantly because the kernel drops the flock at process death. The lock file keeps PID/timestamps purely as diagnostics. - **Hardened atomic writes for state files**: global settings, checkpoint file contents and restores, handovers, and named checkpoints now go through `internal/safewrite` (same 0600 mode as before, plus fsync+rename atomicity and symlink refusal at the destination). - **Dead shell-injection surface removed**: `AssumptionTracker.VerifyCommandSucceeds` ran caller-supplied strings through `sh -c`, bypassing the permission stack; it had zero callers and is deleted. `SelfHealer.RunScript` no longer shell-evaluates the script path (double evaluation) and invokes it directly via `/bin/sh`. @@ -54,22 +55,22 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI - **Homebrew publisher and install instructions**: the `brews` stanza (deprecated in GoReleaser v2) targeted a `GrayCodeAI/homebrew-tap` repository and token that do not exist; the README no longer offers Homebrew or npm installs. - **`flake.nix` and its `.envrc`**: the flake depended on five deleted repositories and could not evaluate. - **ast-grep configuration** (`sgconfig.yaml`, `rules/`, lefthook hook): ast-grep never discovered the config and CI never ran it. -- **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 Flux's `provider.json`, and `SetActiveSelection` is deleted. Stale `model`/`provider` values left in `settings.json` are now ignored on load (Flux'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. `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. +- **BREAKING — `decoder credentials migrate` removed**: the subcommand, its man page entry, and the `MigrateEnvFileCredentials` wrappers are gone, and `decoder path` no longer reports legacy `~/.decoder/env` / `~/.decoder/.env` files. Save keys through `/config`. +- **BREAKING — settings model/provider migration removed**: `LoadSettings` no longer moves `model`/`provider` from `settings.json` into Mux's `provider.json`, and `SetActiveSelection` is deleted. Stale `model`/`provider` values left in `settings.json` are now ignored on load (Mux'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. `decoder 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 `decoder/internal` imports. ## [hawk 0.2.0] — 2026-07-13 ### Changed -- **Rho/Flux production boundary completed**: Rho owns the product face, - sessions, tools, permissions, and public schemas while Flux v0.2.1 (then - named eyrie; flux itself has only tagged v0.0.1) owns +- **Decoder/Mux production boundary completed**: Decoder owns the product face, + sessions, tools, permissions, and public schemas while Mux v0.2.1 (then + named eyrie; mux itself has only tagged v0.0.1) owns credentials, catalog resolution, provider transport, resilience, and usage - telemetry behind the stable `flux/engine` facade. + telemetry behind the stable `mux/engine` facade. - **Provider routing and usage attribution hardened**: resolved route changes, continuation segments, and terminal usage are propagated without duplicate - accounting, and production Flux calls use exactly one resilience layer. + accounting, and production Mux calls use exactly one resilience layer. - **Daemon conversations are durable**: JSON and SSE chat requests create or resume persisted sessions, expose stable session IDs, preserve metadata, and distinguish invalid, missing, and corrupt state. @@ -80,18 +81,18 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI - **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/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 (`flux`, `shrike`, `harrier`, `kestrel`, `merlin`). -- **Architecture boundary hardening**: Rho now owns runtime request/response DTOs, transport config/provider seams, and review/verification product-boundary contracts, with `flux/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. +- **Version re-baselined to `0.1.0`** across `cmd/decoder/main.go`, `cmd/daemon.go`, + `flake.nix`, `.github/workflows/release.yml`, and the `update`/daemon test suites, aligning decoder + with the rest of the GrayCodeAI ecosystem (`mux`, `shrike`, `harrier`, `kestrel`, `merlin`). +- **Architecture boundary hardening**: Decoder now owns runtime request/response DTOs, transport config/provider seams, and review/verification product-boundary contracts, with `mux/client` usage restricted to internal adapters and guarded in CI. +- **`shared/types` removed**: Decoder 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 `.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. +- **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 `.decoder/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/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. +- **GitHub Action** (`.github/actions/decoder`): interactive mode on `@decoder` 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 decoder 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). @@ -113,7 +114,7 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI ### Added — Round 2 ecosystem improvements (2026-06-01) - **Cavecrew personas** (`internal/multiagent/agents`): three new - built-in personas built into GrayCode Rho + built-in personas built into GrayCode Decoder (`cavecrew-investigator`, `cavecrew-builder`, `cavecrew-reviewer`). Each enforces a strict output format so downstream agents can parse outputs mechanically: @@ -152,7 +153,7 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI 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, rho tracks + shrike's `internal/tracking.Tracker` (shrike tracks globally, decoder tracks per-session). ### Added — Production Hardening (top-50 OSS parity) @@ -187,16 +188,16 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI ## [hawk 0.4.0, untagged] — 2026-05-05 ### Added -- **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 +- **Exec Subcommand**: `decoder 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**: `decoder daemon start/stop/status` — background HTTP server with JSON + SSE streaming on `/v1/chat`, `/v1/health`, `/v1/sessions` +- **Mission Mode**: `decoder mission "prompt"` — multi-agent orchestration decomposing work into parallel features executed in isolated git worktrees. `--dry-run` for planning only +- **Session Search**: `decoder search "query"` — full-text search across all saved sessions with `--json` output +- **Custom Agents**: `decoder agent list/create/show/remove` — markdown persona definitions in `~/.decoder/agents/` with YAML frontmatter (name, description, model) +- **Snapshot System**: Shadow git tracking of every file change. `decoder 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 `~/.rho/sessions/` and searchable via `rho search` +- **Session Persistence for exec**: All exec runs saved to `~/.decoder/sessions/` and searchable via `decoder search` ### Packages Added - `mission/` — Multi-agent orchestration with worktree-based parallel workers @@ -271,5 +272,5 @@ Releases are cut by hand from a release PR; see [docs/RELEASING.md](docs/RELEASI ### Added - Project scaffold with cobra CLI and Bubbletea TUI - Interactive chat REPL with textarea input, spinner, lipgloss styling -- flux wired as LLM provider dependency +- mux wired as LLM provider dependency - GitHub Actions CI diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e4bb52a8b..c9fd22d4e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,7 +2,7 @@ ## Our pledge -We — the maintainers and contributors of the rho project — pledge to +We — the maintainers and contributors of the decoder 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 085d7633a..91c056b74 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ -# Contributing to rho +# Contributing to decoder 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 @@ -141,8 +141,8 @@ Before requesting review: ## Reporting bugs -Open an issue using the bug-report template. Include the `rho` -version (`rho --version` for binaries, `rho.Version` for +Open an issue using the bug-report template. Include the `decoder` +version (`decoder --version` for binaries, `decoder.Version` for libraries — see this repo's `VERSION` file), reproduction steps, expected behaviour, and actual behaviour. diff --git a/Makefile b/Makefile index 266adec94..88c137c84 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,18 @@ # Canonical GrayCodeAI Makefile for Go binary repos. # Source of truth: .shared-templates/Makefile.binary.tmpl at the eco root. -# Placeholders rendered per repo: rho, .. +# Placeholders rendered per repo: decoder, .. # --------------------------------------------------------------------------- # Project metadata # --------------------------------------------------------------------------- -NAME := rho -MAIN_PKG := ./cmd/rho +NAME := decoder +MAIN_PKG := ./cmd/decoder # --------------------------------------------------------------------------- -# Versioning — sourced from VERSION file; falls back to git describe. -# See https://github.com/GrayCodeAI/rho/blob/main/docs/versioning.md. +# Versioning — sourced only from VERSION. +# See https://github.com/GrayCodeAI/decoder/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) +VERSION := $(shell tr -d '[:space:]' < VERSION) COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') @@ -27,7 +27,7 @@ LDFLAGS := -s -w \ GOBIN_DIR := $(shell go env GOPATH)/bin GOLANGCI := $(GOBIN_DIR)/golangci-lint # Keep in sync with the pin in .github/workflows/ci.yml (lint job). -GOLANGCI_VERSION := v2.1.0 +GOLANGCI_VERSION := v2.14.0 GOFUMPT := $(GOBIN_DIR)/gofumpt GOIMPORTS := $(GOBIN_DIR)/goimports GOVULNCHECK := $(GOBIN_DIR)/govulncheck @@ -35,7 +35,7 @@ GOVULNCHECK := $(GOBIN_DIR)/govulncheck # --------------------------------------------------------------------------- # Phony declarations (alphabetical). # --------------------------------------------------------------------------- -.PHONY: all bench boundaries build check-replace ci clean ecosystem-guard feature-boundaries-guard flux-client-guard flux-engine-guard manifest-guard peer-guard internal-layers-guard package-boundaries-guard release-parity cover cover-new fmt help install lint lint-fix \ +.PHONY: all bench boundaries build check-replace ci clean ecosystem-guard feature-boundaries-guard mux-client-guard mux-engine-guard manifest-guard peer-guard internal-layers-guard package-boundaries-guard release-parity cover cover-new fmt help install lint lint-fix \ release release-check release-snapshot security setup smoke path sync test test-10x test-live test-new test-race tidy version vet api-docs api-validate workspace check-replace: ## Fail if go.mod has local replace directives (run before tagging) @@ -49,7 +49,7 @@ all: lint test build ## Default — lint, test, build. # --------------------------------------------------------------------------- # Build / install / release. # --------------------------------------------------------------------------- -build: ## Build the binary into bin/$(NAME). +build: version-sync ## Build the binary into bin/$(NAME). CGO_ENABLED=0 go build -trimpath -ldflags="$(LDFLAGS)" -o bin/$(NAME) $(MAIN_PKG) install: ## Install the binary to $GOBIN. @@ -132,19 +132,19 @@ fmt-check: ## Verify formatting without rewriting files (CI-safe). vet: ## Run go vet. go vet ./... -ecosystem-guard: ## Fail if external ecosystem repos import rho/internal. +ecosystem-guard: ## Fail if external ecosystem repos import decoder/internal. bash ./scripts/check-ecosystem-boundaries.sh -flux-client-guard: ## Fail on any production flux/client import. - bash ./scripts/check-flux-client-imports.sh +mux-client-guard: ## Fail on any production mux/client import. + bash ./scripts/check-mux-client-imports.sh -flux-engine-guard: ## Require all production Flux imports to use the stable engine facade. - bash ./scripts/check-flux-engine-boundary.sh +mux-engine-guard: ## Require all production Mux imports to use the stable engine facade. + bash ./scripts/check-mux-engine-boundary.sh -peer-guard: ## Fail if support engines import each other instead of depending only on Rho contracts. +peer-guard: ## Fail if support engines import each other instead of depending only on Decoder contracts. bash ./scripts/check-support-repo-coupling.sh -internal-layers-guard: ## Enforce one-way dependencies across stable Rho internal layers. +internal-layers-guard: ## Enforce one-way dependencies across stable Decoder internal layers. bash ./scripts/check-internal-layer-imports.sh package-boundaries-guard: ## Enforce AST/package-graph boundaries with file/line diagnostics. @@ -153,7 +153,7 @@ package-boundaries-guard: ## Enforce AST/package-graph boundaries with file/line feature-boundaries-guard: ## Prevent feature packages from importing delivery or sibling features. bash ./scripts/check-feature-boundaries.sh -boundaries: manifest-guard check-replace ecosystem-guard feature-boundaries-guard flux-client-guard flux-engine-guard peer-guard internal-layers-guard package-boundaries-guard ## Alias for all boundary guards (matches `make boundaries` in engine repos). +boundaries: manifest-guard check-replace ecosystem-guard feature-boundaries-guard mux-client-guard mux-engine-guard peer-guard internal-layers-guard package-boundaries-guard ## Alias for all boundary guards (matches `make boundaries` in engine repos). release-parity: ## Verify every go.mod ecosystem version resolves to a reachable remote commit. bash ./scripts/check-module-release-parity.sh @@ -177,11 +177,11 @@ tidy: ## Sync workspace modules and verify checksums. # --------------------------------------------------------------------------- # Composite gate used by CI and pre-push. # --------------------------------------------------------------------------- -ci: tidy fmt-check vet boundaries lint test-race security api-validate ## Run everything CI runs. +ci: version-check 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-rho.sh + ./scripts/smoke-decoder.sh path: ## Verify developer path (setup, security, milestone tests). ./scripts/verify-developer-path.sh @@ -205,7 +205,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 rho development environment ===" + @echo "=== Setting up decoder development environment ===" @echo "✓ go.work generated and synced from ecosystem.yaml" @echo "" @echo "=== Environment check ===" @@ -231,7 +231,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 (rho-specific extension to the canonical template). +# Compatibility matrix (decoder-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. # --------------------------------------------------------------------------- @@ -243,7 +243,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 Rho and sibling repositories. Never fails. +compat-drift: ## Advisory: report pin drift between Decoder and sibling repositories. Never fails. @go run ./cmd/compat-test -check-external -file=testdata/compatibility-matrix.json .PHONY: hooks sync @@ -284,3 +284,10 @@ size-check: build ## Fail if bin/$(NAME) (release ldflags) exceeds SIZE_LIMIT_MB echo "::error::Binary size $$(( size / 1048576 )) MiB exceeds the $(SIZE_LIMIT_MB) MiB budget (Makefile SIZE_LIMIT_MB)"; \ exit 1; \ fi + +.PHONY: version-sync version-check +version-sync: ## Regenerate package/API metadata from VERSION. + python3 scripts/sync-version.py + +version-check: ## Reject stale generated version metadata. + python3 scripts/sync-version.py --check diff --git a/README.md b/README.md index 221bc2d0a..5291938f8 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@

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

@@ -25,13 +25,13 @@ --- -## Why rho +## Why decoder -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. +decoder 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, decoder works over SSH and on any machine with a shell. -**Developer path:** one machine, keychain credentials, local memory. Run `rho path` to check readiness. +**Developer path:** one machine, keychain credentials, local memory. Run `decoder path` to check readiness. -- **Model-agnostic** — supports many first-class providers through [flux](https://github.com/GrayCodeAI/flux) (the exact count is dynamic — see `rho --help`), including Anthropic, OpenAI, Gemini, Fireworks AI, Concentrate AI (pay-as-you-go), DeepSeek, and Ollama +- **Model-agnostic** — supports many first-class providers through [mux](https://github.com/GrayCodeAI/mux) (the exact count is dynamic — see `decoder --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,76 +39,79 @@ rho is an AI-powered coding agent that lives in your terminal. It reads your cod ## Status -**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. +**Decoder 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 Rho is ready to try, we will announce it on [graycodeai.com](https://graycodeai.com/changelog). +Follow [GrayCode](https://github.com/GrayCodeAI) for progress. When Decoder is ready to try, we will announce it on [graycodeai.com](https://graycodeai.com/changelog). ## Install -Prebuilt installs are **available from v0.3.0**, the first release published -under the `rho` name. Until v0.3.0 appears on the -[releases page](https://github.com/GrayCodeAI/rho/releases), build from source -(see [Quick Start](#quick-start-contributors--from-source)). The older -`v0.1.0`–`v0.2.0` tags predate the rename: they ship `hawk_*` archives under -the old module path and cannot be installed as `rho`. +The next Decoder release uses the version in [`VERSION`](./VERSION). +Until a release with `decoder_*` archives appears on the +[releases page](https://github.com/GrayCodeAI/decoder/releases), build from source +(see [Quick Start](#quick-start-contributors--from-source)). Historical +`v0.1.0`, `v0.1.1` and `v0.2.0` tags ship `hawk_*` archives under the old module +path and cannot be installed as `decoder`. -From v0.3.0: +After a Decoder release is published: ```bash -# Script: installs ~/.rho/bin/rho- and links it as ~/.rho/bin/rho. +# Script: installs ~/.decoder/bin/decoder- and links it as ~/.decoder/bin/decoder. # Always checks the archive's SHA-256 against checksums.txt; also verifies the # cosign signature of checksums.txt when cosign is installed (it says so when -# it is not). Set RHO_REQUIRE_COSIGN=1 to refuse installing without cosign. -curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/rho/v0.3.0/install.sh | sh +# it is not). Set DECODER_REQUIRE_COSIGN=1 to refuse installing without cosign. +curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/decoder/main/install.sh | sh # Pin a version or choose the install prefix (see: sh install.sh --help) -curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/rho/v0.3.0/install.sh | sh -s -- --version 0.3.0 --prefix "$HOME/.rho" +curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/decoder/main/install.sh | sh -s -- --version "$(cat VERSION)" --prefix "$HOME/.decoder" -# Go toolchain (Go 1.26+): builds the tagged module from source -go install github.com/GrayCodeAI/rho/cmd/rho@latest +# Go toolchain (Go 1.27.2+): builds the tagged module from source +go install github.com/GrayCodeAI/decoder/cmd/decoder@v$(cat VERSION) ``` -Each release carries `rho___` archives for +Each release carries `decoder___` archives for linux/darwin/windows on amd64/arm64, `checksums.txt`, its cosign signature bundle `checksums.txt.sigstore.json`, and SPDX SBOMs; the release notes show how to verify them by hand. Homebrew and npm packages are **not available**: there is no `graycodeai/tap` tap and nothing is published under `@graycodeai` on npm. -If `~/.rho/bin` is not on your `PATH`, add it to your shell profile. +If `~/.decoder/bin` is not on your `PATH`, add it to your shell profile. Then: ```bash -rho # interactive REPL (/config on first run: API key + model) -rho path # verify readiness +decoder # interactive REPL (/config on first run: API key + model) +decoder path # verify readiness ``` ## Quick Start (contributors — from source) +This development revision uses `DECODER_*` environment variables, `.decoder` +state paths, and the `decoder` keychain namespace. Configure credentials and +migrate any existing settings to these names before using the new binary. + ```bash -git clone https://github.com/GrayCodeAI/rho && cd rho +git clone https://github.com/GrayCodeAI/decoder && cd decoder make setup # generates go.work referencing sibling support repos in the graycode-eco workspace -go build -o rho ./cmd/rho -./rho +go build -o decoder ./cmd/decoder +./decoder # First run — paste API key in /config (stored in macOS Keychain / Linux keyring) # Verify readiness -./rho path +./decoder path ``` -**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 +**No Docker or container runtime is required.** Decoder executes agent commands +directly on the host. Run `decoder path` (or `decoder 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 rho. - -Optional: install the current `main` with the Go toolchain. Use `@main` until -v0.3.0 is tagged: `@latest` still resolves to the hawk-era v0.2.0, which does not -contain `cmd/rho`. +Do not put API keys in shell env or `.env` for decoder. +Optional: install the current `main` with the Go toolchain. Historical +higher-numbered hawk tags mean `@latest` cannot be relied on for the reset +version series. Use `@main` or an explicit Decoder release tag. ```bash -go install github.com/GrayCodeAI/rho/cmd/rho@main +go install github.com/GrayCodeAI/decoder/cmd/decoder@main ``` ## Features @@ -131,17 +134,17 @@ Built with [Bubble Tea](https://github.com/charmbracelet/bubbletea) for a smooth ### Portable Execution Graph -Export the latest or a selected Rho session as validated graph nodes, edges, +Export the latest or a selected Decoder session as validated graph nodes, edges, and lifecycle events: ```bash -rho graph export -rho graph export -rho graph export --mission-dir /path/to/mission +decoder graph export +decoder graph export +decoder graph export --mission-dir /path/to/mission # Explicitly privacy-normalize and sync the graph for a connected cloud project -rho cloud graph sync -rho cloud graph sync --mission-dir /path/to/mission +decoder cloud graph sync +decoder cloud graph sync --mission-dir /path/to/mission ``` Syncing needs a [GrayCode Cloud](#graycode-cloud-optional) connection. @@ -156,37 +159,37 @@ synchronized explicitly with the `--mission-dir` variants above. ### GrayCode Cloud (optional) GrayCode Cloud is the optional, opt-in hosted control plane for device login, -usage, delivery context and graph sync. Rho works fully without it and sends +usage, delivery context and graph sync. Decoder works fully without it and sends nothing until you connect. It is alpha and still being rolled out: if -`rho cloud login` cannot reach `cloud.graycodeai.com`, the hosted service is -not available to you yet, and nothing else in Rho is affected. +`decoder cloud login` cannot reach `cloud.graycodeai.com`, the hosted service is +not available to you yet, and nothing else in Decoder is affected. ```bash -rho cloud login # browser device login -rho cloud status # endpoint, device, project, token storage -rho cloud context # sync repository, branch, CI and deployment context -rho cloud graph sync # upload a privacy-normalized execution graph +decoder cloud login # browser device login +decoder cloud status # endpoint, device, project, token storage +decoder cloud context # sync repository, branch, CI and deployment context +decoder cloud graph sync # upload a privacy-normalized execution graph ``` - **Endpoint.** New connections use `https://cloud.graycodeai.com` unless you - pass `--endpoint` or set `RHO_CLOUD_URL` (for example a local Worker at + pass `--endpoint` or set `DECODER_CLOUD_URL` (for example a local Worker at `http://127.0.0.1:8787`). Only `https://` is accepted, except plain `http://` on `localhost`, `127.0.0.1` and `[::1]`. A saved connection keeps the endpoint that issued its token. `https://api.graycodeai.com` is the browser API and rejects device tokens. -- **What is sent.** Once connected, each `rho exec` run reports one usage +- **What is sent.** Once connected, each `decoder exec` run reports one usage event (model, token counts, duration, status). This is fail-open: it never - changes the run's result, and rho waits at most 3 seconds for it after the - output is written. `rho cloud context` and `rho cloud graph sync` are + changes the run's result, and decoder waits at most 3 seconds for it after the + output is written. `decoder cloud context` and `decoder cloud graph sync` are explicit, and any rejection is reported as an error. -- **Device token.** rho keeps the token in the macOS Keychain or the Windows +- **Device token.** decoder keeps the token in the macOS Keychain or the Windows Credential Manager. Linux and other platforms have no credential-store - integration yet, so the token is written to `.tokens` in the Rho config - directory (`~/.config/rho/.tokens` by default, or `$RHO_CONFIG_DIR/.tokens`) - as plaintext with mode `0600`. `rho cloud login`/`connect` print a warning - when that happens and `rho cloud status` shows where the token lives. + integration yet, so the token is written to `.tokens` in the Decoder config + directory (`~/.config/decoder/.tokens` by default, or `$DECODER_CONFIG_DIR/.tokens`) + as plaintext with mode `0600`. `decoder cloud login`/`connect` print a warning + when that happens and `decoder cloud status` shows where the token lives. - **Manual connection.** - `rho cloud connect --device-id --project-id --token-stdin < token.txt` + `decoder cloud connect --device-id --project-id --token-stdin < token.txt` saves an existing device; without `--token-stdin` it prompts for the token without echo. `--token ` still works but is deprecated because it exposes the token in shell history and `ps`. @@ -196,7 +199,7 @@ rho cloud graph sync # upload a privacy-normalized execution grap For larger tasks, decompose work into parallel feature branches (power-user / future team workflows): ```bash -rho mission "Add auth, rate limiting, and logging" +decoder mission "Add auth, rate limiting, and logging" ``` Each sub-agent runs in its own git worktree with full autonomy. @@ -206,14 +209,14 @@ Each sub-agent runs in its own git worktree with full autonomy. Discover and install modular instruction packages for specialized workflows: ```bash -rho skills search api # Search community registry -rho skills install go-review # Install from GitHub -rho skills audit # Security scan installed skills +decoder skills search api # Search community registry +decoder skills install go-review # Install from GitHub +decoder skills audit # Security scan installed skills ``` ### Permission Center -rho exposes two independent chat command centers — trust tier and the +decoder exposes two independent chat command centers — trust tier and the spec-driven workflow gate — rather than one merged permission mode: ```text @@ -245,7 +248,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 `.rho/specs//`, and blocks Write/Edit/Bash + writing real files to `.decoder/specs//`, and blocks Write/Edit/Bash until you approve moving to implementation — at **any** trust tier, including Autonomous. @@ -259,23 +262,23 @@ Connect external tools via [Model Context Protocol](https://modelcontextprotocol ### Watch Mode (AI-comment loop) -`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. +`decoder --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 decoder responds. Off by default; enabled via the `--watch` flag. ### CI / GitHub Action -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`). +A bundled GitHub Action (`.github/actions/decoder`) runs decoder in your pipeline: interactive mode on `@decoder` 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 rho 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 decoder 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, rho 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, decoder can auto-analyze the codebase to seed context (default-off, opt-in). ### Auto-Lint / Auto-Fix Cycle -After edits, rho can run the matching linter and iterate on fixes (bounded retries) before handing back. Opt-in; preserves existing behavior when disabled. +After edits, decoder can run the matching linter and iterate on fixes (bounded retries) before handing back. Opt-in; preserves existing behavior when disabled. ### Image / Multimodal Context @@ -311,17 +314,17 @@ Features adopted from open-source agent projects. All are off by default unless | Feature | Flag / Command | What it does | |---|---|---| -| 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) | +| Best-of-N fan-out | `decoder exec --fanout N` | Run the same prompt in N isolated worktrees, compare, merge winner | +| Completion notifications | `DECODER_NOTIFY_WEBHOOK_URL` / `DECODER_NOTIFY_TELEGRAM_TOKEN` + `_CHAT_ID` | Webhook or Telegram ping when a run finishes | +| Incremental system-context | `DECODER_INCREMENTAL_CONTEXT=1` | Reconcile dynamic sections instead of rebuilding the prompt | +| Tool-catalog shrink | `DECODER_TOOL_SHRINK=1` | Compress the tool catalog sent on every request | +| Compaction segments | `DECODER_COMPACTION_SEGMENT_DETAIL=verbose\|balanced\|minimal\|none` | Persist verbatim compacted turns to disk | +| Skill curator | `decoder skills curator status/run/pin/unpin/archive` + `DECODER_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 | `rho toolset [name]` + `Toolset` tool | Named tool groups (research, dev, ops, full_stack) | +| Composable toolsets | `decoder 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 `flux/client` (`ImageClient`), wired by the host (boundary-guarded — rho routes through the flux 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 `flux/client` (`AudioClient`), wired by the host | +| Media generation | `GenerateMedia` tool | Image/video generation with local persistence. Backend via `tool.SetMediaEngine`; an OpenAI-compatible client ships in `mux/client` (`ImageClient`), wired by the host (boundary-guarded — decoder routes through the mux 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 `mux/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 | | Path reservations | `internal/multiagent` ledger | Detect overlapping-file changes between parallel branches | @@ -329,27 +332,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 `rho 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 `decoder 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 `rho 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 `decoder 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 `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 | +| 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 `DECODER_GRACEFUL_EXHAUSTION=1` | +| Deterministic replay cache | `internal/replaycache` (`DECODER_REPLAY_CACHE_DIR`) | Disk-persisted SHA-256-keyed cache of completions; identical requests replay stored responses for reproducible regression runs | ## Usage ### Interactive Mode ```bash -rho # Start REPL -rho -r abc123 # Resume session -rho -c # Continue latest session -rho --provider openai --model gpt-4o # Override provider +decoder # Start REPL +decoder -r abc123 # Resume session +decoder -c # Continue latest session +decoder --provider openai --model gpt-4o # Override provider ``` ### Permission Examples @@ -368,37 +371,37 @@ rho --provider openai --model gpt-4o # Override provider ### Non-Interactive Mode ```bash -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 +decoder -p "explain this repo" # Print response, exit +decoder -p "fix tests" --allowed-tools "Bash(go test:*) Edit Read" +decoder exec "refactor auth module" # Full engine, non-interactive +decoder exec --auto full "add error handling" # Full autonomy +decoder exec --worktree "add rate limiting" # Isolated branch +decoder exec --agent reviewer "review last commit" # Custom persona ``` ### Diagnostics & ecosystem ```bash -rho path # Developer path readiness (setup + security) -rho doctor # Full health report (flux + token pipeline panel) -rho ecosystem # Ecosystem panel only -rho preflight # Quick ready-to-chat check +decoder path # Developer path readiness (setup + security) +decoder doctor # Full health report (mux + token pipeline panel) +decoder ecosystem # Ecosystem panel only +decoder preflight # Quick ready-to-chat check make path # Developer path verification make smoke # Build + quick verification script ``` See [docs/SECURITY-DEVELOPER.md](docs/SECURITY-DEVELOPER.md). -See [docs/ecosystem-message-flow.md](docs/ecosystem-message-flow.md) for how flux connects during a chat session, and [docs/ECOSYSTEM-WIRING.md](docs/ECOSYSTEM-WIRING.md) for the current-to-proposed architecture and repository boundaries. +See [docs/ecosystem-message-flow.md](docs/ecosystem-message-flow.md) for how mux connects during a chat session, and [docs/ECOSYSTEM-WIRING.md](docs/ECOSYSTEM-WIRING.md) for the current-to-proposed architecture and repository boundaries. In the TUI: `/path`, `/ecosystem`, `/memory` (AGENTS.md). ### Daemon Mode ```bash -rho daemon start # Background HTTP server on port 4590 -rho daemon status # Check if running -rho daemon stop # Graceful shutdown +decoder daemon start # Background HTTP server on port 4590 +decoder daemon status # Check if running +decoder daemon stop # Graceful shutdown ``` Endpoints: `GET /v1/health`, `GET /v1/ready` (dependency-aware readiness), `POST /v1/chat` (JSON or SSE streaming) @@ -406,15 +409,15 @@ Endpoints: `GET /v1/health`, `GET /v1/ready` (dependency-aware readiness), `POST ### Mission Mode ```bash -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 +decoder mission "Add auth, rate limiting, and logging" +decoder mission --workers 6 "Refactor into microservices" +decoder mission --dry-run "What would this decompose into?" +decoder mission --from-tasks # Execute validated dependency waves ``` ## Providers -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. +decoder works with any LLM provider. **Developer path:** paste keys in `/config` (stored in OS keychain) — not shell env or `.env`. Use `decoder credentials status` to verify. | Provider | ID | Key (via `/config`) | |---|---|---| @@ -432,20 +435,20 @@ rho works with any LLM provider. **Developer path:** paste keys in `/config` (st | Xiaomi (MiMo) Token Plan | `xiaomi_mimo_token_plan` | `XIAOMI_MIMO_TOKEN_PLAN_API_KEY` (pick region in `/config`) | | Ollama (local) | `ollama` | `OLLAMA_BASE_URL` (no API key) | -Provider routing, model resolution, and retries are handled by [flux](https://github.com/GrayCodeAI/flux). -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 -Flux's deployment catalog, so new models can be exposed by refreshing the catalog -instead of changing Rho. In chat, run `/refresh-model-catalog` to fetch the latest -deployment-aware catalog into `~/.flux/model_catalog.json`. +Provider routing, model resolution, and retries are handled by [mux](https://github.com/GrayCodeAI/mux). +For deployment-aware routing, set `"deployment_routing": true` in `.decoder/settings.json` +or export `DECODER_DEPLOYMENT_ROUTING=true`. Decoder will route canonical model IDs through +Mux's deployment catalog, so new models can be exposed by refreshing the catalog +instead of changing Decoder. In chat, run `/refresh-model-catalog` to fetch the latest +deployment-aware catalog into `~/.mux/model_catalog.json`. ## Architecture -rho is built in Go with a modular, layered architecture: +decoder is built in Go with a modular, layered architecture: ``` -rho/ -├── bin/ # Built binaries (rho, rho_bin) +decoder/ +├── bin/ # Built binaries (decoder, decoder_bin) ├── cmd/ # CLI entry point (Cobra + Bubble Tea TUI) ├── internal/ │ ├── engine/ # Agent loop, compaction, self-improvement @@ -473,30 +476,30 @@ rho/ Ecosystem sibling repos (independent Git repos in the `graycode-eco` parent folder): -├── flux/ # LLM provider runtime +├── mux/ # LLM provider runtime ``` ### Ecosystem -rho is the main CLI/product and integrates these GrayCodeAI repositories in +decoder is the main CLI/product and integrates these GrayCodeAI repositories in three runtime layers plus optional tooling/platform services: -- **Primary product:** **rho** is the only end-user product surface in this ecosystem. -- **Provider engine mounted by Rho:** **flux** 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. +- **Primary product:** **decoder** is the only end-user product surface in this ecosystem. +- **Provider engine mounted by Decoder:** **mux** is the LLM provider runtime, consumed through its stable engine facade. +- **API consumers/extensions:** **graycode-skills** provides Decoder skills + installed on demand (`decoder skills install`). +- **Tooling/platform:** **graycode-platform** contains the optional web/BFF/Decoder + Cloud plane and is outside the Decoder Go runtime graph. Local development uses: -- **`go.mod` modules:** pinned requirements for `flux` +- **`go.mod` modules:** pinned requirements for `mux` - **Workspace + `go.work`:** sibling support repos are cloned in the `graycode-eco` workspace (as `../`); `go.work` resolves the module paths to those local checkouts - **Module-mode builds:** standalone builds resolve the pinned `go.mod` versions from the module proxy (no workspace) Cross-repo contracts now live in `internal/contracts` (vendored from the removed `github.com/GrayCodeAI/eagle` module) so support repos do not depend -on Rho internals. External consumers should vendor the needed DTOs from `internal/contracts` +on Decoder internals. External consumers should vendor the needed DTOs from `internal/contracts` until a published contracts module exists. Current contract packages (`internal/contracts/`): @@ -514,26 +517,26 @@ You may keep a **personal** parent **`go.work`** that lists alternate clones on | Component | Repository | Purpose | |---|---|---| -| **rho** | This repo | AI coding agent | -| **flux** | [GrayCodeAI/flux](https://github.com/GrayCodeAI/flux) | LLM provider runtime | +| **decoder** | This repo | AI coding agent | +| **mux** | [GrayCodeAI/mux](https://github.com/GrayCodeAI/mux) | 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 Rho Cloud | +| **graycode-platform** | [GrayCodeAI/graycode-platform](https://github.com/GrayCodeAI/graycode-platform) | Web, BFF, and Decoder Cloud | The canonical inventory of sibling repositories is [`GrayCodeAI/graycode-eco/ecosystem.yaml`](https://github.com/GrayCodeAI/graycode-eco/blob/main/ecosystem.yaml), not the `ecosystem.yaml` in this repo, which is deprecated and incomplete. The inventory moved because a product must not own the registry of its siblings: it -lived here, `rho` was renamed from `hawk` without signalling the other nine +lived here, `decoder` was renamed from `hawk` without signalling the other nine repositories, and the list silently omitted `beam`. See [graycode-eco/adr/0001](https://github.com/GrayCodeAI/graycode-eco/blob/main/adr/README.md). `scripts/ecosystem-manifest.sh` reads the roof's file and falls back to the deprecated local copy only when the roof is not checked out alongside. -`flux` is the only Go module dependency in the entire ecosystem; it is consumed +`mux` is the only Go module dependency in the entire ecosystem; it is consumed through its stable engine facade. -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 the consolidated repo map and the current-vs-proposed architecture diagrams, see [docs/architecture/decoder-current-vs-proposed.md](docs/architecture/decoder-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). @@ -542,12 +545,12 @@ commands, see ### Prerequisites -- Go 1.26+ +- Go 1.27.2+ ### Build & Test ```bash -go build ./cmd/rho # Build binary +go build ./cmd/decoder # 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 @@ -571,7 +574,7 @@ Headline numbers (Go 1.26.6, AMD EPYC 7543P, `go test -bench -benchmem -count=3` ### Project Structure -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. +decoder 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 @@ -591,3 +594,10 @@ Use [Conventional Commits](https://www.conventionalcommits.org/) for commit mess MIT — see [LICENSE](LICENSE) for details. © 2026 GrayCode AI + +## Project version + +[`VERSION`](./VERSION) is the single version source. Edit it once, then run +`make version-sync` to regenerate package and OpenAPI metadata. CI and release +workflows run `make version-check` to reject stale metadata. Plain Go builds +also carry the canonical version when run outside the checkout. diff --git a/SECURITY.md b/SECURITY.md index 207af0b89..436841af6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ -# Security Policy — rho +# Security Policy — decoder ## 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/rho/blob/main/docs/versioning.md) +file at the repo root. See [`docs/versioning.md`](https://github.com/GrayCodeAI/decoder/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/rho/security/advisories/new), **or** -2. Email `security@rho.ai` with the details below. +1. Open a private [GitHub Security Advisory](https://github.com/GrayCodeAI/decoder/security/advisories/new), **or** +2. Email `security@decoder.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 rho integrates with (report to the +- LLM provider services that decoder integrates with (report to the provider). - Local filesystem misuse where an attacker already has shell access (out of threat model). -For rho-specific threat-model notes, see the README and any docs in +For decoder-specific threat-model notes, see the README and any docs in this repo. ## Config security model ### Clone-and-load attack defense -Project-level `.rho/settings.json` can be committed to a git repository. +Project-level `.decoder/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 rho in that directory. +arbitrary commands when a developer clones and runs decoder 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 `rho trust add` (`AllowProjectAutomation` in +must be trusted via `decoder trust add` (`AllowProjectAutomation` in `internal/trust/store.go`). There is no `--allow-project-mcp` flag. -Global MCP servers (from `~/.rho/settings.json`) are always loaded. +Global MCP servers (from `~/.decoder/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 `.rho/settings.json` (repository-safe subset only — see above) -5. Global `~/.rho/settings.json` +4. Project `.decoder/settings.json` (repository-safe subset only — see above) +5. Global `~/.decoder/settings.json` 6. Built-in defaults (lowest priority) Project-level config CANNOT escalate permissions beyond what global config @@ -118,7 +118,7 @@ implements field-by-field merging with explicit precedence rules. ### Credential storage API keys and secrets are stored in the OS secret store (macOS Keychain, -Linux secret service, Windows Credential Manager) via the `flux/credentials` +Linux secret service, Windows Credential Manager) via the `mux/credentials` package. They are never written to `settings.json`, `.env`, or any file in the repository. The `/config` command manages credential storage interactively. diff --git a/VERSION b/VERSION index 0d91a54c7..8acdd82b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.0.1 diff --git a/api/openapi.yaml b/api/openapi.yaml index ca80217a4..4a11d74f3 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1,19 +1,19 @@ openapi: "3.1.0" info: - title: Rho Daemon API + title: Decoder Daemon API description: | - HTTP API served by the rho daemon on port 4590. + HTTP API served by the decoder daemon on port 4590. Used by sparrow, robin, and external integrations. - The daemon must be running (`rho daemon start`) before calling these endpoints. + The daemon must be running (`decoder 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" + version: "0.0.1" license: name: MIT - url: https://github.com/GrayCodeAI/rho/blob/main/LICENSE + url: https://github.com/GrayCodeAI/decoder/blob/main/LICENSE contact: - url: https://github.com/GrayCodeAI/rho + url: https://github.com/GrayCodeAI/decoder 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 Rho agent persona to apply. Continuations inherit the persisted persona when omitted. + description: Named Decoder 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: [rho.graph/v1] + enum: [decoder.graph/v1] generated_at: type: string format: date-time @@ -457,7 +457,7 @@ components: properties: status: type: string - description: Output of `rho review status` + description: Output of `decoder review status` Error: type: object @@ -565,7 +565,7 @@ paths: tags: [system] summary: Readiness probe description: | - Returns 200 only when a session factory is configured and Flux's local + Returns 200 only when a session factory is configured and Mux's local preflight confirms provider state, catalog, credentials, and model selection are ready. Returns 503 with the failed dependency otherwise. security: [] @@ -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-Rho-Session-ID` header identify a retrievable `/v1/sessions/{id}`. + `X-Decoder-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-Rho-Session-ID: + X-Decoder-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 Rho's privacy-safe, read-only `rho.graph/v1` projection. + Returns Decoder's privacy-safe, read-only `decoder.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 Rho. + Swift session correlation performed by Decoder. parameters: - name: id in: path diff --git a/cmd/acp.go b/cmd/acp.go index 0ee51b3c0..2cfa5ff16 100644 --- a/cmd/acp.go +++ b/cmd/acp.go @@ -7,19 +7,19 @@ import ( "path/filepath" "syscall" - "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/GrayCodeAI/decoder/internal/acp" + "github.com/GrayCodeAI/decoder/internal/attachment" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + "github.com/GrayCodeAI/decoder/internal/storage" "github.com/spf13/cobra" ) var acpCmd = &cobra.Command{ Use: "acp", - 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 " + + Short: "Run decoder as an Agent Client Protocol (ACP) server", + Long: "Run decoder 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 := rhoconfig.LoadSettings() - newSession := newConfiguredRhoSessionFactory(settings, logger.New(io.Discard, logger.Error)) + settings := decoderconfig.LoadSettings() + newSession := newConfiguredDecoderSessionFactory(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 aa32f471c..fd20159f3 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -6,8 +6,8 @@ import ( "os" "path/filepath" - "github.com/GrayCodeAI/rho/internal/multiagent/agents" - "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/internal/multiagent/agents" + "github.com/GrayCodeAI/decoder/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 Rho user state.", + Long: "Create, list, and manage custom agent personas stored in Decoder user state.", } var agentListJSON bool @@ -82,7 +82,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: rho agent create ", textMuted)) + fmt.Printf("%s\n", auditTint("No agents found. Create one with: decoder agent create ", textMuted)) fmt.Printf("%s\n", auditTint("Agent directory: "+agents.DefaultDir(), textPrimary)) return nil } @@ -165,7 +165,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 ---", rhoColor), a.Prompt) + fmt.Printf("\n%s\n%s\n", auditTint("--- Prompt ---", decoderColor), a.Prompt) return nil } diff --git a/cmd/agent_grid.go b/cmd/agent_grid.go index 0ebae3ae3..d807353bc 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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(rhoColor).Padding(0, 1) + agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(decoderColor).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 06b796535..197578d21 100644 --- a/cmd/agent_grid_test.go +++ b/cmd/agent_grid_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestAgentStateString(t *testing.T) { diff --git a/cmd/agent_test.go b/cmd/agent_test.go index 3dfac3d3b..fe987407d 100644 --- a/cmd/agent_test.go +++ b/cmd/agent_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/multiagent/agents" + "github.com/GrayCodeAI/decoder/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("RHO_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("DECODER_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("RHO_STATE_DIR", stateDir) + t.Setenv("DECODER_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 d29984040..86dc3ded6 100644 --- a/cmd/ai_comments.go +++ b/cmd/ai_comments.go @@ -9,7 +9,7 @@ import ( "sort" "strings" - "github.com/GrayCodeAI/rho/internal/fsutil" + "github.com/GrayCodeAI/decoder/internal/fsutil" ) // AIDirective represents a found AI comment directive in a source file. diff --git a/cmd/audit.go b/cmd/audit.go index ce94e3fbb..0773d5ba3 100644 --- a/cmd/audit.go +++ b/cmd/audit.go @@ -11,8 +11,8 @@ import ( "time" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/rho/internal/hooks/audit" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/hooks/audit" + "github.com/GrayCodeAI/decoder/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 rho would have caught with current policies enabled, +Reports what decoder 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 rho sessions directory - rhoDir := storage.SessionsDir() - entries, err := os.ReadDir(rhoDir) + // Scan decoder sessions directory + decoderDir := storage.SessionsDir() + entries, err := os.ReadDir(decoderDir) 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(rhoDir, e.Name()) + path := filepath.Join(decoderDir, 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("Rho Audit Report", rhoColor)) + _, _ = fmt.Fprintf(w, " %s\n", auditTint("Decoder Audit Report", decoderColor)) _, _ = 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 46a3dfb27..f224c22fe 100644 --- a/cmd/autoinit.go +++ b/cmd/autoinit.go @@ -8,8 +8,8 @@ import ( "path/filepath" "time" - "github.com/GrayCodeAI/rho/internal/autoinit" - "github.com/GrayCodeAI/rho/internal/intelligence/repomap" + "github.com/GrayCodeAI/decoder/internal/autoinit" + "github.com/GrayCodeAI/decoder/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 / RHO.md +// - It is a no-op unless the project has NO context file (AGENTS.md / DECODER.md // / CONTEXT.md), no auto-init marker exists yet, and the -// RHO_DISABLE_AUTO_INIT kill switch is unset — all enforced by +// DECODER_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 / `rho init` without coupling to the agentic flow: a cheap, +// init-deep skill / `decoder 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 rho on first run. Edit freely or delete to regenerate. -> Set RHO_DISABLE_AUTO_INIT=1 to opt out of automatic context generation. +> Auto-generated by decoder on first run. Edit freely or delete to regenerate. +> Set DECODER_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 2df70aba5..fbbeb87d5 100644 --- a/cmd/autoinit_test.go +++ b/cmd/autoinit_test.go @@ -6,14 +6,14 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/autoinit" + "github.com/GrayCodeAI/decoder/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("RHO_STATE_DIR", filepath.Join(t.TempDir(), "state")) + t.Setenv("DECODER_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 8b69d22c2..8eeb576d2 100644 --- a/cmd/autonomy_picker.go +++ b/cmd/autonomy_picker.go @@ -3,7 +3,7 @@ package cmd import ( "strings" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" diff --git a/cmd/autonomy_picker_test.go b/cmd/autonomy_picker_test.go index e03dd772a..37750a307 100644 --- a/cmd/autonomy_picker_test.go +++ b/cmd/autonomy_picker_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" tea "charm.land/bubbletea/v2" "github.com/mattn/go-runewidth" diff --git a/cmd/autonomy_tiers.go b/cmd/autonomy_tiers.go index de7bea817..14d3884c6 100644 --- a/cmd/autonomy_tiers.go +++ b/cmd/autonomy_tiers.go @@ -4,7 +4,7 @@ import ( "fmt" "image/color" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" lipgloss "charm.land/lipgloss/v2" ) diff --git a/cmd/autonomy_tiers_copy_test.go b/cmd/autonomy_tiers_copy_test.go index f0fb1b5ae..27ff03365 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/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) func TestAutonomyTierDescriptions_PlainLanguage(t *testing.T) { diff --git a/cmd/autonomy_tiers_test.go b/cmd/autonomy_tiers_test.go index 0cd28911e..f640d7f6a 100644 --- a/cmd/autonomy_tiers_test.go +++ b/cmd/autonomy_tiers_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) func TestAutonomyTierNames(t *testing.T) { diff --git a/cmd/bg_sessions.go b/cmd/bg_sessions.go index 1d401fb25..8c6cfb78b 100644 --- a/cmd/bg_sessions.go +++ b/cmd/bg_sessions.go @@ -11,12 +11,12 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/storage" "github.com/spf13/cobra" ) // ───────────────────────────────────────────────────────────────────────────── -// Background Sessions — run rho sessions in the background and manage them. +// Background Sessions — run decoder sessions in the background and manage them. // ───────────────────────────────────────────────────────────────────────────── func bgSessionsDir() string { @@ -119,15 +119,15 @@ func KillBGSession(id string) error { return SaveBGSession(info) } -// StartBGSession launches rho in background mode. +// StartBGSession launches decoder in background mode. func StartBGSession(prompt string, args []string) (*BGSessionInfo, error) { id := genID() cwd, _ := os.Getwd() logFile := filepath.Join(bgSessionsDir(), id+".log") - // Build command: rho --print with all inherited flags + // Build command: decoder --print with all inherited flags cmdArgs := append([]string{"--print", "--session-id", id, prompt}, args...) - cmd := exec.CommandContext(context.Background(), "rho", cmdArgs...) // #nosec G204 -- fixed command 'rho' relaunching self with internal flags + cmd := exec.CommandContext(context.Background(), "decoder", cmdArgs...) // #nosec G204 -- fixed command 'decoder' 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 rho in the background and continue working in your terminal. + Long: `Start decoder in the background and continue working in your terminal. Examples: - rho bg "Refactor the auth module" - rho bg "Run tests and fix failures"`, + decoder bg "Refactor the auth module" + decoder 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: rho attach "+attachID, textMuted)) + cmd.Printf("%s\n", auditTint("Attach: decoder attach "+attachID, textMuted)) return nil }, } diff --git a/cmd/braille_spinner.go b/cmd/braille_spinner.go index 4de1382ef..79dad54fb 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" - SpinnerRho SpinnerStyle = "rho" - SpinnerRhoQuad SpinnerStyle = "rhoquad" + SpinnerDecoder SpinnerStyle = "decoder" + SpinnerDecoderQuad SpinnerStyle = "decoderquad" SpinnerDNA SpinnerStyle = "dna" SpinnerScan SpinnerStyle = "scan" SpinnerPulse SpinnerStyle = "pulse" @@ -24,18 +24,18 @@ const ( SpinnerRandom SpinnerStyle = "random" ) -// rhoSpinnerGlyphs is the default TUI spinner — partial-circle compass (smooth, readable). -var rhoSpinnerGlyphs = []string{"◐", "◓", "◑", "◒"} +// decoderSpinnerGlyphs is the default TUI spinner — partial-circle compass (smooth, readable). +var decoderSpinnerGlyphs = []string{"◐", "◓", "◑", "◒"} -// rhoQuadBlockGlyphs is the legacy QUADBLOCK animation (kept for tests / bubbles compat). -var rhoQuadBlockGlyphs = []string{"▛", "▜", "▟", "▙"} +// decoderQuadBlockGlyphs is the legacy QUADBLOCK animation (kept for tests / bubbles compat). +var decoderQuadBlockGlyphs = []string{"▛", "▜", "▟", "▙"} // spinnerFrames maps style names to their animation frames. var spinnerFrames = map[SpinnerStyle][]string{ SpinnerBraille: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, SpinnerBrailleWave: {"⠁⠂⠄⡀", "⠂⠄⡀⢀", "⠄⡀⢀⠠", "⡀⢀⠠⠐", "⢀⠠⠐⠈", "⠠⠐⠈⠁", "⠐⠈⠁⠂", "⠈⠁⠂⠄"}, - SpinnerRho: rhoSpinnerGlyphs, - SpinnerRhoQuad: rhoQuadBlockGlyphs, + SpinnerDecoder: decoderSpinnerGlyphs, + SpinnerDecoderQuad: decoderQuadBlockGlyphs, SpinnerDNA: {"⠋⠉⠙⠚", "⠉⠙⠚⠒", "⠙⠚⠒⠂", "⠚⠒⠂⠂", "⠒⠂⠂⠒", "⠂⠂⠒⠲", "⠂⠒⠲⠴", "⠒⠲⠴⠤", "⠲⠴⠤⠄", "⠴⠤⠄⠋", "⠤⠄⠋⠉", "⠄⠋⠉⠙"}, SpinnerScan: {"⡇⠀⠀⠀", "⣿⠀⠀⠀", "⢸⡇⠀⠀", "⠀⣿⠀⠀", "⠀⢸⡇⠀", "⠀⠀⣿⠀", "⠀⠀⢸⡇", "⠀⠀⠀⣿", "⠀⠀⠀⢸", "⠀⠀⠀⠀"}, SpinnerPulse: {"⠀", "⠄", "⠆", "⠇", "⡇", "⣇", "⣧", "⣷", "⣿", "⣷", "⣧", "⣇", "⡇", "⠇", "⠆", "⠄"}, @@ -45,8 +45,8 @@ var spinnerFrames = map[SpinnerStyle][]string{ SpinnerTalons: {"⩤", "⩥", "⩦", "⩧"}, } -// rhoTypingDots is the number of trailing typing-indicator dots. -const rhoTypingDots = 3 +// decoderTypingDots is the number of trailing typing-indicator dots. +const decoderTypingDots = 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..rhoTypingDots-1 — position of the highlighted dot + dots int // 0..decoderTypingDots-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{SpinnerRho, SpinnerBraille, SpinnerBrailleWave, SpinnerDNA, SpinnerScan, SpinnerPulse, SpinnerSnake, SpinnerOrbit} + styles := []SpinnerStyle{SpinnerDecoder, 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) % rhoTypingDots + s.dots = (s.dots + 1) % decoderTypingDots s.mu.Unlock() return s.Frame() } diff --git a/cmd/braille_spinner_test.go b/cmd/braille_spinner_test.go index 2375c0ebc..f06db36f3 100644 --- a/cmd/braille_spinner_test.go +++ b/cmd/braille_spinner_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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, SpinnerRho, SpinnerDNA, + SpinnerBraille, SpinnerBrailleWave, SpinnerDecoder, SpinnerDNA, SpinnerScan, SpinnerPulse, SpinnerSnake, SpinnerOrbit, SpinnerWing, SpinnerTalons, } @@ -50,14 +50,14 @@ func TestBrailleSpinner_Random(t *testing.T) { } } -func TestRhoSpinner_Frames(t *testing.T) { - if len(rhoSpinnerGlyphs) != 4 { - t.Fatalf("expected 4 compass glyphs, got %d", len(rhoSpinnerGlyphs)) +func TestDecoderSpinner_Frames(t *testing.T) { + if len(decoderSpinnerGlyphs) != 4 { + t.Fatalf("expected 4 compass glyphs, got %d", len(decoderSpinnerGlyphs)) } - if rhoSpinnerGlyphs[0] != "◐" { - t.Fatalf("expected first compass frame ◐, got %q", rhoSpinnerGlyphs[0]) + if decoderSpinnerGlyphs[0] != "◐" { + t.Fatalf("expected first compass frame ◐, got %q", decoderSpinnerGlyphs[0]) } - s := NewBrailleSpinner(SpinnerRho, "Working") + s := NewBrailleSpinner(SpinnerDecoder, "Working") f0 := s.Frame() if !strings.Contains(f0, "◐") { t.Fatalf("expected compass glyph, got %q", f0) @@ -70,20 +70,20 @@ func TestRhoSpinner_Frames(t *testing.T) { } } -func TestRhoQuadBlock_LegacyFrames(t *testing.T) { - s := NewBrailleSpinner(SpinnerRhoQuad, "Working") +func TestDecoderQuadBlock_LegacyFrames(t *testing.T) { + s := NewBrailleSpinner(SpinnerDecoderQuad, "Working") if !strings.Contains(s.Frame(), "▛") { t.Fatalf("expected QuadBlock glyph, got %q", s.Frame()) } } -func TestRhoAnimatedDots_PresentInFrame(t *testing.T) { - s := NewBrailleSpinner(SpinnerRho, "Crafting") +func TestDecoderAnimatedDots_PresentInFrame(t *testing.T) { + s := NewBrailleSpinner(SpinnerDecoder, "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 != rhoTypingDots { - t.Errorf("expected %d trailing circle-dots, got %d in %q", rhoTypingDots, total, f) + if total != decoderTypingDots { + t.Errorf("expected %d trailing circle-dots, got %d in %q", decoderTypingDots, 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 521bd443f..2c11d875b 100644 --- a/cmd/cascade_diag_test.go +++ b/cmd/cascade_diag_test.go @@ -3,14 +3,14 @@ package cmd import ( "testing" - rhobranch "github.com/GrayCodeAI/rho/internal/engine/branching" - "github.com/GrayCodeAI/rho/internal/provider/routing" + decoderbranch "github.com/GrayCodeAI/decoder/internal/engine/branching" + "github.com/GrayCodeAI/decoder/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 := rhobranch.NewCascadeRouter("opencodego/minimax-m2.5", roles) + cr := decoderbranch.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 5bf74fde8..84f9cf99a 100644 --- a/cmd/catalog_startup.go +++ b/cmd/catalog_startup.go @@ -4,7 +4,7 @@ import ( "context" "os" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) var ( @@ -13,15 +13,15 @@ var ( ) func ensureCatalogBeforeAgent(ctx context.Context, strict bool) error { - opts := rhoconfig.CatalogStartupOptions{ + opts := decoderconfig.CatalogStartupOptions{ ForceRefresh: refreshCatalogFlag, SkipAutoRefresh: skipCatalogRefreshFlag, VerboseOutput: refreshCatalogFlag, } if strict { - return rhoconfig.PrepareCatalogForSession(ctx, os.Stderr, opts) + return decoderconfig.PrepareCatalogForSession(ctx, os.Stderr, opts) } - rhoconfig.StartupCatalogPrefetch(ctx) + decoderconfig.StartupCatalogPrefetch(ctx) return nil } @@ -29,5 +29,5 @@ func startBackgroundCatalogRefresh(ctx context.Context) { if skipCatalogRefreshFlag { return } - rhoconfig.ScheduleBackgroundCatalogRefresh(ctx) + decoderconfig.ScheduleBackgroundCatalogRefresh(ctx) } diff --git a/cmd/chat.go b/cmd/chat.go index 5873baf85..c1a064348 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -16,7 +16,7 @@ import ( "syscall" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" "golang.org/x/term" @@ -27,23 +27,23 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "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" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" - sessionfeature "github.com/GrayCodeAI/rho/internal/features/session" - "github.com/GrayCodeAI/rho/internal/features/shellmode" - "github.com/GrayCodeAI/rho/internal/features/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/tui" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/bridge/sessioncapture" + "github.com/GrayCodeAI/decoder/internal/codegraph" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" + sessionfeature "github.com/GrayCodeAI/decoder/internal/features/session" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/features/taste" + "github.com/GrayCodeAI/decoder/internal/intelligence/repomap" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/startup" + decoderstorage "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/system/staleness" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/tui" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // Types, styles, and model struct are in chat_model.go @@ -52,7 +52,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 Rho to inspect, edit, or run something... (Shift+Enter for newline, ? for help)` +const workInputPlaceholder = `Ask Decoder to inspect, edit, or run something... (Shift+Enter for newline, ? for help)` func genID() string { b := make([]byte, 8) @@ -73,7 +73,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("rho: --session-id ignored during resume/continue", textMuted)) + fmt.Fprintf(os.Stderr, "%s\n", auditTint("decoder: --session-id ignored during resume/continue", textMuted)) } if resumeID == "" && !continueFlag { return id, nil, nil @@ -107,7 +107,7 @@ func prepareSession(sess *engine.Session) (string, *session.Session, error) { return saved.ID, saved, nil } -func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhoconfig.Settings, registry *tool.Registry) (chatModel, error) { +func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings decoderconfig.Settings, registry *tool.Registry) (chatModel, error) { startup.MarkPhase("newChatModel:total") startup.MarkPhase("newChatModel:ui-init") @@ -127,15 +127,15 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon CursorLine: lipgloss.NewStyle(), Base: lipgloss.NewStyle().Foreground(textPrimary), Placeholder: lipgloss.NewStyle().Foreground(textPlaceholder), - Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), }, Blurred: textarea.StyleState{ Base: lipgloss.NewStyle().Foreground(textPlaceholder), Placeholder: lipgloss.NewStyle().Foreground(textPlaceholder), - Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), }, Cursor: textarea.CursorStyle{ - Color: rhoColor, + Color: decoderColor, }, }) ta.Prompt = icons.ChevronRight() + " " @@ -147,8 +147,8 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon ci.EchoMode = textinput.EchoNormal sp := spinner.New() - sp.Spinner = spinner.Spinner{Frames: rhoSpinnerFrames, FPS: rhoSpinnerFrameInterval} - sp.Style = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + sp.Spinner = spinner.Spinner{Frames: decoderSpinnerFrames, FPS: decoderSpinnerFrameInterval} + sp.Style = lipgloss.NewStyle().Foreground(decoderColor).Bold(true) startup.EndPhase("newChatModel:ui-init") startup.MarkPhase("newChatModel:effectiveModelAndProvider") @@ -159,9 +159,9 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon startup.MarkPhase("newChatModel:defaultRegistry") startup.EndPhase("newChatModel:defaultRegistry") - startup.MarkPhase("newChatModel:newRhoSession") - sess := newStartupRhoSession(selection, systemPrompt, registry) - startup.EndPhase("newChatModel:newRhoSession") + startup.MarkPhase("newChatModel:newDecoderSession") + sess := newStartupDecoderSession(selection, systemPrompt, registry) + startup.EndPhase("newChatModel:newDecoderSession") startup.MarkPhase("newChatModel:configureSession") if cfgErr := prepareInteractiveSessionStartup(sess, settings); cfgErr != nil { @@ -185,7 +185,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon // Initialize conversation DAG for branching support startup.MarkPhase("newChatModel:dag") - graphPath := filepath.Join(rhostorage.SessionsDir(), "conversations", sid+".json") + graphPath := filepath.Join(decoderstorage.SessionsDir(), "conversations", sid+".json") if graph, err := session.OpenConversationGraph(graphPath, sid); err == nil { sess.SetConversationGraph(graph) } @@ -204,7 +204,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon now := time.Now() // Create a cancel function for background goroutines that gets called on quit. _, bgCancel := context.WithCancel(context.Background()) - m := chatModel{input: ta, configInput: ci, spinner: sp, viewport: vp, session: sess, registry: registry, settings: settings, ref: ref, sessionID: sid, partial: &strings.Builder{}, spinnerVerb: spinnerVerbs[rand.Intn(len(spinnerVerbs))], width: initWidth, height: initHeight, history: chatfeature.NewHistory(nil, chatfeature.DefaultHistoryLimit), autoScroll: true, streamFollow: true, uiFocus: focusPrompt, startedAt: now, sessionStartedAt: now, activeSkills: make(map[string]plugin.SmartSkill), toolResultExpanded: make(map[int]bool), bgCancel: bgCancel, mascotEnabled: rhoMascotEnabled()} // #nosec G404 -- non-cryptographic use: non-cryptographic spinner selection + m := chatModel{input: ta, configInput: ci, spinner: sp, viewport: vp, session: sess, registry: registry, settings: settings, ref: ref, sessionID: sid, partial: &strings.Builder{}, spinnerVerb: spinnerVerbs[rand.Intn(len(spinnerVerbs))], width: initWidth, height: initHeight, history: chatfeature.NewHistory(nil, chatfeature.DefaultHistoryLimit), autoScroll: true, streamFollow: true, uiFocus: focusPrompt, startedAt: now, sessionStartedAt: now, activeSkills: make(map[string]plugin.SmartSkill), toolResultExpanded: make(map[int]bool), bgCancel: bgCancel, mascotEnabled: decoderMascotEnabled()} // #nosec G404 -- non-cryptographic use: non-cryptographic spinner selection applyLiveModelMetadata(sess, effectiveProvider, effectiveModel) startup.MarkPhase("newChatModel:commandPalette") @@ -245,7 +245,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon m.contextualHelp = NewContextualHelp() m.modeManager = shellmode.NewModeManager() m.modeManager.LoadPersistedMode() - m.brailleSpinner = NewBrailleSpinner(SpinnerRho, "") + m.brailleSpinner = NewBrailleSpinner(SpinnerDecoder, "") m.brailleSpinner.SetLabel(m.spinnerVerb) startup.EndPhase("newChatModel:lacy-features") @@ -288,8 +288,8 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon // Prefetch live models for the active provider so footer ctx/pricing stay current. go func() { providerName := effectiveProvider - entries, _ := rhoconfig.ListEngineModels(context.Background(), providerName, false) - opts := configModelOptionsFromFlux(entries) + entries, _ := decoderconfig.ListEngineModels(context.Background(), providerName, false) + opts := configModelOptionsFromMux(entries) if len(opts) > 0 { modelCacheMu.Lock() modelCache[providerName] = opts @@ -315,7 +315,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon m.welcomeCache = buildWelcomeMessageWithSnapshotAndMascot(sess, sid, registry, saved, settings, 0, connectedMCPCount(registry), 0, initWidth, initHeight, quickSnapshot, "", m.mascotEnabled) 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("RHO_QUIET_START") == "" && os.Getenv("RHO_SUPPRESS_HINTS") == "" && os.Getenv("RHO_QUIET") == "" { + if saved == nil && os.Getenv("DECODER_QUIET_START") == "" && os.Getenv("DECODER_SUPPRESS_HINTS") == "" && os.Getenv("DECODER_QUIET") == "" { m.messages = append(m.messages, displayMsg{role: "system", content: controlPlaneOnboardingHint(sess)}) } startup.EndPhase("newChatModel:welcome") @@ -388,7 +388,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon // the initial UI. go func(currentSessionID string) { if recovered := session.CheckForRecovery(); len(recovered) > 0 { - walDir := rhostorage.SessionsDir() + walDir := decoderstorage.SessionsDir() for _, rid := range recovered { if rid == currentSessionID { continue // current session WAL @@ -404,7 +404,7 @@ func newChatModelWithRegistry(ref *progRef, systemPrompt string, settings rhocon // Warm footer data and the model catalog after the first frame. go func(model chatModel) { startup.MarkPhase("newChatModel:ui-cache-warm") - rhoconfig.RefreshConfigCredSnapshot(context.Background()) + decoderconfig.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 != "" { @@ -543,7 +543,7 @@ func (m *chatModel) refreshInputPlaceholder() { func (m chatModel) Init() tea.Cmd { cmds := []tea.Cmd{initTerminalMouseCmd(m.mouseEnabled()), promptKeepAliveCmd(), eyeBlinkTickCmd()} if m.mascotEnabled { - cmds = append(cmds, emitRhoMascotCmd()) + cmds = append(cmds, emitDecoderMascotCmd()) } if gw, _ := m.sessionGatewayModel(); strings.TrimSpace(gw) != "" { cmds = append(cmds, fetchModelsAsync(gw)) @@ -610,7 +610,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 - // RHO_DISABLE_AUTO_INIT. No-op for projects that already have context. + // DECODER_DISABLE_AUTO_INIT. No-op for projects that already have context. maybeAutoInit(context.Background()) ref := &progRef{} @@ -619,7 +619,7 @@ func runChat() error { err error } type startupSettingsResult struct { - settings rhoconfig.Settings + settings decoderconfig.Settings err error } type startupRegistryResult struct { @@ -683,7 +683,7 @@ func runChat() error { m.waiting = true } - // Suppress library log output (e.g. flux retry warnings) from corrupting the TUI. + // Suppress library log output (e.g. mux retry warnings) from corrupting the TUI. // Must be set BEFORE tea.NewProgram so no initialization logs leak through. log.SetOutput(io.Discard) @@ -747,10 +747,10 @@ func runChat() error { // frame and any terminal-persistent mascot so shutdown has one clean, // predictable final state. fmt.Print("\x1b[2J\x1b[H") - fmt.Println("Thank you for using Rho!") + fmt.Println("Thank you for using Decoder!") return nil } - rhoC := ansiOrange + decoderC := ansiOrange rst := ansiReset fmt.Print(fm.welcomeCache) @@ -758,10 +758,10 @@ func runChat() error { for _, msg := range fm.messages { switch msg.role { case "user": - fmt.Println(rhoC + "█" + rst + " " + msg.content) + fmt.Println(decoderC + "█" + rst + " " + msg.content) fmt.Println() case "assistant": - fmt.Println(rhoC + icons.Robot() + " " + rst + msg.content) + fmt.Println(decoderC + icons.Robot() + " " + rst + msg.content) fmt.Println() case "system": fmt.Println(dimStyle.Render("● " + msg.content)) @@ -793,12 +793,12 @@ func runChat() error { border := strings.Repeat("─", viewWidth) borderStyle := lipgloss.NewStyle().Foreground(borderDim) fmt.Println(borderStyle.Render(border)) - fmt.Println(lipgloss.NewStyle().Foreground(rhoColor).Bold(true).Render(">") + " ") + fmt.Println(lipgloss.NewStyle().Foreground(decoderColor).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: rho --resume %s", fm.sessionID))) + fmt.Println(dimStyle.Render(fmt.Sprintf("To resume this session, run: decoder --resume %s", fm.sessionID))) } return nil } diff --git a/cmd/chat_commands.go b/cmd/chat_commands.go index db33636f8..eb97580aa 100644 --- a/cmd/chat_commands.go +++ b/cmd/chat_commands.go @@ -12,11 +12,11 @@ import ( tea "charm.land/bubbletea/v2" - commandfeature "github.com/GrayCodeAI/rho/internal/features/commands" - parallelfeature "github.com/GrayCodeAI/rho/internal/features/parallel" - "github.com/GrayCodeAI/rho/internal/multiagent/parallel" - "github.com/GrayCodeAI/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/ui/icons" + commandfeature "github.com/GrayCodeAI/decoder/internal/features/commands" + parallelfeature "github.com/GrayCodeAI/decoder/internal/features/parallel" + "github.com/GrayCodeAI/decoder/internal/multiagent/parallel" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // slashCmdCache caches the slash commands list to avoid rebuilding. diff --git a/cmd/chat_commands_config.go b/cmd/chat_commands_config.go index 4db437b8a..b4ffd1242 100644 --- a/cmd/chat_commands_config.go +++ b/cmd/chat_commands_config.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - configfeature "github.com/GrayCodeAI/rho/internal/features/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + configfeature "github.com/GrayCodeAI/decoder/internal/features/config" ) // handleConfigCommand handles /config policy through the feature parser and @@ -21,7 +21,7 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, switch command.Action { case configfeature.ActionProvider: - if err := rhoconfig.SetGlobalSetting("provider", command.Value); err != nil { + if err := decoderconfig.SetGlobalSetting("provider", command.Value); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } @@ -31,9 +31,9 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, modelCacheMu.RUnlock() if cacheHit && len(cached) > 0 { m.session.SetModel(cached[0].ID) - _ = rhoconfig.SetGlobalSetting("model", cached[0].ID) + _ = decoderconfig.SetGlobalSetting("model", cached[0].ID) } - m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Provider set to: %s\nModel: %s\nSaved in flux (provider.json).", command.Value, m.session.Model())}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Provider set to: %s\nModel: %s\nSaved in mux (provider.json).", command.Value, m.session.Model())}) return m, nil case configfeature.ActionModel: @@ -53,12 +53,12 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, return m, nil } } - if err := rhoconfig.SetGlobalSetting("model", value); err != nil { + if err := decoderconfig.SetGlobalSetting("model", value); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } m.syncSessionSelection() - m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Model switched to: %s\nSaved in flux (provider.json).", m.session.Model())}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Model switched to: %s\nSaved in mux (provider.json).", m.session.Model())}) return m, nil case configfeature.ActionKeys: @@ -74,7 +74,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 := rhoconfig.SettingValue(settings, command.Key) + value, ok := decoderconfig.SettingValue(settings, command.Key) if !ok { m.messages = append(m.messages, displayMsg{role: "error", content: fmt.Sprintf("Unsupported setting key %q", command.Key)}) return m, nil @@ -86,7 +86,7 @@ func (m *chatModel) handleConfigCommand(parts []string, text string) (tea.Model, return m, nil case configfeature.ActionSet: - if err := rhoconfig.SetGlobalSetting(command.Key, command.Value); err != nil { + if err := decoderconfig.SetGlobalSetting(command.Key, command.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 bfdab010b..5c6d0bac7 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // handleImageCommand implements the /image slash command: @@ -17,7 +17,7 @@ import ( // // It reads an image (or PDF) file, builds a multimodal user message gated on the // active model's vision capability, and starts a turn. For PDFs the text is -// extracted and injected inline (flux has no native document block). Without a +// extracted and injected inline (mux has no native document block). Without a // vision-capable model the image degrades to a text-only note. func (m *chatModel) handleImageCommand(parts []string, text string) (tea.Model, tea.Cmd) { if len(parts) < 2 { diff --git a/cmd/chat_commands_session.go b/cmd/chat_commands_session.go index bd8ebac4c..de489afc5 100644 --- a/cmd/chat_commands_session.go +++ b/cmd/chat_commands_session.go @@ -7,9 +7,9 @@ import ( tea "charm.land/bubbletea/v2" - sessionfeature "github.com/GrayCodeAI/rho/internal/features/session" - workspacefeature "github.com/GrayCodeAI/rho/internal/features/workspace" - "github.com/GrayCodeAI/rho/internal/session" + sessionfeature "github.com/GrayCodeAI/decoder/internal/features/session" + workspacefeature "github.com/GrayCodeAI/decoder/internal/features/workspace" + "github.com/GrayCodeAI/decoder/internal/session" ) type sessionSaveResultMsg struct { @@ -415,7 +415,7 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string case "/session": info := fmt.Sprintf("Session: %s\nModel: %s/%s\nSpec stage: %s\nMessages: %d\nTools: %d\n%s", m.sessionID, m.session.Provider(), m.session.Model(), - specStageLabel(m.session), m.session.MessageCount(), len(m.registry.FluxTools()), m.session.CostValue().Summary()) + specStageLabel(m.session), m.session.MessageCount(), len(m.registry.MuxTools()), m.session.CostValue().Summary()) m.messages = append(m.messages, displayMsg{role: "system", content: info}) return m, nil diff --git a/cmd/chat_commands_skills.go b/cmd/chat_commands_skills.go index 2d2450bf3..6c4e012fb 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/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/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 c7f27e4e5..845357415 100644 --- a/cmd/chat_commands_test.go +++ b/cmd/chat_commands_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/tool" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/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: rhoconfig.Settings{MCPServers: []rhoconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}}, + settings: decoderconfig.Settings{MCPServers: []decoderconfig.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 := rhoconfig.Settings{ + settings := decoderconfig.Settings{ Provider: "openai", Model: "gpt-4o", - MCPServers: []rhoconfig.MCPServerConfig{ + MCPServers: []decoderconfig.MCPServerConfig{ {Name: "demo", Command: "demo-mcp", Args: []string{"--stdio"}}, }, } report := doctorReport(settings) - if !strings.Contains(report, "Rho doctor") || !strings.Contains(report, "Built-in tools") { + if !strings.Contains(report, "Decoder 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 93d1112a8..d8042ccae 100644 --- a/cmd/chat_commands_tools.go +++ b/cmd/chat_commands_tools.go @@ -7,10 +7,10 @@ import ( tea "charm.land/bubbletea/v2" - explainfeature "github.com/GrayCodeAI/rho/internal/features/explain" - "github.com/GrayCodeAI/rho/internal/features/shellmode" - skillsfeature "github.com/GrayCodeAI/rho/internal/features/skills" - "github.com/GrayCodeAI/rho/internal/ui/icons" + explainfeature "github.com/GrayCodeAI/decoder/internal/features/explain" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" + skillsfeature "github.com/GrayCodeAI/decoder/internal/features/skills" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // explainCode traces a file/line back to the git commit and session that created it. diff --git a/cmd/chat_commands_util.go b/cmd/chat_commands_util.go index e2ca1c5fb..fceaba19d 100644 --- a/cmd/chat_commands_util.go +++ b/cmd/chat_commands_util.go @@ -4,11 +4,11 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/features/taste" - workspacefeature "github.com/GrayCodeAI/rho/internal/features/workspace" - "github.com/GrayCodeAI/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/system/staleness" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/features/taste" + workspacefeature "github.com/GrayCodeAI/decoder/internal/features/workspace" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/system/staleness" ) func gitOutput(args ...string) (string, error) { @@ -56,7 +56,7 @@ func (m *chatModel) mcpSummary() string { } if m.registry != nil { var toolNames []string - for _, t := range m.registry.FluxTools() { + for _, t := range m.registry.MuxTools() { if strings.HasPrefix(t.Name, "mcp__") { toolNames = append(toolNames, t.Name) } @@ -77,7 +77,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 Rho user settings" + return "Hooks: pre_query, post_query, pre_tool, post_tool, session_start, session_end, permission_ask, error\nConfigure in Decoder user settings" } func pluginsSummary(rt *plugin.Runtime) string { diff --git a/cmd/chat_config_deployment.go b/cmd/chat_config_deployment.go index 62f159396..f69a1c969 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/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 != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { + if p := strings.TrimSpace(m.configModelProvider); p != "" && decoderconfig.HasStoredCredentialForProvider(ctx, p) { return p } if m.session != nil { - if p := strings.TrimSpace(m.session.Provider()); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { + if p := strings.TrimSpace(m.session.Provider()); p != "" && decoderconfig.HasStoredCredentialForProvider(ctx, p) { return p } } - if p := strings.TrimSpace(rhoconfig.ActiveGateway(ctx)); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { + if p := strings.TrimSpace(decoderconfig.ActiveGateway(ctx)); p != "" && decoderconfig.HasStoredCredentialForProvider(ctx, p) { return p } - if p := rhoconfig.DefaultModelProviderFilter(ctx); p != "" && rhoconfig.HasStoredCredentialForProvider(ctx, p) { + if p := decoderconfig.DefaultModelProviderFilter(ctx); p != "" && decoderconfig.HasStoredCredentialForProvider(ctx, p) { return p } - for _, p := range rhoconfig.AllSetupGateways() { - if rhoconfig.HasStoredCredentialForProvider(ctx, p) { + for _, p := range decoderconfig.AllSetupGateways() { + if decoderconfig.HasStoredCredentialForProvider(ctx, p) { return p } } return "" } -func saveProviderKeyAsync(inference rhoconfig.CredentialInference, secret string) tea.Cmd { +func saveProviderKeyAsync(inference decoderconfig.CredentialInference, secret string) tea.Cmd { return saveCredentialAsync(inference, secret) } func saveOllamaAsync(baseURL string) tea.Cmd { return func() tea.Msg { - inference, err := rhoconfig.LocalCredentialInference(configProviderOllama) + inference, err := decoderconfig.LocalCredentialInference(configProviderOllama) if err != nil { return configApplyCredentialsMsg{err: err} } - inf := rhoconfig.CredentialInference{ + inf := decoderconfig.CredentialInference{ ProviderID: inference.ProviderID, DeploymentID: inference.DeploymentID, EnvVar: inference.EnvVar, @@ -66,22 +66,22 @@ func saveOllamaAsync(baseURL string) tea.Cmd { } } -func saveCredentialAsync(inference rhoconfig.CredentialInference, secret string) tea.Cmd { +func saveCredentialAsync(inference decoderconfig.CredentialInference, secret string) tea.Cmd { return func() tea.Msg { ctx := context.Background() - if err := rhoconfig.SaveCredential(ctx, inference, secret); err != nil { + if err := decoderconfig.SaveCredential(ctx, inference, secret); err != nil { return configApplyCredentialsMsg{ err: err, providerID: inference.ProviderID, deploymentID: inference.DeploymentID, } } - rhoconfig.InvalidateConfigUICache() - rhoconfig.RefreshConfigCredSnapshot(ctx) - result, err := rhoconfig.ApplyFluxCredentialsForProvider(ctx, inference.ProviderID) - if err != nil && rhoconfig.IsCatalogCacheRequired(err) { - if refreshErr := rhoconfig.RefreshCatalogAfterCredentials(ctx, nil); refreshErr == nil { - result, err = rhoconfig.ApplyFluxCredentialsForProvider(ctx, inference.ProviderID) + decoderconfig.InvalidateConfigUICache() + decoderconfig.RefreshConfigCredSnapshot(ctx) + result, err := decoderconfig.ApplyMuxCredentialsForProvider(ctx, inference.ProviderID) + if err != nil && decoderconfig.IsCatalogCacheRequired(err) { + if refreshErr := decoderconfig.RefreshCatalogAfterCredentials(ctx, nil); refreshErr == nil { + result, err = decoderconfig.ApplyMuxCredentialsForProvider(ctx, inference.ProviderID) } else { err = fmt.Errorf("%w; automatic catalog refresh failed: %w", err, refreshErr) } @@ -94,7 +94,7 @@ func saveCredentialAsync(inference rhoconfig.CredentialInference, secret string) } } - entries, listErr := rhoconfig.ListEngineModels(ctx, inference.ProviderID, false) + entries, listErr := decoderconfig.ListEngineModels(ctx, inference.ProviderID, false) if listErr != nil { return configApplyCredentialsMsg{ err: listErr, @@ -102,9 +102,9 @@ func saveCredentialAsync(inference rhoconfig.CredentialInference, secret string) deploymentID: inference.DeploymentID, } } - opts := configModelOptionsFromFlux(entries) + opts := configModelOptionsFromMux(entries) return configApplyCredentialsMsg{ - summary: rhoconfig.FormatApplyCredentialsSummary(result), + summary: decoderconfig.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(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Blurred: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Cursor: textinput.CursorStyle{ - Color: rhoColor, + Color: decoderColor, }, }) 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() - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.RefreshConfigCredSnapshot(ctx) m = m.refreshConfigGatewayRows() if msg.err != nil { m.invalidateConnStatus() if msg.providerID == configProviderOllama { return m.returnToOllamaURLAfterError(msg.err) } - notice := sanitizeConfigNotice(rhoconfig.FormatConfigProviderError(msg.providerID, msg.err)) - saved := rhoconfig.HasStoredCredentialForProvider(ctx, msg.providerID) || + notice := sanitizeConfigNotice(decoderconfig.FormatConfigProviderError(msg.providerID, msg.err)) + saved := decoderconfig.HasStoredCredentialForProvider(ctx, msg.providerID) || strings.Contains(strings.ToLower(msg.err.Error()), "key saved in keychain") if saved { - if rhoconfig.IsCatalogCacheRequired(msg.err) { + if decoderconfig.IsCatalogCacheRequired(msg.err) { notice = "Key saved in " + credentialsStoreLabel() + " — model catalog unavailable: " + notice - notice += " · run rho models refresh" + notice += " · run decoder 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 !rhoconfig.IsCatalogCacheRequired(msg.err) && !strings.Contains(strings.ToLower(notice), "refresh") { - notice += " · press r on " + rhoconfig.GatewayDisplayName(msg.providerID) + " to retry" + if !decoderconfig.IsCatalogCacheRequired(msg.err) && !strings.Contains(strings.ToLower(notice), "refresh") { + notice += " · press r on " + decoderconfig.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 " + rhoconfig.GatewayDisplayName(post) + next.configNotice = "Key updated for " + decoderconfig.GatewayDisplayName(post) return next, cmd } if msg.providerID == configProviderOllama { - _ = rhoconfig.SetGlobalSetting("provider", configProviderOllama) + _ = decoderconfig.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 = rhoconfig.LoadSettings() + m.settings = decoderconfig.LoadSettings() m.syncSessionSelection() - selection := rhoconfig.EffectiveSelectionWithSettings(context.Background(), m.settings, rhoconfig.SelectionOptions{ + selection := decoderconfig.EffectiveSelectionWithSettings(context.Background(), m.settings, decoderconfig.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 9f96f83ef..519c73b72 100644 --- a/cmd/chat_config_gateways.go +++ b/cmd/chat_config_gateways.go @@ -7,8 +7,8 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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 := rhoconfig.GatewayStatuses(ctx, active, activeModel) + statuses := decoderconfig.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 = rhoconfig.CredentialEnvironmentConflict(ctx, status.ID) + credentialEnv, keyConflict = decoderconfig.CredentialEnvironmentConflict(ctx, status.ID) } display := status.DisplayName - if status.RegionRequired || status.RegionLabel != "" || rhoconfig.HasRegionOptions(status.ID) { + if status.RegionRequired || status.RegionLabel != "" || decoderconfig.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 || rhoconfig.ActiveProviderID(status.ID) == rhoconfig.ActiveProviderID(active), + Active: status.Active || decoderconfig.ActiveProviderID(status.ID) == decoderconfig.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 := rhoconfig.GatewayDisplayName(m.configKeysPendingRemove) + name := decoderconfig.GatewayDisplayName(m.configKeysPendingRemove) b.WriteString("\n" + mutedStyle.Render(indent+configGatewayRemovePrompt(m.configKeysRemoveStep, name))) - } else if !rhoconfig.HasConfiguredDeploymentCached(ctx) { + } else if !decoderconfig.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 != "" || rhoconfig.HasRegionOptions(rows[targetIdx].ID)) { + if targetIdx >= 0 && targetIdx < len(rows) && (rows[targetIdx].RegionRequired || rows[targetIdx].RegionLabel != "" || decoderconfig.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 != "" || rhoconfig.HasRegionOptions(rows[targetIdx].ID)) { + if targetIdx >= 0 && targetIdx < len(rows) && (rows[targetIdx].RegionRequired || rows[targetIdx].RegionLabel != "" || decoderconfig.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 || rhoconfig.HasRegionOptions(row.ID)) && (!row.HasKey || row.RegionRequired) { + if (row.RegionRequired || decoderconfig.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 - _ = rhoconfig.SetGlobalSetting("provider", gw) - if active := rhoconfig.ActiveProvider(context.Background()); active != "" { + _ = decoderconfig.SetGlobalSetting("provider", gw) + if active := decoderconfig.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 := rhoconfig.RefreshGatewayCatalog(context.Background(), providerID) + summary, err := decoderconfig.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(rhoconfig.FormatConfigProviderError(msg.providerID, msg.err)) + m.configNotice = sanitizeConfigNotice(decoderconfig.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 434edea1a..5ecbd66c0 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func chatModelForConfigPasteTest() chatModel { @@ -37,12 +37,12 @@ func TestConfigGatewayRows_UsesPanelCacheUntilInvalidated(t *testing.T) { func TestConfigGatewayRowsKeepRegistryOrderAfterKeySaved(t *testing.T) { isolateCredentialHome(t) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{configTab: configTabGateways} @@ -87,12 +87,12 @@ func TestConfigGatewaysView_RequiresKeyForModelCounts(t *testing.T) { } func TestConfigGatewaysView_ShowsSaveOrProbeNotice(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{ @@ -109,17 +109,17 @@ func TestConfigGatewaysView_ShowsSaveOrProbeNotice(t *testing.T) { } func TestConfigGatewayRefreshTargetIndex_UsesSelectedRow(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() sess := engine.NewSession("", "", "", nil) sess.SetProvider("openrouter") @@ -138,17 +138,17 @@ func TestConfigGatewayRefreshTargetIndex_UsesSelectedRow(t *testing.T) { } func TestConfigGatewayRefreshTargetIndex_UsesFocusOnRefreshRow(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() sess := engine.NewSession("", "", "", nil) sess.SetProvider("openrouter") @@ -196,19 +196,19 @@ func TestFocusConfigActiveGateway_SelectsActiveRow(t *testing.T) { } func TestHandleConfigGatewaysSelect_TokenPlanNoKeyShowsRegion(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{configTab: configTabGateways} rows := m.configGatewayRows() idx := -1 for i, row := range rows { - if row.ID == rhoconfig.ProviderXiaomiTokenPlan { + if row.ID == decoderconfig.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 != rhoconfig.ProviderXiaomiTokenPlan { + if next.configProvider != decoderconfig.ProviderXiaomiTokenPlan { t.Fatalf("provider = %q", next.configProvider) } } func TestHandleConfigGatewaysSelect_NoKeyStartsPaste(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModelForConfigPasteTest() @@ -243,7 +243,7 @@ func TestHandleConfigGatewaysSelect_NoKeyStartsPaste(t *testing.T) { } sel := 0 for i, row := range gwRows { - if row.ID == rhoconfig.ProviderXiaomiTokenPlan { + if row.ID == decoderconfig.ProviderXiaomiTokenPlan { continue } sel = i diff --git a/cmd/chat_config_hub.go b/cmd/chat_config_hub.go index 8fd3527ac..9db414fbb 100644 --- a/cmd/chat_config_hub.go +++ b/cmd/chat_config_hub.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func (m chatModel) openConfigPanel() (chatModel, tea.Cmd) { @@ -69,7 +69,7 @@ func catalogPricesAreStale(opts []configModelOption) bool { } func providerHasLiveFetcher(providerID string) bool { - return rhoconfig.GatewaySupportsLiveDiscovery(providerID) + return decoderconfig.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 = rhoconfig.FormatConfigProviderError(configProviderOllama, err) + m.configNotice = decoderconfig.FormatConfigProviderError(configProviderOllama, err) } return m.startConfigOllamaURLWithValue(url) } diff --git a/cmd/chat_config_keys.go b/cmd/chat_config_keys.go index aa88fd77f..87983ec6f 100644 --- a/cmd/chat_config_keys.go +++ b/cmd/chat_config_keys.go @@ -7,11 +7,11 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func credentialsStoreLabel() string { - return rhoconfig.CredentialStoreName() + return decoderconfig.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 := rhoconfig.GatewayDisplayName(providerName) - masked := rhoconfig.MaskCredentialForProvider(context.Background(), providerName) + displayName := decoderconfig.GatewayDisplayName(providerName) + masked := decoderconfig.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 := rhoconfig.GatewayRegionLabel(providerName); reg != "" || rhoconfig.NeedsGatewayRegion(providerName) || rhoconfig.HasRegionOptions(providerName) { + if reg := decoderconfig.GatewayRegionLabel(providerName); reg != "" || decoderconfig.NeedsGatewayRegion(providerName) || decoderconfig.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 rhoconfig.NeedsGatewayRegion(provider) { + if decoderconfig.NeedsGatewayRegion(provider) { m.configPostSaveKeysProvider = provider return m.startConfigGatewayRegion(provider), nil } - name := rhoconfig.GatewayDisplayName(provider) + name := decoderconfig.GatewayDisplayName(provider) m.configNotice = "Paste API key for " + name return m.startConfigEntry(configEntryAPIKeyPaste, provider) } func (m chatModel) startConfigKeyReplace(provider string) (chatModel, tea.Cmd) { - if rhoconfig.NeedsGatewayRegion(provider) { + if decoderconfig.NeedsGatewayRegion(provider) { m.configPostSaveKeysProvider = provider return m.startConfigGatewayRegion(provider), nil } m.configReplaceProvider = provider m.configEntry = configEntryNone - m.configNotice = "Paste replacement API key for " + rhoconfig.GatewayDisplayName(provider) + m.configNotice = "Paste replacement API key for " + decoderconfig.GatewayDisplayName(provider) return m.startConfigEntry(configEntryAPIKeyPaste, provider) } func (m chatModel) beginConfigGatewayKeyRemove(provider string) chatModel { m.configKeysPendingRemove = provider m.configKeysRemoveStep = 1 - name := rhoconfig.GatewayDisplayName(provider) + name := decoderconfig.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 := rhoconfig.GatewayDisplayName(trimmedProvider) + name := decoderconfig.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…", rhoconfig.GatewayDisplayName(trimmedProvider)) + m.configNotice = fmt.Sprintf("Removing key for %s…", decoderconfig.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 (rhoconfig.HasRegionOptions(trimmedProvider) || rhoconfig.GatewayRegionLabel(trimmedProvider) != "") && strings.EqualFold(key.Text, "g") { + if (decoderconfig.HasRegionOptions(trimmedProvider) || decoderconfig.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 f8502db99..050b81b12 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func TestConfigGatewaysView_KeyHintsWithCredentials(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() m := chatModel{configTab: configTabGateways} view := m.configGatewaysView() @@ -30,17 +30,17 @@ func TestConfigGatewaysView_KeyHintsWithCredentials(t *testing.T) { } func TestConfigGatewaysKeyView_OpenWithK(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() sel := 0 @@ -61,17 +61,17 @@ func TestConfigGatewaysKeyView_OpenWithK(t *testing.T) { } func TestConfigGatewaysDelete_PendingRemove(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() sel := 0 @@ -92,17 +92,17 @@ func TestConfigGatewaysDelete_PendingRemove(t *testing.T) { } func TestConfigGatewaysDelete_DoubleConfirm(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() sel := 0 @@ -131,12 +131,12 @@ func TestConfigGatewaysDelete_DoubleConfirm(t *testing.T) { } func TestOpenConfigRemoveKeyPanel_OpensGatewaysTab(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{} diff --git a/cmd/chat_config_models.go b/cmd/chat_config_models.go index 0d3f3000d..85ba9df4d 100644 --- a/cmd/chat_config_models.go +++ b/cmd/chat_config_models.go @@ -5,15 +5,15 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/rho/internal/engine/cost" + "github.com/GrayCodeAI/decoder/internal/engine/cost" tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" ) -// configModelOption is one row in the /config model picker (display from flux, id for settings). +// configModelOption is one row in the /config model picker (display from mux, id for settings). type configModelOption struct { ID string CanonicalID string @@ -44,7 +44,7 @@ func InvalidateModelCache() { modelSyncAttempted = make(map[string]bool) modelSyncMu.Unlock() invalidatePlatformContextCache() - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() } // InvalidateModelCacheProvider drops one gateway's cached picker rows. @@ -56,7 +56,7 @@ func InvalidateModelCacheProvider(provider string) { modelSyncMu.Lock() delete(modelSyncAttempted, provider) modelSyncMu.Unlock() - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() } func fetchModelsAsync(provider string) tea.Cmd { @@ -64,19 +64,19 @@ func fetchModelsAsync(provider string) tea.Cmd { ctx := context.Background() provider = strings.TrimSpace(provider) if provider == "" { - provider = rhoconfig.DefaultModelProviderFilter(ctx) + provider = decoderconfig.DefaultModelProviderFilter(ctx) } - entries, err := rhoconfig.ListEngineModels(ctx, provider, false) + entries, err := decoderconfig.ListEngineModels(ctx, provider, false) if err != nil { - if _, derr := rhoconfig.ListEngineModels(ctx, provider, true); derr == nil { + if _, derr := decoderconfig.ListEngineModels(ctx, provider, true); derr == nil { InvalidateModelCacheProvider(provider) - entries, err = rhoconfig.ListEngineModels(ctx, provider, false) + entries, err = decoderconfig.ListEngineModels(ctx, provider, false) } } if err != nil { return modelsFetchedMsg{provider: provider, err: err} } - opts := configModelOptionsFromFlux(entries) + opts := configModelOptionsFromMux(entries) if len(opts) > 0 { modelCacheMu.Lock() modelCache[provider] = opts @@ -86,7 +86,7 @@ func fetchModelsAsync(provider string) tea.Cmd { } } -func configModelOptionsFromFlux(entries []rhoconfig.EngineModel) []configModelOption { +func configModelOptionsFromMux(entries []decoderconfig.EngineModel) []configModelOption { opts := make([]configModelOption, len(entries)) for i, e := range entries { opts[i] = configModelOption{ @@ -170,10 +170,10 @@ func ensureModelCacheLoaded(provider string) { modelSyncMu.Unlock() ctx := context.Background() - entries, err := rhoconfig.ListEngineModels(ctx, provider, false) + entries, err := decoderconfig.ListEngineModels(ctx, provider, false) if err != nil { - if _, derr := rhoconfig.ListEngineModels(ctx, provider, true); derr == nil { - entries, err = rhoconfig.ListEngineModels(ctx, provider, false) + if _, derr := decoderconfig.ListEngineModels(ctx, provider, true); derr == nil { + entries, err = decoderconfig.ListEngineModels(ctx, provider, false) } } if err != nil || len(entries) == 0 { @@ -182,7 +182,7 @@ func ensureModelCacheLoaded(provider string) { modelSyncMu.Unlock() return } - opts := configModelOptionsFromFlux(entries) + opts := configModelOptionsFromMux(entries) modelCacheMu.Lock() modelCache[provider] = opts modelCacheMu.Unlock() @@ -252,9 +252,9 @@ func loadConfigModelOptions(provider string) []configModelOption { return cached } modelCacheMu.RUnlock() - entries, err := rhoconfig.ListEngineModels(context.Background(), provider, false) + entries, err := decoderconfig.ListEngineModels(context.Background(), provider, false) if err == nil && len(entries) > 0 { - opts := configModelOptionsFromFlux(entries) + opts := configModelOptionsFromMux(entries) modelCacheMu.Lock() modelCache[provider] = opts modelCacheMu.Unlock() diff --git a/cmd/chat_config_models_test.go b/cmd/chat_config_models_test.go index 1b37096ef..986c7eef0 100644 --- a/cmd/chat_config_models_test.go +++ b/cmd/chat_config_models_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func TestFilterConfigModelOptions(t *testing.T) { @@ -45,7 +45,7 @@ func TestModelOptionIsActive(t *testing.T) { } func TestConfigModelOptionsCarryResolvedEngineIdentity(t *testing.T) { - opts := configModelOptionsFromFlux([]rhoconfig.EngineModel{{ + opts := configModelOptionsFromMux([]decoderconfig.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 2f1da79ca..fcdde1989 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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() + " " + rhoconfig.GatewayDisplayName(providerName) + title = icons.Key() + " " + decoderconfig.GatewayDisplayName(providerName) hint = "paste key for this gateway only · " + hint } - if providerName == rhoconfig.ProviderXiaomiTokenPlan { - reg := rhoconfig.GatewayRegionLabel(rhoconfig.ProviderXiaomiTokenPlan) + if providerName == decoderconfig.ProviderXiaomiTokenPlan { + reg := decoderconfig.GatewayRegionLabel(decoderconfig.ProviderXiaomiTokenPlan) if reg == "" { reg = "not set — esc and pick region with g or enter on gateway row" } @@ -116,7 +116,7 @@ func (m chatModel) configOllamaURLView() string { var b strings.Builder b.WriteString(titleStyle.Render(icons.Llama()+" Ollama local") + "\n") - b.WriteString(mutedStyle.Render("no API key · flux discovers installed models") + "\n\n") + b.WriteString(mutedStyle.Render("no API key · mux discovers installed models") + "\n\n") if notice := strings.TrimSpace(m.configNotice); notice != "" { b.WriteString(mutedStyle.Render(sanitizeConfigNotice(notice)) + "\n\n") } @@ -198,7 +198,7 @@ func (m chatModel) configModelsTabView() string { gw = strings.TrimSpace(m.session.Provider()) } if gw != "" { - body.WriteString(renderConfigGatewayLine(rhoconfig.GatewayDisplayName(gw)) + "\n\n") + body.WriteString(renderConfigGatewayLine(decoderconfig.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(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Blurred: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Cursor: textinput.CursorStyle{ - Color: rhoColor, + Color: decoderColor, }, }) m.configInput.Focus() @@ -333,11 +333,11 @@ func (m chatModel) configActiveModelID() string { return modelName } } - return strings.TrimSpace(rhoconfig.ActiveModel(context.Background())) + return strings.TrimSpace(decoderconfig.ActiveModel(context.Background())) } func modelOptionIsActive(opt configModelOption, activeModelID string) bool { - return modelOptionIsActiveResolved(opt, activeModelID, rhoconfig.CanonicalModelID(context.Background(), activeModelID)) + return modelOptionIsActiveResolved(opt, activeModelID, decoderconfig.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 := rhoconfig.CanonicalModelID(context.Background(), activeID) + activeCanonicalID := decoderconfig.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 := rhoconfig.CanonicalModelID(context.Background(), activeModelID) + activeCanonicalID := decoderconfig.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 := rhoconfig.CatalogEmptyHint(context.Background()); hint != "" { + if hint := decoderconfig.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(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Blurred: textinput.StyleState{ - Prompt: lipgloss.NewStyle().Foreground(rhoColor).Bold(true), + Prompt: lipgloss.NewStyle().Foreground(decoderColor).Bold(true), Text: lipgloss.NewStyle().Foreground(textPrimary), }, Cursor: textinput.CursorStyle{ - Color: rhoColor, + Color: decoderColor, }, }) m.configInput.Focus() @@ -580,7 +580,7 @@ func (m chatModel) finishConfigEntry() (chatModel, tea.Cmd) { m.restoreChatInput() return m, nil } - if providerName == rhoconfig.ProviderXiaomiTokenPlan && rhoconfig.NeedsGatewayRegion(providerName) { + if providerName == decoderconfig.ProviderXiaomiTokenPlan && decoderconfig.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 := rhoconfig.CredentialInferenceForProvider(providerName) + inference, err := decoderconfig.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 := rhoconfig.CredentialGuidance(providerName, value); hint != "" { + if hint := decoderconfig.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 == rhoconfig.ProviderXiaomiTokenPlan { + if row, ok := m.selectedConfigGateway(); ok && row.ID == decoderconfig.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 := rhoconfig.SetGlobalSetting("model", modelID); err != nil { + if err := decoderconfig.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 := rhoconfig.SetGlobalSetting("provider", provider); err != nil { + if err := decoderconfig.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 !rhoconfig.EvaluateSetupCached(context.Background()).NeedsSetup { + if !decoderconfig.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 !rhoconfig.ModelCapabilitySupportsThinking(selected.Capabilities) { + if !decoderconfig.ModelCapabilitySupportsThinking(selected.Capabilities) { m.configNotice = "Thinking not supported for this model" return m, nil } - settings := rhoconfig.LoadSettings() - pref := rhoconfig.ThinkingPrefForModel(settings, selected.ID) + settings := decoderconfig.LoadSettings() + pref := decoderconfig.ThinkingPrefForModel(settings, selected.ID) // Current effective display: unset → off. Toggle flips that. currentlyOn := false if pref != nil { currentlyOn = *pref } next := !currentlyOn - if err := rhoconfig.SetModelThinking(selected.ID, next); err != nil { + if err := decoderconfig.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(), rhoconfig.CanonicalModelID(context.Background(), m.configActiveModelID())) { + if m.session != nil && modelOptionIsActiveResolved(selected, m.configActiveModelID(), decoderconfig.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 := rhoconfig.LoadSettings() + settings := decoderconfig.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(rhoconfig.ResolveThinkingForModel(settings, selected.ID, provider)) + m.session.SetThinkingEnabled(decoderconfig.ResolveThinkingForModel(settings, selected.ID, provider)) } diff --git a/cmd/chat_config_region.go b/cmd/chat_config_region.go index 4cbba73a6..8f439e980 100644 --- a/cmd/chat_config_region.go +++ b/cmd/chat_config_region.go @@ -7,7 +7,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func gatewayRegionOptionIndex(providerID, region string) int { @@ -15,7 +15,7 @@ func gatewayRegionOptionIndex(providerID, region string) int { if region == "" { return 0 } - opts := rhoconfig.GatewayRegionOptions(providerID) + opts := decoderconfig.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 rhoconfig.ProviderXiaomiTokenPlan: + case decoderconfig.ProviderXiaomiTokenPlan: m.configEntry = configEntryXiaomiRegion - case rhoconfig.ProviderZAICoding, rhoconfig.ProviderZAIPayg: + case decoderconfig.ProviderZAICoding, decoderconfig.ProviderZAIPayg: m.configEntry = configEntryZAIRegion default: m.configEntry = configEntryGatewayRegion } m.configProvider = providerID idx := 0 - if !rhoconfig.NeedsGatewayRegion(providerID) { - idx = gatewayRegionOptionIndex(providerID, rhoconfig.GatewayRegionLabel(providerID)) + if !decoderconfig.NeedsGatewayRegion(providerID) { + idx = gatewayRegionOptionIndex(providerID, decoderconfig.GatewayRegionLabel(providerID)) } m.configGatewayRegionSel = idx m.configZAIRegionSel = idx - name := rhoconfig.GatewayDisplayName(providerID) + name := decoderconfig.GatewayDisplayName(providerID) notice := fmt.Sprintf("Select %s region (↑↓ · enter · esc cancel)", name) - if saved := rhoconfig.GatewayRegionLabel(providerID); saved != "" { + if saved := decoderconfig.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 := rhoconfig.GatewayDisplayName(prov) + name := decoderconfig.GatewayDisplayName(prov) b.WriteString(renderConfigBreadcrumb(name+" region") + "\n\n") - opts := rhoconfig.GatewayRegionOptions(prov) + opts := decoderconfig.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 := rhoconfig.GatewayRegionOptions(m.configProvider) + opts := decoderconfig.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 := rhoconfig.SetGatewayRegion(m.configProvider, chosen.Value); err != nil { + if err := decoderconfig.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 rhoconfig.HasStoredCredentialForProvider(ctx, m.configProvider) { - m.configNotice = "Saved region for " + rhoconfig.GatewayDisplayName(m.configProvider) + if decoderconfig.HasStoredCredentialForProvider(ctx, m.configProvider) { + m.configNotice = "Saved region for " + decoderconfig.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 b4304e217..6f02b5a3e 100644 --- a/cmd/chat_config_remove.go +++ b/cmd/chat_config_remove.go @@ -6,7 +6,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) type configRemoveCredentialMsg struct { @@ -17,7 +17,7 @@ type configRemoveCredentialMsg struct { func removeCredentialAsync(provider string) tea.Cmd { return func() tea.Msg { - removed, err := rhoconfig.RemoveStoredCredential(context.Background(), provider) + removed, err := decoderconfig.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() - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.RefreshConfigCredSnapshot(ctx) m = m.refreshConfigGatewayRows() - if rhoconfig.ShouldClearSelectionAfterCredentialRemove(ctx, msg.provider) { - _ = rhoconfig.ClearActiveSelection(ctx) + if decoderconfig.ShouldClearSelectionAfterCredentialRemove(ctx, msg.provider) { + _ = decoderconfig.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", rhoconfig.GatewayDisplayName(msg.provider)) - if !rhoconfig.HasConfiguredDeploymentCached(ctx) { + m.configNotice = fmt.Sprintf("Removed API key for %s", decoderconfig.GatewayDisplayName(msg.provider)) + if !decoderconfig.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(rhoconfig.ConfiguredCredentialProviders()) == 0 { + if len(decoderconfig.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 01de618b0..e456873fa 100644 --- a/cmd/chat_config_remove_test.go +++ b/cmd/chat_config_remove_test.go @@ -4,22 +4,22 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func TestConfigGatewayRows_ShowsSavedKey(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() rows := chatModel{}.configGatewayRows() var found bool @@ -35,36 +35,36 @@ func TestConfigGatewayRows_ShowsSavedKey(t *testing.T) { } func TestConfiguredCredentialProviders_UsedByGatewaysTab(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() - got := rhoconfig.ConfiguredCredentialProviders() + got := decoderconfig.ConfiguredCredentialProviders() if len(got) != 1 || got[0] != "openrouter" { t.Fatalf("configured = %v", got) } } func TestRemoveCredentialAsync(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.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 efe5b3154..81b7e6e7b 100644 --- a/cmd/chat_config_save_flow_test.go +++ b/cmd/chat_config_save_flow_test.go @@ -8,25 +8,25 @@ import ( "testing" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/flux/catalog" - "github.com/GrayCodeAI/flux/credentials" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/mux/catalog" + "github.com/GrayCodeAI/mux/credentials" ) func TestFinishConfigEntry_APIKeyPaste_SavesBeforeProbe(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) oldTransport := http.DefaultTransport http.DefaultTransport = configSaveTestTransport(http.StatusUnauthorized) t.Cleanup(func() { http.DefaultTransport = oldTransport }) - if _, err := rhoconfig.CredentialInferenceForProvider("xiaomi_mimo_payg"); err != nil { + if _, err := decoderconfig.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/rho/issues/28 + t.Skip("all gateways already have keys in this environment") // TODO: https://github.com/GrayCodeAI/decoder/issues/28 } m.configSel = sel next, _ := m.handleConfigGatewaysSelect() @@ -134,7 +134,7 @@ func configSaveTestTransport(status int) http.RoundTripper { }) } -// roundTripFunc is defined in flux/runtime tests; duplicate here for rho cmd tests. +// roundTripFunc is defined in mux/runtime tests; duplicate here for decoder 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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), credentials.AccountForEnv("POOLSIDE_API_KEY"), "poolside-test-key"); err != nil { t.Fatalf("store key: %v", err) @@ -218,19 +218,19 @@ 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, "rho models refresh") { + !strings.Contains(next.configNotice, "decoder models refresh") { t.Fatalf("catalog recovery guidance missing: %q", next.configNotice) } } -// Skipped: integration test requiring specific flux model catalog state +// Skipped: integration test requiring specific mux model catalog state func TestHandleConfigApplyCredentialsMsg_ValidationFailureDoesNotBlameProvider(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) if err := store.Set(t.Context(), credentials.AccountForEnv("CONCENTRATE_API_KEY"), "concentrate-test-key"); err != nil { t.Fatalf("store key: %v", err) @@ -250,14 +250,14 @@ func TestHandleConfigApplyCredentialsMsg_ValidationFailureDoesNotBlameProvider(t } } -// Skipped: integration test requiring specific flux model catalog state +// Skipped: integration test requiring specific mux model catalog state func TestHandleConfigApplyCredentialsMsg_AuthenticationFailureBlamesKey(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &credentials.MapStore{} credentials.SetDefaultStore(store) t.Cleanup(func() { credentials.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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 fff732853..5b997c7e7 100644 --- a/cmd/chat_config_security.go +++ b/cmd/chat_config_security.go @@ -4,7 +4,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) var ( diff --git a/cmd/chat_config_tabs.go b/cmd/chat_config_tabs.go index 18b9b46b1..8ba6dc41e 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func (m chatModel) configStatus() (gateway, model string, configured bool) { ctx := context.Background() - if !rhoconfig.HasConfiguredDeploymentCached(ctx) { + if !decoderconfig.HasConfiguredDeploymentCached(ctx) { return "none", "", false } gw := strings.TrimSpace(m.configModelProvider) - if gw != "" && rhoconfig.IsSetupGateway(gw) { - gw = rhoconfig.GatewayDisplayName(gw) - } else if active := rhoconfig.ActiveGateway(ctx); active != "" { - gw = rhoconfig.GatewayDisplayName(active) + if gw != "" && decoderconfig.IsSetupGateway(gw) { + gw = decoderconfig.GatewayDisplayName(gw) + } else if active := decoderconfig.ActiveGateway(ctx); active != "" { + gw = decoderconfig.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(rhoconfig.ActiveModel(ctx)) + model = strings.TrimSpace(decoderconfig.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(rhoColor).Bold(true) + return lipgloss.NewStyle().Foreground(decoderColor).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 && !rhoconfig.HasConfiguredDeploymentCached(ctx) { + if tab == configTabModels && !decoderconfig.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 - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.RefreshConfigCredSnapshot(ctx) m = m.refreshConfigGatewayRows() - setup := rhoconfig.EvaluateSetupCached(ctx) + setup := decoderconfig.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 24af97697..4a519c9d2 100644 --- a/cmd/chat_config_tabs_test.go +++ b/cmd/chat_config_tabs_test.go @@ -4,9 +4,9 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{} @@ -47,12 +47,12 @@ func TestOpenConfigPanel_FirstRunOpensGateways(t *testing.T) { } func TestOpenConfigAtTab_ModelsWithoutCredentialsFallsBackToGateways(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{} diff --git a/cmd/chat_config_ui.go b/cmd/chat_config_ui.go index 23884ffc2..a6f364551 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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(rhoColor).Bold(true) + return lipgloss.NewStyle().Foreground(decoderColor).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(rhoColor).Bold(true) + return lipgloss.NewStyle().Foreground(decoderColor).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(rhoColor) + return lipgloss.NewStyle().Foreground(decoderColor) } func renderConfigBreadcrumb(title string) string { @@ -109,7 +109,7 @@ func configNoticeStyle(notice string) lipgloss.Style { strings.Contains(n, "unauthorized"): return lipgloss.NewStyle().Foreground(errorCoral) case strings.HasPrefix(notice, "Refreshed"), - strings.HasPrefix(notice, "Flux:"), + strings.HasPrefix(notice, "Mux:"), strings.Contains(notice, "Removed API key"), strings.Contains(notice, "Setup complete"), strings.Contains(n, "key saved"), diff --git a/cmd/chat_copy_e2e_test.go b/cmd/chat_copy_e2e_test.go index 49959ff0b..78b39c6c7 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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("RHO_MOUSE", "") + t.Setenv("DECODER_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 rho"}) + m.messages = append(m.messages, displayMsg{role: "assistant", content: "Hello from decoder"}) m.input.SetValue("") - if content, _, got := m.copyContent(copyModeAssistant); !got || content != "Hello from rho" { + if content, _, got := m.copyContent(copyModeAssistant); !got || content != "Hello from decoder" { t.Fatalf("pass %d: /copy assistant content = %q got=%v", pass, content, got) } - if line, got := m.lastMessageContent(); !got || !strings.Contains(line, "Hello from rho") { + if line, got := m.lastMessageContent(); !got || !strings.Contains(line, "Hello from decoder") { 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: rhoconfig.Settings{TuiMouse: &disabled}} + m2 := chatModel{settings: decoderconfig.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 d6d7ca158..0f7bc6dd0 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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("RHO_MOUSE", "") + t.Setenv("DECODER_MOUSE", "") if !(chatModel{}).mouseEnabled() { t.Fatal("expected mouse capture to default on") } disabled := false - m := chatModel{settings: rhoconfig.Settings{TuiMouse: &disabled}} + m := chatModel{settings: decoderconfig.Settings{TuiMouse: &disabled}} if m.mouseEnabled() { t.Fatal("expected settings tui_mouse=false to disable capture") } - t.Setenv("RHO_MOUSE", "0") + t.Setenv("DECODER_MOUSE", "0") if m.mouseEnabled() { - t.Fatal("expected RHO_MOUSE=0 to disable capture") + t.Fatal("expected DECODER_MOUSE=0 to disable capture") } - t.Setenv("RHO_MOUSE", "1") + t.Setenv("DECODER_MOUSE", "1") if !m.mouseEnabled() { - t.Fatal("expected RHO_MOUSE=1 to enable capture") + t.Fatal("expected DECODER_MOUSE=1 to enable capture") } } diff --git a/cmd/chat_errors_test.go b/cmd/chat_errors_test.go index 4eab49735..a67c20e0d 100644 --- a/cmd/chat_errors_test.go +++ b/cmd/chat_errors_test.go @@ -12,7 +12,7 @@ func TestFriendlyError(t *testing.T) { err string contains string }{ - {"rate limit 429", "flux: openai stream request failed: max retries (3) exceeded: HTTP 429", "Rate limited"}, + {"rate limit 429", "mux: openai stream request failed: max retries (3) exceeded: HTTP 429", "Rate limited"}, {"unauthorized 401", "HTTP 401 Unauthorized", "Authentication failed"}, {"forbidden 403", "HTTP 403 Forbidden", "Access denied"}, {"not found 404", "model not found", "/model"}, diff --git a/cmd/chat_export.go b/cmd/chat_export.go index 9dfaa94d8..7ead2cc9c 100644 --- a/cmd/chat_export.go +++ b/cmd/chat_export.go @@ -4,8 +4,8 @@ import ( "fmt" "path/filepath" - sessionfeature "github.com/GrayCodeAI/rho/internal/features/session" - "github.com/GrayCodeAI/rho/internal/storage" + sessionfeature "github.com/GrayCodeAI/decoder/internal/features/session" + "github.com/GrayCodeAI/decoder/internal/storage" ) func chatExportRequest(m *chatModel, format string, redact bool) (sessionfeature.ExportRequest, error) { diff --git a/cmd/chat_focus.go b/cmd/chat_focus.go index 5d03eff29..f2863d613 100644 --- a/cmd/chat_focus.go +++ b/cmd/chat_focus.go @@ -9,9 +9,9 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/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 b48c93b1b..4c01e1189 100644 --- a/cmd/chat_history_search.go +++ b/cmd/chat_history_search.go @@ -4,9 +4,9 @@ import ( "strconv" "strings" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" - "github.com/GrayCodeAI/rho/internal/textutil" - "github.com/GrayCodeAI/rho/internal/ui/icons" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" + "github.com/GrayCodeAI/decoder/internal/textutil" + "github.com/GrayCodeAI/decoder/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 647f5a6dc..bd6060102 100644 --- a/cmd/chat_journey_e2e_test.go +++ b/cmd/chat_journey_e2e_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() if err := store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890"); err != nil { t.Fatal(err) } - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() m := newTestChatModel() diff --git a/cmd/chat_layout_mouse_test.go b/cmd/chat_layout_mouse_test.go index 00e5ba0f2..6a7eb148a 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: "RHO LOGO\nv0.1.0", + welcomeCache: "DECODER 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 95b0582cd..7abbf0961 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: "RHO LOGO\nv0.1.0", + welcomeCache: "DECODER 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, "RHO LOGO") { + if !strings.Contains(got, "DECODER 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: "RHO LOGO\nv0.1.0", + welcomeCache: "DECODER 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(), "RHO LOGO") { + if strings.Contains(m.viewport.View(), "DECODER LOGO") { t.Fatal("expected empty initial viewport before priming") } m.primeInitialViewportContent() - if !strings.Contains(m.viewport.View(), "RHO LOGO") { + if !strings.Contains(m.viewport.View(), "DECODER 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 0f9e9e21b..76f2e45d7 100644 --- a/cmd/chat_model.go +++ b/cmd/chat_model.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" "charm.land/bubbles/v2/spinner" "charm.land/bubbles/v2/textarea" @@ -16,15 +16,15 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "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/features/shellmode" - "github.com/GrayCodeAI/rho/internal/features/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" + "github.com/GrayCodeAI/decoder/internal/bridge/sessioncapture" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/features/taste" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/system/staleness" + "github.com/GrayCodeAI/decoder/internal/tool" ) // sessionWAL is the durability surface the chat model needs from its @@ -56,10 +56,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 rho crashes. Check disk space and permissions." + m.durabilityWarning = "Warning: session persistence is failing — recent messages may be lost if decoder crashes. Check disk space and permissions." } -// All rho color/icon/glyph constants live in theme.go. This file holds +// All decoder 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. @@ -75,8 +75,8 @@ var ( slashCmdStyle = lipgloss.NewStyle().Foreground(textDisabled) slashDescStyle = lipgloss.NewStyle().Foreground(textDisabled) - slashSelCmdStyle = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) - slashSelDescStyle = lipgloss.NewStyle().Foreground(rhoColor) + slashSelCmdStyle = lipgloss.NewStyle().Foreground(decoderColor).Bold(true) + slashSelDescStyle = lipgloss.NewStyle().Foreground(decoderColor) inputBorderStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false, true, false).BorderForeground(borderDim) // Backwards-compatible alias for callers that still use the old name. @@ -88,13 +88,13 @@ func ghostHintStyle() lipgloss.Style { return lipgloss.NewStyle().Foreground(textDisabled).Italic(true) } -// rhoSpinnerFrames feeds the bubbles spinner (matches BrailleSpinner default). -var rhoSpinnerFrames = rhoSpinnerGlyphs +// decoderSpinnerFrames feeds the bubbles spinner (matches BrailleSpinner default). +var decoderSpinnerFrames = decoderSpinnerGlyphs -// rhoSpinnerFrameInterval — compass frame cadence. -const rhoSpinnerFrameInterval = 80 * time.Millisecond +// decoderSpinnerFrameInterval — compass frame cadence. +const decoderSpinnerFrameInterval = 80 * time.Millisecond -// Spinner verbs (from rho-archive) — picked randomly per session +// Spinner verbs (from decoder-archive) — picked randomly per session var spinnerVerbs = []string{ "Abstracting", "Architecting", "Brewing", "Calculating", "Cogitating", "Compiling", "Computing", "Conjuring", "Contemplating", "Cooking", @@ -154,7 +154,7 @@ type ( statusLeftBranch string connStatusVal string connStatusKey string - welcomeSetup rhoconfig.SetupState + welcomeSetup decoderconfig.SetupState welcomeAgentsOK bool } processArrowTickMsg struct { @@ -219,7 +219,7 @@ type chatModel struct { viewport viewport.Model session *engine.Session registry *tool.Registry - settings rhoconfig.Settings + settings decoderconfig.Settings ref *progRef cancel context.CancelFunc // cancel current stream sessionID string @@ -257,7 +257,7 @@ type chatModel struct { width int height int quitting bool - mascotEnabled bool // inline RHO mascot is available in the active terminal + mascotEnabled bool // inline DECODER mascot is available in the active terminal blinkClosed bool eyeFrame int slashSel int @@ -270,7 +270,7 @@ type chatModel struct { configNotice string configEntry string // configEntryNone, configEntryAPIKeyPaste, configEntryOllamaURL configProvider string // e.g. configProviderOllama while entry overlay is open - configModelOptions []configModelOption // labels + ids from flux catalog + configModelOptions []configModelOption // labels + ids from mux catalog configModelProvider string // filter models after API key paste configModelSearch string // active model filter query configModelSearchActive bool // typing into model search input @@ -323,7 +323,7 @@ type chatModel struct { sessionBootstrapDone bool toolStartTime time.Time welcomeCache string - welcomeSetupState rhoconfig.SetupState + welcomeSetupState decoderconfig.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 8f1b86897..78619099c 100644 --- a/cmd/chat_model_test.go +++ b/cmd/chat_model_test.go @@ -11,15 +11,15 @@ import ( "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/viewport" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/rho/internal/bridge/sessioncapture" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" - "github.com/GrayCodeAI/rho/internal/features/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" + "github.com/GrayCodeAI/decoder/internal/bridge/sessioncapture" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/tool" ) func newTestChatModel() *chatModel { @@ -44,7 +44,7 @@ func newTestChatModel() *chatModel { hintsLoader: engine.NewHintsLoader(), selfImprover: engine.NewSelfImprover(), codingSoul: engine.LoadCodingSoul(), - brailleSpinner: NewBrailleSpinner(SpinnerRho, "Thinking"), + brailleSpinner: NewBrailleSpinner(SpinnerDecoder, "Thinking"), testStreamStarter: func() {}, } return m @@ -63,12 +63,12 @@ func isolateChatCommandSweepEnv(t *testing.T) { root := t.TempDir() storage.SetTestDirs(t, root) isolateCredentialHome(t) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() gateway.SetDefaultStore(&gateway.MapStore{}) restoreThemeGlobals(t) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) } @@ -78,7 +78,7 @@ func isolateChatCommandSweepEnv(t *testing.T) { // (e.g. TestAdaptiveNeutralsPreserveDarkAppearance). func restoreThemeGlobals(t *testing.T) { t.Helper() - savedRho, savedSuccess, savedWarn, savedErr, savedInfo := rhoColor, successTeal, warnAmber, errorCoral, infoSky + savedDecoder, savedSuccess, savedWarn, savedErr, savedInfo := decoderColor, successTeal, warnAmber, errorCoral, infoSky savedTool, savedAgent, savedDone, savedFileHeader := toolGold, agentGold, doneGreen, fileHeaderBlue savedInspect, savedEdit, savedRun, savedTrust := tierInspect, tierEdit, tierRun, tierTrust savedHudBorder, savedHudLabel := hudBorderPurple, hudLabelPink @@ -100,7 +100,7 @@ func restoreThemeGlobals(t *testing.T) { savedMinimalChrome := minimalChrome hasDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) t.Cleanup(func() { - rhoColor, successTeal, warnAmber, errorCoral, infoSky = savedRho, savedSuccess, savedWarn, savedErr, savedInfo + decoderColor, successTeal, warnAmber, errorCoral, infoSky = savedDecoder, savedSuccess, savedWarn, savedErr, savedInfo toolGold, agentGold, doneGreen, fileHeaderBlue = savedTool, savedAgent, savedDone, savedFileHeader tierInspect, tierEdit, tierRun, tierTrust = savedInspect, savedEdit, savedRun, savedTrust hudBorderPurple, hudLabelPink = savedHudBorder, savedHudLabel @@ -170,7 +170,7 @@ func TestChatModel_SlashClear(t *testing.T) { func TestFormatQuitResumeMessage(t *testing.T) { got := formatQuitResumeMessage("44418bdd52745678") - want := "Thank you for using Rho!\n\nTo resume this session, run: rho --resume 44418bdd52745678\n" + want := "Thank you for using Decoder!\n\nTo resume this session, run: decoder --resume 44418bdd52745678\n" if got != want { t.Fatalf("quit message mismatch:\nwant %q\ngot %q", want, got) } @@ -178,7 +178,7 @@ func TestFormatQuitResumeMessage(t *testing.T) { func TestFormatQuitResumeMessage_NoSession(t *testing.T) { got := formatQuitResumeMessage("") - want := "Thank you for using Rho!\n" + want := "Thank you for using Decoder!\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 4f403a81b..1de5746e7 100644 --- a/cmd/chat_mouse.go +++ b/cmd/chat_mouse.go @@ -5,11 +5,11 @@ import ( "os" "strings" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func mouseEnabledFromEnv() bool { - v := strings.TrimSpace(os.Getenv("RHO_MOUSE")) + v := strings.TrimSpace(os.Getenv("DECODER_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("RHO_MOUSE")) + v := strings.TrimSpace(os.Getenv("DECODER_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: RHO_MOUSE env → runtime override → settings → default on. +// "mouse": false). Priority: DECODER_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 = "RHO_MOUSE env" + source = "DECODER_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 RHO_MOUSE env in this session. " + + content: "Mouse is controlled by DECODER_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) - _ = rhoconfig.SetGlobalSetting("tui_mouse", "true") + _ = decoderconfig.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) - _ = rhoconfig.SetGlobalSetting("tui_mouse", "false") + _ = decoderconfig.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." } - _ = rhoconfig.SetGlobalSetting("tui_mouse", val) + _ = decoderconfig.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 e90bfea30..21f37f5b6 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func configureReadyChatState(t *testing.T) { t.Helper() isolateChatCommandSweepEnv(t) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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 := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + if err := decoderconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := decoderconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } - rhoconfig.InvalidateConfigUICache() - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.InvalidateConfigUICache() + decoderconfig.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 ad9e595a0..b61d340bc 100644 --- a/cmd/chat_permission_keys_test.go +++ b/cmd/chat_permission_keys_test.go @@ -6,13 +6,13 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/permissions" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" tea "charm.land/bubbletea/v2" - contracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/policy" ) func TestHighRiskApprovalUsesKeyboardActions(t *testing.T) { diff --git a/cmd/chat_platform_ctx.go b/cmd/chat_platform_ctx.go index 008307a18..07230d925 100644 --- a/cmd/chat_platform_ctx.go +++ b/cmd/chat_platform_ctx.go @@ -8,7 +8,7 @@ import ( "time" tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) var platformCtxCache struct { @@ -51,7 +51,7 @@ type platformContextIndexMsg struct { func fetchPlatformContextIndexCmd() tea.Cmd { return func() tea.Msg { - models, err := rhoconfig.ListPublicEngineModels(context.Background(), "xiaomi_mimo_payg") + models, err := decoderconfig.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 09521cf6f..c5e60171c 100644 --- a/cmd/chat_print.go +++ b/cmd/chat_print.go @@ -14,13 +14,13 @@ import ( "unicode/utf8" lipgloss "charm.land/lipgloss/v2" - 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" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" - "github.com/GrayCodeAI/rho/internal/observability/logger" - "github.com/GrayCodeAI/rho/internal/session" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + aiwatch "github.com/GrayCodeAI/decoder/internal/engine/io" + "github.com/GrayCodeAI/decoder/internal/engine/lifecycle" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + "github.com/GrayCodeAI/decoder/internal/session" ) // Print mode and session persistence functions extracted from chat.go @@ -41,7 +41,7 @@ func runPrint(text string) error { return err } - sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) + sess, cfgErr := newConfiguredDecoderSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) if cfgErr != nil { return cfgErr } @@ -138,7 +138,7 @@ func runPrint(text string) error { writePrintResult(printed.String(), sessionID, sess, false, nil) } if !noSessionPersistence { - saveFluxSession(sessionID, sess) + saveMuxSession(sessionID, sess) } return nil } @@ -156,7 +156,7 @@ func runPrint(text string) error { writePrintResult(printed.String(), sessionID, sess, false, nil) } if !noSessionPersistence { - saveFluxSession(sessionID, sess) + saveMuxSession(sessionID, sess) } return nil } @@ -206,7 +206,7 @@ func printTextUsageFooter(usage *engine.StreamUsage, started time.Time, turns in // 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, "# decoder transcript\n") fmt.Fprintf(&b, "session: %s\n", sessionID) if model != "" { fmt.Fprintf(&b, "model: %s\n", model) @@ -312,7 +312,7 @@ func writePrintEvent(sessionID, eventType, content, toolName string) { fmt.Println(string(data)) } -func saveFluxSession(id string, sess *engine.Session) { +func saveMuxSession(id string, sess *engine.Session) { raw := sess.RawMessages() if len(raw) == 0 { return @@ -328,7 +328,7 @@ func saveFluxSession(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("Rho REPL", textPrimary)+auditTint(" — type 'exit' or 'quit' to leave, 'help' for commands", textMuted)) + fmt.Fprintln(os.Stderr, auditTint("Decoder REPL", textPrimary)+auditTint(" — type 'exit' or 'quit' to leave, 'help' for commands", textMuted)) fmt.Fprintln(os.Stderr) systemPrompt, err := buildSystemPrompt() @@ -347,7 +347,7 @@ func runRepl() error { return err } - sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) + sess, cfgErr := newConfiguredDecoderSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error)) if cfgErr != nil { return cfgErr } @@ -482,7 +482,7 @@ func runRepl() error { writePrintResult(printed.String(), sessionID, sess, false, nil) } if !noSessionPersistence { - saveFluxSession(sessionID, sess) + saveMuxSession(sessionID, sess) } } } @@ -492,7 +492,7 @@ func runRepl() error { } } -func replBuiltinResponse(input string, sess *engine.Session, settings rhoconfig.Settings, sessionID string) (string, bool, error) { +func replBuiltinResponse(input string, sess *engine.Session, settings decoderconfig.Settings, sessionID string) (string, bool, error) { switch strings.TrimSpace(input) { case "/tools": return builtInToolsSummary(), true, nil @@ -511,12 +511,12 @@ func replBuiltinResponse(input string, sess *engine.Session, settings rhoconfig. } } -func replModelsSummary(settings rhoconfig.Settings, sessionProvider string) (string, bool, error) { +func replModelsSummary(settings decoderconfig.Settings, sessionProvider string) (string, bool, error) { providerName := effectiveProviderForREPL(settings, sessionProvider) if providerName == "" { - return "No active provider selected. Set one with `rho config provider ` or start REPL with `--provider`.", true, nil + return "No active provider selected. Set one with `decoder config provider ` or start REPL with `--provider`.", true, nil } - models, err := rhoconfig.FetchModelsForProvider(providerName) + models, err := decoderconfig.FetchModelsForProvider(providerName) if err != nil { return "", true, err } @@ -542,7 +542,7 @@ func replModelsSummary(settings rhoconfig.Settings, sessionProvider string) (str return b.String(), true, nil } -func effectiveProviderForREPL(settings rhoconfig.Settings, sessionProvider string) string { +func effectiveProviderForREPL(settings decoderconfig.Settings, sessionProvider string) string { if provider != "" { return strings.TrimSpace(provider) } @@ -567,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__", ".rho"} +var watchIgnoreDirs = []string{".git", "node_modules", "vendor", "__pycache__", ".decoder"} // 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 df0b0f683..a0fe83072 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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, "rho-pro") + }, started, 3, "decoder-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, "rho-pro") { + if !strings.Contains(got, "decoder-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, "rho-pro") { + if strings.Contains(got, "decoder-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 fbc198ebd..41cd2dbae 100644 --- a/cmd/chat_prompt_timeout_test.go +++ b/cmd/chat_prompt_timeout_test.go @@ -5,11 +5,11 @@ import ( "testing" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/tool" - contracts "github.com/GrayCodeAI/rho/internal/contracts/policy" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/policy" ) func TestPermissionPromptTimeoutClearsStaleState(t *testing.T) { @@ -236,7 +236,7 @@ func TestClearInteractivePromptsFailsClosedImmediately(t *testing.T) { m.credentialQueue = []credentialAskMsg{{response: make(chan tool.CredentialResponse, 1)}} m.permReq = &safety.PermissionRequest{Response: permissionResponse} - m.clearInteractivePrompts("rho is exiting") + m.clearInteractivePrompts("decoder is exiting") if m.askReq != nil || m.credentialReq != nil || m.permReq != nil || len(m.askQueue) != 0 || len(m.credentialQueue) != 0 { t.Fatal("interactive prompts were not fully cleared") @@ -248,7 +248,7 @@ func TestClearInteractivePromptsFailsClosedImmediately(t *testing.T) { } select { case response := <-credentialResponse: - if response.Approved || response.Reason != "rho is exiting" { + if response.Approved || response.Reason != "decoder is exiting" { t.Fatalf("unexpected credential shutdown response: %+v", response) } default: diff --git a/cmd/chat_scrollbar.go b/cmd/chat_scrollbar.go index ec4003621..ae818265b 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. func scrollbarThumbStyle() lipgloss.Style { - return lipgloss.NewStyle().Foreground(rhoColor) + return lipgloss.NewStyle().Foreground(decoderColor) } func scrollbarTrackStyle() lipgloss.Style { diff --git a/cmd/chat_select.go b/cmd/chat_select.go index 390eaac45..cf286c6bd 100644 --- a/cmd/chat_select.go +++ b/cmd/chat_select.go @@ -9,7 +9,7 @@ import ( tea "charm.land/bubbletea/v2" "golang.org/x/term" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" ) // selectionResumedMsg is delivered to the chat model after the user finishes a @@ -57,7 +57,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 rho.") + fmt.Fprintln(os.Stderr, " Press any key to return to decoder.") fmt.Fprintln(os.Stderr, "────────────────────────────────────────────────────────────") fmt.Fprintln(os.Stderr, "") // Block on stdin in raw mode so any single keypress resumes the diff --git a/cmd/chat_session_picker.go b/cmd/chat_session_picker.go index 8144fa01b..abf8556af 100644 --- a/cmd/chat_session_picker.go +++ b/cmd/chat_session_picker.go @@ -7,11 +7,11 @@ import ( "unicode/utf8" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/mattn/go-runewidth" - sessionfeature "github.com/GrayCodeAI/rho/internal/features/session" - "github.com/GrayCodeAI/rho/internal/session" + sessionfeature "github.com/GrayCodeAI/decoder/internal/features/session" + "github.com/GrayCodeAI/decoder/internal/session" ) // applySessionPickerFilter filters the session entries based on the current search query. diff --git a/cmd/chat_status.go b/cmd/chat_status.go index 3bc1fdfb8..62fa54b1b 100644 --- a/cmd/chat_status.go +++ b/cmd/chat_status.go @@ -8,8 +8,8 @@ import ( lipgloss "charm.land/lipgloss/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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(rhoconfig.ConfiguredCredentialProviders(), ",") + creds := strings.Join(decoderconfig.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(rhoconfig.ProviderOfModel(model)) + gateway = strings.TrimSpace(decoderconfig.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 !rhoconfig.CredentialSnapshotReady() { + if !decoderconfig.CredentialSnapshotReady() { return "" } - if !rhoconfig.HasConfiguredDeploymentCached(ctx) { + if !decoderconfig.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 = rhoconfig.GatewayDisplayName(gw) + gateway = decoderconfig.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 !rhoconfig.CredentialSnapshotReady() { + if !decoderconfig.CredentialSnapshotReady() { return "", 0, "", 0 } - if !rhoconfig.HasConfiguredDeploymentCached(ctx) { + if !decoderconfig.HasConfiguredDeploymentCached(ctx) { return "", 0, "", 0 } diff --git a/cmd/chat_status_metadata_test.go b/cmd/chat_status_metadata_test.go index ce33becea..d8da3e507 100644 --- a/cmd/chat_status_metadata_test.go +++ b/cmd/chat_status_metadata_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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 := rhoconfig.GatewayDisplayName("xiaomi-mimo-token-plan"); got != "Xiaomi MiMo — Token Plan" { + if got := decoderconfig.GatewayDisplayName("xiaomi-mimo-token-plan"); got != "Xiaomi MiMo — Token Plan" { t.Fatalf("GatewayDisplayName(hyphen) = %q, want nice name", got) } - if got := rhoconfig.GatewayDisplayName("xiaomi_mimo_token_plan"); got != "Xiaomi MiMo — Token Plan" { + if got := decoderconfig.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 5443490a4..6c9bd1543 100644 --- a/cmd/chat_status_test.go +++ b/cmd/chat_status_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" "github.com/charmbracelet/x/ansi" ) @@ -91,23 +91,23 @@ func TestFormatContextUsedLabel(t *testing.T) { } func TestChatConnectionStatus_WithModel(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.SetActiveProvider(ctx, "openrouter") - _ = rhoconfig.SetActiveModel(ctx, "moonshotai/kimi-k2.6") - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.SetActiveProvider(ctx, "openrouter") + _ = decoderconfig.SetActiveModel(ctx, "moonshotai/kimi-k2.6") + decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.ClearActiveSelection(ctx) - _ = rhoconfig.SetActiveProvider(ctx, "openrouter") - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.ClearActiveSelection(ctx) + _ = decoderconfig.SetActiveProvider(ctx, "openrouter") + decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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) } - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.ClearActiveSelection(ctx) - rhoconfig.RefreshConfigCredSnapshot(ctx) + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.ClearActiveSelection(ctx) + decoderconfig.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: rhoconfig.SetupState{NeedsSetup: true}, + welcomeSetup: decoderconfig.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, rhoconfig.Settings{}, 0, false, 80, 24) + msg := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.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 f276753d3..29d3cb98f 100644 --- a/cmd/chat_stream.go +++ b/cmd/chat_stream.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/engine" - chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" + "github.com/GrayCodeAI/decoder/internal/engine" + chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" ) // Streaming and prompt command functions extracted from chat.go diff --git a/cmd/chat_subcommand.go b/cmd/chat_subcommand.go index ede02d5a1..b9828aeea 100644 --- a/cmd/chat_subcommand.go +++ b/cmd/chat_subcommand.go @@ -6,7 +6,7 @@ import ( "sync" tea "charm.land/bubbletea/v2" - commandfeature "github.com/GrayCodeAI/rho/internal/features/commands" + commandfeature "github.com/GrayCodeAI/decoder/internal/features/commands" ) // ChatSubcommand is a single slash-command handler. Implementations live in diff --git a/cmd/chat_subcommand_brainstorm.go b/cmd/chat_subcommand_brainstorm.go index 2585d8cdd..9349d48ae 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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 58f32103c..c1a00430d 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/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) -// branchAgentSubcommand creates a rho/agent-* branch from main/master. +// branchAgentSubcommand creates a decoder/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 rho/agent-* branch if on main/master" + return "create decoder/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 c135c512e..a398adb12 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) // checkpointSubcommand implements the /checkpoint slash command. diff --git a/cmd/chat_subcommand_context.go b/cmd/chat_subcommand_context.go index db9f862ad..b07fb45a5 100644 --- a/cmd/chat_subcommand_context.go +++ b/cmd/chat_subcommand_context.go @@ -6,8 +6,8 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine/project" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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: rhoconfig.BuildContextWithDirs(addDirs)}) + m.messages = append(m.messages, displayMsg{role: "system", content: decoderconfig.BuildContextWithDirs(addDirs)}) return m, nil } diff --git a/cmd/chat_subcommand_council.go b/cmd/chat_subcommand_council.go index ff9b4a21a..8347bc307 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/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/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 16ad6240b..65347b3ad 100644 --- a/cmd/chat_subcommand_ecosystem.go +++ b/cmd/chat_subcommand_ecosystem.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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: rhoconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)}) + m.messages = append(m.messages, displayMsg{role: "system", content: decoderconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)}) return m, nil } diff --git a/cmd/chat_subcommand_harness.go b/cmd/chat_subcommand_harness.go index 40c2a6d13..1d11e4f06 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/rho/internal/harness" + "github.com/GrayCodeAI/decoder/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 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.", + prompt := fmt.Sprintf("I ran a Decoder 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_help.go b/cmd/chat_subcommand_help.go index 3f5952231..66e0a889c 100644 --- a/cmd/chat_subcommand_help.go +++ b/cmd/chat_subcommand_help.go @@ -6,7 +6,7 @@ import ( "strings" tea "charm.land/bubbletea/v2" - commandfeature "github.com/GrayCodeAI/rho/internal/features/commands" + commandfeature "github.com/GrayCodeAI/decoder/internal/features/commands" ) // helpCategory returns the display category for a slash command name, diff --git a/cmd/chat_subcommand_investigate.go b/cmd/chat_subcommand_investigate.go index 971a14946..f843f03e1 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) // investigateSubcommand implements the /investigate slash command. diff --git a/cmd/chat_subcommand_login.go b/cmd/chat_subcommand_login.go index 2cc4b534d..94232850a 100644 --- a/cmd/chat_subcommand_login.go +++ b/cmd/chat_subcommand_login.go @@ -7,7 +7,7 @@ import ( ) // loginSubcommand implements the /login slash command. Tau uses /login as its -// provider-auth entry point; Rho routes it to the existing config panel, which +// provider-auth entry point; Decoder 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 diff --git a/cmd/chat_subcommand_memory.go b/cmd/chat_subcommand_memory.go index e13fbd2f7..8b892ad30 100644 --- a/cmd/chat_subcommand_memory.go +++ b/cmd/chat_subcommand_memory.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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(rhoconfig.LoadAgentsMD()) + md := strings.TrimSpace(decoderconfig.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 e1d78eff2..082be38a9 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/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" ) // modeSubcommand implements the /mode slash command. @@ -41,7 +41,7 @@ func (mo *modeSubcommand) Handle(m *chatModel, args []string, text string) (tea. } visible := 0 if m.session != nil && m.session.Tools() != nil && m.session.Tools().Registry() != nil { - visible = len(m.session.Tools().Registry().FluxTools()) + visible = len(m.session.Tools().Registry().MuxTools()) } m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf( "Work mode: %s (plan | act | review)\nShell mode: %s (auto | shell | agent)\nTrust: %s\nAuto-commit: %s\nTools visible: %d\n\n/start · /trust · /branch-agent · /auto-commit", diff --git a/cmd/chat_subcommand_model.go b/cmd/chat_subcommand_model.go index 80a6e0c72..ce9c4ea80 100644 --- a/cmd/chat_subcommand_model.go +++ b/cmd/chat_subcommand_model.go @@ -6,7 +6,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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 rhoconfig.DeploymentRoutingEnabled(m.settings) { - arg = rhoconfig.ResolveCanonicalModel(arg) + if decoderconfig.DeploymentRoutingEnabled(m.settings) { + arg = decoderconfig.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 := rhoconfig.SetGlobalSetting("model", arg); err != nil { + if err := decoderconfig.SetGlobalSetting("model", arg); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) return m, nil } @@ -81,15 +81,15 @@ func (mo *modelSubcommand) Handle(m *chatModel, args []string, text string) (tea if m.session != nil { provider = m.session.Provider() } - m.session.SetThinkingEnabled(rhoconfig.ResolveThinkingForModel(rhoconfig.LoadSettings(), arg, provider)) + m.session.SetThinkingEnabled(decoderconfig.ResolveThinkingForModel(decoderconfig.LoadSettings(), arg, provider)) } - thinkLabel := rhoconfig.FormatModelThinkingLabel( - selected != nil && rhoconfig.ModelCapabilitySupportsThinking(selected.Capabilities), - rhoconfig.ThinkingPrefForModel(rhoconfig.LoadSettings(), arg), + thinkLabel := decoderconfig.FormatModelThinkingLabel( + selected != nil && decoderconfig.ModelCapabilitySupportsThinking(selected.Capabilities), + decoderconfig.ThinkingPrefForModel(decoderconfig.LoadSettings(), arg), m.session.Provider(), ) m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf( - "Model switched: %s → %s (Think: %s)\nConversation history preserved (%d messages); new requests use the new model.\nSaved in flux (provider.json). Use /model and press t to toggle Think.", + "Model switched: %s → %s (Think: %s)\nConversation history preserved (%d messages); new requests use the new model.\nSaved in mux (provider.json). Use /model and press t to toggle Think.", prevModel, m.session.Model(), thinkLabel, msgCount, )}) return m, nil diff --git a/cmd/chat_subcommand_party.go b/cmd/chat_subcommand_party.go index 4aa0c8347..77e853a23 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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 94d4c6f35..eed5b831e 100644 --- a/cmd/chat_subcommand_path.go +++ b/cmd/chat_subcommand_path.go @@ -5,7 +5,7 @@ import ( tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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: rhoconfig.FormatDeveloperPathReport(context.Background())}) + m.messages = append(m.messages, displayMsg{role: "system", content: decoderconfig.FormatDeveloperPathReport(context.Background())}) return m, nil } diff --git a/cmd/chat_subcommand_recipe.go b/cmd/chat_subcommand_recipe.go index 3ccd5bf80..043e9f0e2 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/rho/internal/recipe" + "github.com/GrayCodeAI/decoder/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 Rho user state or .agents/recipes/"}) + m.messages = append(m.messages, displayMsg{role: "system", content: "No recipes found in Decoder 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 efdba6c3a..294939b4f 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) // reflectSubcommand implements the /reflect slash command. It diff --git a/cmd/chat_subcommand_render.go b/cmd/chat_subcommand_render.go index 5e5d67bb1..55985445d 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 80c181682..746453f5f 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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 aa10dfbe7..14b697efd 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/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) func TestRunSubcommand_UsesBashToolForSafeCommand(t *testing.T) { diff --git a/cmd/chat_subcommand_simple.go b/cmd/chat_subcommand_simple.go index ba85aa986..f3547189e 100644 --- a/cmd/chat_subcommand_simple.go +++ b/cmd/chat_subcommand_simple.go @@ -11,13 +11,13 @@ import ( tea "charm.land/bubbletea/v2" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + analytics "github.com/GrayCodeAI/decoder/internal/observability" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/theme" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/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 := rhoconfig.LoadGlobalSettings().Theme + current := decoderconfig.LoadGlobalSettings().Theme m.themePicker.OpenWithCurrent(current) m.viewDirty = true m.updateViewportContent() @@ -96,7 +96,7 @@ func init() { } // Inline: /theme themeName := args[0] - if err := rhoconfig.SetGlobalSetting("theme", themeName); err != nil { + if err := decoderconfig.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 := rhoconfig.SetGlobalSetting("agentColor", args[0]); err != nil { + if err := decoderconfig.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 := rhoconfig.ActiveModel(context.Background()) + savedModel := decoderconfig.ActiveModel(context.Background()) if m.session.Model() == savedModel { providerName := strings.TrimSpace(m.session.Provider()) - fastModel := rhoconfig.CheapestModelForProvider(providerName, m.session.Model()) + fastModel := decoderconfig.CheapestModelForProvider(providerName, m.session.Model()) if strings.TrimSpace(fastModel) == "" { - fastModel = rhoconfig.DefaultModelForProvider(providerName) + fastModel = decoderconfig.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": - _ = rhoconfig.SetGlobalSetting("reasoningEffort", level) + _ = decoderconfig.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"}) @@ -321,14 +321,14 @@ func init() { description: "toggle compact mode (removes outer padding)", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - settings := rhoconfig.LoadGlobalSettings() + settings := decoderconfig.LoadGlobalSettings() current := settings.CompactMode newVal := !current valStr := "false" if newVal { valStr = "true" } - if err := rhoconfig.SetGlobalSetting("compact_mode", valStr); err != nil { + if err := decoderconfig.SetGlobalSetting("compact_mode", valStr); err != nil { m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()}) } else { state := "disabled" @@ -350,7 +350,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", rhoconfig.LoadGlobalSettings().ScrollSpeed)}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Usage: /scroll-speed <1-100>\nCurrent: %d", decoderconfig.LoadGlobalSettings().ScrollSpeed)}) return m, nil } speed, err := strconv.Atoi(args[0]) @@ -358,7 +358,7 @@ func init() { m.messages = append(m.messages, displayMsg{role: "error", content: "Scroll speed must be 1-100"}) return m, nil } - if err := rhoconfig.SetGlobalSetting("scroll_speed", args[0]); err != nil { + if err := decoderconfig.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]}) @@ -373,7 +373,7 @@ func init() { description: "toggle natural scrolling (invert direction)", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - settings := rhoconfig.LoadGlobalSettings() + settings := decoderconfig.LoadGlobalSettings() current := settings.InvertScroll newVal := !current valStr := "false" @@ -384,7 +384,7 @@ func init() { if newVal { enabled = "enabled" } - if err := rhoconfig.SetGlobalSetting("invert_scroll", valStr); err != nil { + if err := decoderconfig.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}) @@ -400,13 +400,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", rhoconfig.LoadGlobalSettings().ScrollMode)}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Usage: /scroll-mode \nCurrent: %s", decoderconfig.LoadGlobalSettings().ScrollMode)}) return m, nil } mode := strings.ToLower(args[0]) switch mode { case "auto", "wheel", "trackpad": - if err := rhoconfig.SetGlobalSetting("scrollmode", mode); err != nil { + if err := decoderconfig.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)}) @@ -445,7 +445,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, rhoconfig.LoadGlobalSettings().ScrollMode, rhoconfig.LoadGlobalSettings().ScrollSpeed, rhoconfig.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, decoderconfig.LoadGlobalSettings().ScrollMode, decoderconfig.LoadGlobalSettings().ScrollSpeed, decoderconfig.LoadGlobalSettings().CompactMode)}) return m, nil }, }) @@ -457,7 +457,7 @@ func init() { usage: "/pager-config ", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { if len(args) < 2 { - s := rhoconfig.LoadGlobalSettings() + s := decoderconfig.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 { @@ -475,7 +475,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 := rhoconfig.SetGlobalSetting("paginatorlines", value); err != nil { + if err := decoderconfig.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)}) @@ -484,13 +484,13 @@ func init() { case "linenumbers", "linenums", "ln": switch strings.ToLower(value) { case "1", "true", "yes", "on": - if err := rhoconfig.SetGlobalSetting("paginatorshowlinenumbers", "true"); err != nil { + if err := decoderconfig.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 := rhoconfig.SetGlobalSetting("paginatorshowlinenumbers", "false"); err != nil { + if err := decoderconfig.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"}) @@ -531,10 +531,10 @@ func init() { // /upgrade — check for updates subcommandRegistry.Register(&delegatingCommand{ name: "upgrade", - description: "check for rho updates", + description: "check for decoder updates", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - return m.startPromptCommand("/upgrade", "Check for rho updates and show the latest available version.") + return m.startPromptCommand("/upgrade", "Check for decoder updates and show the latest available version.") }, }) @@ -784,7 +784,7 @@ func init() { style := strings.ToLower(args[0]) switch style { case "concise", "normal", "detailed": - _ = rhoconfig.SetGlobalSetting("outputStyle", style) + _ = decoderconfig.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"}) @@ -929,15 +929,15 @@ func init() { }, }) - // /feedback — submit feedback saved to Rho user state. + // /feedback — submit feedback saved to Decoder user state. subcommandRegistry.Register(&delegatingCommand{ name: "feedback", - description: "submit feedback (saved to Rho user state)", + description: "submit feedback (saved to Decoder 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 Rho user state."}) + m.messages = append(m.messages, displayMsg{role: "system", content: "Usage: /feedback \nCaptures session context and saves feedback to Decoder user state."}) return m, nil } feedDir := filepath.Join(storage.StateDir(), "feedback") @@ -1042,7 +1042,7 @@ func init() { description: "show provider deployment status", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - report, err := rhoconfig.DeploymentStatusReport(context.Background(), m.session.Model()) + report, err := decoderconfig.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 @@ -1058,7 +1058,7 @@ func init() { description: "refresh the model catalog", usage: "", handler: func(m *chatModel, args []string, text string) (tea.Model, tea.Cmd) { - summary, err := rhoconfig.RefreshModelCatalogV1(context.Background()) + summary, err := decoderconfig.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 333d6c01e..70d6a27bb 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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 185948c64..cf72b8efb 100644 --- a/cmd/chat_subcommand_spec.go +++ b/cmd/chat_subcommand_spec.go @@ -4,11 +4,11 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/spec" + "github.com/GrayCodeAI/decoder/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 fef470b0f..5f62eecc8 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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("## Rho quick start\n\n") + b.WriteString("## Decoder 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 73e5581c1..320385a97 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/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // statusSubcommand implements the /status slash command. It prints @@ -35,7 +35,7 @@ func buildStatusInfo(m *chatModel) string { visible := 0 if m.registry != nil { toolCount = len(m.registry.PrimaryTools()) - visible = len(m.registry.FluxTools()) + visible = len(m.registry.MuxTools()) } work := m.session.WorkMode() if work == "" { diff --git a/cmd/chat_subcommand_trust.go b/cmd/chat_subcommand_trust.go index 786c3d0ec..2d7dbf1d1 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/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/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 f9eac8efb..f33f8903e 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 rho version. Follows the SubcommandRegistry pattern +// the running decoder 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 rho version" } +func (v *versionSubcommand) Description() string { return "print the running decoder 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("rho v%s", DisplayVersion())}) + m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("decoder v%s", DisplayVersion())}) return m, nil } diff --git a/cmd/chat_subcommand_voice.go b/cmd/chat_subcommand_voice.go index b3cfb62bc..4812deb80 100644 --- a/cmd/chat_subcommand_voice.go +++ b/cmd/chat_subcommand_voice.go @@ -47,19 +47,19 @@ 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(), "rho_voice_input.wav") + tmpFile := filepath.Join(os.TempDir(), "decoder_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 + recordCmd = exec.CommandContext(context.Background(), "sox", "-d", tmpFile, "trim", "0", "10") // #nosec G204 -- fixed command 'sox' resolved via exec.LookPath } else if _, err := exec.LookPath("ffmpeg"); err == nil { - recordCmd = exec.Command("ffmpeg", "-y", "-f", "avfoundation", "-i", ":0", "-t", "10", tmpFile) // #nosec G204 -- fixed command 'ffmpeg' resolved via exec.LookPath + recordCmd = exec.CommandContext(context.Background(), "ffmpeg", "-y", "-f", "avfoundation", "-i", ":0", "-t", "10", tmpFile) // #nosec G204 -- fixed command 'ffmpeg' resolved via exec.LookPath } else { return voiceResultMsg{info: "No audio recorder found. Install sox (brew install sox) or use: whisper --model base -f recording.wav"} } if err := recordCmd.Run(); err != nil { return voiceResultMsg{err: fmt.Sprintf("Recording failed: %v", err)} } - transcribeCmd := exec.Command("whisper", "--model", "base", "--output_format", "txt", "--output_dir", os.TempDir(), tmpFile) // #nosec G204 -- fixed command 'whisper' with internal args + transcribeCmd := exec.CommandContext(context.Background(), "whisper", "--model", "base", "--output_format", "txt", "--output_dir", os.TempDir(), tmpFile) // #nosec G204 -- fixed command 'whisper' with internal args if err := transcribeCmd.Run(); err != nil { return voiceResultMsg{err: fmt.Sprintf("Transcription failed: %v", err)} } diff --git a/cmd/chat_submit.go b/cmd/chat_submit.go index eb6a12e41..4ad3d5a8c 100644 --- a/cmd/chat_submit.go +++ b/cmd/chat_submit.go @@ -9,15 +9,15 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/features/shellmode" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // submitUserMessage handles Enter on a non-empty prompt (slash commands, shell, or agent turn). @@ -79,7 +79,7 @@ func (m chatModel) submitUserMessage() (chatModel, tea.Cmd) { } return m, cmd } - if setup := rhoconfig.EvaluateSetupCached(context.Background()); setup.NeedsSetup { + if setup := decoderconfig.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 a9ea61eb6..1c1c10e49 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 Rho process, then + // Explicitly disable any-motion mode left by an older Decoder 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 49315aae9..598af1340 100644 --- a/cmd/chat_tools.go +++ b/cmd/chat_tools.go @@ -6,9 +6,9 @@ import ( "sync" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/lsp" - "github.com/GrayCodeAI/rho/internal/tool" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/lsp" + "github.com/GrayCodeAI/decoder/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 rhoconfig.Settings) []startupMCPServerSpec { +func configuredStartupMCPServers(settings decoderconfig.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 rhoconfig.Settings) []startupMCPServer // 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 rhoconfig.MCPServerConfig) map[string]string { +func mergedMCPHeaders(cfg decoderconfig.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 rhoconfig.Settings) (*tool.Registry, error) { +func defaultRegistry(settings decoderconfig.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 d1984ea55..f6a2fc639 100644 --- a/cmd/chat_tools_lsp_test.go +++ b/cmd/chat_tools_lsp_test.go @@ -3,12 +3,12 @@ package cmd import ( "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/tool" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/tool" ) func TestDefaultRegistryWiresLanguageServerManager(t *testing.T) { - registry, err := defaultRegistry(rhoconfig.Settings{}) + registry, err := defaultRegistry(decoderconfig.Settings{}) if err != nil { t.Fatal(err) } diff --git a/cmd/chat_tools_test.go b/cmd/chat_tools_test.go index 0a9077c78..2a22edb49 100644 --- a/cmd/chat_tools_test.go +++ b/cmd/chat_tools_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/tool" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/tool" ) type registryTestTool struct { @@ -72,8 +72,8 @@ func TestLoadStartupMCPToolSets_UsesTimeoutAndPreservesOrder(t *testing.T) { } func TestConfiguredStartupMCPServers_DispatchesByType(t *testing.T) { - settings := rhoconfig.Settings{ - MCPServers: []rhoconfig.MCPServerConfig{ + settings := decoderconfig.Settings{ + MCPServers: []decoderconfig.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 := rhoconfig.MCPServerConfig{ + cfg := decoderconfig.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(rhoconfig.Settings{ - MCPServers: []rhoconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}, + registry, err := defaultRegistry(decoderconfig.Settings{ + MCPServers: []decoderconfig.MCPServerConfig{{Name: "demo", Command: "demo-mcp"}}, }) if err != nil { t.Fatalf("defaultRegistry returned error: %v", err) diff --git a/cmd/chat_turn.go b/cmd/chat_turn.go index ef2ba8ac5..bcaf20fd0 100644 --- a/cmd/chat_turn.go +++ b/cmd/chat_turn.go @@ -1,10 +1,10 @@ package cmd -import chatfeature "github.com/GrayCodeAI/rho/internal/features/chat" +import chatfeature "github.com/GrayCodeAI/decoder/internal/features/chat" // turnHadThinkingOnly reports whether the latest user turn ended with internal // reasoning visible but no assistant reply or tool activity. This is the TUI -// symptom of flux's ResponseErrorOnlyReasoning health check. +// symptom of mux's ResponseErrorOnlyReasoning health check. func turnHadThinkingOnly(messages []displayMsg) bool { return chatfeature.HadThinkingOnly(toFeatureMessages(messages)) } diff --git a/cmd/chat_update.go b/cmd/chat_update.go index 66137ed63..ba6867be6 100644 --- a/cmd/chat_update.go +++ b/cmd/chat_update.go @@ -7,15 +7,15 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" "charm.land/bubbles/v2/spinner" tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/spec" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/spec" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // This file holds the Bubble Tea event loop for the chat TUI: the central @@ -92,7 +92,7 @@ func shouldReturnToPromptOnType(msg tea.KeyMsg) bool { // background workers (watcher, parallel agents, background tasks), and mark // the model as quitting so the final view can show the resume hint. func (m *chatModel) quitModel() (tea.Model, tea.Cmd) { - m.clearInteractivePrompts("rho is exiting") + m.clearInteractivePrompts("decoder is exiting") if m.cancel != nil { m.cancel() m.cancel = nil @@ -312,7 +312,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 := rhoconfig.SetGlobalSetting("theme", chosen.Name); err != nil { + if err := decoderconfig.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. @@ -736,7 +736,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(rhoconfig.FormatConfigProviderError(msg.provider, msg.err)) + m.configNotice = sanitizeConfigNotice(decoderconfig.FormatConfigProviderError(msg.provider, msg.err)) m.viewDirty = true m.updateViewportContent() } @@ -753,7 +753,7 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.configNotice = "" } } else if m.configOpen { - m.configNotice = rhoconfig.CatalogEmptyHint(context.Background()) + m.configNotice = decoderconfig.CatalogEmptyHint(context.Background()) } if m.session != nil && msg.provider != "" { gw, _ := m.sessionGatewayModel() @@ -944,8 +944,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { line := fmt.Sprintf( "Context compacted (%s): ~%s → ~%s tokens", msg.strategy, - formatRhoTokenCount(msg.tokensBefore), - formatRhoTokenCount(msg.tokensAfter), + formatDecoderTokenCount(msg.tokensBefore), + formatDecoderTokenCount(msg.tokensAfter), ) m.messages = append(m.messages, displayMsg{role: "system", content: line}) m.invalidateConnStatus() diff --git a/cmd/chat_update_overlays_test.go b/cmd/chat_update_overlays_test.go index 10a60b61b..30e2f8bcd 100644 --- a/cmd/chat_update_overlays_test.go +++ b/cmd/chat_update_overlays_test.go @@ -4,7 +4,7 @@ import ( "testing" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/session" ) func TestHistorySearchOverlaySelectsResultAndCloses(t *testing.T) { @@ -30,7 +30,7 @@ func TestHistorySearchOverlaySelectsResultAndCloses(t *testing.T) { func TestSessionPickerOverlayDismissesAndClearsState(t *testing.T) { m := newTestChatModel() m.sessionPickerOpen = true - m.sessionPickerInput = "rho" + m.sessionPickerInput = "decoder" m.sessionPickerSel = 2 m.sessionPickerEntries = make([]session.Entry, 1) m.sessionPickerFiltered = m.sessionPickerEntries diff --git a/cmd/chat_update_prompts.go b/cmd/chat_update_prompts.go index 67662c03a..3a6f0088d 100644 --- a/cmd/chat_update_prompts.go +++ b/cmd/chat_update_prompts.go @@ -6,11 +6,11 @@ import ( "time" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // maxPendingPrompts bounds memory and waiting tool calls when a provider emits @@ -163,7 +163,7 @@ func (m chatModel) handlePermissionAsk(msg permissionAskMsg) (tea.Model, tea.Cmd func (m chatModel) activatePermissionRequest(req safety.PermissionRequest) (tea.Model, tea.Cmd) { // Autonomous mode is policy-driven, not interactive. The safety engine has // already classified this request as requiring a permission callback; at - // this tier the user explicitly chose for rho to resolve it without a + // this tier the user explicitly chose for decoder to resolve it without a // human-in-the-loop card. if m.session != nil && m.session.PermSvc() != nil && m.session.PermSvc().RuntimeState().Autonomy == safety.AutonomyYOLO { resolvePermissionResponse(&req, true) diff --git a/cmd/chat_update_stream.go b/cmd/chat_update_stream.go index 9a6b24835..c4753a171 100644 --- a/cmd/chat_update_stream.go +++ b/cmd/chat_update_stream.go @@ -7,7 +7,7 @@ import ( "unicode/utf8" tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/session" ) // Stream-event handlers are kept separate from the main input router. They @@ -167,7 +167,7 @@ func (m chatModel) handleStreamDone() (tea.Model, tea.Cmd) { } ClearTabProgress() if m.backgrounded && !wasCancelled && hadOutput { - sendTerminalNotification("rho", "Agent turn complete") + sendTerminalNotification("decoder", "Agent turn complete") } m.backgrounded = false m.notifiedComplete = false diff --git a/cmd/chat_view.go b/cmd/chat_view.go index 9f0b956ae..d8ce628dc 100644 --- a/cmd/chat_view.go +++ b/cmd/chat_view.go @@ -12,7 +12,7 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/mattn/go-runewidth" ) @@ -62,11 +62,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 "rho" / "GrayCode AI". +// sanitizeIdentity replaces model self-identifications with "decoder" / "GrayCode AI". func sanitizeIdentity(s string) string { s = reModelName.ReplaceAllStringFunc(s, func(m string) string { parts := reModelName.FindStringSubmatch(m) - return parts[1] + " rho" + return parts[1] + " decoder" }) s = reCreator.ReplaceAllString(s, "${1} by GrayCode AI") return s @@ -552,7 +552,7 @@ func renderPermissionBox(summary string, width int, timeoutAt time.Time) string } } body := lipgloss.JoinVertical(lipgloss.Left, bodyParts...) - options := lipgloss.NewStyle().Foreground(rhoColor).Render(strings.Join([]string{ + options := lipgloss.NewStyle().Foreground(decoderColor).Render(strings.Join([]string{ "[y] allow once [n] deny once", "[s] allow this action for session", "[d] deny this tool for session", @@ -569,7 +569,7 @@ func renderPermissionBox(summary string, width int, timeoutAt time.Time) string func renderApprovalBox(summary string, width int, timeoutAt time.Time) string { title := lipgloss.NewStyle().Foreground(errorCoral).Bold(true).Render(icons.Alert() + " High-risk approval required") body := renderPromptBody(summary, width) - options := lipgloss.NewStyle().Foreground(rhoColor).Render(strings.Join([]string{ + options := lipgloss.NewStyle().Foreground(decoderColor).Render(strings.Join([]string{ "[y] approve once [n] deny once", "[s] approve category for session", "[5] approve next 5 actions", @@ -581,7 +581,7 @@ func renderApprovalBox(summary string, width int, timeoutAt time.Time) string { func renderCredentialBox(summary string, width int, timeoutAt time.Time) string { title := lipgloss.NewStyle().Foreground(warnAmber).Bold(true).Render(icons.Key() + " Credential access required") body := renderPromptBody(summary, width) - options := lipgloss.NewStyle().Foreground(rhoColor).Render("[y] allow access [n] deny access") + options := lipgloss.NewStyle().Foreground(decoderColor).Render("[y] allow access [n] deny access") hint := lipgloss.NewStyle().Foreground(textMuted).Render("Credentials are never shown in the transcript · Esc denies") return renderPromptCard(title, body, options, hint, warnAmber, width, timeoutAt) } @@ -589,7 +589,7 @@ func renderCredentialBox(summary string, width int, timeoutAt time.Time) string func renderQuestionBox(question string, width int, timeoutAt time.Time) string { title := lipgloss.NewStyle().Foreground(successTeal).Bold(true).Render(icons.HelpCircle() + " Input required") body := renderPromptBody(question, width) - options := lipgloss.NewStyle().Foreground(rhoColor).Render("Type your answer · [Enter] submit · [Esc] deny") + options := lipgloss.NewStyle().Foreground(decoderColor).Render("Type your answer · [Enter] submit · [Esc] deny") hint := lipgloss.NewStyle().Foreground(textMuted).Render("No answer is sent if the prompt expires") return renderPromptCard(title, body, options, hint, successTeal, width, timeoutAt) } @@ -709,7 +709,7 @@ func renderReflectionBox(reflection string, width int) string { boxW = 40 } - titleStyle := lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + titleStyle := lipgloss.NewStyle().Foreground(decoderColor).Bold(true) labelStyle := lipgloss.NewStyle().Foreground(infoSky).Bold(true) contentStyle := lipgloss.NewStyle().Foreground(textPrimary) @@ -739,7 +739,7 @@ func renderReflectionBox(reflection string, width int) string { border := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(rhoColor). + BorderForeground(decoderColor). Width(boxW). Padding(0, 1) @@ -778,12 +778,12 @@ func (m *chatModel) renderTokenCounters() string { var b strings.Builder b.WriteString(ansiMagenta + ansiBold + "↓" + ansiReset) b.WriteString(ansiMagenta) - b.WriteString(formatRhoTokenCount(outTok)) + b.WriteString(formatDecoderTokenCount(outTok)) b.WriteString(ansiReset) b.WriteString(ansiDim + " " + ansiReset) b.WriteString(ansiCyan + ansiBold + "↑" + ansiReset) b.WriteString(ansiCyan) - b.WriteString(formatRhoTokenCount(inTok)) + b.WriteString(formatDecoderTokenCount(inTok)) b.WriteString(ansiReset) return b.String() } @@ -820,9 +820,9 @@ func (m *chatModel) tokenOutputTarget() int { return m.turnEstimatedOutputRunes / 4 } -// formatRhoTokenCount renders a token count in rho's compact form: +// formatDecoderTokenCount renders a token count in decoder's compact form: // ≥1m → "1.5m", ≥10k → "150k", else raw digits. -func formatRhoTokenCount(tokens int) string { +func formatDecoderTokenCount(tokens int) string { if tokens <= 0 { return "0" } diff --git a/cmd/chat_view_test.go b/cmd/chat_view_test.go index 4662015da..cc71eb99e 100644 --- a/cmd/chat_view_test.go +++ b/cmd/chat_view_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestFormatRhoTokenCount(t *testing.T) { +func TestFormatDecoderTokenCount(t *testing.T) { t.Parallel() cases := []struct { in int @@ -25,9 +25,9 @@ func TestFormatRhoTokenCount(t *testing.T) { tc := tc t.Run("", func(t *testing.T) { t.Parallel() - got := formatRhoTokenCount(tc.in) + got := formatDecoderTokenCount(tc.in) if got != tc.want { - t.Fatalf("formatRhoTokenCount(%d) = %q, want %q", tc.in, got, tc.want) + t.Fatalf("formatDecoderTokenCount(%d) = %q, want %q", tc.in, got, tc.want) } }) } diff --git a/cmd/chat_viewport.go b/cmd/chat_viewport.go index 3e4230871..23a1b6fdc 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; rho routes wheel +// syncViewportMouseWheel disables bubbletea viewport auto-wheel; decoder 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 71e592766..3e20fc3c7 100644 --- a/cmd/chat_viewport_render.go +++ b/cmd/chat_viewport_render.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 { - rhoC := ansiOrange + decoderC := 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 + rhoC + "█" + rst + bgDark + " " + wl) + b.WriteString(bgDark + decoderC + "█" + 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(rhoC + icons.Robot() + " " + rst + renderMarkdown(content, viewWidth-3)) + b.WriteString(decoderC + 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 b812e331f..860b81b4b 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" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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("RHO_MOUSE", "") + t.Setenv("DECODER_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; rho routes wheel by pane") + t.Fatal("viewport auto-wheel must stay off; decoder routes wheel by pane") } } func TestSyncViewportMouseWheel_DisabledWithOptOut(t *testing.T) { - t.Setenv("RHO_MOUSE", "0") + t.Setenv("DECODER_MOUSE", "0") vp := viewport.New(viewport.WithWidth(80), viewport.WithHeight(10)) disabled := false - m := chatModel{viewport: vp, uiFocus: focusPrompt, settings: rhoconfig.Settings{TuiMouse: &disabled}} + m := chatModel{viewport: vp, uiFocus: focusPrompt, settings: decoderconfig.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 a0ab5f909..12bc138ba 100644 --- a/cmd/chat_welcome.go +++ b/cmd/chat_welcome.go @@ -9,23 +9,23 @@ import ( "github.com/mattn/go-runewidth" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) type welcomeStatusSnapshot struct { - setup rhoconfig.SetupState + setup decoderconfig.SetupState agentsOK bool } func loadWelcomeStatusSnapshot() welcomeStatusSnapshot { ctx := context.Background() return welcomeStatusSnapshot{ - setup: rhoconfig.EvaluateSetupCached(ctx), - agentsOK: rhoconfig.LoadAgentsMD() != "", + setup: decoderconfig.EvaluateSetupCached(ctx), + agentsOK: decoderconfig.LoadAgentsMD() != "", } } @@ -71,8 +71,8 @@ func (m *chatModel) rebuildWelcomeCache(opts ...any) { m.welcomeCache = buildWelcomeMessageWithSnapshotAndMascot(m.session, m.sessionID, m.registry, nil, m.settings, skillsCount, connectedMCPCount(m.registry), frame, width, height, m.welcomeStatusSnapshot(), m.lastCommand, m.mascotEnabled) } -// 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 { +// buildWelcomeMessage renders the branded inline DECODER welcome block. +func buildWelcomeMessage(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings decoderconfig.Settings, skillsCount int, blinkClosed bool, width, height int) string { frame := 0 if blinkClosed { frame = 2 @@ -80,12 +80,12 @@ 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 rhoconfig.Settings, skillsCount, mcpCount int, eyeFrame int, width, height int, snapshot welcomeStatusSnapshot, lastCommand string) string { +func buildWelcomeMessageWithSnapshot(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings decoderconfig.Settings, skillsCount, mcpCount int, eyeFrame int, width, height int, snapshot welcomeStatusSnapshot, lastCommand string) string { return buildWelcomeMessageWithSnapshotAndMascot(sess, sessionID, registry, saved, settings, skillsCount, mcpCount, eyeFrame, width, height, snapshot, lastCommand, false) } -func buildWelcomeMessageWithSnapshotAndMascot(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, useMascot bool) string { - // Talon Gold is used for the RHO wordmark. All escapes come from the +func buildWelcomeMessageWithSnapshotAndMascot(sess *engine.Session, sessionID string, registry *tool.Registry, saved *session.Session, settings decoderconfig.Settings, skillsCount, mcpCount int, eyeFrame int, width, height int, snapshot welcomeStatusSnapshot, lastCommand string, useMascot bool) string { + // Talon Gold is used for the DECODER wordmark. All escapes come from the // theme palette (theme.go) so a rebrand stays a one-file change. logoC := ansiOrange dimC := ansiDim @@ -123,7 +123,7 @@ func buildWelcomeMessageWithSnapshotAndMascot(sess *engine.Session, sessionID st return strings.Repeat(" ", pad) + styled } - art := rhoLogoArtLines + art := decoderLogoArtLines useMascot = useMascot && !tight // Keep the text wordmark even when a graphical mascot is requested. The // mascot is a best-effort terminal escape sequence; hiding the canonical @@ -137,13 +137,13 @@ func buildWelcomeMessageWithSnapshotAndMascot(sess *engine.Session, sessionID st eyeGlyph = "|-\\/-|" } if eyeGlyph != "" && !useMascot { - art = append([]string(nil), rhoLogoArtLines...) + art = append([]string(nil), decoderLogoArtLines...) for i, line := range art { art[i] = strings.Replace(line, "|0\\/0|", eyeGlyph, 1) } } - // Inject the version into the rho's body — centered in the lower gap. + // Inject the version into the decoder's body — centered in the lower gap. verStr := DisplayVersion() if verStr != "" && !strings.HasPrefix(verStr, "v") && !strings.HasPrefix(verStr, "V") { verStr = "v" + verStr @@ -166,13 +166,13 @@ func buildWelcomeMessageWithSnapshotAndMascot(sess *engine.Session, sessionID st if tight { // Compact single-line wordmark for small terminals — version sits - // inline so it's always visible even when the full rho is hidden. + // inline so it's always visible even when the full decoder is hidden. verDisplay := DisplayVersion() if verDisplay != "" && !strings.HasPrefix(verDisplay, "v") && !strings.HasPrefix(verDisplay, "V") { verDisplay = "v" + verDisplay } - compactArt := logoC + "RHO" + rst + " " + verDisplay - b.WriteString(center(runewidth.StringWidth("RHO "+verDisplay), compactArt) + "\n") + compactArt := logoC + "DECODER" + rst + " " + verDisplay + b.WriteString(center(runewidth.StringWidth("DECODER "+verDisplay), compactArt) + "\n") } else { artW := blockLinesWidth(art) for _, line := range art { @@ -204,7 +204,7 @@ func buildWelcomeMessageWithSnapshotAndMascot(sess *engine.Session, sessionID st // short lines rather than a panel: the input remains the primary action, // while the empty state explains what to type without consuming the chat. b.WriteString("\n") - guidance := dimC + "Inspect, change, or test this codebase with rho." + rst + guidance := dimC + "Inspect, change, or test this codebase with decoder." + rst examples := dimC + `Try: "explain this repo" · "run the tests" · "/help"` + rst b.WriteString(center(visibleWidth(guidance), guidance) + "\n") b.WriteString(center(visibleWidth(examples), examples) + "\n") @@ -321,7 +321,7 @@ func welcomeIndicatorRow(skillsCount int, agentsOK bool, mcpCount int, activeC, ) } -// welcomeModeBadge is intentionally absent; rho uses host permission tiers. +// welcomeModeBadge is intentionally absent; decoder uses host permission tiers. func actLine(saved *session.Session, sessionID string) string { if saved != nil && len(sessionID) >= 8 { @@ -334,7 +334,7 @@ func toolListSummary(registry *tool.Registry) string { if registry == nil { return "No tools enabled." } - tools := registry.FluxTools() + tools := registry.MuxTools() registered := len(registry.PrimaryTools()) if len(tools) == 0 { return "No tools enabled." @@ -364,7 +364,7 @@ func envSummary(provider, model string) string { func envSummaryWithSelection(provider, model string, includeSelection bool) string { var providers []string - for _, gateway := range rhoconfig.GatewayStatuses(context.Background(), provider, model) { + for _, gateway := range decoderconfig.GatewayStatuses(context.Background(), provider, model) { providers = append(providers, gateway.ID) } sort.Strings(providers) @@ -372,17 +372,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", rhoconfig.CredentialStoreName())) + b.WriteString(fmt.Sprintf("Credentials (%s):\n", decoderconfig.CredentialStoreName())) for _, providerID := range providers { - b.WriteString(fmt.Sprintf(" %s: %s\n", providerID, rhoconfig.EnvKeyStatus(providerID))) + b.WriteString(fmt.Sprintf(" %s: %s\n", providerID, decoderconfig.EnvKeyStatus(providerID))) } return strings.TrimRight(b.String(), "\n") } -func configCommandSummary(settings rhoconfig.Settings) string { +func configCommandSummary(settings decoderconfig.Settings) string { _ = settings - providerName := displayConfigValue(rhoconfig.ActiveProvider(context.Background())) - modelName := displayConfigValue(rhoconfig.ActiveModel(context.Background())) + providerName := displayConfigValue(decoderconfig.ActiveProvider(context.Background())) + modelName := displayConfigValue(decoderconfig.ActiveModel(context.Background())) keys := configuredKeyList() keysColor := infoSky if keys == "(none)" { @@ -392,15 +392,15 @@ func configCommandSummary(settings rhoconfig.Settings) string { /config → paste API key (OS keychain) + pick model /path → verify readiness in TUI - rho path (CLI) + decoder path (CLI) %s: %s %s %s %s %s %s -Model catalog and routing live in flux — rho is the UI only.`, - auditTint("Setup (flux)", textPrimary), +Model catalog and routing live in mux — decoder is the UI only.`, + auditTint("Setup (mux)", textPrimary), auditTint("Current", textPrimary), auditTint("provider:", textMuted), auditTint(providerName, infoSky), auditTint("model:", textMuted), auditTint(modelName, infoSky), @@ -408,7 +408,7 @@ Model catalog and routing live in flux — rho is the UI only.`, } func apiKeyConfigSummary() string { - return auditTint("API keys ("+rhoconfig.CredentialStoreName()+")", textPrimary) + "\n" + indentedAPIKeyLines() + return auditTint("API keys ("+decoderconfig.CredentialStoreName()+")", textPrimary) + "\n" + indentedAPIKeyLines() } func configuredKeyList() string { @@ -454,11 +454,11 @@ func apiKeyStatusColor(status string) color.Color { } func apiKeyStatusLines() []string { - providers := rhoconfig.AllSetupGateways() + providers := decoderconfig.AllSetupGateways() sort.Strings(providers) var lines []string for _, provider := range providers { - lines = append(lines, fmt.Sprintf("%s: %s", provider, rhoconfig.EnvKeyStatus(provider))) + lines = append(lines, fmt.Sprintf("%s: %s", provider, decoderconfig.EnvKeyStatus(provider))) } return lines } diff --git a/cmd/chat_yolo_confirm_test.go b/cmd/chat_yolo_confirm_test.go index f48602fcb..4d18103b3 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/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) func TestYOLOConfirm_PendingConsumesNextInput(t *testing.T) { diff --git a/cmd/checkpoint.go b/cmd/checkpoint.go index cab4e4558..70e3c124d 100644 --- a/cmd/checkpoint.go +++ b/cmd/checkpoint.go @@ -5,19 +5,19 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/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 `rho resume `. +// labeled point you can come back to with `decoder 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 "rho resume ". +return to it later with "decoder 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: rho resume "+name, textMuted)) + cmd.Printf("%s\n", auditTint("Resume with: decoder resume "+name, textMuted)) return nil }, } @@ -125,12 +125,12 @@ var checkpointDeleteCmd = &cobra.Command{ }, } -// resumeCmd is a top-level convenience for `rho resume `: it restores a +// resumeCmd is a top-level convenience for `decoder 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 "rho checkpoint save" + Long: `resume restores a session previously saved with "decoder 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 { @@ -154,7 +154,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: rho --resume "+cp.Session.ID, textMuted)) + cmd.Printf("%s\n", auditTint("Continue with: decoder --resume "+cp.Session.ID, textMuted)) return nil } diff --git a/cmd/cli_contracts_test.go b/cmd/cli_contracts_test.go index 010b99b8f..f7c869e61 100644 --- a/cmd/cli_contracts_test.go +++ b/cmd/cli_contracts_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/session" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR"), "sessions", saved.ID+".wal") + walPath := filepath.Join(os.Getenv("DECODER_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, rhoconfig.Settings{}, "session-1") + toolsOut, handled, err := replBuiltinResponse("/tools", sess, decoderconfig.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, rhoconfig.Settings{}, "session-1") + sessionOut, handled, err := replBuiltinResponse("/session", sess, decoderconfig.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, rhoconfig.Settings{}, "session-1") + out, handled, err := replBuiltinResponse("/models", sess, decoderconfig.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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_STATE_DIR", t.TempDir()) oldResumeID := resumeID oldContinueFlag := continueFlag diff --git a/cmd/clipboard.go b/cmd/clipboard.go index 5dc2e99bc..199d27877 100644 --- a/cmd/clipboard.go +++ b/cmd/clipboard.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/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 -// rho state directory when the system clipboard is unavailable. This +// decoder 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 c0437147b..d60b06edb 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 := "rho clipboard test" + text := "decoder 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 24ec543ce..d00c5f3f4 100644 --- a/cmd/cloud.go +++ b/cmd/cloud.go @@ -12,7 +12,7 @@ import ( "time" "unicode" - cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" + cloud "github.com/GrayCodeAI/decoder/internal/platform/cloud" "github.com/spf13/cobra" "golang.org/x/term" ) @@ -29,12 +29,12 @@ var cloudCmd = &cobra.Command{Use: "cloud", Short: "Manage optional GrayCode Clo func newCloudConnectCmd() *cobra.Command { command := &cobra.Command{ Use: "connect", - Short: "Connect this Rho device to GrayCode Cloud with an existing device token", - Long: `Save an existing GrayCode Cloud device connection. Prefer "rho cloud login", + Short: "Connect this Decoder device to GrayCode Cloud with an existing device token", + Long: `Save an existing GrayCode Cloud device connection. Prefer "decoder cloud login", which needs no token. The device token is read with --token-stdin or from a hidden prompt, never from the command line: - rho cloud connect --device-id --project-id --token-stdin < token.txt`, + decoder cloud connect --device-id --project-id --token-stdin < token.txt`, RunE: func(cmd *cobra.Command, _ []string) error { endpointFlag, _ := cmd.Flags().GetString("endpoint") endpoint, err := cloud.ResolveEndpoint(endpointFlag) @@ -88,11 +88,11 @@ func warnIfPlaintextTokenStore(cmd *cobra.Command) { if !plaintext { return } - cmd.PrintErrln(auditTint("Warning: rho has no OS credential store integration on this platform, so the GrayCode Cloud device token was saved in the "+where+ + cmd.PrintErrln(auditTint("Warning: decoder has no OS credential store integration on this platform, so the GrayCode Cloud device token was saved in the "+where+ ". Any process running as your user, and any backup of that file, can read it. Keep the file private and revoke the device in GrayCode Cloud if this machine is shared or lost.", warnAmber)) } -// readDeviceToken returns the device token for `rho cloud connect` from, in +// readDeviceToken returns the device token for `decoder cloud connect` from, in // order: --token-stdin, the deprecated --token flag, or a hidden prompt when // stdin is a terminal. The token never has to appear in argv, where it would // land in shell history and be visible to other users via ps. @@ -123,7 +123,7 @@ func readDeviceToken(cmd *cobra.Command) (string, error) { } raw = string(data) default: - return "", errors.New("a device token is required: pipe it with --token-stdin, or run in a terminal to be prompted (`rho cloud login` needs no token)") + return "", errors.New("a device token is required: pipe it with --token-stdin, or run in a terminal to be prompted (`decoder cloud login` needs no token)") } token := strings.TrimSpace(raw) if token == "" { @@ -167,7 +167,7 @@ http:// on localhost, 127.0.0.1 and [::1].`, if err != nil { return err } - cmd.Printf("%s\n", auditTint("Open ", textPrimary)+auditTint(start.VerificationURI, infoSky)+auditTint(" and enter code ", textPrimary)+auditTint(start.UserCode, rhoColor)) + cmd.Printf("%s\n", auditTint("Open ", textPrimary)+auditTint(start.VerificationURI, infoSky)+auditTint(" and enter code ", textPrimary)+auditTint(start.UserCode, decoderColor)) if err := openBrowser(approvalURL); err != nil { cmd.Printf("%s\n", auditTint(fmt.Sprintf("Could not open the browser automatically: %v", err), textMuted)) } @@ -208,7 +208,7 @@ http:// on localhost, 127.0.0.1 and [::1].`, }, } command.Flags().String("endpoint", "", cloudEndpointFlagHelp) - command.Flags().String("label", "", "Name for this Rho device") + command.Flags().String("label", "", "Name for this Decoder device") return command } @@ -218,7 +218,7 @@ func newCloudStatusCmd() *cobra.Command { RunE: func(cmd *cobra.Command, _ []string) error { client, cfg, err := loadCloudClient() if errors.Is(err, cloud.ErrNotConnected) { - cmd.Println(auditTint("GrayCode Cloud is not connected. Run `rho cloud login` to connect.", textMuted)) + cmd.Println(auditTint("GrayCode Cloud is not connected. Run `decoder cloud login` to connect.", textMuted)) return nil } if err != nil { diff --git a/cmd/cloud_context_test.go b/cmd/cloud_context_test.go index 6941350c2..a05235305 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/rho.git", nil + return "git@github.com:GrayCodeAI/decoder.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/rho" || got.Provider != "github" || got.Branch != "main" || got.Commit != "abc123" { + if got.Repository != "GrayCodeAI/decoder" || 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 3001d55a8..ba1caecca 100644 --- a/cmd/cloud_graph.go +++ b/cmd/cloud_graph.go @@ -5,8 +5,8 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/executiongraph" - cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + cloud "github.com/GrayCodeAI/decoder/internal/platform/cloud" "github.com/spf13/cobra" ) @@ -23,7 +23,7 @@ func newCloudGraphCmd() *cobra.Command { syncCmd := &cobra.Command{ Use: "sync [session-id]", Short: "Upload a privacy-normalized execution graph to GrayCode Cloud", - Long: `Build the same read-only execution graph as "rho graph export", hash + Long: `Build the same read-only execution graph as "decoder graph export", hash cloud-sensitive metadata, enforce GrayCode 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 diff --git a/cmd/cloud_test.go b/cmd/cloud_test.go index cdc503a4f..9dfed2775 100644 --- a/cmd/cloud_test.go +++ b/cmd/cloud_test.go @@ -12,8 +12,8 @@ import ( "sync/atomic" "testing" - "github.com/GrayCodeAI/rho/internal/executiongraph" - cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + cloud "github.com/GrayCodeAI/decoder/internal/platform/cloud" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ func TestCloudLoginHelpNamesDefaultEndpoint(t *testing.T) { } } -func TestCloudLoginUsesRHOCloudURL(t *testing.T) { +func TestCloudLoginUsesDECODERCloudURL(t *testing.T) { var hits atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/v1/auth/device/start" { @@ -56,7 +56,7 @@ func TestCloudLoginUsesRHOCloudURL(t *testing.T) { t.Fatal("login succeeded against a failing server") } if hits.Load() != 1 { - t.Fatalf("device start hits = %d, want 1 (RHO_CLOUD_URL was not used)", hits.Load()) + t.Fatalf("device start hits = %d, want 1 (DECODER_CLOUD_URL was not used)", hits.Load()) } } @@ -110,7 +110,7 @@ func TestCloudContextReportsWorkerRejection(t *testing.T) { defer server.Close() useCloudClient(t, cloud.New(cloud.Config{Endpoint: server.URL, DeviceToken: "hwc_test"}), cloud.DeviceConfig{ProjectID: "project_0123456789"}, nil) - out, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/rho") + out, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/decoder") if err == nil || !strings.Contains(err.Error(), "Device not found") { t.Fatalf("error = %v, want the Worker's rejection", err) } @@ -128,7 +128,7 @@ func TestCloudContextReportsSuccessOnlyWhenAccepted(t *testing.T) { defer server.Close() useCloudClient(t, cloud.New(cloud.Config{Endpoint: server.URL, DeviceToken: "hwc_test"}), cloud.DeviceConfig{ProjectID: "project_0123456789"}, nil) - out, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/rho", "--ci-run", "42", "--ci-status", "succeeded") + out, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/decoder", "--ci-run", "42", "--ci-status", "succeeded") if err != nil || !strings.Contains(out, "synced to GrayCode Cloud") { t.Fatalf("out = %q, err = %v", out, err) } @@ -144,11 +144,11 @@ func TestCloudContextRejectsUnknownStatusFlags(t *testing.T) { defer server.Close() useCloudClient(t, cloud.New(cloud.Config{Endpoint: server.URL, DeviceToken: "hwc_test"}), cloud.DeviceConfig{ProjectID: "project_0123456789"}, nil) - _, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/rho", "--ci-run", "42", "--ci-status", "passed") + _, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/decoder", "--ci-run", "42", "--ci-status", "passed") if err == nil || !strings.Contains(err.Error(), "--ci-status") { t.Fatalf("error = %v, want a --ci-status error", err) } - _, err = runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/rho", "--deployment", "d1", "--deployment-environment", "prod", "--deployment-status", "done") + _, err = runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/decoder", "--deployment", "d1", "--deployment-environment", "prod", "--deployment-status", "done") if err == nil || !strings.Contains(err.Error(), "--deployment-status") || !strings.Contains(err.Error(), "rolled_back") { t.Fatalf("error = %v, want a --deployment-status error listing rolled_back", err) } @@ -190,7 +190,7 @@ func TestExplicitCloudCommandsReportLoadErrors(t *testing.T) { noGitRemote(t) broken := errors.New("GrayCode Cloud configuration is incomplete") useCloudClient(t, nil, cloud.DeviceConfig{}, broken) - if _, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/rho"); !errors.Is(err, broken) { + if _, err := runCloudCommand(t, newCloudContextCmd(), "--repository", "GrayCodeAI/decoder"); !errors.Is(err, broken) { t.Fatalf("context error = %v, want the load failure", err) } missionDir := t.TempDir() @@ -319,12 +319,12 @@ func useTokenStorage(t *testing.T, where string, plaintext bool) { func TestCloudConnectWarnsAboutPlaintextTokenStore(t *testing.T) { captureCloudSave(t) promptable(t, true, "hwc_prompted") - useTokenStorage(t, "plaintext file /home/dev/.config/rho/.tokens (mode 0600)", true) + useTokenStorage(t, "plaintext file /home/dev/.config/decoder/.tokens (mode 0600)", true) out, err := runCloudCommand(t, newCloudConnectCmd(), connectIDs...) if err != nil { t.Fatal(err) } - if !strings.Contains(out, "Warning:") || !strings.Contains(out, "/home/dev/.config/rho/.tokens") { + if !strings.Contains(out, "Warning:") || !strings.Contains(out, "/home/dev/.config/decoder/.tokens") { t.Fatalf("output = %q, want a plaintext token warning naming the file", out) } @@ -338,9 +338,9 @@ func TestCloudConnectWarnsAboutPlaintextTokenStore(t *testing.T) { func TestCloudStatusShowsTokenStorage(t *testing.T) { cfg := cloud.DeviceConfig{Endpoint: cloud.DefaultEndpoint, DeviceID: "device_0123456789", ProjectID: "project_0123456789"} useCloudClient(t, cloud.New(cloud.Config{Endpoint: cfg.Endpoint, DeviceToken: "hwc_test"}), cfg, nil) - useTokenStorage(t, "plaintext file /tmp/rho/.tokens (mode 0600)", true) + useTokenStorage(t, "plaintext file /tmp/decoder/.tokens (mode 0600)", true) out, err := runCloudCommand(t, newCloudStatusCmd()) - if err != nil || !strings.Contains(out, "Device token: plaintext file /tmp/rho/.tokens") || !strings.Contains(out, "not an OS credential store") { + if err != nil || !strings.Contains(out, "Device token: plaintext file /tmp/decoder/.tokens") || !strings.Contains(out, "not an OS credential store") { t.Fatalf("out = %q, err = %v", out, err) } } diff --git a/cmd/cloud_usage.go b/cmd/cloud_usage.go index 2b56f86f9..ac1ddf9ff 100644 --- a/cmd/cloud_usage.go +++ b/cmd/cloud_usage.go @@ -7,7 +7,7 @@ import ( "io" "time" - cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" + cloud "github.com/GrayCodeAI/decoder/internal/platform/cloud" ) // cloudUsageWait bounds how long a command waits, after writing its output, diff --git a/cmd/cloud_usage_test.go b/cmd/cloud_usage_test.go index 56545fd97..3101cfa3a 100644 --- a/cmd/cloud_usage_test.go +++ b/cmd/cloud_usage_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - cloud "github.com/GrayCodeAI/rho/internal/platform/cloud" + cloud "github.com/GrayCodeAI/decoder/internal/platform/cloud" ) var testUsageConfig = cloud.DeviceConfig{Endpoint: "https://cloud.graycodeai.com", DeviceID: "device_0123456789", ProjectID: "project_0123456789"} diff --git a/cmd/cmdhistory_cmd.go b/cmd/cmdhistory_cmd.go index 67c93dc1c..6ed85c44d 100644 --- a/cmd/cmdhistory_cmd.go +++ b/cmd/cmdhistory_cmd.go @@ -6,8 +6,8 @@ import ( "path/filepath" "strconv" - "github.com/GrayCodeAI/rho/internal/cmdhistory" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/cmdhistory" + "github.com/GrayCodeAI/decoder/internal/storage" "github.com/spf13/cobra" ) diff --git a/cmd/command_history.go b/cmd/command_history.go index 7ceda8148..f7d2dc613 100644 --- a/cmd/command_history.go +++ b/cmd/command_history.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/storage" ) // commandHistory persists recently-used slash commands so the command diff --git a/cmd/command_palette.go b/cmd/command_palette.go index 3a212a048..fa8f8b422 100644 --- a/cmd/command_palette.go +++ b/cmd/command_palette.go @@ -8,8 +8,8 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - commandfeature "github.com/GrayCodeAI/rho/internal/features/commands" - "github.com/GrayCodeAI/rho/internal/plugin" + commandfeature "github.com/GrayCodeAI/decoder/internal/features/commands" + "github.com/GrayCodeAI/decoder/internal/plugin" ) // CommandPaletteEntry represents a single command in the palette. @@ -45,7 +45,7 @@ func (cp *CommandPalette) ensureInput() { // Palette styles are functions rather than package-level values so live theme // changes apply to every picker, not only to newly created widgets. func paletteTitleStyle() lipgloss.Style { - return lipgloss.NewStyle().Bold(true).Foreground(rhoColor) + return lipgloss.NewStyle().Bold(true).Foreground(decoderColor) } func paletteInputStyle() lipgloss.Style { @@ -65,7 +65,7 @@ func paletteDescStyle() lipgloss.Style { } func paletteCategoryStyle() lipgloss.Style { - return lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + return lipgloss.NewStyle().Foreground(decoderColor).Bold(true) } func paletteDimStyle() lipgloss.Style { diff --git a/cmd/compact_ui.go b/cmd/compact_ui.go index cb1a3bee9..3d130e196 100644 --- a/cmd/compact_ui.go +++ b/cmd/compact_ui.go @@ -7,14 +7,14 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" "github.com/mattn/go-runewidth" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) const compactProgressBarWidth = 40 diff --git a/cmd/compact_ui_test.go b/cmd/compact_ui_test.go index 0257de29e..2b16bf15c 100644 --- a/cmd/compact_ui_test.go +++ b/cmd/compact_ui_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/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(SpinnerRho, "Compacting conversation"), + brailleSpinner: NewBrailleSpinner(SpinnerDecoder, "Compacting conversation"), manualCompacting: true, } out := m.renderCompactProgressPanel(80) diff --git a/cmd/compat-test/drift.go b/cmd/compat-test/drift.go index 84ca5bed0..6a35174d3 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 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 flux. See docs/compatibility.md. +// a sibling repo can pin an older version than what decoder's own go.mod requires, +// and Go's minimal version selection will silently pull in decoder's newer version +// at build time without the consumer's own CI ever having tested it. Decoder's +// only external GrayCodeAI dependency is mux. See docs/compatibility.md. var trackedPins = []string{ - "github.com/GrayCodeAI/flux", + "github.com/GrayCodeAI/mux", } -// 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 +// checkDrift compares decoder's own go.mod requirements for trackedPins against +// what each sibling repo (a peer checkout of a decoder 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 { - rhoRequires, err := readRequires(filepath.Join(repoRoot, "go.mod")) + decoderRequires, err := readRequires(filepath.Join(repoRoot, "go.mod")) if err != nil { - return fmt.Errorf("read rho go.mod: %w", err) + return fmt.Errorf("read decoder 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 { - rhoVer, rhoHas := rhoRequires[pin] + decoderVer, decoderHas := decoderRequires[pin] consumerVer, consumerHas := consumerRequires[pin] - if !rhoHas || !consumerHas || rhoVer == consumerVer { + if !decoderHas || !consumerHas || decoderVer == consumerVer { continue } drifted++ - fmt.Printf(" %-22s requires %s@%s, rho requires %s@%s\n", - e.Name(), pin, consumerVer, pin, rhoVer) + fmt.Printf(" %-22s requires %s@%s, decoder requires %s@%s\n", + e.Name(), pin, consumerVer, pin, decoderVer) } } if drifted == 0 { - fmt.Println(" OK — no drift between rho's pins and sibling consumers") + fmt.Println(" OK — no drift between decoder's pins and sibling consumers") } return nil } diff --git a/cmd/compat-test/drift_test.go b/cmd/compat-test/drift_test.go index e6c834765..72bef8ca8 100644 --- a/cmd/compat-test/drift_test.go +++ b/cmd/compat-test/drift_test.go @@ -28,7 +28,7 @@ func TestReadRequires(t *testing.T) { go 1.26 require ( - github.com/GrayCodeAI/flux v1.0.0 + github.com/GrayCodeAI/mux v1.0.0 github.com/spf13/cobra v1.8.0 ) @@ -40,7 +40,7 @@ require github.com/GrayCodeAI/other v1.9.0 // indirect t.Fatalf("readRequires: %v", err) } tests := map[string]string{ - "github.com/GrayCodeAI/flux": "v1.0.0", + "github.com/GrayCodeAI/mux": "v1.0.0", "github.com/spf13/cobra": "v1.8.0", "github.com/GrayCodeAI/other": "v1.9.0", } @@ -68,28 +68,28 @@ func TestReadRequires_InvalidMod(t *testing.T) { } } -// TestCheckDrift reports drift when a consumer pins an older version than rho. +// TestCheckDrift reports drift when a consumer pins an older version than decoder. func TestCheckDrift_DetectsDrift(t *testing.T) { ws := t.TempDir() - writeMod(t, filepath.Join(ws, "rho", "go.mod"), `module github.com/GrayCodeAI/rho + writeMod(t, filepath.Join(ws, "decoder", "go.mod"), `module github.com/GrayCodeAI/decoder go 1.26 -require github.com/GrayCodeAI/flux v1.5.0 +require github.com/GrayCodeAI/mux v1.5.0 `) // Consumer sibling pins an older version of the shared dependency. writeMod(t, filepath.Join(ws, "consumer", "go.mod"), `module github.com/GrayCodeAI/consumer go 1.26 -require github.com/GrayCodeAI/flux v1.2.0 +require github.com/GrayCodeAI/mux v1.2.0 `) var buf bytes.Buffer old := os.Stdout r, w, _ := os.Pipe() os.Stdout = w - err := checkDrift(filepath.Join(ws, "rho")) + err := checkDrift(filepath.Join(ws, "decoder")) _ = w.Close() os.Stdout = old _, _ = buf.ReadFrom(r) @@ -106,24 +106,24 @@ require github.com/GrayCodeAI/flux 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, "rho", "go.mod"), `module github.com/GrayCodeAI/rho + writeMod(t, filepath.Join(ws, "decoder", "go.mod"), `module github.com/GrayCodeAI/decoder go 1.26 -require github.com/GrayCodeAI/flux v1.5.0 +require github.com/GrayCodeAI/mux v1.5.0 `) writeMod(t, filepath.Join(ws, "consumer", "go.mod"), `module github.com/GrayCodeAI/consumer go 1.26 -require github.com/GrayCodeAI/flux v1.5.0 +require github.com/GrayCodeAI/mux v1.5.0 `) var buf bytes.Buffer r, w, _ := os.Pipe() old := os.Stdout os.Stdout = w - err := checkDrift(filepath.Join(ws, "rho")) + err := checkDrift(filepath.Join(ws, "decoder")) _ = w.Close() os.Stdout = old _, _ = buf.ReadFrom(r) @@ -140,11 +140,11 @@ require github.com/GrayCodeAI/flux 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, "rho", "go.mod"), `module github.com/GrayCodeAI/rho + writeMod(t, filepath.Join(ws, "decoder", "go.mod"), `module github.com/GrayCodeAI/decoder go 1.26 -require github.com/GrayCodeAI/flux v1.5.0 +require github.com/GrayCodeAI/mux v1.5.0 `) // Directory present but no go.mod — must be skipped silently. if err := os.MkdirAll(filepath.Join(ws, "not-checked-out"), 0o755); err != nil { @@ -154,7 +154,7 @@ require github.com/GrayCodeAI/flux v1.5.0 r, w, _ := os.Pipe() old := os.Stdout os.Stdout = w - err := checkDrift(filepath.Join(ws, "rho")) + err := checkDrift(filepath.Join(ws, "decoder")) _ = 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 4fe909e4d..4f77805fe 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 rho's own +// go run ./cmd/compat-test -check-external # advisory: compare decoder'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 rho/platform-capabilities.json, which is a different document. +// It must not pick decoder/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", - "rho/testdata/compatibility-matrix.json", + "decoder/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 e0b41eb5f..c9488364f 100644 --- a/cmd/completions.go +++ b/cmd/completions.go @@ -9,9 +9,9 @@ import ( "sort" "strings" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/provider/routing" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/provider/routing" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -33,7 +33,7 @@ type CommandInfo struct { Flags []FlagInfo `json:"flags,omitempty"` } -// CompletionGenerator generates shell completion scripts for rho. +// CompletionGenerator generates shell completion scripts for decoder. type CompletionGenerator struct { Commands []CommandInfo `json:"commands"` Flags []FlagInfo `json:"flags"` @@ -42,7 +42,7 @@ type CompletionGenerator struct { Providers []string `json:"providers"` } -// NewCompletionGenerator creates a CompletionGenerator pre-populated with rho's +// NewCompletionGenerator creates a CompletionGenerator pre-populated with decoder's // command structure, flags, providers, models, and slash commands. func NewCompletionGenerator() *CompletionGenerator { g := &CompletionGenerator{} @@ -64,7 +64,7 @@ func (g *CompletionGenerator) populateFlags() { } func (g *CompletionGenerator) populateProviders() { - g.Providers = rhoconfig.AllSetupGateways() + g.Providers = decoderconfig.AllSetupGateways() } func (g *CompletionGenerator) populateModels() { @@ -83,16 +83,16 @@ func (g *CompletionGenerator) populateSlashCommands() { return } // Completion must remain available when a plugin manifest is malformed or - // unreadable. Static rho commands are still a complete fallback. + // unreadable. Static decoder commands are still a complete fallback. g.SlashCommands = slashCommands() } -// GenerateBash returns a complete bash completion script for rho. +// GenerateBash returns a complete bash completion script for decoder. func (g *CompletionGenerator) GenerateBash() string { var b strings.Builder - b.WriteString("# bash completion for rho\n") - b.WriteString("# Auto-generated by rho completions generator\n\n") + b.WriteString("# bash completion for decoder\n") + b.WriteString("# Auto-generated by decoder completions generator\n\n") // Build subcommand list subcommands := make([]string, 0, len(g.Commands)) @@ -115,7 +115,7 @@ func (g *CompletionGenerator) GenerateBash() string { // Build slash commands list slashCmds := strings.Join(g.SlashCommands, " ") - b.WriteString("_rho_completions() {\n") + b.WriteString("_decoder_completions() {\n") b.WriteString(" local cur prev words cword\n") b.WriteString(" _init_completion || return\n") b.WriteString("\n") @@ -162,7 +162,7 @@ func (g *CompletionGenerator) GenerateBash() string { b.WriteString(" return 0\n") b.WriteString(" fi\n") b.WriteString("\n") - b.WriteString(" # Complete subcommands after rho\n") + b.WriteString(" # Complete subcommands after decoder\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") @@ -190,20 +190,20 @@ func (g *CompletionGenerator) GenerateBash() string { b.WriteString(" return 0\n") b.WriteString("}\n") b.WriteString("\n") - b.WriteString("complete -F _rho_completions rho\n") + b.WriteString("complete -F _decoder_completions decoder\n") return b.String() } -// GenerateZsh returns a complete zsh completion script for rho. +// GenerateZsh returns a complete zsh completion script for decoder. func (g *CompletionGenerator) GenerateZsh() string { var b strings.Builder - b.WriteString("#compdef rho\n") - b.WriteString("# zsh completion for rho\n") - b.WriteString("# Auto-generated by rho completions generator\n\n") + b.WriteString("#compdef decoder\n") + b.WriteString("# zsh completion for decoder\n") + b.WriteString("# Auto-generated by decoder completions generator\n\n") - b.WriteString("_rho() {\n") + b.WriteString("_decoder() {\n") b.WriteString(" local -a commands\n") b.WriteString(" local -a global_flags\n") b.WriteString("\n") @@ -246,7 +246,7 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString(" case $state in\n") b.WriteString(" commands)\n") - b.WriteString(" _describe -t commands 'rho commands' commands\n") + b.WriteString(" _describe -t commands 'decoder commands' commands\n") b.WriteString(" ;;\n") b.WriteString(" args)\n") b.WriteString(" case $words[1] in\n") @@ -269,7 +269,7 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString("}\n\n") // Slash commands completion function - b.WriteString("_rho_slash_commands() {\n") + b.WriteString("_decoder_slash_commands() {\n") b.WriteString(" local -a slash_commands\n") b.WriteString(" slash_commands=(\n") for _, sc := range g.SlashCommands { @@ -280,7 +280,7 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString("}\n\n") // Provider completion function - b.WriteString("_rho_providers() {\n") + b.WriteString("_decoder_providers() {\n") b.WriteString(" local -a providers\n") b.WriteString(" providers=(\n") for _, p := range g.Providers { @@ -290,25 +290,25 @@ func (g *CompletionGenerator) GenerateZsh() string { b.WriteString(" compadd -a providers\n") b.WriteString("}\n\n") - b.WriteString("_rho \"$@\"\n") + b.WriteString("_decoder \"$@\"\n") return b.String() } -// GenerateFish returns a complete fish shell completion script for rho. +// GenerateFish returns a complete fish shell completion script for decoder. func (g *CompletionGenerator) GenerateFish() string { var b strings.Builder - b.WriteString("# fish completion for rho\n") - b.WriteString("# Auto-generated by rho completions generator\n\n") + b.WriteString("# fish completion for decoder\n") + b.WriteString("# Auto-generated by decoder completions generator\n\n") // Disable file completions by default - b.WriteString("complete -c rho -f\n\n") + b.WriteString("complete -c decoder -f\n\n") // Subcommands b.WriteString("# Subcommands\n") for _, cmd := range g.Commands { - b.WriteString(fmt.Sprintf("complete -c rho -n '__fish_use_subcommand' -a '%s' -d '%s'\n", + b.WriteString(fmt.Sprintf("complete -c decoder -n '__fish_use_subcommand' -a '%s' -d '%s'\n", cmd.Name, escapeFish(cmd.Description))) } b.WriteString("\n") @@ -318,7 +318,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 rho -n '__fish_seen_subcommand_from %s' -a '%s' -d '%s'\n", + b.WriteString(fmt.Sprintf("complete -c decoder -n '__fish_seen_subcommand_from %s' -a '%s' -d '%s'\n", cmd.Name, sc.Name, escapeFish(sc.Description))) } b.WriteString("\n") @@ -328,10 +328,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 rho -n '__fish_seen_subcommand_from %s' -l '%s' -s '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c decoder -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 rho -n '__fish_seen_subcommand_from %s' -l '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c decoder -n '__fish_seen_subcommand_from %s' -l '%s' -d '%s'", cmd.Name, f.Name, escapeFish(f.Description))) } if f.Type == "bool" { @@ -352,10 +352,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 rho -l '%s' -s '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c decoder -l '%s' -s '%s' -d '%s'", f.Name, f.Short, escapeFish(f.Description))) } else { - b.WriteString(fmt.Sprintf("complete -c rho -l '%s' -d '%s'", + b.WriteString(fmt.Sprintf("complete -c decoder -l '%s' -d '%s'", f.Name, escapeFish(f.Description))) } if f.Type == "bool" { @@ -372,14 +372,14 @@ func (g *CompletionGenerator) GenerateFish() string { // Provider completions for --provider b.WriteString("# Provider completions\n") - b.WriteString(fmt.Sprintf("complete -c rho -l 'provider' -r -a '%s' -d 'LLM provider'\n", + b.WriteString(fmt.Sprintf("complete -c decoder -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 rho -a '%s' -d 'Slash command'\n", sc)) + b.WriteString(fmt.Sprintf("complete -c decoder -a '%s' -d 'Slash command'\n", sc)) } return b.String() @@ -400,7 +400,7 @@ func (g *CompletionGenerator) GenerateJSON() (string, error) { Providers []string `json:"providers"` Models []string `json:"models"` }{ - Name: "rho", + Name: "decoder", Version: v, Commands: g.Commands, GlobalFlags: g.Flags, @@ -530,11 +530,11 @@ func bashInstallPath() string { // Prefer user-local path home, err := os.UserHomeDir() if err != nil { - return "/etc/bash_completion.d/rho" + return "/etc/bash_completion.d/decoder" } localDir := filepath.Join(home, ".local", "share", "bash-completion", "completions") if info, err := os.Stat(localDir); err == nil && info.IsDir() { - return filepath.Join(localDir, "rho") + return filepath.Join(localDir, "decoder") } // On macOS, use homebrew path if available. // ARM Macs (M1+) install Homebrew to /opt/homebrew; Intel Macs use /usr/local. @@ -542,17 +542,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, "rho") + return filepath.Join(brewDir, "decoder") } } } // Fallback: try system-wide sysDir := "/etc/bash_completion.d" if info, err := os.Stat(sysDir); err == nil && info.IsDir() { - return filepath.Join(sysDir, "rho") + return filepath.Join(sysDir, "decoder") } // Final fallback: user-local - return filepath.Join(home, ".local", "share", "bash-completion", "completions", "rho") + return filepath.Join(home, ".local", "share", "bash-completion", "completions", "decoder") } func zshInstallPath() string { @@ -563,28 +563,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, "_rho") + return filepath.Join(p, "_decoder") } } } // Use first entry even if it doesn't exist yet if parts[0] != "" { - return filepath.Join(parts[0], "_rho") + return filepath.Join(parts[0], "_decoder") } } home, err := os.UserHomeDir() if err != nil { - return "/usr/local/share/zsh/site-functions/_rho" + return "/usr/local/share/zsh/site-functions/_decoder" } - return filepath.Join(home, ".zsh", "completions", "_rho") + return filepath.Join(home, ".zsh", "completions", "_decoder") } func fishInstallPath() string { home, err := os.UserHomeDir() if err != nil { - return filepath.Join(os.TempDir(), "rho.fish") + return filepath.Join(os.TempDir(), "decoder.fish") } - return filepath.Join(home, ".config", "fish", "completions", "rho.fish") + return filepath.Join(home, ".config", "fish", "completions", "decoder.fish") } // escapeZsh escapes single quotes for zsh completion descriptions. diff --git a/cmd/completions_test.go b/cmd/completions_test.go index 31009a17e..88dac7fc5 100644 --- a/cmd/completions_test.go +++ b/cmd/completions_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/flux/catalog/registry" + "github.com/GrayCodeAI/mux/catalog/registry" ) func TestNewCompletionGenerator(t *testing.T) { @@ -44,10 +44,10 @@ func TestGenerateBashContainsFunctionDefinition(t *testing.T) { g := NewCompletionGenerator() bash := g.GenerateBash() - if !strings.Contains(bash, "_rho_completions()") { - t.Error("Bash completion should contain _rho_completions() function definition") + if !strings.Contains(bash, "_decoder_completions()") { + t.Error("Bash completion should contain _decoder_completions() function definition") } - if !strings.Contains(bash, "complete -F _rho_completions rho") { + if !strings.Contains(bash, "complete -F _decoder_completions decoder") { t.Error("Bash completion should register the completion function with 'complete'") } } @@ -106,8 +106,8 @@ func TestGenerateZshContainsCompdefHeader(t *testing.T) { g := NewCompletionGenerator() zsh := g.GenerateZsh() - if !strings.HasPrefix(zsh, "#compdef rho") { - t.Error("Zsh completion should start with #compdef rho header") + if !strings.HasPrefix(zsh, "#compdef decoder") { + t.Error("Zsh completion should start with #compdef decoder header") } } @@ -115,8 +115,8 @@ func TestGenerateZshContainsFunction(t *testing.T) { g := NewCompletionGenerator() zsh := g.GenerateZsh() - if !strings.Contains(zsh, "_rho()") { - t.Error("Zsh completion should contain _rho() function") + if !strings.Contains(zsh, "_decoder()") { + t.Error("Zsh completion should contain _decoder() function") } if !strings.Contains(zsh, "_arguments") { t.Error("Zsh completion should use _arguments for flag completion") @@ -165,8 +165,8 @@ func TestGenerateFishContainsCompleteDirectives(t *testing.T) { g := NewCompletionGenerator() fish := g.GenerateFish() - if !strings.Contains(fish, "complete -c rho") { - t.Error("Fish completion should contain 'complete -c rho' directives") + if !strings.Contains(fish, "complete -c decoder") { + t.Error("Fish completion should contain 'complete -c decoder' directives") } } @@ -328,8 +328,8 @@ func TestInstallCompletionBash(t *testing.T) { if path == "" { t.Error("InstallCompletion(bash) returned empty path") } - if !strings.Contains(path, "rho") { - t.Errorf("Bash install path should contain 'rho', got %q", path) + if !strings.Contains(path, "decoder") { + t.Errorf("Bash install path should contain 'decoder', got %q", path) } // Should be a bash-related path if !strings.Contains(path, "bash") && !strings.Contains(path, "completion") { @@ -345,8 +345,8 @@ func TestInstallCompletionZsh(t *testing.T) { if path == "" { t.Error("InstallCompletion(zsh) returned empty path") } - if !strings.Contains(path, "_rho") { - t.Errorf("Zsh install path should contain '_rho', got %q", path) + if !strings.Contains(path, "_decoder") { + t.Errorf("Zsh install path should contain '_decoder', got %q", path) } } @@ -358,8 +358,8 @@ func TestInstallCompletionFish(t *testing.T) { if path == "" { t.Error("InstallCompletion(fish) returned empty path") } - if !strings.Contains(path, "rho.fish") { - t.Errorf("Fish install path should contain 'rho.fish', got %q", path) + if !strings.Contains(path, "decoder.fish") { + t.Errorf("Fish install path should contain 'decoder.fish', got %q", path) } if !strings.Contains(path, "fish") { t.Errorf("Fish install path should be fish-related, got %q", path) @@ -516,8 +516,8 @@ func TestGenerateZshProviderChoices(t *testing.T) { zsh := g.GenerateZsh() // The provider completion function should list all providers - if !strings.Contains(zsh, "_rho_providers()") { - t.Error("Zsh completion should contain _rho_providers() function") + if !strings.Contains(zsh, "_decoder_providers()") { + t.Error("Zsh completion should contain _decoder_providers() function") } } diff --git a/cmd/config_table.go b/cmd/config_table.go index 4aa68cb99..facbc6640 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/mattn/go-runewidth" ) diff --git a/cmd/context_export.go b/cmd/context_export.go index 466edcee6..8b4315e72 100644 --- a/cmd/context_export.go +++ b/cmd/context_export.go @@ -9,9 +9,9 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/rho/internal/fsutil" - "github.com/GrayCodeAI/rho/internal/intelligence/repomap" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/fsutil" + "github.com/GrayCodeAI/decoder/internal/intelligence/repomap" + "github.com/GrayCodeAI/decoder/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", ".rho.md"} { + for _, instrFile := range []string{"AGENTS.md", ".decoder.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 8013ff8e3..c69d416a0 100644 --- a/cmd/contextual_help.go +++ b/cmd/contextual_help.go @@ -84,7 +84,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "/undo", Summary: "Undo last action safely", - Detail: "Reverts the last rho action (commit, file edit, etc.) using safe snapshot-based rollback.", + Detail: "Reverts the last decoder 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", @@ -108,7 +108,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "/config", Summary: "Open configuration panel", - Detail: "Opens the interactive configuration panel for rho settings, model selection, and preferences.", + Detail: "Opens the interactive configuration panel for decoder 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", @@ -171,8 +171,8 @@ var builtinHelpEntries = []*HelpEntry{ }, { Topic: "/session", - Summary: "Manage rho sessions", - Detail: "Start, stop, resume, or list rho working sessions with full context preservation.", + Summary: "Manage decoder sessions", + Detail: "Start, stop, resume, or list decoder 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", @@ -236,7 +236,7 @@ var builtinHelpEntries = []*HelpEntry{ // ─── Common Tasks ──────────────────────────────────────────── { Topic: "how to fix tests", - Summary: "Fixing failing tests with rho", + Summary: "Fixing failing tests with decoder", 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"}, @@ -244,7 +244,7 @@ var builtinHelpEntries = []*HelpEntry{ }, { Topic: "how to commit", - Summary: "Making commits with rho", + Summary: "Making commits with decoder", 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"}, @@ -252,7 +252,7 @@ var builtinHelpEntries = []*HelpEntry{ }, { Topic: "how to debug", - Summary: "Debugging with rho AI assistance", + Summary: "Debugging with decoder 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"}, @@ -269,16 +269,16 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "how to refactor", Summary: "Refactoring code with AI assistance", - Detail: "Use /chat to discuss refactoring strategies, then rho can apply changes. Use /snapshot before large refactors for safety.", + Detail: "Use /chat to discuss refactoring strategies, then decoder 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 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"}, + Summary: "Starting a new decoder session", + Detail: "Run decoder in your project directory to start a session. Your context is preserved across the session. Use /session to manage sessions.", + Examples: []string{"decoder — start decoder in current dir", "/session new — start fresh session", "/session resume — resume last session"}, Related: []string{"/session", "/config", "/status"}, Category: "common-tasks", }, @@ -287,14 +287,14 @@ var builtinHelpEntries = []*HelpEntry{ 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", "rho credentials status — verify stored keys"}, + Examples: []string{"/config — paste API key in the config panel", "decoder 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. Rho will automatically retry with exponential backoff. Consider upgrading your plan for higher limits.", + Detail: "You have exceeded the API rate limit. Decoder 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", @@ -318,7 +318,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "error: git conflict", Summary: "Git merge conflict detected", - Detail: "A merge conflict was encountered. Rho can assist with conflict resolution using AI to understand both sides.", + Detail: "A merge conflict was encountered. Decoder 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", @@ -326,7 +326,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "error: no git repo", Summary: "Not in a git repository", - Detail: "Rho requires a git repository. Initialize one with git init or navigate to an existing repo.", + Detail: "Decoder 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", @@ -360,7 +360,7 @@ var builtinHelpEntries = []*HelpEntry{ 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", "rho credentials status — list configured providers"}, + Examples: []string{"/config — paste API key in the config panel", "/config key remove — remove a stored key", "decoder credentials status — list configured providers"}, Related: []string{"/config", "config: model", "error: api key invalid"}, Category: "configuration", }, @@ -383,7 +383,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "config: editor", Summary: "Set preferred editor", - Detail: "Configure which editor rho uses when opening files for editing.", + Detail: "Configure which editor decoder 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", @@ -391,7 +391,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "config: theme", Summary: "Set color theme", - Detail: "Choose the color theme for rho terminal output. Supports light and dark terminal backgrounds.", + Detail: "Choose the color theme for decoder 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", @@ -399,7 +399,7 @@ var builtinHelpEntries = []*HelpEntry{ { Topic: "config: shell", Summary: "Set shell for command execution", - Detail: "Configure which shell rho uses when running commands.", + Detail: "Configure which shell decoder 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", @@ -473,7 +473,7 @@ var builtinHelpEntries = []*HelpEntry{ 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{"rho daemon start — start the daemon", "rho daemon status — check daemon status", "rho daemon stop — stop the daemon"}, + Examples: []string{"decoder daemon start — start the daemon", "decoder daemon status — check daemon status", "decoder 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 71be5625f..83e62eac1 100644 --- a/cmd/control_plane_hints.go +++ b/cmd/control_plane_hints.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) // controlPlaneOnboardingHint is a short first-session tip (not a wall of text). @@ -24,7 +24,7 @@ func workModeSwitchSummary(sess *engine.Session, wm engine.WorkMode) string { } visible := 0 if sess != nil && sess.Tools() != nil && sess.Tools().Registry() != nil { - visible = len(sess.Tools().Registry().FluxTools()) + visible = len(sess.Tools().Registry().MuxTools()) } return fmt.Sprintf("Work mode → %s\n%s\nModel-visible tools: %d · /status for full control plane", wm, hint, visible) } diff --git a/cmd/cost.go b/cmd/cost.go index 007bfdea3..2ce427e48 100644 --- a/cmd/cost.go +++ b/cmd/cost.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - analytics "github.com/GrayCodeAI/rho/internal/observability" + analytics "github.com/GrayCodeAI/decoder/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/rho/issues", textMuted)) + cmd.Println(auditTint("To track progress: https://github.com/GrayCodeAI/decoder/issues", textMuted)) return nil } diff --git a/cmd/cost_test.go b/cmd/cost_test.go index d7ec4b3e7..f7a05e3ba 100644 --- a/cmd/cost_test.go +++ b/cmd/cost_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - analytics "github.com/GrayCodeAI/rho/internal/observability" + analytics "github.com/GrayCodeAI/decoder/internal/observability" ) func TestCostAnalyze_JSON_Empty(t *testing.T) { diff --git a/cmd/credential_gate.go b/cmd/credential_gate.go index 169de452e..cd64c2700 100644 --- a/cmd/credential_gate.go +++ b/cmd/credential_gate.go @@ -3,7 +3,7 @@ package cmd import ( "sync/atomic" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/tool" ) // credentialGate holds the current host-side credential gate callback. It is diff --git a/cmd/credentials.go b/cmd/credentials.go index 92ccef4d1..342deed5c 100644 --- a/cmd/credentials.go +++ b/cmd/credentials.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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(rhoconfig.FormatCredentialCLIStatus(ctx)) + cmd.Println(decoderconfig.FormatCredentialCLIStatus(ctx)) return nil }, } @@ -33,7 +33,7 @@ var credentialsRemoveCmd = &cobra.Command{ ok, _ := cmd.Flags().GetBool("yes") if !ok { var err error - ok, err = confirmDestructive(fmt.Sprintf("Remove stored API key(s) for %q from %s?", args[0], rhoconfig.CredentialStoreName())) + ok, err = confirmDestructive(fmt.Sprintf("Remove stored API key(s) for %q from %s?", args[0], decoderconfig.CredentialStoreName())) if err != nil { return err } @@ -42,11 +42,11 @@ var credentialsRemoveCmd = &cobra.Command{ cmd.Printf("%s\n", auditTint("Cancelled.", textMuted)) return nil } - removed, err := rhoconfig.RemoveStoredCredential(ctx, args[0]) + removed, err := decoderconfig.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), rhoconfig.CredentialStoreName(), strings.Join(removed, ", ")), doneGreen)) + cmd.Printf("%s\n", auditTint(fmt.Sprintf("Removed %d key(s) from %s: %s", len(removed), decoderconfig.CredentialStoreName(), strings.Join(removed, ", ")), doneGreen)) return nil }, } diff --git a/cmd/daemon.go b/cmd/daemon.go index 24af20d46..d313c3f44 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -14,20 +14,20 @@ import ( "syscall" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" - - 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/GrayCodeAI/decoder/internal/engine/safety" + + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/daemon" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + "github.com/GrayCodeAI/decoder/internal/fsutil" + "github.com/GrayCodeAI/decoder/internal/multiagent/agents" + "github.com/GrayCodeAI/decoder/internal/netutil" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + "github.com/GrayCodeAI/decoder/internal/observability/otellog" + "github.com/GrayCodeAI/decoder/internal/observability/oteltrace" + "github.com/GrayCodeAI/decoder/internal/securitylog" + "github.com/GrayCodeAI/decoder/internal/storage" "github.com/spf13/cobra" ) @@ -45,8 +45,8 @@ var ( var daemonCmd = &cobra.Command{ Use: "daemon", - Short: "Manage the rho background server", - Long: "Run rho as a background HTTP server for programmatic/CI access.", + Short: "Manage the decoder background server", + Long: "Run decoder as a background HTTP server for programmatic/CI access.", } var daemonStartCmd = &cobra.Command{ @@ -70,7 +70,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 RHO_DAEMON_API_KEY or a generated key)") + daemonStartCmd.Flags().StringVar(&daemonAPIKey, "api-key", "", "API key for protected daemon endpoints (defaults to DECODER_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)") @@ -83,9 +83,9 @@ func init() { } func runDaemonStart(_ *cobra.Command, _ []string) error { - settings := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() - // Initialize OpenTelemetry telemetry (opt-in via RHO_ENABLE_TELEMETRY=1). + // Initialize OpenTelemetry telemetry (opt-in via DECODER_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)) @@ -113,7 +113,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } // Set up file-backed logging for the daemon. Logs go to - // ~/.rho/state/daemon.log with slog structured output. + // ~/.decoder/state/daemon.log with slog structured output. logFile, logErr := openDaemonLogFile() var daemonLogger *logger.Logger if logErr != nil { @@ -130,10 +130,10 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } // Replace the discarded logger with the real file-backed logger. - newSession := newConfiguredRhoSessionFactory(settings, daemonLogger) + newSession := newConfiguredDecoderSessionFactory(settings, daemonLogger) // Log startup banner. - daemonLogger.Info("rho daemon starting", map[string]interface{}{ + daemonLogger.Info("decoder daemon starting", map[string]interface{}{ "host": daemonHost, "port": daemonPort, "telemetry_enabled": telemetryProviders != nil && telemetryProviders.IsEnabled(), @@ -141,7 +141,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { apiKey := daemonAPIKey if apiKey == "" { - apiKey = os.Getenv("RHO_DAEMON_API_KEY") + apiKey = os.Getenv("DECODER_DAEMON_API_KEY") } if apiKey == "" { var err error @@ -211,9 +211,9 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { ) }) - // Wire Flux's authoritative local preflight into GET /v1/ready. A session - // factory only proves Rho can attempt construction; readiness additionally - // requires Flux's provider state, catalog, credentials, and model selection. + // Wire Mux's authoritative local preflight into GET /v1/ready. A session + // factory only proves Decoder can attempt construction; readiness additionally + // requires Mux's provider state, catalog, credentials, and model selection. srv.SetReadyFn(daemonReadyProbe(factory)) addr, err := srv.Start() if err != nil { @@ -229,19 +229,19 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { }) defer preheater.Stop() - fmt.Printf("rho daemon running on http://%s\n", addr) + fmt.Printf("decoder 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 RHO_DAEMON_API_KEY)") + fmt.Println("API key: (set via --api-key or DECODER_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 RHO_ENABLE_TELEMETRY=1 to enable)") + fmt.Println("Telemetry: disabled (set DECODER_ENABLE_TELEMETRY=1 to enable)") } keyFile := filepath.Join(storage.DaemonRunDir(), "daemon.key") _ = os.MkdirAll(filepath.Dir(keyFile), 0o700) @@ -285,7 +285,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } // openDaemonLogFile opens (or creates) the daemon log file at -// ~/.rho/state/daemon.log and returns it. The directory is created if needed. +// ~/.decoder/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 @@ -329,26 +329,26 @@ func slogLevelFromString(s string) slog.Level { } // daemonReadyProbe builds the readiness function installed via SetReadyFn. It -// performs Flux's local preflight (provider state, catalog, credentials, and +// performs Mux's local preflight (provider state, catalog, credentials, and // model selection) under a short timeout: // // - No session factory wired -> not ready ("engine not configured"). // - Preflight reports ready -> ready. -// - Preflight reports incomplete -> not ready with the failed Flux check. +// - Preflight reports incomplete -> not ready with the failed Mux check. // -// This never performs a paid/live model call — Flux preflight only inspects +// This never performs a paid/live model call — Mux 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, rhoconfig.EnginePreflightReport) + return daemonReadyProbeWithPreflight(factory, decoderconfig.EnginePreflightReport) } -func daemonReadyProbeWithPreflight(factory daemon.SessionFactory, preflight func(context.Context) rhoconfig.EnginePreflight) func() (bool, string) { +func daemonReadyProbeWithPreflight(factory daemon.SessionFactory, preflight func(context.Context) decoderconfig.EnginePreflight) func() (bool, string) { return func() (bool, string) { if factory == nil { return false, "engine not configured" } if preflight == nil { - return false, "Flux readiness probe not configured" + return false, "Mux readiness probe not configured" } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -362,13 +362,13 @@ func daemonReadyProbeWithPreflight(factory daemon.SessionFactory, preflight func if detail == "" { detail = "failed" } - return false, fmt.Sprintf("Flux %s: %s", check.Name, detail) + return false, fmt.Sprintf("Mux %s: %s", check.Name, detail) } } if ctx.Err() != nil { - return false, "Flux preflight timed out" + return false, "Mux preflight timed out" } - return false, "Flux preflight is not ready" + return false, "Mux preflight is not ready" } } @@ -399,14 +399,14 @@ func generateDaemonAPIKey() (string, error) { return base64.RawURLEncoding.EncodeToString(b[:]), nil } -// daemonAutonomyFromFlag resolves the --autonomy flag / RHO_DAEMON_AUTONOMY +// daemonAutonomyFromFlag resolves the --autonomy flag / DECODER_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) safety.AutonomyLevel { s = strings.TrimSpace(s) if s == "" { - s = os.Getenv("RHO_DAEMON_AUTONOMY") + s = os.Getenv("DECODER_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 179f428d0..854b38194 100644 --- a/cmd/daemon_ready_test.go +++ b/cmd/daemon_ready_test.go @@ -12,10 +12,10 @@ import ( "testing" "time" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/daemon" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/storage" ) // TestDaemonReadyProbe_NilFactory verifies the probe reports not-ready when no @@ -30,30 +30,30 @@ func TestDaemonReadyProbe_NilFactory(t *testing.T) { } } -// TestDaemonReadyProbe_FailedFluxPreflight verifies a factory alone does not -// make the daemon ready when Flux's authoritative preflight is incomplete. -func TestDaemonReadyProbe_FailedFluxPreflight(t *testing.T) { +// TestDaemonReadyProbe_FailedMuxPreflight verifies a factory alone does not +// make the daemon ready when Mux's authoritative preflight is incomplete. +func TestDaemonReadyProbe_FailedMuxPreflight(t *testing.T) { factory := func(daemon.ChatRequest) (*engine.Session, error) { return nil, nil } - probe := daemonReadyProbeWithPreflight(factory, func(context.Context) rhoconfig.EnginePreflight { - return rhoconfig.EnginePreflight{Ready: false} + probe := daemonReadyProbeWithPreflight(factory, func(context.Context) decoderconfig.EnginePreflight { + return decoderconfig.EnginePreflight{Ready: false} }) ok, reason := probe() if ok { - t.Fatal("failed Flux preflight must report not ready") + t.Fatal("failed Mux preflight must report not ready") } if reason == "" { - t.Fatal("failed Flux preflight must include a reason") + t.Fatal("failed Mux preflight must include a reason") } } -func TestDaemonReadyProbe_ReadyFluxPreflight(t *testing.T) { +func TestDaemonReadyProbe_ReadyMuxPreflight(t *testing.T) { factory := func(daemon.ChatRequest) (*engine.Session, error) { return nil, nil } - probe := daemonReadyProbeWithPreflight(factory, func(context.Context) rhoconfig.EnginePreflight { - return rhoconfig.EnginePreflight{Ready: true} + probe := daemonReadyProbeWithPreflight(factory, func(context.Context) decoderconfig.EnginePreflight { + return decoderconfig.EnginePreflight{Ready: true} }) ok, reason := probe() if !ok || reason != "" { - t.Fatalf("ready Flux preflight = (%v, %q), want (true, empty)", ok, reason) + t.Fatalf("ready Mux preflight = (%v, %q), want (true, empty)", ok, reason) } } @@ -62,8 +62,8 @@ func TestDaemonReadyProbe_ReadyFluxPreflight(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) rhoconfig.EnginePreflight { - return rhoconfig.EnginePreflight{Ready: false} + srv.SetReadyFn(daemonReadyProbeWithPreflight(factory, func(context.Context) decoderconfig.EnginePreflight { + return decoderconfig.EnginePreflight{Ready: false} })) addr, err := srv.Start() @@ -93,7 +93,7 @@ func TestDaemonReadyProbe_AffectsReadyEndpoint(t *testing.T) { defer resp.Body.Close() if resp.StatusCode != http.StatusServiceUnavailable { - t.Errorf("expected 503 from /v1/ready when Flux is not ready, got %d", resp.StatusCode) + t.Errorf("expected 503 from /v1/ready when Mux is not ready, got %d", resp.StatusCode) } var body daemon.ReadyResponse if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { diff --git a/cmd/rho/main.go b/cmd/decoder/main.go similarity index 79% rename from cmd/rho/main.go rename to cmd/decoder/main.go index fa7e5e433..ae0294b53 100644 --- a/cmd/rho/main.go +++ b/cmd/decoder/main.go @@ -7,15 +7,17 @@ import ( "os" "time" + "github.com/GrayCodeAI/decoder" + "golang.org/x/term" - "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" - internaltheme "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/cmd" + "github.com/GrayCodeAI/decoder/internal/crash" + "github.com/GrayCodeAI/decoder/internal/decodererr" + "github.com/GrayCodeAI/decoder/internal/mcp" + "github.com/GrayCodeAI/decoder/internal/observability/otellog" + "github.com/GrayCodeAI/decoder/internal/observability/oteltrace" + internaltheme "github.com/GrayCodeAI/decoder/internal/theme" ) // Version, Commit, and BuildDate are set at build time via ldflags. @@ -29,10 +31,10 @@ import ( // -X main.Commit=$(git rev-parse --short HEAD) // -X main.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ) // -// The "dev" / "none" / "unknown" defaults below apply only to local builds -// without ldflags so it's obvious when running an unreleased binary. +// Plain go build/go install embeds VERSION too; commit and date remain unknown +// unless build metadata is supplied. var ( - Version = "dev" + Version = "" Commit = "none" BuildDate = "unknown" ) @@ -43,14 +45,18 @@ func main() { // replaces existing signal handling (Bubble Tea, daemon shutdown). crash.Install() + if Version == "" { + Version = decoder.Version + } + // Handle --version flag immediately if len(os.Args) > 1 && os.Args[1] == "--version" { - fmt.Println("rho " + Version) + fmt.Println("decoder " + Version) return } - // Initialize OpenTelemetry telemetry (opt-in via RHO_ENABLE_TELEMETRY=1). - // Telemetry failures are non-fatal: rho continues with in-memory tracing only. + // Initialize OpenTelemetry telemetry (opt-in via DECODER_ENABLE_TELEMETRY=1). + // Telemetry failures are non-fatal: decoder continues with in-memory tracing only. telemetryProviders, telemetryErr := oteltrace.InitTelemetry(oteltrace.DefaultTelemetryConfig()) if telemetryErr != nil { fmt.Fprintln(os.Stderr, "warning: telemetry initialization failed:", telemetryErr) @@ -64,7 +70,7 @@ func main() { } // OTLP log-record export (DSH session-telemetry-otel port). Opt-in like - // tracing: RHO_ENABLE_TELEMETRY=1 plus an OTLP logs endpoint. The + // tracing: DECODER_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 { @@ -94,7 +100,7 @@ func main() { if errors.As(err, &exitErr) { os.Exit(exitErr.Code) } - os.Exit(rhoerr.ClassifyExitCode(err)) + os.Exit(decodererr.ClassifyExitCode(err)) } } diff --git a/cmd/rho_mascot.go b/cmd/decoder_mascot.go similarity index 55% rename from cmd/rho_mascot.go rename to cmd/decoder_mascot.go index 40c0549c7..fb1cec303 100644 --- a/cmd/rho_mascot.go +++ b/cmd/decoder_mascot.go @@ -5,14 +5,14 @@ import ( tea "charm.land/bubbletea/v2" - "github.com/GrayCodeAI/rho/internal/features/welcome" + "github.com/GrayCodeAI/decoder/internal/features/welcome" ) -func rhoMascotEnabled() bool { +func decoderMascotEnabled() bool { return welcome.Enabled() } -func emitRhoMascotCmd() tea.Cmd { +func emitDecoderMascotCmd() tea.Cmd { return func() tea.Msg { _ = welcome.Emit(os.Stderr) return nil diff --git a/cmd/diagnostics.go b/cmd/diagnostics.go index ce913295c..f06924bc2 100644 --- a/cmd/diagnostics.go +++ b/cmd/diagnostics.go @@ -12,17 +12,17 @@ import ( "strings" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/plugin" - "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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/resilience/health" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/stt" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) -func doctorReport(settings rhoconfig.Settings) string { +func doctorReport(settings decoderconfig.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 @@ -38,7 +38,7 @@ func doctorReport(settings rhoconfig.Settings) string { cwd, _ := os.Getwd() var b strings.Builder - b.WriteString("Rho doctor\n") + b.WriteString("Decoder 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)) @@ -55,7 +55,7 @@ func doctorReport(settings rhoconfig.Settings) string { // Ecosystem versions b.WriteString("\nEcosystem versions:\n") for _, component := range []struct{ directory, product string }{ - {directory: "flux", product: "Flux"}, + {directory: "mux", product: "Mux"}, } { versionFile := filepath.Join(filepath.Dir(cwd), component.directory, "VERSION") // #nosec G304 -- versionFile is built from a fixed sibling-repo list @@ -65,16 +65,16 @@ func doctorReport(settings rhoconfig.Settings) string { b.WriteString(fmt.Sprintf(" %s (%s): not checked out\n", component.product, component.directory)) } } - 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("\n" + decoderconfig.FormatEcosystemPanel(context.Background(), providerName, modelName) + "\n") + b.WriteString("\n" + decoderconfig.FormatCatalogHealth(decoderconfig.CatalogHealthReport(context.Background())) + "\n") + preflight := decoderconfig.EnginePreflightReportWithSettings(context.Background(), settings, decoderconfig.EnginePreflightOptions{}) + b.WriteString("\n" + decoderconfig.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" + rhoconfig.CredentialStorageStatus(context.Background()).Formatted + "\n") - if deployReport, err := rhoconfig.DeploymentStatusReportWithSettings(context.Background(), settings, modelName); err == nil { + b.WriteString("\n" + decoderconfig.CredentialStorageStatus(context.Background()).Formatted + "\n") + if deployReport, err := decoderconfig.DeploymentStatusReportWithSettings(context.Background(), settings, modelName); err == nil { b.WriteString("\n" + deployReport + "\n") } b.WriteString("\n" + envSummaryWithSelection(providerName, modelName, false) + "\n") @@ -86,18 +86,18 @@ func doctorReport(settings rhoconfig.Settings) string { } // Project instructions - if md := rhoconfig.LoadAgentsMD(); md != "" { + if md := decoderconfig.LoadAgentsMD(); md != "" { b.WriteString("\nProject instructions: found\n") } else { b.WriteString("\nProject instructions: not found (consider creating AGENTS.md)\n") } - // Installed skills (rho ships none; skills come from user/marketplace installs) + // Installed skills (decoder 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 `rho skills install`)\n") + b.WriteString("Installed skills: none (install with `decoder skills install`)\n") } // Warn when installed skills drifted from the lockfile pins (content // changed or SKILL.md removed after install). Warn-only: local edits are @@ -120,7 +120,7 @@ func doctorReport(settings rhoconfig.Settings) string { // Session recovery status recoveryCandidates := session.ScanForRecovery() if len(recoveryCandidates) > 0 { - b.WriteString(fmt.Sprintf("\nInterrupted sessions: %d (run rho recover)\n", len(recoveryCandidates))) + b.WriteString(fmt.Sprintf("\nInterrupted sessions: %d (run decoder recover)\n", len(recoveryCandidates))) } else { b.WriteString("\nInterrupted sessions: none\n") } @@ -142,14 +142,14 @@ func backendStatus(name string, enabled bool) string { return "wired (" + name + ")" } -func healthCheckReport(settings rhoconfig.Settings, provider string) string { +func healthCheckReport(settings decoderconfig.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 := rhoconfig.ValidateSettings(settings) + result := decoderconfig.ValidateSettings(settings) if result.Valid { return health.Check{Name: "config", Status: health.Healthy, Message: "Configuration valid"} } @@ -211,7 +211,7 @@ func providerCredentialHealthChecker(provider string) health.Checker { status := health.Unhealthy message := label + " credential not configured" - if providerID != "" && rhoconfig.HasStoredCredentialForProvider(ctx, providerID) { + if providerID != "" && decoderconfig.HasStoredCredentialForProvider(ctx, providerID) { status = health.Healthy message = label + " credential configured" } @@ -229,19 +229,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(rhoconfig.ActiveGateway(ctx)) + provider = strings.TrimSpace(decoderconfig.ActiveGateway(ctx)) if provider == "" || strings.EqualFold(provider, "auto") { - provider = strings.TrimSpace(rhoconfig.EffectiveSelection(ctx, rhoconfig.SelectionOptions{}).Provider) + provider = strings.TrimSpace(decoderconfig.EffectiveSelection(ctx, decoderconfig.SelectionOptions{}).Provider) } } - return rhoconfig.ActiveProviderID(provider) + return decoderconfig.ActiveProviderID(provider) } -func settingsSummary(settings rhoconfig.Settings) string { +func settingsSummary(settings decoderconfig.Settings) string { return configCommandSummary(settings) } -func mcpConfigSummary(settings rhoconfig.Settings) string { +func mcpConfigSummary(settings decoderconfig.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 66a4987af..0f8957330 100644 --- a/cmd/diagnostics_test.go +++ b/cmd/diagnostics_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/resilience/health" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/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 := rhoconfig.Settings{} + settings := decoderconfig.Settings{} report := doctorReport(settings) if report == "" { t.Error("doctorReport should produce non-empty output") @@ -33,14 +33,14 @@ func TestDoctorReport(t *testing.T) { if !strings.Contains(report, "Version") { t.Error("report should mention version") } - if !strings.Contains(report, "Ecosystem (flux · token engine)") { + if !strings.Contains(report, "Ecosystem (mux · token engine)") { t.Error("report should include ecosystem panel") } } func TestDoctorReportProviderModelOrder(t *testing.T) { t.Parallel() - settings := rhoconfig.Settings{ + settings := decoderconfig.Settings{ Model: "claude-sonnet-4-20250514", Provider: "anthropic", } @@ -56,14 +56,14 @@ func TestDoctorReportProviderModelOrder(t *testing.T) { func TestDoctorReportUsesResolvedProviderForChecks(t *testing.T) { isolateCredentialHome(t) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() gateway.SetDefaultStore(&gateway.MapStore{}) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) - report := doctorReport(rhoconfig.Settings{ + report := doctorReport(decoderconfig.Settings{ Model: "claude-sonnet-4-20250514", Provider: "anthropic", }) @@ -74,23 +74,23 @@ func TestDoctorReportUsesResolvedProviderForChecks(t *testing.T) { func TestProviderCredentialHealthCheckerResolvesAuto(t *testing.T) { isolateCredentialHome(t) - t.Setenv("FLUX_CONFIG_DIR", t.TempDir()) - rhoconfig.InvalidateConfigUICache() + t.Setenv("MUX_CONFIG_DIR", t.TempDir()) + decoderconfig.InvalidateConfigUICache() store := &contextRecordingCredentialStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.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 := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + if err := decoderconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := decoderconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } store.contextValue = nil @@ -117,12 +117,12 @@ func TestProviderCredentialHealthCheckerResolvesAuto(t *testing.T) { func TestProviderCredentialHealthCheckerMissingIsUnhealthy(t *testing.T) { isolateCredentialHome(t) - t.Setenv("FLUX_CONFIG_DIR", t.TempDir()) - rhoconfig.InvalidateConfigUICache() + t.Setenv("MUX_CONFIG_DIR", t.TempDir()) + decoderconfig.InvalidateConfigUICache() gateway.SetDefaultStore(&gateway.MapStore{}) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) result := providerCredentialHealthChecker("openai")(context.Background()) @@ -142,7 +142,7 @@ func TestProviderCredentialHealthCheckerMissingIsUnhealthy(t *testing.T) { func TestSettingsSummary(t *testing.T) { t.Parallel() - settings := rhoconfig.Settings{ + settings := decoderconfig.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 := rhoconfig.Settings{} + settings := decoderconfig.Settings{} summary := mcpConfigSummary(settings) if summary == "" { t.Error("mcpConfigSummary should produce output") diff --git a/cmd/dx.go b/cmd/dx.go index ab3bfcca7..6264af107 100644 --- a/cmd/dx.go +++ b/cmd/dx.go @@ -10,19 +10,19 @@ import ( "strings" "time" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/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 rhoconfig.Settings) string { +func doctorOutput(settings decoderconfig.Settings) string { var b strings.Builder - b.WriteString("=== Rho Doctor ===\n\n") + b.WriteString("=== Decoder Doctor ===\n\n") // Go version, OS, arch b.WriteString("System:\n") @@ -49,12 +49,12 @@ func doctorOutput(settings rhoconfig.Settings) string { b.WriteString(fmt.Sprintf(" TERM: %s\n", termVal)) b.WriteString(fmt.Sprintf(" COLORTERM: %s\n", colorTerm)) - // Rho version + // Decoder version v := version if v == "" { v = "(dev)" } - b.WriteString("\nRho:\n") + b.WriteString("\nDecoder:\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 rhoconfig.Settings) string { } b.WriteString("\nProvider:\n") b.WriteString(fmt.Sprintf(" Provider: %s\n", effectiveProvider)) - b.WriteString(fmt.Sprintf(" API key: %s\n", maskedKeyStatus(rhoconfig.ActiveProvider(context.Background())))) + b.WriteString(fmt.Sprintf(" API key: %s\n", maskedKeyStatus(decoderconfig.ActiveProvider(context.Background())))) - // Model configured (flux provider.json) - effectiveModel := strings.TrimSpace(rhoconfig.ActiveModel(context.Background())) + // Model configured (mux provider.json) + effectiveModel := strings.TrimSpace(decoderconfig.ActiveModel(context.Background())) if effectiveModel == "" { effectiveModel = "(not configured)" } @@ -111,7 +111,7 @@ func doctorOutput(settings rhoconfig.Settings) string { } // AGENTS.md found - agentsMD := rhoconfig.LoadAgentsMD() + agentsMD := decoderconfig.LoadAgentsMD() if agentsMD != "" { b.WriteString("AGENTS.md: found\n") } else { @@ -146,7 +146,7 @@ func doctorOutput(settings rhoconfig.Settings) string { // doctorJSON returns the doctor diagnostics as indented JSON, mirroring the // fields of doctorOutput but as machine-parseable structured data. -func doctorJSON(settings rhoconfig.Settings) string { +func doctorJSON(settings decoderconfig.Settings) string { type sessionDirInfo struct { Path string `json:"path"` Status string `json:"status"` @@ -165,7 +165,7 @@ func doctorJSON(settings rhoconfig.Settings) string { if effectiveProvider == "" { effectiveProvider = "(not configured)" } - effectiveModel := strings.TrimSpace(rhoconfig.ActiveModel(context.Background())) + effectiveModel := strings.TrimSpace(decoderconfig.ActiveModel(context.Background())) if effectiveModel == "" { effectiveModel = "(not configured)" } @@ -219,7 +219,7 @@ func doctorJSON(settings rhoconfig.Settings) string { plugins = len(manifests) } - agentsMD := rhoconfig.LoadAgentsMD() + agentsMD := decoderconfig.LoadAgentsMD() agentsState := "not found" if agentsMD != "" { agentsState = "found" @@ -274,7 +274,7 @@ func doctorJSON(settings rhoconfig.Settings) string { Version: v, BuildDate: buildDate, Provider: effectiveProvider, - APIKey: maskedKeyStatus(rhoconfig.ActiveProvider(context.Background())), + APIKey: maskedKeyStatus(decoderconfig.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 := rhoconfig.EnvKeyStatus(provider) + status := decoderconfig.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("# Rho Session: %s\n\n", sessionID)) + b.WriteString(fmt.Sprintf("# Decoder 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("rho-session-%s.md", sessionID) + filename := fmt.Sprintf("decoder-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("rho-session-%s.json", sessionID) + filename := fmt.Sprintf("decoder-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 e730ccac3..21bfc3389 100644 --- a/cmd/dx_test.go +++ b/cmd/dx_test.go @@ -7,15 +7,15 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/tool" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/tool" ) func TestDoctorOutputContainsSections(t *testing.T) { preserveCLICompilerVersionState(t) version = "test-dx-version" - settings := rhoconfig.Settings{ + settings := decoderconfig.Settings{ Provider: "openai", Model: "gpt-4o", } @@ -23,7 +23,7 @@ func TestDoctorOutputContainsSections(t *testing.T) { out := doctorOutput(settings) sections := []string{ - "Rho Doctor", + "Decoder 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 := rhoconfig.Settings{ + settings := decoderconfig.Settings{ Provider: "anthropic", Model: "claude-sonnet-4-20250514", - MCPServers: []rhoconfig.MCPServerConfig{ + MCPServers: []decoderconfig.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 := rhoconfig.Settings{ + settings := decoderconfig.Settings{ Provider: "anthropic", Model: "claude-sonnet-4-20250514", - MCPServers: []rhoconfig.MCPServerConfig{ + MCPServers: []decoderconfig.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 rho"}, + {role: "user", content: "Hello decoder"}, {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, "# Rho Session: test-export-id") { + if !strings.Contains(content, "# Decoder Session: test-export-id") { t.Errorf("export missing session header") } - if !strings.Contains(content, "Hello rho") { + if !strings.Contains(content, "Hello decoder") { 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 aefec2184..6ee8d8006 100644 --- a/cmd/ecosystem.go +++ b/cmd/ecosystem.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" "github.com/spf13/cobra" ) @@ -12,8 +12,8 @@ var ecosystemJSON bool var ecosystemCmd = &cobra.Command{ Use: "ecosystem", - Short: "Show flux and token-pipeline integration status", - Long: "Print the ecosystem panel summarizing the LLM provider runtime (flux) and the local token/compression pipeline. Same block as the top of rho doctor.", + Short: "Show mux and token-pipeline integration status", + Long: "Print the ecosystem panel summarizing the LLM provider runtime (mux) and the local token/compression pipeline. Same block as the top of decoder 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 := rhoconfig.BuildEcosystemReport(context.Background(), providerName, modelName) + report := decoderconfig.BuildEcosystemReport(context.Background(), providerName, modelName) enc := json.NewEncoder(cmd.OutOrStdout()) enc.SetIndent("", " ") return enc.Encode(report) } - cmd.Println(rhoconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)) + cmd.Println(decoderconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)) return nil }, } diff --git a/cmd/ecosystem_test.go b/cmd/ecosystem_test.go index 9b2578e6b..e86d86bae 100644 --- a/cmd/ecosystem_test.go +++ b/cmd/ecosystem_test.go @@ -4,17 +4,17 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func TestEcosystemCmdRuns(t *testing.T) { - settings := rhoconfig.Settings{} + settings := decoderconfig.Settings{} model, provider := effectiveModelAndProvider(settings) if provider == "" { provider = "auto" } - out := rhoconfig.FormatEcosystemPanel(t.Context(), provider, model) - if !strings.Contains(out, "Ecosystem (flux · token engine)") { + out := decoderconfig.FormatEcosystemPanel(t.Context(), provider, model) + if !strings.Contains(out, "Ecosystem (mux · token engine)") { t.Fatalf("unexpected panel: %q", out) } if err := ecosystemCmd.RunE(ecosystemCmd, nil); err != nil { diff --git a/cmd/error_classify.go b/cmd/error_classify.go index 4fc8b2163..7d3895e4e 100644 --- a/cmd/error_classify.go +++ b/cmd/error_classify.go @@ -4,41 +4,41 @@ import ( "fmt" "strings" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/errhint" - "github.com/GrayCodeAI/rho/internal/rhoerr" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/decodererr" + "github.com/GrayCodeAI/decoder/internal/errhint" ) // friendlyErrorMessage returns the user-friendly message for an error. -// Delegates to the shared rhoerr.ClassifyErrorMessage for the base message, +// Delegates to the shared decodererr.ClassifyErrorMessage for the base message, // then enriches specific cases with dynamic hints that require the config -// package (rhoerr can't import internal/config without a cycle). +// package (decodererr can't import internal/config without a cycle). func friendlyErrorMessage(err error) string { - msg := rhoerr.ClassifyErrorMessage(err) + msg := decodererr.ClassifyErrorMessage(err) if err == nil { return msg } - ec := rhoerr.ClassifyError(err) + ec := decodererr.ClassifyError(err) low := strings.ToLower(err.Error()) switch ec.ExitCode { - case rhoerr.ExitNotFound: + case decodererr.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 := rhoconfig.ExampleModelHints() + ex1, ex2 := decoderconfig.ExampleModelHints() msg = fmt.Sprintf( - "Model not found. Check your model name with /model.\n Examples from the flux catalog: %s, %s\n Use /models to list all models, or /config to change provider.", + "Model not found. Check your model name with /model.\n Examples from the mux catalog: %s, %s\n Use /models to list all models, or /config to change provider.", ex1, ex2, ) } - case rhoerr.ExitAuth: + case decodererr.ExitAuth: msg += "\n Check your API key with /config. Keys can expire or be revoked." - case rhoerr.ExitNetwork: + case decodererr.ExitNetwork: msg += "\n Check your internet connection. If you're behind a proxy, configure it with /config." - case rhoerr.ExitTimeout: + case decodererr.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 057af69b8..c10374900 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 (rho's own + // A local error must not draw an errhint provider hint (decoder'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 57f7dc564..a7f38fb82 100644 --- a/cmd/errors.go +++ b/cmd/errors.go @@ -11,8 +11,8 @@ import ( "sync" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/storage" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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 -// Rho's user state crash log, and exits with a user-friendly message. +// Decoder'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: rho-YYMMDD-<6 hex chars from stack hash> + // Format: decoder-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, "\nrho encountered an unexpected error and needs to exit.\n") + _, _ = fmt.Fprintf(os.Stderr, "\ndecoder 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/rho/issues\n") + _, _ = fmt.Fprintf(os.Stderr, "Please report this at: https://github.com/GrayCodeAI/decoder/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: rho-YYMMDD-<6 hex chars> — enough to correlate with crash.log +// Format: decoder-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("rho-%s-%06x", time.Now().Format("060102"), hash&0xFFFFFF) + return fmt.Sprintf("decoder-%s-%06x", time.Now().Format("060102"), hash&0xFFFFFF) } // ─── errorLogger ────────────────────────────────────────────────────────────── -// Writes errors to Rho's user state error log with timestamps. Thread-safe. +// Writes errors to Decoder'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 Rho error log. +// LogError writes a timestamped error entry to the Decoder 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 Rho error log. +// LogErrorf writes a formatted, timestamped error entry to the Decoder 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 rhoconfig.Settings) []StartupWarning { +func validateStartup(settings decoderconfig.Settings) []StartupWarning { var warnings []StartupWarning // 1. Check API key for configured provider. - // Rho reads credentials from the OS secret store (macOS Keychain / + // Decoder 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(rhoconfig.ActiveProvider(context.Background())) + providerName = strings.TrimSpace(decoderconfig.ActiveProvider(context.Background())) } if providerName != "" && providerName != "ollama" { hasEnv := false - if envKey := rhoconfig.ProviderAPIKeyEnv(providerName); envKey != "" { + if envKey := decoderconfig.ProviderAPIKeyEnv(providerName); envKey != "" { hasEnv = os.Getenv(envKey) != "" } - hasStored := rhoconfig.HasStoredCredentialForProvider(context.Background(), providerName) + hasStored := decoderconfig.HasStoredCredentialForProvider(context.Background(), providerName) if !hasEnv && !hasStored { - envKey := rhoconfig.ProviderAPIKeyEnv(providerName) + envKey := decoderconfig.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 rhoconfig.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 rhoconfig.Settings) string { +func checkNetworkReachability(settings decoderconfig.Settings) string { providerName := strings.TrimSpace(settings.Provider) if providerName == "" { - providerName = strings.TrimSpace(rhoconfig.ActiveProvider(context.Background())) + providerName = strings.TrimSpace(decoderconfig.ActiveProvider(context.Background())) } if providerName == "" || providerName == "ollama" { return "" @@ -244,7 +244,7 @@ func checkNetworkReachability(settings rhoconfig.Settings) string { if host == "" { return "" } - if _, err := net.LookupHost(host); err != nil { + if _, err := net.DefaultResolver.LookupHost(context.Background(), host); err != nil { return fmt.Sprintf("Cannot resolve %s. Check your internet connection.", host) } return "" @@ -252,5 +252,5 @@ func checkNetworkReachability(settings rhoconfig.Settings) string { // providerDNSHost returns a hostname to check DNS resolution for a provider. func providerDNSHost(provider string) string { - return rhoconfig.GatewayDNSHost(provider) + return decoderconfig.GatewayDNSHost(provider) } diff --git a/cmd/errors_test.go b/cmd/errors_test.go index aed33822c..6cc28f132 100644 --- a/cmd/errors_test.go +++ b/cmd/errors_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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 := rhoconfig.ExampleModelHints() + example1, example2 := decoderconfig.ExampleModelHints() if !strings.Contains(got, example1) || !strings.Contains(got, example2) { t.Errorf("friendlyError(%q) = %q, should suggest valid model names", tt.errMsg, got) } @@ -582,7 +582,7 @@ func TestFriendlyErrorBackwardCompat(t *testing.T) { err string contains string }{ - {"rate limit 429", "flux: openai stream request failed: max retries (3) exceeded: HTTP 429", "Rate limited"}, + {"rate limit 429", "mux: openai stream request failed: max retries (3) exceeded: HTTP 429", "Rate limited"}, {"unauthorized 401", "HTTP 401 Unauthorized", "Authentication failed"}, {"forbidden 403", "HTTP 403 Forbidden", "Access denied"}, {"not found 404", "model not found", "/model"}, @@ -604,8 +604,8 @@ func TestFriendlyErrorBackwardCompat(t *testing.T) { } // helper for tests -func emptySettings() rhoconfig.Settings { - return rhoconfig.Settings{} +func emptySettings() decoderconfig.Settings { + return decoderconfig.Settings{} } // ── signalHandler test ──────────────────────────────────────────────────────── diff --git a/cmd/eval.go b/cmd/eval.go index c031b08aa..f3f6aced4 100644 --- a/cmd/eval.go +++ b/cmd/eval.go @@ -9,10 +9,10 @@ import ( "strings" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/features/eval" - "github.com/GrayCodeAI/rho/internal/features/evalloop" - "github.com/GrayCodeAI/rho/internal/tool" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/features/eval" + "github.com/GrayCodeAI/decoder/internal/features/evalloop" + "github.com/GrayCodeAI/decoder/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 := rhoconfig.LoadGlobalSettings() + settings := decoderconfig.LoadGlobalSettings() ctx := context.Background() - gw, err := rhoconfig.NewFluxEngineForSettings(settings) + gw, err := decoderconfig.NewMuxEngineForSettings(settings) if err != nil { return fmt.Errorf("eval loop: build engine client: %w", err) } model := strings.TrimSpace(evalLoopModel) if model == "" { - model = strings.TrimSpace(rhoconfig.ActiveModel(ctx)) + model = strings.TrimSpace(decoderconfig.ActiveModel(ctx)) } if model == "" { model = strings.TrimSpace(settings.Model) } - workDir, err := os.MkdirTemp("", "rho-eval-loop-*") + workDir, err := os.MkdirTemp("", "decoder-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: "rho-eval", Tasks: tasks} + suite := &eval.BenchmarkSuite{Name: "decoder-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 8bd13525b..35c107f62 100644 --- a/cmd/eval_tools.go +++ b/cmd/eval_tools.go @@ -6,9 +6,9 @@ import ( "fmt" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/features/eval" - "github.com/GrayCodeAI/rho/internal/types" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/features/eval" + "github.com/GrayCodeAI/decoder/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 rho's standard tools. +// negative tool-trigger situations against decoder'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 := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() registry, err := defaultRegistry(settings) if err != nil { @@ -79,18 +79,18 @@ func runEvalTools(cmd *cobra.Command, _ []string) error { return err } modelName, providerName := effectiveModelAndProvider(settings) - sess, err := newConfiguredRhoSession(settings, providerName, modelName, systemPrompt, registry, nil) + sess, err := newConfiguredDecoderSession(settings, providerName, modelName, systemPrompt, registry, nil) if err != nil { return err } - tools := registry.FluxTools() + tools := registry.MuxTools() // caller performs one tool-aware turn and reports the first tool the model // chose (if any). It does not execute the tool — we are scoring selection, // not effects. caller := func(ctx context.Context, c eval.ToolUseCase) (eval.ObservedCall, error) { - resp, err := sess.Chat(ctx, []types.FluxMessage{ + resp, err := sess.Chat(ctx, []types.MuxMessage{ {Role: "user", Content: c.Prompt}, }, types.ChatOptions{Model: model, Tools: tools}) if err != nil { diff --git a/cmd/exec.go b/cmd/exec.go index 6aa325339..004cc0f44 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -14,17 +14,17 @@ import ( "time" "unicode/utf8" - "github.com/GrayCodeAI/rho/internal/engine/safety" - - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "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/GrayCodeAI/decoder/internal/engine/safety" + + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/errhint" + "github.com/GrayCodeAI/decoder/internal/multiagent/agents" + "github.com/GrayCodeAI/decoder/internal/notify" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + cloud "github.com/GrayCodeAI/decoder/internal/platform/cloud" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/spf13/cobra" ) @@ -94,13 +94,13 @@ Autonomy Levels: yolo Never ask for permission Examples: - 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"`, + decoder exec "analyze this codebase" + decoder exec --auto full "fix the tests and commit" + decoder exec --json "what files are in src/" + decoder exec --ephemeral --json "run tests and report" > result.json + echo "explain main.go" | decoder exec - + decoder exec --agent reviewer "review the latest commit" + decoder exec --model claude-sonnet-4-6 "quick fix: typo in README"`, Args: cobra.MaximumNArgs(1), RunE: runExec, } @@ -111,7 +111,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 Rho user state)") + execCmd.Flags().StringVar(&execAgent, "agent", "", "Agent persona to use (from Decoder 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") @@ -177,7 +177,7 @@ func runExec(_ *cobra.Command, args []string) error { base := getCurrentBranch(cwd) branch := execWorktreeName if branch == "" { - branch = fmt.Sprintf("rho-exec/%d-%s", start.UnixMilli(), randomHex(4)) + branch = fmt.Sprintf("decoder-exec/%d-%s", start.UnixMilli(), randomHex(4)) } var wtErr error wtPath, wtErr = createExecWorktree(cwd, base, branch) @@ -192,7 +192,7 @@ func runExec(_ *cobra.Command, args []string) error { } // Load settings - settings := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() // Build system prompt systemPrompt, err := buildSystemPrompt() @@ -228,7 +228,7 @@ func runExec(_ *cobra.Command, args []string) error { } // Create engine session - sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) + sess, cfgErr := newConfiguredDecoderSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) if cfgErr != nil { return cfgErr } @@ -245,11 +245,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 - // RHO_GHA_TRUST_EVENT=1. + // DECODER_GHA_TRUST_EVENT=1. if ghaCtx.Active && !ghaCtx.Trusted { const ceiling = safety.AutonomyBasic if sess.PermSvc().RuntimeState().Autonomy > ceiling { - fmt.Fprintf(os.Stderr, "%s\n", auditTint(fmt.Sprintf("rho: untrusted GitHub event (author_association=%q); capping autonomy at %s", ghaCtx.AuthorAssociation, ceiling), warnAmber)) + fmt.Fprintf(os.Stderr, "%s\n", auditTint(fmt.Sprintf("decoder: untrusted GitHub event (author_association=%q); capping autonomy at %s", ghaCtx.AuthorAssociation, ceiling), warnAmber)) sess.PermSvc().SetAutonomy(ceiling) } } @@ -379,7 +379,7 @@ func runExec(_ *cobra.Command, args []string) error { DeviceID: cfg.DeviceID, ProjectID: cfg.ProjectID, SessionID: sessionID, - Capability: cloud.CapabilityRho, + Capability: cloud.CapabilityDecoder, Model: effectiveModel, InputTokens: totalIn, OutputTokens: totalOut, @@ -401,10 +401,10 @@ func runExec(_ *cobra.Command, args []string) error { fmt.Println() } if !IsQuiet() { - summary := fmt.Sprintf("rho: %d tokens in / %d out · %d turn(s) · %s · %s", + summary := fmt.Sprintf("decoder: %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("rho: %d tokens in / %d out (cache %d read · %d write) · %d turn(s) · %s · %s", + summary = fmt.Sprintf("decoder: %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)) @@ -469,16 +469,16 @@ type GHAMode string const ( // GHAModeNone means we are not running inside GitHub Actions. GHAModeNone GHAMode = "" - // GHAModeInteractive is used when a human mentioned @rho in a comment and + // GHAModeInteractive is used when a human mentioned @decoder in a comment and // expects a conversational reply. GHAModeInteractive GHAMode = "interactive" - // GHAModeAutomation is used for label/issue triggers where rho should act + // GHAModeAutomation is used for label/issue triggers where decoder 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 = "@rho" +const ghMention = "@decoder" // ghTrustedAssociations are the GitHub author_association values that identify // a repository insider. Everyone else (CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR, @@ -496,7 +496,7 @@ type GHAContext struct { EventName string // GITHUB_EVENT_NAME Mode GHAMode // resolved operating mode Prompt string // event-derived prompt body - Mention bool // whether an @rho mention was found in a comment + Mention bool // whether an @decoder 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) } @@ -529,7 +529,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). - // RHO_GHA_TRUST_EVENT=1 lets a maintainer opt into trusting all events. + // DECODER_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) @@ -557,7 +557,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("RHO_GHA_TRUST_EVENT"))) { + switch strings.ToLower(strings.TrimSpace(getenv("DECODER_GHA_TRUST_EVENT"))) { case "1", "true", "yes", "on": return true default: @@ -614,7 +614,7 @@ func ghIssueBody(payload map[string]interface{}) string { return strings.TrimSpace(ghCommentBody(payload)) } -// ghStripMention removes the leading @rho mention from a comment so the +// ghStripMention removes the leading @decoder mention from a comment so the // remaining text becomes the prompt. func ghStripMention(body string) string { out := body @@ -636,7 +636,7 @@ type skillRunner interface { Run(name string) (string, error) } -// pluginSkillRunner is the production skillRunner backed by Rho skill storage. +// pluginSkillRunner is the production skillRunner backed by Decoder skill storage. type pluginSkillRunner struct{} func (pluginSkillRunner) Run(name string) (string, error) { @@ -646,7 +646,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 `rho skills` to list available skills)", name) + return "", fmt.Errorf("skill %q not found (run `decoder skills` to list available skills)", name) } // defaultSkillRunner is overridable in tests. @@ -775,7 +775,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("rho-exec/%d-fanout%d-%s", start.UnixMilli(), i, randomHex(4)) + branch := fmt.Sprintf("decoder-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) @@ -844,7 +844,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: "rho exec --fanout", OK: anyOK, Body: fanoutSummaryLines(attempts), + Title: title, Source: "decoder exec --fanout", OK: anyOK, Body: fanoutSummaryLines(attempts), }) if !anyOK { @@ -908,7 +908,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 := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() systemPrompt, err := buildSystemPrompt() if err != nil { @@ -931,7 +931,7 @@ func execOnceInWorktree(prompt string, attemptIdx int) (*ExecResult, error) { if err != nil { return nil, err } - sess, cfgErr := newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, logger.New(io.Discard, logger.Error), execMaxTurns) + sess, cfgErr := newConfiguredDecoderSession(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 59e24400d..94abe815e 100644 --- a/cmd/exec_test.go +++ b/cmd/exec_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/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":"@rho please fix the failing test"}}` + payload := `{"comment":{"body":"@decoder 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":"@Rho explain this change"}}` + payload := `{"comment":{"body":"@Decoder 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", - "RHO_GHA_TRUST_EVENT": "1", + "GITHUB_ACTIONS": "true", + "GITHUB_EVENT_NAME": "issues", + "GITHUB_EVENT_PATH": "/tmp/event.json", + "DECODER_GHA_TRUST_EVENT": "1", } outsider := `{"issue":{"title":"t","body":"b","author_association":"NONE"}}` if gha := detectGitHubActions(envFunc(env), fileFunc(outsider)); !gha.Trusted { - t.Error("RHO_GHA_TRUST_EVENT=1 should trust even NONE association") + t.Error("DECODER_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("RHO_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("DECODER_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: "rho-exec/123", + Branch: "decoder-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 != "rho-exec/123" { + if decoded.Branch != "decoder-exec/123" { t.Errorf("expected branch, got %s", decoded.Branch) } } diff --git a/cmd/execution_graph.go b/cmd/execution_graph.go index 58d8fa5fd..2766a1ea8 100644 --- a/cmd/execution_graph.go +++ b/cmd/execution_graph.go @@ -9,14 +9,14 @@ import ( "strings" "time" - 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" + graphcontracts "github.com/GrayCodeAI/decoder/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/decoder/internal/contracts/policy" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + "github.com/GrayCodeAI/decoder/internal/fsutil" + "github.com/GrayCodeAI/decoder/internal/graphjournal" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/taskruntime" + "github.com/GrayCodeAI/decoder/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 Rho-owned sessions, task requests, structured tasks, runtime tasks, + Long: `Project Decoder-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 Rho session or mission as graph JSON", + Short: "Export a Decoder 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: "rho/session/" + saved.ID, + ID: "decoder/session/" + saved.ID, } if _, ok := toolCallIDs[entry.ToolCallID]; ok && entry.ToolCallID != "" { subject = graphcontracts.Ref{ Kind: graphcontracts.NodeExecution, - ID: "rho/tool-call/" + saved.ID + "/" + entry.ToolCallID, + ID: "decoder/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 4c5cee66a..c1367836b 100644 --- a/cmd/execution_graph_test.go +++ b/cmd/execution_graph_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" - 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" + graphcontracts "github.com/GrayCodeAI/decoder/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/decoder/internal/contracts/policy" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + "github.com/GrayCodeAI/decoder/internal/graphjournal" + "github.com/GrayCodeAI/decoder/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: "rho/mission/m1"} + mission := graphcontracts.Ref{Kind: graphcontracts.NodeExecution, ID: "decoder/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: "rho"}, + Provenance: graphcontracts.Provenance{Producer: "decoder"}, }}, Events: []graphcontracts.Event{{ - ID: "rho/event/mission/m1/created", Type: graphcontracts.EventCreated, + ID: "decoder/event/mission/m1/created", Type: graphcontracts.EventCreated, Subject: mission, OccurredAt: now, - Provenance: graphcontracts.Provenance{Producer: "rho"}, + Provenance: graphcontracts.Provenance{Producer: "decoder"}, }}, } dir := t.TempDir() @@ -62,7 +62,7 @@ func TestLoadMissionGraphExportValidatesTopology(t *testing.T) { t.Fatalf("loaded graph = %#v", loaded) } - export.Events[0].Subject.ID = "rho/mission/missing" + export.Events[0].Subject.ID = "decoder/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/rho"); got != "custom" { + if got := executionGraphRepositoryID("custom", "/work/decoder"); got != "custom" { t.Fatalf("override repository ID = %q, want custom", got) } - if got := executionGraphRepositoryID("", "/work/rho"); got != "rho" { - t.Fatalf("derived repository ID = %q, want rho", got) + if got := executionGraphRepositoryID("", "/work/decoder"); got != "decoder" { + t.Fatalf("derived repository ID = %q, want decoder", got) } } @@ -97,11 +97,11 @@ func TestValidateSwiftCheckpointID(t *testing.T) { } func TestExecutionGraphExportCommand(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_STATE_DIR", t.TempDir()) saved := &session.Session{ ID: "graph-command-session", - CWD: "/workspace/rho", + CWD: "/workspace/decoder", 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 != "rho" { - t.Fatalf("Scope.RepositoryID = %q, want rho", export.Scope.RepositoryID) + if export.Scope.RepositoryID != "decoder" { + t.Fatalf("Scope.RepositoryID = %q, want decoder", 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, "rho/policy/") { + if !hasExportNodePrefix(export, "decoder/policy/") { t.Fatal("graph export omitted automatic policy observation") } - if !hasExportNodePrefix(export, "rho/verification/") { + if !hasExportNodePrefix(export, "decoder/verification/") { t.Fatal("graph export omitted automatic verification observation") } if !hasExportNodePrefix(export, "memory/") { diff --git a/cmd/features.go b/cmd/features.go index 9941d7c5c..9a982e323 100644 --- a/cmd/features.go +++ b/cmd/features.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - feature "github.com/GrayCodeAI/rho/internal/features" + feature "github.com/GrayCodeAI/decoder/internal/features" "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: - RHO_FEATURE_=1 rho daemon start + DECODER_FEATURE_=1 decoder daemon start Show a specific flag: - rho features get `, + decoder 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: rho features get ") + return fmt.Errorf("usage: decoder 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 := "RHO_FEATURE_" + strings.ReplaceAll(strings.ToUpper(args[1]), "-", "_") + envVar := "DECODER_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:", rhoColor)) + fmt.Println(auditTint("Feature Flags:", decoderColor)) 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 := "RHO_FEATURE_" + strings.ReplaceAll(strings.ToUpper(name), "-", "_") + envVar := "DECODER_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 7a4ea11d9..5703b2441 100644 --- a/cmd/feedback.go +++ b/cmd/feedback.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/storage" "github.com/spf13/cobra" ) @@ -37,17 +37,17 @@ type FeedbackReport struct { var feedbackCmd = &cobra.Command{ Use: "feedback [message]", - Short: "Submit feedback about rho", - Long: `Capture feedback about your rho experience. By default, opens a + Short: "Submit feedback about decoder", + Long: `Capture feedback about your decoder experience. By default, opens a GitHub issue template URL in your browser. Use --local to save feedback -to Rho's user state directory for later submission. +to Decoder's user state directory for later submission. Categories: bug, feature, ux, performance, other Examples: - rho feedback "The completion is slow" - rho feedback --category bug "Crash when using /compact" - rho feedback --local "Wish it could do X"`, + decoder feedback "The completion is slow" + decoder feedback --category bug "Crash when using /compact" + decoder 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/rho/issues/new?title=%s&body=%s&labels=%s", + "https://github.com/GrayCodeAI/decoder/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 25432e2b0..85a2aba01 100644 --- a/cmd/fingerprint.go +++ b/cmd/fingerprint.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/GrayCodeAI/rho/internal/features/fingerprint" + "github.com/GrayCodeAI/decoder/internal/features/fingerprint" "github.com/spf13/cobra" ) @@ -19,10 +19,10 @@ including detected languages, dependency counts, CI presence, license, and git metadata. Examples: - rho fingerprint - rho fingerprint ./myproject - rho fingerprint --format json . - rho fingerprint --format markdown /path/to/repo`, + decoder fingerprint + decoder fingerprint ./myproject + decoder fingerprint --format json . + decoder fingerprint --format markdown /path/to/repo`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { dir := "." diff --git a/cmd/folder_trust_prompt.go b/cmd/folder_trust_prompt.go index f23f0a848..2a7141675 100644 --- a/cmd/folder_trust_prompt.go +++ b/cmd/folder_trust_prompt.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" "golang.org/x/term" ) diff --git a/cmd/folder_trust_prompt_test.go b/cmd/folder_trust_prompt_test.go index d4df11eff..2aeb76c99 100644 --- a/cmd/folder_trust_prompt_test.go +++ b/cmd/folder_trust_prompt_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) func TestPromptForInteractiveFolderTrustAccepts(t *testing.T) { diff --git a/cmd/footer_layout_clip_test.go b/cmd/footer_layout_clip_test.go index 940851274..43c14427d 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/rho") + left := lipgloss.NewStyle().Foreground(statusCWDColor).Inline(true).Render("~/OSS2026/RealWork/graycode-eco/decoder") 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/rho") + left := lipgloss.NewStyle().Foreground(statusCWDColor).Inline(true).Render("~/Desktop/OSS2026/RealWork/graycode-eco/decoder") 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 eb169e04a..98f7a87dd 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("~/rho") + " " + cwdStyle.Render("⎇ main") + left := cwdStyle.Render("~/decoder") + " " + 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 25a6b6cb8..168c777ed 100644 --- a/cmd/formatter.go +++ b/cmd/formatter.go @@ -9,12 +9,12 @@ import ( "golang.org/x/term" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 rho's output — this is false, and color/Unicode +// shell script captures decoder'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 11a688759..d98ee1714 100644 --- a/cmd/formatter_test.go +++ b/cmd/formatter_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestDetectColorSupport_NonTTYStdout(t *testing.T) { diff --git a/cmd/golden_test.go b/cmd/golden_test.go index 974308102..b1609b054 100644 --- a/cmd/golden_test.go +++ b/cmd/golden_test.go @@ -59,8 +59,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, "rho") { - t.Error("help output should contain 'rho'") + if !strings.Contains(got, "decoder") { + t.Error("help output should contain 'decoder'") } if len(got) < 100 { t.Error("help output seems too short") diff --git a/cmd/governance_cmd.go b/cmd/governance_cmd.go index 89462bf8a..957673965 100644 --- a/cmd/governance_cmd.go +++ b/cmd/governance_cmd.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/GrayCodeAI/rho/internal/governance" + "github.com/GrayCodeAI/decoder/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. - 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`, + decoder governance Show the managed policy status + decoder governance show Print the effective capability rows + decoder governance validate Validate a policy or profile document + decoder 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 ef31e6309..926438f13 100644 --- a/cmd/harness.go +++ b/cmd/harness.go @@ -7,7 +7,7 @@ import ( "os" "path/filepath" - "github.com/GrayCodeAI/rho/internal/harness" + "github.com/GrayCodeAI/decoder/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] Rho Harness Auto-Repair Results:", warnAmber)) + fmt.Printf("%s\n", auditTint("[FIX] Decoder 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, ".rho", "harness") + outDir = filepath.Join(targetDir, ".decoder", "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 Rho execution graph + // Journal quality observation to Decoder execution graph _ = harness.JournalHarnessReport(report, "") finish() - fmt.Printf("%s\n", auditTint("[RHO] Rho Harness Evaluation Complete", rhoColor)) + fmt.Printf("%s\n", auditTint("[DECODER] Decoder Harness Evaluation Complete", decoderColor)) 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: .rho/harness)") + harnessCmd.Flags().StringVar(&harnessOutDir, "out-dir", "", "Directory to save harness reports (default: .decoder/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 f0436ecd8..e85d8a388 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, rhoColor) }) + cobra.AddTemplateFunc("gcHeader", func(s string) string { return auditTint(s, decoderColor) }) 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 b4c2d0099..19b308d48 100644 --- a/cmd/history.go +++ b/cmd/history.go @@ -1,6 +1,6 @@ package cmd -import commandfeature "github.com/GrayCodeAI/rho/internal/features/commands" +import commandfeature "github.com/GrayCodeAI/decoder/internal/features/commands" // Compatibility wrappers keep the TUI and existing command tests independent // of the feature package's storage implementation during migration. diff --git a/cmd/history_test.go b/cmd/history_test.go index d11dce66c..9dbd80810 100644 --- a/cmd/history_test.go +++ b/cmd/history_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/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("RHO_STATE_DIR", stateDir) + t.Setenv("DECODER_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 535536af0..edc2b96fa 100644 --- a/cmd/hud_panel.go +++ b/cmd/hud_panel.go @@ -6,7 +6,7 @@ import ( lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 25af02708..3215206ed 100644 --- a/cmd/image.go +++ b/cmd/image.go @@ -13,8 +13,8 @@ import ( "regexp" "strings" - "github.com/GrayCodeAI/rho/internal/tui" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/tui" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // ImageAttachment represents an image ready to be attached to a message. @@ -188,7 +188,7 @@ func IsPDFFile(path string) bool { // ReadPDFText reads a PDF and extracts its text content using stdlib only. // -// flux does not expose a native document/PDF content block, so PDFs are +// mux does not expose a native document/PDF content block, so PDFs are // degraded to text and injected inline. This is a best-effort extractor: it // inflates FlateDecode content streams and pulls text from PDF text-showing // operators (Tj / TJ). It does not handle every PDF (encrypted, scanned-image, diff --git a/cmd/input_indicator.go b/cmd/input_indicator.go index e0bab78a0..115976366 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/rho/internal/features/shellmode" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // InputClass represents the classification of user input. @@ -31,7 +31,7 @@ func inputIndicatorAgentStyle() lipgloss.Style { } func inputIndicatorSlashStyle() lipgloss.Style { - return lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + return lipgloss.NewStyle().Foreground(decoderColor).Bold(true) } func inputIndicatorNeutralStyle() lipgloss.Style { diff --git a/cmd/input_indicator_test.go b/cmd/input_indicator_test.go index b84a78c7e..fa979bad1 100644 --- a/cmd/input_indicator_test.go +++ b/cmd/input_indicator_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/features/shellmode" + "github.com/GrayCodeAI/decoder/internal/features/shellmode" ) func TestInputIndicator_Classify(t *testing.T) { diff --git a/cmd/issue.go b/cmd/issue.go index 0d3d200b2..4b9a470ae 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 rho**\n\n```\n" + ctx + "\n```\n" + return "**Reported via decoder**\n\n```\n" + ctx + "\n```\n" } diff --git a/cmd/learn_cmd.go b/cmd/learn_cmd.go index 0663a8d2a..fd3d37f61 100644 --- a/cmd/learn_cmd.go +++ b/cmd/learn_cmd.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/spf13/cobra" ) @@ -25,13 +25,13 @@ var ( var learnCmd = &cobra.Command{ Use: "learn", Short: "Manage lessons learned across sessions", - Long: `Rho persists lessons from failures (and manual entries) so future + Long: `Decoder persists lessons from failures (and manual entries) so future sessions avoid repeating them. Lessons are injected into the system prompt. - 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`, + decoder learn List recent lessons + decoder learn add Add a lesson manually + decoder learn prompt Print the lesson-extraction prompt for a context + decoder learn clear Remove all lessons`, RunE: func(cmd *cobra.Command, args []string) error { return runLearnList(cmd) }, @@ -110,7 +110,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: rho learn add --what ... --lesson ...", textMuted)) + cmd.Println(auditTint("No lessons yet. Add one with: decoder learn add --what ... --lesson ...", textMuted)) return nil } diff --git a/cmd/main_test.go b/cmd/main_test.go index 9e0b493ae..70c009e26 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" - "github.com/GrayCodeAI/rho/internal/catalogtest" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/catalogtest" + "github.com/GrayCodeAI/decoder/internal/testutil" ) func TestMain(m *testing.M) { diff --git a/cmd/manpage.go b/cmd/manpage.go index 731564772..8b33b0e27 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 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", + Long: "Generate a man page for decoder in roff format and print it to stdout.\nRedirect to a file in your man path, e.g.: decoder manpage > /usr/local/share/man/man1/decoder.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 rho. +// GenerateManPage produces a man page in roff format for decoder. // The OPTIONS section is generated from the live Cobra flag set so it // never drifts from the actual CLI surface. func GenerateManPage() string { @@ -35,20 +35,20 @@ func GenerateManPage() string { var b strings.Builder // Header - b.WriteString(fmt.Sprintf(`.TH RHO 1 "%s" "%s" "User Commands"`, date, ver)) + b.WriteString(fmt.Sprintf(`.TH DECODER 1 "%s" "%s" "User Commands"`, date, ver)) b.WriteString("\n") // Name - b.WriteString(".SH NAME\nrho \\- AI coding agent powered by flux\n") + b.WriteString(".SH NAME\ndecoder \\- AI coding agent powered by mux\n") // Synopsis b.WriteString(".SH SYNOPSIS\n") - b.WriteString(".B rho\n[\\fIOPTIONS\\fR] [\\fIPROMPT\\fR]\n") + b.WriteString(".B decoder\n[\\fIOPTIONS\\fR] [\\fIPROMPT\\fR]\n") // Description b.WriteString(".SH DESCRIPTION\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 flux, executes tools (file I/O,\n", registeredProviderCount()) + b.WriteString("decoder 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 mux, 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\\fBrho %s\\fR\n%s\n", sub.Use, sub.Short)) + b.WriteString(fmt.Sprintf(".TP\n\\fBdecoder %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 rho"}, + {"/quit", "Exit decoder"}, } 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\\fBRho user config directory\\fR\nGlobal configuration files\n") + b.WriteString(".TP\n\\fBDecoder user config directory\\fR\nGlobal configuration files\n") b.WriteString(".TP\n\\fBAGENTS.md\\fR\nProject instructions file\n") - b.WriteString(".TP\n\\fBRho user state directory\\fR\nSaved session data, plans, skills, and runtime state\n") + b.WriteString(".TP\n\\fBDecoder 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 \\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") + b.WriteString("Use \\fBdecoder\\fR and \\fB/config\\fR to save keys; decoder does not read API keys from .env files.\n") + b.WriteString(".TP\n\\fBdecoder credentials status\\fR\nShow secure storage status\n") + b.WriteString(".TP\n\\fBdecoder 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)"}, - {"RHO_CONFIG_DIR", "Override rho config directory"}, + {"DECODER_CONFIG_DIR", "Override decoder 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 0174c5185..99f24927d 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 RHO 1") { + if !strings.Contains(page, ".TH DECODER 1") { t.Fatal("missing .TH header") } if !strings.Contains(page, "1.0.0") { diff --git a/cmd/markdown.go b/cmd/markdown.go index 91c7fabd2..77563a1b7 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(rhoColor).Bold(true).Underline(true) + mdH1Style = lipgloss.NewStyle().Foreground(decoderColor).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(rhoColor).Bold(true) + mdBoldStyle = lipgloss.NewStyle().Foreground(decoderColor).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("\x00RHO_INLINE_CODE_%d\x00", len(replacements)) + placeholder := fmt.Sprintf("\x00DECODER_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("\x00RHO_INLINE_CODE_%d\x00", i), repl) + s = strings.ReplaceAll(s, fmt.Sprintf("\x00DECODER_INLINE_CODE_%d\x00", i), repl) } return s } diff --git a/cmd/markdown_test.go b/cmd/markdown_test.go index 9836b31a3..c69ca495f 100644 --- a/cmd/markdown_test.go +++ b/cmd/markdown_test.go @@ -196,10 +196,10 @@ func TestRenderMarkdownOrderedList(t *testing.T) { } func TestRenderMarkdownLinks(t *testing.T) { - input := "Visit [Rho](https://example.com) for info" + input := "Visit [Decoder](https://example.com) for info" out := renderMarkdown(input, 80) plain := stripAnsi(out) - if !strings.Contains(plain, "Rho") { + if !strings.Contains(plain, "Decoder") { t.Errorf("expected link text in output, got %q", plain) } if !strings.Contains(plain, "https://example.com") { diff --git a/cmd/mcp_serve.go b/cmd/mcp_serve.go index 4598bc6bb..e76115447 100644 --- a/cmd/mcp_serve.go +++ b/cmd/mcp_serve.go @@ -6,8 +6,8 @@ import ( "os/signal" "syscall" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/mcp" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/mcp" "github.com/spf13/cobra" ) @@ -20,28 +20,25 @@ func init() { mcpCmd.AddCommand(mcpConfigCmd) } -// mcpServeCmd runs rho itself as an MCP server over stdio, exposing rho's +// mcpServeCmd runs decoder itself as an MCP server over stdio, exposing decoder'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 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.", + Short: "Run decoder as an MCP server over stdio", + Long: "Run decoder as a Model Context Protocol server over stdio (JSON-RPC 2.0), " + + "exposing decoder's tools to MCP clients like Claude Desktop, Cursor, and Windsurf.\n\n" + + "Use `decoder mcp config` to print the JSON block that registers this command in a client.", RunE: runMCPServe, } func runMCPServe(cmd *cobra.Command, _ []string) error { - settings := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() - serverVersion := version - if serverVersion == "" { - serverVersion = "dev" - } - server := mcp.NewMCPServer(mcp.ServerInfo{Name: "rho", Version: serverVersion}) + serverVersion := DisplayVersion() + server := mcp.NewMCPServer(mcp.ServerInfo{Name: "decoder", Version: serverVersion}) - // Wire rho's tool registry in as the executor so delegating tools run for + // Wire decoder'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 +53,24 @@ func runMCPServe(cmd *cobra.Command, _ []string) error { return server.ServeStdio(ctx) } -// mcpConfigCmd emits the JSON block that registers rho as an MCP server in a +// mcpConfigCmd emits the JSON block that registers decoder 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 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" + + Short: "Print the MCP-server config block to register decoder in a client", + Long: "Print the JSON block that registers decoder as an MCP server (pointing at " + + "`decoder mcp serve`) for clients like Claude Desktop, Cursor, and Windsurf.\n\n" + "Pipe it to the client's config file, e.g.:\n" + - " rho mcp config >> ~/Library/Application Support/Claude/claude_desktop_config.json", + " decoder mcp config >> ~/Library/Application Support/Claude/claude_desktop_config.json", RunE: runMCPConfig, } func runMCPConfig(cmd *cobra.Command, _ []string) error { - exe := rhoExecutablePath() + exe := decoderExecutablePath() block := map[string]any{ "mcpServers": map[string]any{ - "rho": map[string]any{ + "decoder": map[string]any{ "command": exe, "args": []string{"mcp", "serve"}, }, @@ -85,7 +82,7 @@ func runMCPConfig(cmd *cobra.Command, _ []string) error { } if mcpConfigWrite { - cmd.Println("# Add the \"rho\" entry below into the \"mcpServers\" object of your client config:") + cmd.Println("# Add the \"decoder\" 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 +92,12 @@ func runMCPConfig(cmd *cobra.Command, _ []string) error { return nil } -// 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 +// decoderExecutablePath returns the absolute path to the running decoder binary, or +// the bare name "decoder" if it cannot be resolved (e.g. during `go run`), so the // emitted config is still copy-pasteable. -func rhoExecutablePath() string { +func decoderExecutablePath() string { if exe, err := os.Executable(); err == nil && exe != "" { return exe } - return "rho" + return "decoder" } diff --git a/cmd/mcp_serve_test.go b/cmd/mcp_serve_test.go index 82995e16f..391a87843 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()) } - rho, ok := cfg.MCPServers["rho"] + decoder, ok := cfg.MCPServers["decoder"] if !ok { - t.Fatal(`config missing "rho" server entry`) + t.Fatal(`config missing "decoder" server entry`) } - if rho.Command == "" { - t.Error("rho server entry missing command") + if decoder.Command == "" { + t.Error("decoder server entry missing command") } - if len(rho.Args) != 2 || rho.Args[0] != "mcp" || rho.Args[1] != "serve" { - t.Errorf(`args = %v, want ["mcp" "serve"]`, rho.Args) + if len(decoder.Args) != 2 || decoder.Args[0] != "mcp" || decoder.Args[1] != "serve" { + t.Errorf(`args = %v, want ["mcp" "serve"]`, decoder.Args) } } diff --git a/cmd/mentions.go b/cmd/mentions.go index a9c3f76dc..71a54aec8 100644 --- a/cmd/mentions.go +++ b/cmd/mentions.go @@ -6,8 +6,8 @@ import ( "strings" "unicode/utf8" - "github.com/GrayCodeAI/rho/internal/mention" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/mention" + "github.com/GrayCodeAI/decoder/internal/tool" ) // handleMentions processes @-prefixed file mentions in user input. diff --git a/cmd/mentions_test.go b/cmd/mentions_test.go index 377387475..add9240e7 100644 --- a/cmd/mentions_test.go +++ b/cmd/mentions_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) func TestHandleMentions_BlocksSensitivePaths(t *testing.T) { diff --git a/cmd/mission.go b/cmd/mission.go index 72dde9131..434c301e8 100644 --- a/cmd/mission.go +++ b/cmd/mission.go @@ -9,13 +9,13 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + mission "github.com/GrayCodeAI/decoder/internal/multiagent" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/spf13/cobra" ) @@ -36,10 +36,10 @@ Each feature runs in its own worktree with a full engine session. Results are committed on separate branches for review/merge. Examples: - 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`, + decoder mission "Add auth, rate limiting, and logging to the API" + decoder mission --workers 6 "Refactor the database layer into 3 services" + decoder mission --model claude-sonnet-4-6 "Add tests for all untested packages" + decoder mission --from-tasks`, Args: cobra.ArbitraryArgs, RunE: runMission, } @@ -61,7 +61,7 @@ func runMission(_ *cobra.Command, args []string) error { cwd, _ := os.Getwd() baseBranch := getCurrentBranch(cwd) - settings := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() effectiveModel, effectiveProvider := effectiveModelAndProvider(settings) if missionModel != "" { effectiveModel = missionModel @@ -79,7 +79,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/rho-missions/ + // whether it succeeded or failed. Without this, /tmp/decoder-missions/ // accumulates one directory per run indefinitely (C6 fix). defer func() { _ = m.Cleanup() }() @@ -175,7 +175,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 `rho mission` exited 0. + // when every feature failed (H9), so `decoder mission` exited 0. failed := 0 for _, f := range m.Features { if f.Status == mission.FeatureFailed { @@ -189,7 +189,7 @@ func runMission(_ *cobra.Command, args []string) error { return nil } -func planWithLLM(ctx context.Context, prompt, provider, model string, settings rhoconfig.Settings) ([]mission.Feature, error) { +func planWithLLM(ctx context.Context, prompt, provider, model string, settings decoderconfig.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"+ @@ -202,7 +202,7 @@ func planWithLLM(ctx context.Context, prompt, provider, model string, settings r ) registry, _ := defaultRegistry(settings) - sess, err := newConfiguredRhoSession(settings, provider, model, planPrompt, registry, logger.New(io.Discard, logger.Error)) + sess, err := newConfiguredDecoderSession(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 6b30c7918..5976dd089 100644 --- a/cmd/mission_graph.go +++ b/cmd/mission_graph.go @@ -6,8 +6,8 @@ import ( "strings" "time" - mission "github.com/GrayCodeAI/rho/internal/multiagent" - "github.com/GrayCodeAI/rho/internal/tool" + mission "github.com/GrayCodeAI/decoder/internal/multiagent" + "github.com/GrayCodeAI/decoder/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("rho-mission/%s/%s", missionID, task.ID), + Branch: fmt.Sprintf("decoder-mission/%s/%s", missionID, task.ID), Status: status, }) } diff --git a/cmd/mission_graph_test.go b/cmd/mission_graph_test.go index 5df0550f7..4f6ee271a 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/rho/internal/multiagent" - "github.com/GrayCodeAI/rho/internal/tool" + mission "github.com/GrayCodeAI/decoder/internal/multiagent" + "github.com/GrayCodeAI/decoder/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 != "rho-mission/mission123/"+root.ID { + if features[0].Branch != "decoder-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 242437a41..c86f7a56a 100644 --- a/cmd/model_table.go +++ b/cmd/model_table.go @@ -6,8 +6,8 @@ import ( "strings" lipgloss "charm.land/lipgloss/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/mattn/go-runewidth" ) @@ -119,17 +119,17 @@ func modelTableRowFromOption(o configModelOption) modelTableRow { } func formatModelThinkingCell(o configModelOption) string { - supports := rhoconfig.ModelCapabilitySupportsThinking(o.Capabilities) - settings := rhoconfig.LoadSettings() - pref := rhoconfig.ThinkingPrefForModel(settings, o.ID) + supports := decoderconfig.ModelCapabilitySupportsThinking(o.Capabilities) + settings := decoderconfig.LoadSettings() + pref := decoderconfig.ThinkingPrefForModel(settings, o.ID) if pref == nil && o.CanonicalID != "" && o.CanonicalID != o.ID { - pref = rhoconfig.ThinkingPrefForModel(settings, o.CanonicalID) + pref = decoderconfig.ThinkingPrefForModel(settings, o.CanonicalID) } provider := strings.TrimSpace(o.GatewayID) if provider == "" { provider = strings.TrimSpace(o.ProviderID) } - return rhoconfig.FormatModelThinkingLabel(supports, pref, provider) + return decoderconfig.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 rhoconfig.EngineModel) modelTableRow { +func modelTableRowFromCatalogEntry(m decoderconfig.EngineModel) modelTableRow { name := strings.TrimSpace(m.DisplayName) if name == "" { name = m.ID @@ -401,7 +401,7 @@ func modelTableRowFromCatalogEntry(m rhoconfig.EngineModel) modelTableRow { Model: name, Provider: owner, Caps: formatModelCapabilities(m.Capabilities), - Think: rhoconfig.FormatModelThinkingLabel(rhoconfig.ModelCapabilitySupportsThinking(m.Capabilities), rhoconfig.ThinkingPrefForModel(rhoconfig.LoadSettings(), m.ID), provider), + Think: decoderconfig.FormatModelThinkingLabel(decoderconfig.ModelCapabilitySupportsThinking(m.Capabilities), decoderconfig.ThinkingPrefForModel(decoderconfig.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 504a6c403..84e309cb7 100644 --- a/cmd/model_table_test.go +++ b/cmd/model_table_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/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 rho settings (per-model thinking + // Isolate from the developer's real decoder 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(rhoconfig.EngineModel{GatewayID: "gateway"}) + entry := modelTableRowFromCatalogEntry(decoderconfig.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 c21da469b..2ba68a949 100644 --- a/cmd/models.go +++ b/cmd/models.go @@ -7,7 +7,7 @@ import ( "strings" "time" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" "github.com/spf13/cobra" ) @@ -19,18 +19,18 @@ var ( var modelsCmd = &cobra.Command{ Use: "models", - Short: "Deployment-aware model catalog (via flux)", - Long: `Manage the flux model catalog used by rho for models, pricing, and deployment routing. + Short: "Deployment-aware model catalog (via mux)", + Long: `Manage the mux model catalog used by decoder for models, pricing, and deployment routing. -The catalog is stored at ~/.flux/model_catalog.json (override with FLUX_MODEL_CATALOG_PATH). -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.`, +The catalog is stored at ~/.mux/model_catalog.json (override with MUX_MODEL_CATALOG_PATH). +Decoder refreshes the catalog automatically on startup when the cache is missing, empty, or stale (disable with --no-auto-catalog-refresh or DECODER_AUTO_REFRESH_CATALOG=0). +Use 'decoder models refresh' for a manual refresh or full discover report.`, } var modelsRefreshCmd = &cobra.Command{ Use: "refresh", Aliases: []string{"update"}, - Short: "Discover model catalog (flux remote + live provider APIs) into ~/.flux/model_catalog.json", + Short: "Discover model catalog (mux remote + live provider APIs) into ~/.mux/model_catalog.json", RunE: func(cmd *cobra.Command, _ []string) error { settings, err := loadEffectiveSettings() if err != nil { @@ -41,7 +41,7 @@ var modelsRefreshCmd = &cobra.Command{ prog := NewCLIProgress("Models", []string{"Discovering catalog"}) defer prog.Abort() prog.StartStep(0) - summary, err := rhoconfig.RefreshModelCatalogV1WithSettings(ctx, settings) + summary, err := decoderconfig.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(rhoconfig.FormatCatalogHealth(rhoconfig.CatalogHealthReport(ctx))) + cmd.Println(decoderconfig.FormatCatalogHealth(decoderconfig.CatalogHealthReport(ctx))) cmd.Println() modelName, _ := effectiveModelAndProvider(settings) if len(args) > 0 { modelName = args[0] } - report, err := rhoconfig.DeploymentStatusReportWithSettings(ctx, settings, modelName) + report, err := decoderconfig.DeploymentStatusReportWithSettings(ctx, settings, modelName) if err != nil { return err } @@ -87,7 +87,7 @@ var modelsRoutingPreviewCmd = &cobra.Command{ return err } modelName := args[0] - out, err := rhoconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, modelName) + out, err := decoderconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, modelName) if err != nil { return err } @@ -98,7 +98,7 @@ var modelsRoutingPreviewCmd = &cobra.Command{ var modelsListCmd = &cobra.Command{ Use: "list [provider]", - Short: "List models from the flux catalog cache (or live provider API)", + Short: "List models from the mux catalog cache (or live provider API)", RunE: func(cmd *cobra.Command, args []string) error { settings, err := loadEffectiveSettings() if err != nil { @@ -109,7 +109,7 @@ var modelsListCmd = &cobra.Command{ providerName = args[0] } ctx := cmd.Context() - var models []rhoconfig.EngineModel + var models []decoderconfig.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. rho models list canopywave --live --json)") + return fmt.Errorf("provider required with --live (e.g. decoder models list canopywave --live --json)") } - models, err = rhoconfig.ListLiveEngineModelsWithSettings(ctx, settings, rhoconfig.ActiveProviderID(providerName)) + models, err = decoderconfig.ListLiveEngineModelsWithSettings(ctx, settings, decoderconfig.ActiveProviderID(providerName)) } else { - models, err = rhoconfig.FetchModelsForProviderWithSettings(ctx, settings, providerName) + models, err = decoderconfig.FetchModelsForProviderWithSettings(ctx, settings, providerName) } if err != nil { if prog != nil { @@ -159,9 +159,9 @@ var modelsListCmd = &cobra.Command{ }, } -// modelListJSONEntry is Rho's versioned command-output contract. Keep this -// separate from Flux's host-facing Model DTO so engine-only fields can evolve -// without breaking users that consume `rho models list --json`. +// modelListJSONEntry is Decoder's versioned command-output contract. Keep this +// separate from Mux's host-facing Model DTO so engine-only fields can evolve +// without breaking users that consume `decoder 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 rhoconfig.EngineModel) modelListJSONEntry { +func modelListJSONEntryFromEngine(m decoderconfig.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 []rhoconfig.EngineModel, rawOnly, live bool) ([]byte, error) { +func marshalModelListJSON(models []decoderconfig.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 fb15b2984..07f834e8f 100644 --- a/cmd/models_test.go +++ b/cmd/models_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" ) func TestMarshalModelListJSONCompatibilityGolden(t *testing.T) { @@ -72,7 +72,7 @@ func TestMarshalModelListJSONCompatibilityGolden(t *testing.T) { } func TestModelListJSONEntryFromEnginePreservesLegacyFieldMapping(t *testing.T) { - model := rhoconfig.EngineModel{ + model := decoderconfig.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([]rhoconfig.EngineModel{model}, false, false) + out, err := marshalModelListJSON([]decoderconfig.EngineModel{model}, false, false) if err != nil { t.Fatalf("marshalModelListJSON() error = %v", err) } diff --git a/cmd/notifications.go b/cmd/notifications.go index c438c6260..3a7c6a615 100644 --- a/cmd/notifications.go +++ b/cmd/notifications.go @@ -15,7 +15,7 @@ func notifyCompletion(duration time.Duration) { return } - msg := "Rho query completed" + msg := "Decoder 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 "Rho"`, + `display notification "`+msg+`" with title "Decoder"`, ).Start() case "linux": // Linux: use notify-send if available - _ = exec.CommandContext(context.Background(), "notify-send", "Rho", msg).Start() + _ = exec.CommandContext(context.Background(), "notify-send", "Decoder", msg).Start() } } diff --git a/cmd/notify.go b/cmd/notify.go index 5f673310e..5413adc31 100644 --- a/cmd/notify.go +++ b/cmd/notify.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // Notification represents a single notification event. @@ -23,7 +23,7 @@ type Notification struct { Read bool } -// Notifier manages terminal notifications for rho events. +// Notifier manages terminal notifications for decoder 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('rho').Show($toast)`, +[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('decoder').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 38276703a..1e01db924 100644 --- a/cmd/notify_test.go +++ b/cmd/notify_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestNewNotifier(t *testing.T) { @@ -258,7 +258,7 @@ func TestSetTerminalTitle(t *testing.T) { t.Fatal("expected Sound and Desktop to be disabled") } - n.SetTerminalTitle("rho: running") + n.SetTerminalTitle("decoder: running") n.ClearTitle() } diff --git a/cmd/ocg_live_test.go b/cmd/ocg_live_test.go index 4652069f7..4f00f3819 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 TestLiveOpenCodeGoMiniMaxM3FullRhoPath ./cmd +// OPENCODEGO_API_KEY=... go test -tags=live_test -run TestLiveOpenCodeGoMiniMaxM3FullDecoderPath ./cmd // // Or via `make test-live` in this repo. package cmd @@ -15,15 +15,15 @@ import ( "testing" "time" - fluxcfg "github.com/GrayCodeAI/flux/config" - "github.com/GrayCodeAI/flux/credentials" - "github.com/GrayCodeAI/flux/setup" - "github.com/GrayCodeAI/rho/internal/observability/logger" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + muxcfg "github.com/GrayCodeAI/mux/config" + "github.com/GrayCodeAI/mux/credentials" + "github.com/GrayCodeAI/mux/setup" ) -func TestLiveOpenCodeGoMiniMaxM3FullRhoPath(t *testing.T) { +func TestLiveOpenCodeGoMiniMaxM3FullDecoderPath(t *testing.T) { if credentials.LookupSecret(context.Background(), "OPENCODEGO_API_KEY") == "" { - t.Skip("OPENCODEGO_API_KEY not configured") // TODO: https://github.com/GrayCodeAI/rho/issues/29 + t.Skip("OPENCODEGO_API_KEY not configured") // TODO: https://github.com/GrayCodeAI/decoder/issues/29 } settings, err := loadEffectiveSettings() if err != nil { @@ -38,12 +38,12 @@ func TestLiveOpenCodeGoMiniMaxM3FullRhoPath(t *testing.T) { if err != nil { t.Fatal(err) } - t.Logf("provider=%s model=%s tools=%d system_len=%d", effectiveProvider, effectiveModel, len(registry.FluxTools()), len(systemPrompt)) + t.Logf("provider=%s model=%s tools=%d system_len=%d", effectiveProvider, effectiveModel, len(registry.MuxTools()), len(systemPrompt)) - adapter := setup.ConfiguredDeploymentAdapters(fluxcfg.LoadProviderConfig(""))["opencodego"] + adapter := setup.ConfiguredDeploymentAdapters(muxcfg.LoadProviderConfig(""))["opencodego"] t.Logf("adapter_type=%T", adapter.Provider) - sess := newRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry) + sess := newDecoderSession(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 80fa3390b..76e0b0803 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -9,24 +9,24 @@ import ( "os" "strings" - "github.com/GrayCodeAI/rho/internal/engine/safety" - - "github.com/GrayCodeAI/rho/internal/engine/scaffold" - - 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/permissions" - "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" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + + "github.com/GrayCodeAI/decoder/internal/engine/scaffold" + + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + ctxrepomap "github.com/GrayCodeAI/decoder/internal/context/repomap" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/branching" + "github.com/GrayCodeAI/decoder/internal/engine/lifecycle" + "github.com/GrayCodeAI/decoder/internal/intelligence/memory" + "github.com/GrayCodeAI/decoder/internal/intelligence/repomap" + "github.com/GrayCodeAI/decoder/internal/observability/logger" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/prompt" + "github.com/GrayCodeAI/decoder/internal/prompts" + decodermodel "github.com/GrayCodeAI/decoder/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/snapshot" + "github.com/GrayCodeAI/decoder/internal/tool" ) func buildSystemPrompt() (string, error) { @@ -74,7 +74,7 @@ func buildSystemPromptWithOptions(includeWorkspaceContext, includeRepoMap bool) modularPrompt = "" } - base := prompt.System() + "\n\n" + rhoconfig.BuildStartupContextWithDirs(addDirs) + base := prompt.System() + "\n\n" + decoderconfig.BuildStartupContextWithDirs(addDirs) if modularPrompt != "" { base += "\n\n" + modularPrompt } @@ -124,7 +124,7 @@ func buildDeferredWorkspacePromptContext() string { return "" } var sections []string - if deferred := strings.TrimSpace(rhoconfig.BuildDeferredContextWithDirs(addDirs)); deferred != "" { + if deferred := strings.TrimSpace(decoderconfig.BuildDeferredContextWithDirs(addDirs)); deferred != "" { sections = append(sections, deferred) } if ws := prompts.GatherWorkspaceContext(cwd); ws != nil { @@ -161,7 +161,7 @@ func injectRepoMap(base string) string { return base } - settings := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() if settings.RepoMap == nil || !*settings.RepoMap { return base } @@ -187,43 +187,43 @@ func injectRepoMap(base string) string { return base + "\n\n# Repository Map\n" + formatted } -func loadEffectiveSettings() (rhoconfig.Settings, error) { - return rhoconfig.LoadSettingsWithOverride(settingsFlag) +func loadEffectiveSettings() (decoderconfig.Settings, error) { + return decoderconfig.LoadSettingsWithOverride(settingsFlag) } -func resolveSelection(settings rhoconfig.Settings) rhoconfig.Selection { - return rhoconfig.EffectiveSelectionWithSettings(context.Background(), settings, rhoconfig.SelectionOptions{ +func resolveSelection(settings decoderconfig.Settings) decoderconfig.Selection { + return decoderconfig.EffectiveSelectionWithSettings(context.Background(), settings, decoderconfig.SelectionOptions{ ProviderOverride: firstNonEmptyTrimmed(provider, settings.Provider), ModelOverride: firstNonEmptyTrimmed(model, settings.Model), }) } -func startupSelection(settings rhoconfig.Settings) rhoconfig.Selection { +func startupSelection(settings decoderconfig.Settings) decoderconfig.Selection { providerOverride := firstNonEmptyTrimmed(provider, settings.Provider) modelOverride := firstNonEmptyTrimmed(model, settings.Model) explicitProvider, explicitModel := explicitSelection(context.Background()) - providerID := rhoconfig.ActiveProviderID(firstNonEmptyTrimmed(providerOverride, explicitProvider)) + providerID := decoderconfig.ActiveProviderID(firstNonEmptyTrimmed(providerOverride, explicitProvider)) modelID := strings.TrimSpace(firstNonEmptyTrimmed(modelOverride, explicitModel)) if providerID == "" && modelID != "" { - providerID = rhoconfig.ActiveProviderID(rhoconfig.ProviderOfModelWithSettings(settings, modelID)) + providerID = decoderconfig.ActiveProviderID(decoderconfig.ProviderOfModelWithSettings(settings, modelID)) } if modelID == "" && providerID != "" { - modelID = strings.TrimSpace(rhoconfig.DefaultModelForProviderWithSettings(settings, providerID)) + modelID = strings.TrimSpace(decoderconfig.DefaultModelForProviderWithSettings(settings, providerID)) } if providerID == "" { providerID = startupPlaceholderProvider } - return rhoconfig.Selection{ + return decoderconfig.Selection{ Provider: providerID, Model: modelID, } } -func effectiveModelAndProvider(settings rhoconfig.Settings) (string, string) { +func effectiveModelAndProvider(settings decoderconfig.Settings) (string, string) { selection := resolveSelection(settings) if !selection.HasConfiguredDeployment { return "", "" @@ -231,7 +231,7 @@ func effectiveModelAndProvider(settings rhoconfig.Settings) (string, string) { return selection.Model, selection.Provider } -func newStartupRhoSession(selection rhoconfig.Selection, systemPrompt string, registry *tool.Registry) *engine.Session { +func newStartupDecoderSession(selection decoderconfig.Selection, systemPrompt string, registry *tool.Registry) *engine.Session { providerID := strings.TrimSpace(selection.Provider) if providerID == "" { providerID = startupPlaceholderProvider @@ -239,7 +239,7 @@ func newStartupRhoSession(selection rhoconfig.Selection, systemPrompt string, re return engine.NewSession(providerID, strings.TrimSpace(selection.Model), systemPrompt, registry) } -func newRhoSession(settings rhoconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry) *engine.Session { +func newDecoderSession(settings decoderconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry) *engine.Session { selection := resolveSelection(settings) if strings.TrimSpace(selection.Provider) == "" { selection.Provider = effectiveProvider @@ -247,15 +247,15 @@ func newRhoSession(settings rhoconfig.Settings, effectiveProvider, effectiveMode if strings.TrimSpace(selection.Model) == "" { selection.Model = effectiveModel } - sess := engine.NewRhoSessionForSettings(context.Background(), settings, selection, selection.Provider, selection.Model, systemPrompt, registry) + sess := engine.NewDecoderSessionForSettings(context.Background(), settings, selection, selection.Provider, selection.Model, systemPrompt, registry) return sess } -// newConfiguredRhoSession is the non-interactive command composition root. +// newConfiguredDecoderSession 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 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) +func newConfiguredDecoderSession(settings decoderconfig.Settings, effectiveProvider, effectiveModel, systemPrompt string, registry *tool.Registry, sessionLogger *logger.Logger, maxTurnsOverride ...int) (*engine.Session, error) { + sess := newDecoderSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry) if sessionLogger != nil { sess.SetLogger(sessionLogger) } @@ -265,11 +265,11 @@ func newConfiguredRhoSession(settings rhoconfig.Settings, effectiveProvider, eff return sess, nil } -// newConfiguredRhoSessionFactory is the shared composition seam for +// newConfiguredDecoderSessionFactory 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 newConfiguredRhoSessionFactory(settings rhoconfig.Settings, sessionLogger *logger.Logger) func(string, string, ...int) (*engine.Session, error) { +func newConfiguredDecoderSessionFactory(settings decoderconfig.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 { @@ -279,14 +279,14 @@ func newConfiguredRhoSessionFactory(settings rhoconfig.Settings, sessionLogger * if strings.TrimSpace(modelOverride) != "" { effectiveModel = modelOverride } - return newConfiguredRhoSession(settings, effectiveProvider, effectiveModel, systemPrompt, registry, sessionLogger, maxTurnsOverride...) + return newConfiguredDecoderSession(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 rhoconfig.Settings) error { +func prepareInteractiveSessionStartup(sess *engine.Session, settings decoderconfig.Settings) error { syncSessionFromPersistedSelection(sess) sess.SetLogger(logger.New(io.Discard, logger.Error)) return configureSessionStartup(sess, settings) @@ -301,7 +301,7 @@ func firstNonEmptyTrimmed(values ...string) string { return "" } -func configureSession(sess *engine.Session, settings rhoconfig.Settings, maxTurnsOverride ...int) error { +func configureSession(sess *engine.Session, settings decoderconfig.Settings, maxTurnsOverride ...int) error { if err := configureSessionStartup(sess, settings, maxTurnsOverride...); err != nil { return err } @@ -309,7 +309,7 @@ func configureSession(sess *engine.Session, settings rhoconfig.Settings, maxTurn return nil } -func configureSessionStartup(sess *engine.Session, settings rhoconfig.Settings, maxTurnsOverride ...int) error { +func configureSessionStartup(sess *engine.Session, settings decoderconfig.Settings, maxTurnsOverride ...int) error { sess.WireAgentTool() sess.SetAllowedDirs(addDirs) projectDir, err := os.Getwd() @@ -363,7 +363,7 @@ func configureSessionStartup(sess *engine.Session, settings rhoconfig.Settings, } // Model cascade router: automatically routes tasks to optimal model tier - roles := rhomodel.DefaultRoles(sess.Model()) + roles := decodermodel.DefaultRoles(sess.Model()) if settings.ModelRoles != nil { roles = *settings.ModelRoles } @@ -411,7 +411,7 @@ func configureSessionStartup(sess *engine.Session, settings rhoconfig.Settings, // provider-specific defaults (e.g. LongCat off). modelID := strings.TrimSpace(sess.Model()) providerID := strings.TrimSpace(sess.Provider()) - sess.SetThinkingEnabled(rhoconfig.ResolveThinkingForModel(settings, modelID, providerID)) + sess.SetThinkingEnabled(decoderconfig.ResolveThinkingForModel(settings, modelID, providerID)) return nil } diff --git a/cmd/options_test.go b/cmd/options_test.go index a17908826..ef9a91b9d 100644 --- a/cmd/options_test.go +++ b/cmd/options_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/tool" ) func TestParseToolListFromCLI(t *testing.T) { diff --git a/cmd/options_welcome_test.go b/cmd/options_welcome_test.go index f949a09ef..ab31b36e7 100644 --- a/cmd/options_welcome_test.go +++ b/cmd/options_welcome_test.go @@ -7,65 +7,65 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func isolateCredentialHome(t *testing.T) { t.Helper() home := t.TempDir() - rhoDir := filepath.Join(home, ".rho") - _ = os.MkdirAll(rhoDir, 0o700) + decoderDir := filepath.Join(home, ".decoder") + _ = os.MkdirAll(decoderDir, 0o700) t.Setenv("HOME", home) - t.Setenv("RHO_CONFIG_DIR", rhoDir) - t.Setenv("FLUX_CONFIG_DIR", filepath.Join(home, "flux")) + t.Setenv("DECODER_CONFIG_DIR", decoderDir) + t.Setenv("MUX_CONFIG_DIR", filepath.Join(home, "mux")) } func TestEffectiveModelAndProvider_ClearsWithoutCredentials(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() - if err := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + if err := decoderconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := decoderconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } - model, provider := effectiveModelAndProvider(rhoconfig.Settings{}) + model, provider := effectiveModelAndProvider(decoderconfig.Settings{}) if model != "" || provider != "" { t.Fatalf("expected empty selection without credentials, got model=%q provider=%q", model, provider) } } func TestEffectiveModelAndProvider_KeepsWithCredentials(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - rhoconfig.InvalidateConfigUICache() - if err := rhoconfig.SetActiveProvider(ctx, "openrouter"); err != nil { + decoderconfig.InvalidateConfigUICache() + if err := decoderconfig.SetActiveProvider(ctx, "openrouter"); err != nil { t.Fatal(err) } - if err := rhoconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { + if err := decoderconfig.SetActiveModel(ctx, "gpt-4o"); err != nil { t.Fatal(err) } - model, provider := effectiveModelAndProvider(rhoconfig.Settings{}) + model, provider := effectiveModelAndProvider(decoderconfig.Settings{}) if provider == "" { t.Fatalf("expected provider with credentials, got model=%q provider=%q", model, provider) } diff --git a/cmd/overlay_styles.go b/cmd/overlay_styles.go index 9ffa43335..bb474f5e3 100644 --- a/cmd/overlay_styles.go +++ b/cmd/overlay_styles.go @@ -38,5 +38,5 @@ func overlaySearchValueStyle() lipgloss.Style { } func overlaySelectionMarkerStyle() lipgloss.Style { - return lipgloss.NewStyle().Foreground(rhoColor).Bold(true) + return lipgloss.NewStyle().Foreground(decoderColor).Bold(true) } diff --git a/cmd/overlay_zero_value_test.go b/cmd/overlay_zero_value_test.go index c5d9afd0d..4348af7b7 100644 --- a/cmd/overlay_zero_value_test.go +++ b/cmd/overlay_zero_value_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) func TestInteractiveOverlaysZeroValuesRenderSafely(t *testing.T) { diff --git a/cmd/pager.go b/cmd/pager.go index ae1d3df91..75f6db8aa 100644 --- a/cmd/pager.go +++ b/cmd/pager.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "io" "os" "os/exec" @@ -55,7 +56,7 @@ func StartPager() io.Writer { // G204: name is derived from environment or LookPath, which is the standard // pattern for pager invocation. Users control their own PAGER env var. //nolint:gosec // G204 - cmd := exec.Command(name, args...) // #nosec G204 -- pager executable is selected from a validated allowlist + cmd := exec.CommandContext(context.Background(), name, args...) // #nosec G204 -- pager executable is selected from a validated allowlist cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -95,7 +96,7 @@ func StopPager() { // Returns "" if paging should be disabled. func resolvePager() string { // Disable paging via environment. - if v := os.Getenv("RHO_PAGER"); v != "" { + if v := os.Getenv("DECODER_PAGER"); v != "" { if v == "cat" || v == "none" { return "" } diff --git a/cmd/path.go b/cmd/path.go index 0313dd5b2..7c8995bef 100644 --- a/cmd/path.go +++ b/cmd/path.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" "github.com/spf13/cobra" ) @@ -17,16 +17,16 @@ var ( var pathCmd = &cobra.Command{ Use: "path", Short: "Developer path readiness (setup, security, ecosystem)", - Long: `Check whether rho is configured on the developer path: + Long: `Check whether decoder is configured on the developer path: API keys in OS secret store, model selected, no secrets on disk, -and flux integration. +and mux integration. 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 := rhoconfig.EvaluateDeveloperPath(ctx) + report := decoderconfig.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(rhoconfig.FormatDeveloperPathReport(ctx)) + cmd.Println(decoderconfig.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 48a1dbda0..a766d9141 100644 --- a/cmd/path_test.go +++ b/cmd/path_test.go @@ -5,20 +5,20 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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/rho/issues/30 + t.Skip("machine has full developer path setup") // TODO: https://github.com/GrayCodeAI/decoder/issues/30 } } func TestPathCmdPrintsReport(t *testing.T) { useInMemoryCredentials(t) - out := rhoconfig.FormatDeveloperPathReport(context.Background()) + out := decoderconfig.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 d5508e279..b28197523 100644 --- a/cmd/permissions.go +++ b/cmd/permissions.go @@ -8,10 +8,10 @@ import ( "strconv" "strings" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/permissions" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" "github.com/spf13/cobra" ) @@ -231,7 +231,7 @@ var permissionsInspectCmd = &cobra.Command{ // observational evaluator. It is the same resolver used before execution and // never invokes a prompt, mutates remembered rules, or runs a tool. func inspectPermission(kind stableid.Kind, identity, toolOverride string) (permissionInspectionOutput, error) { - settings, err := rhoconfig.LoadSettingsWithOverride(settingsFlag) + settings, err := decoderconfig.LoadSettingsWithOverride(settingsFlag) if err != nil { return permissionInspectionOutput{}, err } @@ -276,7 +276,7 @@ func inspectPermission(kind stableid.Kind, identity, toolOverride string) (permi // used by session startup, the TUI permission center, and CLI inspection. // Keeping this at one boundary prevents a preview or UI rebuild from drifting // away from the rules used by the executor. -func applyConfiguredPermissionRules(mem *safety.PermissionMemory, settings rhoconfig.Settings, includeCLI bool) { +func applyConfiguredPermissionRules(mem *safety.PermissionMemory, settings decoderconfig.Settings, includeCLI bool) { if mem == nil { return } diff --git a/cmd/permissions_center.go b/cmd/permissions_center.go index be38a8f9b..e47309321 100644 --- a/cmd/permissions_center.go +++ b/cmd/permissions_center.go @@ -5,12 +5,12 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" tea "charm.land/bubbletea/v2" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" ) func normalizePermissionTier(raw string) (safety.AutonomyLevel, string, bool) { @@ -362,7 +362,7 @@ func appendExactPermissionRules(b *strings.Builder, rules []stableid.RuleSnap) { } } -func effectiveAllowRules(settings rhoconfig.Settings) []string { +func effectiveAllowRules(settings decoderconfig.Settings) []string { var rules []string rules = append(rules, settings.AutoAllow...) rules = append(rules, settings.AllowedTools...) @@ -370,7 +370,7 @@ func effectiveAllowRules(settings rhoconfig.Settings) []string { return dedupeStrings(rules) } -func effectiveDenyRules(settings rhoconfig.Settings) []string { +func effectiveDenyRules(settings decoderconfig.Settings) []string { rules := append([]string{}, settings.DisallowedTools...) rules = append(rules, parseToolListFromCLI(disallowedToolsFlag)...) return dedupeStrings(rules) @@ -390,7 +390,7 @@ func dedupeStrings(values []string) []string { return out } -func rebuildSessionPermissionRules(sess *engine.Session, settings rhoconfig.Settings) { +func rebuildSessionPermissionRules(sess *engine.Session, settings decoderconfig.Settings) { if sess == nil { return } @@ -434,7 +434,7 @@ func (m *chatModel) updateAutonomyRule(parts []string, allow bool) { m.messages = append(m.messages, displayMsg{role: "system", content: label + " rules updated.\n" + permissionRulesSummary(m)}) } -func savePermissionSettings(scope string, settings rhoconfig.Settings, level safety.AutonomyLevel) (string, error) { +func savePermissionSettings(scope string, settings decoderconfig.Settings, level safety.AutonomyLevel) (string, error) { scope = strings.ToLower(strings.TrimSpace(scope)) if scope == "" { scope = "global" @@ -448,13 +448,13 @@ func savePermissionSettings(scope string, settings rhoconfig.Settings, level saf case "project": return "", fmt.Errorf("project-local settings writes are disabled; use scope \"global\" or an explicit --settings file") case "global": - target := rhoconfig.LoadGlobalSettings() + target := decoderconfig.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 := rhoconfig.SaveGlobal(target); err != nil { + if err := decoderconfig.SaveGlobal(target); err != nil { return "", err } return "user settings", nil @@ -754,7 +754,7 @@ func (m *chatModel) handleAutonomyCommand(parts []string) (chatModel, tea.Cmd) { m.messages = append(m.messages, displayMsg{role: "system", content: m.session.PermSvc().PermissionMetrics()}) case "reset": resetPermissionCenter(m) - m.messages = append(m.messages, displayMsg{role: "system", content: "Session autonomy reset to defaults. Persisted exact rules were kept; use `rho permissions reset` to remove them with confirmation.\n" + autonomyCenterSummary(m)}) + m.messages = append(m.messages, displayMsg{role: "system", content: "Session autonomy reset to defaults. Persisted exact rules were kept; use `decoder permissions reset` to remove them with confirmation.\n" + autonomyCenterSummary(m)}) default: m.messages = append(m.messages, displayMsg{role: "system", content: autonomyCommandHelp()}) } diff --git a/cmd/permissions_center_test.go b/cmd/permissions_center_test.go index 046f61049..36572bbf1 100644 --- a/cmd/permissions_center_test.go +++ b/cmd/permissions_center_test.go @@ -6,12 +6,12 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/permissions" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" ) func TestNormalizePermissionTier(t *testing.T) { @@ -62,7 +62,7 @@ func TestParseBypassFlagsFailsClosed(t *testing.T) { } func TestEffectivePermissionRules(t *testing.T) { - settings := rhoconfig.Settings{ + settings := decoderconfig.Settings{ AutoAllow: []string{"Read"}, AllowedTools: []string{"Bash(git:*)", "Read"}, DisallowedTools: []string{"Bash(rm -rf *)"}, @@ -83,7 +83,7 @@ func TestAutonomyCenterSummary(t *testing.T) { sess.PermSvc().SetSpecStage(safety.SpecStageSpecify) model := &chatModel{ session: sess, - settings: rhoconfig.Settings{ + settings: decoderconfig.Settings{ AllowedTools: []string{"Bash(git:*)"}, DisallowedTools: []string{"Bash(rm -rf *)"}, }, @@ -230,7 +230,7 @@ func TestResetPermissionCenter(t *testing.T) { sess.PermSvc().SetExactRuleStore(store) model := &chatModel{ session: sess, - settings: rhoconfig.Settings{ + settings: decoderconfig.Settings{ Autonomy: permissionTierSettingValue(safety.AutonomyYOLO), AutoAllow: []string{"Read"}, AllowedTools: []string{"Bash(git:*)"}, diff --git a/cmd/permissions_inspect_test.go b/cmd/permissions_inspect_test.go index a67ee042d..77aa0cda2 100644 --- a/cmd/permissions_inspect_test.go +++ b/cmd/permissions_inspect_test.go @@ -3,8 +3,8 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/permissions" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" ) func TestInspectionToolCall_SelectsPowerShellRuntime(t *testing.T) { diff --git a/cmd/plan.go b/cmd/plan.go index 7a1731225..6e9ac4af9 100644 --- a/cmd/plan.go +++ b/cmd/plan.go @@ -8,8 +8,8 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/rho/internal/intelligence/planner" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/intelligence/planner" + "github.com/GrayCodeAI/decoder/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 Rho's user state directory, partitioned by project. +Plans are stored in Decoder'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 ---", rhoColor)) + cmd.Println(auditTint("--- System ---", decoderColor)) cmd.Println(prompt.System) cmd.Println() - cmd.Println(auditTint("--- User ---", rhoColor)) + cmd.Println(auditTint("--- User ---", decoderColor)) 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 Rho plans.", textMuted)) + cmd.Println(auditTint("Once you have the LLM response, save it with an explicit output path or import it into Decoder 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: rho plan create ", textMuted)) + cmd.Println(auditTint("No plans found. Create one with: decoder 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: rho plan create ", textMuted)) + cmd.Println(auditTint("No plans found. Create one with: decoder 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 Rho's user state directory for the current project. +recently modified plan in Decoder'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: rho plan done `, +followed by the task ID: decoder 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 Rho's user state dir. +// resolvePlanPath converts a plan name to a file path in Decoder'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 f40bbb6a8..341f9217e 100644 --- a/cmd/plugin_dynamic.go +++ b/cmd/plugin_dynamic.go @@ -8,8 +8,8 @@ import ( "path/filepath" "time" - "github.com/GrayCodeAI/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/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 'rho plugin install' to add plugins.", textMuted)) + cmd.Println(auditTint("No plugins discovered. Run 'decoder 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 rho plugin: %s", name), + Description: fmt.Sprintf("A decoder plugin: %s", name), Author: "", Mode: "subprocess", Tools: []plugin.ManifestTool{ @@ -281,12 +281,12 @@ func main() { // Write README.md readme := fmt.Sprintf(`# %s -A rho plugin. +A decoder plugin. ## Installation `+"```bash"+` -rho plugin install ./%s +decoder 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(" rho plugin install ./"+name, textMuted)) - cmd.Printf("%s\n", auditTint(" rho plugin activate "+name, textMuted)) + cmd.Printf("%s\n", auditTint(" decoder plugin install ./"+name, textMuted)) + cmd.Printf("%s\n", auditTint(" decoder 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 rho plugin marketplace add)", err) + return fmt.Errorf("fetch marketplace: %w (indexes may be unpublished; add a source with decoder 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: rho plugin marketplace add ", textMuted)) + cmd.Println(auditTint("Add a source: decoder plugin marketplace add ", textMuted)) return nil } rows := make([][]string, 0, len(entries)) diff --git a/cmd/power.go b/cmd/power.go index 2a7b1adba..be285c303 100644 --- a/cmd/power.go +++ b/cmd/power.go @@ -3,10 +3,10 @@ package cmd import ( "fmt" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/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 9aaf067b4..68aadab64 100644 --- a/cmd/pr.go +++ b/cmd/pr.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - enginediff "github.com/GrayCodeAI/rho/internal/engine/diff" + enginediff "github.com/GrayCodeAI/decoder/internal/engine/diff" "github.com/spf13/cobra" ) @@ -31,10 +31,10 @@ Subcommands: describe Generate or update a PR description Examples: - rho pr review - rho pr review 42 --post-comments - rho pr create --base develop --draft - rho pr describe 42 --update`, + decoder pr review + decoder pr review 42 --post-comments + decoder pr create --base develop --draft + decoder pr describe 42 --update`, } var prReviewCmd = &cobra.Command{ diff --git a/cmd/preflight_test.go b/cmd/preflight_test.go index b8455d978..e43645cd4 100644 --- a/cmd/preflight_test.go +++ b/cmd/preflight_test.go @@ -9,7 +9,7 @@ import ( // preflightReportShape mirrors the JSON-tagged PreflightReport returned by // EnginePreflightReportWithSettings. We assert against a local shape to -// avoid coupling the test to the exact gateway/flux import path. +// avoid coupling the test to the exact gateway/mux import path. type preflightReportShape struct { Ready bool `json:"ready"` Checks []preflightCheckShape `json:"checks"` @@ -23,7 +23,7 @@ type preflightCheckShape struct { func TestPreflightJSON_Structure(t *testing.T) { dir := t.TempDir() - t.Setenv("RHO_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("DECODER_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("RHO_STATE_DIR", filepath.Join(dir, "state")) + t.Setenv("DECODER_STATE_DIR", filepath.Join(dir, "state")) old := preflightJSON oldLive := preflightLiveFlag diff --git a/cmd/progress_cli.go b/cmd/progress_cli.go index 767c524c5..7e1c7c3c2 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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(SpinnerRho, ""), tty: tty} + return &CLIProgress{w: w, pt: pt, spinner: NewBrailleSpinner(SpinnerDecoder, ""), 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(SpinnerRho, c.pt.Steps[i].Name) + c.spinner = NewBrailleSpinner(SpinnerDecoder, 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 e14b91b5f..f1efb9ca1 100644 --- a/cmd/progress_debug_test.go +++ b/cmd/progress_debug_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestZDebugProgress(t *testing.T) { diff --git a/cmd/progress_tracker.go b/cmd/progress_tracker.go index f0957fcc1..d90b11081 100644 --- a/cmd/progress_tracker.go +++ b/cmd/progress_tracker.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 87a2ab390..e06569e74 100644 --- a/cmd/progress_tracker_test.go +++ b/cmd/progress_tracker_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestNewProgressTracker(t *testing.T) { diff --git a/cmd/progressive_disclosure.go b/cmd/progressive_disclosure.go index b291d836d..a61b43a75 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 rho + return `Getting Started with decoder - Type your question or task and rho will help. - rho reads your project and understands your code. + Type your question or task and decoder will help. + decoder 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 rho review your code + /review Have decoder review your code /clear Start a fresh conversation Tips: - Just describe what you want in plain English - - rho will read files, run commands, and make changes + - decoder 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 `rho Commands + return `decoder 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 `rho Full Command Reference + return `decoder Full Command Reference Workflow: /test [cmd] Run tests (default: go test ./...) diff --git a/cmd/prompt_input.go b/cmd/prompt_input.go index 6bceb9552..d2293b914 100644 --- a/cmd/prompt_input.go +++ b/cmd/prompt_input.go @@ -7,9 +7,9 @@ import ( "os" "strings" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" ) var errNoInteractivePromptInput = errors.New("no interactive terminal available for permission prompt") diff --git a/cmd/prompt_input_test.go b/cmd/prompt_input_test.go index 31ad81cf5..49fc90f26 100644 --- a/cmd/prompt_input_test.go +++ b/cmd/prompt_input_test.go @@ -5,11 +5,11 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/contracts/policy" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/permissions" - "github.com/GrayCodeAI/rho/internal/permissions/stableid" + "github.com/GrayCodeAI/decoder/internal/contracts/policy" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/permissions/stableid" ) func TestConfigureInteractivePromptsSessionApprovalIsScoped(t *testing.T) { diff --git a/cmd/record.go b/cmd/record.go index f268d6824..e0505172f 100644 --- a/cmd/record.go +++ b/cmd/record.go @@ -4,7 +4,7 @@ import ( "io" "os" - "github.com/GrayCodeAI/rho/internal/terminal/tape" + "github.com/GrayCodeAI/decoder/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 7e505da9f..d946ae1f3 100644 --- a/cmd/record_test.go +++ b/cmd/record_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/terminal/tape" + "github.com/GrayCodeAI/decoder/internal/terminal/tape" ) func TestStartRecordingCapturesOutput(t *testing.T) { diff --git a/cmd/resize_unix.go b/cmd/resize_unix.go index 514a6dcc8..2b33c1b18 100644 --- a/cmd/resize_unix.go +++ b/cmd/resize_unix.go @@ -7,7 +7,7 @@ import ( "os/signal" "syscall" - "github.com/GrayCodeAI/rho/internal/terminal/tape" + "github.com/GrayCodeAI/decoder/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 e52743d7c..ca835e8b4 100644 --- a/cmd/resize_windows.go +++ b/cmd/resize_windows.go @@ -2,7 +2,7 @@ package cmd -import "github.com/GrayCodeAI/rho/internal/terminal/tape" +import "github.com/GrayCodeAI/decoder/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 44370a883..5e5ca9df8 100644 --- a/cmd/review.go +++ b/cmd/review.go @@ -10,17 +10,17 @@ import ( "github.com/spf13/cobra" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) var reviewCmd = &cobra.Command{ Use: "review", Short: "Continuous AI code review on commits", - Long: `rho review provides continuous background code review. + Long: `decoder review provides continuous background code review. -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'.`, +Run 'decoder review init' to install a post-commit hook, then every commit +is automatically reviewed. View findings with 'decoder review tui' +or fix them with 'decoder review fix'.`, } var reviewInitCmd = &cobra.Command{ @@ -38,10 +38,10 @@ func init() { } const hookScript = `#!/bin/sh -# rho review — continuous code review hook -# Installed by 'rho review init' +# decoder review — continuous code review hook +# Installed by 'decoder review init' SHA=$(git rev-parse HEAD) -rho review run "$SHA" --background & +decoder 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), "rho review") { - fmt.Println(auditTint(icons.CheckBold()+" ", doneGreen) + auditTint("rho review hook already installed", textPrimary)) + if strings.Contains(string(existing), "decoder review") { + fmt.Println(auditTint(icons.CheckBold()+" ", doneGreen) + auditTint("decoder 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: rho review status", textMuted)) - fmt.Println(auditTint(" Interactive: rho review tui", textMuted)) + fmt.Println(auditTint(" View reviews: decoder review status", textMuted)) + fmt.Println(auditTint(" Interactive: decoder review tui", textMuted)) return nil } diff --git a/cmd/review_fix.go b/cmd/review_fix.go index d74770569..e1c5c7d17 100644 --- a/cmd/review_fix.go +++ b/cmd/review_fix.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) var ( @@ -21,8 +21,8 @@ var ( var reviewFixCmd = &cobra.Command{ Use: "fix [id...]", - Short: "Auto-fix review findings using rho exec", - Long: `Feeds review findings to rho's engine which applies fixes and commits. + Short: "Auto-fix review findings using decoder exec", + Long: `Feeds review findings to decoder'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 rho exec with the fix prompt. + // Invoke decoder 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) - rhoBin, err := os.Executable() + decoderBin, err := os.Executable() if err != nil { - rhoBin = "rho" + decoderBin = "decoder" } - cmd := exec.CommandContext(context.Background(), rhoBin, execArgs...) // #nosec G204 -- rhoBin resolved via os.Executable() or literal 'rho'; args are internal flags + cmd := exec.CommandContext(context.Background(), decoderBin, execArgs...) // #nosec G204 -- decoderBin resolved via os.Executable() or literal 'decoder'; 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("rho exec: %w", err) + return fmt.Errorf("decoder exec: %w", err) } // Mark as fixed. diff --git a/cmd/review_pipeline.go b/cmd/review_pipeline.go index f1b559fa7..4abad8269 100644 --- a/cmd/review_pipeline.go +++ b/cmd/review_pipeline.go @@ -3,7 +3,7 @@ package cmd import ( "context" - reviewfeature "github.com/GrayCodeAI/rho/internal/features/review" + reviewfeature "github.com/GrayCodeAI/decoder/internal/features/review" ) // Compatibility aliases keep existing command and workflow code stable while diff --git a/cmd/review_read.go b/cmd/review_read.go index 2d518d8f4..84fefd983 100644 --- a/cmd/review_read.go +++ b/cmd/review_read.go @@ -10,8 +10,8 @@ import ( "github.com/spf13/cobra" - contracts "github.com/GrayCodeAI/rho/internal/contracts/types" - "github.com/GrayCodeAI/rho/internal/ui/icons" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/types" + "github.com/GrayCodeAI/decoder/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 'rho review init' to start.", textMuted)) + fmt.Println(auditTint("No reviews yet. Run 'decoder review init' to start.", textMuted)) return nil } diff --git a/cmd/review_refine.go b/cmd/review_refine.go index 3ff008546..b169f0dad 100644 --- a/cmd/review_refine.go +++ b/cmd/review_refine.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/cobra" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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), rhoColor)) + fmt.Printf("%s\n", auditTint(fmt.Sprintf("── Iteration %d/%d ──", iter, refineMaxIter), decoderColor)) // 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 'rho review show' to inspect, or increase --max-iterations.", textMuted)) + fmt.Println(auditTint(" Run 'decoder 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) - rhoBin, err := os.Executable() + decoderBin, err := os.Executable() if err != nil { - rhoBin = "rho" + decoderBin = "decoder" } 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(), rhoBin, execArgs...) // #nosec G204 -- rhoBin resolved via os.Executable() or literal 'rho'; args are internal flags + cmd := exec.CommandContext(context.Background(), decoderBin, execArgs...) // #nosec G204 -- decoderBin resolved via os.Executable() or literal 'decoder'; 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 { - rhoBin, err := os.Executable() + decoderBin, err := os.Executable() if err != nil { - rhoBin = "rho" + decoderBin = "decoder" } 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(), rhoBin, args...) // #nosec G204 -- rhoBin resolved via os.Executable() or literal 'rho'; args are internal flags + cmd := exec.CommandContext(context.Background(), decoderBin, args...) // #nosec G204 -- decoderBin resolved via os.Executable() or literal 'decoder'; 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 2b4424741..1172fc282 100644 --- a/cmd/review_run.go +++ b/cmd/review_run.go @@ -8,12 +8,12 @@ import ( "strings" "time" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + reviewcontracts "github.com/GrayCodeAI/decoder/internal/contracts/review" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/types" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/types" + "github.com/GrayCodeAI/decoder/internal/ui/icons" "github.com/spf13/cobra" ) @@ -121,9 +121,9 @@ func runReviewRun(_ *cobra.Command, args []string) error { } } - // Resolve the provider through Rho's Flux engine boundary. + // Resolve the provider through Decoder's Mux engine boundary. ctx := context.Background() - selection := rhoconfig.EffectiveSelection(ctx, rhoconfig.SelectionOptions{ + selection := decoderconfig.EffectiveSelection(ctx, decoderconfig.SelectionOptions{ ProviderOverride: strings.TrimSpace(provider), ModelOverride: strings.TrimSpace(reviewRunModel), }) @@ -144,7 +144,7 @@ func runReviewRun(_ *cobra.Command, args []string) error { defer cancel() } - // Run Rho's own multi-concern review pipeline through the provider. + // Run Decoder's own multi-concern review pipeline through the provider. concerns := DefaultConcerns() if strings.TrimSpace(reviewRunConcerns) != "" { wanted := map[string]bool{} @@ -169,7 +169,7 @@ func runReviewRun(_ *cobra.Command, args []string) error { model = reviewRunModel } chatFn := func(chatCtx context.Context, prompt string) (string, error) { - resp, chatErr := chatProvider.Chat(chatCtx, []types.FluxMessage{{Role: "user", Content: prompt}}, types.ChatOptions{ + resp, chatErr := chatProvider.Chat(chatCtx, []types.MuxMessage{{Role: "user", Content: prompt}}, types.ChatOptions{ Provider: providerID, Model: model, }) diff --git a/cmd/review_store.go b/cmd/review_store.go index 0d4bb8c9b..5270736d8 100644 --- a/cmd/review_store.go +++ b/cmd/review_store.go @@ -1,6 +1,7 @@ package cmd import ( + "context" "database/sql" "encoding/json" "fmt" @@ -10,8 +11,8 @@ import ( "sync" "time" - reviewcontracts "github.com/GrayCodeAI/rho/internal/contracts/review" - "github.com/GrayCodeAI/rho/internal/storage" + reviewcontracts "github.com/GrayCodeAI/decoder/internal/contracts/review" + "github.com/GrayCodeAI/decoder/internal/storage" ) // ReviewStatus represents the state of a review. @@ -76,11 +77,11 @@ func OpenReviewStore(projectDir string) (*ReviewStore, error) { if err != nil { return nil, fmt.Errorf("open review db: %w", err) } - if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil { + if _, err := db.ExecContext(context.Background(), "PRAGMA journal_mode=WAL"); err != nil { _ = db.Close() return nil, err } - if _, err := db.Exec("PRAGMA foreign_keys=ON"); err != nil { + if _, err := db.ExecContext(context.Background(), "PRAGMA foreign_keys=ON"); err != nil { _ = db.Close() return nil, err } @@ -94,18 +95,18 @@ func OpenReviewStore(projectDir string) (*ReviewStore, error) { } func (s *ReviewStore) migrate() error { - _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS review_schema_version (version INTEGER PRIMARY KEY)`) + _, err := s.db.ExecContext(context.Background(), `CREATE TABLE IF NOT EXISTS review_schema_version (version INTEGER PRIMARY KEY)`) if err != nil { return err } var current int - if err := s.db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM review_schema_version").Scan(¤t); err != nil { + if err := s.db.QueryRowContext(context.Background(), "SELECT COALESCE(MAX(version), 0) FROM review_schema_version").Scan(¤t); err != nil { return fmt.Errorf("read review schema version: %w", err) } migrations := []string{reviewSchema} for i := current; i < len(migrations); i++ { - tx, err := s.db.Begin() + tx, err := s.db.BeginTx(context.Background(), nil) if err != nil { return err } @@ -114,12 +115,12 @@ func (s *ReviewStore) migrate() error { if stmt == "" { continue } - if _, err := tx.Exec(stmt); err != nil { + if _, err := tx.ExecContext(context.Background(), stmt); err != nil { _ = tx.Rollback() return fmt.Errorf("migration %d: %w", i+1, err) } } - if _, err := tx.Exec("INSERT INTO review_schema_version (version) VALUES (?)", i+1); err != nil { + if _, err := tx.ExecContext(context.Background(), "INSERT INTO review_schema_version (version) VALUES (?)", i+1); err != nil { _ = tx.Rollback() return err } @@ -134,7 +135,8 @@ func (s *ReviewStore) migrate() error { func (s *ReviewStore) Create(sha string) (int64, error) { s.mu.Lock() defer s.mu.Unlock() - res, err := s.db.Exec( + res, err := s.db.ExecContext( + context.Background(), "INSERT INTO reviews (sha, status) VALUES (?, ?)", sha, ReviewStatusPending, ) @@ -152,7 +154,8 @@ func (s *ReviewStore) Update(id int64, status ReviewStatus, result *reviewcontra findingsJSON, _ := json.Marshal(result.Findings) maxSev := result.MaxSeverity().String() - _, err := s.db.Exec( + _, err := s.db.ExecContext( + context.Background(), `UPDATE reviews SET status=?, findings_json=?, report=?, max_severity=?, tokens_used=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`, status, string(findingsJSON), result.Report, maxSev, result.Stats.TokensUsed, id, ) @@ -163,7 +166,7 @@ func (s *ReviewStore) Update(id int64, status ReviewStatus, result *reviewcontra func (s *ReviewStore) SetStatus(id int64, status ReviewStatus) error { s.mu.Lock() defer s.mu.Unlock() - _, err := s.db.Exec("UPDATE reviews SET status=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", status, id) + _, err := s.db.ExecContext(context.Background(), "UPDATE reviews SET status=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", status, id) return err } @@ -171,7 +174,8 @@ func (s *ReviewStore) SetStatus(id int64, status ReviewStatus) error { func (s *ReviewStore) Get(id int64) (*ReviewRecord, error) { s.mu.RLock() defer s.mu.RUnlock() - return s.scanOne(s.db.QueryRow( + return s.scanOne(s.db.QueryRowContext( + context.Background(), "SELECT id, sha, status, findings_json, report, max_severity, tokens_used, created_at, updated_at FROM reviews WHERE id=?", id, )) } @@ -180,7 +184,8 @@ func (s *ReviewStore) Get(id int64) (*ReviewRecord, error) { func (s *ReviewStore) GetBySHA(sha string) (*ReviewRecord, error) { s.mu.RLock() defer s.mu.RUnlock() - return s.scanOne(s.db.QueryRow( + return s.scanOne(s.db.QueryRowContext( + context.Background(), "SELECT id, sha, status, findings_json, report, max_severity, tokens_used, created_at, updated_at FROM reviews WHERE sha=? ORDER BY created_at DESC LIMIT 1", sha, )) } @@ -203,7 +208,7 @@ func (s *ReviewStore) ListAll(limit int) ([]*ReviewRecord, error) { func (s *ReviewStore) Summary() (map[ReviewStatus]int, error) { s.mu.RLock() defer s.mu.RUnlock() - rows, err := s.db.Query("SELECT status, COUNT(*) FROM reviews GROUP BY status") + rows, err := s.db.QueryContext(context.Background(), "SELECT status, COUNT(*) FROM reviews GROUP BY status") if err != nil { return nil, err } @@ -227,7 +232,7 @@ func (s *ReviewStore) Summary() (map[ReviewStatus]int, error) { func (s *ReviewStore) Close() error { // Checkpoint WAL to flush all data into the main db and truncate // the WAL file, so no .db-wal / .db-shm files linger on disk. - if _, err := s.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil { + if _, err := s.db.ExecContext(context.Background(), "PRAGMA wal_checkpoint(TRUNCATE)"); err != nil { _ = s.db.Close() return fmt.Errorf("checkpoint review WAL: %w", err) } @@ -249,7 +254,7 @@ func (s *ReviewStore) scanOne(row *sql.Row) (*ReviewRecord, error) { } func (s *ReviewStore) query(q string) ([]*ReviewRecord, error) { - rows, err := s.db.Query(q) + rows, err := s.db.QueryContext(context.Background(), q) if err != nil { return nil, err } diff --git a/cmd/review_test.go b/cmd/review_test.go index bd1e69853..d780790ab 100644 --- a/cmd/review_test.go +++ b/cmd/review_test.go @@ -6,11 +6,11 @@ import ( "strings" "testing" - reviewcontracts "github.com/GrayCodeAI/rho/internal/contracts/review" - contracts "github.com/GrayCodeAI/rho/internal/contracts/types" + reviewcontracts "github.com/GrayCodeAI/decoder/internal/contracts/review" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/types" - "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(dir, ".decoder"), 0o755) store, err := OpenReviewStore(dir) if err != nil { @@ -356,9 +356,9 @@ func TestStatusIcon(t *testing.T) { } } -func TestHookScript_ContainsRhoReview(t *testing.T) { - if !strings.Contains(hookScript, "rho review") { - t.Error("hook script should contain 'rho review'") +func TestHookScript_ContainsDecoderReview(t *testing.T) { + if !strings.Contains(hookScript, "decoder review") { + t.Error("hook script should contain 'decoder 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 2922e4ae1..ac67691aa 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) var reviewTUICmd = &cobra.Command{ @@ -122,16 +122,16 @@ func (m reviewTUIModel) View() tea.View { return tea.View{} } - header := lipgloss.NewStyle().Bold(true).Foreground(rhoColor) + header := lipgloss.NewStyle().Bold(true).Foreground(decoderColor) dim := lipgloss.NewStyle().Faint(true) selected := lipgloss.NewStyle().Bold(true).Background(bgCode).Foreground(textPrimary) var b strings.Builder - b.WriteString(header.Render(" rho review") + dim.Render(" j/k:nav enter:expand c:close f:fix r:refresh q:quit") + "\n") + b.WriteString(header.Render(" decoder 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 'rho review init' to get started.\n") + b.WriteString("\n No reviews yet. Run 'decoder review init' to get started.\n") v := tea.View{Content: b.String()} v.AltScreen = true return v diff --git a/cmd/root.go b/cmd/root.go index 045d4f074..e09ea4d7e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,13 +12,13 @@ import ( "sync" "time" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/onboarding" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/update" "github.com/spf13/cobra" ) @@ -88,38 +88,38 @@ func SetBuildDate(d string) { } func registeredProviderCount() int { - return rhoconfig.RegisteredProviderCount() + return decoderconfig.RegisteredProviderCount() } var rootCmd = &cobra.Command{ - Use: "rho [prompt]", - Short: "AI coding agent powered by flux", - Long: fmt.Sprintf(`rho is an AI coding agent that reads, writes, and runs code in your terminal. + Use: "decoder [prompt]", + Short: "AI coding agent powered by mux", + Long: fmt.Sprintf(`decoder is an AI coding agent that reads, writes, and runs code in your terminal. -It connects to %d first-class LLM providers through flux, executes tools (file I/O, shell, +It connects to %d first-class LLM providers through mux, 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: - 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 + decoder Start interactive TUI + decoder -p "prompt" One-shot: send prompt, print response, exit + decoder exec "task" Autonomous multi-turn execution + decoder path Check environment readiness + decoder doctor Run diagnostics + decoder config Manage settings and credentials API keys are stored in the OS keychain (macOS Keychain / Linux keyring). -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`, +Run decoder and use /config to set up your first provider.`, registeredProviderCount()), + Example: ` decoder + decoder -p "explain this repo" + decoder exec "fix failing tests" + decoder preflight + decoder 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 + // not just the bare root command. This keeps `decoder --cwd X ` working // and validates the path once, early. PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return applyCwdFlag() @@ -171,7 +171,7 @@ Run rho and use /config to set up your first provider.`, registeredProviderCount // 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 'rho trust add' to trust this folder before running rho", tr.Path) + return fmt.Errorf("cannot start: folder not trusted (%s)\nProject-scoped hooks, MCP servers, and custom specialists are blocked.\nRun 'decoder trust add' to trust this folder before running decoder", tr.Path) } if replFlag { return runRepl() @@ -220,7 +220,7 @@ Run rho and use /config to set up your first provider.`, registeredProviderCount } func init() { - rootCmd.Flags().StringVarP(&model, "model", "m", "", "model to use (from flux catalog; see /models)") + rootCmd.Flags().StringVarP(&model, "model", "m", "", "model to use (from mux 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", "stream-json", or "transcript"`) @@ -260,7 +260,7 @@ func init() { rootCmd.Flags().IntVar(&teachDepth, "teach-depth", 2, "explanation depth: 1=what, 2=why, 3=how") rootCmd.Flags().BoolVar(&autoSkillFlag, "auto-skill", false, "auto-detect project and install matching skills") rootCmd.Flags().BoolVarP(&versionFlag, "version", "v", false, "output the version number") - rootCmd.Flags().BoolVar(&refreshCatalogFlag, "refresh-catalog", false, "refresh the flux model catalog before starting") + rootCmd.Flags().BoolVar(&refreshCatalogFlag, "refresh-catalog", false, "refresh the mux model catalog before starting") rootCmd.Flags().BoolVar(&skipCatalogRefreshFlag, "no-auto-catalog-refresh", false, "disable automatic catalog refresh when cache is missing, empty, or stale") 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") @@ -431,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 -// RHO_DANGEROUSLY_SKIP_PERMISSIONS=1 environment variable. +// DECODER_DANGEROUSLY_SKIP_PERMISSIONS=1 environment variable. func confirmDangerousSkipPermissions() error { if isStdinTerminal() { fmt.Fprintf(os.Stderr, "Type %s to confirm skipping permission prompts: ", dangerSkipConfirmToken) @@ -446,8 +446,8 @@ func confirmDangerousSkipPermissions() error { return nil } // Non-interactive: require explicit env var override. - if os.Getenv("RHO_DANGEROUSLY_SKIP_PERMISSIONS") != "1" { - return fmt.Errorf("--dangerously-skip-permissions requires RHO_DANGEROUSLY_SKIP_PERMISSIONS=1 in non-interactive mode") + if os.Getenv("DECODER_DANGEROUSLY_SKIP_PERMISSIONS") != "1" { + return fmt.Errorf("--dangerously-skip-permissions requires DECODER_DANGEROUSLY_SKIP_PERMISSIONS=1 in non-interactive mode") } return nil } @@ -471,31 +471,31 @@ var completionCmd = &cobra.Command{ Long: `To load completions: Bash: - source <(rho completion bash) + source <(decoder completion bash) # To load completions for each session, execute once: # Linux: - rho completion bash > /etc/bash_completion.d/rho + decoder completion bash > /etc/bash_completion.d/decoder # macOS: - rho completion bash > /usr/local/etc/bash_completion.d/rho + decoder completion bash > /usr/local/etc/bash_completion.d/decoder Zsh: - source <(rho completion zsh) + source <(decoder completion zsh) # To load completions for each session, execute once: - rho completion zsh > "${fpath[1]}/_rho" + decoder completion zsh > "${fpath[1]}/_decoder" Fish: - rho completion fish | source + decoder completion fish | source # To load completions for each session, execute once: - rho completion fish > ~/.config/fish/completions/rho.fish + decoder completion fish > ~/.config/fish/completions/decoder.fish PowerShell: - rho completion powershell | Out-String | Invoke-Expression + decoder completion powershell | Out-String | Invoke-Expression # To load completions for every new session, run: - rho completion powershell > rho.ps1 + decoder completion powershell > decoder.ps1 # and source this file from your PowerShell profile. JSON: - rho completion json + decoder completion json # Print a machine-readable command/flag spec for IDE integration. `, DisableFlagsInUseLine: true, @@ -529,17 +529,17 @@ var completionInstallCmd = &cobra.Command{ Long: `Install the shell completion script to the standard location for your OS. Bash: - rho completion install bash - # Installs to ~/.local/share/bash-completion/completions/rho (Linux) - # or /opt/homebrew/etc/bash_completion.d/rho (macOS Homebrew) + decoder completion install bash + # Installs to ~/.local/share/bash-completion/completions/decoder (Linux) + # or /opt/homebrew/etc/bash_completion.d/decoder (macOS Homebrew) Zsh: - rho completion install zsh - # Installs to the first directory in $fpath (e.g. /usr/local/share/zsh/site-functions/_rho) + decoder completion install zsh + # Installs to the first directory in $fpath (e.g. /usr/local/share/zsh/site-functions/_decoder) Fish: - rho completion install fish - # Installs to ~/.config/fish/completions/rho.fish`, + decoder completion install fish + # Installs to ~/.config/fish/completions/decoder.fish`, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish"}, Args: cobra.ExactArgs(1), @@ -581,11 +581,11 @@ Fish: var updateCmd = &cobra.Command{ Use: "update", - Short: "Check for rho updates", - Long: "Check GitHub for a newer rho release and print upgrade instructions.", + Short: "Check for decoder updates", + Long: "Check GitHub for a newer decoder release and print upgrade instructions.", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - // Same resolution as `rho --version`, so `go install ...@vX.Y.Z` and + // Same resolution as `decoder --version`, so `go install ...@vX.Y.Z` and // source builds compare their real version instead of "dev". ver := DisplayVersion() prog := NewCLIProgress("Update check", []string{"Checking GitHub for updates"}) @@ -599,7 +599,7 @@ var updateCmd = &cobra.Command{ return nil } if release == nil { - cmd.Println(auditTint("rho is up to date ("+ver+")", doneGreen)) + cmd.Println(auditTint("decoder is up to date ("+ver+")", doneGreen)) return nil } cmd.Println(auditTint("Update available: ", warnAmber) + auditTint(ver+" -> "+release.TagName, textPrimary)) @@ -621,13 +621,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("## rho bug report\n\n") + b.WriteString("## decoder 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 := rhoconfig.LoadSettings() + settings := decoderconfig.LoadSettings() b.WriteString(doctorReport(settings)) b.WriteString("\n```\n") cmd.Print(b.String()) @@ -645,7 +645,7 @@ var versionJSON bool var versionCmd = &cobra.Command{ Use: "version", - Short: "Print rho version", + Short: "Print decoder version", Run: func(cmd *cobra.Command, args []string) { if versionJSON { info := versionInfo{Version: DisplayVersion()} @@ -682,7 +682,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 rho.", + Long: "Launch the interactive setup wizard to configure credentials, select providers/models, and initialize decoder.", RunE: func(cmd *cobra.Command, args []string) error { onboarding.Welcome(version) return onboarding.RunSetup() @@ -746,7 +746,7 @@ var preflightCmd = &cobra.Command{ defer prog.Abort() prog.StartStep(0) } - r := rhoconfig.EnginePreflightReportWithSettings(ctx, settings, rhoconfig.EnginePreflightOptions{VerifyLive: preflightLiveFlag}) + r := decoderconfig.EnginePreflightReportWithSettings(ctx, settings, decoderconfig.EnginePreflightOptions{VerifyLive: preflightLiveFlag}) if prog != nil { prog.CompleteStep(0) prog.Done() @@ -758,14 +758,14 @@ var preflightCmd = &cobra.Command{ } cmd.Println(string(out)) } else { - out := rhoconfig.FormatEnginePreflight(r) + out := decoderconfig.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 rho and complete /config") + return fmt.Errorf("preflight failed — run decoder and complete /config") } return nil }, @@ -775,10 +775,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 rhoconfig.Settings) { - oldVal, hadOld := rhoconfig.SettingValue(settings, key) +func printConfigSetResult(cmd *cobra.Command, key, newVal string, settings decoderconfig.Settings) { + oldVal, hadOld := decoderconfig.SettingValue(settings, key) if hadOld && oldVal != "" && oldVal != newVal { - cmd.Println(auditTint(key, textPrimary) + auditTint(": ", textMuted) + auditTint(oldVal, textMuted) + auditTint(" → ", rhoColor) + auditTint(newVal, textPrimary) + auditTint(" (updated)", doneGreen)) + cmd.Println(auditTint(key, textPrimary) + auditTint(": ", textMuted) + auditTint(oldVal, textMuted) + auditTint(" → ", decoderColor) + auditTint(newVal, textPrimary) + auditTint(" (updated)", doneGreen)) return } cmd.Println(auditTint("updated ", doneGreen) + auditTint(key, textPrimary)) @@ -792,13 +792,13 @@ var configCmd = &cobra.Command{ switch args[0] { case "get": if len(args) != 2 { - return fmt.Errorf("usage: rho config get ") + return fmt.Errorf("usage: decoder config get ") } settings, err := loadEffectiveSettings() if err != nil { return err } - value, ok := rhoconfig.SettingValue(settings, args[1]) + value, ok := decoderconfig.SettingValue(settings, args[1]) if !ok { return fmt.Errorf("unsupported setting key %q", args[1]) } @@ -810,7 +810,7 @@ var configCmd = &cobra.Command{ return nil case "set": if len(args) < 3 { - return fmt.Errorf("usage: rho config set ") + return fmt.Errorf("usage: decoder config set ") } key := args[1] newVal := strings.Join(args[2:], " ") @@ -818,35 +818,35 @@ var configCmd = &cobra.Command{ if err != nil { return err } - if err := rhoconfig.SetGlobalSetting(key, newVal); err != nil { + if err := decoderconfig.SetGlobalSetting(key, newVal); err != nil { return err } printConfigSetResult(cmd, key, newVal, settings) return nil case "provider": if len(args) < 2 { - return fmt.Errorf("usage: rho config provider ") + return fmt.Errorf("usage: decoder config provider ") } newVal := strings.Join(args[1:], " ") settings, err := loadEffectiveSettings() if err != nil { return err } - if err := rhoconfig.SetGlobalSetting("provider", newVal); err != nil { + if err := decoderconfig.SetGlobalSetting("provider", newVal); err != nil { return err } printConfigSetResult(cmd, "provider", newVal, settings) return nil case "model": if len(args) < 2 { - return fmt.Errorf("usage: rho config model ") + return fmt.Errorf("usage: decoder config model ") } newVal := strings.Join(args[1:], " ") settings, err := loadEffectiveSettings() if err != nil { return err } - if err := rhoconfig.SetGlobalSetting("model", newVal); err != nil { + if err := decoderconfig.SetGlobalSetting("model", newVal); err != nil { return err } printConfigSetResult(cmd, "model", newVal, settings) @@ -856,13 +856,13 @@ var configCmd = &cobra.Command{ return nil case "routing-preview": if len(args) < 2 { - return fmt.Errorf("usage: rho config routing-preview ") + return fmt.Errorf("usage: decoder config routing-preview ") } settings, err := loadEffectiveSettings() if err != nil { return err } - out, err := rhoconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, strings.Join(args[1:], " ")) + out, err := decoderconfig.RoutingPreviewJSONWithSettings(cmd.Context(), settings, strings.Join(args[1:], " ")) if err != nil { return err } @@ -883,10 +883,10 @@ var configCmd = &cobra.Command{ var mcpCmd = &cobra.Command{ Use: "mcp", - 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", + Short: "Show MCP configuration; run or register decoder as an MCP server", + Long: "With no subcommand, summarizes the MCP servers decoder connects to (consumes).\n" + + " decoder mcp serve — run decoder itself as an MCP server over stdio\n" + + " decoder mcp config — print the JSON block to register decoder in Claude Desktop/Cursor/Windsurf", RunE: func(cmd *cobra.Command, args []string) error { settings, err := loadEffectiveSettings() if err != nil { @@ -968,7 +968,7 @@ var ( var researchCmd = &cobra.Command{ Use: "research [flags] ", Short: "Autonomous research loop (Karpathy autoresearch pattern)", - Long: "rho research --grep '^val_bpb:' --direction lower 'uv run train.py'", + Long: "decoder 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") @@ -1092,9 +1092,9 @@ var recoverCmd = &cobra.Command{ and offer to resume them. If a session-id is provided, resume that specific session. Examples: - rho recover # List interrupted sessions - rho recover abc123 # Resume specific session - rho --recover # Auto-resume most recent interrupted session`, + decoder recover # List interrupted sessions + decoder recover abc123 # Resume specific session + decoder --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]) @@ -1111,8 +1111,8 @@ Examples: cmd.Println(session.FormatRecoveryCandidates(candidates)) if len(candidates) > 0 { - cmd.Println(auditTint("Resume with: rho recover ", textMuted)) - cmd.Println(auditTint("Or launch TUI with: rho --recover", textMuted)) + cmd.Println(auditTint("Resume with: decoder recover ", textMuted)) + cmd.Println(auditTint("Or launch TUI with: decoder --recover", textMuted)) } return nil }, diff --git a/cmd/rules.go b/cmd/rules.go index 1a83d1581..6fe37d535 100644 --- a/cmd/rules.go +++ b/cmd/rules.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/GrayCodeAI/rho/internal/rules" + "github.com/GrayCodeAI/decoder/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: rho, cursor, claudecode, copilot, gemini. +Supported formats: decoder, 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 rho", + Short: "Import rules from another tool's format into decoder", 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 rho format. - if err := rules.Export(".", rules.FormatRho, imported); err != nil { - return fmt.Errorf("export to rho format failed: %w", err) + // Export to decoder format. + if err := rules.Export(".", rules.FormatDecoder, imported); err != nil { + return fmt.Errorf("export to decoder 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 rho rules to another tool's format", + Short: "Export decoder 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 rho rules. - rhoRules, err := rules.Import(".", rules.FormatRho) + // Read decoder rules. + decoderRules, err := rules.Import(".", rules.FormatDecoder) if err != nil { - return fmt.Errorf("read rho rules failed: %w", err) + return fmt.Errorf("read decoder rules failed: %w", err) } - if len(rhoRules) == 0 { - cmd.Println(auditTint("No rho rules found in .agents/rules/. Nothing to export.", textMuted)) + if len(decoderRules) == 0 { + cmd.Println(auditTint("No decoder rules found in .agents/rules/. Nothing to export.", textMuted)) return nil } to := rules.Format(rulesExportTo) - if err := rules.Export(".", to, rhoRules); err != nil { + if err := rules.Export(".", to, decoderRules); 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(rhoRules), rulesExportTo), doneGreen)) - for _, r := range rhoRules { + cmd.Println(auditTint(fmt.Sprintf("Exported %d rule(s) to %s format.", len(decoderRules), rulesExportTo), doneGreen)) + for _, r := range decoderRules { cmd.Println(auditTint(" - "+r.Name, textPrimary)) } return nil diff --git a/cmd/search.go b/cmd/search.go index 50af517b1..6381f6108 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -5,8 +5,8 @@ import ( "os" "strings" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/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 rho sessions. + Long: `Full-text search across all saved decoder sessions. Searches message content, tool results, and assistant responses. Examples: - rho search "authentication" - rho search --limit 5 "database migration" - rho search "func main"`, + decoder search "authentication" + decoder search --limit 5 "database migration" + decoder 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 1d8ee2628..c66693a00 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/rho/internal/securitylog" + "github.com/GrayCodeAI/decoder/internal/securitylog" ) -// withTempState runs a cli test body with RHO_STATE_DIR pointed at a temp dir +// withTempState runs a cli test body with DECODER_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("RHO_STATE_DIR", dir) + t.Setenv("DECODER_STATE_DIR", dir) body(dir) } diff --git a/cmd/securitylog_cmd.go b/cmd/securitylog_cmd.go index d971a13ed..673303230 100644 --- a/cmd/securitylog_cmd.go +++ b/cmd/securitylog_cmd.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/rho/internal/securitylog" + "github.com/GrayCodeAI/decoder/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: `Rho records security-relevant events (permission denials, approval + Long: `Decoder 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. - 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`, + decoder securitylog Show a summary and recent events + decoder securitylog show List logged events + decoder 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 2f51d2e74..32f38d517 100644 --- a/cmd/session_export.go +++ b/cmd/session_export.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/session" "github.com/spf13/cobra" ) diff --git a/cmd/session_migrate.go b/cmd/session_migrate.go index 9947017f2..e173eb349 100644 --- a/cmd/session_migrate.go +++ b/cmd/session_migrate.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/session" "github.com/spf13/cobra" ) diff --git a/cmd/session_migrate_test.go b/cmd/session_migrate_test.go index 7978e1bbe..402e51037 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("RHO_STATE_DIR", state) + t.Setenv("DECODER_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("RHO_STATE_DIR", state) + t.Setenv("DECODER_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 b63c0cb82..372d31583 100644 --- a/cmd/session_share.go +++ b/cmd/session_share.go @@ -9,12 +9,12 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/session" "github.com/spf13/cobra" ) // sessionShareCmd shares a session. It uploads the export to a configured -// hosted endpoint (RHO_SHARE_URL) when one is set, else falls back to the +// hosted endpoint (DECODER_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("RHO_SHARE_URL")) + host := strings.TrimSpace(os.Getenv("DECODER_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 RHO_SHARE_URL to a share service that accepts POST /v1/shares for a hosted URL; GrayCode Cloud does not host session shares.)\n") + fmt.Printf("(Set DECODER_SHARE_URL to a share service that accepts POST /v1/shares for a hosted URL; GrayCode Cloud does not host session shares.)\n") return nil } diff --git a/cmd/session_sync.go b/cmd/session_sync.go index 39bd88bc8..8a08b849c 100644 --- a/cmd/session_sync.go +++ b/cmd/session_sync.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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(rhoconfig.ActiveGateway(ctx)), strings.TrimSpace(rhoconfig.ActiveModel(ctx)) + return strings.TrimSpace(decoderconfig.ActiveGateway(ctx)), strings.TrimSpace(decoderconfig.ActiveModel(ctx)) } -// syncSessionFromPersistedSelection copies explicit flux provider.json +// syncSessionFromPersistedSelection copies explicit mux provider.json // selection into the live session when the session fields are empty. -// It intentionally avoids runtime defaults so Rho can preserve the +// It intentionally avoids runtime defaults so Decoder 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 7f34f9109..18862c9fe 100644 --- a/cmd/session_sync_test.go +++ b/cmd/session_sync_test.go @@ -5,26 +5,26 @@ import ( "strings" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func TestSyncSessionFromPersistedSelection_FillsEmptySessionModel(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.SetActiveProvider(ctx, "openrouter") - _ = rhoconfig.SetActiveModel(ctx, "gpt-4o") + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.SetActiveProvider(ctx, "openrouter") + _ = decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.SetActiveProvider(ctx, "openrouter") - _ = rhoconfig.SetActiveModel(ctx, "gpt-4o") + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.SetActiveProvider(ctx, "openrouter") + _ = decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.ClearActiveSelection(ctx) + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.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) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() isolateCredentialHome(t) store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) ctx := context.Background() _ = store.Set(ctx, gateway.AccountForEnv("OPENROUTER_API_KEY"), "sk-or-test-key-1234567890") - rhoconfig.InvalidateConfigUICache() - _ = rhoconfig.SetActiveProvider(ctx, "openrouter") - _ = rhoconfig.SetActiveModel(ctx, "gpt-4o") + decoderconfig.InvalidateConfigUICache() + _ = decoderconfig.SetActiveProvider(ctx, "openrouter") + _ = decoderconfig.SetActiveModel(ctx, "gpt-4o") m := &chatModel{ session: engine.NewSession("", "", "base", nil), diff --git a/cmd/skills_cmd.go b/cmd/skills_cmd.go index d43009047..435fe7b1a 100644 --- a/cmd/skills_cmd.go +++ b/cmd/skills_cmd.go @@ -8,8 +8,8 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/tool" "github.com/spf13/cobra" ) diff --git a/cmd/skills_create.go b/cmd/skills_create.go index 730f38ad3..96004ad69 100644 --- a/cmd/skills_create.go +++ b/cmd/skills_create.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/plugin" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/types" "github.com/spf13/cobra" ) @@ -30,10 +30,10 @@ func runSkillsCreate(_ *cobra.Command, args []string) error { return err } model, provider := effectiveModelAndProvider(settings) - sess := newRhoSession(settings, provider, model, "You are a skill author.", tool.NewRegistry()) + sess := newDecoderSession(settings, provider, model, "You are a skill author.", tool.NewRegistry()) prompt := plugin.BuildNewSkillPrompt(desc) - resp, err := sess.Chat(context.Background(), []types.FluxMessage{ + resp, err := sess.Chat(context.Background(), []types.MuxMessage{ {Role: "user", Content: prompt}, }, types.ChatOptions{Model: model, MaxTokens: 4096}) if err != nil { diff --git a/cmd/skills_curator_cmd.go b/cmd/skills_curator_cmd.go index d5d0ef1d4..c1fb10586 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/rho/internal/intelligence/skillcurator" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/intelligence/skillcurator" + "github.com/GrayCodeAI/decoder/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 ~/.rho/skills. +// in ~/.decoder/skills. var skillsCuratorCmd = &cobra.Command{ Use: "curator [command]", Short: "Skill lifecycle curation (status, run, pin, unpin, archive)", diff --git a/cmd/slash_commands_test.go b/cmd/slash_commands_test.go index 0bff63aa7..0b036c2a3 100644 --- a/cmd/slash_commands_test.go +++ b/cmd/slash_commands_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - commandfeature "github.com/GrayCodeAI/rho/internal/features/commands" + commandfeature "github.com/GrayCodeAI/decoder/internal/features/commands" ) func TestSlashCommands_NotEmpty(t *testing.T) { diff --git a/cmd/sleep_prevent.go b/cmd/sleep_prevent.go index 39cdd1874..32ab959e3 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=rho", + "--who=decoder", "--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 7825e31e3..1e979eb96 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/rho/internal/snapshot" + "github.com/GrayCodeAI/decoder/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 rho modifies files."}) + m.messages = append(m.messages, displayMsg{role: "system", content: "No snapshots yet. Snapshots are created automatically when decoder 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. Rho automatically snapshots every file modification.", + Long: "View, restore, and diff file snapshots. Decoder automatically snapshots every file modification.", } var snapshotListCmd = &cobra.Command{ diff --git a/cmd/spec_picker.go b/cmd/spec_picker.go index 3ec2fb8bb..ebdbf8b52 100644 --- a/cmd/spec_picker.go +++ b/cmd/spec_picker.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" diff --git a/cmd/spec_picker_test.go b/cmd/spec_picker_test.go index 6738584e4..de648d691 100644 --- a/cmd/spec_picker_test.go +++ b/cmd/spec_picker_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" tea "charm.land/bubbletea/v2" ) diff --git a/cmd/spinner_wave.go b/cmd/spinner_wave.go index 3ea78ee3a..b981f8f7d 100644 --- a/cmd/spinner_wave.go +++ b/cmd/spinner_wave.go @@ -5,12 +5,12 @@ import ( "strings" "unicode/utf8" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 Rho's brand color. + {255, 215, 0}, // Talon Gold — anchor the wave in Decoder'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 + rhoTypingDots + total := 1 + verbRunes + 1 + decoderTypingDots 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 < rhoTypingDots; i++ { + for i := 0; i < decoderTypingDots; i++ { g := icons.CircleOutline() bold := false - if i == dotPhase%rhoTypingDots { + if i == dotPhase%decoderTypingDots { g = icons.CircleFilled() bold = true } diff --git a/cmd/spinner_wave_test.go b/cmd/spinner_wave_test.go index 085d8b852..e977cf5a4 100644 --- a/cmd/spinner_wave_test.go +++ b/cmd/spinner_wave_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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(SpinnerRho, "Hi") + s := NewBrailleSpinner(SpinnerDecoder, "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(SpinnerRho, "Go") + s := NewBrailleSpinner(SpinnerDecoder, "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 d17c2169a..fc6bb281d 100644 --- a/cmd/stats.go +++ b/cmd/stats.go @@ -8,7 +8,7 @@ import ( "strings" "time" - analytics "github.com/GrayCodeAI/rho/internal/observability" + analytics "github.com/GrayCodeAI/decoder/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 rho.", textMuted)) + cmd.Println(auditTint("Sessions are recorded automatically when you use decoder.", 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("Rho Usage Statistics (%s)", out.Period), rhoColor)) + _, _ = fmt.Fprintf(w, " %s\n", auditTint(fmt.Sprintf("Decoder Usage Statistics (%s)", out.Period), decoderColor)) _, _ = fmt.Fprintf(w, "══════════════════════════════════════════════════\n") // Overview section diff --git a/cmd/status_snapshot.go b/cmd/status_snapshot.go index fc1ee6859..194a4d88c 100644 --- a/cmd/status_snapshot.go +++ b/cmd/status_snapshot.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" - 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" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/plugin" + "github.com/GrayCodeAI/decoder/internal/status" "github.com/spf13/cobra" ) @@ -34,11 +34,11 @@ var statusCmd = &cobra.Command{ func buildStatusSnapshot() status.Snapshot { snapshot := status.New() - snapshot.RhoVersion = version + snapshot.DecoderVersion = version snapshot.Workspace = status.Workspace() snapshot.GitBranch = engine.InspectGitBranch("").Branch - settings := rhoconfig.LoadGlobalSettings() - selection := rhoconfig.EffectiveSelection(context.Background(), rhoconfig.SelectionOptions{}) + settings := decoderconfig.LoadGlobalSettings() + selection := decoderconfig.EffectiveSelection(context.Background(), decoderconfig.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("Rho status", rhoColor) + "\n") + b.WriteString(auditTint("Decoder status", decoderColor) + "\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 9196fad69..65243a606 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{"Rho status", "Schema: 1", "Secrets redacted: true"} { + for _, expected := range []string{"Decoder 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 fcc4b283c..fd3cdc92b 100644 --- a/cmd/statusbar.go +++ b/cmd/statusbar.go @@ -7,18 +7,18 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/engine/cost" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine/cost" tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" "golang.org/x/text/language" "golang.org/x/text/message" - "github.com/GrayCodeAI/rho/internal/engine/git" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/engine/git" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) var ( diff --git a/cmd/statusbar_test.go b/cmd/statusbar_test.go index b1245cd44..2f25bbcaf 100644 --- a/cmd/statusbar_test.go +++ b/cmd/statusbar_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" lipgloss "charm.land/lipgloss/v2" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) func TestRenderStatusBar_SignatureExists(t *testing.T) { @@ -120,8 +120,8 @@ func TestRenderStatusBarPrimaryLeft_UsesCachedState(t *testing.T) { func TestShortenHomePath(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) - got := shortenHomePath(home + "/project/rho") - if got != "~/project/rho" { + got := shortenHomePath(home + "/project/decoder") + if got != "~/project/decoder" { t.Fatalf("got %q", got) } } diff --git a/cmd/storage_policy_test.go b/cmd/storage_policy_test.go index 7bd694163..72bd935a3 100644 --- a/cmd/storage_policy_test.go +++ b/cmd/storage_policy_test.go @@ -7,10 +7,10 @@ import ( "testing" ) -func TestStoragePolicyHelpersDoNotCreateProjectRho(t *testing.T) { +func TestStoragePolicyHelpersDoNotCreateProjectDecoder(t *testing.T) { project := t.TempDir() - t.Setenv("RHO_STATE_DIR", filepath.Join(t.TempDir(), "state")) - t.Setenv("RHO_CACHE_DIR", filepath.Join(t.TempDir(), "cache")) + t.Setenv("DECODER_STATE_DIR", filepath.Join(t.TempDir(), "state")) + t.Setenv("DECODER_CACHE_DIR", filepath.Join(t.TempDir(), "cache")) oldwd, err := os.Getwd() if err != nil { t.Fatal(err) @@ -21,13 +21,13 @@ func TestStoragePolicyHelpersDoNotCreateProjectRho(t *testing.T) { } planPath := resolvePlanPath("demo") - if strings.Contains(planPath, filepath.Join(project, ".rho")) { - t.Fatalf("resolvePlanPath leaked project .rho: %q", planPath) + if strings.Contains(planPath, filepath.Join(project, ".decoder")) { + t.Fatalf("resolvePlanPath leaked project .decoder: %q", planPath) } saveInputHistory([]string{"hello"}) recordTipShown("slash-help") - if _, err := os.Stat(filepath.Join(project, ".rho")); !os.IsNotExist(err) { - t.Fatalf("normal storage helpers created project .rho, stat err=%v", err) + if _, err := os.Stat(filepath.Join(project, ".decoder")); !os.IsNotExist(err) { + t.Fatalf("normal storage helpers created project .decoder, stat err=%v", err) } } diff --git a/cmd/tabcomplete.go b/cmd/tabcomplete.go index da9b9a6e6..2b7fa65e7 100644 --- a/cmd/tabcomplete.go +++ b/cmd/tabcomplete.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/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 57a7655c9..56878b97e 100644 --- a/cmd/tabcomplete_test.go +++ b/cmd/tabcomplete_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/tool" ) func TestFilePathCompletions_CurrentDir(t *testing.T) { diff --git a/cmd/taste.go b/cmd/taste.go index b03be545a..7a56c5e3f 100644 --- a/cmd/taste.go +++ b/cmd/taste.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/rho/internal/features/taste" + "github.com/GrayCodeAI/decoder/internal/features/taste" "github.com/spf13/cobra" ) @@ -36,8 +36,8 @@ var tastePushCmd = &cobra.Command{ with teammates or used across machines. Examples: - rho taste push # Export to stdout - rho taste push --file team.json # Export to file`, + decoder taste push # Export to stdout + decoder 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: - rho taste pull team.json - cat team.json | rho taste pull -`, + decoder taste pull team.json + cat team.json | decoder taste pull -`, Args: cobra.ExactArgs(1), RunE: runTastePull, } diff --git a/cmd/terminal_notify.go b/cmd/terminal_notify.go index 7bc213beb..07f55732e 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=rho:d=0;%s\033\\", body) + _, _ = fmt.Fprintf(os.Stderr, "\033]99;i=decoder: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/testfirst_workflow.go b/cmd/testfirst_workflow.go index 1415705a5..1a56cb89f 100644 --- a/cmd/testfirst_workflow.go +++ b/cmd/testfirst_workflow.go @@ -1,6 +1,6 @@ package cmd -import executionfeature "github.com/GrayCodeAI/rho/internal/features/execution" +import executionfeature "github.com/GrayCodeAI/decoder/internal/features/execution" // Compatibility aliases keep callers stable while execution owns the // test-first workflow implementation. diff --git a/cmd/textutil_shim.go b/cmd/textutil_shim.go index 09229a4df..08f29d0da 100644 --- a/cmd/textutil_shim.go +++ b/cmd/textutil_shim.go @@ -1,6 +1,6 @@ package cmd -import "github.com/GrayCodeAI/rho/internal/textutil" +import "github.com/GrayCodeAI/decoder/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 2f5aca857..72e7e0f79 100644 --- a/cmd/theme.go +++ b/cmd/theme.go @@ -1,11 +1,11 @@ package cmd -// theme.go — the single source of truth for rho's visual identity. +// theme.go — the single source of truth for decoder'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 rho, edit this file; to audit +// hex codes or magic strings. To rebrand decoder, edit this file; to audit // what's used where, grep this file. // // Organization: @@ -27,17 +27,17 @@ import ( lipgloss "charm.land/lipgloss/v2" "charm.land/lipgloss/v2/compat" - internaltheme "github.com/GrayCodeAI/rho/internal/theme" + internaltheme "github.com/GrayCodeAI/decoder/internal/theme" ) // --------------------------------------------------------------------------- // 1. Brand & identity // --------------------------------------------------------------------------- -// rhoColor is Talon Gold (#FFD700). Used for the RHO wordmark, rho, +// decoderColor is Talon Gold (#FFD700). Used for the DECODER wordmark, decoder, // ⛬ assistant prefix, prompt arrow, cursor, exit prompt, and // any place that should "speak" as the brand. -var rhoColor = lipgloss.Color(internaltheme.BrandPrimary) +var decoderColor = lipgloss.Color(internaltheme.BrandPrimary) // --------------------------------------------------------------------------- // 2. UI state @@ -217,7 +217,7 @@ func ansiFromHex(hex string) string { // --------------------------------------------------------------------------- // 10. Icons & glyphs // -// Rho's icon registry lives in internal/ui/icons. Every call site +// Decoder'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 @@ -261,7 +261,7 @@ func ApplyTheme(name string) { p := entry.Palette // 1. Brand — fixed across themes; palette accents remain theme-specific. - rhoColor = lipgloss.Color(internaltheme.BrandPrimary) + decoderColor = lipgloss.Color(internaltheme.BrandPrimary) // 2. Semantic feedback. successTeal = lipgloss.Color(p.Green) @@ -338,12 +338,12 @@ var minimalChrome bool // unsynchronized from View. func refreshThemeStyles() { // markdown.go - mdH1Style = lipgloss.NewStyle().Foreground(rhoColor).Bold(true).Underline(true) + mdH1Style = lipgloss.NewStyle().Foreground(decoderColor).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(rhoColor).Bold(true) + mdBoldStyle = lipgloss.NewStyle().Foreground(decoderColor).Bold(true) mdInlineCodeStyle = lipgloss.NewStyle().Foreground(infoSky) mdCodeBlockStyle = lipgloss.NewStyle().Background(bgCode) mdCodeLabelStyle = lipgloss.NewStyle().Foreground(textDisabled).Background(bgCode) @@ -362,8 +362,8 @@ func refreshThemeStyles() { toolDimStyle = lipgloss.NewStyle().Foreground(textDisabled) slashCmdStyle = lipgloss.NewStyle().Foreground(textDisabled) slashDescStyle = lipgloss.NewStyle().Foreground(textDisabled) - slashSelCmdStyle = lipgloss.NewStyle().Foreground(rhoColor).Bold(true) - slashSelDescStyle = lipgloss.NewStyle().Foreground(rhoColor) + slashSelCmdStyle = lipgloss.NewStyle().Foreground(decoderColor).Bold(true) + slashSelDescStyle = lipgloss.NewStyle().Foreground(decoderColor) 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) @@ -373,7 +373,7 @@ func refreshThemeStyles() { dimColor = textDisabled // agent_grid.go - agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(rhoColor).Padding(0, 1) + agentActiveStyle = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(decoderColor).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/theme_picker.go b/cmd/theme_picker.go index ce9356496..9ca9913c0 100644 --- a/cmd/theme_picker.go +++ b/cmd/theme_picker.go @@ -7,7 +7,7 @@ import ( tea "charm.land/bubbletea/v2" lipgloss "charm.land/lipgloss/v2" - internaltheme "github.com/GrayCodeAI/rho/internal/theme" + internaltheme "github.com/GrayCodeAI/decoder/internal/theme" ) // detectAutoIsDark returns whether "auto" theme currently resolves to dark. @@ -167,7 +167,7 @@ func (tp *ThemePicker) View() tea.View { palette := themePickerPalette(tp.entries[tp.sel].Name) titleStyle := lipgloss.NewStyle(). - Background(rhoColor). + Background(decoderColor). Foreground(lipgloss.Color(palette.OnAccent)). Bold(true). Padding(0, 1) diff --git a/cmd/tips.go b/cmd/tips.go index 26a7a01c3..677ae37cf 100644 --- a/cmd/tips.go +++ b/cmd/tips.go @@ -8,10 +8,10 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/storage" ) -// Tip represents a single rho usage tip. +// Tip represents a single decoder 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 rho.", Category: "shortcuts"}, + {ID: "ctrl-c-quit", Text: "Press Ctrl+C twice to quit decoder.", 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"}, @@ -44,14 +44,14 @@ func allTips() []Tip { {ID: "slash-review", Text: "Use /review to get a code review of current changes.", Category: "workflow"}, {ID: "slash-init", Text: "Use /init to analyze a new project automatically.", Category: "project"}, {ID: "add-dir", Text: "Use /add-dir to add extra directories to context.", Category: "context"}, - {ID: "slash-memory", Text: "Use /memory for AGENTS.md; /learn for lessons; /ecosystem for flux and token-engine status.", Category: "context"}, + {ID: "slash-memory", Text: "Use /memory for AGENTS.md; /learn for lessons; /ecosystem for mux and token-engine status.", Category: "context"}, {ID: "slash-rewind", Text: "Use /rewind to undo the last exchange.", Category: "session"}, {ID: "slash-fork", Text: "Use /fork to branch off the current conversation.", Category: "session"}, {ID: "slash-context", Text: "Use /context to see what the agent knows about your project.", Category: "context"}, {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 rho/agent-* branch.", Category: "git"}, + {ID: "slash-branch-agent", Text: "Use /branch-agent before big edits on main — creates decoder/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 a9595ac70..95a553717 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(tmp, ".decoder"), 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, ".rho"), 0o755) + os.MkdirAll(filepath.Join(tmp, ".decoder"), 0o755) defer os.Setenv("HOME", orig) recordTipShown("slash-help") diff --git a/cmd/tool_policy_catalog_test.go b/cmd/tool_policy_catalog_test.go index d753d4342..40486b74f 100644 --- a/cmd/tool_policy_catalog_test.go +++ b/cmd/tool_policy_catalog_test.go @@ -3,7 +3,7 @@ package cmd import ( "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) // Every built-in tool must have explicit capability metadata. Without this diff --git a/cmd/toolset_cmd.go b/cmd/toolset_cmd.go index 8376b5d6f..cc2b833fe 100644 --- a/cmd/toolset_cmd.go +++ b/cmd/toolset_cmd.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/toolset" + "github.com/GrayCodeAI/decoder/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. - rho toolset List available toolsets - rho toolset research Resolve 'research' to its concrete tool list + decoder toolset List available toolsets + decoder 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 e4fff9e92..cb7bf9c95 100644 --- a/cmd/trust.go +++ b/cmd/trust.go @@ -5,10 +5,10 @@ import ( "fmt" "os" - "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/internal/theme" - "github.com/GrayCodeAI/rho/internal/flags" - "github.com/GrayCodeAI/rho/internal/trust" + "github.com/GrayCodeAI/decoder/internal/flags" + "github.com/GrayCodeAI/decoder/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 RHO_Y0_FOLDER_TRUST is enabled (default after Year 0 PACK-03), +When DECODER_Y0_FOLDER_TRUST is enabled (default after Year 0 PACK-03), untrusted projects cannot run project automation (RCE mitigation). -User-global plugins under the Rho state directory always load.`, +User-global plugins under the Decoder state directory always load.`, } var trustAddCmd = &cobra.Command{ @@ -107,7 +107,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 (RHO_Y0_FOLDER_TRUST)", flags.FolderTrust()), textMuted)) + cmd.Printf("%s\n", auditTint(fmt.Sprintf("Folder trust enforcement: %v (DECODER_Y0_FOLDER_TRUST)", flags.FolderTrust()), textMuted)) } return nil } diff --git a/cmd/usage.go b/cmd/usage.go index 4ec3429c1..ea2682e12 100644 --- a/cmd/usage.go +++ b/cmd/usage.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/GrayCodeAI/rho/internal/usage" + "github.com/GrayCodeAI/decoder/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), rhoColor)) + cmd.Println(auditTint(fmt.Sprintf("Usage (last %s)", usagePeriod), decoderColor)) 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 45559f29a..18493084c 100644 --- a/cmd/verify_cmd.go +++ b/cmd/verify_cmd.go @@ -8,9 +8,9 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/governance" - "github.com/GrayCodeAI/rho/internal/securitylog" - "github.com/GrayCodeAI/rho/internal/testrunner" + "github.com/GrayCodeAI/decoder/internal/governance" + "github.com/GrayCodeAI/decoder/internal/securitylog" + "github.com/GrayCodeAI/decoder/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 rho's self-verification checks without a model: + Long: `Run decoder'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 d18ffc71d..defcce659 100644 --- a/cmd/version_display.go +++ b/cmd/version_display.go @@ -1,111 +1,30 @@ package cmd import ( - "os" - "path/filepath" - "runtime/debug" "strings" - "golang.org/x/mod/module" - "golang.org/x/mod/semver" + "github.com/GrayCodeAI/decoder" ) // versionLine is the single user-facing version format shared by -// `rho --version` and `rho version`. +// `decoder --version` and `decoder version`. func versionLine() string { ver := DisplayVersion() if ver != "" && !strings.HasPrefix(ver, "v") && !strings.HasPrefix(ver, "V") { ver = "v" + ver } - line := auditTint("rho", textPrimary) + " " + auditTint(ver, rhoColor) + line := auditTint("decoder", textPrimary) + " " + auditTint(ver, decoderColor) if d := strings.TrimSpace(buildDate); d != "" && d != "unknown" { line += auditTint(" (built "+d+")", textMuted) } return line } -// DisplayVersion returns the user-facing version string for banners and /version. -// -// In order of preference: -// 1. the version injected via ldflags (release archives, `make build`); -// 2. the module version recorded by `go install .../cmd/rho@vX.Y.Z`; -// 3. the repository VERSION file (local `go build` / `go run` checkouts); -// 4. "dev". +// DisplayVersion returns the build-injected version or the embedded VERSION. +// It never depends on the current directory or files next to the binary. func DisplayVersion() string { - v := strings.TrimSpace(version) - if v != "" && v != "dev" { + if v := strings.TrimSpace(version); v != "" && v != "dev" { return v } - if mv := releaseModuleVersion(); mv != "" { - return mv - } - if fromFile := readRepoVERSIONFile(); fromFile != "" { - return fromFile - } - if v != "" { - return v - } - return "dev" -} - -// readBuildInfo is debug.ReadBuildInfo, replaceable in tests. -var readBuildInfo = debug.ReadBuildInfo - -// releaseModuleVersion returns the main module's version without the leading -// "v" when the binary was built from a tagged module (`go install -// github.com/GrayCodeAI/rho/cmd/rho@v0.3.0`), and "" for development builds: -// "(devel)", pseudo-versions (untagged commits such as @main) and versions -// with build metadata (e.g. "+dirty" from a modified checkout). -func releaseModuleVersion() string { - info, ok := readBuildInfo() - if !ok || info == nil { - return "" - } - mv := info.Main.Version - if !semver.IsValid(mv) || module.IsPseudoVersion(mv) || semver.Build(mv) != "" { - return "" - } - return strings.TrimPrefix(mv, "v") -} - -func readRepoVERSIONFile() string { - candidates := versionFileCandidates() - for _, path := range candidates { - data, err := os.ReadFile(path) // #nosec G304 -- path from internal candidate list, not external input - if err != nil { - continue - } - v := strings.TrimSpace(string(data)) - if v != "" { - return v - } - } - return "" -} - -func versionFileCandidates() []string { - var out []string - if exe, err := os.Executable(); err == nil { - dir := filepath.Dir(exe) - for i := 0; i < 4; i++ { - out = append(out, filepath.Join(dir, "VERSION")) - parent := filepath.Dir(dir) - if parent == dir { - break - } - dir = parent - } - } - if cwd, err := os.Getwd(); err == nil { - dir := cwd - for i := 0; i < 4; i++ { - out = append(out, filepath.Join(dir, "VERSION")) - parent := filepath.Dir(dir) - if parent == dir { - break - } - dir = parent - } - } - return out + return decoder.Version } diff --git a/cmd/version_display_test.go b/cmd/version_display_test.go index 622468024..2093781a8 100644 --- a/cmd/version_display_test.go +++ b/cmd/version_display_test.go @@ -3,89 +3,43 @@ package cmd import ( "os" "path/filepath" - "runtime/debug" "testing" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) -func TestDisplayVersion_FromVERSIONFile(t *testing.T) { +func TestDisplayVersion_EmbeddedVersionIgnoresWorkingDirectory(t *testing.T) { + preserveCLICompilerVersionState(t) dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte("0.0.1\n"), 0o644); err != nil { + if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte("9.9.9\n"), 0o644); err != nil { t.Fatal(err) } t.Chdir(dir) - SetVersion("dev") - if got := DisplayVersion(); got != "0.0.1" { - t.Fatalf("DisplayVersion() = %q, want 0.0.1", got) - } -} - -func stubBuildInfo(t *testing.T, mainVersion string) { - t.Helper() - prev := readBuildInfo - readBuildInfo = func() (*debug.BuildInfo, bool) { - return &debug.BuildInfo{Main: debug.Module{Path: "github.com/GrayCodeAI/rho", Version: mainVersion}}, true - } - t.Cleanup(func() { readBuildInfo = prev }) -} - -func TestDisplayVersion_GoInstallTaggedModule(t *testing.T) { - t.Chdir(t.TempDir()) // no VERSION file nearby - SetVersion("dev") - stubBuildInfo(t, "v0.3.0") - if got := DisplayVersion(); got != "0.3.0" { - t.Fatalf("DisplayVersion() = %q, want 0.3.0 for go install ...@v0.3.0", got) - } -} - -func TestDisplayVersion_DevelopmentModuleVersionsFallBack(t *testing.T) { - for _, mv := range []string{ - "(devel)", - "v0.0.0-20260921005105-13e05ceb15e5", // go install ...@main - "v0.3.1-0.20260930120000-abcdefabcdef", // untagged commit after v0.3.0 - "v0.3.0+dirty", // modified checkout at the tag - "", - } { - t.Run(mv, func(t *testing.T) { - dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte("0.3.0\n"), 0o644); err != nil { - t.Fatal(err) - } - t.Chdir(dir) - SetVersion("dev") - stubBuildInfo(t, mv) - if got := DisplayVersion(); got != "0.3.0" { - t.Fatalf("DisplayVersion() = %q, want the VERSION file value for module version %q", got, mv) - } - }) - } -} - -func TestDisplayVersion_LdflagsWinOverModuleVersion(t *testing.T) { - SetVersion("1.4.2") - t.Cleanup(func() { SetVersion("dev") }) - stubBuildInfo(t, "v0.3.0") - if got := DisplayVersion(); got != "1.4.2" { - t.Fatalf("DisplayVersion() = %q, want the ldflags version 1.4.2", got) + for _, value := range []string{"", "dev"} { + SetVersion(value) + if got := DisplayVersion(); got != decoder.Version { + t.Fatalf("got %q, want embedded %q", got, decoder.Version) + } } } func TestDisplayVersion_ReleaseBuild(t *testing.T) { + preserveCLICompilerVersionState(t) SetVersion("1.4.2") if got := DisplayVersion(); got != "1.4.2" { - t.Fatalf("DisplayVersion() = %q, want 1.4.2", got) + t.Fatalf("got %q, want 1.4.2", got) } } func TestChatConnectionStatus_NoCredentials(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{session: nil} @@ -96,12 +50,12 @@ func TestChatConnectionStatus_NoCredentials(t *testing.T) { } func TestChatBottomRightStatus_NoCredentials(t *testing.T) { - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) - rhoconfig.InvalidateConfigUICache() + decoderconfig.InvalidateConfigUICache() }) m := chatModel{inputIndicator: &InputIndicator{}} diff --git a/cmd/visual_diff.go b/cmd/visual_diff.go index 42ffa6650..3404fe3b3 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 rho's semantic palette so diff +// DefaultDiffTheme returns a DiffTheme using decoder'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 // fileHeaderBlue — each color mapped to one meaning, never reused for another. diff --git a/cmd/watch.go b/cmd/watch.go index 88f4a3ddc..104edbc5c 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/gitcmd" + "github.com/GrayCodeAI/decoder/internal/gitcmd" "github.com/fsnotify/fsnotify" ) diff --git a/cmd/welcome_banner.go b/cmd/welcome_banner.go index ab202ab20..91c7e7515 100644 --- a/cmd/welcome_banner.go +++ b/cmd/welcome_banner.go @@ -6,8 +6,8 @@ import ( "github.com/mattn/go-runewidth" ) -// rhoBlockGlyphs — fixed 8-column ██ font used by the welcome gate banners. -var rhoBlockGlyphs = map[rune][5]string{ +// decoderBlockGlyphs — fixed 8-column ██ font used by the welcome gate banners. +var decoderBlockGlyphs = map[rune][5]string{ 'H': {"██ ██ ", "██ ██ ", "███████ ", "██ ██ ", "██ ██ "}, 'A': {" ███ ", " █████ ", "███████ ", "██ ██ ", "██ ██ "}, 'W': {"██ ██ ", "██ ██ ", "██ █ ██ ", "███ ███ ", "██ ██ "}, @@ -20,40 +20,29 @@ var rhoBlockGlyphs = map[rune][5]string{ 'T': {"████████", " ██ ", " ██ ", " ██ ", " ██ "}, } -/* legacyRhoLogoArtLines is retained only as a migration reference. -var legacyRhoLogoArtLines = []string{ - "| ___ | | ___ | (\\ /) | \\", - "| | | | | | | | \\ \\/ / | |\\ \\", - "|___| |___| |___| |___| \\____/\\/\\____/ |___| \\___\\", - " |0\\/0|", - " \\/\\/", - " \\/", -} -*/ - -// rhoLogoArtLines is the canonical portable ASCII RHO wordmark. -var rhoLogoArtLines = []string{ - " ____ _ _ ___ ", - "| _ \\| | | |/ _ \\ ", - "| |_) | |_| | | | |", - "| _ <| _ | |_| |", - "|_| \\_\\_| |_|\\___/", +// decoderLogoArtLines is the canonical portable ASCII DECODER wordmark. +var decoderLogoArtLines = []string{ + " ____ _____ ____ ___ ____ _____ ____ ", + "| _ \\| ____/ ___/ _ \\| _ \\| ____| _ \\ ", + "| | | | _|| | | | | | | | | _| | |_) |", + "| |_| | |__| |__| |_| | |_| | |___| _ < ", + "|____/|_____\\____\\___/|____/|_____|_| \\_\\", } const ( - rhoBlockCellW = 8 - rhoBlockLetterGap = 1 - rhoBlockWordGap = 4 + decoderBlockCellW = 8 + decoderBlockLetterGap = 1 + decoderBlockWordGap = 4 ) // welcomeWordLines — "WELCOME" block (row-aligned, fixed grid). -var welcomeWordLines = composeRhoBlockLines("WELCOME") +var welcomeWordLines = composeDecoderBlockLines("WELCOME") // welcomeToWordLines — "TO" block, centered under WELCOME on the gate. -var welcomeToWordLines = composeRhoBlockLines("TO") +var welcomeToWordLines = composeDecoderBlockLines("TO") // welcomeToPhraseLines — "WELCOME TO" block for wide welcome gates. -var welcomeToPhraseLines = composeRhoBlockLines("WELCOME TO") +var welcomeToPhraseLines = composeDecoderBlockLines("WELCOME TO") // welcomeToBannerMinWidth is the visible width for the WELCOME block. const welcomeToBannerMinWidth = 61 @@ -61,24 +50,24 @@ const welcomeToBannerMinWidth = 61 // welcomeToPhraseMinWidth is the visible width for the combined "WELCOME TO" block. var welcomeToPhraseMinWidth = blockLinesWidth(welcomeToPhraseLines) -func composeRhoBlockLines(text string) []string { +func composeDecoderBlockLines(text string) []string { rows := make([]string, 5) words := strings.Fields(text) for wi, word := range words { for ci, ch := range word { - glyph, ok := rhoBlockGlyphs[ch] + glyph, ok := decoderBlockGlyphs[ch] if !ok { continue } for i := range rows { if rows[i] != "" { if ci == 0 && wi > 0 { - rows[i] += strings.Repeat(" ", rhoBlockWordGap) + rows[i] += strings.Repeat(" ", decoderBlockWordGap) } else { - rows[i] += strings.Repeat(" ", rhoBlockLetterGap) + rows[i] += strings.Repeat(" ", decoderBlockLetterGap) } } - cell := padBlockCell(glyph[i], rhoBlockCellW) + cell := padBlockCell(glyph[i], decoderBlockCellW) rows[i] += cell } } diff --git a/cmd/welcome_banner_test.go b/cmd/welcome_banner_test.go index d87d359c5..332343ab9 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 := composeRhoBlockLines("TO") + to := composeDecoderBlockLines("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_SameWGlyphAsRHO(t *testing.T) { - w := strings.TrimRight(rhoBlockGlyphs['W'][0], " ") +func TestWelcomeWordLines_SameWGlyphAsDECODER(t *testing.T) { + w := strings.TrimRight(decoderBlockGlyphs['W'][0], " ") if !strings.HasPrefix(welcomeWordLines[0], w) { - t.Fatalf("W row should start with rho logo glyph:\nwelcome: %q\nrho W: %q", + t.Fatalf("W row should start with decoder logo glyph:\nwelcome: %q\ndecoder W: %q", welcomeWordLines[0], w) } } diff --git a/cmd/welcome_inline_test.go b/cmd/welcome_inline_test.go index 5374c921d..c9bfe6e19 100644 --- a/cmd/welcome_inline_test.go +++ b/cmd/welcome_inline_test.go @@ -8,9 +8,9 @@ import ( "charm.land/bubbles/v2/textarea" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/ui/icons" + decoderconfig "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/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, rhoconfig.Settings{}, 0, false, 100, 24) + out := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.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, rhoconfig.Settings{}, 0, false, 100, 24) + out := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.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, rhoconfig.Settings{}, 0, false, 100, 24) + out := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.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) @@ -97,7 +97,7 @@ func TestBuildWelcomeMessage_InlineShowsGuidance(t *testing.T) { t.Fatalf("minimal welcome should omit %q, got:\n%s", noise, out) } } - for _, guidance := range []string{"Inspect, change, or test this codebase with rho.", `Try: "explain this repo"`} { + for _, guidance := range []string{"Inspect, change, or test this codebase with decoder.", `Try: "explain this repo"`} { if !strings.Contains(out, guidance) { t.Fatalf("minimal welcome missing first-run guidance %q in:\n%s", guidance, out) } @@ -105,7 +105,7 @@ func TestBuildWelcomeMessage_InlineShowsGuidance(t *testing.T) { } func TestBuildWelcomeMessage_ShortTerminalUsesCompactCopy(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 72, 20) + out := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.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) } @@ -114,29 +114,29 @@ func TestBuildWelcomeMessage_ShortTerminalUsesCompactCopy(t *testing.T) { } } -func TestBuildWelcomeMessage_WideTerminalUsesRhoWordmark(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, false, 120, 40) +func TestBuildWelcomeMessage_WideTerminalUsesDecoderWordmark(t *testing.T) { + out := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.Settings{}, 0, false, 120, 40) for _, want := range []string{ - " ____ _ _ ___ ", - "| _ \\| | | |/ _ \\ ", - "|_| \\_\\_| |_|\\___/", + " ____ _____ ____ ___ ____ _____ ____ ", + "| _ \\| ____/ ___/ _ \\| _ \\| ____| _ \\ ", + "|____/|_____\\____\\___/|____/|_____|_| \\_\\", } { if !strings.Contains(out, want) { - t.Fatalf("wide welcome missing rho wordmark line %q in:\n%s", want, out) + t.Fatalf("wide welcome missing decoder wordmark line %q in:\n%s", want, out) } } } -func TestBuildWelcomeMessage_RhoWordmarkIsStatic(t *testing.T) { - out := buildWelcomeMessage(nil, "", nil, nil, rhoconfig.Settings{}, 0, true, 120, 40) - if !strings.Contains(out, " ____ _ _ ___ ") { - t.Fatalf("blinking welcome should retain the RHO wordmark, got:\n%s", out) +func TestBuildWelcomeMessage_DecoderWordmarkIsStatic(t *testing.T) { + out := buildWelcomeMessage(nil, "", nil, nil, decoderconfig.Settings{}, 0, true, 120, 40) + if !strings.Contains(out, " ____ _____ ____ ___ ____ _____ ____ ") { + t.Fatalf("blinking welcome should retain the DECODER wordmark, got:\n%s", out) } } func TestBuildWelcomeMessage_GraphicalMascotKeepsTextFallback(t *testing.T) { - out := buildWelcomeMessageWithSnapshotAndMascot(nil, "", nil, nil, rhoconfig.Settings{}, 0, 0, 0, 120, 40, welcomeStatusSnapshot{}, "", true) - if !strings.Contains(out, " ____ _ _ ___ ") { + out := buildWelcomeMessageWithSnapshotAndMascot(nil, "", nil, nil, decoderconfig.Settings{}, 0, 0, 0, 120, 40, welcomeStatusSnapshot{}, "", true) + if !strings.Contains(out, " ____ _____ ____ ___ ____ _____ ____ ") { t.Fatalf("graphical mascot mode must retain the text wordmark fallback, got:\n%s", out) } } diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md index aa24a9210..d0272abd0 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -1,6 +1,6 @@ # Benchmarks -Published, reproducible CPU benchmarks for rho. These are TUI-independent +Published, reproducible CPU benchmarks for decoder. 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 7b54fc6f2..0fe5482c0 100644 --- a/docs/COMPETITIVE.md +++ b/docs/COMPETITIVE.md @@ -1,7 +1,7 @@ -# rho vs Top 20 OSS — Competitive Analysis +# decoder vs Top 20 OSS — Competitive Analysis Status: Implemented (branch `feat/competitive-analysis-top20`, 2026-09-09) -Scope: rho (this repo) vs 10 AI coding CLIs + 6 dev CLIs + 4 terminals (incl. `herdr` multiplexer) +Scope: decoder (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` (permission 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. - Execution: direct host execution behind the permission engine and path guard; no container backend. - 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 `rho://share/`, no hosted URL. +- Share: local deeplink only — `internal/session/export.go:801-819` returns `decoder://share/`, no hosted URL. - Custom providers: supported — `internal/config/settings.go:50` (`custom_providers`), `internal/config/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/features/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. rho 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. decoder 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 rho | +| # | Repo | Stars~ | Lang / Lic | Lesson for decoder | |---|---|---|---|---| | 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.** 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 `flux/engine` facade (`docs/SECURITY-DEVELOPER.md:51-56`; facade declared in `GrayCodeAI/graycode-eco/ecosystem.yaml`); 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. +1. **Traction.** decoder has no star-moat (pre-release). Leaders won via day-1 provider-agnostic + one-liner install + Web/Desktop alongside TUI. decoder 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 — decoder has `make bench` but publishes none (Gap-04). +3. **Providers.** decoder routes only via `mux/engine` facade (`docs/SECURITY-DEVELOPER.md:51-56`; facade declared in `GrayCodeAI/graycode-eco/ecosystem.yaml`); 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 `decoder://` deeplink), multi-session grid (we have `mission` worktrees + daemon — unsurfaced like herdr/cmux). Gap-02. 5. **Execution safety.** Host execution keeps onboarding simple, but requires strong permission, path, approval, and audit controls. Keep those controls explicit and testable; do not add a hidden execution backend. 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 3c0d74e17..2253d92ac 100644 --- a/docs/DAEMON-PORT-THREAT-MODEL.md +++ b/docs/DAEMON-PORT-THREAT-MODEL.md @@ -1,6 +1,6 @@ -# Rho Daemon — Port 4590 Threat Model +# Decoder Daemon — Port 4590 Threat Model -The Rho daemon (`rho daemon`) binds an HTTP server on port **4590** (default) +The Decoder daemon (`decoder 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 `RHO_DAEMON_API_KEY` and configure the daemon with `--api-key` to +- Set `DECODER_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 rho. +Any local process can detect that port 4590 is open and identify decoder. **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 -`~/.rho/sessions/rho.db`. A local attacker with filesystem read access can +`~/.decoder/sessions/decoder.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 `RHO_DAEMON_MAX_CONCURRENT`). When the cap is hit, new `/v1/chat` + via `DECODER_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**: rho does not run as root and does not use +- **Process privilege escalation**: decoder 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 RHO_DAEMON_API_KEY="your-random-secret-here" -rho daemon +export DECODER_DAEMON_API_KEY="your-random-secret-here" +decoder daemon # Option 2: CLI flag -rho daemon --api-key "your-random-secret-here" +decoder 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 `RHO_DAEMON_API_KEY` automatically if the env var is +The SDK reads this from `DECODER_DAEMON_API_KEY` automatically if the env var is set. --- @@ -104,10 +104,10 @@ set. ## Changing the port ```bash -rho daemon --port 9000 +decoder daemon --port 9000 ``` -Or in `~/.rho/settings.json`: +Or in `~/.decoder/settings.json`: ```json { "daemon": { @@ -120,13 +120,13 @@ Or in `~/.rho/settings.json`: ## Shared machine considerations -If rho is running on a machine shared by multiple OS users (e.g., a dev +If decoder 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 rho-owner UID +# Linux — iptables: allow only the decoder-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 4766fb0ab..8825b96be 100644 --- a/docs/DEVELOPER-PATH.md +++ b/docs/DEVELOPER-PATH.md @@ -1,120 +1,120 @@ -# Rho Developer Path +# Decoder Developer Path -This guide explains what `rho path` checks and how to get a fresh developer machine ready to use Rho safely. +This guide explains what `decoder path` checks and how to get a fresh developer machine ready to use Decoder safely. ## What "developer path" means -For Rho, the developer path is the minimum local setup required to chat, edit code, and keep credentials off disk: +For Decoder, 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 Rho settings -- A local model catalog available through flux -- No plaintext API keys left in Flux's configured `provider.json` or legacy env files +- A model selected in Decoder settings +- A local model catalog available through mux +- No plaintext API keys left in Mux's configured `provider.json` or legacy env files - Safe defaults for Bash execution and filesystem access - Optional but healthy local memory and token-pipeline state Run the report at any time: ```bash -rho path -rho path --strict -rho doctor -rho preflight +decoder path +decoder path --strict +decoder doctor +decoder preflight ``` ## Setup checklist ### 1. Build and workspace setup -If you are contributing from source, clone Rho as the main CLI. The support +If you are contributing from source, clone Decoder as the main CLI. The support repositories are independent sibling checkouts when you need the full local -workspace; they are not nested under Rho: +workspace; they are not nested under Decoder: ```bash mkdir graycode-eco && cd graycode-eco -git clone https://github.com/GrayCodeAI/rho -git clone https://github.com/GrayCodeAI/flux -cd rho +git clone https://github.com/GrayCodeAI/decoder +git clone https://github.com/GrayCodeAI/mux +cd decoder make setup -go build -o rho ./cmd/rho +go build -o decoder ./cmd/decoder ``` `make setup` validates the canonical repository manifest and regenerates the parent `../go.work` from the local Go repositories marked `workspace: true`. -Rho can also be built as a standalone checkout with `GOWORK=off go build ./cmd/rho`; +Decoder can also be built as a standalone checkout with `GOWORK=off go build ./cmd/decoder`; the sibling workspace is only required for cross-repository development and boundary checks. ### 2. Configure credentials -Start Rho and use `/config` to paste an API key or configure a local provider like Ollama. +Start Decoder and use `/config` to paste an API key or configure a local provider like Ollama. -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. +Decoder 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 -rho credentials status -rho preflight +decoder credentials status +decoder preflight ``` -`rho preflight` is a local-ready check; it does not contact a provider. To +`decoder 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 `rho models list --live`. +validation or `decoder models list --live`. ### 3. Select a model -Pick a model in `/config`. Rho stores the selected model in settings and uses flux for provider routing and catalog resolution. +Pick a model in `/config`. Decoder stores the selected model in settings and uses mux for provider routing and catalog resolution. If the catalog is missing or empty: ```bash -rho models refresh +decoder models refresh ``` ## Security checks -`rho path` treats these as important security conditions: +`decoder path` treats these as important security conditions: -- Flux's resolved `provider.json` must not contain secret fields +- Mux's resolved `provider.json` must not contain secret fields - sensitive files like provider config and SSH paths should be blocked from agent reads -Flux resolves provider state from `FLUX_CONFIG_DIR` first, then the +Mux resolves provider state from `MUX_CONFIG_DIR` first, then the platform user-config directory. -Rho protects that resolved path even when it is customized or symlinked. +Decoder protects that resolved path even when it is customized or symlinked. -If Rho detects secret fields in `provider.json`, back up the file, remove those fields manually, and save your keys again through `/config`. +If Decoder 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 -Rho executes agent commands directly on the host. No Docker daemon or +Decoder 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 -`rho path` also verifies the provider layer behind Rho: +`decoder path` also verifies the provider layer behind Decoder: -- `flux` for provider routing and local preflight readiness +- `mux` for provider routing and local preflight readiness - the embedded token pipeline for estimation and compression If you want the broader status summary: ```bash -rho ecosystem -rho doctor +decoder ecosystem +decoder doctor ``` ## Typical recovery path -If `rho path` says you are not ready, this is the intended order: +If `decoder path` says you are not ready, this is the intended order: -1. Run `rho` +1. Run `decoder` 2. Open `/config` 3. Paste an API key or configure Ollama 4. Pick a model -5. Re-run `rho preflight` -6. Re-run `rho path` +5. Re-run `decoder preflight` +6. Re-run `decoder 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 214728d9f..25a3066d3 100644 --- a/docs/DYNAMIC-MODELS.md +++ b/docs/DYNAMIC-MODELS.md @@ -1,10 +1,10 @@ -# Dynamic Model Discovery — Rho Developer Guide +# Dynamic Model Discovery — Decoder Developer Guide ## Ownership and flow -Rho is the product face; Flux is the provider engine. Rho owns the CLI/TUI, +Decoder is the product face; Mux is the provider engine. Decoder owns the CLI/TUI, model-picker presentation, user intent, and output compatibility. The -`flux/engine` facade alone owns provider registry details, credentials, +`mux/engine` facade alone owns provider registry details, credentials, discovery, catalog/cache policy, model aliases, deployment routing, and chat transport. @@ -12,23 +12,23 @@ transport. provider APIs / remote catalog / local cache | v - flux/engine.Engine + mux/engine.Engine catalog + credentials + routing | stable host DTOs and methods v - Rho composition and presentation + Decoder composition and presentation /config | models | conversation ``` -Production Rho packages must not import Flux packages below -`github.com/GrayCodeAI/flux/engine`. Shell and AST guards enforce a +Production Decoder packages must not import Mux packages below +`github.com/GrayCodeAI/mux/engine`. Shell and AST guards enforce a zero-exception boundary. -## Rho composition boundary +## Decoder composition boundary -`internal/config` is Rho's control-plane composition root. It creates an -Flux engine and projects engine models into Rho UI and command contracts: +`internal/config` is Decoder's control-plane composition root. It creates an +Mux engine and projects engine models into Decoder UI and command contracts: ```go models, err := config.ListEngineModels(ctx, "anthropic", false) @@ -36,36 +36,36 @@ live, err := config.ListEngineModels(ctx, "anthropic", true) public, err := config.ListPublicEngineModels(ctx, "xiaomi_mimo_payg") ``` -Conversation construction goes through Rho's `internal/engine` adapter. The -adapter translates Rho-owned message, tool, usage, and stream DTOs to the -Flux engine facade; conversation history, WAL, resume, approvals, and tool -execution remain Rho-owned. +Conversation construction goes through Decoder's `internal/engine` adapter. The +adapter translates Decoder-owned message, tool, usage, and stream DTOs to the +Mux engine facade; conversation history, WAL, resume, approvals, and tool +execution remain Decoder-owned. ## Catalog and live discovery -The Flux engine combines its provider registry, provider-scoped live -discovery, and the cache at `~/.flux/model_catalog.json` by default. Override -the cache with `FLUX_MODEL_CATALOG_PATH`. Credential and state dependencies +The Mux engine combines its provider registry, provider-scoped live +discovery, and the cache at `~/.mux/model_catalog.json` by default. Override +the cache with `MUX_MODEL_CATALOG_PATH`. Credential and state dependencies are injected into each Engine instance, so discovery does not need hidden process-global credentials. Use the normal cache-backed path for repeatable UI and automation: ```bash -rho models list anthropic -rho models list anthropic --json +decoder models list anthropic +decoder models list anthropic --json ``` Use a provider-scoped live request when current connectivity and credentials must be checked: ```bash -rho models list anthropic --live -rho models list anthropic --live --json -rho models list anthropic --live --raw +decoder models list anthropic --live +decoder models list anthropic --live --json +decoder models list anthropic --live --raw ``` -`rho preflight` reports **local readiness**: usable local state, a selected +`decoder 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,8 +73,8 @@ validation) as **live verified**. ## Stable command output -`rho models list --json` is a Rho-owned compatibility contract, not a direct -serialization of Flux's evolving `engine.Model` DTO. Its stable fields are: +`decoder models list --json` is a Decoder-owned compatibility contract, not a direct +serialization of Mux's evolving `engine.Model` DTO. Its stable fields are: ```text id, input_price_per_1m, output_price_per_1m, context_window, max_output, @@ -83,37 +83,37 @@ 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 Rho compatibility row instead of `null`. +metadata, it returns the stable Decoder compatibility row instead of `null`. ## Custom gateways -Rho converts effective `custom_providers` settings into +Decoder 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 Flux process-global state: tests, parallel sessions, and future multi-tenant +in Mux process-global state: tests, parallel sessions, and future multi-tenant hosts must be isolated from one another. ## Adding or changing a provider 1. Implement registry, discovery, credentials, aliases, and transport behavior - behind Flux's engine facade. -2. Add Flux tests for cache and live discovery, credential status, selection, + behind Mux's engine facade. +2. Add Mux tests for cache and live discovery, credential status, selection, and generation/streaming. -3. Commit and verify standalone Flux. -4. Advance Rho's `../flux` sibling checkout to that exact commit, then update - Rho's module version when the Flux revision is published. +3. Commit and verify standalone Mux. +4. Advance Decoder's `../mux` sibling checkout to that exact commit, then update + Decoder's module version when the Mux revision is published. 5. Verify both the workspace (`go.work`) and published-module (`GOWORK=off`) build modes. -Rho changes are needed only for a new product behavior or an additive -Rho-owned presentation field—not for provider-specific mechanics. +Decoder changes are needed only for a new product behavior or an additive +Decoder-owned presentation field—not for provider-specific mechanics. ## Checks ```bash -rho models refresh -rho models status -rho preflight -make flux-engine-guard +decoder models refresh +decoder models status +decoder preflight +make mux-engine-guard go test ./cmd ./internal/config ./internal/engine -count=1 ``` diff --git a/docs/ECOSYSTEM-CONFIG.md b/docs/ECOSYSTEM-CONFIG.md index 72e33b32b..5020cfa23 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: Rho now depends only on flux and embeds its own token engine. +> Note: Decoder now depends only on mux and embeds its own token engine. > References below to harrier, shrike, and swift describe a proposed -> ecosystem-wide schema, not Rho's current runtime. +> ecosystem-wide schema, not Decoder'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 (rho: `config.json` + `RHO_*`/`RHO_*` env; flux: +flags, and config files (decoder: `config.json` + `DECODER_*`/`DECODER_*` env; mux: 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 rho + flux). A repo only reads its sections. + `model`/`providers` shared by decoder + mux). 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. `$RHO_ECO_CONFIG` +2. `$DECODER_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 (rho + flux) ─────────────────────────────── +# ─── Shared: model + providers (decoder + mux) ─────────────────────────────── model: default: anthropic/claude-sonnet-4-5 # provider/model the agent uses small_fast: anthropic/claude-haiku # cheap model for trivial steps @@ -70,16 +70,16 @@ providers: api_key_env: GEMINI_API_KEY model: gemini-2.0-flash -# ─── flux: gateway / runtime ─────────────────────────────────────────────── +# ─── mux: gateway / runtime ─────────────────────────────────────────────── gateway: - base_url: http://localhost:8080 # flux endpoint rho talks to - api_key_env: FLUX_API_KEY + base_url: http://localhost:8080 # mux endpoint decoder talks to + api_key_env: MUX_API_KEY allow_insecure_public_api: false - deployment_routing: "" # FLUX_DEPLOYMENT_ROUTING + deployment_routing: "" # MUX_DEPLOYMENT_ROUTING model_catalog: - path_env: FLUX_MODEL_CATALOG_PATH - url_env: FLUX_MODEL_CATALOG_URL - refresh: FLUX_MODEL_CATALOG_REFRESH + path_env: MUX_MODEL_CATALOG_PATH + url_env: MUX_MODEL_CATALOG_URL + refresh: MUX_MODEL_CATALOG_REFRESH # ─── harrier: memory ─────────────────────────────────────────────────────────── memory: @@ -123,9 +123,9 @@ swift: telemetry: # OTel exporter settings shared by all repos. Span attribute keys follow # docs/OTEL-CONVENTIONS.md. - enabled: false # rho: RHO_ENABLE_TELEMETRY + enabled: false # decoder: DECODER_ENABLE_TELEMETRY otlp_endpoint: "" # OTEL_EXPORTER_OTLP_ENDPOINT - shutdown_timeout_ms: 0 # RHO_OTEL_SHUTDOWN_TIMEOUT_MS + shutdown_timeout_ms: 0 # DECODER_OTEL_SHUTDOWN_TIMEOUT_MS ``` ## Setting → repo → existing mechanism @@ -133,30 +133,30 @@ telemetry: The authoritative mapping. "Mechanism today" is what already implements the setting; the unified key is rendered down to it. -### Shared: model / providers (rho + flux) +### Shared: model / providers (decoder + mux) | Unified key | Repo | Mechanism today | |---------------------------------|-------------|--------------------------------------------------| -| `model.default` | rho | `RHO_MODEL` env / `config.json` | -| `model.small_fast` | rho | `RHO_SMALL_FAST_MODEL` env | -| `providers[].api_key_env` | flux/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` | flux | `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL` / `OPENAI_API_BASE`, `OLLAMA_BASE_URL`, `FIREWORKS_BASE_URL` | -| `providers[].model` (openai) | flux | `OPENAI_MODEL` env | -| `providers[].model` (gemini) | flux | `GEMINI_MODEL` env | -| `providers[].model` (anthropic) | flux | `ANTHROPIC_MODEL` env | +| `model.default` | decoder | `DECODER_MODEL` env / `config.json` | +| `model.small_fast` | decoder | `DECODER_SMALL_FAST_MODEL` env | +| `providers[].api_key_env` | mux/decoder | `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` | mux | `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL` / `OPENAI_API_BASE`, `OLLAMA_BASE_URL`, `FIREWORKS_BASE_URL` | +| `providers[].model` (openai) | mux | `OPENAI_MODEL` env | +| `providers[].model` (gemini) | mux | `GEMINI_MODEL` env | +| `providers[].model` (anthropic) | mux | `ANTHROPIC_MODEL` env | -### flux: gateway / runtime +### mux: gateway / runtime -| Unified key | Mechanism today (flux) | +| Unified key | Mechanism today (mux) | |--------------------------------------|--------------------------------------| -| `gateway.base_url` | `FLUX_BASE_URL` (rho→flux link) | -| `gateway.api_key_env` | `FLUX_API_KEY` | -| `gateway.allow_insecure_public_api` | `FLUX_ALLOW_INSECURE_PUBLIC_API` | -| `gateway.deployment_routing` | `FLUX_DEPLOYMENT_ROUTING` (also `RHO_DEPLOYMENT_ROUTING`) | -| `gateway.model_catalog.path_env` | `FLUX_MODEL_CATALOG_PATH` | -| `gateway.model_catalog.url_env` | `FLUX_MODEL_CATALOG_URL` | -| `gateway.model_catalog.refresh` | `FLUX_MODEL_CATALOG_REFRESH` / `RHO_AUTO_REFRESH_CATALOG` / `RHO_CATALOG_REFRESH_ALWAYS` | -| `gateway` config dir | `FLUX_CONFIG_DIR` (default `/flux`) | +| `gateway.base_url` | `MUX_BASE_URL` (decoder→mux link) | +| `gateway.api_key_env` | `MUX_API_KEY` | +| `gateway.allow_insecure_public_api` | `MUX_ALLOW_INSECURE_PUBLIC_API` | +| `gateway.deployment_routing` | `MUX_DEPLOYMENT_ROUTING` (also `DECODER_DEPLOYMENT_ROUTING`) | +| `gateway.model_catalog.path_env` | `MUX_MODEL_CATALOG_PATH` | +| `gateway.model_catalog.url_env` | `MUX_MODEL_CATALOG_URL` | +| `gateway.model_catalog.refresh` | `MUX_MODEL_CATALOG_REFRESH` / `DECODER_AUTO_REFRESH_CATALOG` / `DECODER_CATALOG_REFRESH_ALWAYS` | +| `gateway` config dir | `MUX_CONFIG_DIR` (default `/mux`) | ### harrier: memory @@ -197,22 +197,22 @@ few env vars. ### swift + telemetry -| Unified key | Mechanism today (swift / rho) | +| Unified key | Mechanism today (swift / decoder) | |-----------------------------------|-------------------------------------------------| | `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` | `RHO_ENABLE_TELEMETRY` env (rho) | -| `telemetry.shutdown_timeout_ms` | `RHO_OTEL_SHUTDOWN_TIMEOUT_MS` env (rho) | +| `telemetry.enabled` | `DECODER_ENABLE_TELEMETRY` env (decoder) | +| `telemetry.shutdown_timeout_ms` | `DECODER_OTEL_SHUTDOWN_TIMEOUT_MS` env (decoder) | | `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** (rho, flux, shrike, swift): export the mapped env var for +- **env-based repos** (decoder, mux, 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 @@ -234,7 +234,7 @@ The schema is encoding-agnostic. The YAML above is identical in structure to: "providers": [ { "name": "anthropic", "api_key_env": "ANTHROPIC_API_KEY" } ], - "gateway": { "base_url": "http://localhost:8080", "api_key_env": "FLUX_API_KEY" }, + "gateway": { "base_url": "http://localhost:8080", "api_key_env": "MUX_API_KEY" }, "memory": { "addr": "127.0.0.1:3456", "data_dir": "~/.harrier" }, "compression": { "enabled": true, "db_path": "~/.shrike/usage.db" }, "swift": { "telemetry_optout": false }, @@ -246,5 +246,5 @@ The schema is encoding-agnostic. The YAML above is identical in structure to: The `telemetry` and `swift` sections only configure *transport/opt-out*. The *attribute vocabulary* emitted on spans is defined separately in -[`OTEL-CONVENTIONS.md`](./OTEL-CONVENTIONS.md), with flux's +[`OTEL-CONVENTIONS.md`](./OTEL-CONVENTIONS.md), with mux's `internal/observability` package as the reference implementation. diff --git a/docs/ECOSYSTEM-ROADMAP.md b/docs/ECOSYSTEM-ROADMAP.md index b299cc794..24c4cd317 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 — `rho`, `flux`, +**Scope:** the 4 in-workspace repos — `decoder`, `mux`, `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` @@ -30,7 +30,7 @@ load-bearing bets: 3. **Portable execution graph** — every agent run is exported as a verifiable, hash-addressed graph (provenance for replay/audit). 4. **Router-facade-only provider access** — the CLI never talks to an LLM API - directly; `flux/engine` is the sole boundary. + directly; `mux/engine` is the sole boundary. The roadmap is organized around making those bets *true and honest* end-to-end, then extending the surface where the field (Codex, OpenCode, Gemini CLI, Goose, @@ -38,7 +38,7 @@ Claude Code) has proven demand. ### The single most important fact (historical) -`rho` imports **5 engine modules** — `harrier` (memory graph), +`decoder` 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 @@ -56,12 +56,12 @@ dropped. | Repo | Role | Build | Tests | Health | |---|---|---|---|---| -| `rho` | Product face (Go, Bubble Tea v2) | ✅ `go build ./...` | ✅ 179 pkgs green | Engines restored; memory/token/review/audit/correlation live | -| `flux` | Provider runtime (Go) | ✅ | ✅ 37 pkgs green | Healthy; 28 providers; gRPC ChatService wired | +| `decoder` | Product face (Go, Bubble Tea v2) | ✅ `go build ./...` | ✅ 179 pkgs green | Engines restored; memory/token/review/audit/correlation live | +| `mux` | 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 rho +### 2.1 decoder - Builds and vets clean **only because** the 5 engines are stubbed. - **5 failing tests:** `internal/token/shrike_test.go` asserts real token counts @@ -70,10 +70,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` — `rho review run|analyze` always + - `internal/bridge/kestrel/bridge.go` — `decoder 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` — `rho swift` has no + - `cmd/swift.go` / `cmd/swift_correlation.go` — `decoder 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 @@ -84,7 +84,7 @@ dropped. `shrike: embedded · token/compress pipeline OK (sample=0 tokens)` — sample 0 should be a red flag, not OK. -### 2.2 flux +### 2.2 mux - Healthy and self-contained. 28 `ProviderSpec`s, ~25 adapters, weighted/strategy LB, retries, semantic cache, circuit breakers, deployment router, OpenAI-compat @@ -188,7 +188,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)") | `rho 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)") | `decoder 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 ✅ | @@ -210,14 +210,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; `rho path` shows all engines genuinely ready; `go test ./...` +passes; `decoder 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 `rho audit`/`merlin` command (orphaned code) | command runs the pipeline end-to-end with a real merlin | +| 2.1 | cli | Wire `RunMerlinPipeline` into a `decoder 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 | @@ -242,7 +242,7 @@ green with real engines. **Effort:** 4-8 weeks total across the 5 repos. - IDE integration (VS Code extension) — only after engines restored. - Hosted share links / multi-session grid (herdr-style) — Gap-02 follow-through. -- Provider-count messaging: expose `flux` catalog count dynamically. +- Provider-count messaging: expose `mux` catalog count dynamically. - OpenTelemetry/metrics consolidation across engines (per `OTEL-CONVENTIONS.md`). --- @@ -271,7 +271,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. -- `rho path` / `rho ecosystem` report engine availability honestly. +- `decoder path` / `decoder 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 50c5d3027..022ece72d 100644 --- a/docs/ECOSYSTEM-WIRING.md +++ b/docs/ECOSYSTEM-WIRING.md @@ -1,7 +1,7 @@ # GrayCode ecosystem wiring > **STALE (2026-09-18):** Diagrams below predate `eagle` removal (2026-09-04) and list 14 repos / 6 engines. -> NOTE (2026-09-04): `eagle` contracts vendored into `rho/internal/contracts`. +> NOTE (2026-09-04): `eagle` contracts vendored into `decoder/internal/contracts`. > **INVENTORY MOVED (2026-10-02):** the canonical machine-readable inventory is > [`GrayCodeAI/graycode-eco/ecosystem.yaml`](https://github.com/GrayCodeAI/graycode-eco/blob/main/ecosystem.yaml) > (10 products, including `beam`). The `ecosystem.yaml` in this repo is @@ -18,23 +18,23 @@ flowchart LR Lists --> Scripts[Workspace and release scripts] Lists --> Owl[Owl catalog] - Rho[Rho product] --> Engines[Six engine repositories] + Decoder[Decoder product] --> Engines[Six engine repositories] Engines --> Eagle[Eagle contracts] - Rho --> Eagle + Decoder --> Eagle - RhoSpec[Rho OpenAPI] -. manual snapshots .-> Sparrow - RhoSpec -. manual snapshots .-> Robin - RhoSpec -. no snapshot .-> Wren + DecoderSpec[Decoder OpenAPI] -. manual snapshots .-> Sparrow + DecoderSpec -. manual snapshots .-> Robin + DecoderSpec -. no snapshot .-> Wren Browser[GrayCode browser] --> Mixed[One mixed Worker] Mixed --> Identity[(Identity tables)] - Mixed --> Cloud[(Rho Cloud tables)] + Mixed --> Cloud[(Decoder 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 Rho control plane +SDK contracts were inconsistent, browser identity and the Decoder control plane shared a Worker and D1 binding, and a successful usage write did not guarantee a durable Queue-delivery intent. @@ -48,15 +48,15 @@ flowchart TB Manifest --> OwlSnapshot[owl/ecosystem.json] subgraph Local[Local-first runtime] - User[CLI / daemon user] --> Rho[Rho composition root] - Rho --> Flux[Flux 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] - Flux --> Eagle + User[CLI / daemon user] --> Decoder[Decoder composition root] + Decoder --> Mux[Mux facade] + Decoder --> Harrier[Harrier / Harrier facade] + Decoder --> Shrike[Shrike / Shrike facade] + Decoder --> Swift[Swift / Swift facade] + Decoder --> Kestrel[Kestrel / Kestrel facade] + Decoder --> Merlin[Merlin / Merlin facade] + Decoder --> Eagle[Eagle neutral contracts] + Mux --> Eagle Harrier --> Eagle Shrike --> Eagle Swift --> Eagle @@ -64,7 +64,7 @@ flowchart TB Merlin --> Eagle end - Rho --> Daemon[Rho daemon API] + Decoder --> Daemon[Decoder daemon API] Daemon --> OpenAPI[api/openapi.yaml] OpenAPI --> Sparrow[Sparrow Go SDK snapshot] OpenAPI --> Robin[Robin Python SDK snapshot] @@ -73,7 +73,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[Rho Cloud Worker] + BFF -->|private typed Service Binding| Cloud[Decoder Cloud Worker] Cloud --> Control[(Control-plane D1)] Cloud -->|same D1 transaction| Outbox[(Usage outbox)] Outbox -->|immediate + scheduled retry| Queue[Cloudflare Queue] @@ -81,28 +81,28 @@ flowchart TB Queue --> Archive[(R2 archive)] end - Rho -. explicit opt-in; fail-open .-> Cloud + Decoder -. explicit opt-in; fail-open .-> Cloud ``` ## Ownership rules | Boundary | Owner | Rule | |---|---|---| -| Product orchestration | Rho | Engines never orchestrate Rho or one another. | -| Provider runtime | Flux | Rho imports its supported `engine` facade. | +| Product orchestration | Decoder | Engines never orchestrate Decoder or one another. | +| Provider runtime | Mux | Decoder imports its supported `engine` facade. | | Shared data contracts | Eagle | Neutral types only; no product behavior. | -| 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. | +| Portable local graph | Source engine/Decoder | Emit bounded `decoder.graph/v1` facts without raw secrets or prompts. | +| Daemon HTTP API | Decoder | `decoder/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 | Rho Cloud | Organizations, projects, devices, usage, billing, graph ledger, and audit. | +| Hosted control plane | Decoder 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. | ## Repository identity Repository directories are repository and Go module identities. Product names -remain the user-facing labels. In particular, `flux` is Flux. Scripts and CI +remain the user-facing labels. In particular, `mux` is Mux. Scripts and CI must use `directory`/`github_repo`; UI copy may use `product_name`. ## Contract and event flow @@ -113,7 +113,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 `rho.graph/v1`. Rho Cloud independently +3. Local graph projections use `decoder.graph/v1`. Decoder 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` @@ -124,12 +124,12 @@ must use `directory`/`github_repo`; UI copy may use `product_name`. ## Release and development workflow -- Run `make workspace` in Rho to regenerate the parent `go.work`. +- Run `make workspace` in Decoder 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 Rho and separately checks +- SDK CI checks its snapshot byte-for-byte against Decoder 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. @@ -138,33 +138,33 @@ must use `directory`/`github_repo`; UI copy may use `product_name`. ### Coordinated Go publication gate -The compatible Eagle-migrated Flux source is currently ahead of Flux's -published `origin/main`. Do not merge or release Rho against the older Flux -pseudo-version: it still exposes the retired `rho-core-contracts` types and is -not type-compatible with Rho's Eagle boundary. +The compatible Eagle-migrated Mux source is currently ahead of Mux's +published `origin/main`. Do not merge or release Decoder against the older Mux +pseudo-version: it still exposes the retired `decoder-core-contracts` types and is +not type-compatible with Decoder's Eagle boundary. -1. Merge and publish the Flux ecosystem-wiring branch first. +1. Merge and publish the Mux ecosystem-wiring branch first. 2. Resolve that final remote commit to its canonical Go pseudo-version with - `go list -m github.com/GrayCodeAI/flux@`. -3. Update Rho's Flux requirement, run `GOWORK=off go mod tidy`, and remove any + `go list -m github.com/GrayCodeAI/mux@`. +3. Update Decoder's Mux requirement, run `GOWORK=off go mod tidy`, and remove any transition excludes no longer required by the published engine graphs. -4. Require Rho's `public-modules` and `release-parity` CI jobs to pass before - merging or tagging Rho. +4. Require Decoder's `public-modules` and `release-parity` CI jobs to pass before + merging or tagging Decoder. 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 `rho-identity` D1 database and +1. Create or automatically provision the `decoder-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 `RhoCloudService` entrypoint and +3. Deploy `graycode-cloud` with the named `DecoderCloudService` entrypoint and apply migration `0023_usage_outbox.sql`. -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. +4. Deploy `decoder-bff` with its D1 binding and private service binding. +5. Point the web frontend API hostname at the BFF. Decoder/CLI device start and + poll traffic continues to target Decoder 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 4ff1688e0..fbcdb9911 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/rho-architecture-baseline.md`. +`docs/architecture/decoder-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 (rho repo) +### 1. Add VS Code Extension Integration (decoder repo) **Effort:** Large (2-3 weeks) **Priority:** HIGH **What:** -- Create `rho-vscode` repo for VS Code extension -- Use Rho SDK for communication +- Create `decoder-vscode` repo for VS Code extension +- Use Decoder 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 rho-vscode -cd rho-vscode +mkdir decoder-vscode +cd decoder-vscode # Initialize git init -go mod init github.com/GrayCodeAI/rho-vscode +go mod init github.com/GrayCodeAI/decoder-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": "rho", - "displayName": "Rho", + "name": "decoder", + "displayName": "Decoder", "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": "rho.activate", - "title": "Activate Rho" + "command": "decoder.activate", + "title": "Activate Decoder" } ], "menus": { "commandPalette": [ { - "command": "rho.activate", + "command": "decoder.activate", "when": "editorLangId" } ] @@ -94,22 +94,22 @@ EOF # Add extension scaffold cat > extension/src/extension.ts << 'EOF' import * as vscode from 'vscode'; -import * as rhosdk from '@graycodeai/rhosdk'; +import * as decodersdk from '@graycodeai/decodersdk'; export function activate(context: vscode.ExtensionContext) { - const client = new rhosdk.Client(); + const client = new decodersdk.Client(); // Register completion provider vscode.languages.registerCompletionItemProvider( 'go', - new RhoCompletionProvider(client), + new DecoderCompletionProvider(client), 'g', 'h' ); // Register hover provider vscode.languages.registerHoverProvider( 'go', - new RhoHoverProvider(client) + new DecoderHoverProvider(client) ); } EOF @@ -123,7 +123,7 @@ vsce publish **Repository Structure:** ``` -rho-vscode/ +decoder-vscode/ ├── extension/ # VS Code extension code │ ├── src/ │ │ ├── extension.ts @@ -140,25 +140,25 @@ rho-vscode/ --- -### 2. Add Extension Marketplace (rho repo) +### 2. Add Extension Marketplace (decoder repo) **Effort:** Medium (1-2 weeks) **Priority:** HIGH **What:** - Create marketplace for community extensions -- Add extension discovery endpoint to rho +- Add extension discovery endpoint to decoder - Create `starling` integration - Add version compatibility checking **Why:** - Top agents have 100+ extensions -- Build ecosystem around Rho +- Build ecosystem around Decoder - Increase adoption **Implementation Plan:** ```go -// Add to rho +// Add to decoder package marketplace // Extension represents a community extension @@ -305,12 +305,12 @@ cat >> SKILL.md << 'EOF' To discover available extensions, use: ``` -rho extensions list +decoder extensions list ``` To install an extension: ``` -rho extensions install +decoder extensions install ``` EOF ``` @@ -431,7 +431,7 @@ go build forum.go --- -### 5. Add More Extension Points (rho) +### 5. Add More Extension Points (decoder) **Effort:** Small (3-5 days) **Priority:** MEDIUM @@ -499,7 +499,7 @@ func main() { ## Phase 3: Low Priority (Backlog - Score Impact: +0.2) -### 6. Add AI Code Completion (flux) +### 6. Add AI Code Completion (mux) **Effort:** Medium (1-2 weeks) **Priority:** LOW @@ -514,7 +514,7 @@ func main() { --- -### 7. Add Debugging Support (rho) +### 7. Add Debugging Support (decoder) **Effort:** Large (2-3 weeks) **Priority:** LOW @@ -530,7 +530,7 @@ func main() { --- -### 8. Add Web UI for Monitoring (rho) +### 8. Add Web UI for Monitoring (decoder) **Effort:** Small (3-5 days) **Priority:** LOW @@ -562,7 +562,7 @@ func main() { --- -### 10. Add Completion Endpoint to flux +### 10. Add Completion Endpoint to mux **Effort:** Medium (1 week) **Priority:** LOW @@ -579,7 +579,7 @@ func main() { ## Detailed Repo-Specific Roadmap -### **rho** (Main Repo) - Primary Product +### **decoder** (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/rho-architecture-baseline.md`. +Current architecture status: see `docs/architecture/decoder-architecture-baseline.md`. --- @@ -628,7 +628,7 @@ Current architecture status: see `docs/architecture/rho-architecture-baseline.md --- -### **flux** (LLM Runtime) +### **mux** (LLM Runtime) | Phase | Improvement | Effort | Impact | Status | |-------|--------------|--------|--------|--------| @@ -657,7 +657,7 @@ Current architecture status: see `docs/architecture/rho-architecture-baseline.md 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/rho-architecture-baseline.md`. +in `docs/architecture/decoder-architecture-baseline.md`. --- @@ -667,7 +667,7 @@ in `docs/architecture/rho-architecture-baseline.md`. |------|--------|------------| | Marketplace competition | High | Focus on quality extensions | | Community building | Medium | Active engagement, good docs | -| Development velocity | Medium | Prioritize rho SDK first | +| Development velocity | Medium | Prioritize decoder SDK first | | Adoption curve | Medium | VS Code extension is key | --- diff --git a/docs/OTEL-CONVENTIONS.md b/docs/OTEL-CONVENTIONS.md index 196b37d10..a64defec5 100644 --- a/docs/OTEL-CONVENTIONS.md +++ b/docs/OTEL-CONVENTIONS.md @@ -1,10 +1,10 @@ # graycode-eco OpenTelemetry Semantic Conventions for AI Agent Spans Status: Draft / shared spec -Applies to: rho, flux +Applies to: decoder, mux -This document defines the OpenTelemetry (OTel) semantic conventions Rho and -Flux should follow when emitting spans for AI agent and LLM operations. The +This document defines the OpenTelemetry (OTel) semantic conventions Decoder and +Mux 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 cost across both repos using one common attribute vocabulary. @@ -18,24 +18,24 @@ concerns that the GenAI spec does not yet standardize. ## Reference ownership -Flux owns provider-call instrumentation behind its `flux/engine` facade. +Mux owns provider-call instrumentation behind its `mux/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 Flux implementation detail; Rho must not import or compose it +decorator is an Mux implementation detail; Decoder must not import or compose it directly. -- `flux/internal/observability/observability.go` provides a stdlib-only, +- `mux/internal/observability/observability.go` provides a stdlib-only, zero-dependency telemetry/metrics layer (spans, latency histograms, Prometheus + JSON export) for environments that cannot pull in the OTel SDK. -- `flux/internal/observability/genai_semconv.go` exports the canonical +- `mux/internal/observability/genai_semconv.go` exports the canonical attribute-key constants defined below, so Go code can reference them instead of hard-coding strings. A pinning test (`genai_semconv_test.go`) guards the exact key values. -When adding tracing to Rho, propagate trace context through the Engine call -and use the attribute keys in this document. Flux wraps provider operations; -Rho wraps product turns, tools, and the embedded token engine. +When adding tracing to Decoder, propagate trace context through the Engine call +and use the attribute keys in this document. Mux wraps provider operations; +Decoder wraps product turns, tools, and the embedded token engine. ## Span kinds and names @@ -50,7 +50,7 @@ Rho wraps product turns, tools, and the embedded token engine. LLM/provider request spans SHOULD use span kind `CLIENT`. Tool invocations and agent steps that represent internal work SHOULD use span kind `INTERNAL`. -flux's existing stdlib layer also defines short span names +mux's existing stdlib layer also defines short span names (`llm.chat`, `llm.stream`, `llm.retry`, `llm.cache_hit`) — these remain valid for the internal metrics collector; the names above are the cross-repo convention for OTLP-exported spans. @@ -58,10 +58,10 @@ convention for OTLP-exported spans. ## Required / recommended attributes The required attribute set for any AI agent span. Keys are exported as Go -constants in `flux/internal/observability/genai_semconv.go` (constant name in +constants in `mux/internal/observability/genai_semconv.go` (constant name in parentheses). -| Attribute key | Go const (`flux` observability) | Type | Required | Meaning | +| Attribute key | Go const (`mux` observability) | Type | Required | Meaning | |---------------------------------|------------------------------------|--------|----------|----------------------------------------------------------------| | `gen_ai.system` | `AttrGenAISystem` | string | yes | Provider/system: `openai`, `anthropic`, `gemini`, etc. | | `gen_ai.request.model` | `AttrGenAIRequestModel` | string | yes | Model requested by the caller, e.g. `gpt-4o`. | @@ -77,13 +77,13 @@ parentheses). ### Notes - **Tokens as integers.** Emit `gen_ai.usage.input_tokens` / - `gen_ai.usage.output_tokens` as OTel integer attributes. flux's stdlib + `gen_ai.usage.output_tokens` as OTel integer attributes. mux's stdlib `Span.Attributes` is `map[string]string`; when bridging that layer to OTLP, convert to integer attributes. - **Cost is a double in USD.** `cost.usd` is the ecosystem standard. - flux's metrics collector stores cost internally in micro-USD for precision + mux's metrics collector stores cost internally in micro-USD for precision (`costMicroUSD`) but exports USD; emit the USD value on spans. -- **No prompt/response bodies by default.** Following flux's audit design +- **No prompt/response bodies by default.** Following mux's audit design (`observability/audit.go`), spans MUST NOT carry raw prompt/response text by default. If content capture is enabled, use the OTel GenAI event/log channel, not span attributes, and gate it behind explicit opt-in. @@ -92,7 +92,7 @@ parentheses). ## Legacy attribute mapping -flux's older stdlib constants (`AttrLLMProvider`, `AttrLLMModel`, +mux's older stdlib constants (`AttrLLMProvider`, `AttrLLMModel`, `AttrLLMInputTokens`, …, keyed `llm.*`) predate this spec and remain for backwards compatibility. New instrumentation should use the `gen_ai.*` keys. Mapping: @@ -109,12 +109,12 @@ Mapping: ## Per-repo guidance -- **flux** — owns provider/model/usage spans behind `flux/engine`; align +- **mux** — owns provider/model/usage spans behind `mux/engine`; align attribute keys to `gen_ai.*` over time. -- **rho** — daemon/orchestrator. Already has OTel hooks - (`RHO_ENABLE_TELEMETRY`, `RHO_OTEL_SHUTDOWN_TIMEOUT_MS`). Emit +- **decoder** — daemon/orchestrator. Already has OTel hooks + (`DECODER_ENABLE_TELEMETRY`, `DECODER_OTEL_SHUTDOWN_TIMEOUT_MS`). Emit `agent.id` and `session.id` on agent-turn spans; propagate them downstream to - flux via context so provider spans inherit the same IDs. + mux via context so provider spans inherit the same IDs. - **internal/token** — embedded token engine. Token accounting is its domain; when it emits spans, populate `gen_ai.usage.input_tokens` / `output_tokens` and `cost.usd` so compression savings are visible in the same trace. @@ -122,5 +122,5 @@ Mapping: ## Versioning This spec tracks OTel GenAI conventions, which are still evolving. Pin changes -to the constants via `genai_semconv_test.go` in flux and update this table in +to the constants via `genai_semconv_test.go` in mux and update this table in the same change. diff --git a/docs/PERMISSION-MODEL-IMPROVEMENTS.md b/docs/PERMISSION-MODEL-IMPROVEMENTS.md index 0aa1f2e58..0e123c1e2 100644 --- a/docs/PERMISSION-MODEL-IMPROVEMENTS.md +++ b/docs/PERMISSION-MODEL-IMPROVEMENTS.md @@ -1,6 +1,6 @@ -# Rho permission model +# Decoder permission model -Rho executes tools directly on the host. There is no Docker-backed execution +Decoder executes tools directly on the host. There is no Docker-backed execution path and no product sandbox runtime. Safety comes from a layered, fail-closed policy pipeline, path guards, trust checks, tool-level hard stops, and explicit human approval. @@ -47,11 +47,11 @@ auto-mode learner and never silently becomes a rule. Durable project rules require an explicit command: ```bash -rho permissions list -rho permissions add allow command 'git status' -rho permissions add deny file '.env' -rho permissions revoke -rho permissions reset +decoder permissions list +decoder permissions add allow command 'git status' +decoder permissions add deny file '.env' +decoder permissions revoke +decoder permissions reset ``` The reset command asks for confirmation before mutating the rule store. @@ -88,7 +88,7 @@ local rule cannot widen an administrator ceiling. ## Host-native boundaries -Rho does not pretend that a host permission prompt is an operating-system +Decoder does not pretend that a host permission prompt is an operating-system containment boundary. The following are separate defenses: - Path tools enforce project and protected-path rules. @@ -99,8 +99,8 @@ containment boundary. The following are separate defenses: - Dry-run provides a deterministic preview mode for CI and review. If a task requires stronger process or filesystem containment than these host -controls provide, run Rho inside an environment chosen by the operator. That -deployment decision is intentionally outside Rho's product runtime. +controls provide, run Decoder inside an environment chosen by the operator. That +deployment decision is intentionally outside Decoder's product runtime. ## Verification @@ -117,7 +117,7 @@ The actual binary should also be smoke-tested: ```bash make build -bin/rho --help -bin/rho permissions list --json -bin/rho completion json +bin/decoder --help +bin/decoder permissions list --json +bin/decoder completion json ``` diff --git a/docs/README.md b/docs/README.md index 94ed11d27..9ae63d035 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Rho Documentation +# Decoder Documentation -Index for `rho/docs/`. The canonical repo inventory is +Index for `decoder/docs/`. The canonical repo inventory is [`GrayCodeAI/graycode-eco/ecosystem.yaml`](https://github.com/GrayCodeAI/graycode-eco/blob/main/ecosystem.yaml) — the `ecosystem.yaml` in this repo is deprecated and incomplete, and `ECOSYSTEM-WIRING.md` is historical. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index b6aa8dde6..4582af349 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -1,13 +1,15 @@ -# Releasing rho +# Releasing decoder -Rho is pre-1.0. Releases are cut by hand: a maintainer merges a release PR +Decoder is pre-1.0. Releases are cut by hand: a maintainer merges a release PR that sets `VERSION` and the `CHANGELOG.md` section, then pushes the matching `v` tag. The tag triggers [`.github/workflows/release.yml`](../.github/workflows/release.yml), which builds, signs and publishes the GitHub release. There is no release-please or other bot: nothing bumps `VERSION` or writes `CHANGELOG.md` automatically. -The first release under the `rho` name is **v0.3.0**. +The next release version comes from [`VERSION`](../VERSION). The new Decoder +series restarts below the historical hawk versions; use explicit tags for +Go installation rather than assuming `@latest` selects this series. ## Tag protection @@ -19,7 +21,7 @@ actor. A push of a `v*` tag by anyone else is rejected, so a write-access account cannot trigger a signed release. Inspect it with: ```bash -gh api repos/GrayCodeAI/rho/rulesets/24053191 +gh api repos/GrayCodeAI/decoder/rulesets/24053191 ``` The `main` branch is separately protected (required status checks, no force @@ -27,13 +29,13 @@ pushes). ## Release checklist -1. **Release PR** (conventional title, e.g. `chore(release): v0.3.0`): +1. **Release PR** (conventional title, e.g. `chore(release): vX.Y.Z`): - `VERSION` holds `X.Y.Z` (no leading `v`). - `CHANGELOG.md`: rename `## [Unreleased]` to `## [X.Y.Z] — YYYY-MM-DD` and add a new, empty `## [Unreleased]` above it. This section becomes the GitHub release notes verbatim. - - `testdata/compatibility-matrix.json`: set the `stable` matrix to rho - `X.Y.Z` and the flux version pinned in `go.mod`. + - `testdata/compatibility-matrix.json`: set the `stable` matrix to decoder + `X.Y.Z` and the mux version pinned in `go.mod`. - `README.md` install section: point the `install.sh` URL at the new tag. - Run locally: @@ -47,15 +49,15 @@ pushes). ```bash git fetch origin - git tag -s vX.Y.Z -m "rho vX.Y.Z" + git tag -s vX.Y.Z -m "decoder vX.Y.Z" git push origin vX.Y.Z ``` 4. **Watch** the `release` workflow run to completion (next section). 5. **Verify** the published release as a user would (see [Verifying a release](#verifying-a-release)), including - `curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/rho/vX.Y.Z/install.sh | RHO_REQUIRE_COSIGN=1 sh` - and `go install github.com/GrayCodeAI/rho/cmd/rho@vX.Y.Z`. + `curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/decoder/vX.Y.Z/install.sh | DECODER_REQUIRE_COSIGN=1 sh` + and `go install github.com/GrayCodeAI/decoder/cmd/decoder@vX.Y.Z`. `make release` deliberately refuses to publish from a workstation: releases are signed with the workflow's GitHub OIDC identity, which a local run cannot have. @@ -68,15 +70,15 @@ On a pushed `v*` tag, with `permissions: contents: write, id-token: write`: `v` + `VERSION` and `CHANGELOG.md` has a non-empty `## []` section; that section is extracted as the release notes. 2. `scripts/check-no-replace-directives.sh` refuses local `replace` - directives. The job sets `GOWORK=off`, so dependencies (including flux) + directives. The job sets `GOWORK=off`, so dependencies (including mux) come only from `go.mod`/`go.sum` via the public proxy. 3. syft (`anchore/sbom-action/download-syft`) and cosign (`sigstore/cosign-installer`) are installed; both actions are pinned by commit SHA. 4. GoReleaser v2.17.0 (`.goreleaser.yml`) builds and publishes: - - `rho___.tar.gz` for linux and darwin and `.zip` for + - `decoder___.tar.gz` for linux and darwin and `.zip` for windows, each on amd64 and arm64 (six archives, `-trimpath`, `CGO_ENABLED=0`); - - `rho__source.tar.gz`; + - `decoder__source.tar.gz`; - one SPDX SBOM per archive (`.spdx.sbom.json`); - `checksums.txt` (SHA-256 of every asset above); - `checksums.txt.sigstore.json`, a cosign keyless Sigstore bundle @@ -89,11 +91,11 @@ On a pushed `v*` tag, with `permissions: contents: write, id-token: write`: ```bash tag=vX.Y.Z -gh release download "$tag" --repo GrayCodeAI/rho \ +gh release download "$tag" --repo GrayCodeAI/decoder \ --pattern checksums.txt --pattern checksums.txt.sigstore.json \ - --pattern "rho_${tag#v}_linux_amd64.tar.gz" + --pattern "decoder_${tag#v}_linux_amd64.tar.gz" cosign verify-blob --bundle checksums.txt.sigstore.json \ - --certificate-identity "https://github.com/GrayCodeAI/rho/.github/workflows/release.yml@refs/tags/$tag" \ + --certificate-identity "https://github.com/GrayCodeAI/decoder/.github/workflows/release.yml@refs/tags/$tag" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ checksums.txt sha256sum --ignore-missing -c checksums.txt @@ -102,17 +104,17 @@ sha256sum --ignore-missing -c checksums.txt `install.sh` performs the same checks: it always verifies the SHA-256, verifies the bundle when cosign (v2.4 or later; v3 recommended) is installed, says so plainly when it is not, and refuses to install without cosign when -`RHO_REQUIRE_COSIGN=1`. +`DECODER_REQUIRE_COSIGN=1`. ## Distribution channels | Channel | Status | |---|---| -| GitHub release archives | From v0.3.0. | -| `install.sh` | From v0.3.0; refuses older (hawk-era) versions. | -| `go install github.com/GrayCodeAI/rho/cmd/rho@latest` | From v0.3.0. Until then `@main` works; `@latest` resolves to the hawk-era v0.2.0. | -| GitHub Action (`.github/actions/rho`) | From v0.3.0; installs through `install.sh` with cosign required. | -| systemd unit (`packaging/systemd/rho-daemon.service`) | Template for running `rho daemon`; see the troubleshooting guide. | +| GitHub release archives | Pending a Decoder release with matching archives. | +| `install.sh` | Pending a Decoder release; refuses known hawk-era tags. | +| `go install github.com/GrayCodeAI/decoder/cmd/decoder@latest` | Use an explicit Decoder tag or `@main`; historical higher tags affect `@latest`. | +| GitHub Action (`.github/actions/decoder`) | Requires Decoder release archives; installs through `install.sh` with cosign required. | +| systemd unit (`packaging/systemd/decoder-daemon.service`) | Template for running `decoder daemon`; see the troubleshooting guide. | | Homebrew | **Not available.** Needs a `GrayCodeAI/homebrew-tap` repository, a fine-grained token with write access to it stored as a secret, and a `homebrew_casks` stanza in `.goreleaser.yml`. | | npm | **Not available.** The scaffolding in `npm/` is marked private; see [`npm/README.md`](../npm/README.md). | | Nix | **Not available.** The old flake could not evaluate and was removed; a new one needs a computed `vendorHash`, a committed `flake.lock` and `nix flake check` in CI. | @@ -120,16 +122,16 @@ plainly when it is not, and refuses to install without cosign when Add a channel to the README only after it has worked end to end for a real release. -## Notes for v0.3.0 (first rho release) +## Historical tags and the reset version series - Tags `v0.1.0`, `v0.1.1` and `v0.2.0` were published under the previous module path `github.com/GrayCodeAI/hawk`; their assets are `hawk_*` archives containing a `hawk` binary. None of them is an ancestor of `main`, and - `v0.1.1` has no GitHub release. The Go proxy keeps serving them, so v0.3.0's + `v0.1.1` has no GitHub release. The Go proxy keeps serving them, so the current `go.mod` retracts `[v0.1.0, v0.2.0]`. - Because no earlier tag is reachable from `main`, the release notes come only from the `CHANGELOG.md` section and the "Full changelog" compare link is omitted. -- v0.2.0 is the current "Latest" release. Once v0.3.0 is published it becomes - Latest. Consider editing the v0.2.0 release notes to say it is the legacy - hawk build and cannot be installed as rho. +- The historical higher-numbered hawk releases must not be confused with the + reset Decoder series. Publishing a lower version does not automatically make + it the Go proxy's `@latest`. Use explicit tags; do not rewrite existing tags. diff --git a/docs/RESEARCH.md b/docs/RESEARCH.md index 37b689a3c..4bfe6a6b3 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 (`rho`, `flux`, `graycode-platform`, `graycode-skills`) vs +Scope: 4 repos (`decoder`, `mux`, `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,15 +12,15 @@ 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: `rho/README.md`, `flux/README.md`, +- Repo capability inventory: `decoder/README.md`, `mux/README.md`, `graycode-platform/README.md`, `graycode-skills/README.md`, plus source audits. ## Our repos at a glance | Repo | Role | Key strengths | |---|---|---| -| rho | Product face (Go, Bubble Tea v2) | 120+ tools, host permission/path controls, `/autonomy`+`/spec` gates, `mission` multi-agent, execution-graph export, AST repomap + Harrier memory, MCP/LSP | -| flux | Provider engine facade | 22 gateways, routing/retry/caching/compaction, OpenAI-compat proxy, model catalog | +| decoder | Product face (Go, Bubble Tea v2) | 120+ tools, host permission/path controls, `/autonomy`+`/spec` gates, `mission` multi-agent, execution-graph export, AST repomap + Harrier memory, MCP/LSP | +| mux | 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 59a4445a0..7be706a96 100644 --- a/docs/SECURITY-DEVELOPER.md +++ b/docs/SECURITY-DEVELOPER.md @@ -1,13 +1,13 @@ -# Rho developer security model +# Decoder developer security model -This document describes how rho and flux 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 decoder and mux 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). -- Rho does not read API keys from `.env`, shell env, or plaintext files. -- Flux's `provider.json` holds routing and deployment metadata only — never secrets on disk. -- Rho talks to flux without putting keys in JSON or chat messages. +- Decoder does not read API keys from `.env`, shell env, or plaintext files. +- Mux's `provider.json` holds routing and deployment metadata only — never secrets on disk. +- Decoder talks to mux 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 rho and flux handle API keys and agent isolation for | Write | Read | Remove | |-------|------|--------| -| `/config` paste flow → `flux/engine.Engine.SaveCredential` | `Engine.ResolveCredential` (secret store only) | `/config key remove` or `rho credentials remove` | +| `/config` paste flow → `mux/engine.Engine.SaveCredential` | `Engine.ResolveCredential` (secret store only) | `/config key remove` or `decoder credentials remove` | -Rho does not import keys from legacy `~/.rho/env` / `~/.rho/.env` files or +Decoder does not import keys from legacy `~/.decoder/env` / `~/.decoder/.env` files or from secret fields in `provider.json`. Save every key through `/config`. -Check status: `rho credentials status`, `rho path`, or `rho preflight`. +Check status: `decoder credentials status`, `decoder path`, or `decoder preflight`. ## First-run flow (`/config`) @@ -28,34 +28,34 @@ Check status: `rho credentials status`, `rho path`, or `rho preflight`. User pastes API key in /config | v -Rho /config -> Flux engine credential service (OS secret store) +Decoder /config -> Mux engine credential service (OS secret store) | v -Flux engine discover/apply (credentials from store, not JSON body) +Mux engine discover/apply (credentials from store, not JSON body) | v SetupUI JSON (display_name + canonical_id per model) | v -User picks model -> Flux provider.json (canonical id only) +User picks model -> Mux provider.json (canonical id only) ``` Remove a stored key: `/config key remove` (interactive picker). -## Rho to Flux +## Decoder to Mux -- **Control plane**: Rho calls only `flux/engine`; no lower Flux package is a +- **Control plane**: Decoder calls only `mux/engine`; no lower Mux 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**: Rho sends model intent, messages, and tool definitions; Flux +- **Chat**: Decoder sends model intent, messages, and tool definitions; Mux resolves the gateway and reads secrets internally. ## Agent execution ``` +------------------+ -| Rho TUI/host | +| Decoder TUI/host | | Keychain access | | /config paste | +------------------+ @@ -65,51 +65,51 @@ Remove a stored key: `/config key remove` (interactive picker). Host command execution ``` -Rho executes agent commands directly on the host. Every tool call passes +Decoder 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 Rho env files, Flux's configured `provider.json`, +- **Read** tool: legacy Decoder env files, Mux's configured `provider.json`, `~/.ssh/*`, etc. -- **Bash**: `printenv`, `env`, reading rho env paths, echoing `*_API_KEY` variables. +- **Bash**: `printenv`, `env`, reading decoder env paths, echoing `*_API_KEY` variables. ## Secrets left on disk -- **provider.json secrets**: `rho path` fails its `provider.json` security - check when the file still holds secret fields. Rho does not remove them +- **provider.json secrets**: `decoder path` fails its `provider.json` security + check when the file still holds secret fields. Decoder does not remove them automatically: back up the file, delete the secret fields, and save the keys again through `/config`. -- **Provider state writes**: the Flux engine sanitizes provider state and +- **Provider state writes**: the Mux engine sanitizes provider state and writes it atomically, so secret fields are never written back to disk. ## Provider state path -Flux owns the provider-state path. Resolution order is: +Mux owns the provider-state path. Resolution order is: -1. `FLUX_CONFIG_DIR/provider.json` -2. the platform user-config directory under `flux/provider.json` +1. `MUX_CONFIG_DIR/provider.json` +2. the platform user-config directory under `mux/provider.json` -Rho's Read/Edit/Write and Bash safety checks protect the resolved path, -including a custom or symlinked `FLUX_CONFIG_DIR`; protection is not limited +Decoder's Read/Edit/Write and Bash safety checks protect the resolved path, +including a custom or symlinked `MUX_CONFIG_DIR`; protection is not limited to the historical default provider-state location. ## Environment variables -Non-secret overrides only (rho does not load provider API keys from env): +Non-secret overrides only (decoder does not load provider API keys from env): | Variable | Meaning | |----------|---------| -| `RHO_CONFIG_DIR` | Override rho config directory | -| `FLUX_CONFIG_DIR` | Override Flux provider-state directory; takes precedence for `provider.json` | +| `DECODER_CONFIG_DIR` | Override decoder config directory | +| `MUX_CONFIG_DIR` | Override Mux 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 -- Rho: `internal/config/engine.go`, `internal/tool/safety.go`, +- Decoder: `internal/config/engine.go`, `internal/tool/safety.go`, `internal/storage/paths.go`, `cmd/credentials.go` -- Flux public host boundary: `engine/` +- Mux 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 27cd98788..43d2d64e6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,10 +1,10 @@

-# bird rho Architecture +# bird decoder Architecture **AI Coding Agent for Your Terminal** -[![Go](https://img.shields.io/badge/Go-1.26+-00ADD8?logo=go)](https://go.dev/) +[![Go](https://img.shields.io/badge/Go-1.27.2+-00ADD8?logo=go)](https://go.dev/) [![Port](https://img.shields.io/badge/Port-4590-orange)](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml) [![Protocol](https://img.shields.io/badge/Protocol-REST-blue)](https://swagger.io/specification/) @@ -14,19 +14,19 @@ ## target Overview -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. +decoder 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 Rho product architecture live in [`docs/architecture/`](architecture/README.md). +Detailed planning docs for the Decoder product architecture live in [`docs/architecture/`](architecture/README.md). --- ## blocks Layered Architecture ``` -rho/ +decoder/ ├── api/openapi.yaml file-text Daemon REST API contract (OpenAPI 3.1) ├── cmd/ terminal Cobra CLI commands (200+ files) -│ ├── rho/main.go zap Entry point — calls cmd.Execute() +│ ├── decoder/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 @@ -61,8 +61,8 @@ rho/ | | | |---|---| | **Contract** | [`api/openapi.yaml`](../api/openapi.yaml) | -| **Port** | `:4590` (default). Override: `RHO_DAEMON_PORT` | -| **Auth** | Bearer token or `X-API-Key`. Set via `RHO_DAEMON_API_KEY` | +| **Port** | `:4590` (default). Override: `DECODER_DAEMON_PORT` | +| **Auth** | Bearer token or `X-API-Key`. Set via `DECODER_DAEMON_API_KEY` |
radio Endpoint Summary @@ -86,7 +86,7 @@ rho/ | Service | Role | Connection | |---------|------|------------| -| bird **flux** | LLM provider runtime | Library — all LLM calls routed through its engine facade | +| bird **mux** | LLM provider runtime | Library — all LLM calls routed through its engine facade | | scissors **token** | Token optimization | Embedded — counting, compression, secrets, usage tracking | ### Client SDKs @@ -99,7 +99,7 @@ rho/ All three SDKs share types from **eagle** and consume the daemon REST API (:4590). -> lightbulb **rho never talks to LLM APIs directly** — all calls go through flux. +> lightbulb **decoder never talks to LLM APIs directly** — all calls go through mux. --- @@ -117,9 +117,9 @@ Tool Call → ` — persisted Rho session; -- `rho/task-request//` — user task request, content represented by +- `decoder/session/` — persisted Decoder session; +- `decoder/task-request//` — user task request, content represented by SHA-256 only; -- `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; -- `flux/route/` and `flux/generation/` — model route and +- `decoder/task/` — structured task; +- `decoder/runtime-task/` — background agent, shell, or monitor task; +- `decoder/tool-call//` — tool invocation metadata; +- `decoder/policy/` — permission verdict; +- `decoder/verification/` — neutral verification result; +- `mux/route/` and `mux/generation/` — model route and normalized generation operations. Edges: @@ -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`. -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 +Decoder writes runtime observations to a per-session append-only JSONL journal. +The journal is stored with mode `0600` under Decoder'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 `rho.graph/v1` envelope, but it is mission-scoped rather than +portable `decoder.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 -rho graph export [session-id] +decoder 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 `rho.graph/v1` envelope. The handler owns transport concerns -only; the Rho composition root injects the existing graph builder, so CLI, +returns the typed `decoder.graph/v1` envelope. The handler owns transport concerns +only; the Decoder 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 `rho cloud login` or `rho cloud connect`, a +After connecting a project with `decoder cloud login` or `decoder cloud connect`, a completed session snapshot can be uploaded explicitly: ```bash -rho cloud graph sync [session-id] +decoder 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. -Rho does not upload prompt bodies, response bodies, tool arguments, tool +Decoder 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 Rho session: +pretended to be a Decoder session: ```bash -rho graph export --mission-dir -rho cloud graph sync --mission-dir +decoder graph export --mission-dir +decoder cloud graph sync --mission-dir ``` -The CLI reads only `mission-graph.json`, validates its `rho.graph/v1` schema +The CLI reads only `mission-graph.json`, validates its `decoder.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. @@ -202,15 +202,15 @@ The central tool-execution seam automatically records: - secret matches removed from persisted-session responses; - token usage summaries and budget decisions emitted from the central, deduplicated provider-accounting seam; -- Flux model route and usage reported for persisted-session turns. +- Mux model route and usage reported for persisted-session turns. Mission execution now also persists a portable graph artifact for local mission -runs. Plain `rho mission` runs emit mission and feature execution nodes; the -graph-driven `rho mission --from-tasks` path additionally emits operations +runs. Plain `decoder mission` runs emit mission and feature execution nodes; the +graph-driven `decoder 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 Rho cost accounting and limits remain +limits to the pre-turn guard. Existing Decoder 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 index 526c1cef6..a2fa4d170 100644 --- a/docs/architecture/frontend-contract.md +++ b/docs/architecture/frontend-contract.md @@ -1,6 +1,6 @@ # Frontend contract -Rho has one agent brain and several frontends. They meet at a single typed +Decoder 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." diff --git a/docs/architecture/graycode-ecosystem-summary.md b/docs/architecture/graycode-ecosystem-summary.md index e1725dca5..ffc3c0c0f 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. Rho now depends only on `flux` and embeds its own token engine; +> ecosystem. Decoder now depends only on `mux` 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. `rho` is the main CLI and the -only primary Rho product; the other repositories provide capabilities, +`graycode-eco` is only a local parent folder. `decoder` is the main CLI and the +only primary Decoder product; the other repositories provide capabilities, contracts, integrations, tooling, or optional platform services. ## Repository layers @@ -15,18 +15,18 @@ contracts, integrations, tooling, or optional platform services. API consumers/extensions sparrow robin wren starling \ | | / - rho + decoder (main CLI and daemon) | Support engines - flux harrier shrike swift kestrel merlin + mux harrier shrike swift kestrel merlin | Foundations eagle falcon Outside the Go runtime graph owl architecture tooling - graycode-platform web, BFF, and Rho Cloud Worker + graycode-platform web, BFF, and Decoder Cloud Worker ``` Product labels map to directories as follows: `harrier`/Harrier, `shrike`/Shrike, @@ -35,42 +35,42 @@ Product labels map to directories as follows: `harrier`/Harrier, `shrike`/Shrike ## Dependency direction ```text -rho -> flux / harrier / shrike / swift / kestrel / merlin / eagle +decoder -> mux / harrier / shrike / swift / kestrel / merlin / eagle engines -> eagle # when shared contracts are needed harrier / kestrel / merlin -> falcon -sparrow / robin / wren -> Rho daemon API -starling -> Rho skill/plugin API +sparrow / robin / wren -> Decoder daemon API +starling -> Decoder skill/plugin API ``` -Forbidden edges are engine-to-engine, engine-to-Rho-internal, SDK-to-engine, +Forbidden edges are engine-to-engine, engine-to-Decoder-internal, SDK-to-engine, skills-to-engine, and any Go-module dependency on GrayCode Platform. ## Runtime and hosted plane ```text -Rho main CLI - ├── Flux provider execution +Decoder main CLI + ├── Mux provider execution ├── Harrier memory ├── Shrike token/context management ├── Swift swift/provenance ├── Kestrel review └── Merlin verification -rho ── optional authenticated HTTP ──> graycode-platform/apps/worker +decoder ── 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 Rho Go module graph. -Rho's cloud usage path is fail-open; graph synchronization is explicit. +not a repository. GrayCode Platform remains outside the Decoder Go module graph. +Decoder's cloud usage path is fail-open; graph synchronization is explicit. ## Repository roles | Repository | Role | Direct dependency rule | |---|---|---| -| `rho` | Main CLI, daemon, orchestration, policy | Integrates engines and contracts | -| `flux` | Provider runtime | Uses Eagle contracts; exposes `engine` | +| `decoder` | Main CLI, daemon, orchestration, policy | Integrates engines and contracts | +| `mux` | Provider runtime | Uses Eagle contracts; exposes `engine` | | `harrier` | Harrier memory | Uses Eagle/Falcon where required | | `shrike` | Shrike context engine | Uses Eagle where required | | `swift` | Swift/provenance | Uses Eagle where required | @@ -78,10 +78,10 @@ Rho'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 | Rho API contract | -| `robin` | Python SDK | Rho API contract | -| `wren` | TypeScript SDK | Rho API contract | -| `starling` | Skills/extensions | Rho skill surface | +| `sparrow` | Go SDK | Decoder API contract | +| `robin` | Python SDK | Decoder API contract | +| `wren` | TypeScript SDK | Decoder API contract | +| `starling` | Skills/extensions | Decoder skill surface | | `owl` | Architecture explorer | Generated read-only projection | | `graycode-platform` | Web/BFF/cloud | HTTP and Service Binding only | @@ -90,6 +90,6 @@ Rho's cloud usage path is fail-open; graph synchronization is explicit. 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 Flux revision, remove the transitional -`rho-core-contracts` dependency from the standalone graph, and decide whether -Rho's graph/projection packages need additional engine-owned facades. +the Eagle-compatible Mux revision, remove the transitional +`decoder-core-contracts` dependency from the standalone graph, and decide whether +Decoder's graph/projection packages need additional engine-owned facades. diff --git a/docs/architecture/plan.md b/docs/architecture/plan.md index ac343cf3f..31daab6c3 100644 --- a/docs/architecture/plan.md +++ b/docs/architecture/plan.md @@ -1,8 +1,8 @@ -# Rho Architecture - Technical Plan +# Decoder Architecture - Technical Plan ## Overview -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. +This plan defines the technical approach for implementing the decoder architecture specification. The architecture is already largely implemented; this plan documents the existing design decisions and identifies gaps. ## Architecture Decisions @@ -151,7 +151,7 @@ services; it is not a claim that those fields have already moved. ## Dependencies -- **flux:** LLM provider engine behind `flux/engine` (workspace sibling) +- **mux:** LLM provider engine behind `mux/engine` (workspace sibling) - **harrier:** Graph-based persistent memory (workspace sibling) - **shrike:** Tokenizer, compression (workspace sibling) - **eagle:** Shared types (workspace sibling) diff --git a/docs/architecture/spec.md b/docs/architecture/spec.md index 420952949..6296bbcea 100644 --- a/docs/architecture/spec.md +++ b/docs/architecture/spec.md @@ -1,8 +1,8 @@ -# Rho Architecture Specification +# Decoder Architecture Specification ## Problem Statement -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. +Decoder 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 decoder works. ## Scope @@ -20,15 +20,15 @@ Rho is an AI-powered coding agent for the terminal. This specification defines t ### REQ-1: Repository Structure -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 +Decoder SHALL be organized as a Go repository and workspace entry point within a +multi-repository ecosystem. The Decoder 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: rho, eagle, falcon, flux, harrier (Harrier), shrike (Shrike), swift (Swift), kestrel (Kestrel), and merlin (Merlin) | +| Parent `go.work` | Resolves the nine local Go siblings: decoder, eagle, falcon, mux, 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 | @@ -44,7 +44,7 @@ The `internal/` directory SHALL contain the following packages: | `tool/` | 40+ built-in tools (file edit, git, codegen, spec tools, etc.) | | `permissions/` | Guardian, rules DSL, boundary checker | | `plugin/` | Skills loader, registry, auto-skill, marketplace install | -| `config/` | Product settings, Flux composition, state migration | +| `config/` | Product settings, Mux composition, state migration | | `session/` | SQLite persistence, search, export, replay | | `hooks/` | Event-driven plugin system | | `mcp/` | Model Context Protocol client/server | @@ -73,7 +73,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` | Rho ChatClient port, engine adapter coordination, compact | +| `chat_service.go` | Decoder 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 | @@ -93,17 +93,17 @@ The `internal/engine/` package SHALL contain the following sub-systems: Provider boundary invariant: ```text -Rho CLI/TUI + conversation + tools +Decoder CLI/TUI + conversation + tools | - Rho-owned ports/DTOs + Decoder-owned ports/DTOs | v - flux/engine + mux/engine credentials -> catalog -> routing -> generate/stream ``` -No production Rho package may import a lower Flux package. Custom gateways -are supplied per Engine instance, and Flux DTOs are not Rho persistence or +No production Decoder package may import a lower Mux package. Custom gateways +are supplied per Engine instance, and Mux DTOs are not Decoder persistence or CLI output schemas. ### REQ-4: Agent Loop Lifecycle @@ -128,7 +128,7 @@ The agent loop in `stream.go` SHALL execute the following phases: 6. Refresh Harrier memories 7. Build LLM ChatOptions (system prompt, tools, model) 8. Inject ephemeral context (beliefs, matched skills, spec stage) -9. Execute the LLM call via `ChatService.Stream()` and the `flux/engine` adapter +9. Execute the LLM call via `ChatService.Stream()` and the `mux/engine` adapter 10. Process response (tool calls, messages, cost tracking) 11. Check termination conditions diff --git a/docs/architecture/tasks.md b/docs/architecture/tasks.md index 0a5193e6e..21ff441b3 100644 --- a/docs/architecture/tasks.md +++ b/docs/architecture/tasks.md @@ -1,7 +1,7 @@ -# Rho Architecture - Implementation Tasks +# Decoder Architecture - Implementation Tasks > **Historical.** This was the initial architecture implementation checklist. -> It is superseded by `rho-architecture-v1-definition-of-done.md`, which +> It is superseded by `decoder-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 ccc9e627e..5f675e689 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -12,19 +12,20 @@ Platform/provider capability metadata is separate: [`platform-capabilities.json` ```jsonc { - "components": ["rho", "flux", ...], // canonical eco roster - "dependencies": { "rho": ["flux", ...] }, // who depends on who + "components": ["decoder", "mux", ...], // canonical eco roster + "dependencies": { "decoder": ["mux", ...] }, // who depends on who "matrices": [ - { "name": "stable", "components": { "rho": "0.1.0", "flux": "0.1.0", ... } }, - { "name": "next", "components": { "rho": "main", "flux": "main", ... } } + { "name": "stable", "components": { "decoder": "0.1.0", "mux": "0.1.0", ... } }, + { "name": "next", "components": { "decoder": "main", "mux": "main", ... } } ] } ``` -Two named matrices: +Supported matrix names: -- **`stable`** — the most recent combination of released versions that has - been tested end-to-end. New users getting "the eco" install these versions. +- **`stable`** — reserved for the most recent combination of released versions + tested end-to-end. It is currently omitted because the existing tags declare + different module paths; add it after both renamed modules have releases. - **`next`** — the bleeding edge: the latest commit on each repo's `main` branch. CI runs the integration tests against this matrix on every PR to any eco repo. @@ -48,7 +49,7 @@ existing consumers. `.shared-templates/workflows/compatibility-test.yml.tmpl` is a reusable workflow that: -1. Reads `testdata/compatibility-matrix.json` from the rho repo. +1. Reads `testdata/compatibility-matrix.json` from the decoder repo. 2. Checks out each component at the version listed in the named matrix. 3. Builds + tests the cross-repo integration scenarios. @@ -59,7 +60,7 @@ It runs on: ## Why bother -- **Bug reports become triageable.** "I ran rho 0.4 with flux 0.2" — you +- **Bug reports become triageable.** "I ran decoder 0.4 with mux 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 +71,17 @@ It runs on: ## Pin freshness (advisory) -Separate from the matrix above: `rho`'s own `go.mod` directly pins shared +Separate from the matrix above: `decoder`'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 `rho` pins wins in `rho`'s own build — but if a +selection means whatever `decoder` pins wins in `decoder`'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 `rho/go.mod` against each +step) reports any such mismatch by comparing `decoder/go.mod` against each `..//go.mod`: ```bash @@ -89,7 +90,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 Rho-only checkout simply reports no local consumers to compare. +present; a Decoder-only checkout simply reports no local consumers to compare. ## Validating the file diff --git a/docs/design/RHO-CLOUD-SAAS.md b/docs/design/DECODER-CLOUD-SAAS.md similarity index 89% rename from docs/design/RHO-CLOUD-SAAS.md rename to docs/design/DECODER-CLOUD-SAAS.md index ba1ce97cf..1342310c2 100644 --- a/docs/design/RHO-CLOUD-SAAS.md +++ b/docs/design/DECODER-CLOUD-SAAS.md @@ -1,4 +1,4 @@ -# Rho Cloud — Hosted Execution Plane (Design Doc) +# Decoder Cloud — Hosted Execution Plane (Design Doc) **Status:** Draft / Proposal **Author:** Platform team @@ -9,17 +9,17 @@ ## 1. Overview & Competitive Context -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` +Decoder today is a terminal-native, single-user, privacy-first Go binary. The daemon +(`decoder/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. Rho does not provide a container or OS sandbox runtime; commands +execution. Decoder does not provide a container or OS sandbox runtime; commands run on the host behind the permission engine and tool-level path guards. -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 +This doc designs **Decoder Cloud**: a hosted, multi-tenant execution plane that runs +the decoder 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 rho P0 table (lines 27-37) and P2 table +From `TOP20_COMPARISON.md`, the decoder 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 rho 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 `/rho/internal/auth/auth.go`" (`TOP20_COMPARISON.md:72`). +token store at `/decoder/internal/auth/auth.go`" (`TOP20_COMPARISON.md:72`). -The flux side of the house has the parallel gaps: a LiteLLM-compatible proxy +The mux 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`). Rho Cloud and flux multi-tenancy share +budgets (`TOP20_COMPARISON.md:95-96`). Decoder Cloud and mux multi-tenancy share the same identity, billing, and RBAC substrate; this doc designs that substrate once and shows how both consume it. @@ -68,13 +68,13 @@ 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 Rho Cloud's versioned server catalog, enforce + price normalized usage with Decoder 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-isolated execution.** Keep local rho host-native and, if this +7. **Cloud-isolated execution.** Keep local decoder host-native and, if this proposal is implemented, run cloud workers in on-demand isolated microVMs (E2B/Daytona/Firecracker) behind a new cloud-only boundary. 8. **Browser/web UI** for chat-driven editing, served from the plane, consuming @@ -83,14 +83,14 @@ once and shows how both consume it. ### Non-Goals -- **Not** replacing the local/offline single-binary mode. Local rho remains +- **Not** replacing the local/offline single-binary mode. Local decoder 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. Flux remains the provider engine behind - Rho's `flux/engine` integration; the plane calls a Rho agent worker, which - composes Flux. The plane does not re-implement routing, caching, or provider +- **Not** an LLM gateway rewrite. Mux remains the provider engine behind + Decoder's `mux/engine` integration; the plane calls a Decoder agent worker, which + composes Mux. 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). - **Not** on-prem/self-hosted enterprise distribution in P0 (P2). @@ -104,7 +104,7 @@ once and shows how both consume it. ``` ┌────────────────────────────────────────┐ - Browser UI ──┐ │ Rho Cloud │ + Browser UI ──┐ │ Decoder Cloud │ IDE (ACP) ──┼──TLS──▶│ │ CLI (token)──┘ │ ┌──────────────┐ ┌────────────────┐ │ │ │ Edge / API │ │ Identity svc │ │ @@ -122,7 +122,7 @@ once and shows how both consume it. │ │ │ │ ▼ │ │ ┌──────────────┐ ┌─────────────┐ │ - │ │ Agent Worker │─────▶│ flux │ │ + │ │ Agent Worker │─────▶│ mux │ │ │ │ (engine. │ │ (model │ │ │ │ Session) │ │ runtime) │ │ │ └──────┬───────┘ └─────────────┘ │ @@ -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. Rho Cloud owns this authority; Flux -supplies normalized model and token usage through Rho's Engine boundary. +hard-limit enforcement, and invoicing. Decoder Cloud owns this authority; Mux +supplies normalized model and token usage through Decoder's Engine boundary. **Workspace service** — workspace membership, shared session state, share links, permission checks for view/continue. @@ -156,13 +156,13 @@ 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 rho agent loop, unchanged, +(`internal/engine/stream.go:20`). This is the existing decoder 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. **Cloud execution provider** — a future `CloudSandbox` boundary backed by -E2B/Daytona. This is intentionally net-new cloud code; local rho has no +E2B/Daytona. This is intentionally net-new cloud code; local decoder has no container executor or sandbox backend to reuse. ### 3.2 Data Model @@ -197,11 +197,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 Rho Cloud + -- kind ∈ {llm, sandbox}; cost_usd is priced by Decoder Cloud ``` `meter_events.cost_usd` is computed from normalized model/token dimensions by -Rho Cloud's versioned pricing catalog. Client-side estimates may be displayed +Decoder Cloud's versioned pricing catalog. Client-side estimates may be displayed or audited, but never affect credits or invoices. ### 3.3 API Surface @@ -257,7 +257,7 @@ Edge: check credit balance(org) > floor [Billing svc] ← rejec Edge → Router: route(org, ws, user, session_id?) Router → Worker: acquire/lease Agent Worker (+ Sandbox) Worker: engine.Session.Stream(ctx) [stream.go:20] -Worker → flux: Chat/StreamChat with ctx carrying virtual key +Worker → mux: Chat/StreamChat with ctx carrying virtual key (WithVirtualKey, budget_provider.go:23) Worker → Edge: SSE content events (daemon.go:321-337, reused verbatim) Edge → Client: SSE relay @@ -268,9 +268,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** — -rho already implements RFC 8628 device flow end-to-end +decoder already implements RFC 8628 device flow end-to-end (`internal/auth/device_flow.go`: `RequestCode`, `PollForToken`, -`exchangeCode`). Rho Cloud stands up the *server* side of these grants; the +`exchangeCode`). Decoder Cloud stands up the *server* side of these grants; the client side is largely present. **C. Share a conversation** @@ -304,7 +304,7 @@ This is the crux: **what is reusable today vs. net-new.** ### Reusable today (high leverage) -| Existing asset | File | How Rho Cloud reuses it | +| Existing asset | File | How Decoder 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. | @@ -312,10 +312,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). | -| Flux normalized usage | `flux/engine` response and stream usage DTOs through Rho's adapter | Reuse model/token dimensions as metering input. Do not import lower Flux packages or trust client-computed prices for billing. | +| Mux normalized usage | `mux/engine` response and stream usage DTOs through Decoder's adapter | Reuse model/token dimensions as metering input. Do not import lower Mux packages or trust client-computed prices for billing. | | Remote execution interface | Planned cloud execution boundary | A future `CloudSandbox` would be isolated from the local host runtime behind an explicit provider interface. | -| Sandbox lifecycle manager | Net-new cloud component | E2B/Daytona pause/resume/snapshot semantics should be designed at the cloud boundary; local rho has no sandbox lifecycle manager. | -| 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. | +| Sandbox lifecycle manager | Net-new cloud component | E2B/Daytona pause/resume/snapshot semantics should be designed at the cloud boundary; local decoder has no sandbox lifecycle manager. | +| Messaging gateways | `internal/daemon/gateway.go`, `telegram.go`, `discord.go`, `slack.go` | Already forward to `/v1/chat` via `forwardToDecoder` (`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) @@ -340,7 +340,7 @@ This is the crux: **what is reusable today vs. net-new.** in-process engine. Net-new control loop. 6. **`CloudSandbox`** — E2B/Daytona-backed cloud-only execution provider. The interface and call sites exist; the cloud backend does not. -7. **Billing/credit ledger + invoicing + Stripe integration** — flux gives +7. **Billing/credit ledger + invoicing + Stripe integration** — mux gives enforcement and cost math; the ledger, top-ups, plans, and payment processor are net-new. 8. **Workspace sharing** — `session_shares` and the share/continue flows. @@ -363,11 +363,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 Rho Cloud catalog, persist the Postgres credit ledger, and + the versioned Decoder 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 rho against a +**Exit criteria:** a paying single team can log in via OAuth, run decoder against a cloud sandbox, see streamed output and diffs in the browser, and get cut off when credits hit zero. @@ -386,7 +386,7 @@ credits hit zero. - **M2.1** SSO/SAML + SCIM provisioning. - **M2.2** Multi-region worker pools + autoscaling; second sandbox provider for redundancy. - **M2.3** JetBrains extension (ACP). -- **M2.4** Org-scoped provider config (BYO keys / BYO model endpoints), aligning with flux multi-tenant proxy (`TOP20_COMPARISON.md:96`). +- **M2.4** Org-scoped provider config (BYO keys / BYO model endpoints), aligning with mux multi-tenant proxy (`TOP20_COMPARISON.md:96`). - **M2.5** Audit logging, IT-managed policy tiers, self-hosted/on-prem distribution. - **M2.6** Cloud Routines (scheduled/triggered runs) on the worker plane (`TOP20_COMPARISON.md:48`). @@ -398,9 +398,9 @@ credits hit zero. |---|---|---| | MicroVM sandboxing | **Buy/integrate** E2B (P0) + Daytona (P2 redundancy) | Building Firecracker orchestration is a multi-quarter effort orthogonal to product value. E2B SDK is open source (Apache-2.0/MIT family); Daytona is Apache-2.0. Both have hosted offerings; we can also self-host E2B later. The `containerExecutor` seam (`container.go:18`) keeps us provider-agnostic. | | 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 flux). Stripe SDK is permissive. | +| 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 mux). Stripe SDK is permissive. | | Database | **Buy** managed Postgres | Standard. | -| Metering enforcement | **Build in Rho Cloud** on normalized Engine usage | Billing authority must be server-owned and versioned; lower Flux packages remain engine implementation details. | +| Metering enforcement | **Build in Decoder Cloud** on normalized Engine usage | Billing authority must be server-owned and versioned; lower Mux 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. | @@ -416,14 +416,14 @@ 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 rho never phones home; +1. **Cloud is opt-in and isolated from local mode.** Local decoder never phones home; nothing in `internal/engine` 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 rho's isolation model) carries over. Cloud microVMs should default to + (already decoder's isolation model) carries over. Cloud microVMs should default to no network egress unless a workspace explicitly grants it through a cloud-owned - allowlist. Local host execution remains governed by rho's permission and path + allowlist. Local host execution remains governed by decoder's permission and path policies, not by this cloud design. 3. **Secrets never stored raw.** API keys stored as hashes only (the constant-time compare in `daemon.go:207` becomes hash-compare). OAuth refresh tokens encrypted @@ -431,7 +431,7 @@ erode that. 4. **Per-line SSE escaping is a real injection defense** already present (`daemon.go:324-329`) and must be preserved in the relay path — LLM output can contain newlines that would otherwise forge SSE events. -5. **Credit enforcement is a DoS control too.** Hard floors (HTTP 402) via flux +5. **Credit enforcement is a DoS control too.** Hard floors (HTTP 402) via mux `CheckBudget` (`budget_provider.go:181`) bound runaway agent spend per tenant. 6. **Code privacy.** Customer code lives only in the ephemeral microVM and the session store; offer per-org data-retention controls and a "no-retention" mode @@ -455,13 +455,13 @@ 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 rho's + Daytona = richer dev-environment model. Which maps better onto decoder'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? 4. **BYO keys vs. managed keys:** if an org brings its own provider keys, do their LLM tokens still consume credits (we only charge sandbox/orchestration), or run - free? Affects flux provider-config plumbing (`TOP20_COMPARISON.md:96`). + free? Affects mux provider-config plumbing (`TOP20_COMPARISON.md:96`). 5. **Identity build-vs-buy:** Ory self-hosted (privacy, ops burden) vs. WorkOS/Auth0 (speed, third party in auth path). Privacy posture argues for Ory. 6. **Session store backend:** Postgres for everything vs. Postgres (metadata) + @@ -498,7 +498,7 @@ Assumes a small senior team (3-4 engineers). Estimates are for engineering only with P0 a hard ~8-10 calendar weeks for a usable single-team beta.** Leverage note: the agent loop, SSE, sandbox interface, device-flow client, and -flux budget machinery are **already built** — they collapse what would otherwise +mux budget machinery are **already built** — they collapse what would otherwise be the most expensive parts (the agent runtime and the metering enforcement engine) into integration work. The genuinely greenfield cost is identity/multi-tenancy, worker orchestration, and the cloud sandbox backend. diff --git a/docs/design/ECOSYSTEM-MARKETPLACE.md b/docs/design/ECOSYSTEM-MARKETPLACE.md index be88c788b..12c0d52aa 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 (rho, flux, harrier, shrike, swift) + a new gallery web property + a unified docs site. +**Scope:** Multi-month effort spanning all 5 repos (decoder, mux, 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` (rho P1) and `TOP20_COMPARISON.md:227` (cross-cutting P1). +1. **Centralized extension gallery / plugin marketplace** — `TOP20_COMPARISON.md:65` (decoder 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 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`. +- **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 decoder at `decoder/internal/plugin/registry.go:18` (`defaultIndexURL = https://raw.githubusercontent.com/GrayCodeAI/starling/main/registry.json`). +- **A registry client already works.** `decoder/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.** `decoder skills {list,search,install,remove,info,trending,audit}` is wired in `decoder/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.** `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`). +- **A V2 manifest already supports MCP-adjacent bundles.** `decoder/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.** `decoder/internal/plugin/malware_check.go:19` blocks `eval()`, pipe-to-shell, reverse shells; `decoder/internal/plugin/audit.go` flags hidden-Unicode / homoglyph attacks (`decoder 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 (`rho/docs/`, `flux/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 (`decoder/docs/`, `mux/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 **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`. +- **G1.** A single standardized **Decoder 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** (decoderskills.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 `decoder/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 `~/.rho/skills` / `.rho/skills` layout (`registry.go:201-203`). +- **NG4.** A new package manager. We reuse `git clone`-based install (`registry.go:215`) and the `~/.decoder/skills` / `.decoder/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 ▼ ▼ ┌───────────────────────────────┐ ┌──────────────────────────────┐ -│ rho CLI (Go) │ │ Gallery + Docs site (static) │ +│ decoder 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/{rho,flux,...} │ +│ cmd/skills_cmd.go (+ ext cmd)│ │ - /docs/{decoder,mux,...} │ │ 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 `rho/internal/plugin/skill_loader.go:51` (`parseSkillFrontMatter`) and discovered at `rho/internal/tool/skill.go:62` (`discoverSkills`). We keep this working verbatim. +Already parsed at `decoder/internal/plugin/skill_loader.go:51` (`parseSkillFrontMatter`) and discovered at `decoder/internal/tool/skill.go:62` (`discoverSkills`). We keep this working verbatim. **(b) Full — `extension.yaml`** (new, for multi-asset bundles): ```yaml -apiVersion: rho.extension/v1 +apiVersion: decoder.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://rhoskills.dev/extensions/terraform-pro +homepage: https://decoderskills.dev/extensions/terraform-pro keywords: [terraform, iac, devops] compat: - minRhoVersion: "1.2.0" # maps to ManifestV2.MinRhoVersion (manifest_v2.go:20) + minDecoderVersion: "1.2.0" # maps to ManifestV2.MinDecoderVersion (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: "rho" + publisher: "decoder" signature: "signatures/terraform-pro.sig" ``` -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. +The `provides` block is intentionally a **strict superset of the existing assets decoder 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": "rho", + "publisher": "decoder", "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 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. +Backward compatibility: `registry.go`'s `SkillIndex.Skills` (`:36`) keeps reading `registry.json`. A small shim maps v2 `extensions[]` → `SkillEntry` so older decoder 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 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). +2. **The decoder CLI**, extended: + - Existing: `decoder skills {list,search,install,remove,info,trending,audit}` (`cmd/skills_cmd.go`). + - New: `decoder ext {search,install,update,verify,info,publish}` for multi-asset extensions, plus `decoder 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: rho ext install GrayCodeAI/starling terraform-pro +user: decoder 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: rho 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 ~/.rho/skills/ (and commands/hooks/mcp dirs) + → copy assets to ~/.decoder/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: rho ext install GrayCodeAI/starling terraform-pro **Flow B — Gallery browse & install (no account, no server)** ``` -browser → rhoskills.dev/extensions (static Docusaurus/Mintlify page) +browser → decoderskills.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: rho ext install (copy button) + → "Install" shows: decoder 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 rho, flux, + pull README.md / ARCHITECTURE.md / docs/** from each of decoder, mux, 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` | `rho/internal/plugin/registry.go:60` | +| Registry fetch + 1h cache | `RegistryClient.FetchIndex` | `decoder/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 | `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` | +| CLI command tree | `skillsCmd` + subcommands | `decoder/cmd/skills_cmd.go:16-211` | +| Skill discovery roots | `discoverSkills`, `skillRoots` | `decoder/internal/tool/skill.go:62`, `:91` | +| Frontmatter parsing | `parseSkillFrontMatter` | `decoder/internal/plugin/skill_loader.go:51` | +| Multi-asset manifest | `ManifestV2` (+`Hooks`,`Config`,`Dependencies`,`Mode`) | `decoder/internal/plugin/manifest_v2.go:11`, `:32` | | Hook bundling | `ManifestHook{Event,Command,Priority}` | `manifest_v2.go:32` | -| 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` | +| MCP server bundling | MCP client/loader | `decoder/internal/mcp/mcp.go`, `decoder/internal/tool/mcp_tool.go` | +| Sub-agent bundling | Persona system (YAML frontmatter MD) | `decoder/internal/multiagent/agents/` (cf. `TOP20_COMPARISON.md:62`) | +| Rule/skill discovery precedence | `DefaultRuleSources`, `RuleDiscoverer` | `decoder/internal/plugin/` rule discovery | +| Trust — static scan | `malware_check.go` (blocked/suspicious regex) | `decoder/internal/plugin/malware_check.go:19` | +| Trust — unicode/homoglyph | `audit.go` (`AuditFinding`, severities) | `decoder/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):** -- `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`. +- `decoder/internal/plugin/trust.go` — signature verification (Flow A) and a `trustdb` of publisher public keys. +- `decoder/internal/plugin/extension.go` — parse `extension.yaml`, expand `provides` into the existing loaders. +- `decoder/cmd/ext_cmd.go` — `decoder 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. `rho-docs`). +- The gallery + docs site (new repo, e.g. `decoder-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 `.rho/skills` / `~/.rho/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 `.decoder/skills` / `~/.decoder/skills` (project then user), exactly the precedence `RuleDiscoverer` already honors. --- @@ -257,22 +257,22 @@ 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`; `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. +- **M0.3** `extension.go` parser + adapter into `ManifestV2`; `decoder 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 decoder; `--allow-unsigned` escape hatch; wire `malware_check.go` + `audit.go` into the install path as hard gates. +- **Exit:** `decoder 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/{rho,flux,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/{decoder,mux,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** `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. +- **M1.4** `decoder ext update` (diff installed version vs index, re-verify, atomic replace) and `decoder ext verify ` (re-check signature/audit of an installed extension). +- **Exit:** decoderskills.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** `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.1** `decoder 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 decoder'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: flux 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. +- **M2.4** Cross-repo extension kinds: mux 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 rho +### New runtime dependencies for decoder - 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 rho'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 decoder'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 `rho skills audit`). + 3. **Hidden-Unicode / homoglyph audit** (`audit.go`): catches prompt-injection-via-invisible-characters in `SKILL.md`/prompts (already `decoder 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 rho's binary; rotation requires a rho release. P2 cosign/sigstore would move trust to a transparency log, reducing reliance on bundled keys. +- **Signing key custody.** Verified-publisher keys ship in decoder's binary; rotation requires a decoder 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 `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 flux/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.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). +2. **One repo or two?** Keep gallery + docs in `starling`, or split docs into a new `decoder-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 mux/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.minDecoderVersion` exists (`manifest_v2.go:20`), but do we also need per-asset compat (e.g., an MCP server needing a specific transport decoder 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 `~/.decoder/`. What are the canonical install dirs for each asset kind? +6. **Domain & hosting:** confirm `decoderskills.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,14 +342,14 @@ 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, `rho 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, `decoder 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** | `rho ext update`, `rho ext verify`, atomic replace | **2 ew** | -| **P2 — publisher self-service** | `rho ext publish` scaffolder + validators, PR automation | **3 ew** | +| **P1 — CLI update/verify** | `decoder ext update`, `decoder ext verify`, atomic replace | **2 ew** | +| **P2 — publisher self-service** | `decoder 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** | flux/shrike/swift extension kinds + federated index | **3–4 ew** | +| **P2 — cross-repo kinds** | mux/shrike/swift extension kinds + federated index | **3–4 ew** | | **Cross-cutting** | docs writing (content, not framework), security review, CI, design iteration | **4–6 ew** | | **Total** | | **≈ 31–44 eng-weeks** (~7.5–11 eng-months; 2 engineers ~4–5 calendar months) | @@ -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` -- `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` +- `decoder/internal/plugin/registry.go:18,21,36,60,196,201,215,289,309` +- `decoder/internal/plugin/manifest_v2.go:11,18,20,32,42` +- `decoder/internal/plugin/skill_loader.go:51` +- `decoder/internal/plugin/malware_check.go:19` +- `decoder/internal/plugin/audit.go:12` +- `decoder/internal/tool/skill.go:62,91` +- `decoder/internal/plugin/` rule discovery +- `decoder/cmd/skills_cmd.go:16-211` +- `decoder/internal/mcp/mcp.go`, `decoder/internal/tool/mcp_tool.go` - `TOP20_COMPARISON.md:65,74,83,181,227,232,240,241` diff --git a/docs/design/three-repo-architecture.md b/docs/design/three-repo-architecture.md index 290fd04d8..f0bb84e6e 100644 --- a/docs/design/three-repo-architecture.md +++ b/docs/design/three-repo-architecture.md @@ -8,12 +8,12 @@ proposal. See also `gateway-architecture.md` (human-readable version), ```mermaid flowchart LR - subgraph CLI["rho"] + subgraph CLI["decoder"] TUI["chat · exec · review\nagent loop · sessions"] - ENG["flux engine\n(compiled in)"] + ENG["mux engine\n(compiled in)"] end - subgraph ROUTER["flux"] + subgraph ROUTER["mux"] LIB["engine library\n(router, adapters)"] SRV["HTTP server (skeleton)\ninternal/api + openapi.yaml"] end @@ -32,18 +32,18 @@ Wires, with evidence: | From → To | Mechanism | Evidence | |---|---|---| -| CLI → Router | Go module dep, in-process construction | `go.mod` requires `github.com/GrayCodeAI/flux`; `internal/provider/gateway/gateway.go` is the sole importer | +| CLI → Router | Go module dep, in-process construction | `go.mod` requires `github.com/GrayCodeAI/mux`; `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 rho refs | +| Router → CLI | none | router `go.mod`/`go.sum` contain zero decoder refs | | Router ↔ Platform | none | no imports, package deps, or API calls either direction | ## Target state (proposal) ```mermaid flowchart LR - CLI2["rho\n(default: in-process)"] -. "opt-in --model-gateway" .-> GW + CLI2["decoder\n(default: in-process)"] -. "opt-in --model-gateway" .-> GW WEB2["platform web/BFF"] -->|per-user keys| GW - subgraph GW["flux: common service"] + subgraph GW["mux: common service"] API["OpenAI-compatible API\n/v1/chat/completions + SSE"] POL["routing · retry · guardrails\nspend tracking"] end diff --git a/docs/ecosystem-message-flow.md b/docs/ecosystem-message-flow.md index 43bf54819..963387331 100644 --- a/docs/ecosystem-message-flow.md +++ b/docs/ecosystem-message-flow.md @@ -1,11 +1,11 @@ -# Message flow (flux + embedded token engine) +# Message flow (mux + embedded token engine) -How one user message travels through rho. +How one user message travels through decoder. ## Overview ``` -User prompt (TUI or rho exec) +User prompt (TUI or decoder exec) │ ▼ ┌───────────────────┐ @@ -14,7 +14,7 @@ User prompt (TUI or rho exec) │ ▼ ┌───────────────────┐ ┌─────────────┐ -│ local memory │◄────│ ~/.rho/ │ conventions, decisions, lessons +│ local memory │◄────│ ~/.decoder/ │ conventions, decisions, lessons │ recall │ │ memories │ └─────────┬─────────┘ └─────────────┘ │ @@ -26,7 +26,7 @@ User prompt (TUI or rho exec) │ ▼ ┌───────────────────┐ ┌─────────────┐ -│ Rho ChatClient │────►│ flux/engine│ catalog, credentials, routing +│ Decoder ChatClient │────►│ mux/engine│ catalog, credentials, routing │ port + adapter │ │ generate/ │────► provider API └─────────┬─────────┘ │ stream │ └─────────────┘ @@ -40,27 +40,27 @@ User prompt (TUI or rho exec) ▼ (when context grows) ┌───────────────────┐ │ token Compress │ fast path before LLM summarization -│ + flux compact │ +│ + mux compact │ └───────────────────┘ ``` ## Step by step -### 1. Session start (`rho` or `rho exec`) +### 1. Session start (`decoder` or `decoder exec`) -- **flux**: The Rho composition root creates an `flux/engine.Engine` with - Flux-owned state paths, an injected secret store, and per-engine custom +- **mux**: The Decoder composition root creates an `mux/engine.Engine` with + Mux-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 Rho's `ChatClient` port. + builds transport behind Decoder's `ChatClient` port. - **memory**: `configureSession` initializes the local memory manager (core, auto, evolving, zen, retrieval metrics, continuity). If the state - directory is unavailable, rho runs without persistent memory. + directory is unavailable, decoder runs without persistent memory. - **token**: No startup step — embedded at compile time. ### 2. System prompt assembly -- Rho templates (`internal/prompts/templates/*.md`) define behavior, tools, and practices. -- Project `AGENTS.md` is appended via `rhoconfig.BuildContextWithDirs`. +- Decoder templates (`internal/prompts/templates/*.md`) define behavior, tools, and practices. +- Project `AGENTS.md` is appended via `decoderconfig.BuildContextWithDirs`. - **memory**: `Memory.Recall` injects relevant stored memories into the system prompt. ### 3. User message → agent loop (`internal/engine/stream.go`) @@ -68,8 +68,8 @@ User prompt (TUI or rho exec) Each turn: 1. **memory** — recall memories matching the latest user message (token budget ~2000). -2. **flux** — Rho's adapter calls engine generate/stream with Rho-owned tool - definitions; Flux normalizes provider events and tool requests. +2. **mux** — Decoder's adapter calls engine generate/stream with Decoder-owned tool + definitions; Mux 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. **flux** — if token reduction is insufficient, rho calls the LLM to summarize, then keeps recent messages. +2. **mux** — if token reduction is insufficient, decoder calls the LLM to summarize, then keeps recent messages. ### 5. Token accounting @@ -88,21 +88,21 @@ When messages exceed limits (`internal/engine/compact.go`): ## Verify locally ```bash -rho doctor # ecosystem panel + flux preflight -./scripts/smoke-rho.sh # build + quick tests +decoder doctor # ecosystem panel + mux preflight +./scripts/smoke-decoder.sh # build + quick tests ``` ## Module layout -| Module | Role in rho | Required? | +| Module | Role in decoder | Required? | |--------|----------------|-----------| -| **flux** | LLM APIs, catalog, credentials, routing | Yes | +| **mux** | LLM APIs, catalog, credentials, routing | Yes | | **internal/token** | Token estimate + context compression + secrets + usage | Yes (embedded, no config) | -`flux` is an independent sibling checkout; the parent `go.work` wires the +`mux` is an independent sibling checkout; the parent `go.work` wires the local Go workspace. -Production Rho code imports Flux only through `flux/engine`. Conversation -history, WAL/resume, permissions, tool execution, and memory remain in Rho; +Production Decoder code imports Mux only through `mux/engine`. Conversation +history, WAL/resume, permissions, tool execution, and memory remain in Decoder; provider credentials, discovery, selection, transport, resilience, and -normalized streaming remain in Flux. \ No newline at end of file +normalized streaming remain in Mux. \ No newline at end of file diff --git a/docs/ecosystem-remediation-plan.md b/docs/ecosystem-remediation-plan.md index e594bc66f..78454dc46 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 (rho + swift) +## 1. charmbracelet v1/v2 Dependency Duplication (decoder + swift) **Status:** ✅ Done (2026-07-11) -**Impact:** High — contributed ~20-30MB to rho binary size +**Impact:** High — contributed ~20-30MB to decoder binary size ### Outcome -- Rho and Swift imports migrated to `charm.land/*/v2` (`bubbles`, `bubbletea`, +- Decoder and Swift imports migrated to `charm.land/*/v2` (`bubbles`, `bubbletea`, `lipgloss`, `huh`, `glamour`). - Direct `github.com/charmbracelet/{bubbles,bubbletea,lipgloss}` requires removed - from rho/swift `go.mod`. + from decoder/swift `go.mod`. - Binary size gate tightened: **110MB → 80MB** (`make size-check`). -- Verified build: rho binary **~75 MB** (under gate) after migration. +- Verified build: decoder binary **~75 MB** (under gate) after migration. -Commits (rho): `2890c74` (migrate), `ccfa286` (API fixups), `7a42c1e` (size gate). +Commits (decoder): `2890c74` (migrate), `ccfa286` (API fixups), `7a42c1e` (size gate). ### Residual notes @@ -38,13 +38,13 @@ Commits (rho): `2890c74` (migrate), `ccfa286` (API fixups), `7a42c1e` (size gate (`replace github.com/GrayCodeAI/harrier => ../..`). - Core `github.com/GrayCodeAI/harrier` no longer requires `charmbracelet/{bubbles,bubbletea,lipgloss}`. -- Rho embeds only the library packages (`engine`, `storage`, `graph`, …), so - the default rho binary does not pull the demo TUI module. +- Decoder embeds only the library packages (`engine`, `storage`, `graph`, …), so + the default decoder 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 Rho's `go.mod` must resolve to a +- The local `harrier` (Harrier) checkout and Decoder'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 `rho` fetch the registry from a CDN or GitHub +3. **Runtime fetch:** Have `decoder` fetch the registry from a CDN or GitHub raw URL rather than embedding it. ### Risk -If `registry.json` is read offline by rho, removing it requires adding a -fetch-or-cache mechanism. Verify how rho consumes the registry before +If `registry.json` is read offline by decoder, removing it requires adding a +fetch-or-cache mechanism. Verify how decoder consumes the registry before removing from git. --- diff --git a/docs/intelligent-cli.md b/docs/intelligent-cli.md index 46f0a55c2..06f29ad2c 100644 --- a/docs/intelligent-cli.md +++ b/docs/intelligent-cli.md @@ -1,6 +1,6 @@ # Intelligent CLI capabilities -Rho keeps the startup tool schema small while making the full capability +Decoder 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 -rho tools -rho tools --json +decoder tools +decoder 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 18f47b390..f606cd49a 100644 --- a/docs/mcp-servers.md +++ b/docs/mcp-servers.md @@ -1,10 +1,10 @@ # MCP Server Configuration -rho supports connecting to external MCP (Model Context Protocol) servers to extend its capabilities with additional tools, resources, and prompts. +decoder 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: `~/.rho/settings.json`, project: `.rho/settings.json`). +MCP servers are configured in `settings.json` (global: `~/.decoder/settings.json`, project: `.decoder/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 rho settings +# Add to decoder settings ``` **settings.json:** @@ -78,16 +78,16 @@ npm install -g my-jogyo ```bash # Add an MCP server -rho mcp add [args...] +decoder mcp add [args...] # List configured servers -rho mcp list +decoder mcp list # Remove a server -rho mcp remove +decoder mcp remove # Test a server connection -rho mcp test +decoder mcp test ``` ## Troubleshooting @@ -98,8 +98,8 @@ rho mcp test - Run the command manually to check for errors **Tools not appearing:** -- Restart rho after adding a new server -- Check `rho mcp test ` for connection errors +- Restart decoder after adding a new server +- Check `decoder 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 5f6499051..478170c3a 100644 --- a/docs/monitoring-guide.md +++ b/docs/monitoring-guide.md @@ -1,6 +1,6 @@ # Monitoring Guide -This guide covers how to monitor rho's daemon and CLI for production health, +This guide covers how to monitor decoder's daemon and CLI for production health, performance, and security. ## 1. Daemon Health & Readiness @@ -18,7 +18,7 @@ curl -sf http://localhost:4590/v1/health ### `GET /v1/ready` — Readiness probe Returns 200 when the daemon is fully ready to serve traffic (session factory -configured, Flux preflight checks pass). Returns 503 with the failed +configured, Mux preflight checks pass). Returns 503 with the failed dependency during startup or when dependencies are unavailable. ```bash @@ -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: $RHO_DAEMON_API_KEY" \ +curl -H "X-API-Key: $DECODER_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: $RHO_DAEMON_API_KEY" \ +curl -H "X-API-Key: $DECODER_DAEMON_API_KEY" \ http://localhost:4590/v1/metrics ``` @@ -51,9 +51,9 @@ Available metrics: | Metric | Type | Description | |--------|------|-------------| -| `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 | +| `decoder_daemon_active_sessions` | gauge | Number of active daemon sessions | +| `decoder_daemon_chat_concurrency_used` | gauge | Number of in-use chat concurrency slots | +| `decoder_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 RHO_ENABLE_TELEMETRY=1 +export DECODER_ENABLE_TELEMETRY=1 export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 -rho daemon start +decoder 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: `rho`) and version. +- Set the service name (default: `decoder`) and version. ### Configuration | Environment Variable | Default | Description | |---------------------|---------|-------------| -| `RHO_ENABLE_TELEMETRY` | `0` | Set to `1` to enable OTP telemetry | +| `DECODER_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 | -| `RHO_OTEL_SHUTDOWN_TIMEOUT_MS` | `2000` | Shutdown timeout in milliseconds | +| `DECODER_OTEL_SHUTDOWN_TIMEOUT_MS` | `2000` | Shutdown timeout in milliseconds | ### OTel Conventions -rho follows the OpenTelemetry semantic conventions for traces. See +decoder 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 `~/.rho/state/daemon.log` by +The daemon writes structured SLOG logs to `~/.decoder/state/daemon.log` by default. Control the log level via: ```bash -rho daemon start --log-level DEBUG +decoder 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 `~/.rho/state/securitylog/security_events.jsonl`. +tamper-evident log at `~/.decoder/state/securitylog/security_events.jsonl`. Verify the audit log integrity: ```bash # The securitylog package provides a verify command -go run ./cmd/rho securitylog verify +go run ./cmd/decoder securitylog verify ``` ## 5. Prometheus Scraping @@ -144,14 +144,14 @@ go run ./cmd/rho securitylog verify apiVersion: v1 kind: Service metadata: - name: rho-daemon + name: decoder-daemon annotations: prometheus.io/scrape: "true" prometheus.io/port: "4590" prometheus.io/path: "/v1/metrics" spec: selector: - app: rho-daemon + app: decoder-daemon ports: - port: 4590 targetPort: 4590 @@ -159,18 +159,18 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: rho-daemon + name: decoder-daemon spec: template: spec: containers: - - name: rho - image: ghcr.io/graycodeai/rho-daemon:latest + - name: decoder + image: ghcr.io/graycodeai/decoder-daemon:latest env: - - name: RHO_DAEMON_API_KEY + - name: DECODER_DAEMON_API_KEY valueFrom: secretKeyRef: - name: rho-secret + name: decoder-secret key: api-key ports: - containerPort: 4590 @@ -192,26 +192,26 @@ spec: | Alert | Condition | Severity | |-------|-----------|----------| -| Daemon down | `rho_daemon_uptime_seconds` does not increase for 2+ minutes | critical | +| Daemon down | `decoder_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 | `rho_daemon_chat_concurrency_used` sustained at max for 5+ minutes | warning | -| No active sessions | `rho_daemon_active_sessions` = 0 during business hours | warning | +| High concurrency | `decoder_daemon_chat_concurrency_used` sustained at max for 5+ minutes | warning | +| No active sessions | `decoder_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 rho-daemon -f +journalctl -u decoder-daemon -f ``` The daemon also writes its own structured log to -`~/.rho/state/daemon.log`: +`~/.decoder/state/daemon.log`: ```bash -tail -f ~/.rho/state/daemon.log +tail -f ~/.decoder/state/daemon.log ``` ## 8. Feature Flags @@ -220,7 +220,7 @@ Feature flags allow runtime configuration without restarts. They are controlled via environment variables: ```bash -export RHO_FEATURE_=1 +export DECODER_FEATURE_=1 ``` | Flag | Default | Description | @@ -234,5 +234,5 @@ export RHO_FEATURE_=1 List all registered flags: ```bash -rho features +decoder features ``` diff --git a/docs/operations-checklist.md b/docs/operations-checklist.md index c979c7089..5d9c931ac 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 rho in a production +Use this checklist when deploying or upgrading decoder in a production environment. Each item links to the relevant configuration option or documentation section. ## Pre-Deployment -- [ ] **API key is set** — `RHO_DAEMON_API_KEY` environment variable is +- [ ] **API key is set** — `DECODER_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 RHO_DAEMON_API_KEY=$(openssl rand -base64 32) + export DECODER_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 Rho bound to loopback or + flags. If a reverse proxy terminates TLS, keep Decoder 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 - `RHO_DAEMON_MAX_CONCURRENT`. + `DECODER_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 `~/.rho/state/daemon.log`. +- [ ] **Log retention** — Daemon logs at `~/.decoder/state/daemon.log`. Configure log rotation (logrotate, journald retention) to prevent disk exhaustion. -- [ ] **State directory backed up** — The `~/.rho/state/` directory contains +- [ ] **State directory backed up** — The `~/.decoder/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 `RHO_ENABLE_TELEMETRY=1` +- [ ] **Telemetry enabled (optional)** — Set `DECODER_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: 'rho-daemon' - bearer_token: '' + - job_name: 'decoder-daemon' + bearer_token: '' static_configs: - - targets: ['rho-daemon:4590'] + - targets: ['decoder-daemon:4590'] metrics_path: '/v1/metrics' ``` - [ ] **Health/readiness probes** — Configure in your orchestrator: @@ -62,24 +62,24 @@ documentation section. ## Security Hardening - [ ] **API key rotated** — The API key is written to a pinned file at - `~/.rho/state/daemon.key` for convenience. **Remove this file in + `~/.decoder/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/rho securitylog verify + go run ./cmd/decoder securitylog verify ``` - [ ] **Security headers** — Verify `X-Content-Type-Options`, `X-Frame-Options`, and `Content-Security-Policy` headers are present (enabled by default via the `security-headers` feature flag). -- [x] **Execution model** — Rho runs tools directly on the host under its +- [x] **Execution model** — Decoder runs tools directly on the host under its permission, path-safety, trust, and worktree controls. Docker/Podman is not required and is not an execution backend for the CLI. - [ ] **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/rho # should say "not a dynamic executable" + ldd /usr/local/bin/decoder # 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: $RHO_DAEMON_API_KEY" http://localhost:4590/v1/metrics + curl -H "X-API-Key: $DECODER_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 `~/.rho/state/` to a safe location. +1. **Back up state** — Copy `~/.decoder/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 `rho daemon start` +3. **Install new binary** — Replace the binary and run `decoder 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-RHO-ECO-PORT-PLAN.md b/docs/plans/FULL-GROK-ECO-TO-DECODER-ECO-PORT-PLAN.md similarity index 74% rename from docs/plans/FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md rename to docs/plans/FULL-GROK-ECO-TO-DECODER-ECO-PORT-PLAN.md index bb04c0333..ccea97bcc 100644 --- a/docs/plans/FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md +++ b/docs/plans/FULL-GROK-ECO-TO-DECODER-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 rho into a Rust monorepo. +**Not meaning:** Copy Rust crates, depend on Grok binaries, or collapse decoder 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,10 +18,10 @@ ## 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 rho / engines / contracts / cloud / skills. -3. **Prefer wire-first** — Rho already has partial types; complete wiring before greenfield. +2. **Go multi-repo stays** — map Grok crates into decoder / engines / contracts / cloud / skills.
+3. **Prefer wire-first** — Decoder 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 **flux**, not a single-vendor clone. +5. **Multi-provider stays** — Grok sampler/auth maps to **mux**, not a single-vendor clone.
6. **Memory stays harrier** — Grok markdown memory maps to **harrier APIs + UX**, not a second store. 7. **Tokens stay shrike** — Grok `xai-token-estimation` is obsolete relative to shrike. 8. **Track everything** — even 70-line crates appear in the matrix (Done / Partial / Port / Skip / N/A). @@ -67,9 +67,9 @@ Critical path remains **months**; **complete parity** including TUI polish and e ```text grok-eco/grok-build (one Rust workspace) │ - ├─► rho product CLI/TUI/engine/tools/hooks/plugins/ACP + ├─► decoder product CLI/TUI/engine/tools/hooks/plugins/ACP ├─► eagle pure DTOs (tool/spawn/hooks/policy) - ├─► flux LLM routing/stream/retry/catalog/auth credentials + ├─► mux LLM routing/stream/retry/catalog/auth credentials ├─► harrier memory graph + dream UX ├─► shrike compression / secrets / token cost ├─► swift session capture/import/replay @@ -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 | 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-pager` | 415k | Full TUI | decoder `cmd/` | Partial | XXL | Bubble Tea already; parity of panes/modals/slash is multi-year | +| `xai-grok-pager-render` | 35k | Render pipeline | decoder | Partial | L | Streaming render, blocks, media | +| `xai-grok-pager-minimal` | 5.6k | `--minimal` native scrollback | decoder | Partial | M | `decoder --repl` / print modes exist | +| `xai-grok-pager-bin` | 2.9k | Binary composition root | decoder `cmd/decoder` | Done | — | | +| `xai-grok-pager-pty-harness` | 10k | PTY test harness | decoder tests | Partial | M | Test infra | +| `xai-grok-shell` | 336k | Agent runtime host | decoder `internal/engine` | Partial | XXL | Largest runtime port | +| `xai-grok-shell-base` | 2.7k | Shared shell foundation | decoder | Partial | S | Absorb | +| `xai-grok-shell-session-support` | 1.5k | Session support extract | decoder `session` | Partial | S | Absorb | +| `xai-grok-tools` | 112k | Tool implementations | decoder `internal/tool` | Partial | XL | See tool matrix §3 | +| `xai-grok-tools-api` | 0.7k | Tool API / slash wording | decoder + contracts | Partial | S | | +| `xai-grok-workspace` | 77k | FS/VCS/permissions/hub | decoder + 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 | 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 | **flux** | Done* | — | Different shape; flux owns | -| `xai-grok-sampler` | 11k | HTTP stream + retry | **flux** | Done* | — | Do not replace flux | -| `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-workspace-client` | 0.8k | Workspace RPC client | decoder (if hub) | Port | M | Only if computer-hub ported | +| `xai-grok-agent` | 21k | Agent defs + system prompt | decoder agent/personas | Partial | L | | +| `xai-grok-subagent-resolution` | 2.6k | Capability/isolation resolve | decoder + contracts | Port | M | Critical | +| `xai-chat-state` | 13k | Chat state actor | decoder session/engine | Partial | L | | +| `xai-grok-markdown` | 22k | Streaming MD TUI | decoder markdown | Partial | L | | +| `xai-grok-markdown-core` | 1.1k | Headless MD analysis | decoder | Partial | S | | +| `xai-ratatui-textarea` | 12k | Input widget | decoder (Bubble Tea) | N/A | — | Different UI stack | +| `xai-ratatui-inline` | 3.7k | Inline render | decoder | N/A | — | UI stack | +| `xai-fast-worktree` | 19k | CoW worktree speed | decoder worktree | Port | L | Perf enhancement | +| `xai-file-utils` | 15k | Event tracking / upload | decoder observability | Partial | M | Privacy opt-in | +| `xai-grok-telemetry` | 14k | Events + OTEL + Mixpanel + Sentry | decoder observability | Partial | L | **Skip Mixpanel default**; keep OTEL | +| `xai-hunk-tracker` | 13k | Agent vs external hunks | decoder | Port | L | | +| `xai-grok-sampling-types` | 13k | Chat API types | **mux** | Done* | — | Different shape; mux owns | +| `xai-grok-sampler` | 11k | HTTP stream + retry | **mux** | Done* | — | Do not replace mux | +| `xai-grok-update` | 11k | Auto-update | decoder | Partial | M | | +| `xai-grok-mcp` | 10k | MCP client (oauth, wire) | decoder `mcp` | Partial | L | | +| `xai-codebase-graph` | 9.7k | Tree-sitter graph | decoder 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 | 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-hooks` | 8.3k | File/HTTP hooks | decoder hooks | Port | L | | +| `xai-fsnotify` | 6.7k | FS events | decoder (fsnotify) | Partial | S | | +| `xai-grok-config` | 6k | Config layers + managed | decoder config + cloud | Port | L | | | `xai-grok-config-types` | 2.7k | Config DTOs | contracts/config | Partial | 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 host permissions | 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-grok-plugin-marketplace` | 5.3k | Marketplace | decoder + community-skills | Port | L | | +| `xai-grok-shared` | 5.2k | Shared utils | decoder | N/A | — | Absorb | +| `xai-grok-test-support` | 4.6k | Test harness | decoder testutil | Partial | M | | +| `xai-grok-sandbox` | 3.9k | OS sandbox profiles | decoder host permissions | Port | L | | +| `xai-grok-voice` | 2.7k | Streaming STT | decoder | Partial | M | whisper path exists | +| `xai-acp-lib` | 2.3k | ACP protocol | decoder acp | Port | L | | +| `xai-grok-mermaid` | 2.2k | Mermaid→PNG | decoder | Port | M | | +| `xai-crash-handler` | 1.9k | Crash + startup detect | decoder | Port | S | | +| `ptyctl` | 2.3k | Headless PTY control | decoder | Port | M | | +| `ptyctl-cli` | 0.8k | PTY CLI | decoder tests/tools | Optional | S | | +| `xai-tty-utils` | 1.2k | TTY-safe spawn | decoder | Port | S | | | `xai-hooks-plugins-types` | 1.2k | Hooks/plugins ACP DTOs | contracts | Port | S | | -| `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 + flux | Partial | S | | -| `xai-grok-announcements` | 0.4k | Release announcements | rho tips/notify | Port | S | | -| `xai-grok-auth` | 0.4k | Auth seam | flux + rho auth | Partial | M | Browser OAuth optional | +| `xai-sqlite-journal` | 0.8k | SQLite journal mode | decoder/harrier/shrike | Partial | XS | | +| `xai-system-power` | 0.7k | Sleep/wake notify | decoder sleep_prevent | Partial | S | | +| `xai-grok-http` | 0.6k | Shared HTTP client | decoder/netutil | Partial | XS | | +| `xai-agent-lifecycle` | 0.6k | Lifecycle hooks data | decoder hooks/engine | Partial | S | | +| `xai-gix-status` | 0.6k | Fast git status | decoder git tools | Partial | S | | +| `xai-grok-paths` | 0.6k | AbsPath types | decoder | N/A | XS | Go path.Clean enough | +| `xai-grok-secrets` | 0.6k | Secrets helpers | shrike + mux | Partial | S | | +| `xai-grok-announcements` | 0.4k | Release announcements | decoder tips/notify | Port | S | | +| `xai-grok-auth` | 0.4k | Auth seam | mux + decoder 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 | rho observability | N/A | XS | | -| `xai-grok-env` | 0.2k | Backend env presets | flux/rho | Partial | XS | | -| `xai-prompt-queue` | 0.2k | Prompt queue types | rho | Port | S | | +| `xai-tracing-macros` | 0.2k | Log macros | decoder observability | N/A | XS | | +| `xai-grok-env` | 0.2k | Backend env presets | mux/decoder | Partial | XS | | +| `xai-prompt-queue` | 0.2k | Prompt queue types | decoder | Port | S | | | `xai-mixpanel` | 0.1k | Mixpanel client | — | **Skip** | — | Privacy; OTEL opt-in | -| `xai-grok-version` | 0.1k | Version | rho VERSION | Done | — | | -| `xai-grok-models` | 0.1k | Default model IDs | flux catalog | Done* | — | | +| `xai-grok-version` | 0.1k | Version | decoder VERSION | Done | — | | +| `xai-grok-models` | 0.1k | Default model IDs | mux 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 rho | Port (Y2+) | XL | Only if multi-host tools wanted | +| `xai-computer-hub-sdk` | 14k | Remote tool server SDK | new or decoder | 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 + rho | Port (Y2) | L | | -| `xai-tool-runtime` | 5.4k | Tool trait runtime | rho tool | Partial | L | | +| `xai-tool-protocol` | 6.6k | Wire protocol | contracts + decoder | Port (Y2) | L | | +| `xai-tool-runtime` | 5.4k | Tool trait runtime | decoder tool | Partial | L | | | `xai-tool-types` | 3.6k | Spawn/task types | **contracts** | Port | M | **P0** | -| `xai-grok-compaction` | 6.8k | Compaction engine | rho engine + shrike | Partial | L | | -| `xai-circuit-breaker` | 2.2k | HTTP breaker | flux/resilience | Partial | S | | -| `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-grok-compaction` | 6.8k | Compaction engine | decoder engine + shrike | Partial | L | | +| `xai-circuit-breaker` | 2.2k | HTTP breaker | mux/resilience | Partial | S | | +| `xai-tracing` | 0.8k | Tracing | decoder OTEL | Partial | S | | +| `xai-test-utils` | 0.4k | Hermetic git tests | decoder testutil | Partial | S | | +| `xai-interjection-core` | 0.3k | Interjection messaging | decoder | Port | S | Mid-turn user inject | | `xai-proto-build` | — | Build tooling | — | Skip | — | Rust build | -\*Done* = domain covered by rho engine with different API. +\*Done* = domain covered by decoder engine with different API. --- @@ -180,7 +180,7 @@ grok-eco/grok-build (one Rust workspace) ### 3.1 Grok Build native tools -| Grok tool | Rho equivalent | Status | Port work | +| Grok tool | Decoder 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 | Rho | Action | +| Compat surface | Decoder | Action | |----------------|------|--------| -| 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 | +| Codex apply_patch / read / list / grep | decoder tools | Optional compat profile | +| OpenCode read/write/edit/bash/glob/grep/todo/skill | decoder | Optional `compat.opencode` | +| Claude import | missing | **Port** into swift/decoder | | Cursor skills scan | missing | **Port** | --- ## 4. Slash commands & UX (pager) -Grok slash modules (port checklist → rho `/` commands or CLI): +Grok slash modules (port checklist → decoder `/` commands or CLI): -| Grok slash | Status in rho | Action | +| Grok slash | Status in decoder | Action | |------------|----------------|--------| | help | Partial | Port completeness | | model / effort | Partial | Port effort levels | @@ -259,17 +259,17 @@ Grok slash modules (port checklist → rho `/` commands or CLI): --- -## 5. User-guide docs (port as rho user-guide) +## 5. User-guide docs (port as decoder user-guide) | Grok doc | Port to | |----------|---------| -| 01–05 essentials | `rho/docs/user-guide/` | +| 01–05 essentials | `decoder/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 | flux + rho | +| 11 custom models | mux + decoder | | 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 → rho `/` commands or CLI): These are “small” crates/modules but required for **full** port claims: -| Item | Grok home | Rho action | Effort | +| Item | Grok home | Decoder 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 rho swift | S | +| Swift classifier | shell | map to decoder 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, 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 | +| **Q1** | Contracts spawn DTOs; wire explore/plan/general; stop hardcoded explore; explore bash hard gate; unify taskruntime; structured SpawnResult | contracts, decoder | +| **Q2** | sandbox.toml; folder trust; safe-bash; permission pipeline hooks-first; PreToolUse deny | decoder | +| **Q3** | File+HTTP hooks; vendor aliases; multi-harness skills; multi-component plugins; marketplace MVP | decoder, community-skills | +| **Q4** | Monitor/Wait/Kill; /loop; structured AskUser; plan/spec alignment; user-guide 01–12; crash handler; announcements; prompt queue; interjection | decoder | **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 → rho; IT tier; config layer order | +| **Q6** | Managed policy (signed) graycode-cloud → decoder; 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 rho issues +- Diff audit bot: scan Grok public releases → open decoder 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:** rho can import types; no engine deps. +**DoD:** decoder can import types; no engine deps. ### PACK-02: Spawn control plane (6–8 weeks) -**Repo:** `rho` +**Repo:** `decoder` - [ ] 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 RHO_PLUGIN_ROOT/DATA +- [ ] Plugin env DECODER_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) -- [ ] rho apply layers +- [ ] decoder 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 rho Bubble Tea: +Track Grok pager features against decoder 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 -### `rho` (majority) +### `decoder` (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. @@ -553,7 +553,7 @@ Port/finish: spawn, tools, sandbox, trust, hooks, plugins, marketplace client, T Port: spawn/capability/isolation types, hook events, optional sandbox policy DTO, tool protocol types (later). -### `flux` +### `mux` Absorb: sampling/stream/retry/circuit-breaker lessons; catalog already ahead; managed deployment hooks; **do not** become Grok-only auth. @@ -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 rho composition matches Grok “review/merlin” product moments if any. +No direct Grok crates; keep peer engines; ensure decoder composition matches Grok “review/merlin” product moments if any. ### `falcon` @@ -605,10 +605,10 @@ Dashboard/usage/marketplace web UI only. | SpaceXAI-only browser auth as sole path | Multi-provider + keychain | | `xai-token-estimation` | shrike wins | | Replace harrier with markdown memory files | Graph better | -| Replace flux with xai-grok-sampler | Multi-provider better | +| Replace mux with xai-grok-sampler | Multi-provider better | | ratatui widgets | Bubble Tea stack | | `xai-proto-build` | Rust build | -| Closed contribution policy | Rho is open | +| Closed contribution policy | Decoder is open | Skipping is **documented completion**, not unfinished work. @@ -650,7 +650,7 @@ Declare complete when: 6. Folder trust + sandbox deny globs pass security tests on macOS+Linux. 7. Marketplace can install a multi-component plugin. 8. Subagent explore/plan/general + resume + worktree isolation work. -9. No second memory/token/provider stack duplicated against harrier/shrike/flux. +9. No second memory/token/provider stack duplicated against harrier/shrike/mux. 10. Continuous parity process (PACK-17) running for 2 releases. --- @@ -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 Rho’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 Decoder’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 0677beccc..f810e85fe 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:** `rho` -**Supporting repositories:** `eagle`, `flux`, `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) +**Primary product:** `decoder`
+**Supporting repositories:** `eagle`, `mux`, `harrier` (Harrier), `shrike` (Shrike), `swift` (Swift), `kestrel` (Kestrel), `merlin` (Merlin), `falcon`, `starling`, `sparrow`, `robin`, `wren`, `owl`, and `graycode-platform` (web/BFF/Decoder Cloud; outside the Decoder Go runtime graph) --- @@ -13,21 +13,21 @@ ### Goal -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 (flux multi-provider, harrier graph memory, shrike compression, mission mode, contracts, cloud ledger). +Bring Decoder 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 Decoder’s multi-repo Go platform advantages (mux multi-provider, harrier graph memory, shrike compression, mission mode, contracts, cloud ledger). ### Non-goals -- Rewrite Rho in Rust or monorepo-collapse engines into rho. +- Rewrite Decoder in Rust or monorepo-collapse engines into decoder. - Vendor lock-in to a single LLM host auth model. -- Replace flux/harrier/shrike/swift with Grok-shaped internal crates. +- Replace mux/harrier/shrike/swift with Grok-shaped internal crates. - Default opt-in product telemetry that violates privacy-first posture. ### Strategy -1. **Close wiring gaps first** — Rho already has many *types* and *partial systems* that are not exposed on the Agent tool or unified. +1. **Close wiring gaps first** — Decoder 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**; rho remains the only product orchestrator. +4. **Keep engines peer-independent**; decoder remains the only product orchestrator. --- @@ -110,7 +110,7 @@ This is the single highest-ROI fix: **wire what already exists**, then extend. ```text ┌──────────────────────────────┐ - │ rho TUI / CLI / daemon/ACP │ + │ decoder TUI / CLI / daemon/ACP │ └──────────────┬───────────────┘ │ ┌─────────────────────────┼─────────────────────────┐ @@ -130,10 +130,10 @@ This is the single highest-ROI fix: **wire what already exists**, then extend. │ ┌────┴────┬──────────┬──────────┬──────────┐ ▼ ▼ ▼ ▼ ▼ - flux harrier shrike swift kestrel/merlin + mux harrier shrike swift kestrel/merlin ``` -**New core package (proposed):** `rho/internal/spawn` (or expand `engine/agent`) owning: +**New core package (proposed):** `decoder/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 RHO_PLUGIN_ROOT/DATA | Missing | Full | +| C5 | Plugin env DECODER_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 rho core + fractional engine/cloud; scale parallelizes quarters. +Assumes ~1–2 senior Go engineers on decoder 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; rho can depend on new pseudo-version without engine imports. +- `go test ./...` green; decoder can depend on new pseudo-version without engine imports. --- ### Phase 2 — Spawn controller & Agent tool (4–6 weeks) ★ highest ROI -**Repo:** `rho` +**Repo:** `decoder`
**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 `.rho/worktrees/` using shared helper from mission worker | -| 6 | Persist child session transcript under `~/.rho/subagents//` | +| 5 | Isolation worktree: create under `.decoder/worktrees/` using shared helper from mission worker | +| 6 | Persist child session transcript under `~/.decoder/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 rho +1. Contracts bump in decoder
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:** `rho` +**Repo:** `decoder`
**Packages:** `internal/sandbox`, `internal/trust` (new), `internal/mcp`, `internal/hooks`, `internal/plugin` #### 3.1 sandbox.toml ```toml -# ~/.rho/sandbox.toml +# ~/.decoder/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 `.rho/sandbox.toml` **additive only** (new names only). +2. Load project `.decoder/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:** `~/.rho/trusted_folders.toml` +**Store:** `~/.decoder/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:** `rho` +**Repo:** `decoder`
**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 RHO_* | +| command | shell with timeout, env DECODER_* | | 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 -- `~/.rho/hooks/*.json` -- `.rho/hooks/*.json` (trust required) +- `~/.decoder/hooks/*.json`
+- `.decoder/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:** `rho`, `starling` +**Repos:** `decoder`, `starling` #### 5.1 Plugin layout (convention) @@ -603,19 +603,19 @@ Loader merges components; tools optional. 1. Session meta / SDK 2. `--plugin-dir` -3. Project `.rho/plugins` (trust) -4. User `~/.rho/plugins` +3. Project `.decoder/plugins` (trust)
+4. User `~/.decoder/plugins`
5. Config extra paths #### 5.3 Marketplace | Component | Owner | |-----------|--------| -| 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 | +| Source list config | decoder | +| Index schema | community-skills + decoder | +| Install resolve (git) | decoder | +| Audit | decoder plugin malware_check (extend) | +| CLI `decoder plugins` / TUI tab | decoder | | 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 `.rho/plans/` or specs | +| Plan subagent | Default tool for “design only”; writes plan file under `.decoder/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:** `rho`, `sparrow`, `robin` +**Repos:** `decoder`, `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 | `rho agent serve --bind` WS | +| Optional | `decoder agent serve --bind` WS | **Exit criteria** -- Zed/VS Code can drive rho ACP for multi-turn with permissions. +- Zed/VS Code can drive decoder 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`), `rho`, optional `flux` +**Repos:** `graycode-platform/apps/worker` (deployed as `graycode-cloud`), `decoder`, optional `mux` | Piece | Detail | |-------|--------| | Policy document | models allow/deny, capabilities, sandbox deny, max budget, tool denylist | -| Signing | Ed25519 envelope; rho verifies before apply | -| Apply path | `~/.rho/managed_policy.json` layers under user config | +| Signing | Ed25519 envelope; decoder verifies before apply | +| Apply path | `~/.decoder/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 + 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 | +| Foreign import | swift + decoder CLI | Claude/Codex session metadata → index | +| Hunk attribution | decoder | agent vs external edits via fsnotify | +| Memory UX | harrier + decoder | toggle priority, `/dream` consolidate | +| Mermaid optional | decoder | 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 | 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 | +| Spawn / capability / isolation | decoder | — | yes | +| Sandbox profiles | decoder | — | optional DTO | +| Folder trust | decoder | — | no | +| Hooks | decoder | — | event names yes | +| Plugins / marketplace | decoder | community-skills | manifest schema | +| Multi-harness skills | decoder | community-skills | no | +| Monitor/tasks | decoder | — | optional | +| ACP | decoder | sdk-go/python | OpenAPI | +| Managed policy | graycode-platform/apps/worker (deployed as `graycode-cloud`) | decoder, graycode-platform/apps/bff | yes DTO | +| Foreign import | swift | decoder | optional | +| Memory dream UX | harrier | decoder | no | | Token | shrike | — | no change | -| Providers | flux | rho | no change | -| Review engines | kestrel/merlin | rho | findings already | +| Providers | mux | decoder | no change | +| Review engines | kestrel/merlin | decoder | 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_rho | +| Signature change of AgentSpawnFn breaks plugins | High | Adapter release; version min_decoder | | 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 | |------|-------|-------|-------| -| Rho core (Go) | 1.5 FTE | 1.5 FTE | 1 FTE | +| Decoder 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 → Rho names +## 14. Appendix B — Mapping Grok names → Decoder names -| Grok | Rho | +| Grok | Decoder | |------|------| | `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` | `~/.rho/sandbox.toml` | -| folder trust | `~/.rho/trusted_folders.toml` | +| `sandbox.toml` | `~/.decoder/sandbox.toml` | +| folder trust | `~/.decoder/trusted_folders.toml` | | `/loop` | `/loop` over CronScheduler | -| plugin marketplace | `rho plugins` + community registry | +| plugin marketplace | `decoder plugins` + community registry | | managed_config | managed policy via graycode-cloud | -| ACP | `rho acp` | +| ACP | `decoder acp` | --- @@ -976,7 +976,7 @@ Week 4 | Trust default | On | Security | | Marketplace before trust? | No | Supply chain | | Replace harrier? | No | Graph memory superior | -| Replace flux? | No | Multi-provider is differentiator | +| Replace mux? | No | Multi-provider is differentiator | | Three bg systems | Unify early | Blocks monitor/wait | --- diff --git a/docs/plans/SPEC_DRIVEN_PHASE2_PLAN.md b/docs/plans/SPEC_DRIVEN_PHASE2_PLAN.md index 29e1dffa3..a290602c6 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 .rho/specs/ changes + - Stage .decoder/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 aaf9be48d..7f91cfb43 100644 --- a/docs/plans/YEAR-0-ACTIVE.md +++ b/docs/plans/YEAR-0-ACTIVE.md @@ -1,13 +1,13 @@ -# Year 0 Active Track (Grok → Rho) +# Year 0 Active Track (Grok → Decoder) > **Historical.** This track predates the removal of the harrier, kestrel, -> merlin, shrike, and swift engines. Rho now depends only on `flux` and +> merlin, shrike, and swift engines. Decoder now depends only on `mux` 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-RHO-ECO-PORT-PLAN.md](./FULL-GROK-ECO-TO-RHO-ECO-PORT-PLAN.md), +**Full matrices:** [FULL-GROK-ECO-TO-DECODER-ECO-PORT-PLAN.md](./FULL-GROK-ECO-TO-DECODER-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,8 +18,8 @@ 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 rho monorepo | -| Wire existing modes/budgets first | Rebuild flux/harrier/shrike as Grok clones | +| Map capabilities to graycode-eco repos | Collapse engines into decoder monorepo | +| Wire existing modes/budgets first | Rebuild mux/harrier/shrike as Grok clones | | Privacy-first telemetry (OTEL opt-in) | Port Mixpanel defaults | ## Pack status @@ -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 (`.rho/plugins`, `.rho/hooks`; MCP/LSP follow same AllowLoadPath) +- [x] Folder trust gates project hooks / plugins (`.decoder/plugins`, `.decoder/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 `rho/docs/user-guide/` (completed July 2026) +- [x] User-guide docs `01`–`24` under `decoder/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 | |-----|---------|---------| -| `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 | +| `DECODER_Y0_SPAWN_V2` | `1` once PACK-02 ships; `0` during dual path | Typed SpawnRequest path | +| `DECODER_Y0_FOLDER_TRUST` | `1` recommended after PACK-03 | Gate project automation | +| `DECODER_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 66ad9a4ae..500522875 100644 --- a/docs/plans/codex-adoption-plan.md +++ b/docs/plans/codex-adoption-plan.md @@ -1,9 +1,9 @@ # OpenAI Codex CLI Adoption Plan -Status: Audited. Core ideas already implemented natively in rho; remaining +Status: Audited. Core ideas already implemented natively in decoder; remaining deltas recorded as future RFCs. -> **Superseded runtime assumptions (2026-09-20):** Rho no longer ships a +> **Superseded runtime assumptions (2026-09-20):** Decoder no longer ships a > native OS/container sandbox or a Docker execution backend. The permission > engine, path guard, trust policy, and destructive-command hard blocks are > the current local safety boundary. Sandbox rows below are historical design @@ -14,9 +14,9 @@ Source: `https://github.com/openai/codex` (Apache-2.0, Rust workspace ## Executive Decision -The audit found that every codex-rs capability relevant to rho's security and +The audit found that every codex-rs capability relevant to decoder's security and runtime model already has a native Go implementation, several of them deeper -than codex's equivalents because they build on Rho's independent ecosystem +than codex's equivalents because they build on Decoder's independent ecosystem repositories. No second runtime, sandbox layer, or policy engine was created. @@ -27,18 +27,18 @@ Three codex ideas are deliberately deferred as future RFCs; see ## Capability Audit -| codex-rs crate/concept | rho implementation | Decision | +| codex-rs crate/concept | decoder implementation | Decision | |---|---|---| -| `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` | flux credential store in OS keychain across 28 providers | Keep rho (broader) | -| `model-provider(-info)`, `models-manager`, `ollama`, `lmstudio` | sibling `flux` 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 | +| `core` agent loop | `internal/engine` | Keep decoder | +| `tui`, `ansi-escape`, `terminal-detection` | Bubble Tea/Lipgloss TUI | Keep decoder | +| `rollout`, `thread-store`, `history` JSONL sessions with resume/fork | `internal/session` JSONL + WAL + named checkpoints + fork + recovery + handover | Keep decoder (richer) | +| `app-server-daemon`, `app-server-protocol` (JSON-RPC for IDE/desktop) | `internal/daemon` HTTP/SSE on 4590 + `internal/acp` | Keep decoder | +| `mcp-server`, `codex-mcp`, `rmcp-client`, `connectors` | `internal/mcp` client+server, sibling `falcon` scaffolding | Keep decoder | +| `skills`, `plugin`, `hooks` | community skill registry + structural validator, plugins, expanded lifecycle hook events | Keep decoder | +| `login`, `keyring-store`, `aws-auth` | mux credential store in OS keychain across 28 providers | Keep decoder (broader) | +| `model-provider(-info)`, `models-manager`, `ollama`, `lmstudio` | sibling `mux` adapters, catalog, cascade routing | Keep decoder (much broader) | +| `memories`, `agent-graph-store`, `context-fragments` | sibling `harrier` (Harrier) graph memory; eventlog/graphjournal projections | Keep decoder | +| `apply-patch`, `file-search`, `file-watcher`, `git-utils` | edit tools, codegraph, git tooling, watcher hooks | Keep decoder | | `external-agent-migration` | swift reads Claude Code / Codex / Gemini CLI / OpenCode / Cursor sessions | Parity | | **`linux-sandbox`** (Landlock + seccomp-bpf) | Removed from the local product runtime | Removed | | **macOS Seatbelt** | Removed from the local product runtime | Removed | @@ -68,19 +68,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`): rho already provides a +- **Agent identity signing** (`agent-identity`): decoder 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. - Rho Cloud already provides sync/review surfaces; a queue protocol would + Decoder 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`, `rho verify` green. +- `make vet`, `make lint`, `decoder 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 6f68896f1..15f5c5345 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 Rho working tree where the existing architecture +Status: Implemented in the Decoder 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 Rho skills | +| `awesome-agents` | Apache-2.0 example applications | Reference only; do not merge into Decoder 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 Rho already provides native equivalents +The Kimi Code comparison confirmed that Decoder 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`. -- Rho's existing permission engine already supports ordered allow/deny rules +- Decoder'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. -- Rho's existing goal tracker already provides durable objective state, +- Decoder'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 Rho's stronger Harrier memory, Shrike token controls, Swift replay, Kestrel -review, Merlin auditing, or Flux provider runtime. +replace Decoder's stronger Harrier memory, Shrike token controls, Swift replay, Kestrel +review, Merlin auditing, or Mux provider runtime. ### 1. Skill metadata interoperability -Rho's smart-skill parser accepts both hyphenated and community-schema +Decoder'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 -Rho's existing Unicode audit remains the runtime security scanner. A new +Decoder'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 -`rho skills audit` now reports both Unicode and structural findings. This is a +`decoder 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 -Rho already has `internal/features/taste` with confidence, sample count, +Decoder already has `internal/features/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. Rho now +CommandCodeAI's strongest product contribution is discoverability. Decoder now documents its existing capabilities in: - `docs/user-guide/26-learned-preferences.md` - `docs/user-guide/27-workflows.md` -- `docs/architecture/rho-harness.md` +- `docs/architecture/decoder-harness.md` - `docs/user-guide/28-workflow-budgets.md` These cover slash/shell/file-context input, headless review, MCP-backed @@ -111,22 +111,22 @@ tool, depth, time, token, and cost budgets. ## Deliberately Not Adopted -- CommandCodeAI provider adapters: Flux owns provider protocols and routing. -- BaseAI remote pipes: incompatible with Rho's local authority and durable +- CommandCodeAI provider adapters: Mux owns provider protocols and routing. +- BaseAI remote pipes: incompatible with Decoder's local authority and durable event model. -- BaseAI `lowdb` JSON memory: weaker than Harrier and Rho persistence. +- BaseAI `lowdb` JSON memory: weaker than Harrier and Decoder 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 Rho's code-intelligence boundary. +- Media/UI/status repositories: outside Decoder's code-intelligence boundary. - CommandCodeAI branding, proprietary model claims, and undocumented services. ## Verification Plan -1. Run formatting and static checks on all changed Rho Go files. +1. Run formatting and static checks on all changed Decoder Go files. 2. Run focused parser, validator, taste, engine, and command tests. -3. Run the full Rho test suite and vet. +3. Run the full Decoder test suite and vet. 4. Repeat the focused and full checks independently. 5. Merlin the final diff, worktree, and sibling-repository status. -The Flux repository remains a separate repository change and must be published -through its own feature branch and PR before updating Rho's module pin. +The Mux repository remains a separate repository change and must be published +through its own feature branch and PR before updating Decoder's module pin. diff --git a/docs/plans/competitive-gap-02-share-multisession.md b/docs/plans/competitive-gap-02-share-multisession.md index 543c1b782..687eb0440 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 rho capabilities (verified) +## Existing decoder capabilities (verified) -- `GenerateShareLink` returns local deeplink `rho://share/` (`internal/session/export.go:801-819`); deterministic, no hosted URL. +- `GenerateShareLink` returns local deeplink `decoder://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 `rho://share/` deeplink + export file path for copy-paste. +- P1: TUI session picker surfaces the `decoder://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 6293be2f3..f53710212 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 rho capabilities (verified) +## Existing decoder 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 09ed2c9bb..526d81142 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 rho capabilities (verified) +## Existing decoder 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 `RHO_TOOL_SHRINK=1`. +- P1: `docs/BENCHMARKS.md` table (TUI-independent, no latency theater): session save/load, repomap tokens, tool-catalog size before/after `DECODER_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 177ea64c2..1e741200e 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 `../flux`; rho consumes only the stable engine facade (`AGENTS.md`, `docs/SECURITY-DEVELOPER.md:51-56`). -- Boundary guards must stay green: `make boundaries` (incl. `flux-client-guard`, `flux-engine-guard`). +- Provider ownership lives in `../mux`; decoder consumes only the stable engine facade (`AGENTS.md`, `docs/SECURITY-DEVELOPER.md:51-56`). +- Boundary guards must stay green: `make boundaries` (incl. `mux-client-guard`, `mux-engine-guard`). - Tools fail safe today with explicit errors when unwired — preserve that behavior when disabled. -## Existing rho capabilities (verified) +## Existing decoder 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`). @@ -20,18 +20,18 @@ Constraints (non-negotiable): Adopt: opt-in host wiring through the router facade, env-gated, off by default. -Do not adopt: direct `flux/client` production imports, new secrets paths, always-on media/computer-use. +Do not adopt: direct `mux/client` production imports, new secrets paths, always-on media/computer-use. ## 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. `RHO_MEDIA=1`) + docs; unwired default error text unchanged. -- P2: `rho doctor` reports backend status (wired/unwired) without leaking secrets. +- P1: env-gated wiring (e.g. `DECODER_MEDIA=1`) + docs; unwired default error text unchanged. +- P2: `decoder doctor` reports backend status (wired/unwired) without leaking secrets. ## Steps -1. Confirm facade methods exist in sibling `../flux/engine`; if missing, file the change there first (router repo owns providers). -2. Implement wiring in rho behind env gates; keep `Set*` seams for tests. +1. Confirm facade methods exist in sibling `../mux/engine`; if missing, file the change there first (router repo owns providers). +2. Implement wiring in decoder 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/rho-contracts-migration-backlog.md b/docs/plans/decoder-contracts-migration-backlog.md similarity index 63% rename from docs/plans/rho-contracts-migration-backlog.md rename to docs/plans/decoder-contracts-migration-backlog.md index 34cc8ff62..26c9295d0 100644 --- a/docs/plans/rho-contracts-migration-backlog.md +++ b/docs/plans/decoder-contracts-migration-backlog.md @@ -1,8 +1,8 @@ -# Plan: Rho Contracts Migration Backlog +# Plan: Decoder Contracts Migration Backlog > Status: locally complete > Scope: graycode-eco ecosystem architecture cleanup after introducing `eagle` -> Goal: keep `rho` as the product while moving stable cross-repo contracts out of Rho internals +> Goal: keep `decoder` as the product while moving stable cross-repo contracts out of Decoder 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 Rho `internal/types/severity.go` to re-export from contracts -- removed `rho/shared/types` after local ecosystem migration +- switched Decoder `internal/types/severity.go` to re-export from contracts +- removed `decoder/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,15 +31,15 @@ These items are already completed in the current workspace. ### Tool contract migration - added `eagle/tools` -- switched Rho session persistence to provider-neutral tool contracts +- switched Decoder session persistence to provider-neutral tool contracts - added runtime/session conversion helpers - added `eagle/tools/tool_test.go` -- centralized message slice conversion in `rho/internal/session` -- removed direct lower-level provider message reconstruction from Rho +- centralized message slice conversion in `decoder/internal/session` +- removed direct lower-level provider message reconstruction from Decoder cmd/session restore paths -- replaced the provider-owned message alias with a Rho-owned runtime DTO -- added explicit Rho-owned runtime DTOs; the final boundary translates them - through `flux/engine` +- replaced the provider-owned message alias with a Decoder-owned runtime DTO +- added explicit Decoder-owned runtime DTOs; the final boundary translates them + through `mux/engine` ### Event contract migration - added `eagle/events` @@ -56,14 +56,14 @@ These items are already completed in the current workspace. ### Governance - added `check-shared-types-imports.sh` - added `check-ecosystem-boundaries.sh` -- added `check-flux-client-imports.sh` +- added `check-mux-client-imports.sh` - wired both guards into `Makefile` and CI -- wired the Flux engine-boundary guards into `Makefile` and CI +- wired the Mux 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 Rho, kestrel, merlin, and external workspace copies +- updated docs across Decoder, kestrel, merlin, and external workspace copies - added standalone boundary guards in `kestrel` and `merlin` -- added standalone boundary guards in `shrike`, `flux`, `harrier`, and `swift` +- added standalone boundary guards in `shrike`, `mux`, `harrier`, and `swift` - updated support repo READMEs with ecosystem boundary rules ### Review and verification contract migration @@ -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 Rho review persistence to neutral review contracts -- switched Rho review/merlin bridge paths to return neutral review/verify contracts +- switched Decoder review persistence to neutral review contracts +- switched Decoder 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: -- Rho local integration snapshot points at architecture-aligned support-repo revisions +- Decoder local integration snapshot points at architecture-aligned support-repo revisions Still external: -- confirm released module versions used by Rho match the merged contract changes +- confirm released module versions used by Decoder match the merged contract changes -### 3. Remove Rho production dependency on lower Flux packages +### 3. Remove Decoder production dependency on lower Mux packages Current state: - session persistence uses neutral tool contracts -- Rho now owns the runtime message DTO in `internal/types.FluxMessage` -- 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 `flux/engine` -- production imports of every lower Flux package are zero +- Decoder now owns the runtime message DTO in `internal/types.MuxMessage` +- Decoder now owns runtime tool call/result DTOs in `internal/types` +- Decoder now owns runtime response/usage/stream DTOs in `internal/types` +- Decoder now owns runtime chat options, response format, tool choice, continuation config, and tool definition DTOs in `internal/types` +- Decoder now owns the transport-provider seam in `internal/types.ChatProvider` +- Decoder session, review, setup, catalog, diagnostics, and custom-provider paths + now enter through `mux/engine` +- production imports of every lower Mux package are zero - cmd/session restore paths now go through centralized `session.ToRuntimeMessages` and `session.FromRuntimeMessages` Decision: -- completed: Rho owns the product DTO/port seam and Flux owns the engine, +- completed: Decoder owns the product DTO/port seam and Mux 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` -- Rho consumes neutral review/verification contracts at persistence and bridge boundaries +- Decoder 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 `rho/shared/types` +### 6. Remove `decoder/shared/types` Completed for the local ecosystem. Current status: -- Rho no longer ships `rho/shared/types` +- Decoder no longer ships `decoder/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 Rho orchestration logic into contracts +- move Decoder orchestration logic into contracts - move sandbox manager internals into contracts -- move every event struct in Rho into contracts -- force Lark/Gitant architecture into Rho contracts +- move every event struct in Decoder into contracts +- force Lark/Gitant architecture into Decoder contracts ## Recommended PR order ### PR 1 -- completed: moved chat options/request DTOs behind Rho-owned runtime adapters +- completed: moved chat options/request DTOs behind Decoder-owned runtime adapters ### PR 2 -- completed: moved provider/config interfaces behind Rho-owned transport adapters +- completed: moved provider/config interfaces behind Decoder-owned transport adapters ### PR 3 -- completed: removed all lower-level Flux imports from Rho production code and +- completed: removed all lower-level Mux imports from Decoder production code and replaced the compatibility allowlist with a zero-exception guard ### PR 4 -- completed: added neutral review/verification result contracts and wired Rho bridge/persistence edges +- completed: added neutral review/verification result contracts and wired Decoder bridge/persistence edges ### PR 5 -- completed: removed `rho/shared/types` from the local ecosystem and kept a legacy import guard +- completed: removed `decoder/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 `rho/internal/*` -- support repos do not import `rho/shared/types` +- support repos do not import `decoder/internal/*` +- support repos do not import `decoder/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/decoder-tau-alignment.md similarity index 77% rename from docs/plans/rho-tau-alignment.md rename to docs/plans/decoder-tau-alignment.md index 8078ba628..7041d1a95 100644 --- a/docs/plans/rho-tau-alignment.md +++ b/docs/plans/decoder-tau-alignment.md @@ -1,32 +1,32 @@ -# Plan: Rho — Tau-style TUI restyle + architecture tightening +# Plan: Decoder — Tau-style TUI restyle + architecture tightening Status: in progress -Branch: `feat/rename-rho` +Branch: `feat/rename-decoder` ## Context -Rho (formerly Hawk) is a Go terminal coding agent (Bubble Tea + Lip Gloss, +Decoder (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 +"Make Decoder 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. + Decoder'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`, +- [x] Content rename hawk → decoder (0 refs remain except intentional shims) +- [x] File/dir renames (`cmd/decoder`, `internal/decodererr`, npm packages) +- [x] Module path `github.com/GrayCodeAI/decoder` +- [x] Compatibility shims: `env.GetenvDecoder`, `storage.resolveAppDir`, legacy `HAWK_*_DIR` overrides -- [x] Committed + pushed to `feat/rename-rho` +- [x] Committed + pushed to `feat/rename-decoder` ## Phase 1 — Tau-style theme (visual) @@ -70,6 +70,6 @@ accent, generous dim text, no heavy borders. ## Out of scope (explicitly) -- Rewriting Rho in Python / cloning Textual. -- Deleting Rho's tool/command surface (separate decision). +- Rewriting Decoder in Python / cloning Textual. +- Deleting Decoder's tool/command surface (separate decision). - Changing the daemon/ACP wire protocols. \ No newline at end of file diff --git a/docs/plans/dsh-harness-gap-port-plan.md b/docs/plans/dsh-harness-gap-port-plan.md index 81c172863..cb65196fb 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) | Rho gap | Port status | +| # | DSH package(s) | Decoder 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` | rho exposes ACP server instead of DSH JSON-RPC SDK | deferred (keep ACP) | +| 24 | `sdk/client`, `sdk/protocol`, `sdk/server`, `python/sdk` | decoder 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): `$RHO_HOME`-style home (`~/.rho`), +- Resolved once per process (memoized): `$DECODER_HOME`-style home (`~/.decoder`), `.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 rho home data dir. + filesystem `Store` under the decoder 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 — rho has no Cordis bus; records reach the +coordinator stays out of scope — decoder 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 - (`RHO_ENABLE_TELEMETRY=1` + `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`); - wired in `cmd/rho/main.go` and `cmd/daemon.go`. + (`DECODER_ENABLE_TELEMETRY=1` + `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`); + wired in `cmd/decoder/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 `rho verify` — run **twice** (the second pass re-runs the full touched +and `decoder 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`) -- `rho verify`: exit 0 +- `decoder 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; `rho verify`: exit 0 +- `make lint`: 0 issues; `decoder 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/rho/main.go, cmd/daemon.go, internal/observability/otellog): clean +- `gofmt -l` (cmd/decoder/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** -- `rho verify`: exit 0 +- `decoder 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; `rho verify`: exit 0 +- `make lint`: 0 issues; `decoder 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 9c4dba9b3..ef4b2ab41 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 rho +# Adopt durable-design ideas from deepseek-harness in decoder 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 rho | +| dsh file | Extract | Maps to decoder | | --- | --- | --- | | `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/rho-capability-seams.md` (Phase 2) | +| `docs/capability-seams.md` | owner / impl / consumer roles | `docs/architecture/decoder-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/rho-capability-seams.md`: owner / impl / consumer table. +- `docs/architecture/decoder-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 Rho against the real deepseek-harness source, not the +These numbers compare Decoder 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`. -Rho is a **deliberate subset**. It ports the session-log spine, the fail-closed +Decoder 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, Rho has roughly **20–30%** +product-wide plugin catalogue. Measured head-to-head, Decoder has roughly **20–30%** of upstream by feature surface, and roughly **80–90%** of the plan's stated scope. -| Surface | DSH | Rho today | Gap | +| Surface | DSH | Decoder 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 | @@ -234,7 +234,7 @@ surface operations, ignorable markers, format version enforcement, and session header parity. The port is functionally complete. Remaining optional depth: - ~~DSH `surface.ts` `SurfaceManager`/`SurfaceReplacePlan` (live correction protocol — replacement operations).~~ Delivered: `internal/eventlog/surface.go` — `FoldSurface` (complete replay → surface nodes + replacement history) and an incremental `SurfaceManager` (bound to a `*Log`, with `Nodes`/`ReplaceGeneration`/`ValidateNext` atomic pre-flight), mirroring DSH's surface provenance, replacement-range, contiguity, and tool-result-rewrite invariants. - ~~Zstd compression in persistence (DSH `session-persistence-jsonl/src/zstd.ts`).~~ Delivered: `internal/eventlog/zstdz/zstd.go` + `internal/session/session.go` (`.jsonl.zstd` saves) — see Phase 9. -- DSH `packages/llm/llm/src/assembler.ts` (response normalization — covered by Flux facade). +- DSH `packages/llm/llm/src/assembler.ts` (response normalization — covered by Mux facade). This matrix is the honest anchor: the skeleton is ported; the deep fidelity is the actual remaining work. @@ -429,4 +429,4 @@ UI additions (Safari support, outside pointer handling) or ACP content admission ## Gates -Each phase: `make ci` + `rho verify`; no direct commits to `main`. +Each phase: `make ci` + `decoder 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 89f86b81d..dc6539700 100644 --- a/docs/plans/dsh-harness-rfc-port-plan.md +++ b/docs/plans/dsh-harness-rfc-port-plan.md @@ -1,6 +1,6 @@ -# Wave 2 — high-value deepseek-harness RFC ports for rho +# Wave 2 — high-value deepseek-harness RFC ports for decoder -> **Sandbox phases are obsolete.** Rho no longer contains `internal/sandbox`, +> **Sandbox phases are obsolete.** Decoder no longer contains `internal/sandbox`, > Docker execution, or an OS/container sandbox backend. Do not implement Phase > 2.3 or 2.11 as written; any future policy work must target the host-native > permission, path, trust, and environment controls. @@ -36,7 +36,7 @@ Reference source: `deepseek-ai/deepseek-harness` at `dsh-v0.1.0-rc.7` ## Wave 2 gap inventory -| # | DSH source | Rho gap | Status | +| # | DSH source | Decoder 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 | @@ -64,7 +64,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 rho-native form of DSH's + time.Duration`, 0 = no declaration) — the decoder-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. @@ -94,7 +94,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. Rho's +`toolPairingBalancedBefore` / `toolPairingBalancedAfter` helpers. Decoder'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 @@ -125,14 +125,14 @@ detection and adoption on load; deterministic replay after replace. ## Phase 2.3 — Sandbox mode vocabulary + model-facing policy (`internal/sandbox` + `internal/eventlog` + `internal/engine`) > **Obsolete specification — retained for historical provenance only.** Do not -> implement this phase. Rho's current product boundary is host-native +> implement this phase. Decoder's current product boundary is host-native > permission, path, trust, and environment policy; there is no sandbox mode or > `internal/sandbox` package to extend. Port of DSH `sandbox/sandbox-policy` semantics (`SandboxMode` read-only / workspace-write / danger-full-access, `ctx.sandboxPolicy.resolve()`, `setSandboxMode`, the `sandbox:policy` context -contribution). Rho already has the vocabulary — `internal/sandbox/mode.go` +contribution). Decoder already has the vocabulary — `internal/sandbox/mode.go` `Mode` strict / workspace / off — so this phase ports the **policy resolution and durability** around it. @@ -153,7 +153,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 rho's + the prompt assembler exactly like DSH's three templates, but with decoder's vocabulary: - `strict` — read-only; do not refuse a required modification from this policy alone; try the tool and follow denial/escalation guidance. @@ -234,7 +234,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 rho's session logs. +`session-query/session-query-sqlite` semantics over decoder's session logs. - New `internal/sessionquery` package: - FTS5 index (external content or contentless) over the session store, @@ -282,9 +282,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`. Rho has the ACP **server** (Wave 1 `internal/acp/server.go` — +`subagent-codex`. Decoder 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 rho's mission mode +session/request_permission); this phase adds the client so decoder's mission mode can **delegate to** other ACP agents, Claude Code, and Codex. - `internal/acp/client.go`: @@ -327,7 +327,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 - rho constraint); Windows via `ConPTY` later, outside this phase. + decoder 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 @@ -377,12 +377,12 @@ pre-publication failure. ## Phase 2.11 — Native Windows ACL sandbox (`internal/sandbox`) > **Obsolete specification — retained for historical provenance only.** Do not -> implement this phase. Rho no longer provides a local OS/container sandbox or +> implement this phase. Decoder no longer provides a local OS/container sandbox or > Docker fallback on any platform. The original proposal ported DSH `sandbox/sandbox-windows-acl` and assumed a cross-platform confinement backend. That assumption is no longer part of -Rho's local product architecture. +Decoder's local product architecture. - `internal/sandbox/windows_acl.go` (`//go:build windows`): - Implements the same `Sandbox` interface as `landlock.go` @@ -409,7 +409,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 rho's fs, `textDocument/didOpen` (version 1, full text), the + through decoder'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. @@ -439,7 +439,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 -`rho verify`, run **twice** (second pass re-runs the full touched suite after +`decoder 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 ea4631074..f96550f5b 100644 --- a/docs/plans/engine-refactor-plan.md +++ b/docs/plans/engine-refactor-plan.md @@ -1,20 +1,20 @@ -# rho/engine sub-package split — analysis and migration plan +# decoder/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 rho's +> split is multi-PR work that should land incrementally to keep decoder's > build green at every step. ## The problem ``` -rho/engine/ +decoder/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. rho's engine +~110K lines split across **dozens** of internal packages. decoder'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. ``` -rho/engine/ +decoder/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 rho -that imports `engine.Foo`. To keep rho green at every commit: +The split is high-risk because it touches every other package in decoder +that imports `engine.Foo`. To keep decoder 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/rho/engine" + import "github.com/GrayCodeAI/decoder/engine" type Strategy = engine.CompactStrategy var Default = engine.DefaultCompactStrategy ``` - Rho's external callers can start migrating to the new import paths. + Decoder'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 rho green at every commit: coordinator package only. This approach scales: each PR is small, reviewable, and individually -revertible. No "big bang" merge that paralyses rho for a week. +revertible. No "big bang" merge that paralyses decoder 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/rho/engine/` +New code should import `github.com/GrayCodeAI/decoder/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/rho/engine" +import "github.com/GrayCodeAI/decoder/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 56e83bba4..349c9456e 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 — rho +# Plan: Fix Critical + High-Impact Review Findings — decoder > 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. > @@ -81,12 +81,12 @@ These were documented in the original plan as out-of-scope-for-this-PR. They rem ## Context -A deep code review of `flux` and `rho` (companion plan at -`../flux/docs/plans/fix-critical-and-high-review.md`) surfaced 7 critical -and 9 high items. This plan covers **all rho items** (C3, C4, C5, H5, +A deep code review of `mux` and `decoder` (companion plan at +`../mux/docs/plans/fix-critical-and-high-review.md`) surfaced 7 critical +and 9 high items. This plan covers **all decoder items** (C3, C4, C5, H5, H6, H7, H8, H9) broken into a sequence of small, reviewable PRs. -## Scope (rho) +## Scope (decoder) | ID | Severity | Title | File(s) | Effort | |----|----------|-------|---------|--------| @@ -101,9 +101,9 @@ H6, H7, H8, H9) broken into a sequence of small, reviewable PRs. ## Out of scope (deferred to next plan) -- H10 from flux: `//nolint:errcheck` on type-assertion that can panic. +- H10 from mux: `//nolint:errcheck` on type-assertion that can panic. - M1–M20 medium items. -- L-tier quick wins (e.g. `cosmenticFlags` typo, `cmd/.rho/` leaked state). +- L-tier quick wins (e.g. `cosmenticFlags` typo, `cmd/.decoder/` leaked state). - `internal/intelligence/repomap/` documentation (large, separate effort). - Anything that requires a new dependency. @@ -131,7 +131,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 `~/.rho/.env` while +the error. If migration fails, secrets may remain in `~/.decoder/.env` while the agent is told to ignore that file. **Fix**: @@ -240,7 +240,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 -`RHO_MULTIAGENT_POLLING=1` to revert. +`DECODER_MULTIAGENT_POLLING=1` to revert. --- @@ -367,7 +367,7 @@ legitimate-input fix, not a security regression. > This section records the original review finding only. Do not recreate the > removed sandbox package or Docker fallback from this plan. -**Bug**: `internal/sandbox/seatbelt.go:70-108` `DefaultRhoPolicy` defaults +**Bug**: `internal/sandbox/seatbelt.go:70-108` `DefaultDecoderPolicy` defaults to `AllowWrite: true` and `AllowProcess: true`. A sandboxed bash can write and spawn processes out of the box. @@ -471,7 +471,7 @@ behavioral changes, only structural. ```bash go mod verify -go build ./cmd/rho +go build ./cmd/decoder go test -race -count=1 -shuffle=on ./... go vet ./... golangci-lint run @@ -499,10 +499,10 @@ Coverage target: maintained at 60%+ (CI gate). ## Cross-repo coordination -- **flux PR 4 (C2 — Vertex fix)** and **rho PR 4 (H6 — Session +- **mux PR 4 (C2 — Vertex fix)** and **decoder PR 4 (H6 — Session decomposition)** are independent. -- **flux PR 8 (H4 — FluxError)** is a prerequisite for any future - rho-side `errors.As(err, &fluxErr)` use (currently none). No +- **mux PR 8 (H4 — MuxError)** is a prerequisite for any future + decoder-side `errors.As(err, &muxErr)` 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 d46b88893..5c8b46539 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 Rho's native Go architecture +Status: Implemented in Decoder'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 Rho and its independent ecosystem repositories. It is an -adoption plan, not a code-porting plan. Rho should reimplement compatible +`fx` Unix-like coding agent with Decoder and its independent ecosystem repositories. It is an +adoption plan, not a code-porting plan. Decoder should reimplement compatible behavior in Go and preserve its existing provider, memory, review, audit, session, and security boundaries. ## Executive Decision -Rho should adopt the following `fx` ideas: +Decoder 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 @@ Rho 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 Rho's existing daemon and ACP surfaces. +7. A narrow embeddable host API over Decoder's existing daemon and ACP surfaces. -Rho should not copy `fx` source code, replace its Go runtime with Zig, create a +Decoder 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 Rho Capabilities +## Existing Decoder Capabilities -The comparison found that Rho already provides the foundation for most `fx` +The comparison found that Decoder already provides the foundation for most `fx` features: -| `fx` capability | Rho implementation | Current decision | +| `fx` capability | Decoder implementation | Current decision | |---|---|---| -| Native single binary | Go static binary and cross-platform release builds | Keep Rho implementation | -| Agent loop | `internal/engine` | Keep Rho implementation | +| Native single binary | Go static binary and cross-platform release builds | Keep Decoder implementation | +| Agent loop | `internal/engine` | Keep Decoder implementation | | Permission and host-policy split | `internal/engine/safety`, `internal/permissions`, `internal/trust` | 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 `flux` | Do not add provider logic to Rho | +| Provider runtime | sibling `mux` | Do not add provider logic to Decoder | ## Priority Model @@ -63,11 +63,11 @@ when their original command, path, or workspace has changed. ### Scope and ownership -- Primary implementation: Rho `internal/permissions` and +- Primary implementation: Decoder `internal/permissions` and `internal/engine/safety`. - Shared contract changes, if needed: sibling `eagle/policy`. - User-facing commands: `cmd` and the existing slash-command surface. -- No changes to sibling `flux` or `falcon`. +- No changes to sibling `mux` or `falcon`. ### Required behavior @@ -88,12 +88,12 @@ when their original command, path, or workspace has changed. ### Proposed interfaces ```text -rho permissions list [--json] -rho permissions revoke -rho permissions reset [--scope user|project] +decoder permissions list [--json] +decoder permissions revoke +decoder permissions reset [--scope user|project] ``` -The exact command names may follow existing Rho conventions, but the typed +The exact command names may follow existing Decoder 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: Rho root or `eagle` if consumed cross-repo. +- Snapshot contract: Decoder 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 -rho_version +decoder_version session_id workspace git_branch @@ -193,8 +193,8 @@ identifiers rather than raw secrets. ### Required interfaces ```text -rho status -rho status --json +decoder status +decoder 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 Rho's existing +Add `fx`-style terminal byte and resize recording to complement Decoder's existing agent-session swift and replay features. ### Ownership - Preferred home: sibling `swift` (Swift) if the capability is intended for reuse by other agents. -- Rho integration: `internal/swift` or the TUI composition layer. +- Decoder 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 -rho swift record --output -rho swift replay -rho swift replay --frames -rho swift replay --json +decoder swift record --output +decoder swift replay +decoder swift replay --frames +decoder 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 Rho's richer policy +Expose familiar `fx` permission names without weakening Decoder's richer policy engine. ### Mapping -| Compatibility name | Rho behavior | +| Compatibility name | Decoder 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 Rho's +These are aliases or presentation-layer modes, not a replacement for Decoder'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 Rho tier and hard safety constraints. +2. Display the effective Decoder 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 Rho representation, not an ambiguous alias. +4. Persist the canonical Decoder 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 Rho's continuable +Adopt `fx`'s useful child-agent visibility while retaining Decoder'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 Rho's existing MCP and skills architecture. +without changing Decoder's existing MCP and skills architecture. ### Required behavior @@ -344,7 +344,7 @@ without changing Rho's existing MCP and skills architecture. ### Goal Offer a supported embedding boundary inspired by `fx`'s ACP and WASM surfaces, -without committing Rho to a WASM rewrite. +without committing Decoder to a WASM rewrite. ### First implementation @@ -417,12 +417,12 @@ make test-race make vet make lint make security -rho verify +decoder verify ``` For sibling-repository changes, run that repository's own tests and boundary checks before -updating the Rho pointer. Do not modify provider protocols or adapters in Rho; -those belong in the Flux repository. +updating the Decoder pointer. Do not modify provider protocols or adapters in Decoder; +those belong in the Mux repository. ## Delivery Sequence @@ -456,7 +456,7 @@ those belong in the Flux 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 Rho without coupling tape parsing to the +- Integrate recording controls into Decoder without coupling tape parsing to the agent loop. ### Milestone 5: Host integration and trust UX @@ -469,18 +469,18 @@ those belong in the Flux repository. ## Deliberately Rejected - Copying Zig implementation code from `vercel-labs/fx`. -- Replacing Rho's Go runtime or independent ecosystem repositories. +- Replacing Decoder'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 Rho's code intelligence, review, audit, token, or provider features +- Removing Decoder'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 Rho can provide the operational clarity of +The adoption is successful when Decoder 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 Rho 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 Rho memory, provider, review, audit, MCP, ACP, and session behavior +- Existing Decoder memory, provider, review, audit, MCP, ACP, and session behavior remains intact. ## Implemented Scope @@ -504,7 +504,7 @@ The adoption is successful when Rho 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. Rho's subprocess LSP +implemented surfaces, not missing baseline features. Decoder'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 2d6dd7c90..85a4cf61f 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 rho found that most of its runtime concepts already have -a native rho implementation (providers via flux, sessions, MCP, ACP, skills, -host-native policy, permissions). The genuinely novel, rho-relevant ideas are +Goose's audit against decoder found that most of its runtime concepts already have +a native decoder implementation (providers via mux, sessions, MCP, ACP, skills, +host-native policy, permissions). The genuinely novel, decoder-relevant ideas are adopted here in Go, without copying Rust code or adding a second execution sandbox runtime. -## Existing Rho Capabilities +## Existing Decoder Capabilities -| Goose package/concept | Rho implementation | Decision | +| Goose package/concept | Decoder implementation | Decision | |---|---|---| -| Provider abstraction (~36) | sibling `flux` (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 | +| Provider abstraction (~36) | sibling `mux` (28 built-in + 75+ live) | Keep decoder | +| Sessions (SQLite WAL) | `internal/session` JSONL+WAL+zstd + sibling `swift` (Swift) | Keep decoder | +| MCP (client+server) | `internal/mcp` + sibling `falcon` | Keep decoder | +| ACP | `internal/acp` | Keep decoder | +| Extensions/skills | `internal/plugin`, skills registry | Keep decoder | | Execution safety | `internal/engine/safety`, path guards, trust policy | Host-native, fail-closed | -| OSV malware gate | `internal/permissions/osv_checker.go` (`CheckCommand`/`CheckPackage`) | Keep rho | +| OSV malware gate | `internal/permissions/osv_checker.go` (`CheckCommand`/`CheckPackage`) | Keep decoder | | 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 @@ sandbox runtime. ## Priority Model -- **P0:** Security-relevant, bounded, rho-native adoptions. +- **P0:** Security-relevant, bounded, decoder-native adoptions. - **P1:** High-value product improvements. - **Defer:** Larger or cross-cutting changes needing an RFC. @@ -50,7 +50,7 @@ or MCP stdio processes by filtering dangerous environment overrides checker) — a `sanitizeEnv` / `SafeEnv` helper. - Consumers: `internal/mcp` stdio launch path and `internal/plugin` package execution. -- No changes to sibling `flux`. +- No changes to sibling `mux`. ### Required behavior @@ -114,7 +114,7 @@ strict size/depth budgets — matching goose `hints/import_files.rs`. ### Goal -Upgrade rho's context compaction to a structured summary with a progressive +Upgrade decoder'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): rho's +- Goose's SQLite session store (`usage_ledger`, token/cost schema): decoder'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 flux's local-model +- Local-inference tool emulation / toolshim: depends on mux's local-model path. -- Recipe security scanner / cron recipes: rho already has schedule/cron. +- Recipe security scanner / cron recipes: decoder already has schedule/cron. ## Verification - `go test ./...` full suite. -- `make vet`, `make lint`, `rho verify`. +- `make vet`, `make lint`, `decoder 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 0dc41b40a..d110114e1 100644 --- a/docs/plans/minimax-adoption-plan.md +++ b/docs/plans/minimax-adoption-plan.md @@ -1,40 +1,40 @@ -# MiniMax-AI → rho Adoption Plan +# MiniMax-AI → decoder 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 Rho ecosystem (Rho plus independent repositories: Flux, Eagle, +repos into the Decoder ecosystem (Decoder plus independent repositories: Mux, Eagle, Falcon, Kestrel, and Merlin). ## Findings summary -Deep code review of 6 MiniMax-AI repos against Rho's existing sibling +Deep code review of 6 MiniMax-AI repos against Decoder's existing sibling repositories and internals produced these adoptions, ordered by value: | # | MiniMax repo | Adopt into | Action | |---|---|---|---| -| 1 | MiniMax-Provider-Verifier | sibling `flux` | Add provider-conformance metrics + wire the orphaned `verify` harness into CI | +| 1 | MiniMax-Provider-Verifier | sibling `mux` | 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 | 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) | +| 3 | MiniMax-Coding-Plan-MCP | decoder / `falcon` | MCP v2 no-network test pattern + image-source normalization (patterns only) | +| 4 | minimax_search | decoder tooling | Jina page→Markdown browse extractor + evidence-extraction prompt (pattern) | +| 5 | Mini-Agent | decoder engine | Cooperative-cancellation-with-cleanup + token-aware lossy summarization (patterns) | | 6 | MiniMax-MCP / JS | — | Not adoptable (media generation, out of scope) | -## 1. Flux provider-conformance metrics + CI wiring (highest value) +## 1. Mux provider-conformance metrics + CI wiring (highest value) -### Verified current state (flux repository) +### Verified current state (mux repository) -- `flux/verify/verify.go` — a complete behavioral conformance harness +- `mux/verify/verify.go` — a complete behavioral conformance harness exists: `Case`/`CaseResult`/`Expectation`/`Report`, `Run()`, `scoreResponse()`, `ToolCallF1()`, `DiffBaseline()`, `Markdown()`. -- `flux/verify/cases.go` — `CanonicalCases()` with 3 cases: +- `mux/verify/cases.go` — `CanonicalCases()` with 3 cases: `basic-chat`, `deterministic-answer`, `tool-call`. -- `flux/verify/metrics.go` — `ToolCallF1()` already implemented. +- `mux/verify/metrics.go` — `ToolCallF1()` already implemented. - **Confirmed gap:** the `verify` package is referenced ONLY by its own tests. It is not wired into any CLI, Makefile target, CI job, or provider-registration gate. Only structural registry/parity tests are enforced. -- flux already ships MiniMax providers (`minimax_token_plan`, `minimax_payg`) +- mux already ships MiniMax providers (`minimax_token_plan`, `minimax_payg`) as OpenAI-compatible adapters with `ThinkingFormat: "minimax"`. - `client/structured.go` already has `ValidateStructuredOutput()` — a recursive JSON-schema validator (`validateValue`, `validateObject`, `validateArray`) @@ -43,7 +43,7 @@ internals produced these adoptions, ordered by value: ### What to implement **A. Add a `SchemaValidate` helper (argument-level) — new file -`flux/verify/schema.go`** +`mux/verify/schema.go`** Add `SchemaValidate(args map[string]any, schema map[string]any) error` that checks tool-call arguments against the tool's `Parameters` JSON schema: @@ -52,7 +52,7 @@ checks tool-call arguments against the tool's `Parameters` JSON schema: This is the `ToolCalls-Schema-Accuracy` dimension from MiniMax-Provider-Verifier. -**B. Add `MatchRate` to `Report` — edit `flux/verify/verify.go`** +**B. Add `MatchRate` to `Report` — edit `mux/verify/verify.go`** Compute the `ToolCalls-Match-Rate` (trigger-vs-stop correctness) alongside F1. Extend `CaseResult` already has `ExpectedTool`/`CalledAnyTool`/`CorrectTool`, so @@ -63,9 +63,9 @@ tool_calls_match_rate = (TP + TN) / expected_tool_call_total_count mirroring MiniMax's confusion-matrix metric. **C. Add a `verify` CLI entrypoint — new file -`flux/cmd/verify/main.go`** +`mux/cmd/verify/main.go`** -A small `go run`-able binary (or a `verify` subcommand if flux has a cmd/) that +A small `go run`-able binary (or a `verify` subcommand if mux has a cmd/) that runs `verify.Run` against a provider endpoint (live or cassette) and exits non-zero on unmet thresholds: - `--model`, `--base-url`, `--api-key`, `--provider` @@ -74,8 +74,8 @@ non-zero on unmet thresholds: This makes the orphaned harness operational and enables CI gating. -**D. Add a Makefile target + CI job — edit `flux/Makefile` and -`flux/.github/workflows/ci.yml`** +**D. Add a Makefile target + CI job — edit `mux/Makefile` and +`mux/.github/workflows/ci.yml`** - Makefile: `verify` target that runs the harness in cassette mode (no tokens) and `verify-live` for manual live runs. @@ -83,27 +83,27 @@ This makes the orphaned harness operational and enables CI gating. on PRs, ensuring the harness is exercised and providers don't regress. ### Files -- `flux/verify/schema.go` (new) -- `flux/verify/schema_test.go` (new) -- `flux/verify/verify.go` (add MatchRate) -- `flux/verify/verify_test.go` (add tests) -- `flux/cmd/verify/main.go` (new) -- `flux/Makefile` (verify target) -- `flux/.github/workflows/ci.yml` (verify job) +- `mux/verify/schema.go` (new) +- `mux/verify/schema_test.go` (new) +- `mux/verify/verify.go` (add MatchRate) +- `mux/verify/verify_test.go` (add tests) +- `mux/cmd/verify/main.go` (new) +- `mux/Makefile` (verify target) +- `mux/.github/workflows/ci.yml` (verify job) ## 2. Curate MiniMax/skills content into starling -### 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`). +### Verified current state (decoder) +- decoder's skills system (`internal/plugin`) reads markdown+YAML-frontmatter skills + from dirs (`~/.decoder/skills`, `.claude/skills`, `.zero/skills`, `skills`) with a + registry (`starling` repo, `decoder 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 rho's convention (`globs`, `alwaysApply`). + repo), adapting frontmatter to decoder'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 (rho) +### Verified current state (decoder) - `internal/mcp` implements its own JSON-RPC client + server and does NOT use the shared `falcon` scaffolding (architectural divergence). -- rho has `internal/attachment/image.go` for image decode, and `ScreenshotTool` +- decoder 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 (rho) -- rho has `WebSearchTool` (6-provider cascade: Brave/SearXNG/DeepSeek/Exa/ +### Verified current state (decoder) +- decoder 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 (rho) -- rho's `Session.agentLoop` (`internal/engine/stream.go:133`) is a complete +### Verified current state (decoder) +- decoder'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 rho's loop: on cancellation +- Add `_cleanup_incomplete_messages` equivalent to decoder'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,27 +175,27 @@ 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; 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. + relevance to code intelligence; decoder has no TTS and that is a product decision. +- minimax_search's search layer: redundant (decoder has more providers). +- Mini-Agent's core loop: outclassed by decoder. - The `verify` harness's live-token path: optional, off by default. ## Execution order -1. Flux verify metrics + schema validation + tests (highest value) -2. Flux verify CLI + Makefile + CI wiring -3. rho image-source normalization helper + tests -4. rho Jina browse extractor + tests +1. Mux verify metrics + schema validation + tests (highest value) +2. Mux verify CLI + Makefile + CI wiring +3. decoder image-source normalization helper + tests +4. decoder Jina browse extractor + tests 5. Agent-loop cancellation/summarization patterns 6. starling content curation (follow-up PR in separate repo) ## Implementation status -- [x] Flux schema-accuracy validation and tool-call match-rate metrics. -- [x] Flux verification CLI, deterministic Makefile target, and CI job. -- [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 +- [x] Mux schema-accuracy validation and tool-call match-rate metrics. +- [x] Mux verification CLI, deterministic Makefile target, and CI job. +- [x] Decoder image-source normalization for data URIs, URLs, local files, and raw base64. +- [x] Decoder Jina Reader page-to-Markdown client with opt-in configuration and tests. +- [x] Decoder cancellation cleanup for incomplete assistant tool-use/tool-result turns. +- [x] Confirmed decoder's existing `internal/engine/compact` already provides token-triggered compaction; no duplicate summarizer was added. -- [ ] Publish the Flux repository changes through its own feature branch and PR. +- [ ] Publish the Mux 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 280a150ad..54153c5e2 100644 --- a/docs/plans/pi-adoption-plan.md +++ b/docs/plans/pi-adoption-plan.md @@ -4,17 +4,17 @@ Status: Proposed Source: `https://github.com/earendil-works/pi` (MIT, TypeScript/Bun monorepo) -This plan records the features identified as genuinely missing from rho while -auditing the Pi agent harness against Rho and its independent ecosystem +This plan records the features identified as genuinely missing from decoder while +auditing the Pi agent harness against Decoder and its independent ecosystem repositories. It is -an adoption plan, not a code-porting plan: rho reimplements compatible behavior +an adoption plan, not a code-porting plan: decoder reimplements compatible behavior in Go and preserves its existing provider, session, permission, observability, -and protocol boundaries. Rho executes tools directly on the host; it does not +and protocol boundaries. Decoder executes tools directly on the host; it does not ship a product sandbox runtime. ## Executive Decision -Rho should adopt the following Pi features: +Decoder should adopt the following Pi features: 1. A Go telemetry conformance suite that verifies emitted OpenTelemetry spans and attributes match the documented schema. @@ -26,24 +26,24 @@ Rho should adopt the following Pi features: 5. Kitty graphics protocol support for terminal images. 6. Session lease/ownership semantics in the daemon protocol. -Rho should not copy Pi's TypeScript code, replace its Go runtime, or adopt Pi's +Decoder should not copy Pi's TypeScript code, replace its Go runtime, or adopt Pi's custom CBOR RPC protocol. Its host-native permission and path/trust policy is the local safety boundary. -## Existing Rho Capabilities +## Existing Decoder Capabilities -The audit found that rho already provides the foundation for most Pi features: +The audit found that decoder already provides the foundation for most Pi features: -| Pi package | Rho implementation | Current decision | +| Pi package | Decoder implementation | Current decision | |---|---|---| -| `pi-ai` (multi-provider) | sibling `flux` 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 and trust | `internal/engine/safety`, path guards, `internal/trust`, `internal/env` | Keep rho's host-native boundary | +| `pi-ai` (multi-provider) | sibling `mux` client + engine facade + catalog + router + credentials | Keep decoder (broader) | +| `pi-agent-core` (agent loop) | `internal/engine` | Keep decoder | +| `pi-coding-agent` (CLI) | `cmd` TUI + CLI + daemon | Keep decoder | +| `pi-session-backends` (storage) | `internal/session` JSONL + zstd + WAL + SQLite index, sibling `swift` (Swift) | Keep decoder | +| `pi-server` (RPC) | `internal/daemon` + `internal/acp` + `internal/mcp` | Keep decoder (broader) | +| Permissions and trust | `internal/engine/safety`, path guards, `internal/trust`, `internal/env` | Keep decoder's host-native boundary | | `pi-tui` differential rendering | Bubble Tea v2 full-frame redraw | Adopt line-diff engine | -| `pi-telemetry` conformance | `docs/OTEL-CONVENTIONS.md`, flux `genai_semconv` pinning | Adopt conformance suite | +| `pi-telemetry` conformance | `docs/OTEL-CONVENTIONS.md`, mux `genai_semconv` pinning | Adopt conformance suite | | `pi-evals` agent-level eval | `internal/features/eval` (model benchmark only) | Adopt agent-runtime eval | ## Priority Model @@ -59,16 +59,16 @@ The audit found that rho 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 Rho and its independent +contract, so the schema cannot silently drift across Decoder and its independent ecosystem repositories. ### Scope and ownership - Primary implementation: `internal/observability` and - `flux/internal/observability` (the canonical `genai_semconv` + `mux/internal/observability` (the canonical `genai_semconv` constants and their pinning test). - Shared schema constants: sibling `eagle` if a cross-repo - contract is needed, otherwise keep them in flux as today. + contract is needed, otherwise keep them in mux as today. - No changes to `internal/mcp` or `internal/daemon` for telemetry adoption. ### Required behavior @@ -84,24 +84,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 rho and flux test suites so CI +4. Wire the conformance harness into the decoder and mux 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 rho emits is covered by the schema. +- Every span decoder 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 rho and flux CI. +- The conformance harness runs green in decoder and mux CI. ## P0: Agent-Runtime Eval Harness ### Goal -Evaluate the full rho agent end-to-end (real session, tool loop, planning, +Evaluate the full decoder agent end-to-end (real session, tool loop, planning, host policy) against tasks, and snapshot session data as artifacts — not just a model-level benchmark. @@ -276,7 +276,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 rho and flux CI. +- Cross-repo telemetry conformance in decoder and mux CI. ### Security tests @@ -294,11 +294,11 @@ make test-race make vet make lint make security -rho verify +decoder verify ``` For sibling-repository changes, run that repository's own tests and boundary checks before -updating the Rho pointer. +updating the Decoder pointer. ## Delivery Sequence @@ -313,7 +313,7 @@ updating the Rho pointer. - [x] Define the typed span/attribute schema. - [x] Implement the conformance harness. -- [x] Wire rho and flux CI. +- [x] Wire decoder and mux CI. - [x] Add schema-drift regression tests. ### Milestone 2: Agent-runtime eval (P0) @@ -321,7 +321,7 @@ updating the Rho 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 `rho eval` with a loop mode. +- [x] Extend `decoder eval` with a loop mode. ### Milestone 3: Differential renderer (P1) @@ -347,19 +347,19 @@ updating the Rho pointer. ## Deliberately Deferred - Copying Pi's TypeScript code or adopting its custom CBOR RPC protocol in place - of rho's ACP/MCP/daemon stack. -- Replacing rho's native permission/sandbox model with Pi's container-only + of decoder's ACP/MCP/daemon stack. +- Replacing decoder's native permission/sandbox model with Pi's container-only approach. -- Porting Pi's extension system verbatim; rho's plugin/hook/skills model already +- Porting Pi's extension system verbatim; decoder'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 rho closes each confirmed gap while retaining +The adoption is successful when decoder closes each confirmed gap while retaining its stronger architecture: -- Telemetry spans always conform to the documented schema across rho and its +- Telemetry spans always conform to the documented schema across decoder 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 61bd6349c..44a8d813a 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 rho's current UI framework: +cannot be adopted cleanly in decoder'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,13 +28,13 @@ 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 `rho eval loop` output. +- Wire it into the existing `evalloop` package and `decoder eval loop` output. ### Scope and ownership - Primary: `internal/features/evalloop`. - CLI: `cmd/eval.go` loop mode. -- No changes to a local sandbox runtime (none ships in rho), `internal/session`, +- No changes to a local sandbox runtime (none ships in decoder), `internal/session`, or `internal/daemon`. ### Required behavior @@ -44,7 +44,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. `rho eval loop --report` prints the comparative summary. +4. `decoder eval loop --report` prints the comparative summary. ### Acceptance criteria @@ -65,6 +65,6 @@ reporting** — is fully feasible and safe (non-TUI) and is adopted here. ## Verification - `go test ./...` full suite. -- `make vet`, `make lint`, `rho verify`. +- `make vet`, `make lint`, `decoder verify`. - Focused `internal/features/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 0f41d70eb..6a8b7b752 100644 --- a/docs/plans/qwen-code-adoption-plan.md +++ b/docs/plans/qwen-code-adoption-plan.md @@ -1,18 +1,18 @@ # Qwen Code Adoption Plan -Status: Implemented selectively in the current Rho feature branch. +Status: Implemented selectively in the current Decoder feature branch. ## Guardrails -Qwen Code is Apache-2.0 TypeScript software with a Gemini-shaped core. Rho -will independently reimplement behavioral contracts in Go, preserve the Flux -provider boundary, and retain Rho's event-sourced sessions and OS sandbox. +Qwen Code is Apache-2.0 TypeScript software with a Gemini-shaped core. Decoder +will independently reimplement behavioral contracts in Go, preserve the Mux +provider boundary, and retain Decoder's event-sourced sessions and OS sandbox. No Qwen source or dependency is vendored. -## Existing Rho Capabilities +## Existing Decoder Capabilities -Rho already has durable sessions, event logging, WAL/recovery, branching, -review contracts, provider routing in Flux, MCP integration, skills, memory, +Decoder already has durable sessions, event logging, WAL/recovery, branching, +review contracts, provider routing in Mux, 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 Rho's existing cancellation transcript cleanup and compaction. +4. Preserved Decoder'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 Rho's typed SpawnRequest. +- Map Markdown subagent frontmatter to Decoder'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 Rho build, vet, and test suite. +- Full Decoder build, vet, and test suite. - Independent second verification pass. - Final diff and sibling-repository status inspection. diff --git a/docs/plans/tau-feature-parity.md b/docs/plans/tau-feature-parity.md index 65edf68db..20082a9bb 100644 --- a/docs/plans/tau-feature-parity.md +++ b/docs/plans/tau-feature-parity.md @@ -1,17 +1,17 @@ -# Plan: bring Tau's feature set into Rho (Go) +# Plan: bring Tau's feature set into Decoder (Go) Status: in progress -Branch: `feat/rename-rho` +Branch: `feat/rename-decoder` ## Principle -Tau's features must exist in Rho **without leaving Go**. Rho already has a +Tau's features must exist in Decoder **without leaving Go**. Decoder already has a superset of Tau's agent capabilities; this plan closes the specific gaps where -Tau offers a documented feature Rho does not. +Tau offers a documented feature Decoder does not. -## Gap analysis (Tau feature → Rho status) +## Gap analysis (Tau feature → Decoder status) -| Tau feature | Rho status | Action | +| Tau feature | Decoder status | Action | |---|---|---| | Interactive TUI | ✅ Bubble Tea TUI | none | | `-p` print mode | ✅ `-p` | none | @@ -20,13 +20,13 @@ Tau offers a documented feature Rho does not. | Durable JSONL sessions + resume + branching | ✅ JSONL + DAG branching | none | | Slash commands (login, model, sessions, compaction, export, theme) | ✅ `/login` added | none | | `AGENTS.md` project instructions | ✅ | none | -| `.tau/` project resources | ✅ `.rho/` | none | +| `.tau/` project resources | ✅ `.decoder/` | none | | `.agents/` shared resources | ✅ skills/providers/recipes/hooks/rules/workflows + `.agents/AGENTS.md` instructions | none | | User skills, prompt templates, themes | ✅ | none | | Context accounting + manual/auto compaction | ✅ | none | | Provider-neutral output: text, JSON, transcript | ✅ text + json + stream-json + transcript | none | -| `~/.tau/catalog.toml` custom providers/models, no code changes | ❌ missing | **flux-side** (see below) | -| `tau update` | ✅ `rho update` | none | +| `~/.tau/catalog.toml` custom providers/models, no code changes | ❌ missing | **mux-side** (see below) | +| `tau update` | ✅ `decoder update` | none | | Installers | ✅ `install.sh` | none | ## Work items @@ -34,11 +34,11 @@ Tau offers a documented feature Rho does not. 1. ~~**`--cwd` global flag**~~ — done (`cmd/root.go`, applied to interactive, `-p`, and `exec`). 2. ~~**`/login` slash command**~~ — done (`cmd/chat_subcommand_login.go`). 3. ~~**`transcript` output format**~~ — done (`--output-format transcript`). -4. **User catalog `~/.rho/catalog.toml`** — NOT implemented. The model catalog - is owned by the flux engine (`github.com/GrayCodeAI/flux/catalog`), whose +4. **User catalog `~/.decoder/catalog.toml`** — NOT implemented. The model catalog + is owned by the mux engine (`github.com/GrayCodeAI/mux/catalog`), whose `LoadCatalogOptions` has no user-catalog override. Per `AGENTS.md` - (provider/catalog ownership lives in flux), this must be added in flux and - surfaced through the engine facade before rho can consume it. + (provider/catalog ownership lives in mux), this must be added in mux and + surfaced through the engine facade before decoder can consume it. 5. ~~**`.agents/` instruction resources**~~ — done (`.agents/AGENTS.md` is in the instruction search path in `internal/config/config.go`). @@ -50,5 +50,5 @@ Tau offers a documented feature Rho does not. ## Out of scope -- Python/Textual port. Rho stays Go. -- Removing Rho's larger feature surface. \ No newline at end of file +- Python/Textual port. Decoder stays Go. +- Removing Decoder'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 0463dce79..663111420 100644 --- a/docs/plans/toolbench-comparison-vs-top20.md +++ b/docs/plans/toolbench-comparison-vs-top20.md @@ -1,17 +1,17 @@ -# Toolbench Comparison: rho vs the Top-20 OSS Coding Agents (2026) +# Toolbench Comparison: decoder vs the Top-20 OSS Coding Agents (2026) > Status: analysis complete -> Scope: built-in tool inventory of rho and the leading open-source coding agents +> Scope: built-in tool inventory of decoder and the leading open-source coding agents > Related issue: browser/screenshot automation gap ## Executive summary -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: +decoder 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; rho remains terminal-first by design. +2. **IDE surfaces** — Cline/Continue/Zed are editor-native; decoder remains terminal-first by design. -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. +decoder's tool count exceeds the field (OpenCode ~10, Claude Code 18, Qwen Code ~26, Codex ~9+), but count alone is not the point: decoder 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 | |---|---|---| -| rho | **69** (+Power, +MCP) | all | +| decoder | **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 | -## rho's current tool set +## decoder'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. rho 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. decoder previously had only file/shell/web tools. -**Outcome:** added `internal/tool/browser.go` and `internal/tool/screenshot.go`, exposed through rho's existing `tool.Tool` interface: +**Outcome:** added `internal/tool/browser.go` and `internal/tool/screenshot.go`, exposed through decoder'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 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). +Implementation uses `github.com/chromedp/chromedp` v0.16.0, the canonical pure-Go Chrome DevTools client, and reuses decoder'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 `RHO_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 `DECODER_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 | rho | Field leaders | +| Category | decoder | Field leaders | |---|---|---| -| 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 | +| File ops | read/write/edit/structured/multi/notebook (Go) | Claude Code/Qwen have notebook_edit; decoder has it | +| Search | grep/glob/ls + LSP + code_search/code_graph | OpenCode LSP; decoder 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 `RHO_LIVE_BROWSER=1`) navi ## Remaining deltas (out of scope here) - VS Code / JetBrains extension (planned in `docs/IMPLEMENTATION-ROADMAP.md`). -- Auto-commit-per-edit discipline à la Aider (rho uses AGENTS.md branch rules instead). +- Auto-commit-per-edit discipline à la Aider (decoder 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 `RHO_LIVE_BROWSER=1`. +- `go test ./internal/tool/` — unit tests pass; live browser test passes with `DECODER_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 df481c4fb..36b054bb9 100644 --- a/docs/platform-capabilities.json +++ b/docs/platform-capabilities.json @@ -136,6 +136,6 @@ ] }, "ecosystem_compatibility": { - "rho+flux": "full" + "decoder+mux": "full" } } diff --git a/docs/plugin-development.md b/docs/plugin-development.md index 99b2d1886..753a14909 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -2,10 +2,10 @@ ## Overview -Rho supports three plugin types: +Decoder 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 `~/.rho/plugins/`. +2. **Managed subprocess plugins** — binary executables managed by `PluginManager`. Supports security scanning, timeout enforcement, and auto-discovery from `~/.decoder/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 (`RHO_EVENT=file.write`, `RHO_FILE_PATH=/path/to/file`). +Hooks can specify `priority` (higher runs first). Event data is passed as environment variables (`DECODER_EVENT=file.write`, `DECODER_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 `.rho/skills//SKILL.md`: +Skills are Markdown files with YAML frontmatter placed in `.decoder/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 rho, Claude Code, and Codex. +- **Cross-agent compatibility**: Skills follow the Agent Skills spec (agentskills.io) and work with decoder, Claude Code, and Codex. ## Event Hooks @@ -89,27 +89,27 @@ Available events for plugin and skill hooks: | Event | Data | Description | |-------|------|-------------| -| `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 | +| `session.start` | `DECODER_SESSION_ID` | Session began | +| `session.end` | `DECODER_SESSION_ID`, `DECODER_DURATION_MS` | Session ended | +| `turn.start` | `DECODER_TURN_NUM` | Agent turn started | +| `turn.end` | `DECODER_TURN_NUM`, `DECODER_TOOL_COUNT` | Agent turn ended | +| `tool_call.start` | `DECODER_TOOL_NAME`, `DECODER_TOOL_INPUT` | Tool execution started | +| `tool_call.end` | `DECODER_TOOL_NAME`, `DECODER_TOOL_RESULT` | Tool execution finished | +| `tool_call.error` | `DECODER_TOOL_NAME`, `DECODER_ERROR` | Tool execution failed | +| `file.read` | `DECODER_FILE_PATH` | File was read | +| `file.write` | `DECODER_FILE_PATH` | File was written | +| `file.edit` | `DECODER_FILE_PATH` | File was edited | +| `file.delete` | `DECODER_FILE_PATH` | File was deleted | +| `compaction.start` | `DECODER_COMPACTION_REASON` | Context compaction began | +| `compaction.end` | `DECODER_COMPACTION_NEW_LENGTH` | Context compaction finished | +| `budget.warning` | `DECODER_BUDGET_USAGE_PCT` | Token/cost budget warning | +| `budget.exceeded` | `DECODER_BUDGET_TYPE` | Budget exceeded (hard stop) | +| `error.occurred` | `DECODER_ERROR_TYPE` | Error occurred | +| `error.recovered` | `DECODER_RECOVERY_STRATEGY` | Error was recovered | +| `model.switch` | `DECODER_NEW_MODEL` | Active model changed | +| `provider.switch` | `DECODER_NEW_PROVIDER` | Active provider changed | +| `user.input` | `DECODER_USER_MESSAGE` | User sent a message | +| `agent.response` | `DECODER_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 `rho plugin search `. -4. Users install via `rho plugin install `. +3. Users discover plugins via `decoder plugin search `. +4. Users install via `decoder plugin install `. 5. Registry installation includes automatic audit and security scanning. ## Local Development ```bash # Create a plugin directory -mkdir -p ~/.rho/plugins/my-plugin -cd ~/.rho/plugins/my-plugin +mkdir -p ~/.decoder/plugins/my-plugin +cd ~/.decoder/plugins/my-plugin # Create a manifest cat > plugin.json << 'EOF' @@ -147,14 +147,14 @@ cat > plugin.json << 'EOF' EOF # List installed plugins -rho plugin list +decoder plugin list # Run a plugin command -rho plugin exec my-plugin hello +decoder plugin exec my-plugin hello # Create a skill (project-local) -mkdir -p .rho/skills/my-skill -cat > .rho/skills/my-skill/SKILL.md << 'EOF' +mkdir -p .decoder/skills/my-skill +cat > .decoder/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 `~/.rho/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 `~/.decoder/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 a9ca457b1..9de93578d 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: `rho/internal/engine/session.go` (the 35-collaborator `Session` struct) +> Scope: `decoder/internal/engine/session.go` (the 35-collaborator `Session` struct) ## Problem -`Session` at `rho/internal/engine/session.go:42-141` is a god object: +`Session` at `decoder/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.) @@ -84,13 +84,13 @@ backends without an explicit migration and recovery decision. **Owns:** `client ChatClient`, `provider string`, `model string`, `apiKeys map[string]string`, `Router *modelPkg.Router`, `DeploymentRouting bool`, `RateLimiter *ratelimit.Limiter`, `OutputSchema string`, `GLMThinkingEnabled *bool`, `ContCfg types.ContinuationConfig`, `ChatOptions{}` builder. **Methods:** -- `BuildOptions(systemPrompt string, tools []client.FluxTool, activeModel string, maxTokens int) types.ChatOptions` -- `Chat(ctx, msgs, opts) (*types.FluxResponse, error)` — non-streaming, used by background goroutines +- `BuildOptions(systemPrompt string, tools []client.MuxTool, activeModel string, maxTokens int) types.ChatOptions` +- `Chat(ctx, msgs, opts) (*types.MuxResponse, error)` — non-streaming, used by background goroutines - `Stream(ctx, msgs, opts) (*types.StreamResult, error)` — with retry, circuit-breaker record, rate-limit wait - `RecordSuccess(latency time.Duration)` / `RecordFailure(err error)` - `BuildContinuationConfig() types.ContinuationConfig` -**File:** `rho/internal/engine/chat_service.go` (~150 LOC) +**File:** `decoder/internal/engine/chat_service.go` (~150 LOC) ### 2. `MemoryService` — owns memory recall/remember @@ -99,12 +99,12 @@ backends without an explicit migration and recovery decision. **Methods:** - `RecallContext(ctx, lastUserMsg string, budget int) string` — unifies backend recall behind one nil-safe call - `Remember(ctx, content, category string)` — wraps memory.Remember -- `RunSleeptimeConsolidation(ctx, provider ChatService, messages []types.FluxMessage)` — background +- `RunSleeptimeConsolidation(ctx, provider ChatService, messages []types.MuxMessage)` — background - `RunSkillDistillation(ctx, provider ChatService, ...)` — background - `RecordFeedback(ctx, content string, action string)` — wraps EnhancedMemory feedback - `ShouldRemember(text string) bool` — heuristic for auto-remember -**File:** `rho/internal/engine/memory_service.go` (~200 LOC) +**File:** `decoder/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:** `rho/internal/engine/tool_service.go` (~400 LOC — biggest, owns the 15-stage pipeline) +**File:** `decoder/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:** `rho/internal/engine/permission_service.go` (~150 LOC) +**File:** `decoder/internal/engine/permission_service.go` (~150 LOC) ### 5. `LifecycleService` — owns the self-improvement loop @@ -139,16 +139,16 @@ backends without an explicit migration and recovery decision. **Methods:** - `OnSessionStart(ctx, lastUserMsg string) (learnedCtx string)` -- `OnSessionEnd(ctx, success bool, duration time.Duration, messages []types.FluxMessage)` +- `OnSessionEnd(ctx, success bool, duration time.Duration, messages []types.MuxMessage)` - `SelectModel(taskType, currentModel, hint string) string` — wraps Cascade -- `OnToolFailure(ctx, intent string, history []types.FluxMessage, errOutput string) (*Reflection, error)` -- `RecordDecision(turn int, toolNames string, msgs []types.FluxMessage)` +- `OnToolFailure(ctx, intent string, history []types.MuxMessage, errOutput string) (*Reflection, error)` +- `RecordDecision(turn int, toolNames string, msgs []types.MuxMessage)` - `CheckLimits(turnCount int) (cont bool, reason string)` — wraps Limits - `RecordTurn(turn int)`, `RecordToolCall(name string)` - `DetectDoomLoop(steps []ToolStep) bool` -- `InjectSteering(messages []types.FluxMessage) []types.FluxMessage` +- `InjectSteering(messages []types.MuxMessage) []types.MuxMessage` -**File:** `rho/internal/engine/lifecycle_service.go` (~250 LOC) +**File:** `decoder/internal/engine/lifecycle_service.go` (~250 LOC) ### 6. `PersistenceService` — owns checkpoint and session state @@ -161,13 +161,13 @@ backends without an explicit migration and recovery decision. - `AddUser(content string)`, `AddAssistant(content string)`, `AddUserWithImage(...)`, `AddUserWithAttachment(...)` - `AddUserWithDocumentText(content, label, extracted string)` - `AppendSystemContext(content string)`, `ReplaceSystemContextSection(header, content string)` -- `Messages() []types.FluxMessage`, `RawMessages()`, `MessageCount()`, `LoadMessages([]types.FluxMessage)` +- `Messages() []types.MuxMessage`, `RawMessages()`, `MessageCount()`, `LoadMessages([]types.MuxMessage)` - `RemoveLastExchange()` - `ConvoHead() string`, `ForkConversation(nodeID string) (string, error)`, `SwitchBranch(nodeID string) error`, `ListBranches(nodeID string) ([]*storage.DAGNode, error)` - `CheckpointOnCompaction(strategy string, before, after int, manual bool)` - `TokenTracking() (prompt, completion int)`, `RecordAPIUsage(prompt, completion int)` -**File:** `rho/internal/engine/persistence_service.go` (~300 LOC) +**File:** `decoder/internal/engine/persistence_service.go` (~300 LOC) ## What stays on `Session` @@ -222,7 +222,7 @@ func (s *Session) agentLoop(ctx context.Context, ch chan<- StreamEvent) { for turn := 0; ; turn++ { if !s.Lifecycle.CheckLimits(turn) { return } - opts := s.Chat.BuildOptions(s.system, s.Tools.Registry().FluxTools(), activeModel, maxTok) + opts := s.Chat.BuildOptions(s.system, s.Tools.Registry().MuxTools(), activeModel, maxTok) result, err := s.Chat.Stream(ctx, s.Persistence.Messages(), opts) if err != nil { /* error path */ } defer result.Close() @@ -256,7 +256,7 @@ The loop body shrinks from 800 lines to ~250 because: 3. **Phase 3**: extract `MemoryService` (most fields, but only consumed in the preamble/postamble of `agentLoop`). 4. **Phase 4**: extract `PermissionService` (smallest surface). 5. **Phase 5**: extract `LifecycleService` (biggest behavioral surface, but mostly reads `messages` and `tools`). -6. **Phase 6**: extract `PersistenceService` (last because it owns the `messages []types.FluxMessage` field which is touched everywhere). +6. **Phase 6**: extract `PersistenceService` (last because it owns the `messages []types.MuxMessage` field which is touched everywhere). 7. **Phase 7**: extract `ToolService` (depends on 4, 5; do last). 8. **Phase 8**: delete fields from `Session`, add backwards-compatible shims that delegate to the new services, mark for removal in v0.2.0. diff --git a/docs/terminal-icons.md b/docs/terminal-icons.md index 4b76ab924..7bc837df5 100644 --- a/docs/terminal-icons.md +++ b/docs/terminal-icons.md @@ -1,6 +1,6 @@ # Terminal icons -Rho uses current Nerd Font Codicon glyphs for interactive terminal output. +Decoder 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) -RHO_ICONS=nerd ./bin/rho +DECODER_ICONS=nerd ./bin/decoder # Portable text-only output -RHO_ICONS=ascii ./bin/rho +DECODER_ICONS=ascii ./bin/decoder ``` -For the real icons, configure the terminal profile—not Rho's Go code—with a +For the real icons, configure the terminal profile—not Decoder'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. Rho applies bold +Font size and glyph scale are controlled by that profile. Decoder 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 1b6ed3168..c2d32bca8 100644 --- a/docs/troubleshooting-guide.md +++ b/docs/troubleshooting-guide.md @@ -1,6 +1,6 @@ # Troubleshooting Guide -A practical guide for diagnosing common rho daemon and CLI issues. +A practical guide for diagnosing common decoder daemon and CLI issues. ## Table of Contents @@ -29,27 +29,27 @@ because the auth middleware would be open to the network. **Fix:** Set an API key: ```bash -export RHO_DAEMON_API_KEY=$(openssl rand -base64 32) -rho daemon start --host 0.0.0.0 --port 4590 +export DECODER_DAEMON_API_KEY=$(openssl rand -base64 32) +decoder daemon start --host 0.0.0.0 --port 4590 ``` Or bind to loopback only (no API key required, but not remotely accessible): ```bash -rho daemon start --host 127.0.0.1 --port 4590 +decoder 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 `~/.rho/state/`. +The daemon writes logs, PID files, and the audit log to `~/.decoder/state/`. **Fix:** ```bash -mkdir -p ~/.rho/state -chmod 750 ~/.rho/state -# If running under systemd as user 'rho', ensure ownership: -chown -R rho:rho ~/.rho +mkdir -p ~/.decoder/state +chmod 750 ~/.decoder/state +# If running under systemd as user 'decoder', ensure ownership: +chown -R decoder:decoder ~/.decoder ``` ### "port already in use" @@ -63,7 +63,7 @@ Another process is using port 4590. lsof -i :4590 # Or use a different port -rho daemon start --port 4591 +decoder daemon start --port 4591 ``` --- @@ -72,7 +72,7 @@ rho daemon start --port 4591 ### `GET /v1/ready` returns 503 -The readiness probe fails when Flux's local preflight doesn't pass. This +The readiness probe fails when Mux's local preflight doesn't pass. This checks provider state, catalog, credentials, and model selection. **Diagnosis:** @@ -83,9 +83,9 @@ curl -v http://localhost:4590/v1/ready Check the response body for the specific failed check. Common causes: -- No model configured — set `RHO_MODEL` or provider credentials. -- Flux catalog not initialized — the Go modules are independent sibling - repositories; from a full parent workspace, run `make setup` in rho to +- No model configured — set `DECODER_MODEL` or provider credentials. +- Mux catalog not initialized — the Go modules are independent sibling + repositories; from a full parent workspace, run `make setup` in decoder to regenerate the parent `go.work`: ```bash make setup @@ -102,11 +102,11 @@ failed to start. ```bash # Check if the process is running -ps aux | grep rho +ps aux | grep decoder # Check logs -journalctl -u rho-daemon -n 50 -tail -50 ~/.rho/state/daemon.log +journalctl -u decoder-daemon -n 50 +tail -50 ~/.decoder/state/daemon.log ``` --- @@ -120,7 +120,7 @@ The daemon requires `Authorization: Bearer ` or `X-API-Key: `. **Fix:** ```bash -curl -H "X-API-Key: $RHO_DAEMON_API_KEY" http://localhost:4590/v1/stats +curl -H "X-API-Key: $DECODER_DAEMON_API_KEY" http://localhost:4590/v1/stats ``` ### "constant time comparison" errors in logs @@ -131,16 +131,16 @@ No action needed. ### Forgotten API key -The API key is written to `~/.rho/state/daemon.key` (permissions 0600). +The API key is written to `~/.decoder/state/daemon.key` (permissions 0600). ```bash -cat ~/.rho/state/daemon.key +cat ~/.decoder/state/daemon.key ``` **Security note:** Remove this file in production after initial testing: ```bash -rm ~/.rho/state/daemon.key +rm ~/.decoder/state/daemon.key ``` --- @@ -152,7 +152,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 `RHO_DAEMON_MAX_CONCURRENT`) +- **Concurrent chat sessions**: 4 (configurable via `DECODER_DAEMON_MAX_CONCURRENT`) **Fix:** @@ -170,7 +170,7 @@ your request. The default limits are: CORS is **disabled by default**. Enable it when serving browser-based clients: ```bash -rho daemon start --cors https://app.example.com --cors https://admin.example.com +decoder daemon start --cors https://app.example.com --cors https://admin.example.com ``` Use `--cors '*'` only for development — it allows any origin. @@ -181,7 +181,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 RHO_FEATURE_CORS=1 +export DECODER_FEATURE_CORS=1 ``` --- @@ -197,7 +197,7 @@ mode) or when the engine is not ready (daemon mode). curl http://localhost:4590/v1/ready ``` -If readiness fails, the session factory exists but Flux preflight is not +If readiness fails, the session factory exists but Mux preflight is not satisfied (missing model, credentials, etc.). --- @@ -210,7 +210,7 @@ The metrics endpoint is protected by the same API key authentication as other endpoints. ```bash -curl -H "X-API-Key: $RHO_DAEMON_API_KEY" http://localhost:4590/v1/metrics +curl -H "X-API-Key: $DECODER_DAEMON_API_KEY" http://localhost:4590/v1/metrics ``` ### Metrics output is empty @@ -226,7 +226,7 @@ Prometheus version supports this format (Prometheus 2.20+). For troubleshooting, try the JSON format: ```bash -curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ +curl -H "X-API-Key: $DECODER_DAEMON_API_KEY" \ "http://localhost:4590/v1/metrics?format=json" ``` @@ -239,13 +239,13 @@ curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ Telemetry is **opt-in**. You must explicitly enable it: ```bash -export RHO_ENABLE_TELEMETRY=1 +export DECODER_ENABLE_TELEMETRY=1 export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 -rho daemon start +decoder daemon start ``` **Important**: Setting `OTEL_EXPORTER_OTLP_ENDPOINT` alone does **not** -enable telemetry. The `RHO_ENABLE_TELEMETRY=1` flag is required. +enable telemetry. The `DECODER_ENABLE_TELEMETRY=1` flag is required. ### "telemetry initialization failed" warning in logs @@ -264,7 +264,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 -`RHO_OTEL_SHUTDOWN_TIMEOUT_MS`) to flush pending spans. +`DECODER_OTEL_SHUTDOWN_TIMEOUT_MS`) to flush pending spans. To force a flush, send SIGTERM to the daemon — it will flush telemetry before shutting down. @@ -275,25 +275,25 @@ before shutting down. ### Security log won't open -The audit log is stored in `~/.rho/state/securitylog/`. If the directory +The audit log is stored in `~/.decoder/state/securitylog/`. If the directory doesn't exist or isn't writable: ```bash -mkdir -p ~/.rho/state/securitylog -chmod 700 ~/.rho/state/securitylog +mkdir -p ~/.decoder/state/securitylog +chmod 700 ~/.decoder/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 `~/.rho/state/securitylog/` directory. +of the `~/.decoder/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 -`~/.rho/state/securitylog/` directory.** +`~/.decoder/state/securitylog/` directory.** --- @@ -312,13 +312,13 @@ audit log for the `denied` event type. 1. Check the metrics endpoint for request duration: ```bash - curl -H "X-API-Key: $RHO_DAEMON_API_KEY" \ + curl -H "X-API-Key: $DECODER_DAEMON_API_KEY" \ "http://localhost:4590/v1/metrics?format=json" ``` -2. Check concurrent sessions: `rho_daemon_chat_concurrency_used` +2. Check concurrent sessions: `decoder_daemon_chat_concurrency_used` 3. If at capacity, increase the concurrency limit: ```bash - export RHO_DAEMON_MAX_CONCURRENT=8 + export DECODER_DAEMON_MAX_CONCURRENT=8 ``` ### High memory usage @@ -334,41 +334,41 @@ large contexts can consume significant memory. Consider: ## Systemd Issues -### "Failed to start rho-daemon.service: Unit not found" +### "Failed to start decoder-daemon.service: Unit not found" Install the unit file, the service user and the binary it runs: ```bash -sudo useradd --system --home-dir /var/lib/rho --shell /usr/sbin/nologin rho -sudo install -m 0755 ~/.rho/bin/rho /usr/local/bin/rho -sudo cp packaging/systemd/rho-daemon.service /etc/systemd/system/ -# Optional: sudo install -D -m 0600 /dev/null /etc/rho/daemon.env, then add -# RHO_DAEMON_API_KEY= (the key is read from the environment, not argv). +sudo useradd --system --home-dir /var/lib/decoder --shell /usr/sbin/nologin decoder +sudo install -m 0755 ~/.decoder/bin/decoder /usr/local/bin/decoder +sudo cp packaging/systemd/decoder-daemon.service /etc/systemd/system/ +# Optional: sudo install -D -m 0600 /dev/null /etc/decoder/daemon.env, then add +# DECODER_DAEMON_API_KEY= (the key is read from the environment, not argv). sudo systemctl daemon-reload -sudo systemctl enable --now rho-daemon +sudo systemctl enable --now decoder-daemon ``` ### "Permission denied" accessing state directory -The unit runs as user `rho` with `ProtectSystem=strict` and +The unit runs as user `decoder` with `ProtectSystem=strict` and `ProtectHome=true`, so the only writable locations are the directories -systemd creates for it (`StateDirectory=rho`, `CacheDirectory=rho`) and a -private `/tmp`. Config lives in `/var/lib/rho/config`, state (sessions, security -log, `run/daemon.log`) in `/var/lib/rho/state`, and cache in `/var/cache/rho`. +systemd creates for it (`StateDirectory=decoder`, `CacheDirectory=decoder`) and a +private `/tmp`. Config lives in `/var/lib/decoder/config`, state (sessions, security +log, `run/daemon.log`) in `/var/lib/decoder/state`, and cache in `/var/cache/decoder`. Home directories are hidden from the service, so do not point -`ReadWritePaths=` or the `RHO_*_DIR` variables at `/home`, `/root` or `%h` +`ReadWritePaths=` or the `DECODER_*_DIR` variables at `/home`, `/root` or `%h` (`%h` is always `/root` in a system unit). To let the agent edit project checkouts, add them explicitly, for example `ReadWritePaths=/srv/projects` -in a drop-in (`sudo systemctl edit rho-daemon`). +in a drop-in (`sudo systemctl edit decoder-daemon`). ### Daemon not logging to journald If using the systemd unit, logs go to both journald (stdout/stderr) and -`/var/lib/rho/state/run/daemon.log`. Check: +`/var/lib/decoder/state/run/daemon.log`. Check: ```bash -journalctl -u rho-daemon -f -sudo tail -f /var/lib/rho/state/run/daemon.log +journalctl -u decoder-daemon -f +sudo tail -f /var/lib/decoder/state/run/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 df787591c..a7a984bee 100644 --- a/docs/user-guide/01-getting-started.md +++ b/docs/user-guide/01-getting-started.md @@ -1,25 +1,25 @@ # Getting Started -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. +Decoder 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 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). +You can use Decoder 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 -Rho is currently in active development. Contributor source builds are the primary path while we harden the product in the open. +Decoder 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/rho && cd rho -GOWORK=off go build -o rho ./cmd/rho -./rho +git clone https://github.com/GrayCodeAI/decoder && cd decoder +GOWORK=off go build -o decoder ./cmd/decoder +./decoder ``` For full cross-repository development, place the nine Go repositories beside -Rho in a parent workspace and run `make setup` from `rho`; `graycode-eco` is +Decoder in a parent workspace and run `make setup` from `decoder`; `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 -./rho path +./decoder path ``` This checks setup and security readiness. @@ -36,15 +36,15 @@ This checks setup and security readiness. ## First Launch -Start Rho by running: +Start Decoder by running: ```bash -rho +decoder ``` -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. +On first launch, Decoder 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. -Rho supports multiple providers: +Decoder 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, Rho presents a full-screen TUI powered by Bubble Tea with two main areas: +Once authenticated, Decoder presents a full-screen TUI powered by Bubble Tea with two main areas: -- **Scrollback** — the conversation history showing your prompts, Rho's responses, tool calls, file edits, and more +- **Scrollback** — the conversation history showing your prompts, Decoder'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. Rho 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. Decoder 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 -Rho exposes two independent control surfaces: +Decoder 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: `rho -c` +- Continue the most recent session: `decoder -c` ### Scrollback The scrollback shows: - **User prompts** — your messages, rendered as sticky headers -- **Agent messages** — Rho's responses with markdown rendering -- **Thinking blocks** — Rho's reasoning process (collapsible) +- **Agent messages** — Decoder's responses with markdown rendering +- **Thinking blocks** — Decoder's reasoning process (collapsible) - **Tool calls** — file edits, command executions, search results - **Task lists** — TODO items tracking progress ### Tools -Rho has built-in tools for: +Decoder 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 -rho +decoder # Submit an initial prompt as the first turn -rho "fix the failing auth test and run it" +decoder "fix the failing auth test and run it" # Start in a specific project directory -rho --cwd ~/projects/my-app +decoder --cwd ~/projects/my-app # Resume a previous session -rho -r abc123 +decoder -r abc123 # Continue the most recent session -rho -c +decoder -c # Use a specific provider/model -rho --provider openai --model gpt-4o +decoder --provider openai --model gpt-4o # Isolated worktree for changes -rho --worktree "refactor module X" +decoder --worktree "refactor module X" # Non-interactive (headless) mode -rho -p "Explain this codebase" +decoder -p "Explain this codebase" # Full auto mode -rho exec --auto full "add error handling" +decoder exec --auto full "add error handling" # Dry-run mode (denies all tools) -rho exec --autonomy dry-run "What would this do?" +decoder exec --autonomy dry-run "What would this do?" ``` --- ## Headless Mode -Run Rho non-interactively for scripting, CI/CD, and automation: +Run Decoder non-interactively for scripting, CI/CD, and automation: ```bash -rho -p "Your prompt here" +decoder -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. Rho 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. Decoder reads these files and injects their contents as a project-instructions message at the start of the conversation: ``` -~/.rho/AGENTS.md # Global rules (apply to all projects) +~/.decoder/AGENTS.md # Global rules (apply to all projects) /AGENTS.md # Repository-level rules /AGENTS.md # Directory-level rules (highest priority) ``` -Deeper files take precedence. Rho also reads `CLAUDE.md` files for compatibility. +Deeper files take precedence. Decoder also reads `CLAUDE.md` files for compatibility. --- diff --git a/docs/user-guide/02-authentication.md b/docs/user-guide/02-authentication.md index 8f36e99a6..bec9288ec 100644 --- a/docs/user-guide/02-authentication.md +++ b/docs/user-guide/02-authentication.md @@ -1,14 +1,14 @@ # Authentication -Rho supports several authentication methods, including API key configuration through the TUI and multi-provider support via Flux. +Decoder supports several authentication methods, including API key configuration through the TUI and multi-provider support via Mux. --- ## API Key Configuration -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. +On first launch, Decoder 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. -Rho supports multiple providers: +Decoder supports multiple providers: | Provider | ID | Key | |----------|----|-----| @@ -24,10 +24,10 @@ Rho supports multiple providers: ```bash # Verify credential status -rho credentials status +decoder credentials status # Run the TUI and use /config to set keys interactively -rho +decoder ``` ### 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-..." -rho +decoder ``` 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 -Rho uses Flux for provider routing, health checks, and retry logic. To configure providers: +Decoder uses Mux for provider routing, health checks, and retry logic. To configure providers: ```bash # In the TUI, press /config to open provider settings -rho +decoder # Or validate readiness -rho path +decoder path ``` ### Deployment-Aware Routing -For deployment-aware routing, set in `.rho/settings.json`: +For deployment-aware routing, set in `.decoder/settings.json`: ```json { @@ -72,10 +72,10 @@ For deployment-aware routing, set in `.rho/settings.json`: Or export: ```bash -export RHO_DEPLOYMENT_ROUTING=true +export DECODER_DEPLOYMENT_ROUTING=true ``` -Rho will route canonical model IDs through Flux's deployment catalog. Refresh the catalog with: +Decoder will route canonical model IDs through Mux'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 -// .rho/settings.json +// .decoder/settings.json { "oidc": { "issuer": "https://acme.okta.com", @@ -99,7 +99,7 @@ Authenticate developers through your own Identity Provider (IdP) — such as Okt } ``` -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`. +Decoder 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. Rho runs your command via `sh -c ""` +1. Decoder 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 -// .rho/settings.json +// .decoder/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 RHO_AUTH_PROVIDER_COMMAND="/usr/local/bin/my-auth-provider" -export RHO_AUTH_PROVIDER_LABEL="Acme Corp" +export DECODER_AUTH_PROVIDER_COMMAND="/usr/local/bin/my-auth-provider" +export DECODER_AUTH_PROVIDER_LABEL="Acme Corp" ``` ### Token Refresh -When Rho needs to refresh an expired token, it re-runs your binary with `RHO_AUTH_EXPIRED=1` set in the environment: +When Decoder needs to refresh an expired token, it re-runs your binary with `DECODER_AUTH_EXPIRED=1` set in the environment: ```bash #!/bin/sh -if [ "$RHO_AUTH_EXPIRED" = "1" ]; then +if [ "$DECODER_AUTH_EXPIRED" = "1" ]; then echo "Refreshing token..." >&2 TOKEN=$(my-company-auth --refresh --silent) else @@ -182,16 +182,16 @@ echo "{\"access_token\": \"$TOKEN\", \"expires_in\": 3600}" Check credential status at any time: ```bash -rho credentials status +decoder credentials status ``` -This verifies keychain entries and validates Flux's provider status. +This verifies keychain entries and validates Mux's provider status. --- ## Credential Precedence -Rho resolves credentials in this order: +Decoder 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 -Rho works with any LLM provider through Flux's adapter system: +Decoder works with any LLM provider through Mux's adapter system: | Provider | Status | |----------|--------| diff --git a/docs/user-guide/03-keyboard-shortcuts.md b/docs/user-guide/03-keyboard-shortcuts.md index 6af07dd38..c0456c243 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 Rho TUI. Bindings are built-in and cannot currently be remapped. +Reference for key bindings in the Decoder TUI. Bindings are built-in and cannot currently be remapped. --- ## Input Modes -Rho has two input modes that control how you navigate the scrollback: +Decoder 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 `~/.rho/settings.json`: +Or set `vim_mode = true` under `[ui]` in `~/.decoder/settings.json`: ```json { diff --git a/docs/user-guide/04-slash-commands.md b/docs/user-guide/04-slash-commands.md index c5877c978..6656ea70d 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 -rho skills install go-review +decoder skills install go-review ``` ### `/skills audit` @@ -195,7 +195,7 @@ Security scan installed skills. ``` /skills audit -rho skills audit +decoder skills audit ``` --- @@ -317,7 +317,7 @@ Quick ready-to-chat check. ### `/ecosystem` -Show ecosystem component status (Flux and the token pipeline). +Show ecosystem component status (Mux and the token pipeline). ``` /ecosystem @@ -413,7 +413,7 @@ Toggle vim-style keybindings. ### `/refresh-model-catalog` -Fetch the latest deployment-aware model catalog from Flux. +Fetch the latest deployment-aware model catalog from Mux. ``` /refresh-model-catalog diff --git a/docs/user-guide/05-configuration.md b/docs/user-guide/05-configuration.md index aed869a04..53e2a975c 100644 --- a/docs/user-guide/05-configuration.md +++ b/docs/user-guide/05-configuration.md @@ -1,6 +1,6 @@ # Configuration -Rho reads configuration from settings files, environment variables, and has defaults for all options. This document covers the common configuration options. +Decoder 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** (`.rho/settings.json`, repository-safe subset only — +4. **Project settings** (`.decoder/settings.json`, repository-safe subset only — `model`, `provider`, permissions, MCP servers and providers are stripped) -5. **User settings** (`~/.rho/settings.json`) +5. **User settings** (`~/.decoder/settings.json`) 6. **Built-in defaults** --- ## Settings File -Location: `~/.rho/settings.json` +Location: `~/.decoder/settings.json` -This is the main configuration file. Rho writes to it when you save changes via `/config` or `/autonomy save`. +This is the main configuration file. Decoder 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 | |----------|-------------| -| `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` | +| `DECODER_Y0_FOLDER_TRUST` | Folder trust feature flag (default: `1`) | +| `DECODER_Y0_MARKETPLACE` | Marketplace feature flag (default: `1`, set `0` to disable remote installs) | +| `DECODER_DEPLOYMENT_ROUTING` | Not an environment variable: set `deployment_routing` in `settings.json` | ### Paths | Variable | Description | |----------|-------------| -| `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 | +| `DECODER_HOME` | Harness home override used by identity only (default: `~/.decoder`); most config paths honor `DECODER_CONFIG_DIR` / `DECODER_STATE_DIR` / `DECODER_CACHE_DIR` instead | --- ## Project Configuration -Place configuration in `.rho/` within your repository: +Place configuration in `.decoder/` within your repository: | File | Purpose | |------|---------| -| `.rho/settings.json` | Project settings (autonomy, rules) | -| `.rho/lsp.json` | LSP server configuration | +| `.decoder/settings.json` | Project settings (autonomy, rules) | +| `.decoder/lsp.json` | LSP server configuration | | `AGENTS.md` | Project instructions | --- ## MCP Servers -Configure MCP servers in global `~/.rho/settings.json` only — project -`.rho/settings.json` cannot register MCP servers (stripped by +Configure MCP servers in global `~/.decoder/settings.json` only — project +`.decoder/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: `~/.rho/trusted_folders.toml` +Location: `~/.decoder/trusted_folders.toml` ```toml [[folders]] diff --git a/docs/user-guide/06-theming.md b/docs/user-guide/06-theming.md index 10533c6a5..0260d1e30 100644 --- a/docs/user-guide/06-theming.md +++ b/docs/user-guide/06-theming.md @@ -1,16 +1,16 @@ # Theming and Appearance Customization -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. +Decoder 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 -Rho includes 17 built-in themes, plus an `auto` option that follows your system appearance: +Decoder includes 17 built-in themes, plus an `auto` option that follows your system appearance: | Theme | Description | |-------|-------------| -| **dark** | Neutral dark base with Rho's Talon Gold accent. Default theme. | +| **dark** | Neutral dark base with Decoder'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, Rho 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, Decoder 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 `~/.rho/settings.json`: +Set the theme in `~/.decoder/settings.json`: ```json { @@ -66,7 +66,7 @@ Set the theme in `~/.rho/settings.json`: ## Auto Theme (System Appearance) -Set `theme: "auto"` to have Rho follow your operating system's light/dark appearance and switch themes automatically: +Set `theme: "auto"` to have Decoder 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, Rho checks for appearance changes when you explicitly switch themes (no continuous polling to avoid resource drain). +Once running, Decoder checks for appearance changes when you explicitly switch themes (no continuous polling to avoid resource drain). --- ## Color Support Detection -On startup, Rho detects your terminal's color capability: +On startup, Decoder detects your terminal's color capability: | Level | Description | |-------|-------------| @@ -107,7 +107,7 @@ On startup, Rho 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, Rho uses truecolor mode. When `TERM` contains `256color`, it uses 256-color mode. When `NO_COLOR` is set, Rho renders in monochrome. +When `COLORTERM=truecolor` is set, Decoder uses truecolor mode. When `TERM` contains `256color`, it uses 256-color mode. When `NO_COLOR` is set, Decoder 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 Rho experience. +This shows detected color support, current settings, and tips for optimal Decoder experience. --- diff --git a/docs/user-guide/07-mcp-servers.md b/docs/user-guide/07-mcp-servers.md index bf919ed1a..b4bcba0d7 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 Rho with external tool integrations. They let Rho interact with any service that implements the MCP standard. +MCP (Model Context Protocol) servers extend Decoder with external tool integrations. They let Decoder interact with any service that implements the MCP standard. --- ## What Are MCP Servers? -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. +An MCP server is a process that exposes tools to Decoder over a standardized protocol. When you configure an MCP server, its tools become available to the model alongside Decoder'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 `.rho/settings.json` under the `mcp_servers` key. +MCP servers are configured in `.decoder/settings.json` under the `mcp_servers` key. ### stdio Transport (Local Process) -Rho spawns a local process and communicates over stdin/stdout: +Decoder 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 -rho mcp list +decoder mcp list # Add a stdio server -rho mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir +decoder mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir # Add a remote HTTP server -rho mcp add linear --transport http https://mcp.linear.app/mcp +decoder mcp add linear --transport http https://mcp.linear.app/mcp # Remove a server -rho mcp remove github +decoder mcp remove github # Diagnose server configuration -rho mcp doctor -rho mcp doctor +decoder mcp doctor +decoder mcp doctor ``` -Use `--scope project` to write to `.rho/settings.json` in the current directory instead of the user config. +Use `--scope project` to write to `.decoder/settings.json` in the current directory instead of the user config. --- ## Project-Scoped MCP Servers -MCP servers can be configured per-project in `.rho/settings.json`: +MCP servers can be configured per-project in `.decoder/settings.json`: ``` my-project/ - .rho/ + .decoder/ settings.json src/ ... ``` ```json -// .rho/settings.json +// .decoder/settings.json { "mcp_servers": { "linear": { @@ -125,7 +125,7 @@ MCP tools are namespaced with the server name to avoid collisions: ## Discovering and Using Tools -Rho provides built-in tools to work with MCP servers: +Decoder 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 519c5f1a4..58adaa97f 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 Rho 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 Decoder 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 Rho 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 Decoder 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. Rho 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. Decoder activates a skill when it applies to your current task. --- ## Skill Locations -Rho discovers skills from these directories, in priority order: +Decoder discovers skills from these directories, in priority order: | Location | Scope | Priority | |----------|-------|----------| -| `.rho/skills/`, `.rho/commands/` | Local (CWD) | Highest | -| `/.rho/skills/` | Repo | Medium | -| `~/.rho/skills/` | User | Lowest | +| `.decoder/skills/`, `.decoder/commands/` | Local (CWD) | Highest | +| `/.decoder/skills/` | Repo | Medium | +| `~/.decoder/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: ``` -~/.rho/skills/ +~/.decoder/skills/ commit/ SKILL.md review-pr/ @@ -103,13 +103,13 @@ Pass arguments after the name: When names collide, use qualified forms: ``` -/local:commit # From ./.rho/skills/ -/user:commit # From ~/.rho/skills/ +/local:commit # From ./.decoder/skills/ +/user:commit # From ~/.decoder/skills/ ``` ### Automatic Invocation -Rho can invoke a skill automatically when it recognizes a relevant task. Write specific descriptions in the `description` and `when-to-use` fields. +Decoder 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 -rho skills search go +decoder skills search go # Install a skill from a source -rho skills install go-review +decoder skills install go-review # Audit installed skills for security -rho skills audit +decoder skills audit ``` --- ## Installing Skills -Rho ships **no bundled skills** by default. Skills are installed on demand +Decoder ships **no bundled skills** by default. Skills are installed on demand from the separate `GrayCodeAI/starling` repo (or any GitHub repo): ```bash -rho skills search # find a skill in the registry -rho skills install [skill] # install after user approval -rho skills audit # security-scan installed skills +decoder skills search # find a skill in the registry +decoder skills install [skill] # install after user approval +decoder 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 bdcbdec16..a8c01fc80 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 -Rho discovers plugins from these locations, in priority order: +Decoder discovers plugins from these locations, in priority order: | Location | Scope | Trust Required | |----------|-------|----------------| -| `.rho/plugins/` | Project | Yes | -| `~/.rho/plugins/` | User | No | +| `.decoder/plugins/` | Project | Yes | +| `~/.decoder/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 -rho plugins list +decoder plugins list # Install a plugin -rho plugins install user/repo -rho plugins install user/repo@v1.0 -rho plugins install /path/to/local/plugin +decoder plugins install user/repo +decoder plugins install user/repo@v1.0 +decoder plugins install /path/to/local/plugin # Install with trust (required for project plugins) -rho plugins install user/repo --trust +decoder plugins install user/repo --trust # Uninstall a plugin -rho plugins uninstall my-plugin +decoder plugins uninstall my-plugin # Update plugins -rho plugins update +decoder plugins update ``` --- @@ -63,15 +63,15 @@ Browse and install plugins from the community marketplace: ```bash # Search marketplace -rho plugins search go +decoder plugins search go # Install from marketplace -rho plugins install go-review --trust +decoder plugins install go-review --trust ``` ### Adding Marketplace Sources -Configure additional marketplaces in `~/.rho/settings.json`: +Configure additional marketplaces in `~/.decoder/settings.json`: ```json { @@ -90,13 +90,13 @@ Configure additional marketplaces in `~/.rho/settings.json`: ## Trust Model -- **User plugins** (`~/.rho/plugins/`) — Trusted automatically -- **Project plugins** (`.rho/plugins/`) — Require explicit trust before activation +- **User plugins** (`~/.decoder/plugins/`) — Trusted automatically +- **Project plugins** (`.decoder/plugins/`) — Require explicit trust before activation Trust is required because plugins can execute code via hooks and MCP servers. To trust a plugin: ```bash -rho plugins install --trust +decoder plugins install --trust ``` --- diff --git a/docs/user-guide/10-hooks.md b/docs/user-guide/10-hooks.md index f7430a1c6..38c128b02 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 Rho 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 Decoder 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 Rho calls when a specific lifecycle event occurs. Hooks can: +A hook is a shell command or HTTP endpoint that Decoder 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 `.rho/hooks/hooks.json`: +Create hooks in `.decoder/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** (`~/.rho/hooks/`) — Always trusted -- **Project hooks** (`.rho/hooks/`) — Require folder trust +- **User hooks** (`~/.decoder/hooks/`) — Always trusted +- **Project hooks** (`.decoder/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 -Rho sets these variables for every hook: +Decoder sets these variables for every hook: | Variable | Description | |----------|-------------| -| `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 | +| `DECODER_HOOK_EVENT` | Event name (e.g., `PreToolUse`) | +| `DECODER_SESSION_ID` | Current session ID | +| `DECODER_WORKSPACE_ROOT` | Project root path | +| `DECODER_PLUGIN_ROOT` | Plugin directory (for plugin hooks) | +| `DECODER_PLUGIN_DATA` | Plugin data directory | --- @@ -130,20 +130,20 @@ Rho sets these variables for every hook: ```bash # List hooks -rho hooks list +decoder hooks list # Trust project hooks -rho hooks trust +decoder hooks trust # Untrust project hooks -rho hooks untrust +decoder hooks untrust ``` --- ## Example: Safe Shell Guard -Create `.rho/hooks/safety.json`: +Create `.decoder/hooks/safety.json`: ```json { diff --git a/docs/user-guide/11-custom-models.md b/docs/user-guide/11-custom-models.md index 1ce0e2f4a..c11870512 100644 --- a/docs/user-guide/11-custom-models.md +++ b/docs/user-guide/11-custom-models.md @@ -1,12 +1,12 @@ # Custom Models -Rho connects to custom model endpoints through Flux for alternative providers, self-hosted models, and overriding built-in settings. +Decoder connects to custom model endpoints through Mux for alternative providers, self-hosted models, and overriding built-in settings. --- ## Supported Providers -Rho works with any LLM provider. Built-in support includes: +Decoder works with any LLM provider. Built-in support includes: | Provider | ID | Key | |----------|-----|-----| @@ -24,7 +24,7 @@ Rho works with any LLM provider. Built-in support includes: ### CLI Flag ```bash -rho -m gpt-4o -p "Hello" +decoder -m gpt-4o -p "Hello" ``` ### Slash Command @@ -39,7 +39,7 @@ In the TUI: ### Config Default ```json -// ~/.rho/settings.json +// ~/.decoder/settings.json { "default_provider": "openai", "default_model": "gpt-4o" @@ -50,7 +50,7 @@ In the TUI: ## Configuring Custom Models -Add custom models in `~/.rho/settings.json`: +Add custom models in `~/.decoder/settings.json`: ```json { @@ -73,7 +73,7 @@ Add custom models in `~/.rho/settings.json`: ### Credential Resolution -Rho resolves credentials in this order: +Decoder resolves credentials in this order: 1. Per-model `api_key` field 2. Environment variable (`env_key`) @@ -127,11 +127,11 @@ ollama pull llama-3.1-70b ## Deployment-Aware Routing -Enable deployment-aware routing to use Flux's model catalog: +Enable deployment-aware routing to use Mux's model catalog: ```bash -export RHO_DEPLOYMENT_ROUTING=true -rho +export DECODER_DEPLOYMENT_ROUTING=true +decoder ``` Or in settings: diff --git a/docs/user-guide/12-project-rules.md b/docs/user-guide/12-project-rules.md index 32dc93fad..f710b464a 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 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. +Project rules let you configure Decoder per project or directory. By placing an AGENTS.md file in your repository, you can set coding conventions, build instructions, and style guides that Decoder follows automatically. --- ## Supported File Names -Rho checks for these filenames in each directory (from repo root to CWD): +Decoder checks for these filenames in each directory (from repo root to CWD): - `AGENTS.md` - `Agents.md` @@ -14,26 +14,26 @@ Rho checks for these filenames in each directory (from repo root to CWD): - `Claude.md` - `AGENT.md` -Rho loads all matching files. Deeper files take precedence when instructions conflict. +Decoder loads all matching files. Deeper files take precedence when instructions conflict. --- ## Rules Directories -Rho also scans for `*.md` files in rules directories: +Decoder also scans for `*.md` files in rules directories: | Location | Purpose | |----------|---------| -| `/.rho/rules/` | Always scanned | +| `/.decoder/rules/` | Always scanned | | `/.claude/rules/` | Claude compatibility | --- ## How Discovery Works -Rho scans rules in this order: +Decoder scans rules in this order: -1. **Global rules**: `~/.rho/AGENTS.md` (applies to all projects) +1. **Global rules**: `~/.decoder/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 Rho runs in `packages/frontend/`, it loads all instructions. The frontend-specific rules appear later and take precedence. +When Decoder 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 -rho --rules "Always use TypeScript" -p "Implement feature" +decoder --rules "Always use TypeScript" -p "Implement feature" ``` --- diff --git a/docs/user-guide/13-memory.md b/docs/user-guide/13-memory.md index b3d8ad9b6..45806ca31 100644 --- a/docs/user-guide/13-memory.md +++ b/docs/user-guide/13-memory.md @@ -1,20 +1,20 @@ # Memory -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. +Memory lets Decoder recall facts, decisions, and patterns from earlier sessions. Decoder indexes saved information and searches it automatically, so new sessions can reuse relevant context. --- ## What Is Memory? -Without memory, each Rho session starts fresh. When memory is enabled, Rho can: +Without memory, each Decoder session starts fresh. When memory is enabled, Decoder 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 Rho. It stores memories as files under -Rho's state directory; no external service is required. +Memory is a local subsystem built into Decoder. It stores memories as files under +Decoder'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 -// ~/.rho/settings.json +// ~/.decoder/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 Rho's state directory (`~/.rho/` by default). +Memory is stored under Decoder's state directory (`~/.decoder/` by default). | Location | Scope | Description | |----------|-------|-------------| -| `~/.rho/memories/` | Global | Core memories and preferences | -| `~/.rho/` state files | Workspace | Project-specific memory and lessons | +| `~/.decoder/memories/` | Global | Core memories and preferences | +| `~/.decoder/` state files | Workspace | Project-specific memory and lessons | -Rho combines core memories, automatic captures, evolving guidelines, and +Decoder 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 Rho to remember something: +Ask Decoder to remember something: ``` /remember always open PR links after pushing ``` -Rho records entries as durable statements organized by topic. +Decoder records entries as durable statements organized by topic. ### Forget -Ask what Rho should forget: +Ask what Decoder should forget: ``` /forget the snake_case convention @@ -77,13 +77,13 @@ Forget is best-effort. ### Recall -Ask what Rho remembers: +Ask what Decoder remembers: ``` /what do you remember about auth? ``` -Rho searches across all memory sources and summarizes. +Decoder 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, Rho automatically searches memory for content relevant to the current project and injects it as context. +On the first turn of each session, Decoder 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 8d7d43a38..3a8b86642 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 Rho non-interactively from the command line. It accepts a prompt, executes tools, and returns results — ideal for automation and CI/CD. +Headless mode runs Decoder 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 Rho non-interactively from the command line. It accepts a pro Pass a prompt to run headless: ```bash -rho -p "Your prompt here" +decoder -p "Your prompt here" ``` -Rho processes the prompt and prints the result to stdout. +Decoder processes the prompt and prints the result to stdout. --- @@ -23,7 +23,7 @@ Rho processes the prompt and prints the result to stdout. Human-readable text: ```bash -rho -p "Summarize this codebase" +decoder -p "Summarize this codebase" ``` ### json @@ -31,7 +31,7 @@ rho -p "Summarize this codebase" Single JSON object after completion: ```bash -rho -p "Summarize this codebase" --output-format json | jq -r '.response' +decoder -p "Summarize this codebase" --output-format json | jq -r '.response' ``` Output includes: @@ -44,7 +44,7 @@ Output includes: NDJSON events in real time: ```bash -rho -p "Summarize" --output-format stream-json | jq -r 'select(.type=="content") | .content' +decoder -p "Summarize" --output-format stream-json | jq -r 'select(.type=="content") | .content' ``` Event types: @@ -59,20 +59,20 @@ Event types: ### Named Session -Each `rho -p` creates a fresh session by default. To continue a session: +Each `decoder -p` creates a fresh session by default. To continue a session: ```bash # Get session ID -rho -p "Initial prompt" --output-format json | jq -r '.sessionId' +decoder -p "Initial prompt" --output-format json | jq -r '.sessionId' # Resume the session -rho -p "Follow-up" --resume +decoder -p "Follow-up" --resume ``` ### Continue Most Recent ```bash -rho -p "Continue" --continue +decoder -p "Continue" --continue ``` --- @@ -83,10 +83,10 @@ Restrict available tools: ```bash # Allow only read tools -rho -p "Explain this" --tools "Read,Grep,LS" +decoder -p "Explain this" --tools "Read,Grep,LS" # Deny specific tools -rho -p "Review" --disallowed-tools "Bash,WebSearch" +decoder -p "Review" --disallowed-tools "Bash,WebSearch" ``` --- @@ -97,10 +97,10 @@ Control tool permissions: ```bash # Allow shell commands through the explicit tool policy flag -rho -p "Build" --allowed-tools "Bash(git:*) Bash(npm:*)" +decoder -p "Build" --allowed-tools "Bash(git:*) Bash(npm:*)" # Deny dangerous commands -rho -p "Clean" --disallowed-tools "Bash(rm:*) Bash(sudo:*)" +decoder -p "Clean" --disallowed-tools "Bash(rm:*) Bash(sudo:*)" ``` --- @@ -110,8 +110,8 @@ rho -p "Clean" --disallowed-tools "Bash(rm:*) Bash(sudo:*)" Use `--auto` for fully automated runs: ```bash -rho -p "Format all files" --dangerously-skip-permissions -rho exec --auto full "Add error handling" +decoder -p "Format all files" --dangerously-skip-permissions +decoder exec --auto full "Add error handling" ``` **Warning:** This grants full autonomy. Use only in trusted environments. @@ -124,14 +124,14 @@ rho exec --auto full "Add error handling" ```bash #!/bin/bash -rho -p "Review staged changes for bugs. Reply OK if fine." \ +decoder -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 -rho -p "Review PR for security issues" \ +decoder -p "Review PR for security issues" \ --output-format json --dangerously-skip-permissions | jq -r '.response' > review.md ``` @@ -139,7 +139,7 @@ rho -p "Review PR for security issues" \ ```bash for file in src/*.go; do - rho -p "Format $file" --auto + decoder -p "Format $file" --auto done ``` @@ -149,8 +149,8 @@ done ```bash export XAI_API_KEY="xai-..." # API key -export RHO_HOME="/path" # Custom config location -export RHO_LOG_FILE="/tmp/rho.log" # Log file +export DECODER_HOME="/path" # Custom config location +export DECODER_LOG_FILE="/tmp/decoder.log" # Log file ``` --- diff --git a/docs/user-guide/15-agent-mode.md b/docs/user-guide/15-agent-mode.md index 6837cdb96..d968e2290 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 Rho as an ACP (Agent Client Protocol) server for IDE integration and custom tooling. +Agent mode runs Decoder 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 Rho as an ACP server over stdio: +Run Decoder as an ACP server over stdio: ```bash -rho agent stdio +decoder agent stdio ``` Clients include: @@ -31,9 +31,9 @@ Clients include: ### Options ```bash -rho agent --model gpt-4o stdio -rho agent --auto stdio -rho agent --agent-profile path/to/profile.yaml stdio +decoder agent --model gpt-4o stdio +decoder agent --auto stdio +decoder agent --agent-profile path/to/profile.yaml stdio ``` | Flag | Description | @@ -49,7 +49,7 @@ rho agent --agent-profile path/to/profile.yaml stdio ### WebSocket Server ```bash -rho agent serve --bind 127.0.0.1:2419 --secret +decoder 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 -Rho provides `x.ai/*` extension methods: +Decoder provides `x.ai/*` extension methods: | Category | Methods | |----------|---------| @@ -110,7 +110,7 @@ Rho provides `x.ai/*` extension methods: import { spawn } from "child_process"; // Start ACP server -const proc = spawn("rho", ["agent", "stdio"]); +const proc = spawn("decoder", ["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 2790aa8df..42053115b 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 Rho needs to delegate work, it spawns a child session using the `Agent` tool. The child runs with: +When Decoder 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 `~/.rho/settings.json`: +Personas are behavioral overlays applied to subagents. Define them in `~/.decoder/settings.json`: ```json { diff --git a/docs/user-guide/17-sessions.md b/docs/user-guide/17-sessions.md index 639923568..b159f0622 100644 --- a/docs/user-guide/17-sessions.md +++ b/docs/user-guide/17-sessions.md @@ -1,6 +1,6 @@ # Sessions -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. +Decoder saves every conversation to disk automatically. Whether you work in the TUI, in headless mode, or over ACP, Decoder 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 `~/.rho/sessions/`. +Sessions are identified by a unique session ID and stored under `~/.decoder/sessions/`. --- @@ -36,7 +36,7 @@ This clears the current context and starts fresh. Alias: `/exit` -To leave the session but stay in Rho: +To leave the session but stay in Decoder: ``` /home @@ -58,13 +58,13 @@ Opens a session picker. Select a session to resume. ```bash # Resume specific session -rho --resume +decoder --resume # Continue most recent -rho --continue +decoder --continue # New session with specific ID -rho --session-id -p "prompt" +decoder --session-id -p "prompt" ``` --- @@ -122,10 +122,10 @@ Maintain context across headless calls: ```bash # Start and capture ID -ID=$(rho -p "First" --output-format json | jq -r '.sessionId') +ID=$(decoder -p "First" --output-format json | jq -r '.sessionId') # Continue -rho -p "Second" --resume "$ID" +decoder -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 4f0e65ab5..76786b607 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 -Rho executes agent commands directly on the host. There is no container or +Decoder 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 Rho may do without asking) +# Set the autonomy tier (how much Decoder 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, Rho always enforces: +Independent of the autonomy tier, Decoder 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, - Rho env files) are blocked for Read/Edit/Write. + Decoder 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 bb1bdc2c8..e432299fa 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 Rho 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 Decoder 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, Rho: +When plan mode is active, Decoder: 1. Reads and searches the codebase to understand patterns 2. Designs an implementation approach -3. Writes the plan to a file under `.rho/specs/` +3. Writes the plan to a file under `.decoder/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 `.rho/specs//plan.md`: +Plans are written to `.decoder/specs//plan.md`: - **Context** — Why the change is needed - **Approach** — Recommended implementation strategy @@ -56,7 +56,7 @@ Plans are written to `.rho/specs//plan.md`: ## Plan Approval -When planning completes, Rho opens a preview with action bar: +When planning completes, Decoder 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 57d97792f..237558681 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 -Rho runs long-lived processes without blocking the conversation. This covers background commands, `/loop`, and the `monitor` tool. +Decoder 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 a268d926f..0b8d2dd25 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 -Rho runs as a full-screen TUI powered by Bubble Tea. This covers terminal compatibility and common fixes. +Decoder 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 -Rho detects these terminals: +Decoder 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 `~/.rho/settings.json`: +**Fix**: Set in `~/.decoder/settings.json`: ```json { "terminal": { "alt_screen": "always" } } @@ -128,7 +128,7 @@ set -g set-clipboard on ## Diagnostics -Run in Rho: +Run in Decoder: ``` /terminal-setup diff --git a/docs/user-guide/22-permissions-and-safety.md b/docs/user-guide/22-permissions-and-safety.md index c87e29dc2..ccf06ca76 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 -Rho can read files, edit code, and run shell commands. The permission system controls what the agent is allowed to do. +Decoder 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 cdd2ae202..c50116109 100644 --- a/docs/user-guide/23-dashboard.md +++ b/docs/user-guide/23-dashboard.md @@ -1,6 +1,6 @@ # Dashboard and HUD -The Rho dashboard provides system status and monitoring information. +The Decoder dashboard provides system status and monitoring information. --- @@ -9,7 +9,7 @@ The Rho dashboard provides system status and monitoring information. In the TUI: ``` -/ecosystem # Ecosystem status (Flux and token pipeline) +/ecosystem # Ecosystem status (Mux and token pipeline) /path # Developer path readiness /preflight # Quick health check ``` @@ -18,11 +18,11 @@ In the TUI: ## Ecosystem Status -Shows the status of all Rho components: +Shows the status of all Decoder components: | Component | Status | |-----------|--------| -| Flux (providers) | Ready / Error | +| Mux (providers) | Ready / Error | | token pipeline | Available | | memory | Ready | @@ -33,7 +33,7 @@ Shows the status of all Rho components: Check readiness to chat: ```bash -rho path +decoder path ``` This verifies: diff --git a/docs/user-guide/24-monitoring-usage.md b/docs/user-guide/24-monitoring-usage.md index eee140720..6e4b38fb4 100644 --- a/docs/user-guide/24-monitoring-usage.md +++ b/docs/user-guide/24-monitoring-usage.md @@ -1,6 +1,6 @@ # Monitoring and Usage -Rho tracks token usage, costs, and session activity for monitoring and debugging. +Decoder 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 -rho usage -rho doctor # Full health report +decoder usage +decoder doctor # Full health report ``` Usage shows: @@ -24,12 +24,12 @@ Usage shows: ## Telemetry -Rho can send anonymous usage telemetry. +Decoder can send anonymous usage telemetry. ### Enable/Disable ```json -// ~/.rho/settings.json +// ~/.decoder/settings.json { "telemetry": { "enabled": false @@ -40,7 +40,7 @@ Rho can send anonymous usage telemetry. Or: ```bash -rho --telemetry # Enable +decoder --telemetry # Enable haw --no-telemetry # Disable ``` @@ -77,7 +77,7 @@ Accessible via `/ecosystem` in TUI. ## Cloud Integration -Rho Cloud provides managed usage tracking: +Decoder 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 d13c8e0ed..9e4266ec2 100644 --- a/docs/user-guide/25-shell-completions.md +++ b/docs/user-guide/25-shell-completions.md @@ -1,15 +1,15 @@ # Shell Completions -rho ships completion scripts for **bash**, **zsh**, **fish**, and **PowerShell**, +decoder 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: -rho completion install bash -rho completion install zsh -rho completion install fish +decoder completion install bash +decoder completion install zsh +decoder completion install fish ``` ## Manual Setup @@ -18,50 +18,50 @@ rho completion install fish ```bash # Load for current session: -source <(rho completion bash) +source <(decoder completion bash) # Persist (Linux): -rho completion bash > ~/.local/share/bash-completion/completions/rho +decoder completion bash > ~/.local/share/bash-completion/completions/decoder # Persist (macOS with Homebrew): -rho completion bash > /opt/homebrew/etc/bash_completion.d/rho +decoder completion bash > /opt/homebrew/etc/bash_completion.d/decoder ``` ### Zsh ```bash # Load for current session: -source <(rho completion zsh) +source <(decoder completion zsh) # Persist: -rho completion zsh > "${fpath[1]}/_rho" +decoder completion zsh > "${fpath[1]}/_decoder" ``` ### Fish ```bash # Load for current session: -rho completion fish | source +decoder completion fish | source # Persist: -rho completion fish > ~/.config/fish/completions/rho.fish +decoder completion fish > ~/.config/fish/completions/decoder.fish ``` ### PowerShell ```powershell # Load for current session: -rho completion powershell | Out-String | Invoke-Expression +decoder completion powershell | Out-String | Invoke-Expression # Persist: add to your $PROFILE -rho completion powershell > rho.ps1 -. ./rho.ps1 +decoder completion powershell > decoder.ps1 +. ./decoder.ps1 ``` ## JSON Spec (IDE Integration) ```bash -rho completion json +decoder 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 -`rho completion install` resolves the correct path automatically: +`decoder completion install` resolves the correct path automatically: | Shell | Linux | macOS (Homebrew) | |-------|-------|-------------------| -| 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 | +| bash | `~/.local/share/bash-completion/completions/decoder` | `/opt/homebrew/etc/bash_completion.d/decoder` | +| zsh | First `$fpath` entry (e.g. `/usr/local/share/zsh/site-functions/_decoder`) | Same | +| fish | `~/.config/fish/completions/decoder.fish` | Same | diff --git a/docs/user-guide/26-learned-preferences.md b/docs/user-guide/26-learned-preferences.md index 10ca692f4..94086c990 100644 --- a/docs/user-guide/26-learned-preferences.md +++ b/docs/user-guide/26-learned-preferences.md @@ -1,6 +1,6 @@ # Learned Preferences -Rho can learn coding-style tendencies from feedback through its taste system. +Decoder 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 1ee5b0742..b3c146c37 100644 --- a/docs/user-guide/27-workflows.md +++ b/docs/user-guide/27-workflows.md @@ -2,7 +2,7 @@ ## Interactive Input -- `/command` invokes a Rho slash command. +- `/command` invokes a Decoder 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 -rho review run HEAD --output-format json --max-turns 8 +decoder 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 `rho mcp`. +2. Merlin configured MCP servers with `decoder 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. Rho trims incomplete tool turns before a cancelled session +from interruptions. Decoder 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 8197a23e7..4afd5724c 100644 --- a/docs/user-guide/28-workflow-budgets.md +++ b/docs/user-guide/28-workflow-budgets.md @@ -1,6 +1,6 @@ # Workflow Budgets -Rho exposes several independent limits. Configure the smallest useful scope +Decoder 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 fc6339889..8e6002472 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -12,34 +12,19 @@ repository that follows this layout. Adopted 2026-05-14. ## Pattern by repo type -### Go binaries (`rho`) +### Go binaries (`decoder`) - `VERSION` at the repo root. -- A version package (`internal/version` for binaries, or `main` itself) declares - three settable variables defaulting to `"dev"` / `"none"` / `"unknown"`: - ```go - var ( - Version = "dev" // overridden by ldflags at release time - Commit = "none" - Date = "unknown" - ) - ``` -- The `Makefile` reads `VERSION` and injects all three via ldflags: - ```make - VERSION := $(shell cat VERSION 2>/dev/null | tr -d '[:space:]' || echo "dev") - COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none") - DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') - LDFLAGS := -s -w \ - -X main.Version=$(VERSION) \ - -X main.Commit=$(COMMIT) \ - -X main.BuildDate=$(DATE) - ``` +- The root `decoder` package embeds `VERSION` at compile time. The CLI, daemon, + MCP client and telemetry defaults read this value. A standalone binary does + not search the filesystem for version files. +- The `Makefile` reads `VERSION` and injects it alongside commit and build date. - `goreleaser` injects from the git tag at release time. The release workflow refuses a tag that is not exactly `v` + `VERSION` (`scripts/check-release-tag.sh`), so the two cannot disagree. - This is the Kubernetes / Helm / gh-cli pattern. -### Go libraries (`flux`) +### Go libraries (`mux`) - `VERSION` at the repo root. - A `version.go` file co-located with `VERSION` uses `//go:embed` to read it at @@ -91,7 +76,7 @@ func init() { pattern = "^(?P[^\\s]+)" [tool.hatch.build.targets.wheel] - force-include = { "VERSION" = "rho/VERSION" } + force-include = { "VERSION" = "decoder/VERSION" } ``` - `_version.py` reads the same `VERSION` file at runtime via `pathlib`, so `__version__` matches the package metadata both in source checkouts and in @@ -111,7 +96,7 @@ func init() { Versions are bumped by hand in a release PR: edit `VERSION`, turn the `## [Unreleased]` section of `CHANGELOG.md` into `## [X.Y.Z] — YYYY-MM-DD`, merge, then push the `vX.Y.Z` tag. There is no release-please or other bot. -For rho the full procedure, tag protection and verification steps are in +For decoder the full procedure, tag protection and verification steps are in [RELEASING.md](RELEASING.md). ## Cross-repo compatibility @@ -119,3 +104,11 @@ For rho the full procedure, tag protection and verification steps are in Versions are independent per repo. A future `compatibility-matrix.json` (at the eco root) will record which combinations are tested together (similar to the AWS SDK / Hashicorp release matrices). + +### Generated distribution metadata + +Edit only the root `VERSION`, then run `make version-sync`. The generator stamps +OpenAPI metadata, npm packages and optional dependency pins, SDK manifests, +and Mux's lower-layer Go version default. These checked-in outputs are generated +copies, not independent settings. `make version-check` and both CI/release +workflows fail on drift. Each repository remains independently buildable. diff --git a/ecosystem.yaml b/ecosystem.yaml index d334c5fe8..42358927e 100644 --- a/ecosystem.yaml +++ b/ecosystem.yaml @@ -6,10 +6,10 @@ # GrayCodeAI/graycode-eco -> ecosystem.yaml # # Why it moved: a product cannot own the registry of its siblings. This file -# lived inside `rho` and described itself as canonical, which meant `rho` could +# lived inside `decoder` and described itself as canonical, which meant `decoder` could # rename itself without signalling the other nine repositories. It did exactly # that — the organization holds `hawk` and `eyrie` as redirects from the prior -# names of `rho` and `flux`. The list also omitted `beam` entirely. +# names of `decoder` and `mux`. The list also omitted `beam` entirely. # # What is still true below, and what is not: # @@ -23,10 +23,10 @@ # scripts/ecosystem-manifest.sh reads this file # scripts/generate-workspace.sh calls the above # scripts/check-module-release-parity.sh reads this file -# .github/actions/checkout-flux calls the above +# .github/actions/checkout-mux calls the above # .github/actions/setup-deps calls the above # -# The migration keeps `rho/scripts/ecosystem-manifest.sh` as the single reader, +# The migration keeps `decoder/scripts/ecosystem-manifest.sh` as the single reader, # repointed at the roof's file, with a fallback to this one when the roof is not # checked out. The scripts do not need to know where the manifest lives. # @@ -35,31 +35,31 @@ schema_version: 1 contracts: - portable_graph_schema: rho.graph/v1 + portable_graph_schema: decoder.graph/v1 cloud_graph_schema: graycode-cloud.graph/v1 usage_event: usage.recorded.v1 repositories: - - directory: rho - github_repo: rho - product_name: Rho + - directory: decoder + github_repo: decoder + product_name: Decoder kind: product language: go - module: github.com/GrayCodeAI/rho + module: github.com/GrayCodeAI/decoder workspace: true - - directory: flux - github_repo: flux - product_name: Flux + - directory: mux + github_repo: mux + product_name: Mux kind: engine language: go - module: github.com/GrayCodeAI/flux + module: github.com/GrayCodeAI/mux workspace: true - facade: github.com/GrayCodeAI/flux/engine + facade: github.com/GrayCodeAI/mux/engine - directory: graycode-skills github_repo: graycode-skills - product_name: Rho Community Skills + product_name: Decoder Community Skills kind: extension language: python workspace: false @@ -93,7 +93,7 @@ repositories: kind: product language: typescript # Not a Go module: radius is a pnpm workspace, so it takes no part in - # rho's go.work. `workspace: true` here would make CI try to check it out + # decoder's go.work. `workspace: true` here would make CI try to check it out # as a Go sibling and fail. workspace: false diff --git a/examples/README.md b/examples/README.md index 4c045ac78..a42487b45 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,26 +1,26 @@ -# Rho Examples +# Decoder Examples -Rho is an AI coding agent that understands your codebase. +Decoder is an AI coding agent that understands your codebase. ## Basic Usage ### Start a chat session ```bash -rho chat +decoder chat > Explain how the authentication system works ``` ### Generate code ```bash -rho "Add input validation to the user registration endpoint" +decoder "Add input validation to the user registration endpoint" ``` ### Review changes ```bash -rho review +decoder review ``` ## Advanced Examples @@ -28,52 +28,52 @@ rho review ### Use with skills ```bash -rho --skill code-review "Review my latest changes" +decoder --skill code-review "Review my latest changes" ``` ### Analyze codebase ```bash -rho analyze --depth full +decoder analyze --depth full ``` ### Fix issues ```bash -rho fix --auto +decoder fix --auto ``` ### Headless agent in CI -Copy [rho-ci-exec.yml](github/rho-ci-exec.yml) to run `rho exec --ephemeral --json` +Copy [decoder-ci-exec.yml](github/decoder-ci-exec.yml) to run `decoder exec --ephemeral --json` on pull requests (summarize diff, risk list, or your own prompt). Pin your -rho install step and provider secrets before enabling the job. +decoder install step and provider secrets before enabling the job. ### Report CI delivery context -Copy [rho-delivery-context.yml](github/rho-delivery-context.yml) to your -repository to report GitHub Actions runs to Rho Cloud. Create a dedicated, +Copy [decoder-delivery-context.yml](github/decoder-delivery-context.yml) to your +repository to report GitHub Actions runs to Decoder 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 -Rho can use MCP servers for extended capabilities: +Decoder can use MCP servers for extended capabilities: ```bash # With harrier for persistent memory harrier setup -rho chat +decoder chat # With swift for session capture swift start -rho "refactor the API layer" +decoder "refactor the API layer" swift stop ``` ## Configuration -Create `.rho/config.json`: +Create `.decoder/config.json`: ```json { diff --git a/examples/github/rho-ci-exec.yml b/examples/github/decoder-ci-exec.yml similarity index 60% rename from examples/github/rho-ci-exec.yml rename to examples/github/decoder-ci-exec.yml index d5614ada3..f20f21e2a 100644 --- a/examples/github/rho-ci-exec.yml +++ b/examples/github/decoder-ci-exec.yml @@ -1,15 +1,15 @@ -# Rho CI agent (headless) +# Decoder CI agent (headless) # -# Copy to .github/workflows/rho-ci-exec.yml. +# Copy to .github/workflows/decoder-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: -# - rho binary on PATH (pin a release or install from your org's package) +# - decoder binary on PATH (pin a release or install from your org's package) # - LLM credentials via secrets (never commit keys) # - A host runner with the permissions required by the checked-out project # -name: Rho CI Exec +name: Decoder CI Exec on: pull_request: @@ -26,27 +26,27 @@ permissions: pull-requests: read jobs: - rho-exec: + decoder-exec: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - # Install rho with your org's preferred mechanism, for example: + # Install decoder with your org's preferred mechanism, for example: # - curl -fsSL https://…/install.sh | sh - # - go install github.com/GrayCodeAI/rho/cmd/rho@vX.Y.Z + # - go install github.com/GrayCodeAI/decoder/cmd/decoder@vX.Y.Z # - download a release asset into $GITHUB_PATH - - name: Ensure rho is available + - name: Ensure decoder is available run: | - if ! command -v rho >/dev/null 2>&1; then - echo "Install rho before this step (see comments in this workflow)." + if ! command -v decoder >/dev/null 2>&1; then + echo "Install decoder before this step (see comments in this workflow)." exit 1 fi - rho version || rho --version || true + decoder version || decoder --version || true - name: Trust workspace (folder trust) - run: rho trust add --reason "github-actions ci" || true + run: decoder 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 }} - RHO_PROMPT: ${{ github.event.inputs.prompt || 'Summarize the changes in this PR (git log/diff) and list risks.' }} + DECODER_PROMPT: ${{ github.event.inputs.prompt || 'Summarize the changes in this PR (git log/diff) and list risks.' }} run: | set -euo pipefail - PROMPT="${RHO_PROMPT}" - rho exec \ + PROMPT="${DECODER_PROMPT}" + decoder exec \ --ephemeral \ --json \ --auto full \ --max-turns 20 \ - "$PROMPT" | tee rho-result.json + "$PROMPT" | tee decoder-result.json - name: Upload agent result if: always() uses: actions/upload-artifact@v4 with: - name: rho-result - path: rho-result.json + name: decoder-result + path: decoder-result.json diff --git a/examples/github/decoder-delivery-context.yml b/examples/github/decoder-delivery-context.yml new file mode 100644 index 000000000..389055579 --- /dev/null +++ b/examples/github/decoder-delivery-context.yml @@ -0,0 +1,47 @@ +# Decoder Delivery Context +# +# Copy to .github/workflows/decoder-delivery-context.yml. This workflow assumes +# `decoder` is available on PATH (install it using your team's normal release +# mechanism before this reporting step). +# +# Create a dedicated, revocable Decoder Cloud device token for CI. Do not reuse a +# developer's local device token. Store all three values as repository secrets. +name: Decoder 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 Decoder here using the release package your organization pins. + # The remaining steps are intentionally independent of the installer. + - name: Connect CI device to Decoder Cloud + run: | + decoder cloud connect \ + --endpoint "$DECODER_CLOUD_URL" \ + --device-id "$DECODER_CLOUD_DEVICE_ID" \ + --project-id "$DECODER_CLOUD_PROJECT_ID" \ + --token "$DECODER_CLOUD_DEVICE_TOKEN" + env: + DECODER_CLOUD_URL: ${{ secrets.DECODER_CLOUD_URL }} + DECODER_CLOUD_DEVICE_ID: ${{ secrets.DECODER_CLOUD_DEVICE_ID }} + DECODER_CLOUD_PROJECT_ID: ${{ secrets.DECODER_CLOUD_PROJECT_ID }} + DECODER_CLOUD_DEVICE_TOKEN: ${{ secrets.DECODER_CLOUD_DEVICE_TOKEN }} + + - name: Report successful CI run + if: ${{ success() }} + run: decoder cloud context --ci-status succeeded + + - name: Report failed CI run + if: ${{ failure() }} + run: decoder cloud context --ci-status failed diff --git a/examples/github/rho-issue-resolver.yml b/examples/github/decoder-issue-resolver.yml similarity index 56% rename from examples/github/rho-issue-resolver.yml rename to examples/github/decoder-issue-resolver.yml index 4466ae0e3..76a58ada3 100644 --- a/examples/github/rho-issue-resolver.yml +++ b/examples/github/decoder-issue-resolver.yml @@ -1,16 +1,16 @@ -# Rho Issue Resolver +# Decoder Issue Resolver # -# Copy this file to .github/workflows/rho-issue-resolver.yml in your repository. +# Copy this file to .github/workflows/decoder-issue-resolver.yml in your repository. # -# It runs the Rho agent in two scenarios: -# 1. Automation mode - when an issue is labeled `rho-agent`, Rho reads the +# It runs the Decoder agent in two scenarios: +# 1. Automation mode - when an issue is labeled `decoder-agent`, Decoder reads the # issue title + body and attempts to resolve it autonomously. -# 2. Interactive mode - when someone comments "@rho ..." on an issue or PR, -# Rho replies conversationally to the request. +# 2. Interactive mode - when someone comments "@decoder ..." on an issue or PR, +# Decoder replies conversationally to the request. # -# The mode is auto-detected by `rho exec` from the GitHub event payload, so a +# The mode is auto-detected by `decoder exec` from the GitHub event payload, so a # single composite action handles both paths. -name: Rho Issue Resolver +name: Decoder Issue Resolver on: issues: @@ -26,12 +26,12 @@ permissions: pull-requests: write jobs: - rho: - # Run on the `rho-agent` label, or on any comment mentioning @rho. + decoder: + # Run on the `decoder-agent` label, or on any comment mentioning @decoder. if: >- - (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')) + (github.event_name == 'issues' && github.event.label.name == 'decoder-agent') || + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@decoder')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@decoder')) runs-on: ubuntu-latest steps: - name: Checkout @@ -39,11 +39,11 @@ jobs: with: fetch-depth: 0 - - name: Run Rho - id: rho - uses: GrayCodeAI/rho/.github/actions/rho@main + - name: Run Decoder + id: decoder + uses: GrayCodeAI/decoder/.github/actions/decoder@main with: - # Leave prompt empty so Rho derives it from the triggering event. + # Leave prompt empty so Decoder 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 Rho response as a comment - if: ${{ steps.rho.outputs.response != '' }} + - name: Post Decoder response as a comment + if: ${{ steps.decoder.outputs.response != '' }} uses: actions/github-script@v7 with: script: | - const body = `🦅 **Rho**\n\n${{ toJSON(steps.rho.outputs.response) }}`; + const body = `🦅 **Decoder**\n\n${{ toJSON(steps.decoder.outputs.response) }}`; const issueNumber = context.issue.number; if (issueNumber) { await github.rest.issues.createComment({ diff --git a/examples/github/rho-delivery-context.yml b/examples/github/rho-delivery-context.yml deleted file mode 100644 index dfcd1b864..000000000 --- a/examples/github/rho-delivery-context.yml +++ /dev/null @@ -1,47 +0,0 @@ -# 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/go.mod b/go.mod index 388fb9360..e0fe2b2e5 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ -module github.com/GrayCodeAI/rho +module github.com/GrayCodeAI/decoder -go 1.26.6 +go 1.27.2 // v0.1.0, v0.1.1 and v0.2.0 were tagged before the rename: their go.mod -// declares module github.com/GrayCodeAI/hawk and they contain no cmd/rho, so -// they cannot be used as github.com/GrayCodeAI/rho. +// declares module github.com/GrayCodeAI/hawk and they contain no cmd/decoder, so +// they cannot be used as github.com/GrayCodeAI/decoder. retract [v0.1.0, v0.2.0] // The charmbracelet v2 modules (bubbles, bubbletea, lipgloss, glamour, huh) have @@ -16,7 +16,7 @@ require ( charm.land/bubbles/v2 v2.1.1 charm.land/bubbletea/v2 v2.0.8 charm.land/lipgloss/v2 v2.0.5 - github.com/GrayCodeAI/flux v0.0.1 + github.com/GrayCodeAI/mux v0.0.2-0.20261011023222-e418cdb5eb91 github.com/alecthomas/chroma/v2 v2.26.1 github.com/bwmarrin/discordgo v0.28.1 github.com/charmbracelet/x/ansi v0.11.7 @@ -27,60 +27,53 @@ require ( github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 github.com/gofrs/flock v0.13.0 github.com/google/uuid v1.6.0 + github.com/klauspost/compress v1.19.1 github.com/mattn/go-runewidth v0.0.27 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/tetratelabs/wazero v1.12.0 - go.opentelemetry.io/otel v1.44.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 - go.opentelemetry.io/otel/log v0.20.0 - go.opentelemetry.io/otel/sdk v1.44.0 - go.opentelemetry.io/otel/sdk/log v0.20.0 - go.opentelemetry.io/otel/sdk/metric v1.44.0 - go.opentelemetry.io/otel/trace v1.44.0 + github.com/tiktoken-go/tokenizer v0.8.0 + go.opentelemetry.io/otel v1.45.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 + go.opentelemetry.io/otel/log v0.21.0 + go.opentelemetry.io/otel/metric v1.45.0 + go.opentelemetry.io/otel/sdk v1.45.0 + go.opentelemetry.io/otel/sdk/log v0.21.0 + go.opentelemetry.io/otel/sdk/metric v1.45.0 + go.opentelemetry.io/otel/trace v1.45.0 golang.org/x/image v0.45.0 - golang.org/x/mod v0.38.0 - golang.org/x/sys v0.47.0 - golang.org/x/term v0.45.0 - golang.org/x/text v0.41.0 + golang.org/x/mod v0.41.0 + golang.org/x/sys v0.48.0 + golang.org/x/term v0.46.0 + golang.org/x/text v0.42.0 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.51.0 ) -require ( - github.com/chromedp/sysutil v1.1.0 // indirect - github.com/gobwas/httphead v0.1.0 // indirect - github.com/gobwas/pool v0.2.1 // indirect - github.com/gobwas/ws v1.4.0 // indirect -) - -require ( - github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect - github.com/charmbracelet/x/termios v0.1.1 // indirect - github.com/charmbracelet/x/windows v0.2.2 // indirect - github.com/danieljoos/wincred v1.2.3 // indirect - github.com/godbus/dbus/v5 v5.2.2 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/klauspost/compress v1.19.1 - github.com/zalando/go-keyring v0.2.8 // indirect - golang.org/x/crypto v0.55.0 // indirect - golang.org/x/sync v0.22.0 // indirect -) - require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260703014108-f5a850f9c2b7 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect + github.com/chromedp/sysutil v1.1.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/danieljoos/wincred v1.2.3 // indirect github.com/dlclark/regexp2/v2 v2.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.4.0 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect @@ -89,17 +82,18 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/tiktoken-go/tokenizer v0.8.0 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/zalando/go-keyring v0.2.8 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 - go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect + go.opentelemetry.io/proto/otlp v1.11.0 // indirect + golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect - golang.org/x/net v0.57.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.82.1 // indirect + golang.org/x/net v0.60.0 // indirect + golang.org/x/sync v0.23.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/grpc v1.83.1 // indirect google.golang.org/protobuf v1.36.11 // indirect modernc.org/libc v1.72.5 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 5bf61a2ff..965423d4d 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY= charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss= charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY= charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc= -github.com/GrayCodeAI/flux v0.0.1 h1:HGY6SIEUf1kPQUt4am/5kdgc0GmQY3bh9otJagz651Q= -github.com/GrayCodeAI/flux v0.0.1/go.mod h1:NpD0KF8gRaZuPChgpe3tEcwG9zOzwoJPOTZtUEiUz3c= +github.com/GrayCodeAI/mux v0.0.2-0.20261011023222-e418cdb5eb91 h1:Ld1uxyr2M1pI7O6gHeUXOOomk9/g+EdvQ8S0yDCcoN0= +github.com/GrayCodeAI/mux v0.0.2-0.20261011023222-e418cdb5eb91/go.mod h1:fX5bQXz4QsN+NU1d58JYRttTM9SaIl94V6iIjbkSElw= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= @@ -64,8 +64,8 @@ github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxv github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 h1:KZaTBSyshWX3MP5jukJcNSuXDQTO+rNpt0J564dX/eg= github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -147,70 +147,70 @@ github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPc github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 h1:owlhcJ3QO3X0YTDTCcDZ4V+6aVDkWbNmBoQ5NUp7Oww= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0/go.mod h1:MP4eemTiI9zC8fgg+DYynhYDYf3ba72S376TvP+Ye0Q= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= -go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs= -go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY= -go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU= -go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg= -go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0 h1:fvNHGyo3CdRv/DQveXqhqBxnKTDyRaC5sMSQxilX/A0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.21.0/go.mod h1:zyGrjRKL2B/6+Jc/m4/otPoZqV2MY9ZjC/aBraRO7zc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= +go.opentelemetry.io/otel/log v0.21.0 h1:SLsVDGmtyBrdw8/a2Z0bOIxou/+bN4z56GebH7T0LvA= +go.opentelemetry.io/otel/log v0.21.0/go.mod h1:iReetQrZL9Wyg84cCkOoCmqDHS5RCFfyxC7J+r8fn8g= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= +go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/sdk/log v0.21.0 h1:QsE7XSR0ktQdKmRKGnR+f1ObGF32WG+7MER/P9KgmYc= +go.opentelemetry.io/otel/sdk/log v0.21.0/go.mod h1:m9mApjCoD2/1QuKCAptjv+BrG9WKOvQLVdNx+iBldTo= +go.opentelemetry.io/otel/sdk/log/logtest v0.21.0 h1:X+JBBgKlswCGYsmgL0CnoUUtlE//VB345c84jYAYkdQ= +go.opentelemetry.io/otel/sdk/log/logtest v0.21.0/go.mod h1:HD1575K8e6sIFBBDd5tZB3t9DlMytWXq9FuR+Y4rfjE= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= +go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk= +go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= +google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/install.sh b/install.sh index a73380ab0..326b69412 100755 --- a/install.sh +++ b/install.sh @@ -1,42 +1,41 @@ #!/bin/sh -# Install rho (the GrayCode terminal AI coding agent) from a GitHub release. +# Install decoder (the GrayCode terminal AI coding agent) from a GitHub release. # -# curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/rho/v0.3.0/install.sh | sh -# curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/rho/v0.3.0/install.sh | sh -s -- --version 0.3.0 --prefix "$HOME/.rho" +# curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/decoder/main/install.sh | sh +# curl -fsSL https://raw.githubusercontent.com/GrayCodeAI/decoder/main/install.sh | sh -s -- --version "$(cat VERSION)" --prefix "$HOME/.decoder" # # Steps: -# 1. Resolve the version: --version / RHO_VERSION, otherwise the latest -# GitHub release. Releases before v0.3.0 predate the rename to rho (they +# 1. Resolve the version: --version / DECODER_VERSION, otherwise the latest +# GitHub release. Historical hawk-era releases predate the rename to decoder (they # ship hawk_* archives) and are refused. -# 2. Download rho___.tar.gz (.zip on Windows) and +# 2. Download decoder___.tar.gz (.zip on Windows) and # checksums.txt from the release. # 3. When cosign is installed, verify checksums.txt against the release's # Sigstore bundle (checksums.txt.sigstore.json) with the exact identity of # this repository's release workflow for that tag. A missing or invalid # signature aborts the install. Without cosign the signature is NOT -# verified and the script says so; RHO_REQUIRE_COSIGN=1 makes cosign +# verified and the script says so; DECODER_REQUIRE_COSIGN=1 makes cosign # mandatory. # 4. Always verify the archive's SHA-256 against checksums.txt. -# 5. Install /bin/rho- and atomically point /bin/rho -# at it (a copy named rho.exe on Windows). +# 5. Install /bin/decoder- and atomically point /bin/decoder +# at it (a copy named decoder.exe on Windows). # # Options: -# --version install this version (e.g. 0.3.0 or v0.3.0) -# --prefix install under /bin (default: $RHO_HOME or ~/.rho) +# --version install this version (e.g. X.Y.Z or vX.Y.Z) +# --prefix install under /bin (default: $DECODER_HOME or ~/.decoder) # -h, --help show this help # # Environment: -# RHO_VERSION= same as --version (the flag wins) -# RHO_HOME= same as --prefix (the flag wins) -# RHO_REQUIRE_COSIGN=1 fail unless the cosign signature can be verified -# RHO_INSTALL_REPO= install from a fork's releases (default GrayCodeAI/rho); +# DECODER_VERSION= same as --version (the flag wins) +# DECODER_HOME= same as --prefix (the flag wins) +# DECODER_REQUIRE_COSIGN=1 fail unless the cosign signature can be verified +# DECODER_INSTALL_REPO= install from a fork's releases (default GrayCodeAI/decoder); # the cosign identity follows the chosen repository set -eu -MIN_VERSION="0.3.0" -BINARY="rho" +BINARY="decoder" OIDC_ISSUER="https://token.actions.githubusercontent.com" -REPO="${RHO_INSTALL_REPO:-GrayCodeAI/rho}" +REPO="${DECODER_INSTALL_REPO:-GrayCodeAI/decoder}" say() { printf '%s\n' "$*"; } warn() { printf 'WARNING: %s\n' "$*" >&2; } @@ -47,15 +46,15 @@ die() { usage() { cat <<'EOF' -Install rho from a GitHub release. +Install decoder from a GitHub release. Usage: install.sh [--version ] [--prefix ] - --version install this version (e.g. 0.3.0); default: latest release - --prefix install under /bin; default: $RHO_HOME or ~/.rho + --version install this version (e.g. 1.2.3); default: latest release + --prefix install under /bin; default: $DECODER_HOME or ~/.decoder -h, --help show this help -Environment: RHO_VERSION, RHO_HOME, RHO_REQUIRE_COSIGN=1, RHO_INSTALL_REPO. +Environment: DECODER_VERSION, DECODER_HOME, DECODER_REQUIRE_COSIGN=1, DECODER_INSTALL_REPO. EOF } @@ -82,21 +81,11 @@ sha256_of() { fi } -# version_core_ge A B — true when MAJOR.MINOR.PATCH of A >= that of B. -version_core_ge() { - awk -v a="$1" -v b="$2" 'BEGIN { - split(a, x, /[.+-]/); split(b, y, /[.+-]/) - for (i = 1; i <= 3; i++) { - if (x[i] + 0 > y[i] + 0) exit 0 - if (x[i] + 0 < y[i] + 0) exit 1 - } - exit 0 - }' -} + # --- Arguments ----------------------------------------------------------------- -VERSION_PIN="${RHO_VERSION:-}" -PREFIX="${RHO_HOME:-}" +VERSION_PIN="${DECODER_VERSION:-}" +PREFIX="${DECODER_HOME:-}" while [ $# -gt 0 ]; do case "$1" in --version) @@ -131,11 +120,11 @@ done if [ -z "$PREFIX" ]; then [ -n "${HOME:-}" ] || die "HOME is not set; pass --prefix " - PREFIX="$HOME/.rho" + PREFIX="$HOME/.decoder" fi if ! printf '%s' "$REPO" | grep -Eq '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$'; then - die "RHO_INSTALL_REPO must look like owner/repo (got: $REPO)" + die "DECODER_INSTALL_REPO must look like owner/repo (got: $REPO)" fi command -v curl >/dev/null 2>&1 || die "curl is required" @@ -176,23 +165,25 @@ else fi if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then - die "invalid version '$VERSION' (expected MAJOR.MINOR.PATCH, e.g. 0.3.0)" -fi -if ! version_core_ge "$VERSION" "$MIN_VERSION"; then - die "v${VERSION} predates the rename to rho: releases before v${MIN_VERSION} ship hawk_* archives that this script cannot install. Install v${MIN_VERSION} or later, or build from source (https://github.com/${REPO}#readme)." + die "invalid version '$VERSION' (expected MAJOR.MINOR.PATCH, e.g. 1.2.3)" fi +case "$VERSION" in + 0.1.0 | 0.1.1 | 0.2.0) + die "v${VERSION} predates the rename to decoder: this historical tag ships hawk_* archives. Choose a decoder release or build from source (https://github.com/${REPO}#readme)." + ;; +esac TAG="v${VERSION}" BASE_URL="https://github.com/${REPO}/releases/download/${TAG}" ARCHIVE_NAME="${BINARY}_${VERSION}_${OS}_${ARCH}.${ARCHIVE_EXT}" -TMP=$(mktemp -d 2>/dev/null || mktemp -d -t rho-install) +TMP=$(mktemp -d 2>/dev/null || mktemp -d -t decoder-install) trap 'rm -rf "$TMP"' EXIT trap 'exit 130' INT trap 'exit 143' TERM trap 'exit 129' HUP -say "Downloading rho ${TAG} for ${OS}/${ARCH}..." +say "Downloading decoder ${TAG} for ${OS}/${ARCH}..." fetch "${BASE_URL}/${ARCHIVE_NAME}" "$TMP/$ARCHIVE_NAME" || die "could not download ${BASE_URL}/${ARCHIVE_NAME} (does release ${TAG} exist for ${OS}/${ARCH}?)" fetch "${BASE_URL}/checksums.txt" "$TMP/checksums.txt" || @@ -214,12 +205,12 @@ if command -v cosign >/dev/null 2>&1; then die "cosign could not verify checksums.txt for ${TAG} against ${IDENTITY}; the release may have been tampered with (cosign v2.4 or later is required, v3 recommended)" fi SIGNATURE_STATUS="verified with cosign (${IDENTITY})" -elif [ "${RHO_REQUIRE_COSIGN:-0}" = 1 ]; then - die "RHO_REQUIRE_COSIGN=1 but cosign is not installed (see https://docs.sigstore.dev/cosign/system_config/installation/)" +elif [ "${DECODER_REQUIRE_COSIGN:-0}" = 1 ]; then + die "DECODER_REQUIRE_COSIGN=1 but cosign is not installed (see https://docs.sigstore.dev/cosign/system_config/installation/)" else warn "cosign is not installed, so the release signature was NOT verified." warn "The SHA-256 check only detects corrupted downloads, not a tampered release." - warn "Install cosign (https://docs.sigstore.dev) or set RHO_REQUIRE_COSIGN=1 to require it." + warn "Install cosign (https://docs.sigstore.dev) or set DECODER_REQUIRE_COSIGN=1 to require it." SIGNATURE_STATUS="NOT verified (cosign not installed)" fi @@ -250,27 +241,27 @@ fi # A binary is never overwritten in place. On macOS (and any codesigned # platform) replacing a file that a running process has mmap'd invalidates the # kernel's code-signature cache and the kernel then SIGKILLs that process. -# Installing into a per-version file and swapping a symlink keeps running rho +# Installing into a per-version file and swapping a symlink keeps running decoder # processes on the old inode. The symlink is created under a temporary name and # renamed into place so the swap is atomic. Windows lacks reliable non-admin # symlinks, so the launcher there is a plain copy. BINDIR="$PREFIX/bin" mkdir -p "$BINDIR" if [ "$OS" = windows ]; then - mv -f "$TMP/extract/$BIN_NAME" "$BINDIR/rho-${VERSION}.exe" - cp -f "$BINDIR/rho-${VERSION}.exe" "$BINDIR/rho.exe" - INSTALLED="$BINDIR/rho-${VERSION}.exe (launcher: $BINDIR/rho.exe)" + mv -f "$TMP/extract/$BIN_NAME" "$BINDIR/decoder-${VERSION}.exe" + cp -f "$BINDIR/decoder-${VERSION}.exe" "$BINDIR/decoder.exe" + INSTALLED="$BINDIR/decoder-${VERSION}.exe (launcher: $BINDIR/decoder.exe)" else - mv -f "$TMP/extract/$BIN_NAME" "$BINDIR/rho-${VERSION}" - chmod 0755 "$BINDIR/rho-${VERSION}" - rm -f "$BINDIR/rho.tmp" - ln -s "rho-${VERSION}" "$BINDIR/rho.tmp" - mv -f "$BINDIR/rho.tmp" "$BINDIR/rho" - INSTALLED="$BINDIR/rho-${VERSION} (linked: $BINDIR/rho)" + mv -f "$TMP/extract/$BIN_NAME" "$BINDIR/decoder-${VERSION}" + chmod 0755 "$BINDIR/decoder-${VERSION}" + rm -f "$BINDIR/decoder.tmp" + ln -s "decoder-${VERSION}" "$BINDIR/decoder.tmp" + mv -f "$BINDIR/decoder.tmp" "$BINDIR/decoder" + INSTALLED="$BINDIR/decoder-${VERSION} (linked: $BINDIR/decoder)" fi say "" -say "Installed rho ${TAG} to ${INSTALLED}" +say "Installed decoder ${TAG} to ${INSTALLED}" say " checksum: verified (SHA-256 from checksums.txt)" say " signature: ${SIGNATURE_STATUS}" say "" @@ -282,5 +273,5 @@ case ":${PATH:-}:" in say "" ;; esac -say "Restart running rho sessions to pick up the new binary; they keep running" +say "Restart running decoder sessions to pick up the new binary; they keep running" say "the previous version until restarted." diff --git a/internal/acp/acp_extra_test.go b/internal/acp/acp_extra_test.go index bf0452e73..d297daa8a 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/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) // --- Error factory for testing --- diff --git a/internal/acp/client.go b/internal/acp/client.go index d09f76ae1..43c7564a5 100644 --- a/internal/acp/client.go +++ b/internal/acp/client.go @@ -93,7 +93,7 @@ func Start(ctx context.Context, command string, args []string, opts ...ClientOpt o(&opt) } - cmd := exec.Command(command, args...) + cmd := exec.CommandContext(context.Background(), command, args...) // #nosec G204 -- host-configured ACP executable and argv; process lifetime is managed by Client.Close setCmdProcessGroup(cmd) if len(opt.Env) > 0 { cmd.Env = append(cmd.Environ(), opt.Env...) diff --git a/internal/acp/client_test.go b/internal/acp/client_test.go index 79f9d3ed9..7833bb0c7 100644 --- a/internal/acp/client_test.go +++ b/internal/acp/client_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine" + "github.com/GrayCodeAI/decoder/internal/engine" ) // mockSession creates a minimal engine session for testing. diff --git a/internal/acp/content.go b/internal/acp/content.go index 2f3969e37..9792d7178 100644 --- a/internal/acp/content.go +++ b/internal/acp/content.go @@ -17,7 +17,7 @@ import ( "regexp" "strings" - "github.com/GrayCodeAI/rho/internal/attachment" + "github.com/GrayCodeAI/decoder/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 2e317ec76..5286edd02 100644 --- a/internal/acp/content_test.go +++ b/internal/acp/content_test.go @@ -16,7 +16,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/attachment" + "github.com/GrayCodeAI/decoder/internal/attachment" ) // pngFixture encodes a 4x3 NRGBA raster as PNG bytes. diff --git a/internal/acp/server.go b/internal/acp/server.go index 5233f412d..9aff98bab 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 rho, exposing +// Package acp implements an Agent Client Protocol (ACP) server for decoder, 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 rho's local tools; client fs routing is intentionally out of scope. +// use decoder's local tools; client fs routing is intentionally out of scope. package acp import ( @@ -20,12 +20,12 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "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" + "github.com/GrayCodeAI/decoder/internal/attachment" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/session" + statussnapshot "github.com/GrayCodeAI/decoder/internal/status" ) // ProtocolVersion is the ACP protocol version this server implements. @@ -188,10 +188,10 @@ func (s *Server) handle(ctx context.Context, msg rpcMessage) { "audio": false, }, }, - // Rho control-plane metadata for IDE clients that want it. - "rhoCapabilities": map[string]any{ + // Decoder control-plane metadata for IDE clients that want it. + "decoderCapabilities": map[string]any{ "workModes": []string{"plan", "act", "review"}, - // Rho has no local OS/container sandbox runtime. These are the + // Decoder has no local OS/container sandbox runtime. These are the // only isolation choices the ACP surface can actually honor. "isolation": []string{"none", "worktree"}, "folderTrust": true, @@ -328,8 +328,8 @@ func (s *Server) handleSessionNew(msg rpcMessage) { s.reply(msg.ID, map[string]any{ "sessionId": id, - // Rho extensions (ignored by clients that only read sessionId). - "rho": map[string]any{ + // Decoder extensions (ignored by clients that only read sessionId). + "decoder": 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 f49539308..8b25d584d 100644 --- a/internal/acp/server_test.go +++ b/internal/acp/server_test.go @@ -10,9 +10,9 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/tool" ) // testFactory builds a session backed by the engine's canned mock chat client, @@ -78,17 +78,17 @@ func TestACP_InitializeAndPrompt(t *testing.T) { case hasID(m, 1): gotInit = true var r struct { - ProtocolVersion int `json:"protocolVersion"` - RhoCapabilities struct { + ProtocolVersion int `json:"protocolVersion"` + DecoderCapabilities struct { Isolation []string `json:"isolation"` - } `json:"rhoCapabilities"` + } `json:"decoderCapabilities"` } _ = json.Unmarshal(m.Result, &r) if r.ProtocolVersion != ProtocolVersion { t.Errorf("initialize protocolVersion = %d, want %d", r.ProtocolVersion, ProtocolVersion) } - if strings.Join(r.RhoCapabilities.Isolation, ",") != "none,worktree" { - t.Errorf("initialize isolation = %v, want [none worktree]", r.RhoCapabilities.Isolation) + if strings.Join(r.DecoderCapabilities.Isolation, ",") != "none,worktree" { + t.Errorf("initialize isolation = %v, want [none worktree]", r.DecoderCapabilities.Isolation) } case hasID(m, 2): gotNew = true @@ -144,7 +144,7 @@ func TestACP_ParseError(t *testing.T) { func TestACP_SessionLoad(t *testing.T) { tempDir := t.TempDir() - t.Setenv("RHO_SESSIONS_DIR", tempDir) + t.Setenv("DECODER_SESSIONS_DIR", tempDir) // Create and persist a session sessID := "acp-load-test-1" @@ -195,7 +195,7 @@ func TestACP_SessionLoad(t *testing.T) { func TestACP_SessionList(t *testing.T) { tempDir := t.TempDir() - t.Setenv("RHO_SESSIONS_DIR", tempDir) + t.Setenv("DECODER_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 38651531d..d7307f60d 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{ - ".rho/verify/environment.json": "{not json", + ".decoder/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 840b87889..9268f7c45 100644 --- a/internal/appverify/manifest.go +++ b/internal/appverify/manifest.go @@ -6,13 +6,13 @@ import ( "os" "path/filepath" - "github.com/GrayCodeAI/rho/internal/safewrite" + "github.com/GrayCodeAI/decoder/internal/safewrite" ) // ManifestPath returns the location of the persisted verify environment -// manifest for a project: /.rho/verify/environment.json. +// manifest for a project: /.decoder/verify/environment.json. func ManifestPath(root string) string { - return filepath.Join(root, ".rho", "verify", "environment.json") + return filepath.Join(root, ".decoder", "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 377ffc286..1eae8166f 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 = ".rho/verify/artifacts" +const EvidenceDir = ".decoder/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 fb8855b3c..752cd3b87 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -13,8 +13,8 @@ import ( "runtime" "strings" - "github.com/GrayCodeAI/rho/internal/safewrite" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/safewrite" + "github.com/GrayCodeAI/decoder/internal/storage" ) // TokenStore manages authentication tokens. @@ -43,7 +43,7 @@ func (t *TokenStore) Has(provider string) bool { return ok } -// SecureStorage stores secrets in the OS credential store where rho has one: +// SecureStorage stores secrets in the OS credential store where decoder has one: // the macOS Keychain and the Windows Credential Manager. On every other // platform (Linux included; there is no Secret Service integration yet) it // falls back to a plaintext JSON file at TokenFilePath, written atomically diff --git a/internal/auth/auth_extra_test.go b/internal/auth/auth_extra_test.go index b5f29023e..60eb84924 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("RHO_CONFIG_DIR", t.TempDir()) + t.Setenv("DECODER_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 67b401578..3a39fb11f 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/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("rho-test") + ss := NewSecureStorage("decoder-test") if ss == nil { t.Fatal("NewSecureStorage returned nil") } - if ss.service != "rho-test" { - t.Errorf("service = %q, want %q", ss.service, "rho-test") + if ss.service != "decoder-test" { + t.Errorf("service = %q, want %q", ss.service, "decoder-test") } }) @@ -132,7 +132,7 @@ func TestSecureStorage(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", dir) - ss := NewSecureStorage("rho-test") + ss := NewSecureStorage("decoder-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("rho-test") + ss := NewSecureStorage("decoder-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("rho-test") + ss := NewSecureStorage("decoder-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("rho-test") + ss := NewSecureStorage("decoder-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("rho-test") + ss := NewSecureStorage("decoder-test") if err := ss.setFile("provider1", "token1"); err != nil { t.Fatal(err) } diff --git a/internal/auth/plaintext_store_test.go b/internal/auth/plaintext_store_test.go index 25f8eed97..d4031a207 100644 --- a/internal/auth/plaintext_store_test.go +++ b/internal/auth/plaintext_store_test.go @@ -25,7 +25,7 @@ func TestUsesPlaintextTokenFileOnlyWithoutOSStore(t *testing.T) { func TestCredentialStoreNameNamesThePlaintextFile(t *testing.T) { dir := t.TempDir() - t.Setenv("RHO_CONFIG_DIR", dir) + t.Setenv("DECODER_CONFIG_DIR", dir) if TokenFilePath() != filepath.Join(dir, ".tokens") { t.Fatalf("TokenFilePath = %q", TokenFilePath()) } diff --git a/internal/auth/storage_extra_test.go b/internal/auth/storage_extra_test.go index 8283a7a93..e209012b0 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("RHO_CONFIG_DIR", tmpDir) + t.Setenv("DECODER_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("RHO_CONFIG_DIR", tmpDir) + t.Setenv("DECODER_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("RHO_CONFIG_DIR", tmpDir) + t.Setenv("DECODER_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("RHO_CONFIG_DIR", tmpDir) + t.Setenv("DECODER_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("RHO_CONFIG_DIR", tmpDir) + t.Setenv("DECODER_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 7462398b6..41efb3572 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 rho runs in a project that has no context files (AGENTS.md / -// RHO.md / CONTEXT.md). It mirrors the behaviour of the `init-deep` skill but +// first time decoder runs in a project that has no context files (AGENTS.md / +// DECODER.md / CONTEXT.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 / `rho init`). When no runner is wired, +// init analysis (the init-deep skill / `decoder 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/rho/internal/config" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/config" + "github.com/GrayCodeAI/decoder/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 = "RHO_DISABLE_AUTO_INIT" +const disableEnv = "DECODER_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 rho. -var contextFiles = []string{"AGENTS.md", "RHO.md", "CONTEXT.md"} +// the convention files recognized elsewhere in decoder. +var contextFiles = []string{"AGENTS.md", "DECODER.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 RHO_DISABLE_AUTO_INIT -> skip. +// 1. Disabled via DECODER_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 3f7992986..3e86fdd28 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, "RHO.md"), []byte("# x"), 0o644); err != nil { + if err := os.WriteFile(filepath.Join(root, "DECODER.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("RHO_STATE_DIR", stateFile) + t.Setenv("DECODER_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("RHO_STATE_DIR", filepath.Join(t.TempDir(), "state")) + t.Setenv("DECODER_STATE_DIR", filepath.Join(t.TempDir(), "state")) } diff --git a/internal/catalogtest/install.go b/internal/catalogtest/install.go index d2ead8838..46637f39d 100644 --- a/internal/catalogtest/install.go +++ b/internal/catalogtest/install.go @@ -16,11 +16,11 @@ var ( globalPath string ) -// InstallGlobal writes the test catalog to a temp file and sets FLUX_MODEL_CATALOG_PATH. +// InstallGlobal writes the test catalog to a temp file and sets MUX_MODEL_CATALOG_PATH. // Call from TestMain; returns cleanup to unset env. func InstallGlobal() (cleanup func()) { globalOnce.Do(func() { - dir, err := os.MkdirTemp("", "rho-catalog-*") + dir, err := os.MkdirTemp("", "decoder-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. @@ -30,19 +30,19 @@ func InstallGlobal() (cleanup func()) { if err := os.WriteFile(globalPath, minimalCatalogJSON, 0o600); err != nil { panic("catalogtest: failed to write catalog: " + err.Error()) } - _ = os.Setenv("FLUX_MODEL_CATALOG_PATH", globalPath) + _ = os.Setenv("MUX_MODEL_CATALOG_PATH", globalPath) }) return func() { - _ = os.Unsetenv("FLUX_MODEL_CATALOG_PATH") + _ = os.Unsetenv("MUX_MODEL_CATALOG_PATH") } } -// Install sets FLUX_MODEL_CATALOG_PATH for a single test (per-test temp file). +// Install sets MUX_MODEL_CATALOG_PATH for a single test (per-test temp file). func Install(t testing.TB) { t.Helper() path := filepath.Join(t.TempDir(), "model_catalog.json") if err := os.WriteFile(path, minimalCatalogJSON, 0o600); err != nil { t.Fatalf("catalogtest: failed to write catalog: %v", err) } - t.Setenv("FLUX_MODEL_CATALOG_PATH", path) + t.Setenv("MUX_MODEL_CATALOG_PATH", path) } diff --git a/internal/circuitbreaker/circuitbreaker.go b/internal/circuitbreaker/circuitbreaker.go index bd264f89a..5037e5ef5 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. Rho uses it to stop runaway auto-compaction (an +// failure re-opens it. Decoder 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 43079550f..9fa4e2b9c 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 rho, flux, and other packages. +// Useful for finding relationships between decoder, mux, 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 rho calls a symbol in flux, this traces that cross-repo dependency. +// If a symbol in decoder calls a symbol in mux, 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, rho calling flux functions. +// For example, decoder calling mux 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 d9428feb1..40c5a8504 100644 --- a/internal/config/catalog_api.go +++ b/internal/config/catalog_api.go @@ -5,8 +5,8 @@ import ( "sort" "strings" - llm "github.com/GrayCodeAI/flux/llm" - gw "github.com/GrayCodeAI/rho/internal/provider/gateway" + gw "github.com/GrayCodeAI/decoder/internal/provider/gateway" + llm "github.com/GrayCodeAI/mux/llm" ) type GatewayStatus struct { @@ -22,19 +22,19 @@ type GatewayStatus struct { DNSHost string } -// IsCatalogCacheRequired reports whether an Flux operation failed because +// IsCatalogCacheRequired reports whether an Mux operation failed because // the local model catalog has not been created yet. func IsCatalogCacheRequired(err error) bool { return gw.IsCatalogCacheRequired(err) } -// RegisteredProviderCount returns Flux's canonical first-class provider count. +// RegisteredProviderCount returns Mux's canonical first-class provider count. func RegisteredProviderCount() int { return gw.RegisteredProviderCount() } func AllCatalogProviders() []string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil } @@ -57,7 +57,7 @@ func AllCatalogProviders() []string { } func AllSetupGateways() []string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil } @@ -122,7 +122,7 @@ func GatewaySupportsLiveDiscovery(providerID string) bool { } func ActiveGateway(ctx context.Context) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } @@ -137,7 +137,7 @@ func ActiveGateway(ctx context.Context) string { } func GatewayStatuses(ctx context.Context, activeProvider, activeModel string) []GatewayStatus { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil } @@ -163,7 +163,7 @@ func GatewayStatuses(ctx context.Context, activeProvider, activeModel string) [] } func GatewayForModel(modelID string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } @@ -171,7 +171,7 @@ func GatewayForModel(modelID string) string { } func ShouldClearSelectionAfterCredentialRemove(ctx context.Context, removedProvider string) bool { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return true } @@ -183,7 +183,7 @@ func ShouldClearSelectionAfterCredentialRemove(ctx context.Context, removedProvi } func ClearActiveSelection(ctx context.Context) error { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return err } @@ -191,7 +191,7 @@ func ClearActiveSelection(ctx context.Context) error { } func SyncSelectionWithCredentials(ctx context.Context) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return } @@ -213,7 +213,7 @@ func SyncSelectionWithCredentials(ctx context.Context) { } func DefaultModelForProvider(provider string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } @@ -221,7 +221,7 @@ func DefaultModelForProvider(provider string) string { } func DefaultModelForProviderWithSettings(settings Settings, provider string) string { - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { return "" } @@ -248,7 +248,7 @@ func ModelIDsForProvider(provider string) ([]string, error) { } func CheapestModelForProvider(provider, fallback string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return fallback } @@ -256,7 +256,7 @@ func CheapestModelForProvider(provider, fallback string) string { } func ProviderOfModel(modelName string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } @@ -264,7 +264,7 @@ func ProviderOfModel(modelName string) string { } func ProviderOfModelWithSettings(settings Settings, modelName string) string { - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { return "" } @@ -272,7 +272,7 @@ func ProviderOfModelWithSettings(settings Settings, modelName string) string { } func ExampleModelHints() (string, string) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "an Anthropic model", "an OpenAI model" } @@ -281,7 +281,7 @@ func ExampleModelHints() (string, string) { } func AllCanonicalModelIDs() []string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil } @@ -298,7 +298,7 @@ func AllCanonicalModelIDs() []string { } func ProviderIDForDeployment(deploymentID string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } @@ -311,7 +311,7 @@ func ProviderIDForDeployment(deploymentID string) string { } func PrimaryAPIKeyEnvForDeployment(deploymentID string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } @@ -324,7 +324,7 @@ func PrimaryAPIKeyEnvForDeployment(deploymentID string) string { } func engineGateway(providerID string) (gw.GatewayDefs, bool) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return gw.GatewayDefs{}, false } diff --git a/internal/config/catalog_compat_test.go b/internal/config/catalog_compat_test.go index 44e83330d..f2c26e99c 100644 --- a/internal/config/catalog_compat_test.go +++ b/internal/config/catalog_compat_test.go @@ -4,12 +4,12 @@ import ( "context" "strings" - "github.com/GrayCodeAI/flux/catalog" - fluxconfig "github.com/GrayCodeAI/flux/config" + "github.com/GrayCodeAI/mux/catalog" + muxconfig "github.com/GrayCodeAI/mux/config" ) // CompiledCatalogV1 is retained only for lower-level migration/security tests. -// Production Rho code consumes engine DTOs exclusively. +// Production Decoder 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 { @@ -20,7 +20,7 @@ func CompiledCatalogV1() *catalog.CompiledCatalog { return compiled } -func deploymentHasSecrets(deployment fluxconfig.DeploymentConfig) bool { +func deploymentHasSecrets(deployment muxconfig.DeploymentConfig) bool { return strings.TrimSpace(deployment.APIKey) != "" || strings.TrimSpace(deployment.Token) != "" || strings.TrimSpace(deployment.SecretAccessKey) != "" || strings.TrimSpace(deployment.AccessKeyID) != "" || strings.TrimSpace(deployment.SessionToken) != "" diff --git a/internal/config/catalog_gateways_test.go b/internal/config/catalog_gateways_test.go index 75686a4ef..5464ab05d 100644 --- a/internal/config/catalog_gateways_test.go +++ b/internal/config/catalog_gateways_test.go @@ -5,9 +5,9 @@ import ( "fmt" "testing" - "github.com/GrayCodeAI/flux/catalog" - "github.com/GrayCodeAI/flux/credentials" - "github.com/GrayCodeAI/rho/internal/catalogtest" + "github.com/GrayCodeAI/decoder/internal/catalogtest" + "github.com/GrayCodeAI/mux/catalog" + "github.com/GrayCodeAI/mux/credentials" ) func TestIsCatalogCacheRequired(t *testing.T) { @@ -30,7 +30,7 @@ func TestAllSetupGateways_RegistryOnly(t *testing.T) { t.Fatalf("owner slug %q should not be a gateway", id) } } - // Required gateways that exist in the published flux v0.1.3. + // Required gateways that exist in the published mux v0.1.3. want := map[string]bool{"azure": true, "bedrock": true, "gemini": true, "grok": true, "openrouter": true, "kimi": true, "vertex": true, "xiaomi_mimo_payg": true, "xiaomi_mimo_token_plan": true, "deepseek": true, "minimax_token_plan": true, "minimax_payg": true, "zai_payg": true, "zai_coding": true, "agnes": true} for id := range want { found := false @@ -44,12 +44,12 @@ func TestAllSetupGateways_RegistryOnly(t *testing.T) { t.Fatalf("missing setup gateway %q in %v", id, gws) } } - // Newly-added gateways (groq, poolside) present in the local Flux checkout - // but not yet in a published flux release — log if present, don't + // Newly-added gateways (groq, poolside) present in the local Mux checkout + // but not yet in a published mux release — log if present, don't // require them so the test passes with both GOWORK=on and GOWORK=off. for _, extra := range []string{"groq", "poolside"} { if containsString(gws, extra) { - t.Logf("extra gateway %q present (local flux build)", extra) + t.Logf("extra gateway %q present (local mux build)", extra) } } if containsString(gws, "google") || containsString(gws, "xai") { @@ -140,7 +140,7 @@ func TestGatewayDisplayName(t *testing.T) { } } -func TestCachedModelCountForProvider_MatchesFluxList(t *testing.T) { +func TestCachedModelCountForProvider_MatchesMuxList(t *testing.T) { catalogtest.Install(t) compiled := CompiledCatalogV1() for _, gw := range AllSetupGateways() { diff --git a/internal/config/catalog_health.go b/internal/config/catalog_health.go index daf8e38e7..1c2122de1 100644 --- a/internal/config/catalog_health.go +++ b/internal/config/catalog_health.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/internal/theme" ) var ( @@ -18,7 +18,7 @@ var ( const catalogHealthCacheTTL = 15 * time.Second -// CatalogHealth summarizes the on-disk flux model catalog for doctor / status output. +// CatalogHealth summarizes the on-disk mux model catalog for doctor / status output. type CatalogHealth struct { CachePath string `json:"cache_path"` Exists bool `json:"exists"` @@ -33,7 +33,7 @@ type CatalogHealth struct { Error string `json:"error,omitempty"` } -// CatalogHealthReport inspects ~/.flux/model_catalog.json (or FLUX_MODEL_CATALOG_PATH). +// CatalogHealthReport inspects ~/.mux/model_catalog.json (or MUX_MODEL_CATALOG_PATH). func CatalogHealthReport(ctx context.Context) CatalogHealth { path := CatalogCachePathForDisplay() catalogHealthMu.Lock() @@ -61,7 +61,7 @@ func InvalidateCatalogHealthCache() { } func catalogHealthReportUncached(ctx context.Context) CatalogHealth { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return CatalogHealth{Error: err.Error()} } @@ -73,15 +73,15 @@ func catalogHealthReportUncached(ctx context.Context) CatalogHealth { Source: status.Source, Error: status.Error, } if !h.Exists && h.Error == "" { - h.Error = "cache missing — rho will discover automatically on start" + h.Error = "cache missing — decoder will discover automatically on start" } return h } -// FormatCatalogHealth returns human-readable catalog status for rho doctor. +// FormatCatalogHealth returns human-readable catalog status for decoder doctor. func FormatCatalogHealth(h CatalogHealth) string { var b strings.Builder - b.WriteString(theme.Tint("Model catalog (flux):", theme.ReportInfo) + "\n") + b.WriteString(theme.Tint("Model catalog (mux):", theme.ReportInfo) + "\n") b.WriteString(" " + theme.Tint("path:", theme.ReportMuted) + " " + theme.Tint(h.CachePath, theme.ReportInfo) + "\n") if h.Error != "" { b.WriteString(" " + theme.Tint("status:", theme.ReportMuted) + " " + theme.Tint(h.Error, theme.ReportError) + "\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) — rho 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) — decoder 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 rho preflight or /config — rho refreshes the catalog automatically" + return "check network access, then decoder preflight or /config — decoder refreshes the catalog automatically" } // EnsureCatalogAvailable returns an error when the production catalog cache is missing or empty. @@ -128,7 +128,7 @@ func EnsureCatalogAvailable(ctx context.Context) error { // CatalogCachePathForDisplay returns the path users should care about. func CatalogCachePathForDisplay() string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err == nil { return engine.StatePaths().Catalog } diff --git a/internal/config/catalog_health_test.go b/internal/config/catalog_health_test.go index 5988a63ae..f71e7990f 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/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func TestCatalogEmptyHint_NoCredentials(t *testing.T) { @@ -43,7 +43,7 @@ func TestCatalogEmptyHint_WithCredentials(t *testing.T) { func TestEnsureCatalogAvailable_MissingCache(t *testing.T) { dir := t.TempDir() - t.Setenv("FLUX_MODEL_CATALOG_PATH", dir+"/missing.json") + t.Setenv("MUX_MODEL_CATALOG_PATH", dir+"/missing.json") err := EnsureCatalogAvailable(context.Background()) if err == nil || !strings.Contains(err.Error(), "/config") { @@ -53,7 +53,7 @@ func TestEnsureCatalogAvailable_MissingCache(t *testing.T) { func TestCatalogStatusLine_Empty(t *testing.T) { dir := t.TempDir() - t.Setenv("FLUX_MODEL_CATALOG_PATH", dir+"/missing.json") + t.Setenv("MUX_MODEL_CATALOG_PATH", dir+"/missing.json") line := CatalogStatusLine(context.Background()) if !strings.Contains(line, "missing") && !strings.Contains(line, "empty") { diff --git a/internal/config/catalog_startup.go b/internal/config/catalog_startup.go index 5b13e13b4..0ddc70ad2 100644 --- a/internal/config/catalog_startup.go +++ b/internal/config/catalog_startup.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/env" + "github.com/GrayCodeAI/decoder/internal/env" ) type gatewayModelCount struct { @@ -72,13 +72,13 @@ func CatalogStatusLine(ctx context.Context) string { return formatCatalogGatewayStatus("Catalog: ", rows, h.Models) } -// CatalogReady reports whether the flux catalog cache exists and has models. +// CatalogReady reports whether the mux catalog cache exists and has models. func CatalogReady(ctx context.Context) bool { h := CatalogHealthReport(ctx) return h.Error == "" && h.Models > 0 && !h.Stale } -// CatalogStartupOptions controls automatic catalog refresh at rho startup. +// CatalogStartupOptions controls automatic catalog refresh at decoder 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 rho auto-discovers when the cache is missing, empty, or stale. +// By default decoder 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) { @@ -146,7 +146,7 @@ func catalogNeedsAutoRefresh(h CatalogHealth, opts CatalogStartupOptions) bool { return h.Stale } -// AutoRefreshCatalog runs flux discover (remote + live APIs when keys are set). +// AutoRefreshCatalog runs mux discover (remote + live APIs when keys are set). func AutoRefreshCatalog(ctx context.Context, out io.Writer, verbose bool) error { if out != nil { if verbose { @@ -180,12 +180,12 @@ 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 (RHO_AUTO_REFRESH_CATALOG=0)") + return fmt.Errorf("automatic catalog refresh is disabled (DECODER_AUTO_REFRESH_CATALOG=0)") } return AutoRefreshCatalog(ctx, nil, false) } -// RefreshCatalogAfterCredentials runs flux discover after /config saves API keys. +// RefreshCatalogAfterCredentials runs mux discover after /config saves API keys. func RefreshCatalogAfterCredentials(ctx context.Context, out io.Writer) error { if !autoRefreshCatalogEnabled() { return nil @@ -209,7 +209,7 @@ func StartupCatalogPrefetch(ctx context.Context) { }() } -// DiscoverCatalogAfterSetup runs during optional rho setup after API keys are saved. +// DiscoverCatalogAfterSetup runs during optional decoder 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 rho preflight or /config." + return "Check network access and stored keys (" + CredentialStoreName() + "). Run decoder preflight or /config." } func autoRefreshCatalogEnabled() bool { - switch strings.ToLower(strings.TrimSpace(env.Getenv("RHO_AUTO_REFRESH_CATALOG"))) { + switch strings.ToLower(strings.TrimSpace(env.Getenv("DECODER_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("RHO_CATALOG_REFRESH_ALWAYS"))) { + switch strings.ToLower(strings.TrimSpace(env.Getenv("DECODER_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 ccf9868e3..cb2a2b604 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/rho/internal/catalogtest" - rhoconfig "github.com/GrayCodeAI/rho/internal/config" + "github.com/GrayCodeAI/decoder/internal/catalogtest" + decoderconfig "github.com/GrayCodeAI/decoder/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 := rhoconfig.CatalogHealthReport(context.Background()) + h := decoderconfig.CatalogHealthReport(context.Background()) var buf bytes.Buffer - err := rhoconfig.PrepareCatalogForSession(context.Background(), &buf, rhoconfig.CatalogStartupOptions{ + err := decoderconfig.PrepareCatalogForSession(context.Background(), &buf, decoderconfig.CatalogStartupOptions{ ForceRefresh: true, }) // With ForceRefresh, remote may fail; if we had models before, we tolerate failure. @@ -27,8 +27,8 @@ func TestPrepareCatalogForSession_StaleCacheRefreshFailureContinues(t *testing.T func TestCatalogCachePathForDisplay_RespectsEnv(t *testing.T) { custom := filepath.Join(t.TempDir(), "custom.json") - t.Setenv("FLUX_MODEL_CATALOG_PATH", custom) - if got := rhoconfig.CatalogCachePathForDisplay(); got != custom { + t.Setenv("MUX_MODEL_CATALOG_PATH", custom) + if got := decoderconfig.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 f1bb50964..381afcdae 100644 --- a/internal/config/catalog_startup_test.go +++ b/internal/config/catalog_startup_test.go @@ -7,13 +7,13 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/catalogtest" + "github.com/GrayCodeAI/decoder/internal/catalogtest" ) func TestCatalogReady_MissingCache(t *testing.T) { InvalidateCatalogHealthCache() dir := t.TempDir() - t.Setenv("FLUX_MODEL_CATALOG_PATH", filepath.Join(dir, "missing.json")) + t.Setenv("MUX_MODEL_CATALOG_PATH", filepath.Join(dir, "missing.json")) if CatalogReady(context.Background()) { t.Fatal("expected not ready without cache") } @@ -50,11 +50,11 @@ func TestCatalogNeedsAutoRefresh_Fresh(t *testing.T) { } func TestAutoRefreshCatalogEnabled(t *testing.T) { - t.Setenv("RHO_AUTO_REFRESH_CATALOG", "false") + t.Setenv("DECODER_AUTO_REFRESH_CATALOG", "false") if autoRefreshCatalogEnabled() { t.Fatal("expected disabled") } - t.Setenv("RHO_AUTO_REFRESH_CATALOG", "") + t.Setenv("DECODER_AUTO_REFRESH_CATALOG", "") if !autoRefreshCatalogEnabled() { t.Fatal("expected enabled by default") } diff --git a/internal/config/config.go b/internal/config/config.go index a19352a46..7097647bb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -24,7 +24,7 @@ var agentFiles = []string{ } // sharedAgentFiles lists instruction files under a shared resource directory. -// Tau reads `.agents/` resources; Rho honors the same layout in addition to +// Tau reads `.agents/` resources; Decoder 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{ diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 842446dfe..77473899d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/testutil" ) func TestLoadAgentsMD(t *testing.T) { @@ -169,8 +169,8 @@ func TestLoadSettingsUsesUserConfigOnly(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) configDir := filepath.Join(home, "config") - t.Setenv("RHO_CONFIG_DIR", configDir) - t.Setenv("FLUX_CONFIG_DIR", filepath.Join(home, "flux")) + t.Setenv("DECODER_CONFIG_DIR", configDir) + t.Setenv("MUX_CONFIG_DIR", filepath.Join(home, "mux")) 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) } - // Rho: API keys rejected from settings file + // Decoder: 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 09119eede..db8b39f67 100644 --- a/internal/config/credentials_store.go +++ b/internal/config/credentials_store.go @@ -5,18 +5,18 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/theme" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/theme" ) -// PersistAPIKey saves a provider API key via flux (OS secret store). +// PersistAPIKey saves a provider API key via mux (OS secret store). func PersistAPIKey(ctx context.Context, envKey, secret string) error { secret = strings.TrimSpace(secret) envKey = strings.TrimSpace(envKey) if secret == "" || envKey == "" { return nil } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return err } @@ -27,7 +27,7 @@ func PersistAPIKey(ctx context.Context, envKey, secret string) error { return nil } -// CredentialInference is one flux provider match for a pasted API key. +// CredentialInference is one mux provider match for a pasted API key. type CredentialInference struct { ProviderID string DeploymentID string @@ -35,7 +35,7 @@ type CredentialInference struct { DisplayName string } -// CredentialProviderOption is one flux provider row for /config pickers. +// CredentialProviderOption is one mux provider row for /config pickers. type CredentialProviderOption struct { ProviderID string DeploymentID string @@ -46,7 +46,7 @@ type CredentialProviderOption struct { Rank int } -// CredentialResolveResult is flux paste-key resolution (format check + full provider list; no prefix inference). +// CredentialResolveResult is mux paste-key resolution (format check + full provider list; no prefix inference). type CredentialResolveResult struct { FormatOK bool FormatError string @@ -54,9 +54,9 @@ type CredentialResolveResult struct { ProbeDisambiguationUsed bool } -// ResolveCredential validates format and lists all providers from flux registry. +// ResolveCredential validates format and lists all providers from mux registry. func ResolveCredential(ctx context.Context, secret string) CredentialResolveResult { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return CredentialResolveResult{FormatError: err.Error()} } @@ -91,9 +91,9 @@ func InferenceFromOption(opt CredentialProviderOption) CredentialInference { } } -// SaveCredential validates, probes, and stores via flux keychain. +// SaveCredential validates, probes, and stores via mux keychain. func SaveCredential(ctx context.Context, inference CredentialInference, secret string) error { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return err } @@ -106,7 +106,7 @@ func SaveCredential(ctx context.Context, inference CredentialInference, secret s // HasStoredCredentialForProvider reports whether the OS secret store has a key for this gateway. func HasStoredCredentialForProvider(ctx context.Context, providerID string) bool { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return false } @@ -117,7 +117,7 @@ func HasStoredCredentialForProvider(ctx context.Context, providerID string) bool // CredentialEnvironmentConflict reports a safe environment-vs-keychain // mismatch for a provider. It returns identifiers only, never secret values. func CredentialEnvironmentConflict(ctx context.Context, providerID string) (envVar string, conflict bool) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "", false } @@ -137,7 +137,7 @@ func ConfiguredCredentialProviders() []string { return configuredCredentialProvidersCached(context.Background()) } -// FormatCredentialCLIStatus returns rho credentials status output (providers, not raw env names). +// FormatCredentialCLIStatus returns decoder credentials status output (providers, not raw env names). func FormatCredentialCLIStatus(ctx context.Context) string { if ctx == nil { ctx = context.Background() @@ -165,7 +165,7 @@ func RemoveStoredCredential(ctx context.Context, target string) ([]string, error if target == "" { return nil, fmt.Errorf("provider or env var name required") } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil, err } @@ -189,7 +189,7 @@ func MaskCredentialForProvider(ctx context.Context, provider string) string { if ctx == nil { ctx = context.Background() } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err == nil { status, statusErr := engine.CredentialStatus(ctx, provider) if statusErr == nil && status.Masked != "" { @@ -214,7 +214,7 @@ func maskCredentialSecret(secret string) string { // CredentialInferenceForProvider returns save metadata for a gateway chosen in /config. func CredentialInferenceForProvider(providerID string) (CredentialInference, error) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return CredentialInference{}, err } @@ -235,7 +235,7 @@ func LocalCredentialInference(providerID string) (CredentialInference, error) { return CredentialInferenceForProvider(providerID) } -// FormatConfigProviderError maps flux setup errors to user-facing /config hints. +// FormatConfigProviderError maps mux setup errors to user-facing /config hints. func FormatConfigProviderError(providerID string, err error) string { if err == nil { return "" diff --git a/internal/config/credentials_store_test.go b/internal/config/credentials_store_test.go index b8a8759d4..bceff472d 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/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func TestRemoveStoredCredential_ByProvider(t *testing.T) { diff --git a/internal/config/deployment.go b/internal/config/deployment.go index 5b96e6ad0..2aea47239 100644 --- a/internal/config/deployment.go +++ b/internal/config/deployment.go @@ -1,7 +1,7 @@ package config -// DeploymentRoutingEnabled delegates deployment-routing policy ownership to Flux runtime. +// DeploymentRoutingEnabled delegates deployment-routing policy ownership to Mux runtime. func DeploymentRoutingEnabled(s Settings) bool { - engine, err := newFluxEngine() + engine, err := newMuxEngine() return err == nil && engine.DeploymentRoutingEnabled(s.DeploymentRouting) } diff --git a/internal/config/deployment_status.go b/internal/config/deployment_status.go index 1d6626eb6..f4568a37e 100644 --- a/internal/config/deployment_status.go +++ b/internal/config/deployment_status.go @@ -10,9 +10,9 @@ func ResolveCanonicalModel(model string) string { return CanonicalModelID(context.Background(), strings.TrimSpace(model)) } -// DeploymentStatusReport returns rho deployment routing diagnostics. +// DeploymentStatusReport returns decoder deployment routing diagnostics. func DeploymentStatusReport(ctx context.Context, activeModel string) (string, error) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "", err } @@ -20,16 +20,16 @@ func DeploymentStatusReport(ctx context.Context, activeModel string) (string, er } func DeploymentStatusReportWithSettings(ctx context.Context, settings Settings, activeModel string) (string, error) { - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { return "", err } return engine.DeploymentStatus(ctx, activeModel) } -// RoutingPreviewJSON returns effective routing for a model (flux routing JSON preview). +// RoutingPreviewJSON returns effective routing for a model (mux routing JSON preview). func RoutingPreviewJSON(ctx context.Context, model string) (string, error) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "", err } @@ -37,7 +37,7 @@ func RoutingPreviewJSON(ctx context.Context, model string) (string, error) { } func RoutingPreviewJSONWithSettings(ctx context.Context, settings Settings, model string) (string, error) { - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { return "", err } diff --git a/internal/config/deployments_ui_test.go b/internal/config/deployments_ui_test.go index e61e731b1..3db3727c1 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("RHO_DEPLOYMENT_ROUTING", "") + t.Setenv("DECODER_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 3565e694b..8e290039b 100644 --- a/internal/config/developer_path.go +++ b/internal/config/developer_path.go @@ -7,13 +7,13 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/rho/internal/home" - "github.com/GrayCodeAI/rho/internal/pathsafe" - "github.com/GrayCodeAI/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/theme" - "github.com/GrayCodeAI/rho/internal/token" + "github.com/GrayCodeAI/decoder/internal/home" + "github.com/GrayCodeAI/decoder/internal/pathsafe" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/theme" + "github.com/GrayCodeAI/decoder/internal/token" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 rho and /config to paste an API key (or configure Ollama)", + FixHint: "Run decoder 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 rho models refresh after adding credentials", + FixHint: "Run decoder models refresh after adding credentials", }) default: checks = append(checks, PathCheck{ Section: "Setup", Name: "catalog", Status: PathWarn, Detail: CatalogEmptyHint(ctx), - FixHint: "Add credentials then run rho models refresh", + FixHint: "Add credentials then run decoder models refresh", }) } @@ -133,10 +133,10 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { }) } - rhoDir := home.MustDir() + decoderDir := home.MustDir() provPath := ProviderStateSecurityStatus().Path if provPath == "" { - provPath = filepath.Join(rhoDir, ".rho", "provider.json") + provPath = filepath.Join(decoderDir, ".decoder", "provider.json") } if reason := pathsafe.IsSensitivePath(provPath); reason != "" { checks = append(checks, PathCheck{ @@ -155,7 +155,7 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { pre := EnginePreflightReport(ctx) if pre.Ready { checks = append(checks, PathCheck{ - Section: "Ecosystem", Name: "flux", Status: PathPass, + Section: "Ecosystem", Name: "mux", Status: PathPass, Detail: "Preflight ready to chat", Blocking: true, }) @@ -168,14 +168,14 @@ func EvaluateDeveloperPath(ctx context.Context) DeveloperPathReport { } } checks = append(checks, PathCheck{ - Section: "Ecosystem", Name: "flux", Status: status, - Detail: "Preflight not ready — see rho preflight", + Section: "Ecosystem", Name: "mux", Status: status, + Detail: "Preflight not ready — see decoder preflight", FixHint: "Complete /config (credentials + model)", Blocking: true, }) } - sample := token.CountTokensFast("rho developer path readiness") + sample := token.CountTokensFast("decoder 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 rho and start chatting" + return "Ready — run decoder and start chatting" } if !setup.HasCredentials { - return "Run rho → /config → paste API key (or Ollama local)" + return "Run decoder → /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 rho preflight for details, then /config if needed" + return "Run decoder 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 (rho · flux · token engine)", theme.ReportInfo) + "\n\n") + b.WriteString(theme.Tint("Developer path (decoder · mux · 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 · rho doctor · rho preflight", theme.ReportMuted) + "\n") + b.WriteString("\n" + theme.Tint("Docs: docs/DEVELOPER-PATH.md · docs/SECURITY-DEVELOPER.md · decoder doctor · decoder 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 d0a25da24..048c38da7 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/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestEvaluateDeveloperPath_FreshInstall(t *testing.T) { diff --git a/internal/config/ecosystem_report.go b/internal/config/ecosystem_report.go index 1c89bb6a1..95f9d8b48 100644 --- a/internal/config/ecosystem_report.go +++ b/internal/config/ecosystem_report.go @@ -5,17 +5,17 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/theme" - "github.com/GrayCodeAI/rho/internal/token" + "github.com/GrayCodeAI/decoder/internal/theme" + "github.com/GrayCodeAI/decoder/internal/token" ) // EcosystemReport is the structured view of the ecosystem panel. type EcosystemReport struct { - Flux EcosystemFlux `json:"flux"` - Token EngineToken `json:"token"` + Mux EcosystemMux `json:"mux"` + Token EngineToken `json:"token"` } -type EcosystemFlux struct { +type EcosystemMux struct { CatalogExists bool `json:"catalog_exists"` ModelCount int `json:"model_count,omitempty"` Ready bool `json:"ready"` @@ -33,60 +33,60 @@ type EngineToken struct { func BuildEcosystemReport(ctx context.Context, provider, model string) EcosystemReport { var r EcosystemReport - // flux + // mux cat := CatalogHealthReport(ctx) - r.Flux.CatalogExists = cat.Exists - r.Flux.ModelCount = cat.Models + r.Mux.CatalogExists = cat.Exists + r.Mux.ModelCount = cat.Models pre := EnginePreflightReport(ctx) - r.Flux.Ready = pre.Ready + r.Mux.Ready = pre.Ready if strings.TrimSpace(provider) != "" && provider != "auto" { - r.Flux.Provider = provider + r.Mux.Provider = provider } if dep, err := EngineDeploymentSummary(ctx, model); err == nil { - r.Flux.RoutingSource = dep.RoutingSource - r.Flux.RoutingStages = dep.RoutingStages + r.Mux.RoutingSource = dep.RoutingSource + r.Mux.RoutingStages = dep.RoutingStages } // embedded token engine r.Token.Embedded = token.ShrikeAvailable() - r.Token.SampleTokens = token.CountTokensFast("rho context compression pipeline") + r.Token.SampleTokens = token.CountTokensFast("decoder context compression pipeline") return r } -// FormatEcosystemPanel summarizes flux and token-engine integration for doctor and status output. +// FormatEcosystemPanel summarizes mux and token-engine integration for doctor and status output. func FormatEcosystemPanel(ctx context.Context, provider, model string) string { var b strings.Builder - b.WriteString(theme.Tint("Ecosystem (flux · token engine):", theme.ReportInfo) + "\n") + b.WriteString(theme.Tint("Ecosystem (mux · token engine):", theme.ReportInfo) + "\n") - // flux — LLM provider layer + // mux — LLM provider layer cat := CatalogHealthReport(ctx) - fluxLine := " " + theme.Tint("flux:", theme.ReportMuted) + " " + muxLine := " " + theme.Tint("mux:", theme.ReportMuted) + " " if cat.Exists { - fluxLine += theme.Tint(fmt.Sprintf("catalog %d models", cat.Models), theme.ReportInfo) + muxLine += theme.Tint(fmt.Sprintf("catalog %d models", cat.Models), theme.ReportInfo) } else { - fluxLine += theme.Tint("catalog missing (run rho models refresh)", theme.ReportWarn) + muxLine += theme.Tint("catalog missing (run decoder models refresh)", theme.ReportWarn) } pre := EnginePreflightReport(ctx) if pre.Ready { - fluxLine += " · " + theme.Tint("locally ready", theme.ReportSuccess) + muxLine += " · " + theme.Tint("locally ready", theme.ReportSuccess) } else { - fluxLine += " · " + theme.Tint("setup incomplete", theme.ReportWarn) + muxLine += " · " + theme.Tint("setup incomplete", theme.ReportWarn) } if strings.TrimSpace(provider) != "" && provider != "auto" { - fluxLine += " · " + theme.Tint("provider "+provider, theme.ReportInfo) + muxLine += " · " + theme.Tint("provider "+provider, theme.ReportInfo) } if dep, err := EngineDeploymentSummary(ctx, model); err == nil { if dep.RoutingStages > 0 { - fluxLine += " · " + theme.Tint(fmt.Sprintf("routing %s (%d stages)", dep.RoutingSource, dep.RoutingStages), theme.ReportInfo) + muxLine += " · " + theme.Tint(fmt.Sprintf("routing %s (%d stages)", dep.RoutingSource, dep.RoutingStages), theme.ReportInfo) } else { - fluxLine += " · " + theme.Tint("routing "+dep.RoutingSource, theme.ReportInfo) + muxLine += " · " + theme.Tint("routing "+dep.RoutingSource, theme.ReportInfo) } } - b.WriteString(fluxLine + "\n") + b.WriteString(muxLine + "\n") // Embedded token engine — token counting and context compression. - sample := token.CountTokensFast("rho context compression pipeline") + sample := token.CountTokensFast("decoder 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/ecosystem_report_test.go b/internal/config/ecosystem_report_test.go index ad6a6877d..424651269 100644 --- a/internal/config/ecosystem_report_test.go +++ b/internal/config/ecosystem_report_test.go @@ -10,8 +10,8 @@ func TestFormatEcosystemPanel(t *testing.T) { t.Parallel() out := FormatEcosystemPanel(context.Background(), "anthropic", "claude-sonnet-4-20250514") for _, want := range []string{ - "Ecosystem (flux · token engine):", - "flux:", + "Ecosystem (mux · token engine):", + "mux:", "token:", } { if !strings.Contains(out, want) { diff --git a/internal/config/envmanager.go b/internal/config/envmanager.go index a320d922e..f5c2f3950 100644 --- a/internal/config/envmanager.go +++ b/internal/config/envmanager.go @@ -11,7 +11,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/rho/internal/env" + "github.com/GrayCodeAI/decoder/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 1de9b4f78..28c433604 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["RHO_MODEL"] = &EnvVar{ - Key: "RHO_MODEL", + em.Vars["DECODER_MODEL"] = &EnvVar{ + Key: "DECODER_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, "RHO_MODEL") { - t.Fatal("output should contain RHO_MODEL") + if !strings.Contains(output, "DECODER_MODEL") { + t.Fatal("output should contain DECODER_MODEL") } if !strings.Contains(output, "claude-sonnet-4-6") { t.Fatal("output should show non-secret value in full") diff --git a/internal/config/main_test.go b/internal/config/main_test.go index a6f047e96..b4b0d51ca 100644 --- a/internal/config/main_test.go +++ b/internal/config/main_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" - "github.com/GrayCodeAI/rho/internal/catalogtest" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/catalogtest" + "github.com/GrayCodeAI/decoder/internal/testutil" ) func TestMain(m *testing.M) { diff --git a/internal/config/milestone_verify_test.go b/internal/config/milestone_verify_test.go index d19c3cb18..cb22f075d 100644 --- a/internal/config/milestone_verify_test.go +++ b/internal/config/milestone_verify_test.go @@ -8,23 +8,23 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/flux/catalog" - fluxcfg "github.com/GrayCodeAI/flux/config" - "github.com/GrayCodeAI/flux/credentials" + "github.com/GrayCodeAI/mux/catalog" + muxcfg "github.com/GrayCodeAI/mux/config" + "github.com/GrayCodeAI/mux/credentials" ) -// isolateMilestoneTest uses a temp HOME and RHO_CONFIG_DIR so verification does not touch the user machine. +// isolateMilestoneTest uses a temp HOME and DECODER_CONFIG_DIR so verification does not touch the user machine. func isolateMilestoneTest(t *testing.T) string { t.Helper() home := t.TempDir() - rhoDir := filepath.Join(home, ".rho") - if err := os.MkdirAll(rhoDir, 0o700); err != nil { + decoderDir := filepath.Join(home, ".decoder") + if err := os.MkdirAll(decoderDir, 0o700); err != nil { t.Fatal(err) } t.Setenv("HOME", home) - t.Setenv("RHO_CONFIG_DIR", rhoDir) - t.Setenv("FLUX_CONFIG_DIR", rhoDir) - return rhoDir + t.Setenv("DECODER_CONFIG_DIR", decoderDir) + t.Setenv("MUX_CONFIG_DIR", decoderDir) + return decoderDir } func TestVerify_ProviderJSONOnDiskHasNoSecrets(t *testing.T) { @@ -34,19 +34,19 @@ func TestVerify_ProviderJSONOnDiskHasNoSecrets(t *testing.T) { t.Fatal("compiled catalog required") } env := map[string]string{"ANTHROPIC_API_KEY": "sk-ant-verify-test-key-1234567890"} - cfg := fluxcfg.SyncProviderConfigFromCatalog(compiled, env) - path, err := fluxcfg.GetProviderConfigPath() + cfg := muxcfg.SyncProviderConfigFromCatalog(compiled, env) + path, err := muxcfg.GetProviderConfigPath() if err != nil { t.Fatalf("GetProviderConfigPath: %v", err) } - if err := fluxcfg.SaveProviderConfig(cfg, path); err != nil { + if err := muxcfg.SaveProviderConfig(cfg, path); err != nil { t.Fatal(err) } assertProviderJSONFileHasNoSecrets(t, path) } func TestVerify_PersistAPIKeyDoesNotWriteProviderJSON(t *testing.T) { - rhoDir := isolateMilestoneTest(t) + decoderDir := 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(rhoDir, "provider.json") + path := filepath.Join(decoderDir, "provider.json") if _, err := os.Stat(path); err == nil { data, _ := os.ReadFile(path) if strings.Contains(string(data), secret) { @@ -99,13 +99,13 @@ func TestVerify_EvaluateSetupFlow(t *testing.T) { t.Fatal("expected setup still needed until model selected") } - providerPath := filepath.Join(os.Getenv("HOME"), ".rho", "provider.json") - cfg := &fluxcfg.ProviderConfig{ + providerPath := filepath.Join(os.Getenv("HOME"), ".decoder", "provider.json") + cfg := &muxcfg.ProviderConfig{ ActiveProvider: "anthropic", ActiveModel: "claude-sonnet-4-20250514", AnthropicModel: "claude-sonnet-4-20250514", } - if err := fluxcfg.SaveProviderConfig(cfg, providerPath); err != nil { + if err := muxcfg.SaveProviderConfig(cfg, providerPath); err != nil { t.Fatal(err) } st = EvaluateSetup(ctx) @@ -133,7 +133,7 @@ func assertProviderJSONFileHasNoSecrets(t *testing.T, path string) { t.Fatalf("provider.json at %s contains non-empty %s", path, needle) } } - var cfg fluxcfg.ProviderConfig + var cfg muxcfg.ProviderConfig if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } diff --git a/internal/config/flux_apply.go b/internal/config/mux_apply.go similarity index 72% rename from internal/config/flux_apply.go rename to internal/config/mux_apply.go index 4559fed39..8a22b9ebd 100644 --- a/internal/config/flux_apply.go +++ b/internal/config/mux_apply.go @@ -5,22 +5,22 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) -// ApplyCredentialsResult is Rho's UI-safe view of an Flux catalog/routing -// application. It intentionally excludes Flux setup/config implementation +// ApplyCredentialsResult is Decoder's UI-safe view of an Mux catalog/routing +// application. It intentionally excludes Mux setup/config implementation // types from the product boundary. type ApplyCredentialsResult struct { Catalog gateway.CatalogSnapshot } -// ApplyFluxCredentialsForProvider refreshes live models and writes sanitized +// ApplyMuxCredentialsForProvider refreshes live models and writes sanitized // deployment routing after /config saves a key. -func ApplyFluxCredentialsForProvider(ctx context.Context, providerID string) (*ApplyCredentialsResult, error) { +func ApplyMuxCredentialsForProvider(ctx context.Context, providerID string) (*ApplyCredentialsResult, error) { ctx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil, err } @@ -32,12 +32,12 @@ func ApplyFluxCredentialsForProvider(ctx context.Context, providerID string) (*A return &ApplyCredentialsResult{Catalog: snapshot}, nil } -// ApplyFluxCredentials refreshes all configured providers and writes +// ApplyMuxCredentials refreshes all configured providers and writes // sanitized deployment routing. -func ApplyFluxCredentials(ctx context.Context) (*ApplyCredentialsResult, error) { +func ApplyMuxCredentials(ctx context.Context) (*ApplyCredentialsResult, error) { ctx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return nil, err } @@ -53,7 +53,7 @@ func ApplyFluxCredentials(ctx context.Context) (*ApplyCredentialsResult, error) func RefreshGatewayCatalog(ctx context.Context, providerID string) (string, error) { ctx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "", err } diff --git a/internal/config/flux_engine.go b/internal/config/mux_engine.go similarity index 82% rename from internal/config/flux_engine.go rename to internal/config/mux_engine.go index d282abc86..c46c93f4c 100644 --- a/internal/config/flux_engine.go +++ b/internal/config/mux_engine.go @@ -3,7 +3,7 @@ package config import ( "context" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) type ( @@ -12,16 +12,16 @@ type ( EnginePreflightOptions = gateway.PreflightOptions ) -func newFluxEngine() (*gateway.Gateway, error) { +func newMuxEngine() (*gateway.Gateway, error) { return gateway.New(context.Background(), globalCustomProviders()) } -func NewFluxEngine() (*gateway.Gateway, error) { return newFluxEngine() } +func NewMuxEngine() (*gateway.Gateway, error) { return newMuxEngine() } -// NewFluxEngineForSettings composes a fresh gateway for one effective -// Rho settings snapshot. It performs no package-global registration and does +// NewMuxEngineForSettings composes a fresh gateway for one effective +// Decoder settings snapshot. It performs no package-global registration and does // not mutate provider environment variables. -func NewFluxEngineForSettings(settings Settings) (*gateway.Gateway, error) { +func NewMuxEngineForSettings(settings Settings) (*gateway.Gateway, error) { return gateway.New(context.Background(), gatewayCustomGateways(settings.CustomProviders)) } @@ -80,7 +80,7 @@ func FormatEnginePreflight(report EnginePreflight) string { } func EngineGatewayRegion(ctx context.Context, providerID string) (string, bool) { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { return "", false } @@ -88,7 +88,7 @@ func EngineGatewayRegion(ctx context.Context, providerID string) (string, bool) } func SetEngineGatewayRegion(ctx context.Context, providerID, region string) error { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { return err } @@ -96,7 +96,7 @@ func SetEngineGatewayRegion(ctx context.Context, providerID, region string) erro } func CanonicalModelID(ctx context.Context, modelID string) string { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { return modelID } @@ -104,7 +104,7 @@ func CanonicalModelID(ctx context.Context, modelID string) string { } func HasCredentialEnv(ctx context.Context, envVar string) bool { - gw, err := newFluxEngine() + gw, err := newMuxEngine() return err == nil && gw.HasCredentialEnv(ctx, envVar) } @@ -113,30 +113,30 @@ func CredentialGuidance(providerID, secret string) string { } func ProviderStateSecurityStatus() gateway.ProviderStateSecurity { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { - return gateway.ProviderStateSecurity{Error: err.Error(), Detail: "Flux engine initialization failed"} + return gateway.ProviderStateSecurity{Error: err.Error(), Detail: "Mux engine initialization failed"} } return gw.ProviderStateSecurityStatus() } func EngineDeploymentSummary(ctx context.Context, model string) (gateway.DeploymentSummary, error) { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { return gateway.DeploymentSummary{}, err } return gw.DeploymentSummary(ctx, model) } -// newFluxEngine is Rho's default composition root for Flux's stable host +// newMuxEngine is Decoder's default composition root for Mux's stable host // facade. Command paths that support --settings must use -// NewFluxEngineForSettings instead of relying on this global-settings default. +// NewMuxEngineForSettings instead of relying on this global-settings default. -// ListEngineModels returns model-picker rows through Flux's stable facade. +// ListEngineModels returns model-picker rows through Mux's stable facade. // EngineModel is an alias of gateway.ModelInfo, so model lists returned by the // gateway pass through without conversion. func ListEngineModels(ctx context.Context, providerID string, refresh bool) ([]EngineModel, error) { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { return nil, err } @@ -144,7 +144,7 @@ func ListEngineModels(ctx context.Context, providerID string, refresh bool) ([]E } func ListEngineModelsWithSettings(ctx context.Context, settings Settings, providerID string, refresh bool) ([]EngineModel, error) { - gw, err := NewFluxEngineForSettings(settings) + gw, err := NewMuxEngineForSettings(settings) if err != nil { return nil, err } @@ -152,7 +152,7 @@ func ListEngineModelsWithSettings(ctx context.Context, settings Settings, provid } func ListLiveEngineModelsWithSettings(ctx context.Context, settings Settings, providerID string) ([]EngineModel, error) { - gw, err := NewFluxEngineForSettings(settings) + gw, err := NewMuxEngineForSettings(settings) if err != nil { return nil, err } @@ -160,7 +160,7 @@ func ListLiveEngineModelsWithSettings(ctx context.Context, settings Settings, pr } func ListPublicEngineModels(ctx context.Context, providerID string) ([]EngineModel, error) { - gw, err := newFluxEngine() + gw, err := newMuxEngine() if err != nil { return nil, err } diff --git a/internal/config/flux_engine_test.go b/internal/config/mux_engine_test.go similarity index 85% rename from internal/config/flux_engine_test.go rename to internal/config/mux_engine_test.go index 076686c29..ee5d2d8fa 100644 --- a/internal/config/flux_engine_test.go +++ b/internal/config/mux_engine_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) -func TestNewFluxEngineForSettingsIsInvocationScoped(t *testing.T) { +func TestNewMuxEngineForSettingsIsInvocationScoped(t *testing.T) { previousStore := gateway.DefaultStore() store := &gateway.MapStore{} gateway.SetDefaultStore(store) @@ -17,7 +17,7 @@ func TestNewFluxEngineForSettingsIsInvocationScoped(t *testing.T) { Name: "private-gateway", BaseURL: "https://private.example.test/v1", APIKeyEnv: "PRIVATE_GATEWAY_API_KEY", Model: "private/model-v1", }}} - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { t.Fatal(err) } @@ -44,7 +44,7 @@ func TestNewFluxEngineForSettingsIsInvocationScoped(t *testing.T) { t.Fatalf("custom credential surface = %+v, err=%v", status, err) } - if _, collisionErr := NewFluxEngineForSettings(Settings{CustomProviders: []CustomProviderConfig{{ + if _, collisionErr := NewMuxEngineForSettings(Settings{CustomProviders: []CustomProviderConfig{{ Name: "openai", BaseURL: "https://collision.example.test/v1", }}}); collisionErr == nil { t.Fatal("built-in custom gateway collision was accepted") @@ -53,7 +53,7 @@ func TestNewFluxEngineForSettingsIsInvocationScoped(t *testing.T) { if err != nil || len(models) != 1 { t.Fatalf("independent invalid construction changed existing engine: %+v, err=%v", models, err) } - other, err := NewFluxEngineForSettings(Settings{}) + other, err := NewMuxEngineForSettings(Settings{}) if err != nil { t.Fatal(err) } diff --git a/internal/config/flux_selection.go b/internal/config/mux_selection.go similarity index 71% rename from internal/config/flux_selection.go rename to internal/config/mux_selection.go index 703f96c6e..7b4b6c36b 100644 --- a/internal/config/flux_selection.go +++ b/internal/config/mux_selection.go @@ -3,7 +3,7 @@ package config import ( "context" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) type ( @@ -12,9 +12,9 @@ type ( ) // EffectiveSelection resolves persisted selection and optional host overrides -// through Flux's host-neutral engine contract. +// through Mux's host-neutral engine contract. func EffectiveSelection(ctx context.Context, opts SelectionOptions) Selection { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return Selection{} } @@ -22,60 +22,60 @@ func EffectiveSelection(ctx context.Context, opts SelectionOptions) Selection { } func EffectiveSelectionWithSettings(ctx context.Context, settings Settings, opts SelectionOptions) Selection { - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { return Selection{} } return engine.EffectiveSelection(ctx, opts) } -// ActiveModel returns the selected model from flux provider.json (not rho settings). +// ActiveModel returns the selected model from mux provider.json (not decoder settings). func ActiveModel(ctx context.Context) string { if ctx == nil { ctx = context.Background() } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } return engine.ActiveSelection(ctx).Model } -// ActiveProvider returns the selected provider from flux provider.json. +// ActiveProvider returns the selected provider from mux provider.json. func ActiveProvider(ctx context.Context) string { if ctx == nil { ctx = context.Background() } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } return engine.ActiveSelection(ctx).Provider } -// ActiveProviderID canonicalizes a host-facing provider/gateway id through Flux runtime. +// ActiveProviderID canonicalizes a host-facing provider/gateway id through Mux runtime. func ActiveProviderID(provider string) string { return gateway.NormalizeProviderID(provider) } -// SetActiveModel persists model selection to flux provider.json. +// SetActiveModel persists model selection to mux provider.json. func SetActiveModel(ctx context.Context, modelID string) error { if ctx == nil { ctx = context.Background() } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return err } return engine.SetActiveModel(ctx, modelID) } -// SetActiveProvider persists provider selection to flux provider.json. +// SetActiveProvider persists provider selection to mux provider.json. func SetActiveProvider(ctx context.Context, provider string) error { if ctx == nil { ctx = context.Background() } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return err } diff --git a/internal/config/provider_filter.go b/internal/config/provider_filter.go index 4b0cd5715..8cf7b1ac8 100644 --- a/internal/config/provider_filter.go +++ b/internal/config/provider_filter.go @@ -4,13 +4,13 @@ import ( "context" ) -// DefaultModelProviderFilter picks which flux provider to list models for when the UI -// has no explicit filter. Host prefs (settings) win; otherwise flux routing/deployments decide. +// DefaultModelProviderFilter picks which mux provider to list models for when the UI +// has no explicit filter. Host prefs (settings) win; otherwise mux routing/deployments decide. func DefaultModelProviderFilter(ctx context.Context) string { if p := ActiveGateway(ctx); p != "" { return p } - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "" } diff --git a/internal/config/security_test.go b/internal/config/security_test.go index f5d04b1ef..3069f3a76 100644 --- a/internal/config/security_test.go +++ b/internal/config/security_test.go @@ -87,8 +87,8 @@ 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("RHO_CONFIG_DIR", dir) - t.Setenv("FLUX_CONFIG_DIR", dir) + t.Setenv("DECODER_CONFIG_DIR", dir) + t.Setenv("MUX_CONFIG_DIR", dir) s := Settings{ MaxBudgetUSD: 10.0, } diff --git a/internal/config/settings.go b/internal/config/settings.go index 289bab1db..9c7132020 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -13,27 +13,27 @@ import ( "sync" "time" - "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/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/safewrite" + "github.com/GrayCodeAI/decoder/internal/smartrouting" + "github.com/GrayCodeAI/decoder/internal/storage" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func fetchModelsViaRuntime(ctx context.Context, provider string) ([]EngineModel, error) { return ListEngineModels(ctx, provider, false) } -// Settings holds rho configuration. -// Rho: no API keys stored here. Secrets come from the OS secret store via flux. +// Settings holds decoder configuration. +// Decoder: no API keys stored here. Secrets come from the OS secret store via mux. type Settings struct { // PolicySchemaVersion versions permission/autonomy fields. Zero is // the legacy format and is migrated to CurrentPolicySchemaVersion on load. PolicySchemaVersion int `json:"policy_schema_version,omitempty"` // Model and Provider carry runtime host overrides only (e.g. --settings). - // Flux owns the stored selection (SetActiveModel / SetActiveProvider); + // Mux owns the stored selection (SetActiveModel / SetActiveProvider); // values found in settings.json are dropped on load and on save. Model string `json:"model,omitempty"` Provider string `json:"provider,omitempty"` @@ -105,7 +105,7 @@ func ToolPresetByName(name string) (ToolPreset, bool) { return p, ok } -// Attribution controls how rho identifies itself in git commits. +// Attribution controls how decoder 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 rho global settings path from internal storage config, not external input + data, err := os.ReadFile(path) // #nosec G304 -- path is the decoder 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 Rho's user config settings.json. +// LoadGlobalSettings loads only Decoder's user config settings.json. func LoadGlobalSettings() Settings { var s Settings path := globalSettingsPath() @@ -221,7 +221,7 @@ func LoadGlobalSettings() Settings { slog.Warn("failed to parse settings", "path", path, "error", err) } } - // Flux owns the stored model/provider selection. Stale values left in + // Mux owns the stored model/provider selection. Stale values left in // settings.json must not act as a host override. s = stripHostModelSelection(s) if s.PolicySchemaVersion == 0 { @@ -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 .rho/settings.json. +// directory looking for .decoder/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 .rho/settings.json file. Returns nil if none is found. The nearest +// a .decoder/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, ".rho", "settings.json") + candidate := filepath.Join(dir, ".decoder", "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) - // Rho: API key status comes from OS secret store, not settings file + // Decoder: 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 Rho user config. -// Rho: API keys are NOT stored in settings.json. Use /config and the OS secret store. +// SetGlobalSetting updates a supported scalar/list setting in Decoder user config. +// Decoder: 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) - // Rho: reject API key persistence to disk + // Decoder: 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 } // ───────────────────────────────────────────────────────────── -// Rho: API keys from OS secret store only (no .env) +// Decoder: API keys from OS secret store only (no .env) // ───────────────────────────────────────────────────────────── // ProviderAPIKeyEnv returns the API key env var for a provider (registry first for setup gateways). @@ -700,7 +700,7 @@ func ProviderAPIKeyEnv(provider string) string { // EnvKeyStatus returns set, empty, or local from the OS credential store. func EnvKeyStatus(provider string) string { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return "empty" } @@ -736,7 +736,7 @@ func providerCredentialEnvAliases(provider string) []string { primary := strings.TrimSpace(ProviderAPIKeyEnv(provider)) seen := map[string]bool{} var out []string - engine, _ := newFluxEngine() + engine, _ := newMuxEngine() var aliases []string if engine != nil { aliases = engine.CredentialEnvKeys(provider) @@ -753,10 +753,10 @@ func providerCredentialEnvAliases(provider string) []string { } // ───────────────────────────────────────────────────────────── -// Live model catalog fetch from flux +// Live model catalog fetch from mux // ───────────────────────────────────────────────────────────── -// FetchModelsForProvider returns models from the flux catalog (dynamic; no rho hardcoded lists). +// FetchModelsForProvider returns models from the mux catalog (dynamic; no decoder 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 rho catalog data. + // Custom OpenAI-compatible providers: single model from settings, not decoder 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 flux catalog (check API keys; rho will refresh automatically on next start)", provider) + return nil, fmt.Errorf("no models found for provider %s in mux catalog (check API keys; decoder will refresh automatically on next start)", provider) } // FetchModelsForProviderWithSettings resolves cached models using one @@ -796,7 +796,7 @@ func FetchModelsForProviderWithSettings(ctx context.Context, settings Settings, if provider == "" { return nil, fmt.Errorf("no provider specified") } - engine, engineErr := NewFluxEngineForSettings(settings) + engine, engineErr := NewMuxEngineForSettings(settings) if engineErr != nil { return nil, engineErr } @@ -810,18 +810,18 @@ func FetchModelsForProviderWithSettings(ctx context.Context, settings Settings, if err != nil { return nil, err } - return nil, fmt.Errorf("no models found for provider %s in flux catalog", provider) + return nil, fmt.Errorf("no models found for provider %s in mux catalog", provider) } func refreshModelCatalog(ctx context.Context, _ bool) (gateway.CatalogSnapshot, error) { - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err != nil { return gateway.CatalogSnapshot{}, err } return engine.RefreshCatalog(ctx, "") } -// RefreshModelCatalogV1 asks flux to refresh the remote catalog and provider APIs using env API keys. +// RefreshModelCatalogV1 asks mux to refresh the remote catalog and provider APIs using env API keys. func RefreshModelCatalogV1(ctx context.Context) (string, error) { ctx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() @@ -838,7 +838,7 @@ func RefreshModelCatalogV1(ctx context.Context) (string, error) { func RefreshModelCatalogV1WithSettings(ctx context.Context, settings Settings) (string, error) { ctx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() - engine, err := NewFluxEngineForSettings(settings) + engine, err := NewMuxEngineForSettings(settings) if err != nil { return "", err } diff --git a/internal/config/settings_status_test.go b/internal/config/settings_status_test.go index 93a46f37d..818db40f3 100644 --- a/internal/config/settings_status_test.go +++ b/internal/config/settings_status_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) -func TestEnvKeyStatusUsesFluxCredentialStatus(t *testing.T) { +func TestEnvKeyStatusUsesMuxCredentialStatus(t *testing.T) { store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) }) diff --git a/internal/config/settings_test.go b/internal/config/settings_test.go index 24a4b7889..af8e40002 100644 --- a/internal/config/settings_test.go +++ b/internal/config/settings_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/provider/routing" ) func TestMergeSettings_ModelOverride(t *testing.T) { diff --git a/internal/config/setup_status.go b/internal/config/setup_status.go index b5320336c..ba623798c 100644 --- a/internal/config/setup_status.go +++ b/internal/config/setup_status.go @@ -45,16 +45,16 @@ func evaluateSetupFrom(hasCreds, hasModel bool) SetupState { return st } -// HasConfiguredDeployment reports whether at least one flux deployment has credentials. +// HasConfiguredDeployment reports whether at least one mux deployment has credentials. func HasConfiguredDeployment(ctx context.Context) bool { if ctx == nil { ctx = context.Background() } - engine, err := newFluxEngine() + engine, err := newMuxEngine() return err == nil && engine.EffectiveSelection(ctx, SelectionOptions{}).HasConfiguredDeployment } -// HasSelectedModel reports whether flux provider.json has a selected model. +// HasSelectedModel reports whether mux provider.json has a selected model. func HasSelectedModel() bool { return strings.TrimSpace(ActiveModel(context.Background())) != "" } diff --git a/internal/config/setup_status_test.go b/internal/config/setup_status_test.go index 893a8dd9c..0b79635e4 100644 --- a/internal/config/setup_status_test.go +++ b/internal/config/setup_status_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/flux/catalog" - "github.com/GrayCodeAI/flux/credentials" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/testutil" + "github.com/GrayCodeAI/mux/catalog" + "github.com/GrayCodeAI/mux/credentials" ) func TestHasConfiguredDeployment_FromStore(t *testing.T) { diff --git a/internal/config/ui_cache.go b/internal/config/ui_cache.go index 004ac54b7..b7defe751 100644 --- a/internal/config/ui_cache.go +++ b/internal/config/ui_cache.go @@ -74,7 +74,7 @@ func gatewayCredentialSnapshot(ctx context.Context) map[string]gatewayCredential credSnapMu.Unlock() out := make(map[string]gatewayCredentialState) - engine, err := newFluxEngine() + engine, err := newMuxEngine() if err == nil { for _, gateway := range engine.Gateways(ctx) { out[gateway.ID] = gatewayCredentialState{ diff --git a/internal/config/validator.go b/internal/config/validator.go index c6596a37b..1a54ed6e8 100644 --- a/internal/config/validator.go +++ b/internal/config/validator.go @@ -43,9 +43,9 @@ func (r ValidationResult) Error() string { func ValidateSettings(s Settings) ValidationResult { var errors []ValidationError - // Provider names are delegated to Flux. Do not hardcode/validate here. + // Provider names are delegated to Mux. Do not hardcode/validate here. - // Validate model selection (stored in flux provider.json) + // Validate model selection (stored in mux provider.json) activeModel := strings.TrimSpace(s.Model) if activeModel == "" { activeModel = ActiveModel(context.Background()) @@ -62,7 +62,7 @@ func ValidateSettings(s Settings) ValidationResult { if activeProvider == "" { activeProvider = ActiveProvider(context.Background()) } - // Rho: validate API key is in the OS secret store (not in settings) + // Decoder: 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 a083de6ff..07d282042 100644 --- a/internal/config/validator_test.go +++ b/internal/config/validator_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" ) func TestValidateSettingsValid(t *testing.T) { @@ -25,7 +25,7 @@ func TestValidateSettingsValid(t *testing.T) { } } -func TestValidateSettingsProviderDelegatedToFlux(t *testing.T) { +func TestValidateSettingsProviderDelegatedToMux(t *testing.T) { store := &gateway.MapStore{} gateway.SetDefaultStore(store) t.Cleanup(func() { gateway.SetDefaultStore(nil) }) @@ -33,7 +33,7 @@ func TestValidateSettingsProviderDelegatedToFlux(t *testing.T) { s := Settings{Provider: "anthropic"} result := ValidateSettings(s) if result.Valid { - t.Fatal("expected invalid (missing env key for flux provider)") + t.Fatal("expected invalid (missing env key for mux provider)") } } diff --git a/internal/config/xiaomi_setup_test.go b/internal/config/xiaomi_setup_test.go index 33d75d77f..dd69b2dcf 100644 --- a/internal/config/xiaomi_setup_test.go +++ b/internal/config/xiaomi_setup_test.go @@ -4,27 +4,27 @@ import ( "os" "testing" - fluxcfg "github.com/GrayCodeAI/flux/config" + muxcfg "github.com/GrayCodeAI/mux/config" ) func TestSetGatewayRegion_XiaomiClearsStaleBaseHost(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", t.TempDir()) - t.Setenv("RHO_CONFIG_DIR", dir) - t.Setenv("FLUX_CONFIG_DIR", dir) + t.Setenv("DECODER_CONFIG_DIR", dir) + t.Setenv("MUX_CONFIG_DIR", dir) t.Setenv("XIAOMI_MIMO_TOKEN_PLAN_BASE_URL", "https://caller-owned.example.test/v1") - cfg := &fluxcfg.ProviderConfig{ + cfg := &muxcfg.ProviderConfig{ Version: "1", XiaomiMimoTokenPlanRegion: "cn", XiaomiMimoTokenPlanBaseURL: "https://token-plan-cn.xiaomimimo.com/v1", } - if err := fluxcfg.SaveProviderConfig(cfg, ""); err != nil { + if err := muxcfg.SaveProviderConfig(cfg, ""); err != nil { t.Fatal(err) } if err := SetGatewayRegion(ProviderXiaomiTokenPlan, "sgp"); err != nil { t.Fatal(err) } - loaded := fluxcfg.LoadProviderConfig("") + loaded := muxcfg.LoadProviderConfig("") if loaded.XiaomiMimoTokenPlanRegion != "sgp" { t.Fatalf("region = %q", loaded.XiaomiMimoTokenPlanRegion) } @@ -36,13 +36,13 @@ func TestSetGatewayRegion_XiaomiClearsStaleBaseHost(t *testing.T) { func TestNeedsGatewayRegion_XiaomiInvalidAndMissing(t *testing.T) { dir := t.TempDir() t.Setenv("HOME", t.TempDir()) - t.Setenv("RHO_CONFIG_DIR", dir) - t.Setenv("FLUX_CONFIG_DIR", dir) + t.Setenv("DECODER_CONFIG_DIR", dir) + t.Setenv("MUX_CONFIG_DIR", dir) if !NeedsGatewayRegion(ProviderXiaomiTokenPlan) { t.Fatal("expected true when no config file") } - if err := fluxcfg.SaveProviderConfig(&fluxcfg.ProviderConfig{Version: "1", XiaomiMimoTokenPlanRegion: "tokyo"}, ""); err != nil { + if err := muxcfg.SaveProviderConfig(&muxcfg.ProviderConfig{Version: "1", XiaomiMimoTokenPlanRegion: "tokyo"}, ""); err != nil { t.Fatal(err) } if !NeedsGatewayRegion(ProviderXiaomiTokenPlan) { diff --git a/internal/context/repomap/repomap.go b/internal/context/repomap/repomap.go index c8132ef6a..5d5127e34 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 rho's context layer. It builds an import/refer +// repository overview for decoder'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 // -// rho ships a second package, internal/intelligence/repomap, that exposes +// decoder 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 0c54d53c7..f1a9ab3c3 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": {}, - ".rho": {}, + ".decoder": {}, "dist": {}, "build": {}, "target": {}, diff --git a/internal/contracts/agent/hooks.go b/internal/contracts/agent/hooks.go index b8e717dd3..008e467b0 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package agent import "strings" // Hook event names for lifecycle and tool gates. // -// Rho may accept vendor aliases (Claude/Cursor); normalize to these -// canonical names at the boundary. Constants are shared so rho, plugins, +// Decoder may accept vendor aliases (Claude/Cursor); normalize to these +// canonical names at the boundary. Constants are shared so decoder, 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 Rho names. +// VendorHookAliases maps common third-party hook names to canonical Decoder 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 Rho canonical event name for s, or "" if unknown. +// CanonicalHookEvent returns the Decoder 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 53c6c40d5..9ceb51ad6 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder 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 427af9fd6..d1641f23c 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package events import "time" -// ToolEvent represents a normalized tool event emitted by Rho workflows. +// ToolEvent represents a normalized tool event emitted by Decoder 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 8bc3aef5b..481c3d56d 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder 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 09a589577..b3c5fff2e 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package harness import ( "time" - contracts "github.com/GrayCodeAI/rho/internal/contracts/types" + contracts "github.com/GrayCodeAI/decoder/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 Rho Agent Harness evaluations. +// Report is the neutral cross-repo contract for Decoder 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 972ab067c..0e191b876 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package policy import ( diff --git a/internal/contracts/review/review.go b/internal/contracts/review/review.go index 9d35500a9..5fb675f9c 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package review import ( @@ -7,10 +7,10 @@ import ( "strings" "time" - contracts "github.com/GrayCodeAI/rho/internal/contracts/types" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/types" ) -// Finding is the neutral review finding contract shared across Rho and review engines. +// Finding is the neutral review finding contract shared across Decoder 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 3c5d9fe0c..2998b952f 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package types import ( @@ -8,7 +8,7 @@ import ( "time" ) -// Finding represents a unified analysis concern sourced from Rho support engines. +// Finding represents a unified analysis concern sourced from Decoder 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 ce772fed1..4747f9540 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package types import ( diff --git a/internal/contracts/verify/verify.go b/internal/contracts/verify/verify.go index 9650d35b4..1fd7878f2 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 Rho as its contract surface. +// The upstream repository no longer exists; this copy is owned by Decoder as its contract surface. package verify import ( "time" - contracts "github.com/GrayCodeAI/rho/internal/contracts/types" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/types" ) -// Finding is the neutral verification finding contract shared across Rho and verification engines. +// Finding is the neutral verification finding contract shared across Decoder 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 06957d9a8..f289f1ba8 100644 --- a/internal/crash/crash.go +++ b/internal/crash/crash.go @@ -31,7 +31,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/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("rho crash report\n") + b.WriteString("decoder 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 34b5d88a5..9e9432615 100644 --- a/internal/crash/crash_extra_test.go +++ b/internal/crash/crash_extra_test.go @@ -9,7 +9,7 @@ import ( "time" ) -const testEnvStateDir = "RHO_STATE_DIR" +const testEnvStateDir = "DECODER_STATE_DIR" // --- WriteReport tests --- @@ -112,7 +112,7 @@ func TestFormatReport_FullFields(t *testing.T) { } result := formatReport(r) - if !strings.Contains(result, "rho crash report") { + if !strings.Contains(result, "decoder 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 c913ae015..80d8fdd5b 100644 --- a/internal/crash/crash_test.go +++ b/internal/crash/crash_test.go @@ -5,14 +5,14 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/crash" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/crash" + "github.com/GrayCodeAI/decoder/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 RHO_STATE_DIR override production uses. +// the same DECODER_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) { - // RHO_STATE_DIR must be set before Install/crash writes anything; mirror + // DECODER_STATE_DIR must be set before Install/crash writes anything; mirror // the convention storage's own tests use. dir := t.TempDir() - t.Setenv("RHO_STATE_DIR", dir) + t.Setenv("DECODER_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("RHO_STATE_DIR", dir) + t.Setenv("DECODER_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 6e0d4c37c..3094e5c8c 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("rho signal report\nsignal: %s\nreason: %s\ntimestamp: %s\n\n%s\n", + content := fmt.Sprintf("decoder 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 a520d62e3..086c1a37d 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/rho/internal/contracts/graph" - "github.com/GrayCodeAI/rho/internal/testutil" + graphcontracts "github.com/GrayCodeAI/decoder/internal/contracts/graph" + "github.com/GrayCodeAI/decoder/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 a89b438aa..834270828 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -22,14 +22,16 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" - - "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" + "github.com/GrayCodeAI/decoder" + + "github.com/GrayCodeAI/decoder/internal/engine/safety" + + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/netutil" + "github.com/GrayCodeAI/decoder/internal/observability/metrics" + "github.com/GrayCodeAI/decoder/internal/securitylog" + decodersession "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/storage" ) const maxRequestBodyBytes = 1 << 20 @@ -74,12 +76,12 @@ func (e *InvalidChatRequestError) Error() string { func (e *InvalidChatRequestError) Unwrap() error { return e.Err } // version is set via SetVersion from main.go at startup. -var version = "0.0.0" +var version = decoder.Version -// SetVersion propagates the canonical rho version into the daemon. +// SetVersion propagates the canonical decoder version into the daemon. func SetVersion(v string) { version = v } -// Server is the rho daemon HTTP server for programmatic/CI access. +// Server is the decoder daemon HTTP server for programmatic/CI access. type Server struct { addr string mux *http.ServeMux @@ -94,14 +96,14 @@ type Server struct { apiKey string gateways *gatewayManager - // readyFn reports whether Flux's preflight/readiness dependencies are + // readyFn reports whether Mux's preflight/readiness dependencies are // initialized and the server can serve real traffic. It is consulted by // GET /v1/ready. A nil probe fails closed: a session factory alone does not - // prove that Flux's catalog, credentials, and model selection are ready. + // prove that Mux's catalog, credentials, and model selection are ready. readyMu sync.RWMutex readyFn func() (bool, string) - // graphFactory projects a persisted Rho session into the portable, + // graphFactory projects a persisted Decoder 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 @@ -127,7 +129,7 @@ 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 + // reviewWG tracks in-flight `decoder 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. @@ -348,7 +350,7 @@ func (s *Server) Start() (string, error) { s.gateways.Start(context.Background()) } - slog.Info("rho daemon started", "addr", actualAddr) + slog.Info("decoder daemon started", "addr", actualAddr) return actualAddr, nil } @@ -387,7 +389,7 @@ func (s *Server) warnInsecureAuthConfig() { return } slog.Warn( - "rho daemon started without API key authentication; only loopback access allowed", + "decoder 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.", ) @@ -456,7 +458,7 @@ func (s *Server) SetGraphFactory(factory GraphFactory) { s.graphMu.Unlock() } -// ready evaluates readiness using the installed Flux probe. It fails closed +// ready evaluates readiness using the installed Mux probe. It fails closed // when the engine is absent or the composition root did not install a probe; // merely having a factory does not prove provider readiness. func (s *Server) ready() (bool, string) { @@ -469,7 +471,7 @@ func (s *Server) ready() (bool, string) { if s.newSession == nil { return false, "engine not configured" } - return false, "Flux readiness probe not configured" + return false, "Mux readiness probe not configured" } func (s *Server) routes() { @@ -555,11 +557,11 @@ func (s *Server) cancelSession(sessionID string) bool { return true } -// maxConcurrentFromEnv reads RHO_DAEMON_MAX_CONCURRENT (clamped to >= 1) so +// maxConcurrentFromEnv reads DECODER_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("RHO_DAEMON_MAX_CONCURRENT"); raw != "" { + if raw := os.Getenv("DECODER_DAEMON_MAX_CONCURRENT"); raw != "" { if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 { n = parsed } @@ -721,12 +723,12 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { lock.Lock() defer lock.Unlock() - var saved *rhosession.Session + var saved *decodersession.Session if requestedID != "" { var loadErr error - saved, loadErr = rhosession.Load(sessionID) + saved, loadErr = decodersession.Load(sessionID) if loadErr != nil { - if !errors.Is(loadErr, rhosession.ErrNotFound) { + if !errors.Is(loadErr, decodersession.ErrNotFound) { slog.Error("load continuation session failed", "err", loadErr, "session_id", sessionID) writeJSON(w, http.StatusInternalServerError, ErrorResponse{ Error: "session load failed", @@ -798,7 +800,7 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { return } if saved != nil { - sess.LoadMessages(rhosession.ToRuntimeMessages(saved.Messages)) + sess.LoadMessages(decodersession.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"}) @@ -822,7 +824,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 (RHO_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 (DECODER_DAEMON_AUTONOMY) to allow it", requested.String(), max.String()), Code: "autonomy_denied", }) return @@ -882,11 +884,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 *rhosession.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 *decodersession.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-Rho-Session-ID", sessionID) + w.Header().Set("X-Decoder-Session-ID", sessionID) w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") flusher, _ := w.(http.Flusher) @@ -977,7 +979,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 *rhosession.Session, start time.Time) { +func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.StreamEvent, sessionID string, req ChatRequest, sess *engine.Session, saved *decodersession.Session, start time.Time) { var response strings.Builder var totalIn, totalOut, turns int @@ -1000,7 +1002,7 @@ func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.St return } s.trackSession(sessionID, req, saved, start, turns) - w.Header().Set("X-Rho-Session-ID", sessionID) + w.Header().Set("X-Decoder-Session-ID", sessionID) writeJSON(w, http.StatusOK, ChatResponse{ SessionID: sessionID, @@ -1013,7 +1015,7 @@ func writeJSONResponse(s *Server, w http.ResponseWriter, events <-chan engine.St } func validSessionID(id string) bool { - return rhosession.ValidID(id) + return decodersession.ValidID(id) } // CancelRequest is the JSON body for POST /v1/cancel. @@ -1092,7 +1094,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 *rhosession.Session, startedAt time.Time) error { +func persistDaemonSession(id string, req ChatRequest, sess *engine.Session, previous *decodersession.Session, startedAt time.Time) error { createdAt := startedAt name := "" if previous != nil { @@ -1107,20 +1109,20 @@ func persistDaemonSession(id string, req ChatRequest, sess *engine.Session, prev if j := sess.Persistence().Journal(); j != nil { j.AppendSessionTitle(name) } - return rhosession.Save(&rhosession.Session{ + return decodersession.Save(&decodersession.Session{ ID: id, Model: sess.Model(), Provider: sess.Provider(), Agent: req.Agent, CWD: req.CWD, Name: name, - Messages: rhosession.FromRuntimeMessages(sess.RawMessages()), + Messages: decodersession.FromRuntimeMessages(sess.RawMessages()), Events: sess.JournalWire(), CreatedAt: createdAt, }) } -func (s *Server) trackSession(id string, req ChatRequest, previous *rhosession.Session, startedAt time.Time, turns int) { +func (s *Server) trackSession(id string, req ChatRequest, previous *decodersession.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 6e6a4e5b2..d2a964786 100644 --- a/internal/daemon/daemon_test.go +++ b/internal/daemon/daemon_test.go @@ -14,10 +14,10 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/engine" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/testutil" ) func startTestDaemon(t *testing.T, srv *Server) string { @@ -94,7 +94,7 @@ func TestDaemon_Ready(t *testing.T) { wantReady: false, }, { - name: "engine wired without Flux probe is not ready", + name: "engine wired without Mux probe is not ready", factory: factory, wantStatus: http.StatusServiceUnavailable, wantReady: false, @@ -238,7 +238,7 @@ func TestDaemon_RejectsUnknownFields(t *testing.T) { } func TestDaemon_Chat_WithEngine(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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-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")) + if chat.SessionID == "" || resp.Header.Get("X-Decoder-Session-ID") != chat.SessionID { + t.Fatalf("session ID response/header mismatch: body=%q header=%q", chat.SessionID, resp.Header.Get("X-Decoder-Session-ID")) } factoryReq := <-seen @@ -371,7 +371,7 @@ func TestDaemon_ChatPersistsRetrievableSessionAndRequestMetadata(t *testing.T) { } func TestDaemon_ChatContinuationReusesDurableSession(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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-Rho-Session-ID") + id := resp.Header.Get("X-Decoder-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 ce6ebf87a..aa73dfc4d 100644 --- a/internal/daemon/discord.go +++ b/internal/daemon/discord.go @@ -11,7 +11,7 @@ import ( "github.com/bwmarrin/discordgo" ) -// DiscordGateway bridges rho to Discord via the official Gateway (WebSocket), +// DiscordGateway bridges decoder 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 forwardToRho + client *http.Client // for forwardToDecoder 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 rho.") + reply("Paired. You can now chat with decoder.") } else { reply("Pairing failed: invalid code.") } @@ -151,7 +151,7 @@ func (g *DiscordGateway) handleMessage(ctx context.Context, senderID, channelID, return } - resp, err := forwardToRho(ctx, g.client, g.daemonAddr, g.apiKey, text) + resp, err := forwardToDecoder(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 5c0aa111b..62efc1f9d 100644 --- a/internal/daemon/e2e_test.go +++ b/internal/daemon/e2e_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - feature "github.com/GrayCodeAI/rho/internal/features" - "github.com/GrayCodeAI/rho/internal/testutil" + feature "github.com/GrayCodeAI/decoder/internal/features" + "github.com/GrayCodeAI/decoder/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), "rho_daemon_active_sessions") { - t.Error("expected rho_daemon_active_sessions metric in output") + if !strings.Contains(string(body), "decoder_daemon_active_sessions") { + t.Error("expected decoder_daemon_active_sessions metric in output") } - if !strings.Contains(string(body), "rho_daemon_uptime_seconds") { - t.Error("expected rho_daemon_uptime_seconds metric in output") + if !strings.Contains(string(body), "decoder_daemon_uptime_seconds") { + t.Error("expected decoder_daemon_uptime_seconds metric in output") } // Test JSON format too. diff --git a/internal/daemon/gateway.go b/internal/daemon/gateway.go index ef282e83b..3f7a01d0f 100644 --- a/internal/daemon/gateway.go +++ b/internal/daemon/gateway.go @@ -11,11 +11,11 @@ import ( "sync" ) -// forwardToRho posts a prompt to the daemon's /v1/chat endpoint and returns the +// forwardToDecoder 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 forwardToRho(ctx context.Context, client *http.Client, daemonAddr, apiKey, prompt string) (string, error) { +func forwardToDecoder(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 forwardToRho(ctx context.Context, client *http.Client, daemonAddr, apiKey, } // Gateway is a bidirectional messaging bridge between an external chat platform -// (Telegram, Discord, Slack, ...) and the rho daemon. Implementations forward +// (Telegram, Discord, Slack, ...) and the decoder 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 82bfd9365..f36730449 100644 --- a/internal/daemon/gateway_test.go +++ b/internal/daemon/gateway_test.go @@ -211,7 +211,7 @@ func TestAuthorizer_PairingAndAllowlist(t *testing.T) { } } -func TestForwardToRho(t *testing.T) { +func TestForwardToDecoder(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 TestForwardToRho(t *testing.T) { })) defer ts.Close() - reply, err := forwardToRho(context.Background(), ts.Client(), ts.URL, "key123", "ping") + reply, err := forwardToDecoder(context.Background(), ts.Client(), ts.URL, "key123", "ping") if err != nil { - t.Fatalf("forwardToRho: %v", err) + t.Fatalf("forwardToDecoder: %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) { - rho := newIPv4GatewayServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, ChatResponse{Response: "rho-reply"}) + decoder := newIPv4GatewayServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, ChatResponse{Response: "decoder-reply"}) })) - defer rho.Close() + defer decoder.Close() - g := newDiscordGateway(DiscordConfig{Token: "bot", PairingCode: "code"}, rho.URL, "") + g := newDiscordGateway(DiscordConfig{Token: "bot", PairingCode: "code"}, decoder.URL, "") var sent []string send := func(s string) error { sent = append(sent, s); return nil } - // Unauthorized non-pair message -> "Unauthorized", no rho call. + // Unauthorized non-pair message -> "Unauthorized", no decoder 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 rho. + // Authorized -> forwarded to decoder. 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] != "rho-reply" { - t.Errorf("sent[3]=%q want rho-reply", sent[3]) + if sent[3] != "decoder-reply" { + t.Errorf("sent[3]=%q want decoder-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 afa37e2a5..e9b25343c 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/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/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: "rho.graph/v1", + SchemaVersion: "decoder.graph/v1", Digest: "abc123", Facts: 4, - GraphJSON: `{"schema_version":"rho.graph/v1","nodes":[]}`, + GraphJSON: `{"schema_version":"decoder.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 != "rho.graph/v1" { + if got.ProjectID != "proj" || got.SessionID != "sess" || got.SchemaVersion != "decoder.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 a9398cb48..863586a45 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/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/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("RHO_DAEMON_MAX_CONCURRENT", "1") + t.Setenv("DECODER_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 109830cfe..b293a9e96 100644 --- a/internal/daemon/middleware.go +++ b/internal/daemon/middleware.go @@ -10,7 +10,7 @@ import ( "strings" "time" - feature "github.com/GrayCodeAI/rho/internal/features" + feature "github.com/GrayCodeAI/decoder/internal/features" ) // requestIDKey is the context key for the request ID. diff --git a/internal/daemon/middleware_test.go b/internal/daemon/middleware_test.go index 545e505b6..1f06a41f4 100644 --- a/internal/daemon/middleware_test.go +++ b/internal/daemon/middleware_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - feature "github.com/GrayCodeAI/rho/internal/features" - "github.com/GrayCodeAI/rho/internal/testutil" + feature "github.com/GrayCodeAI/decoder/internal/features" + "github.com/GrayCodeAI/decoder/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 df3d62078..21eca4df1 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: "rho", Turns: 3, LastUsed: now}) + s.sessions.Store("working-1", &Session{ID: "working-1", Agent: "decoder", 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 4f78b4d9b..1a8ab17e9 100644 --- a/internal/daemon/routes_graph.go +++ b/internal/daemon/routes_graph.go @@ -9,8 +9,8 @@ import ( "time" "unicode" - "github.com/GrayCodeAI/rho/internal/executiongraph" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + "github.com/GrayCodeAI/decoder/internal/session" ) const ( @@ -26,7 +26,7 @@ type GraphRequest struct { GeneratedAt time.Time } -// GraphFactory projects one persisted session into Rho's portable graph. +// GraphFactory projects one persisted session into Decoder'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 0cc93f865..72ffb5af2 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 = "rho-local.graph/v1" + graphReadSchemaVersion = "decoder-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 93735227a..8cf6d33d6 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/rho/internal/executiongraph" + "github.com/GrayCodeAI/decoder/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 != "rho-local.graph/v1" { - t.Errorf("schema_version = %q, want rho-local.graph/v1", out.Graph.SchemaVersion) + if out.Graph.SchemaVersion != "decoder-local.graph/v1" { + t.Errorf("schema_version = %q, want decoder-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 c8232993c..bff03bc79 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/rho/internal/contracts/graph" - "github.com/GrayCodeAI/rho/internal/executiongraph" + graphcontracts "github.com/GrayCodeAI/decoder/internal/contracts/graph" + "github.com/GrayCodeAI/decoder/internal/executiongraph" ) // POST /v1/graph/sync lets a producer (a future ecosystem repo) push portable -// `*.graph/v1` facts into Rho. It mirrors the cloud plane's /v1/graph/sync +// `*.graph/v1` facts into Decoder. 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 cbb32c2da..4baaa7f80 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/rho/internal/contracts/graph" - "github.com/GrayCodeAI/rho/internal/executiongraph" - "github.com/GrayCodeAI/rho/internal/testutil" + graphcontracts "github.com/GrayCodeAI/decoder/internal/contracts/graph" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + "github.com/GrayCodeAI/decoder/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 3d29958ed..0d1cf6a29 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/rho/internal/executiongraph" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/internal/executiongraph" + "github.com/GrayCodeAI/decoder/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=rho&swift_checkpoint=012345abcdef&swift_checkpoint=fedcba987654", + "/v1/sessions/session-1/graph?repository=decoder&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 != "rho" { + if captured.SessionID != "session-1" || captured.RepositoryID != "decoder" { 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=rho%0Aother", + url: "/v1/sessions/session-1/graph?repository=decoder%0Aother", code: "invalid_repository", }, { diff --git a/internal/daemon/routes_lease.go b/internal/daemon/routes_lease.go index 19e10877e..afbc3cdef 100644 --- a/internal/daemon/routes_lease.go +++ b/internal/daemon/routes_lease.go @@ -6,7 +6,7 @@ import ( "errors" "net/http" - "github.com/GrayCodeAI/rho/internal/session" + "github.com/GrayCodeAI/decoder/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 8898e48b5..cb32c923c 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/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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 a408e1f1c..41baf0ec1 100644 --- a/internal/daemon/routes_metrics.go +++ b/internal/daemon/routes_metrics.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/observability/metrics" + "github.com/GrayCodeAI/decoder/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 rho_daemon_active_sessions gauge\n") - sb.WriteString(fmt.Sprintf("rho_daemon_active_sessions %d\n", activeSessions)) + sb.WriteString("# TYPE decoder_daemon_active_sessions gauge\n") + sb.WriteString(fmt.Sprintf("decoder_daemon_active_sessions %d\n", activeSessions)) // Concurrency slots used - sb.WriteString("# TYPE rho_daemon_chat_concurrency_used gauge\n") - sb.WriteString(fmt.Sprintf("rho_daemon_chat_concurrency_used %d\n", len(s.concurrencySem))) + sb.WriteString("# TYPE decoder_daemon_chat_concurrency_used gauge\n") + sb.WriteString(fmt.Sprintf("decoder_daemon_chat_concurrency_used %d\n", len(s.concurrencySem))) // Uptime - sb.WriteString("# TYPE rho_daemon_uptime_seconds gauge\n") - sb.WriteString(fmt.Sprintf("rho_daemon_uptime_seconds %.0f\n", time.Since(s.startedAt).Seconds())) + sb.WriteString("# TYPE decoder_daemon_uptime_seconds gauge\n") + sb.WriteString(fmt.Sprintf("decoder_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 4468433fc..615865c38 100644 --- a/internal/daemon/routes_review.go +++ b/internal/daemon/routes_review.go @@ -20,14 +20,14 @@ const reviewArgMaxLen = 4096 var reviewArgCharset = regexp.MustCompile(`^[^\x00-\x1f\x7f]*$`) -// reviewSem bounds the number of concurrent `rho review run` subprocesses +// reviewSem bounds the number of concurrent `decoder 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 +// reviewTimeout bounds a spawned `decoder review run` subprocess so a hung review // cannot run forever and cannot block daemon shutdown. const reviewTimeout = 30 * time.Minute @@ -97,7 +97,7 @@ func (s *Server) handleReview(w http.ResponseWriter, r *http.Request) { return } - // Trigger review asynchronously via rho review run. Track it in the + // Trigger review asynchronously via decoder review run. Track it in the // server lifecycle so Stop can wait for in-flight reviews. s.reviewWG.Add(1) go func() { @@ -112,7 +112,7 @@ func (s *Server) handleReview(w http.ResponseWriter, r *http.Request) { } 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) + _ = exec.CommandContext(ctx, "decoder", args...).Run() // #nosec G204 -- binary is fixed "decoder"; args are validated (SHA regex, no "--" prefix, printable charset) }() resp := ReviewResponse{ @@ -124,8 +124,8 @@ func (s *Server) handleReview(w http.ResponseWriter, r *http.Request) { } func (s *Server) handleReviewStatus(w http.ResponseWriter, _ *http.Request) { - // Run rho review status and return output. - out, err := exec.CommandContext(context.Background(), "rho", "review", "status").Output() + // Run decoder review status and return output. + out, err := exec.CommandContext(context.Background(), "decoder", "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 7432e7c7c..3722e6deb 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/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/testutil" ) // newReviewTestServer starts a daemon. RegisterReviewRoutes() is already @@ -119,7 +119,7 @@ func TestDaemon_ReviewStatus(t *testing.T) { } defer resp.Body.Close() - // `rho review status` shells out to the rho binary; in a restricted + // `decoder review status` shells out to the decoder binary; in a restricted // 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 d80c1ed54..cff33f34b 100644 --- a/internal/daemon/routes_sessions.go +++ b/internal/daemon/routes_sessions.go @@ -8,8 +8,8 @@ import ( "path/filepath" "strconv" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/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 344b876c3..e7fba4de1 100644 --- a/internal/daemon/routes_sessions_test.go +++ b/internal/daemon/routes_sessions_test.go @@ -6,16 +6,16 @@ import ( "net/http" "testing" - contracts "github.com/GrayCodeAI/flux/tools" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/testutil" + contracts "github.com/GrayCodeAI/mux/tools" ) // 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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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 585bd73e8..3f0058bd5 100644 --- a/internal/daemon/routes_stats.go +++ b/internal/daemon/routes_stats.go @@ -5,7 +5,7 @@ import ( "strconv" "time" - analytics "github.com/GrayCodeAI/rho/internal/observability" + analytics "github.com/GrayCodeAI/decoder/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 06e8958d9..75c909137 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/rho/internal/observability" - "github.com/GrayCodeAI/rho/internal/testutil" + analytics "github.com/GrayCodeAI/decoder/internal/observability" + "github.com/GrayCodeAI/decoder/internal/testutil" ) func TestDaemon_Stats_Aggregation(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_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 7c089a957..8cb07b823 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/rho/internal/status" + "github.com/GrayCodeAI/decoder/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.RhoVersion = version + snapshot.DecoderVersion = 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 9580acaab..406de4b9f 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 -// forwardToRho, so it needs the resolved address even though it replies via the +// forwardToDecoder, 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 rho.") + g.reply(ctx, ev.Channel, threadTS, "Paired. You can now chat with decoder.") } 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 := forwardToRho(ctx, g.client, g.daemonAddr, g.apiKey, text) + reply, err := forwardToDecoder(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 f7898da0f..438a29a75 100644 --- a/internal/daemon/telegram.go +++ b/internal/daemon/telegram.go @@ -1,5 +1,5 @@ -// Package daemon provides a Telegram gateway for rho. -// Allows users to interact with rho via Telegram bot messages. +// Package daemon provides a Telegram gateway for decoder. +// Allows users to interact with decoder via Telegram bot messages. package daemon import ( @@ -15,13 +15,13 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/stt" + "github.com/GrayCodeAI/decoder/internal/stt" ) -// TelegramGateway connects rho to a Telegram bot. +// TelegramGateway connects decoder to a Telegram bot. type TelegramGateway struct { Token string - DaemonAddr string // rho daemon address to forward messages to + DaemonAddr string // decoder 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 rho.") + tg.reply(ctx, msg.Chat.ID, "Paired. You can now chat with decoder.") } 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 rho daemon - response, err := tg.forwardToRho(ctx, prompt) + // Forward to decoder daemon + response, err := tg.forwardToDecoder(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) forwardToRho(ctx context.Context, prompt string) (string, error) { +func (tg *TelegramGateway) forwardToDecoder(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) forwardToRho(ctx context.Context, prompt string) (str // 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 forwardToRho response", "error", err) + slog.Warn("partial read in forwardToDecoder 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 e74ea7653..80e48dcc3 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 + rho +// telegramMockAPI captures sendMessage calls and serves a fake Telegram + decoder // 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 rho daemon /v1/chat. - rho := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, ChatResponse{Response: "rho-reply"}) + // Mock decoder daemon /v1/chat. + decoder := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, ChatResponse{Response: "decoder-reply"}) })) - defer rho.Close() + defer decoder.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"}, - rho.URL, "apikey", + decoder.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-rho goes to the real mock server; let it through. + // forward-to-decoder 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 rho call. + // 1. Unauthorized non-pair message -> "Unauthorized" reply, no decoder 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 -> rho reply forwarded. + // 4. Now authorized -> decoder 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] != "rho-reply" { - t.Errorf("send[3]=%q want rho-reply", sends[3]) + if sends[3] != "decoder-reply" { + t.Errorf("send[3]=%q want decoder-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 rho. - rhoCalled := false - rho := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rhoCalled = true + // senders (no pairing code / allowlist) rather than forwarding to decoder. + decoderCalled := false + decoder := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + decoderCalled = true writeJSON(w, http.StatusOK, ChatResponse{Response: "ok"}) })) - defer rho.Close() + defer decoder.Close() var sends []string - tg := NewTelegramGateway("tok", rho.URL) + tg := NewTelegramGateway("tok", decoder.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 rhoCalled { - t.Fatal("bare constructor must not forward unauthorized messages to rho") + if decoderCalled { + t.Fatal("bare constructor must not forward unauthorized messages to decoder") } } @@ -166,14 +166,14 @@ func TestTelegramSafeFileName(t *testing.T) { } func TestTelegramVoiceWithoutTranscriberFallsBackToText(t *testing.T) { - rho := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - writeJSON(w, http.StatusOK, ChatResponse{Response: "rho-reply"}) + decoder := newIPv4TelegramServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeJSON(w, http.StatusOK, ChatResponse{Response: "decoder-reply"}) })) - defer rho.Close() + defer decoder.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"}}, rho.URL, "k") + tg := newTelegramGatewayFromConfig(TelegramConfig{Token: "tok", AllowList: []string{"user"}}, decoder.URL, "k") tg.handleMessage(context.Background(), &TelegramMessage{ Text: "hello", Chat: struct { diff --git a/internal/rhoerr/bridge_error_test.go b/internal/decodererr/bridge_error_test.go similarity index 96% rename from internal/rhoerr/bridge_error_test.go rename to internal/decodererr/bridge_error_test.go index 7dbd00bc3..e9c16d9da 100644 --- a/internal/rhoerr/bridge_error_test.go +++ b/internal/decodererr/bridge_error_test.go @@ -1,4 +1,4 @@ -package rhoerr +package decodererr import ( "errors" diff --git a/internal/rhoerr/classify.go b/internal/decodererr/classify.go similarity index 97% rename from internal/rhoerr/classify.go rename to internal/decodererr/classify.go index ac2316c6e..9e6e3ba74 100644 --- a/internal/rhoerr/classify.go +++ b/internal/decodererr/classify.go @@ -1,4 +1,4 @@ -package rhoerr +package decodererr import ( "fmt" @@ -133,7 +133,7 @@ func classify(err error) errorClass { // cost before fulfilling a request. When the account balance can't cover // that hold they return 403 with an insufficient_user_quota code rather // than a 401 — so this must be checked before the generic 403 branch below, - // otherwise the user is misled into "check your API key". flux already + // otherwise the user is misled into "check your API key". mux already // tags these as "billing/quota problem"; the Agnes body also carries the // Chinese pre-deduction phrasing (预扣费) and an insufficient_user_quota code. if strings.Contains(low, "insufficient_user_quota") || strings.Contains(low, "insufficient_quota") || @@ -251,7 +251,7 @@ func classify(err error) errorClass { strings.Contains(low, "not enough space") || strings.Contains(low, "disk quota") { return errorClass{ exitCode: ExitDiskFull, - message: "Disk is full or quota exceeded. Free up space and try again.\n Check Rho's user state sessions directory for old sessions you can remove.", + message: "Disk is full or quota exceeded. Free up space and try again.\n Check Decoder's user state sessions directory for old sessions you can remove.", } } @@ -260,7 +260,7 @@ func classify(err error) errorClass { (strings.Contains(low, "settings") || strings.Contains(low, "config") || strings.Contains(low, "parse")) { return errorClass{ exitCode: ExitConfig, - message: "Invalid JSON in configuration. Check your Rho settings.json files for syntax errors.\n Tip: use a JSON linter to find the issue.", + message: "Invalid JSON in configuration. Check your Decoder settings.json files for syntax errors.\n Tip: use a JSON linter to find the issue.", } } diff --git a/internal/rhoerr/classify_extra_test.go b/internal/decodererr/classify_extra_test.go similarity index 98% rename from internal/rhoerr/classify_extra_test.go rename to internal/decodererr/classify_extra_test.go index 344c64c1a..40a92747b 100644 --- a/internal/rhoerr/classify_extra_test.go +++ b/internal/decodererr/classify_extra_test.go @@ -1,4 +1,4 @@ -package rhoerr +package decodererr import ( "errors" @@ -100,7 +100,7 @@ func TestClassifyError_InsufficientCredits(t *testing.T) { // cover the maximum-token pre-authorization hold. That must surface as a // quota problem, NOT as "check your API key". func TestClassifyError_AgnesPreDeductionQuota(t *testing.T) { - err := errors.New("flux: agnes chat failed (HTTP 403) [request_id=202607300111184425982109LnnftLG]: " + + err := errors.New("mux: agnes chat failed (HTTP 403) [request_id=202607300111184425982109LnnftLG]: " + "billing/quota problem — check the provider account's balance and limits — " + "AgnesAI_error: 预扣费额度失败, 用户剩余额度: $0.000740, 需要预扣费额度: $0.002068") result := ClassifyError(err) @@ -116,7 +116,7 @@ func TestClassifyError_AgnesPreDeductionQuota(t *testing.T) { } } -// The flux layer tags quota holds as "billing/quota problem"; a bare hint +// The mux layer tags quota holds as "billing/quota problem"; a bare hint // (without the full Agnes body) must still be classified as a quota problem. func TestClassifyError_QuotaHintOnly(t *testing.T) { err := errors.New("billing/quota problem — check the provider account's balance and limits") diff --git a/internal/rhoerr/errors.go b/internal/decodererr/errors.go similarity index 92% rename from internal/rhoerr/errors.go rename to internal/decodererr/errors.go index 17b461b85..8f7dd0eb7 100644 --- a/internal/rhoerr/errors.go +++ b/internal/decodererr/errors.go @@ -1,4 +1,4 @@ -package rhoerr +package decodererr import "fmt" @@ -6,7 +6,7 @@ import "fmt" // It captures which bridge failed, the operation attempted, and the // underlying reason or wrapped error. type BridgeError struct { - Bridge string // e.g., "flux", "memory", "token" + Bridge string // e.g., "mux", "memory", "token" Op string // e.g., "Remember", "Recall", "Enable" Reason string // human-readable reason Err error // underlying error, if any diff --git a/internal/rhoerr/exitcode.go b/internal/decodererr/exitcode.go similarity index 86% rename from internal/rhoerr/exitcode.go rename to internal/decodererr/exitcode.go index fa905891b..8a30b45b4 100644 --- a/internal/rhoerr/exitcode.go +++ b/internal/decodererr/exitcode.go @@ -1,15 +1,15 @@ -package rhoerr +package decodererr // Exit-code taxonomy. // -// rho historically collapsed every failure to exit code 1, which gives an -// agent or shell script driving `rho --print` no way to branch on *why* the +// decoder historically collapsed every failure to exit code 1, which gives an +// agent or shell script driving `decoder --print` no way to branch on *why* the // run failed without scraping stderr. The codes below assign a stable, // documented small-integer meaning to each failure class so a caller can, for // example, retry on a rate-limit (5) but stop immediately on bad credentials // (3). // -// The numbers are part of rho's public CLI contract — append new codes, do +// The numbers are part of decoder's public CLI contract — append new codes, do // not renumber existing ones. Codes are kept below 64 to avoid colliding with // the shell's 128+signal convention and 126/127 (not-executable / not-found). const ( diff --git a/internal/rhoerr/exitcode_test.go b/internal/decodererr/exitcode_test.go similarity index 99% rename from internal/rhoerr/exitcode_test.go rename to internal/decodererr/exitcode_test.go index 76d793ed8..5b003f272 100644 --- a/internal/rhoerr/exitcode_test.go +++ b/internal/decodererr/exitcode_test.go @@ -1,4 +1,4 @@ -package rhoerr +package decodererr import ( "errors" diff --git a/internal/engine/adaptive_prompt.go b/internal/engine/adaptive_prompt.go index 81edd807a..2c65feaa3 100644 --- a/internal/engine/adaptive_prompt.go +++ b/internal/engine/adaptive_prompt.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/textutil" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/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 Rho user state. +// NewAdaptivePrompt creates an adaptive prompt backed by Decoder 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 48fa7302f..636f30996 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/rho/internal/testutil" + "github.com/GrayCodeAI/decoder/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 74273a6cd..bad140c27 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/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/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 rho uses. +// DefaultSections returns the built-in prompt sections that decoder uses. func DefaultSections(ctx PromptBuildContext) []PromptSection { sections := []PromptSection{ { Name: "identity", - 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.", + Content: "You are decoder, 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 27109e5bc..f66aed459 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/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/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 rho.", Priority: 1}) + b.AddSection(PromptSection{Name: "identity", Content: "You are decoder.", 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 rho v2.", Priority: 1}) + b.AddSection(PromptSection{Name: "identity", Content: "You are decoder 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 rho v2." { + if b.Sections[0].Content != "You are decoder 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 rho."}, + {Name: "identity", Content: "You are decoder."}, {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 rho.") { + if !strings.Contains(result, "You are decoder.") { 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 e9203d120..f7dea8d46 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/rho/internal/contracts/agent" + agentcontracts "github.com/GrayCodeAI/decoder/internal/contracts/agent" - "github.com/GrayCodeAI/rho/internal/taskruntime" + "github.com/GrayCodeAI/decoder/internal/taskruntime" ) // BackgroundAgentPool manages async sub-agents that run in the background. diff --git a/internal/engine/agent_intelligence.go b/internal/engine/agent_intelligence.go index bee371c0e..f44c92b65 100644 --- a/internal/engine/agent_intelligence.go +++ b/internal/engine/agent_intelligence.go @@ -4,7 +4,7 @@ import ( "context" "strings" - "github.com/GrayCodeAI/rho/internal/engine/agent" + "github.com/GrayCodeAI/decoder/internal/engine/agent" ) // AgentIntelligence provides smart routing, auto-spawning, and synthesis for agents. diff --git a/internal/engine/agent_intelligence_test.go b/internal/engine/agent_intelligence_test.go index 4db5e5488..e550bb52d 100644 --- a/internal/engine/agent_intelligence_test.go +++ b/internal/engine/agent_intelligence_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/GrayCodeAI/rho/internal/engine/agent" + "github.com/GrayCodeAI/decoder/internal/engine/agent" ) func TestAgentIntelligence_SelectMode(t *testing.T) { diff --git a/internal/engine/agent_resume_test.go b/internal/engine/agent_resume_test.go index 78ade5b31..346c6f814 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/rho/internal/contracts/agent" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/tool" + agentcontracts "github.com/GrayCodeAI/decoder/internal/contracts/agent" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/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 52b5f19ae..957f17c83 100644 --- a/internal/engine/agent_session_tool.go +++ b/internal/engine/agent_session_tool.go @@ -7,17 +7,17 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - agentcontracts "github.com/GrayCodeAI/rho/internal/contracts/agent" + agentcontracts "github.com/GrayCodeAI/decoder/internal/contracts/agent" - 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" + engagent "github.com/GrayCodeAI/decoder/internal/engine/agent" + "github.com/GrayCodeAI/decoder/internal/eventlog" + "github.com/GrayCodeAI/decoder/internal/gitworktree" + "github.com/GrayCodeAI/decoder/internal/hooks" + "github.com/GrayCodeAI/decoder/internal/prompts" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/tool" ) // WireAgentTool sets up typed sub-agent spawning via SpawnController. @@ -56,7 +56,7 @@ func (s *Session) spawnSubAgentRequest(ctx context.Context, req agentcontracts.S Agent: string(mode), Depth: depth, Mode: "one-shot", - Provider: "rho", + Provider: "decoder", Label: truncateSummary(norm.Prompt, 200), AgentProvider: s.ChatLLM().Provider(), AgentModel: s.ChatLLM().Model(), diff --git a/internal/engine/approval_gate.go b/internal/engine/approval_gate.go index a9a1b6790..ea703b1ec 100644 --- a/internal/engine/approval_gate.go +++ b/internal/engine/approval_gate.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) // approval_gate.go implements human-in-the-loop approval gates for high-risk diff --git a/internal/engine/approval_gate_test.go b/internal/engine/approval_gate_test.go index 5d599d4b0..5821619d5 100644 --- a/internal/engine/approval_gate_test.go +++ b/internal/engine/approval_gate_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) func TestApprovalGate_Disabled_NoOp(t *testing.T) { diff --git a/internal/engine/arc_test.go b/internal/engine/arc_test.go index 178027138..4e37487d0 100644 --- a/internal/engine/arc_test.go +++ b/internal/engine/arc_test.go @@ -3,7 +3,7 @@ package engine import ( "testing" - "github.com/GrayCodeAI/rho/internal/conversationarc" + "github.com/GrayCodeAI/decoder/internal/conversationarc" ) func TestSessionArcAccessors(t *testing.T) { diff --git a/internal/engine/architect.go b/internal/engine/architect.go index 08f816be1..b686bec5d 100644 --- a/internal/engine/architect.go +++ b/internal/engine/architect.go @@ -5,9 +5,9 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/planning" - "github.com/GrayCodeAI/rho/internal/provider/routing" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/planning" + "github.com/GrayCodeAI/decoder/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/types" ) // ArchitectConfig configures the two-model architect/editor pipeline. @@ -42,7 +42,7 @@ type PlanStep struct { } // Message is a lightweight chat message used by the architect pipeline. -// This avoids coupling to external message types from flux. +// This avoids coupling to external message types from mux. type ArchitectMessage struct { Role string // "system", "user", "assistant" Content string @@ -133,7 +133,7 @@ type architectProvider struct { a *Architect } -func (p *architectProvider) Chat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) { +func (p *architectProvider) Chat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) { archMsgs := make([]ArchitectMessage, len(messages)) for i, m := range messages { archMsgs[i] = ArchitectMessage{Role: m.Role, Content: m.Content} @@ -146,10 +146,10 @@ func (p *architectProvider) Chat(ctx context.Context, messages []types.FluxMessa if err != nil { return nil, err } - return &types.FluxResponse{Content: out}, nil + return &types.MuxResponse{Content: out}, nil } -func (p *architectProvider) StreamChat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.StreamResult, error) { +func (p *architectProvider) StreamChat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.StreamResult, error) { return nil, nil } func (p *architectProvider) Ping(ctx context.Context) error { return nil } diff --git a/internal/engine/assumptions.go b/internal/engine/assumptions.go index ea8f62415..65cc09d4a 100644 --- a/internal/engine/assumptions.go +++ b/internal/engine/assumptions.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 48edefa69..0d6491ea1 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 "rho: " + description + return "decoder: " + description } - return fmt.Sprintf("rho: auto-commit %s", time.Now().Format("15:04:05")) + return fmt.Sprintf("decoder: 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 0df314b2f..2d04db864 100644 --- a/internal/engine/auto_commit_wire_test.go +++ b/internal/engine/auto_commit_wire_test.go @@ -7,10 +7,10 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/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 ad15b23d6..eee4886cf 100644 --- a/internal/engine/background_runner.go +++ b/internal/engine/background_runner.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - agentcontracts "github.com/GrayCodeAI/rho/internal/contracts/agent" + agentcontracts "github.com/GrayCodeAI/decoder/internal/contracts/agent" - "github.com/GrayCodeAI/rho/internal/taskruntime" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/taskruntime" + "github.com/GrayCodeAI/decoder/internal/tool" ) // BackgroundTask represents an async subagent task running in the background. diff --git a/internal/engine/bmad_features_test.go b/internal/engine/bmad_features_test.go index 1e2c385aa..fd9151882 100644 --- a/internal/engine/bmad_features_test.go +++ b/internal/engine/bmad_features_test.go @@ -5,7 +5,7 @@ import ( "path/filepath" "testing" - "github.com/GrayCodeAI/rho/internal/engine/project" + "github.com/GrayCodeAI/decoder/internal/engine/project" ) func TestClassifyScale(t *testing.T) { diff --git a/internal/engine/branching/cascade.go b/internal/engine/branching/cascade.go index d947da305..f9e5b2a7c 100644 --- a/internal/engine/branching/cascade.go +++ b/internal/engine/branching/cascade.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/engine/cost" - "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/engine/cost" + "github.com/GrayCodeAI/decoder/internal/provider/routing" ) // CascadeRouter selects the optimal model for each request based on task complexity. @@ -211,7 +211,7 @@ func classifyPrompt(prompt string) string { } // modelForTask maps a task type to the appropriate model using configured roles -// and flux catalog tier defaults. +// and mux catalog tier defaults. func (cr *CascadeRouter) modelForTask(taskType string, frugal bool) string { tier := routing.SuggestTierForTask(taskType) @@ -250,7 +250,7 @@ func (cr *CascadeRouter) modelForTask(taskType string, frugal bool) string { } } -// defaultFor returns the best model for a given cost tier via flux catalog tier defaults. +// defaultFor returns the best model for a given cost tier via mux catalog tier defaults. func (cr *CascadeRouter) defaultFor(tier ModelTier) string { provider := "" if info, ok := routing.Find(cr.DefaultModel); ok { diff --git a/internal/engine/branching/cascade_test.go b/internal/engine/branching/cascade_test.go index 4ed89c5bb..da96631d6 100644 --- a/internal/engine/branching/cascade_test.go +++ b/internal/engine/branching/cascade_test.go @@ -3,12 +3,12 @@ package branching import ( "testing" - "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/provider/routing" ) const testProvider = "anthropic" -// testTierModels loads haiku/sonnet/opus model IDs from flux's catalog (not hardcoded). +// testTierModels loads haiku/sonnet/opus model IDs from mux's catalog (not hardcoded). func testTierModels(t *testing.T, provider string) (haiku, sonnet, opus string) { t.Helper() haiku = routing.PreferredModelForTier(provider, routing.TierHaiku, "") diff --git a/internal/engine/branching/shadow.go b/internal/engine/branching/shadow.go index af710200d..30ed424c9 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("", "rho-shadow-*") + dir, err := os.MkdirTemp("", "decoder-shadow-*") if err != nil { return nil, fmt.Errorf("shadow workspace: create temp dir: %w", err) } diff --git a/internal/engine/cache_gate.go b/internal/engine/cache_gate.go index 8311b0af2..1f3523b38 100644 --- a/internal/engine/cache_gate.go +++ b/internal/engine/cache_gate.go @@ -6,4 +6,4 @@ package engine // cache_planner.go (planCache / cacheDecision) implements this arithmetic: // segments the stable prefix, computes breakpoints, and enables caching only // when the expected reuse count beats the write premium. Full wire-format -// lowering and fleet-wide key-sharding remain flux-side. +// lowering and fleet-wide key-sharding remain mux-side. diff --git a/internal/engine/cache_gate_test.go b/internal/engine/cache_gate_test.go index 813445eb4..5860a40ca 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestCacheDecisionNonAnthropicOff(t *testing.T) { @@ -31,7 +31,7 @@ func TestCacheDecisionLargePrefixOn(t *testing.T) { } func TestCacheDecisionCountsToolCatalog(t *testing.T) { - tools := []types.FluxTool{ + tools := []types.MuxTool{ {Name: "t1", Description: strings.Repeat("d", 9000), Parameters: map[string]interface{}{"type": "object"}}, } sys := "small" diff --git a/internal/engine/cache_planner.go b/internal/engine/cache_planner.go index e73eb7de4..d0975042e 100644 --- a/internal/engine/cache_planner.go +++ b/internal/engine/cache_planner.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // Prompt-cache segment planning, extending the caveman-style break-even gate @@ -12,7 +12,7 @@ import ( // catalog) is split into cacheable segments, breakpoints are computed at the // last tool and last system boundaries, and the whole thing is enabled only // when the provider economics pay off for the expected reuse count. Full -// wire-format lowering and key sharding across a fleet remain flux-side; +// wire-format lowering and key sharding across a fleet remain mux-side; // this is the deterministic client planner. // Anthropic 5m cache economics: uncached = 1.0x, cache write = 1.25x, @@ -52,7 +52,7 @@ type CachePlan struct { // planCache computes whether and how to cache the stable prefix for a call. // Deterministic and pure so it can be tested without a provider connection. -func planCache(provider, systemPrompt string, tools []types.FluxTool, expectedReuse int) CachePlan { +func planCache(provider, systemPrompt string, tools []types.MuxTool, expectedReuse int) CachePlan { if expectedReuse <= 0 { expectedReuse = cacheDefaultReuse } @@ -112,6 +112,6 @@ func planCache(provider, systemPrompt string, tools []types.FluxTool, expectedRe // cacheDecision reports whether to request provider-native caching, delegating // to the planner at the default reuse. Kept for backward compatibility. -func cacheDecision(provider, systemPrompt string, tools []types.FluxTool) bool { +func cacheDecision(provider, systemPrompt string, tools []types.MuxTool) bool { return planCache(provider, systemPrompt, tools, cacheDefaultReuse).Enabled } diff --git a/internal/engine/cache_planner_test.go b/internal/engine/cache_planner_test.go index c87bdcb2c..cf21858d5 100644 --- a/internal/engine/cache_planner_test.go +++ b/internal/engine/cache_planner_test.go @@ -4,14 +4,14 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func bigSys(n int) string { return strings.Repeat("s", n) } func TestPlanCacheSegmentsAndBreakpoints(t *testing.T) { sys := bigSys(cacheMinPrefixBytes + 2048) - tools := []types.FluxTool{{Name: "t", Description: strings.Repeat("d", 4096), Parameters: map[string]interface{}{"type": "object"}}} + tools := []types.MuxTool{{Name: "t", Description: strings.Repeat("d", 4096), Parameters: map[string]interface{}{"type": "object"}}} p := planCache("anthropic", sys, tools, 2) if !p.Enabled { t.Fatalf("expected enabled, reason=%q", p.Reason) diff --git a/internal/engine/chat_provider.go b/internal/engine/chat_provider.go index 090ffeda4..84b33cda3 100644 --- a/internal/engine/chat_provider.go +++ b/internal/engine/chat_provider.go @@ -5,13 +5,13 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/provider/gateway" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/types" ) -// BuildChatProvider adapts Rho's engine-backed session client to the smaller +// BuildChatProvider adapts Decoder'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 Flux's engine facade. +// credentials, routing, and transport remain owned by Mux's engine facade. func BuildChatProvider(ctx context.Context, selection gateway.Selection, legacyProvider string) (types.ChatProvider, string, error) { client, provider, _, err := BuildChatClient(ctx, selection, legacyProvider) if err != nil { @@ -19,7 +19,7 @@ func BuildChatProvider(ctx context.Context, selection gateway.Selection, legacyP } provider = strings.TrimSpace(provider) if provider == "" { - return nil, "", fmt.Errorf("flux transport: provider unavailable") + return nil, "", fmt.Errorf("mux transport: provider unavailable") } return &engineChatProvider{ client: client, @@ -28,7 +28,7 @@ func BuildChatProvider(ctx context.Context, selection gateway.Selection, legacyP }, provider, nil } -// engineChatProvider keeps compatibility-only provider behavior at Rho's +// engineChatProvider keeps compatibility-only provider behavior at Decoder's // integration edge while all generation goes through the engine ChatClient. type engineChatProvider struct { client ChatClient @@ -38,7 +38,7 @@ type engineChatProvider struct { var _ types.ChatProvider = (*engineChatProvider)(nil) -func (p *engineChatProvider) Chat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) { +func (p *engineChatProvider) Chat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) { if strings.TrimSpace(opts.Provider) == "" { opts.Provider = p.provider } @@ -48,7 +48,7 @@ func (p *engineChatProvider) Chat(ctx context.Context, messages []types.FluxMess return p.client.Chat(ctx, messages, opts) } -func (p *engineChatProvider) StreamChat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.StreamResult, error) { +func (p *engineChatProvider) StreamChat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.StreamResult, error) { if strings.TrimSpace(opts.Provider) == "" { opts.Provider = p.provider } diff --git a/internal/engine/chat_provider_test.go b/internal/engine/chat_provider_test.go index 35bc02d26..9083595ab 100644 --- a/internal/engine/chat_provider_test.go +++ b/internal/engine/chat_provider_test.go @@ -5,8 +5,8 @@ import ( "errors" "testing" - "github.com/GrayCodeAI/flux/llm" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" + "github.com/GrayCodeAI/mux/llm" ) type recordingChatClient struct { @@ -14,14 +14,14 @@ type recordingChatClient struct { streamOptions types.ChatOptions } -func (c *recordingChatClient) Chat(_ context.Context, _ []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) { +func (c *recordingChatClient) Chat(_ context.Context, _ []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) { c.chatOptions = opts - return &types.FluxResponse{Content: "ok"}, nil + return &types.MuxResponse{Content: "ok"}, nil } -func (c *recordingChatClient) StreamChatContinue(_ context.Context, _ []types.FluxMessage, opts types.ChatOptions, _ types.ContinuationConfig) (*types.StreamResult, error) { +func (c *recordingChatClient) StreamChatContinue(_ context.Context, _ []types.MuxMessage, opts types.ChatOptions, _ types.ContinuationConfig) (*types.StreamResult, error) { c.streamOptions = opts - events := make(chan types.FluxStreamEvent) + events := make(chan types.MuxStreamEvent) close(events) return llm.NewStreamResult(events, "", nil), nil } diff --git a/internal/engine/chat_replay.go b/internal/engine/chat_replay.go index ca7174fa4..dc9278778 100644 --- a/internal/engine/chat_replay.go +++ b/internal/engine/chat_replay.go @@ -4,29 +4,29 @@ import ( "context" "os" - "github.com/GrayCodeAI/rho/internal/replaycache" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/replaycache" + "github.com/GrayCodeAI/decoder/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 = "RHO_REPLAY_CACHE_DIR" +const replayCacheDirEnv = "DECODER_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 = "RHO_REPLAY_FINGERPRINT" +const replayFingerprintEnv = "DECODER_REPLAY_FINGERPRINT" // replayKey builds the cache key for one completion request. -func replayKey(opts types.ChatOptions, messages []types.FluxMessage) string { +func replayKey(opts types.ChatOptions, messages []types.MuxMessage) string { return replaycache.Key(replaycache.Fingerprint(os.Getenv(replayFingerprintEnv)), opts.Provider, opts.Model, messages, opts.MaxTokens) } // chatWithReplay wraps client.Chat with the replay cache when -// RHO_REPLAY_CACHE_DIR is set; otherwise it calls straight through. -func chatWithReplay(ctx context.Context, client ChatClient, messages []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) { +// DECODER_REPLAY_CACHE_DIR is set; otherwise it calls straight through. +func chatWithReplay(ctx context.Context, client ChatClient, messages []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) { dir := os.Getenv(replayCacheDirEnv) if dir == "" { return client.Chat(ctx, messages, opts) diff --git a/internal/engine/chat_replay_test.go b/internal/engine/chat_replay_test.go index ac25ea8b2..81d345af6 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestChatWithReplayDisabledPassesThrough(t *testing.T) { @@ -14,7 +14,7 @@ func TestChatWithReplayDisabledPassesThrough(t *testing.T) { calls := 0 client := &countingClient{n: &calls, resp: "live"} resp, err := chatWithReplay(context.Background(), client, - []types.FluxMessage{{Role: "user", Content: "hi"}}, types.ChatOptions{Model: "m"}) + []types.MuxMessage{{Role: "user", Content: "hi"}}, types.ChatOptions{Model: "m"}) if err != nil || resp.Content != "live" { t.Fatalf("resp=%v err=%v", resp, err) } @@ -29,7 +29,7 @@ func TestChatWithReplayCachesAndHits(t *testing.T) { calls := 0 client := &countingClient{n: &calls, resp: "live"} - msgs := []types.FluxMessage{{Role: "user", Content: "deterministic"}} + msgs := []types.MuxMessage{{Role: "user", Content: "deterministic"}} opts := types.ChatOptions{Provider: "p", Model: "m", MaxTokens: 10} first, err := chatWithReplay(context.Background(), client, msgs, opts) @@ -57,7 +57,7 @@ func TestChatWithReplayFingerprintInvalidates(t *testing.T) { calls := 0 client := &countingClient{n: &calls, resp: "live"} - msgs := []types.FluxMessage{{Role: "user", Content: "x"}} + msgs := []types.MuxMessage{{Role: "user", Content: "x"}} opts := types.ChatOptions{Provider: "p", Model: "m"} if _, err := chatWithReplay(context.Background(), client, msgs, opts); err != nil { @@ -80,7 +80,7 @@ func TestChatWithReplayDoesNotCacheErrors(t *testing.T) { client := &countingClient{n: &calls, err: errors.New("boom")} for i := 0; i < 2; i++ { if _, err := chatWithReplay(context.Background(), client, - []types.FluxMessage{{Role: "user", Content: "e"}}, types.ChatOptions{}); err == nil { + []types.MuxMessage{{Role: "user", Content: "e"}}, types.ChatOptions{}); err == nil { t.Fatal("expected error to pass through") } } @@ -99,20 +99,20 @@ type countingClient struct { err error } -func (c *countingClient) Chat(context.Context, []types.FluxMessage, types.ChatOptions) (*types.FluxResponse, error) { +func (c *countingClient) Chat(context.Context, []types.MuxMessage, types.ChatOptions) (*types.MuxResponse, error) { *c.n++ if c.err != nil { return nil, c.err } - return &types.FluxResponse{Content: c.resp}, nil + return &types.MuxResponse{Content: c.resp}, nil } -func (c *countingClient) StreamChatContinue(context.Context, []types.FluxMessage, types.ChatOptions, types.ContinuationConfig) (*types.StreamResult, error) { +func (c *countingClient) StreamChatContinue(context.Context, []types.MuxMessage, types.ChatOptions, types.ContinuationConfig) (*types.StreamResult, error) { *c.n++ if c.err != nil { return nil, c.err } - ch := make(chan types.FluxStreamEvent, 1) - ch <- types.FluxStreamEvent{Type: "done"} + ch := make(chan types.MuxStreamEvent, 1) + ch <- types.MuxStreamEvent{Type: "done"} return &types.StreamResult{Events: ch}, nil } diff --git a/internal/engine/chat_service.go b/internal/engine/chat_service.go index 96f1dd458..dd40788c3 100644 --- a/internal/engine/chat_service.go +++ b/internal/engine/chat_service.go @@ -7,16 +7,16 @@ import ( "sync" "time" - "github.com/GrayCodeAI/flux/engine" - "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" + "github.com/GrayCodeAI/decoder/internal/observability/metrics" + "github.com/GrayCodeAI/decoder/internal/resilience/ratelimit" + "github.com/GrayCodeAI/decoder/internal/resilience/retry" + "github.com/GrayCodeAI/decoder/internal/types" + "github.com/GrayCodeAI/mux/engine" ) // ChatService is the Session's view of the LLM transport. It owns the -// Flux client, provider/model identity, and compatibility-only rate/retry -// controls. Production facade clients own resilience inside Flux. The service +// Mux client, provider/model identity, and compatibility-only rate/retry +// controls. Production facade clients own resilience inside Mux. The service // is constructed once in NewSessionWithClient and consulted by // agentLoop every turn. // @@ -26,13 +26,13 @@ import ( // plan. type ChatService struct { mu sync.RWMutex - // client is the flux transport. Always non-nil after construction. + // client is the mux transport. Always non-nil after construction. client ChatClient // provider / model are the active LLM identity. provider string model string // deploymentRouting is true when the client is catalog-backed - // (e.g. DeploymentRouter from flux/runtime.ChatProvider). + // (e.g. DeploymentRouter from mux/runtime.ChatProvider). deploymentRouting bool // rateLimiter is the per-session token bucket. rateLimiter *ratelimit.Limiter @@ -43,7 +43,7 @@ type ChatService struct { // contCfg is the continuation config for StreamChatContinue. contCfg types.ContinuationConfig // outputSchema, when non-empty, requests a JSON-schema-constrained - // response. Plumbed into flux's ChatOptions.ResponseFormat. + // response. Plumbed into mux's ChatOptions.ResponseFormat. outputSchema string // thinkingEnabled is the generic host preference for provider thinking / // reasoning toggles (Z.AI, LongCat, Agnes, …). nil leaves provider default. @@ -99,7 +99,7 @@ func NewChatService(client ChatClient, cfg ChatServiceConfig) *ChatService { } } -// Client returns the underlying flux client. Exposed for callers (e.g. +// Client returns the underlying mux client. Exposed for callers (e.g. // background goroutines) that need to issue one-off LLM calls without // the agent-loop retry wrapper. func (c *ChatService) Client() ChatClient { @@ -185,7 +185,7 @@ func (c *ChatService) Reattach(client ChatClient, provider string) { // BuildOptions constructs a types.ChatOptions for an outgoing LLM call, // encoding all the knobs the agent loop needs (system prompt, model, // max tokens, tools, structured output, etc.). -func (c *ChatService) BuildOptions(systemPrompt, activeModel string, maxTokens int, tools []types.FluxTool) types.ChatOptions { +func (c *ChatService) BuildOptions(systemPrompt, activeModel string, maxTokens int, tools []types.MuxTool) types.ChatOptions { c.mu.RLock() provider := c.provider thinkingEnabled := c.thinkingEnabled @@ -207,25 +207,25 @@ 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 (RHO_TOOL_SHRINK=1): fail-open, so the + // Opt-in tool-catalog compression (DECODER_TOOL_SHRINK=1): fail-open, so the // returned tools equal the input whenever anything is off or drifts. - opts.Tools = shrinkFluxTools(opts.Tools) + opts.Tools = shrinkMuxTools(opts.Tools) return opts } // Stream issues a streaming LLM call with retry, rate-limit, and // emergency-compact. The returned *types.StreamResult's Events channel -// emits FluxStreamEvent values; the caller must Close() the result +// emits MuxStreamEvent values; the caller must Close() the result // when done. // // On context cancellation mid-call, returns the cancellation error wrapped // with whatever partial state the upstream had emitted (caller should // check ctx.Err()). // -// Flux facade clients advertise that they manage provider resilience. For +// Mux 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 Rho's compatibility retry/rate layer. -func (c *ChatService) Stream(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.StreamResult, error) { +// once; injected legacy clients retain Decoder's compatibility retry/rate layer. +func (c *ChatService) Stream(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.StreamResult, error) { c.mu.RLock() client := c.client rateLimiter := c.rateLimiter @@ -282,11 +282,11 @@ const ( // emergencyCompactWindow messages. This is a last-resort path (the normal // context governor in the agent loop does the real summarization); here we // only need the retry to succeed once. -func emergencyCompact(messages []types.FluxMessage) []types.FluxMessage { +func emergencyCompact(messages []types.MuxMessage) []types.MuxMessage { if len(messages) <= emergencyCompactMin { return messages } - out := make([]types.FluxMessage, 0, emergencyCompactWindow+1) + out := make([]types.MuxMessage, 0, emergencyCompactWindow+1) for _, m := range messages { if m.Role == "system" { out = append(out, m) @@ -304,7 +304,7 @@ func emergencyCompact(messages []types.FluxMessage) []types.FluxMessage { // Chat issues a non-streaming LLM call. Used by background goroutines // (sleeptime consolidation, skill distillation) that don't need // incremental events. -func (c *ChatService) Chat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) { +func (c *ChatService) Chat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) { client := c.Client() if client == nil { return nil, errors.New("chat service: no client configured") diff --git a/internal/engine/chat_service_test.go b/internal/engine/chat_service_test.go index cd8c51198..1b651c433 100644 --- a/internal/engine/chat_service_test.go +++ b/internal/engine/chat_service_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/GrayCodeAI/flux/llm" - "github.com/GrayCodeAI/rho/internal/resilience/retry" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/resilience/retry" + "github.com/GrayCodeAI/decoder/internal/types" + "github.com/GrayCodeAI/mux/llm" ) // 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 rho. ", cacheMinPrefixBytes/7+64), "claude-opus-4", 4096, nil) - opts.System = "you are rho" // restore exact assertion target + opts := svc.BuildOptions(strings.Repeat("you are decoder. ", cacheMinPrefixBytes/7+64), "claude-opus-4", 4096, nil) + opts.System = "you are decoder" // 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 rho" { + if opts.System != "you are decoder" { t.Errorf("expected system prompt to be set, got %q", opts.System) } } @@ -137,7 +137,7 @@ func TestChatService_ChatDelegatesToClient(t *testing.T) { }) resp, err := svc.Chat( context.Background(), - []types.FluxMessage{{Role: "user", Content: "hi"}}, + []types.MuxMessage{{Role: "user", Content: "hi"}}, svc.BuildOptions("sys", "claude-opus-4", 1024, nil), ) if err != nil { @@ -152,11 +152,11 @@ func TestChatService_ChatDelegatesToClient(t *testing.T) { // ChatService.Chat surfaces the underlying error unchanged. type errClient struct{ err error } -func (e *errClient) Chat(_ context.Context, _ []types.FluxMessage, _ types.ChatOptions) (*types.FluxResponse, error) { +func (e *errClient) Chat(_ context.Context, _ []types.MuxMessage, _ types.ChatOptions) (*types.MuxResponse, error) { return nil, e.err } -func (e *errClient) StreamChatContinue(_ context.Context, _ []types.FluxMessage, _ types.ChatOptions, _ types.ContinuationConfig) (*types.StreamResult, error) { +func (e *errClient) StreamChatContinue(_ context.Context, _ []types.MuxMessage, _ types.ChatOptions, _ types.ContinuationConfig) (*types.StreamResult, error) { return nil, e.err } @@ -174,11 +174,11 @@ type resilienceManagingTestClient struct { calls int } -func (c *resilienceManagingTestClient) Chat(context.Context, []types.FluxMessage, types.ChatOptions) (*types.FluxResponse, error) { +func (c *resilienceManagingTestClient) Chat(context.Context, []types.MuxMessage, types.ChatOptions) (*types.MuxResponse, error) { return nil, c.err } -func (c *resilienceManagingTestClient) StreamChatContinue(context.Context, []types.FluxMessage, types.ChatOptions, types.ContinuationConfig) (*types.StreamResult, error) { +func (c *resilienceManagingTestClient) StreamChatContinue(context.Context, []types.MuxMessage, types.ChatOptions, types.ContinuationConfig) (*types.StreamResult, error) { c.calls++ return nil, c.err } @@ -187,7 +187,7 @@ func (c *resilienceManagingTestClient) ManagesResilience() bool { return true } func TestChatService_DoesNotDuplicateEngineResilience(t *testing.T) { client := &resilienceManagingTestClient{err: errors.New("routed transport failed")} svc := NewChatService(client, ChatServiceConfig{}) - _, err := svc.Stream(context.Background(), []types.FluxMessage{{Role: "user", Content: "hi"}}, types.ChatOptions{}) + _, err := svc.Stream(context.Background(), []types.MuxMessage{{Role: "user", Content: "hi"}}, types.ChatOptions{}) if err == nil { t.Fatal("expected stream error") } @@ -200,16 +200,16 @@ type flakyLegacyStartClient struct { calls int } -func (*flakyLegacyStartClient) Chat(context.Context, []types.FluxMessage, types.ChatOptions) (*types.FluxResponse, error) { +func (*flakyLegacyStartClient) Chat(context.Context, []types.MuxMessage, types.ChatOptions) (*types.MuxResponse, error) { return nil, nil } -func (c *flakyLegacyStartClient) StreamChatContinue(context.Context, []types.FluxMessage, types.ChatOptions, types.ContinuationConfig) (*types.StreamResult, error) { +func (c *flakyLegacyStartClient) StreamChatContinue(context.Context, []types.MuxMessage, types.ChatOptions, types.ContinuationConfig) (*types.StreamResult, error) { c.calls++ if c.calls == 1 { return nil, errors.New("temporary transport failure") } - events := make(chan types.FluxStreamEvent) + events := make(chan types.MuxStreamEvent) close(events) return llm.NewStreamResult(events, "", nil), nil } @@ -243,21 +243,21 @@ func retryConfigForBoundaryTest() retry.Config { // received on the retry so tests can assert the emergency compact (H3). type overflowThenCaptureClient struct { calls int - seen []types.FluxMessage + seen []types.MuxMessage started bool } -func (*overflowThenCaptureClient) Chat(context.Context, []types.FluxMessage, types.ChatOptions) (*types.FluxResponse, error) { +func (*overflowThenCaptureClient) Chat(context.Context, []types.MuxMessage, types.ChatOptions) (*types.MuxResponse, error) { return nil, nil } -func (c *overflowThenCaptureClient) StreamChatContinue(_ context.Context, messages []types.FluxMessage, _ types.ChatOptions, _ types.ContinuationConfig) (*types.StreamResult, error) { +func (c *overflowThenCaptureClient) StreamChatContinue(_ context.Context, messages []types.MuxMessage, _ types.ChatOptions, _ types.ContinuationConfig) (*types.StreamResult, error) { c.calls++ if c.calls == 1 { return nil, errors.New("input too long: 120000 tokens exceeds the limit of 100000") } c.started = true - c.seen = append([]types.FluxMessage(nil), messages...) + c.seen = append([]types.MuxMessage(nil), messages...) return &types.StreamResult{}, nil } @@ -265,10 +265,10 @@ func (c *overflowThenCaptureClient) StreamChatContinue(_ context.Context, messag // context-overflow error, the retry sends a compacted (smaller) transcript // instead of re-sending the same overflowing messages. func TestChatService_EmergencyCompactTrimsBeforeRetry(t *testing.T) { - var messages []types.FluxMessage - messages = append(messages, types.FluxMessage{Role: "system", Content: "sys"}) + var messages []types.MuxMessage + messages = append(messages, types.MuxMessage{Role: "system", Content: "sys"}) for i := 0; i < 100; i++ { - messages = append(messages, types.FluxMessage{Role: "user", Content: "message-" + itoaForTest(i)}) + messages = append(messages, types.MuxMessage{Role: "user", Content: "message-" + itoaForTest(i)}) } client := &overflowThenCaptureClient{} diff --git a/internal/engine/client_interface.go b/internal/engine/client_interface.go index fb3560a61..d4679dcfc 100644 --- a/internal/engine/client_interface.go +++ b/internal/engine/client_interface.go @@ -3,15 +3,15 @@ package engine import ( "context" - "github.com/GrayCodeAI/flux/llm" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" + "github.com/GrayCodeAI/mux/llm" ) // ChatClient abstracts the LLM client methods used by Session. -// The production implementation is the Flux engine adapter; tests can inject a mock. +// The production implementation is the Mux engine adapter; tests can inject a mock. type ChatClient interface { - Chat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) - StreamChatContinue(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions, cfg types.ContinuationConfig) (*types.StreamResult, error) + Chat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) + StreamChatContinue(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions, cfg types.ContinuationConfig) (*types.StreamResult, error) } // resilienceManagingChatClient is an optional capability implemented by @@ -58,18 +58,18 @@ func NewMockClientForTest() ChatClient { type exportedMockClient struct{} -func (m *exportedMockClient) Chat(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions) (*types.FluxResponse, error) { - return &types.FluxResponse{ +func (m *exportedMockClient) Chat(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions) (*types.MuxResponse, error) { + return &types.MuxResponse{ Content: "mock test response", FinishReason: "end_turn", - Usage: &types.FluxUsage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15}, + Usage: &types.MuxUsage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15}, }, nil } -func (m *exportedMockClient) StreamChatContinue(ctx context.Context, messages []types.FluxMessage, opts types.ChatOptions, cfg types.ContinuationConfig) (*types.StreamResult, error) { - ch := make(chan types.FluxStreamEvent, 5) - ch <- types.FluxStreamEvent{Type: "content", Content: "mock streamed response"} - ch <- types.FluxStreamEvent{Type: "done", StopReason: "end_turn", Usage: &types.FluxUsage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15}} +func (m *exportedMockClient) StreamChatContinue(ctx context.Context, messages []types.MuxMessage, opts types.ChatOptions, cfg types.ContinuationConfig) (*types.StreamResult, error) { + ch := make(chan types.MuxStreamEvent, 5) + ch <- types.MuxStreamEvent{Type: "content", Content: "mock streamed response"} + ch <- types.MuxStreamEvent{Type: "done", StopReason: "end_turn", Usage: &types.MuxUsage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15}} close(ch) return &types.StreamResult{Events: ch}, nil } diff --git a/internal/engine/coding_soul.go b/internal/engine/coding_soul.go index f85d58f0b..c256f5664 100644 --- a/internal/engine/coding_soul.go +++ b/internal/engine/coding_soul.go @@ -5,11 +5,11 @@ import ( "path/filepath" "strings" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/storage" ) // CodingSoul defines the persistent coding personality and style preferences. -// Loaded from Rho user state. +// Loaded from Decoder 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 Rho user state based on my coding patterns. Analyze my recent code and infer: + return `Generate a coding soul profile for Decoder 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 5f777cf95..845a6ae68 100644 --- a/internal/engine/compact.go +++ b/internal/engine/compact.go @@ -7,11 +7,11 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/types" - modelPkg "github.com/GrayCodeAI/rho/internal/provider/routing" + modelPkg "github.com/GrayCodeAI/decoder/internal/provider/routing" ) // ShouldAutoCompact returns true if the conversation is approaching context limits. @@ -104,22 +104,22 @@ 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("RHO_COMPACTION_SEGMENT_DETAIL")) + detail, _ := compact.ParseCompactionDetail(os.Getenv("DECODER_COMPACTION_SEGMENT_DETAIL")) if _, err := compact.WriteCompactionSegment(sessionID, compactedMsgs, detail); err != nil { slog.Debug("compaction segment persistence skipped", "error", err) } } tail := raw[len(raw)-keepEnd:] - keep := make([]types.FluxMessage, 0, len(tail)+2) - keep = append(keep, types.FluxMessage{ + keep := make([]types.MuxMessage, 0, len(tail)+2) + keep = append(keep, types.MuxMessage{ Role: "user", Content: "[Conversation summary]\n" + summary + "\n\n[Continue from the recent messages below.]", }) // Only emit the assistant ack when the tail starts with a user message; // two adjacent assistant messages are rejected by OpenAI-compat providers. if len(tail) == 0 || tail[0].Role != "assistant" { - keep = append(keep, types.FluxMessage{ + keep = append(keep, types.MuxMessage{ Role: "assistant", Content: "Understood. I have the context from the summary above. Continuing.", }) @@ -133,7 +133,7 @@ func (s *Session) smartCompactBody(ctx context.Context) { // window on very long transcripts. const summaryInputRuneCap = 32_000 -func (s *Session) generateSummary(ctx context.Context, raw []types.FluxMessage) string { +func (s *Session) generateSummary(ctx context.Context, raw []types.MuxMessage) string { // Incremental compaction: if a prior summary was persisted by an earlier // compaction, merge the NEW messages into it rather than re-summarizing the // entire conversation from scratch. This preserves already-captured context @@ -152,9 +152,9 @@ func (s *Session) generateSummary(ctx context.Context, raw []types.FluxMessage) // Build a compact version of the conversation for summarization // using the structured compaction prompt from compact_prompt.go - var summaryMsgs []types.FluxMessage + var summaryMsgs []types.MuxMessage compactPrompt := compact.BuildIncrementalCompactPrompt(prior) - summaryMsgs = append(summaryMsgs, types.FluxMessage{ + summaryMsgs = append(summaryMsgs, types.MuxMessage{ Role: "user", Content: compactPrompt + "\n\nConversation:\n", }) @@ -269,7 +269,7 @@ func CompressMessageContent(content string, maxTokens int) string { } // compactModel returns the cheapest available model for the current provider. -// Queries flux's catalog at runtime — no hardcoded model names. +// Queries mux's catalog at runtime — no hardcoded model names. // Summarization doesn't need frontier reasoning, so the cheapest model suffices. func (s *Session) compactModel() string { provider := strings.ToLower(s.ChatLLM().Provider()) diff --git a/internal/engine/compact/aliases_extra_test.go b/internal/engine/compact/aliases_extra_test.go index b67ab875c..2fe73f972 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestDefaultConfig(t *testing.T) { @@ -83,7 +83,7 @@ func TestCountClearableToolResults(t *testing.T) { func TestIsThinkingMessage(t *testing.T) { // Empty message - result := isThinkingMessage(types.FluxMessage{}) + result := isThinkingMessage(types.MuxMessage{}) if result { t.Error("expected false for empty message") } @@ -130,7 +130,7 @@ func TestReadSessionMemory_NonExistent(t *testing.T) { func TestIsCompactBoundary(t *testing.T) { // Empty message - result := IsCompactBoundary(types.FluxMessage{}) + result := IsCompactBoundary(types.MuxMessage{}) if result { t.Error("expected false for empty message") } diff --git a/internal/engine/compact/api.go b/internal/engine/compact/api.go index ad8f736d3..3c353b66d 100644 --- a/internal/engine/compact/api.go +++ b/internal/engine/compact/api.go @@ -1,9 +1,9 @@ package compact import ( - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" ) type APICompactConfig struct { @@ -30,7 +30,7 @@ var mutatingTools = map[string]bool{ "NotebookEdit": true, } -func APICompactMessages(msgs []types.FluxMessage, cfg APICompactConfig) []types.FluxMessage { +func APICompactMessages(msgs []types.MuxMessage, cfg APICompactConfig) []types.MuxMessage { totalTokens := token.EstimateTokens(msgs) if totalTokens < cfg.TriggerTokens { return msgs @@ -41,7 +41,7 @@ func APICompactMessages(msgs []types.FluxMessage, cfg APICompactConfig) []types. return msgs } - result := make([]types.FluxMessage, len(msgs)) + result := make([]types.MuxMessage, len(msgs)) copy(result, msgs) freed := 0 @@ -101,7 +101,7 @@ func APICompactMessages(msgs []types.FluxMessage, cfg APICompactConfig) []types. return result } -func CountClearableToolResults(msgs []types.FluxMessage) int { +func CountClearableToolResults(msgs []types.MuxMessage) int { count := 0 for _, m := range msgs { if len(m.ToolResults) > 0 && m.ToolResults[0].Content != "[Old tool result content cleared]" { @@ -114,6 +114,6 @@ func CountClearableToolResults(msgs []types.FluxMessage) int { return count } -func isThinkingMessage(m types.FluxMessage) bool { +func isThinkingMessage(m types.MuxMessage) bool { return len(m.Content) > 0 && m.Content[0] == '<' && len(m.ToolUse) == 0 } diff --git a/internal/engine/compact/files.go b/internal/engine/compact/files.go index c8e4c7c4b..252c561dc 100644 --- a/internal/engine/compact/files.go +++ b/internal/engine/compact/files.go @@ -6,8 +6,8 @@ import ( "strconv" "strings" - "github.com/GrayCodeAI/rho/internal/permissions" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/types" ) type FileTracker struct { @@ -36,7 +36,7 @@ func (ft *FileTracker) RecordModified(path string) { ft.ModifiedFiles[path]++ } -func (ft *FileTracker) ExtractFromMessages(messages []types.FluxMessage) { +func (ft *FileTracker) ExtractFromMessages(messages []types.MuxMessage) { for _, msg := range messages { if msg.Role != "assistant" { continue diff --git a/internal/engine/compact/files_test.go b/internal/engine/compact/files_test.go index cb02e5fda..18dd95f02 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestFileTracker_NewFileTracker(t *testing.T) { @@ -67,7 +67,7 @@ func TestFileTracker_RecordModified_Empty(t *testing.T) { func TestFileTracker_ExtractFromMessages(t *testing.T) { t.Parallel() ft := NewFileTracker() - messages := []types.FluxMessage{ + messages := []types.MuxMessage{ { Role: "assistant", ToolUse: []types.ToolCall{ @@ -88,7 +88,7 @@ func TestFileTracker_ExtractFromMessages(t *testing.T) { func TestFileTracker_ExtractFromMessages_SkipNonAssistant(t *testing.T) { t.Parallel() ft := NewFileTracker() - messages := []types.FluxMessage{ + messages := []types.MuxMessage{ {Role: "user", ToolUse: []types.ToolCall{{Name: "Read", Arguments: map[string]interface{}{"path": "x.go"}}}}, } ft.ExtractFromMessages(messages) diff --git a/internal/engine/compact/incremental_test.go b/internal/engine/compact/incremental_test.go index 93726db52..a63f63de6 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestBuildIncrementalCompactPromptIncludesPriorSummary(t *testing.T) { @@ -32,7 +32,7 @@ func TestBuildIncrementalCompactPromptEmptyPriorFallsBack(t *testing.T) { } func TestExtractPriorSummaryFound(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "[Conversation summary]\n## Goal\n- build\n\n[Continue from the recent messages below.]"}, {Role: "assistant", Content: "ok"}, } @@ -43,7 +43,7 @@ func TestExtractPriorSummaryFound(t *testing.T) { } func TestExtractPriorSummaryNone(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hello"}, } if got := ExtractPriorSummary(msgs); got != "" { @@ -52,7 +52,7 @@ func TestExtractPriorSummaryNone(t *testing.T) { } func TestExtractPriorSummaryIgnoresNonSummaryFirst(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "[Session memory summary]\nstuff"}, {Role: "user", Content: "[Conversation summary]\n## Goal\n- x"}, } diff --git a/internal/engine/compact/micro.go b/internal/engine/compact/micro.go index 3c74ec7f4..3b5fa3c77 100644 --- a/internal/engine/compact/micro.go +++ b/internal/engine/compact/micro.go @@ -3,7 +3,7 @@ package compact import ( "time" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) type MicroCompactConfig struct { @@ -25,7 +25,7 @@ type resultInfo struct { toolName string } -func MicrocompactMessages(msgs []types.FluxMessage, cfg MicroCompactConfig) []types.FluxMessage { +func MicrocompactMessages(msgs []types.MuxMessage, cfg MicroCompactConfig) []types.MuxMessage { var compactableResults []resultInfo for i, m := range msgs { if len(m.ToolResults) == 0 { @@ -47,7 +47,7 @@ func MicrocompactMessages(msgs []types.FluxMessage, cfg MicroCompactConfig) []ty clearSet[compactableResults[i].index] = true } - result := make([]types.FluxMessage, len(msgs)) + result := make([]types.MuxMessage, len(msgs)) copy(result, msgs) for idx := range clearSet { oldResults := result[idx].ToolResults @@ -59,7 +59,7 @@ func MicrocompactMessages(msgs []types.FluxMessage, cfg MicroCompactConfig) []ty IsError: tr.IsError, } } - result[idx] = types.FluxMessage{ + result[idx] = types.MuxMessage{ Role: result[idx].Role, ToolResults: newResults, } @@ -68,7 +68,7 @@ func MicrocompactMessages(msgs []types.FluxMessage, cfg MicroCompactConfig) []ty return result } -func ToolNameForResult(m types.FluxMessage, msgs []types.FluxMessage) string { +func ToolNameForResult(m types.MuxMessage, msgs []types.MuxMessage) string { if len(m.ToolResults) == 0 { return "" } @@ -83,7 +83,7 @@ func ToolNameForResult(m types.FluxMessage, msgs []types.FluxMessage) string { return "" } -func HasTimeGap(msgs []types.FluxMessage, threshold time.Duration) bool { +func HasTimeGap(msgs []types.MuxMessage, threshold time.Duration) bool { lastTextIdx := -1 for i := len(msgs) - 1; i >= 0; i-- { if HasTextContent(msgs[i]) && msgs[i].Role == "assistant" { diff --git a/internal/engine/compact/prompt.go b/internal/engine/compact/prompt.go index 244dd2dee..130c455e6 100644 --- a/internal/engine/compact/prompt.go +++ b/internal/engine/compact/prompt.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) const noToolsPreamble = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools. @@ -121,14 +121,14 @@ func BuildIncrementalCompactPrompt(priorSummary string) string { fmt.Sprintf(incrementalUpdateTemplate, priorSummary) } -// PriorSummaryPrefix is the marker prefix rho prepends to a persisted +// PriorSummaryPrefix is the marker prefix decoder prepends to a persisted // conversation summary message. const PriorSummaryPrefix = "[Conversation summary]" // ExtractPriorSummary extracts the previously generated summary text from the // first message of a conversation if one was persisted by an earlier // compaction. It returns "" when no prior summary is present. -func ExtractPriorSummary(msgs []types.FluxMessage) string { +func ExtractPriorSummary(msgs []types.MuxMessage) string { for _, m := range msgs { if m.Role != "user" { continue diff --git a/internal/engine/compact/session_memory.go b/internal/engine/compact/session_memory.go index 0ac6aa6b8..f8c66e238 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" - "github.com/GrayCodeAI/rho/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/storage" ) type SessionMemoryConfig struct { @@ -25,7 +25,7 @@ func DefaultSessionMemoryConfig() SessionMemoryConfig { } } -func CalculateMessagesToKeepIndex(msgs []types.FluxMessage, cfg SessionMemoryConfig) int { +func CalculateMessagesToKeepIndex(msgs []types.MuxMessage, cfg SessionMemoryConfig) int { if len(msgs) == 0 { return 0 } @@ -55,8 +55,8 @@ func CalculateMessagesToKeepIndex(msgs []types.FluxMessage, cfg SessionMemoryCon return idx } -func FilterCompactBoundaries(msgs []types.FluxMessage) []types.FluxMessage { - result := make([]types.FluxMessage, 0, len(msgs)) +func FilterCompactBoundaries(msgs []types.MuxMessage) []types.MuxMessage { + result := make([]types.MuxMessage, 0, len(msgs)) for _, m := range msgs { if IsCompactBoundary(m) { continue @@ -66,7 +66,7 @@ func FilterCompactBoundaries(msgs []types.FluxMessage) []types.FluxMessage { return result } -func IsCompactBoundary(m types.FluxMessage) bool { +func IsCompactBoundary(m types.MuxMessage) bool { if m.Role != "user" { return false } diff --git a/internal/engine/compact/split_test.go b/internal/engine/compact/split_test.go index 21543392d..76b777e8b 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // --------------------------------------------------------------------------- @@ -39,28 +39,28 @@ func TestIsCompactableTool_Empty(t *testing.T) { // --------------------------------------------------------------------------- func TestHasTextContent_WithContent(t *testing.T) { - msg := types.FluxMessage{Role: "assistant", Content: "Hello world"} + msg := types.MuxMessage{Role: "assistant", Content: "Hello world"} if !HasTextContent(msg) { t.Error("expected true for message with content") } } func TestHasTextContent_EmptyContent(t *testing.T) { - msg := types.FluxMessage{Role: "assistant", Content: ""} + msg := types.MuxMessage{Role: "assistant", Content: ""} if HasTextContent(msg) { t.Error("expected false for empty content") } } func TestHasTextContent_WhitespaceOnly(t *testing.T) { - msg := types.FluxMessage{Role: "assistant", Content: " \n\t "} + msg := types.MuxMessage{Role: "assistant", Content: " \n\t "} if HasTextContent(msg) { t.Error("expected false for whitespace-only content") } } func TestHasTextContent_ToolResultIgnored(t *testing.T) { - msg := types.FluxMessage{ + msg := types.MuxMessage{ Role: "user", Content: "has content", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: "output"}}, @@ -71,7 +71,7 @@ func TestHasTextContent_ToolResultIgnored(t *testing.T) { } func TestHasTextContent_WithToolUse(t *testing.T) { - msg := types.FluxMessage{ + msg := types.MuxMessage{ Role: "assistant", Content: "Using tool", ToolUse: []types.ToolCall{{ID: "t1", Name: "Bash"}}, @@ -134,7 +134,7 @@ func TestCompactResult_Fields(t *testing.T) { // --------------------------------------------------------------------------- func TestAdjustIndex_StartIndexZero(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "assistant", ToolUse: []types.ToolCall{{ID: "t1", Name: "Bash"}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: "ok"}}}, } @@ -145,7 +145,7 @@ func TestAdjustIndex_StartIndexZero(t *testing.T) { } func TestAdjustIndex_StartBeyondRange(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hello"}, } got := AdjustIndexToPreserveAPIInvariants(msgs, 100) @@ -155,7 +155,7 @@ func TestAdjustIndex_StartBeyondRange(t *testing.T) { } func TestAdjustIndex_UnresolvedToolPair(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "do stuff"}, {Role: "assistant", ToolUse: []types.ToolCall{{ID: "t1", Name: "Bash"}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: "ok"}}}, diff --git a/internal/engine/compact/strategy.go b/internal/engine/compact/strategy.go index c065e25f9..fc4850e38 100644 --- a/internal/engine/compact/strategy.go +++ b/internal/engine/compact/strategy.go @@ -4,11 +4,11 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) type CompactResult struct { - Messages []types.FluxMessage + Messages []types.MuxMessage Summary string TokensBefore int TokensAfter int @@ -52,7 +52,7 @@ func IsCompactableTool(name string) bool { return compactableTools[name] } -func AdjustIndexToPreserveAPIInvariants(msgs []types.FluxMessage, startIdx int) int { +func AdjustIndexToPreserveAPIInvariants(msgs []types.MuxMessage, startIdx int) int { if startIdx <= 0 { return 0 } @@ -87,7 +87,7 @@ func AdjustIndexToPreserveAPIInvariants(msgs []types.FluxMessage, startIdx int) return idx } -func HasTextContent(m types.FluxMessage) bool { +func HasTextContent(m types.MuxMessage) bool { if len(m.ToolResults) > 0 { return false } diff --git a/internal/engine/compact/strategy_test.go b/internal/engine/compact/strategy_test.go index 733c08802..ed9de0030 100644 --- a/internal/engine/compact/strategy_test.go +++ b/internal/engine/compact/strategy_test.go @@ -4,13 +4,13 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" ) func TestCompactEstimateTokens(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "Hello world"}, {Role: "assistant", Content: strings.Repeat("x", 400)}, } @@ -18,7 +18,7 @@ func TestCompactEstimateTokens(t *testing.T) { if tokens < 1 { t.Errorf("expected at least 1 token, got %d", tokens) } - shortMsgs := []types.FluxMessage{ + shortMsgs := []types.MuxMessage{ {Role: "user", Content: "hi"}, } shortTokens := token.EstimateTokens(shortMsgs) @@ -30,7 +30,7 @@ func TestCompactEstimateTokens(t *testing.T) { func TestAdjustIndexToPreserveAPIInvariants(t *testing.T) { tests := []struct { name string - msgs []types.FluxMessage + msgs []types.MuxMessage startIdx int wantIdx int }{ @@ -42,7 +42,7 @@ func TestAdjustIndexToPreserveAPIInvariants(t *testing.T) { }, { name: "no tool pairs", - msgs: []types.FluxMessage{ + msgs: []types.MuxMessage{ {Role: "user", Content: "hello"}, {Role: "assistant", Content: "hi"}, {Role: "user", Content: "bye"}, @@ -52,7 +52,7 @@ func TestAdjustIndexToPreserveAPIInvariants(t *testing.T) { }, { name: "tool_result at startIdx - moves back past tool_use", - msgs: []types.FluxMessage{ + msgs: []types.MuxMessage{ {Role: "user", Content: "hello"}, {Role: "assistant", Content: "", ToolUse: []types.ToolCall{{ID: "t1", Name: "Bash"}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: "output"}}}, @@ -63,7 +63,7 @@ func TestAdjustIndexToPreserveAPIInvariants(t *testing.T) { }, { name: "at boundary already", - msgs: []types.FluxMessage{ + msgs: []types.MuxMessage{ {Role: "user", Content: "hello"}, {Role: "assistant", Content: "response"}, }, @@ -83,7 +83,7 @@ func TestAdjustIndexToPreserveAPIInvariants(t *testing.T) { } func TestMicrocompactMessages(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "read file.go"}, {Role: "assistant", ToolUse: []types.ToolCall{{ID: "t1", Name: "Read"}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: "package main\nfunc main() {}"}}}, @@ -132,7 +132,7 @@ func TestMicrocompactMessages(t *testing.T) { } func TestAPICompactMessages(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hello"}, {Role: "assistant", ToolUse: []types.ToolCall{{ID: "t1", Name: "Bash", Arguments: map[string]interface{}{"command": strings.Repeat("x", 1000)}}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: strings.Repeat("output ", 1000)}}}, @@ -158,7 +158,7 @@ func TestAPICompactMessages(t *testing.T) { } func TestAPICompactPreservesMutatingTools(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "edit file"}, {Role: "assistant", ToolUse: []types.ToolCall{{ID: "t1", Name: "Edit", Arguments: map[string]interface{}{"old_string": strings.Repeat("x", 1000), "new_string": "y"}}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: strings.Repeat("edited ", 500)}}}, @@ -180,7 +180,7 @@ func TestAPICompactPreservesMutatingTools(t *testing.T) { } func TestCalculateMessagesToKeepIndex(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: strings.Repeat("hello ", 100)}, {Role: "assistant", Content: strings.Repeat("response ", 100)}, {Role: "user", Content: strings.Repeat("follow up ", 100)}, @@ -205,7 +205,7 @@ func TestCalculateMessagesToKeepIndex(t *testing.T) { } func TestFilterCompactBoundaries(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "[Session memory summary]\nold stuff"}, {Role: "assistant", Content: "Understood."}, {Role: "user", Content: "real message"}, diff --git a/internal/engine/compact/transcript_segments.go b/internal/engine/compact/transcript_segments.go index d40493082..ad72cf8ac 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/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/types" ) // Compaction transcript segments, adopted from grok-build's @@ -80,7 +80,7 @@ func IndexPath(sessionID string) string { // RenderSegmentToMarkdown renders messages into a self-contained markdown // segment. Pure: no I/O. -func RenderSegmentToMarkdown(msgs []types.FluxMessage, segIndex int, detail CompactionDetail) string { +func RenderSegmentToMarkdown(msgs []types.MuxMessage, segIndex int, detail CompactionDetail) string { var b strings.Builder fmt.Fprintf(&b, "# Compaction segment %d\n\n", segIndex) fmt.Fprintf(&b, "Recorded: %s\n", time.Now().UTC().Format(time.RFC3339)) @@ -112,7 +112,7 @@ func RenderSegmentToMarkdown(msgs []types.FluxMessage, segIndex int, detail Comp return b.String() } -func renderTurn(m types.FluxMessage, detail CompactionDetail) string { +func renderTurn(m types.MuxMessage, detail CompactionDetail) string { var b strings.Builder role := strings.ToUpper(m.Role) fmt.Fprintf(&b, "## %s\n", role) @@ -212,7 +212,7 @@ func NextSegmentIndex(sessionID string) int { // WriteCompactionSegment persists msgs as the next segment and appends an // INDEX.md row. It returns the written segment path. Callers should treat // errors as non-fatal: segment persistence must never block compaction. -func WriteCompactionSegment(sessionID string, msgs []types.FluxMessage, detail CompactionDetail) (string, error) { +func WriteCompactionSegment(sessionID string, msgs []types.MuxMessage, detail CompactionDetail) (string, error) { if sessionID == "" { return "", fmt.Errorf("compact: empty session id") } diff --git a/internal/engine/compact/transcript_segments_test.go b/internal/engine/compact/transcript_segments_test.go index da543558d..b8990584b 100644 --- a/internal/engine/compact/transcript_segments_test.go +++ b/internal/engine/compact/transcript_segments_test.go @@ -6,11 +6,11 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) -func segTestMessages() []types.FluxMessage { - return []types.FluxMessage{ +func segTestMessages() []types.MuxMessage { + return []types.MuxMessage{ {Role: "user", Content: "fix the flaky test in pkg/foo"}, { Role: "assistant", Content: "Looking into it.", @@ -74,7 +74,7 @@ func TestParseCompactionDetail(t *testing.T) { func TestWriteCompactionSegmentAndIndex(t *testing.T) { stateDir := t.TempDir() - t.Setenv("RHO_STATE_DIR", stateDir) + t.Setenv("DECODER_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 bea8551f8..75a6a9612 100644 --- a/internal/engine/compact_auto.go +++ b/internal/engine/compact_auto.go @@ -6,12 +6,12 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/circuitbreaker" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/circuitbreaker" + "github.com/GrayCodeAI/decoder/internal/types" ) // AutoCompactor orchestrates compaction with circuit breaker protection. @@ -177,7 +177,7 @@ type SmartCompactStrategy struct{} func (s *SmartCompactStrategy) Name() string { return "smart" } -func (s *SmartCompactStrategy) ShouldTrigger(msgs []types.FluxMessage, tokenCount, threshold int) bool { +func (s *SmartCompactStrategy) ShouldTrigger(msgs []types.MuxMessage, tokenCount, threshold int) bool { return tokenCount >= threshold && len(msgs) > 20 } @@ -200,7 +200,7 @@ type TruncateStrategy struct{} func (s *TruncateStrategy) Name() string { return "truncate" } -func (s *TruncateStrategy) ShouldTrigger(_ []types.FluxMessage, tokenCount, threshold int) bool { +func (s *TruncateStrategy) ShouldTrigger(_ []types.MuxMessage, tokenCount, threshold int) bool { return tokenCount >= threshold } diff --git a/internal/engine/compact_clear.go b/internal/engine/compact_clear.go index 1fa0f3668..3704858ee 100644 --- a/internal/engine/compact_clear.go +++ b/internal/engine/compact_clear.go @@ -4,9 +4,9 @@ import ( "context" "sort" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // Two-tier context management (adopted from herm): before compacting, "clear" @@ -30,7 +30,7 @@ const ( // recent clearKeepRecent tool-result messages intact, until estimatedTokens // drops below threshold. It returns the new messages and the estimated tokens // freed. Adopted from herm's clearOldToolResults. -func clearOldToolResults(msgs []types.FluxMessage, estimatedTokens, threshold int) ([]types.FluxMessage, int) { +func clearOldToolResults(msgs []types.MuxMessage, estimatedTokens, threshold int) ([]types.MuxMessage, int) { if threshold <= 0 || estimatedTokens <= 0 || len(msgs) == 0 { return msgs, 0 } @@ -96,7 +96,7 @@ func (s *Session) ClearOldToolResults(ctx context.Context) bool { } // toolResultBytes returns the total byte size of a message's tool results. -func toolResultBytes(m types.FluxMessage) int { +func toolResultBytes(m types.MuxMessage) int { total := 0 for _, tr := range m.ToolResults { total += len(tr.Content) @@ -106,7 +106,7 @@ func toolResultBytes(m types.FluxMessage) int { // clearMessageToolResults replaces each non-empty tool-result content with the // placeholder and returns the estimated tokens freed (~4 bytes per token). -func clearMessageToolResults(m *types.FluxMessage) int { +func clearMessageToolResults(m *types.MuxMessage) int { freedBytes := 0 for i := range m.ToolResults { c := m.ToolResults[i].Content @@ -121,8 +121,8 @@ func clearMessageToolResults(m *types.FluxMessage) int { // cloneToolResults copies msgs, deep-copying the ToolResults slices so callers // can mutate the copy without touching the persisted originals. -func cloneToolResults(msgs []types.FluxMessage) []types.FluxMessage { - out := make([]types.FluxMessage, len(msgs)) +func cloneToolResults(msgs []types.MuxMessage) []types.MuxMessage { + out := make([]types.MuxMessage, len(msgs)) for i, m := range msgs { out[i] = m if m.ToolResults != nil { diff --git a/internal/engine/compact_clear_test.go b/internal/engine/compact_clear_test.go index c4879a56d..f29f34065 100644 --- a/internal/engine/compact_clear_test.go +++ b/internal/engine/compact_clear_test.go @@ -3,15 +3,15 @@ package engine import ( "testing" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) -func mkMsg(role string, trs []types.ToolResult) types.FluxMessage { - return types.FluxMessage{Role: role, ToolResults: trs} +func mkMsg(role string, trs []types.ToolResult) types.MuxMessage { + return types.MuxMessage{Role: role, ToolResults: trs} } func TestClearOldToolResultsNoOpWhenBelowThreshold(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ mkMsg("user", []types.ToolResult{{Content: "aaaa"}}), } out, freed := clearOldToolResults(msgs, 100, 1000) @@ -22,7 +22,7 @@ func TestClearOldToolResultsNoOpWhenBelowThreshold(t *testing.T) { func TestClearOldToolResultsClearsBiggestFirst(t *testing.T) { // 6 tool-result messages; threshold low so clearing triggers; keep last 4. - msgs := make([]types.FluxMessage, 6) + msgs := make([]types.MuxMessage, 6) for i := range msgs { msgs[i] = mkMsg("user", []types.ToolResult{{Content: "12345678"}}) // 8 bytes } @@ -46,7 +46,7 @@ func TestClearOldToolResultsClearsBiggestFirst(t *testing.T) { func TestClearOldToolResultsStopsWhenUnderThreshold(t *testing.T) { // Huge contents, small threshold: it clears until under threshold, then stops. - msgs := make([]types.FluxMessage, 6) + msgs := make([]types.MuxMessage, 6) for i := range msgs { msgs[i] = mkMsg("user", []types.ToolResult{{Content: "12345678"}}) } @@ -63,7 +63,7 @@ func TestClearOldToolResultsStopsWhenUnderThreshold(t *testing.T) { } func TestClearOldToolResultsSkipsAlreadyCleared(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ mkMsg("user", []types.ToolResult{{Content: outputClearedPlaceholder}}), mkMsg("user", []types.ToolResult{{Content: outputClearedPlaceholder}}), mkMsg("user", []types.ToolResult{{Content: outputClearedPlaceholder}}), diff --git a/internal/engine/compact_micro_engine.go b/internal/engine/compact_micro_engine.go index 7bf6d3f2c..22a756013 100644 --- a/internal/engine/compact_micro_engine.go +++ b/internal/engine/compact_micro_engine.go @@ -4,18 +4,18 @@ import ( "context" "time" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" ) type MicroCompactStrategy struct{} func (s *MicroCompactStrategy) Name() string { return "micro" } -func (s *MicroCompactStrategy) ShouldTrigger(msgs []types.FluxMessage, tokenCount, threshold int) bool { +func (s *MicroCompactStrategy) ShouldTrigger(msgs []types.MuxMessage, tokenCount, threshold int) bool { if tokenCount < threshold/2 { return false } diff --git a/internal/engine/compact_provider_native.go b/internal/engine/compact_provider_native.go index 830f23523..cc303982c 100644 --- a/internal/engine/compact_provider_native.go +++ b/internal/engine/compact_provider_native.go @@ -4,22 +4,22 @@ import ( "context" "fmt" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/flux/llm" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" + "github.com/GrayCodeAI/mux/llm" ) // ProviderNativeCompactStrategy delegates provider-specific compaction to -// Flux. Rho owns when conversation state is compacted and how the resulting -// summary is inserted; Flux owns credentials and provider transport details. +// Mux. Decoder owns when conversation state is compacted and how the resulting +// summary is inserted; Mux owns credentials and provider transport details. type ProviderNativeCompactStrategy struct{} func (s *ProviderNativeCompactStrategy) Name() string { return "provider_native" } -func (s *ProviderNativeCompactStrategy) ShouldTrigger(msgs []types.FluxMessage, tokenCount, threshold int) bool { +func (s *ProviderNativeCompactStrategy) ShouldTrigger(msgs []types.MuxMessage, tokenCount, threshold int) bool { return tokenCount >= threshold && len(msgs) >= 8 } @@ -53,8 +53,8 @@ func (s *ProviderNativeCompactStrategy) Compact(ctx context.Context, sess *Sessi if keepEnd > len(messagesBefore) { keepEnd = len(messagesBefore) } - tail := append([]types.FluxMessage(nil), messagesBefore[len(messagesBefore)-keepEnd:]...) - messages := append([]types.FluxMessage{{Role: "user", Content: compact.FormatCompactSummary(summary)}}, tail...) + tail := append([]types.MuxMessage(nil), messagesBefore[len(messagesBefore)-keepEnd:]...) + messages := append([]types.MuxMessage{{Role: "user", Content: compact.FormatCompactSummary(summary)}}, tail...) compact := &compact.CompactResult{ Messages: messages, TokensBefore: tokensBefore, diff --git a/internal/engine/compact_regression_test.go b/internal/engine/compact_regression_test.go index 50f17cc2a..2d1bc586c 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // Regression tests for compaction fixes. Each test pins a specific bug so it @@ -24,18 +24,18 @@ func testSession(t *testing.T, window int) *Session { // toolPairMessages returns a transcript where an assistant tool_use is followed // by a user tool_result, embedded among filler messages. -func toolPairMessages(total int, toolUseAt int) []types.FluxMessage { - msgs := make([]types.FluxMessage, total) +func toolPairMessages(total int, toolUseAt int) []types.MuxMessage { + msgs := make([]types.MuxMessage, total) for i := range msgs { - msgs[i] = types.FluxMessage{Role: "user", Content: "filler"} + msgs[i] = types.MuxMessage{Role: "user", Content: "filler"} } - msgs[toolUseAt] = types.FluxMessage{ + msgs[toolUseAt] = types.MuxMessage{ Role: "assistant", Content: "calling tool", ToolUse: []types.ToolCall{{ID: "tu-1", Name: "read_file", Arguments: map[string]any{"path": "a.go"}}}, } if toolUseAt+1 < total { - msgs[toolUseAt+1] = types.FluxMessage{ + msgs[toolUseAt+1] = types.MuxMessage{ Role: "user", Content: "tool responded", ToolResults: []types.ToolResult{ @@ -100,8 +100,8 @@ func TestSmartCompactAvoidsAdjacentAssistantRoles(t *testing.T) { // Build a long transcript whose kept tail starts with an assistant message // (conversation ended on a final assistant turn). msgs := makeMessages(40) - msgs[30] = types.FluxMessage{Role: "assistant", Content: "tail starts with assistant"} - msgs[31] = types.FluxMessage{Role: "user", Content: "next user turn"} + msgs[30] = types.MuxMessage{Role: "assistant", Content: "tail starts with assistant"} + msgs[31] = types.MuxMessage{Role: "user", Content: "next user turn"} s := testSession(t, 128000) s.Persistence().SetRawMessages(msgs) @@ -121,7 +121,7 @@ func TestSplitTurnCompactTruncatesOversizedMessageToSecondHalf(t *testing.T) { // and the retained message must contain exactly its second half. msgs := makeMessages(40) big := strings.Repeat("x", 20000) - msgs[35] = types.FluxMessage{Role: "user", Content: big} + msgs[35] = types.MuxMessage{Role: "user", Content: big} s := testSession(t, 128000) s.Persistence().SetRawMessages(msgs) @@ -156,11 +156,11 @@ func TestApplyCompactionPreservesConcurrentAppends(t *testing.T) { raw := s.Persistence().RawMessages() // snapshot (len 30) // Concurrent AddUser between snapshot and apply: concurrent := s.Persistence().RawMessages() - concurrent = append(concurrent, types.FluxMessage{Role: "user", Content: "concurrent user turn"}) + concurrent = append(concurrent, types.MuxMessage{Role: "user", Content: "concurrent user turn"}) s.Persistence().SetRawMessages(concurrent) // Compaction result computed from the stale snapshot (keeps only 20): - keep := append([]types.FluxMessage(nil), raw[:20]...) + keep := append([]types.MuxMessage(nil), raw[:20]...) s.Persistence().ApplyCompaction(keep, len(raw)) kept := s.Persistence().RawMessages() @@ -176,12 +176,12 @@ func TestShouldAutoCompactCountsToolTokens(t *testing.T) { // Content alone stays under the threshold; adding tool_result payloads // crosses it. ShouldAutoCompact must count the tool tokens. s := testSession(t, 2000) // threshold = 85% of 2000 = 1700 - msgs := make([]types.FluxMessage, 40) + msgs := make([]types.MuxMessage, 40) for i := range msgs { if i%2 == 0 { - msgs[i] = types.FluxMessage{Role: "user", Content: "short"} + msgs[i] = types.MuxMessage{Role: "user", Content: "short"} } else { - msgs[i] = types.FluxMessage{Role: "assistant", Content: "short reply"} + msgs[i] = types.MuxMessage{Role: "assistant", Content: "short reply"} } } s.Persistence().SetRawMessages(msgs) @@ -204,9 +204,9 @@ func TestShouldAutoCompactCountsToolTokens(t *testing.T) { func TestGenerateSummaryInputCapped(t *testing.T) { // generateSummary must not feed an unbounded prompt even if the transcript // is huge (summaryInputRuneCap applies). - msgs := make([]types.FluxMessage, 0, 500) + msgs := make([]types.MuxMessage, 0, 500) for i := 0; i < 500; i++ { - msgs = append(msgs, types.FluxMessage{Role: "user", Content: strings.Repeat("content ", 100)}) + msgs = append(msgs, types.MuxMessage{Role: "user", Content: strings.Repeat("content ", 100)}) } s := testSession(t, 128000) summary := s.generateSummary(context.Background(), msgs) diff --git a/internal/engine/compact_relevance.go b/internal/engine/compact_relevance.go index 3bba9155e..57b4df492 100644 --- a/internal/engine/compact_relevance.go +++ b/internal/engine/compact_relevance.go @@ -4,12 +4,12 @@ import ( "context" "time" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/relevanceprune" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/relevanceprune" + "github.com/GrayCodeAI/decoder/internal/types" ) // RelevancePruneStrategy prunes context by relevance: it scores older messages @@ -27,7 +27,7 @@ type RelevancePruneStrategy struct { func (s *RelevancePruneStrategy) Name() string { return "relevance" } -func (s *RelevancePruneStrategy) ShouldTrigger(msgs []types.FluxMessage, tokenCount, threshold int) bool { +func (s *RelevancePruneStrategy) ShouldTrigger(msgs []types.MuxMessage, tokenCount, threshold int) bool { return tokenCount >= threshold && len(msgs) >= 20 } @@ -47,7 +47,7 @@ func (s *RelevancePruneStrategy) Compact(ctx context.Context, sess *Session) (*c }, ) - out := toFluxMessages(pruned) + out := toMuxMessages(pruned) sess.Persistence().SetMessages(out) tokensAfter := token.EstimateTokens(sess.Persistence().RawMessages()) return &compact.CompactResult{ @@ -59,7 +59,7 @@ func (s *RelevancePruneStrategy) Compact(ctx context.Context, sess *Session) (*c } // toPruneMessages adapts raw session messages to the relevance-prune shape. -func toPruneMessages(raw []types.FluxMessage, now time.Time) []relevanceprune.Message { +func toPruneMessages(raw []types.MuxMessage, now time.Time) []relevanceprune.Message { out := make([]relevanceprune.Message, len(raw)) for i, m := range raw { out[i] = relevanceprune.Message{ @@ -73,15 +73,15 @@ func toPruneMessages(raw []types.FluxMessage, now time.Time) []relevanceprune.Me return out } -func toFluxMessages(msgs []relevanceprune.Message) []types.FluxMessage { - out := make([]types.FluxMessage, len(msgs)) +func toMuxMessages(msgs []relevanceprune.Message) []types.MuxMessage { + out := make([]types.MuxMessage, len(msgs)) for i, m := range msgs { - out[i] = types.FluxMessage{Role: m.Role, Content: m.Content} + out[i] = types.MuxMessage{Role: m.Role, Content: m.Content} } return out } -func hasErrorResult(m types.FluxMessage) bool { +func hasErrorResult(m types.MuxMessage) bool { for _, tr := range m.ToolResults { if tr.IsError { return true @@ -90,7 +90,7 @@ func hasErrorResult(m types.FluxMessage) bool { return false } -func lastUserText(raw []types.FluxMessage) string { +func lastUserText(raw []types.MuxMessage) string { for i := len(raw) - 1; i >= 0; i-- { if raw[i].Role == "user" { return raw[i].Content diff --git a/internal/engine/compact_relevance_test.go b/internal/engine/compact_relevance_test.go index 313d1fb97..0b238b311 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestRelevancePruneStrategy_ShouldTrigger(t *testing.T) { @@ -21,11 +21,11 @@ func TestRelevancePruneStrategy_ShouldTrigger(t *testing.T) { func TestRelevancePruneStrategy_Compact(t *testing.T) { sess := NewSessionWithClient(NewMockClientForTest(), "test", "test-model", "", nil, false) // A long transcript; last user message is the task context. - var msgs []types.FluxMessage + var msgs []types.MuxMessage for i := 0; i < 60; i++ { - msgs = append(msgs, types.FluxMessage{Role: "user", Content: "unrelated filler content that has no shared keywords"}) + msgs = append(msgs, types.MuxMessage{Role: "user", Content: "unrelated filler content that has no shared keywords"}) } - msgs = append(msgs, types.FluxMessage{Role: "user", Content: "refactor the payment module and update all tests"}) + msgs = append(msgs, types.MuxMessage{Role: "user", Content: "refactor the payment module and update all tests"}) sess.Persistence().SetRawMessages(msgs) s := &RelevancePruneStrategy{TargetTokens: 200} @@ -54,7 +54,7 @@ func TestRelevancePruneStrategy_Compact(t *testing.T) { } func TestToPruneMessagesAdapter(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hi"}, {Role: "assistant", Content: "ok", ToolUse: []types.ToolCall{{Name: "Bash"}}}, {Role: "user", Content: "err", ToolResults: []types.ToolResult{{IsError: true}}}, diff --git a/internal/engine/compact_session_memory_engine.go b/internal/engine/compact_session_memory_engine.go index 1f5b338dd..2bc5709c9 100644 --- a/internal/engine/compact_session_memory_engine.go +++ b/internal/engine/compact_session_memory_engine.go @@ -6,18 +6,18 @@ import ( "os" "strings" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" ) type SessionMemoryStrategy struct{} func (s *SessionMemoryStrategy) Name() string { return "session_memory" } -func (s *SessionMemoryStrategy) ShouldTrigger(msgs []types.FluxMessage, tokenCount, threshold int) bool { +func (s *SessionMemoryStrategy) ShouldTrigger(msgs []types.MuxMessage, tokenCount, threshold int) bool { if tokenCount < threshold { return false } @@ -52,12 +52,12 @@ func (s *SessionMemoryStrategy) Compact(ctx context.Context, sess *Session) (*co kept := messages[keepIdx:] kept = compact.FilterCompactBoundaries(kept) - result := make([]types.FluxMessage, 0, len(kept)+2) - result = append(result, types.FluxMessage{ + result := make([]types.MuxMessage, 0, len(kept)+2) + result = append(result, types.MuxMessage{ Role: "user", Content: "[Session memory summary]\n" + memContent + "\n\n[Continue from the recent messages below.]", }) - result = append(result, types.FluxMessage{ + result = append(result, types.MuxMessage{ Role: "assistant", Content: "Understood. I have the context from the session memory above. Continuing with the recent conversation.", }) diff --git a/internal/engine/compact_split.go b/internal/engine/compact_split.go index 0c811edb4..28c9bb41b 100644 --- a/internal/engine/compact_split.go +++ b/internal/engine/compact_split.go @@ -5,11 +5,11 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // splitTurnCompact handles the edge case where a single turn's messages @@ -25,7 +25,7 @@ import ( // average token count of the tail, with a 2000-token floor to avoid false // positives. A single message exceeding this budget is treated as an // oversized turn. -func turnTokenBudget(tail []types.FluxMessage) int { +func turnTokenBudget(tail []types.MuxMessage) int { if len(tail) == 0 { return 0 } @@ -135,7 +135,7 @@ func (s *Session) splitTurnCompact(ctx context.Context) { // Reconstruct messages: summary + tail from oversized turn onward // Keep the second half of the oversized message + everything after // (its first half is covered by phase2Summary above). - remaining := append([]types.FluxMessage(nil), raw[splitPoint:]...) + remaining := append([]types.MuxMessage(nil), raw[splitPoint:]...) if len(remaining) > 0 { first := remaining[0] first.Content = secondHalfRunes(first.Content) @@ -145,8 +145,8 @@ func (s *Session) splitTurnCompact(ctx context.Context) { remaining[0] = first } - keep := make([]types.FluxMessage, 0, len(remaining)+2) - keep = append(keep, types.FluxMessage{ + keep := make([]types.MuxMessage, 0, len(remaining)+2) + keep = append(keep, types.MuxMessage{ Role: "user", Content: combined.String() + "\n\n[Continue from the recent messages below.]", }) @@ -154,7 +154,7 @@ func (s *Session) splitTurnCompact(ctx context.Context) { // message; adjacent assistant messages are rejected by OpenAI-compat // providers. if len(remaining) == 0 || remaining[0].Role != "assistant" { - keep = append(keep, types.FluxMessage{ + keep = append(keep, types.MuxMessage{ Role: "assistant", Content: "Understood. I have the context from the summary above. Continuing.", }) @@ -174,12 +174,12 @@ func secondHalfRunes(s string) string { } // generatePartialSummary generates an LLM summary for a subset of messages. -func (s *Session) generatePartialSummary(ctx context.Context, messages []types.FluxMessage) string { +func (s *Session) generatePartialSummary(ctx context.Context, messages []types.MuxMessage) string { if len(messages) == 0 { return "" } - var summaryMsgs []types.FluxMessage + var summaryMsgs []types.MuxMessage compactPrompt := compact.BuildCompactPrompt(compact.CompactPartial) var content strings.Builder content.WriteString(compactPrompt) @@ -195,7 +195,7 @@ func (s *Session) generatePartialSummary(ctx context.Context, messages []types.F } } - summaryMsgs = append(summaryMsgs, types.FluxMessage{ + summaryMsgs = append(summaryMsgs, types.MuxMessage{ Role: "user", Content: content.String(), }) @@ -219,7 +219,7 @@ func (s *Session) generatePartialSummary(ctx context.Context, messages []types.F } // summarizeOversizedTurn summarizes the content of a single oversized message. -func (s *Session) summarizeOversizedTurn(ctx context.Context, msg types.FluxMessage) string { +func (s *Session) summarizeOversizedTurn(ctx context.Context, msg types.MuxMessage) string { content := msg.Content if content == "" { // If it's a tool result, use that @@ -238,8 +238,8 @@ func (s *Session) summarizeOversizedTurn(ctx context.Context, msg types.FluxMess halfLen = 4000 } - var summaryMsgs []types.FluxMessage - summaryMsgs = append(summaryMsgs, types.FluxMessage{ + var summaryMsgs []types.MuxMessage + summaryMsgs = append(summaryMsgs, types.MuxMessage{ Role: "user", Content: compact.BuildCompactPrompt(compact.CompactUpTo) + "\n\nSummarize the key information from this content prefix (the rest will be retained verbatim):\n\n" + msg.Role + ": " + truncateRunes(content, halfLen), diff --git a/internal/engine/compact_strategy_engine.go b/internal/engine/compact_strategy_engine.go index 957bc4fad..35938282e 100644 --- a/internal/engine/compact_strategy_engine.go +++ b/internal/engine/compact_strategy_engine.go @@ -3,14 +3,14 @@ package engine import ( "context" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) type CompactStrategy interface { Name() string - ShouldTrigger(msgs []types.FluxMessage, tokenCount, threshold int) bool + ShouldTrigger(msgs []types.MuxMessage, tokenCount, threshold int) bool Compact(ctx context.Context, s *Session) (*compact.CompactResult, error) } @@ -33,7 +33,7 @@ func NewStrategyRegistry(config compact.CompactConfig) *StrategyRegistry { return r } -func (r *StrategyRegistry) SelectStrategy(sess *Session, msgs []types.FluxMessage, tokenCount int) CompactStrategy { +func (r *StrategyRegistry) SelectStrategy(sess *Session, msgs []types.MuxMessage, tokenCount int) CompactStrategy { threshold := r.config.ContextWindowSize - r.config.AutoCompactBuffer - r.config.MaxOutputTokens for _, s := range r.strategies { if _, ok := s.(*ProviderNativeCompactStrategy); ok && (sess == nil || !sess.supportsNativeCompaction()) { diff --git a/internal/engine/compact_strategy_test.go b/internal/engine/compact_strategy_test.go index 84e3467d4..78575b005 100644 --- a/internal/engine/compact_strategy_test.go +++ b/internal/engine/compact_strategy_test.go @@ -6,11 +6,11 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestSessionMemoryStrategy_ShouldTrigger(t *testing.T) { @@ -104,14 +104,14 @@ func TestTurnTokenBudget(t *testing.T) { t.Errorf("turnTokenBudget(nil) = %d, want 0", got) } - small := []types.FluxMessage{{Role: "user", Content: "short"}} + small := []types.MuxMessage{{Role: "user", Content: "short"}} if got := turnTokenBudget(small); got != 2000 { t.Errorf("turnTokenBudget(small) = %d, want minimum floor 2000", got) } - big := make([]types.FluxMessage, 10) + big := make([]types.MuxMessage, 10) for i := range big { - big[i] = types.FluxMessage{Role: "user", Content: strings.Repeat("x", 10000)} + big[i] = types.MuxMessage{Role: "user", Content: strings.Repeat("x", 10000)} } avg := token.EstimateMessageTokens(big[0]) if avg*3 <= 2000 { @@ -122,13 +122,13 @@ func TestTurnTokenBudget(t *testing.T) { } } -func makeMessages(n int) []types.FluxMessage { - msgs := make([]types.FluxMessage, n) +func makeMessages(n int) []types.MuxMessage { + msgs := make([]types.MuxMessage, n) for i := range msgs { if i%2 == 0 { - msgs[i] = types.FluxMessage{Role: "user", Content: strings.Repeat("message ", 50)} + msgs[i] = types.MuxMessage{Role: "user", Content: strings.Repeat("message ", 50)} } else { - msgs[i] = types.FluxMessage{Role: "assistant", Content: strings.Repeat("response ", 50)} + msgs[i] = types.MuxMessage{Role: "assistant", Content: strings.Repeat("response ", 50)} } } return msgs diff --git a/internal/engine/compression/cross_session.go b/internal/engine/compression/cross_session.go index 2c47fdd79..121336388 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/rho/internal/mathutil" - "github.com/GrayCodeAI/rho/internal/safewrite" + "github.com/GrayCodeAI/decoder/internal/mathutil" + "github.com/GrayCodeAI/decoder/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 2a50dd485..c636c1922 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 1a5a09447..22a56f073 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestNewTimeline(t *testing.T) { diff --git a/internal/engine/context_compaction.go b/internal/engine/context_compaction.go index a211664c5..016ccdabc 100644 --- a/internal/engine/context_compaction.go +++ b/internal/engine/context_compaction.go @@ -3,12 +3,12 @@ package engine import ( "path/filepath" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/eventlog" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/storage" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/eventlog" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/storage" + "github.com/GrayCodeAI/decoder/internal/types" ) // CompactionEvent describes a completed context compaction. @@ -22,7 +22,7 @@ type CompactionEvent struct { // OnCompaction is invoked after compaction (TUI, logging, etc.). type OnCompaction func(CompactionEvent) -// PersistID is the on-disk session id under Rho's user state sessions dir. +// PersistID is the on-disk session id under Decoder'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 { @@ -135,7 +135,7 @@ func (s *Session) checkpointManager() *session.CheckpointManager { return p.CheckpointManager() } -func rawToSessionMessages(raw []types.FluxMessage) []session.Message { +func rawToSessionMessages(raw []types.MuxMessage) []session.Message { return session.FromRuntimeMessages(raw) } diff --git a/internal/engine/context_compaction_test.go b/internal/engine/context_compaction_test.go index 53462718b..b4f9092b9 100644 --- a/internal/engine/context_compaction_test.go +++ b/internal/engine/context_compaction_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "github.com/GrayCodeAI/flux/credentials" - fluxengine "github.com/GrayCodeAI/flux/engine" - "github.com/GrayCodeAI/rho/internal/provider/gateway" + "github.com/GrayCodeAI/decoder/internal/provider/gateway" + "github.com/GrayCodeAI/mux/credentials" + muxengine "github.com/GrayCodeAI/mux/engine" ) func TestContextUsedTokens_PrefersAPI(t *testing.T) { @@ -21,21 +21,21 @@ func TestContextUsedTokens_PrefersAPI(t *testing.T) { } } -func TestNativeCompactionSupportUsesFluxCredentialStore(t *testing.T) { +func TestNativeCompactionSupportUsesMuxCredentialStore(t *testing.T) { ctx := context.Background() store := &credentials.MapStore{} - runtime, err := fluxengine.New(fluxengine.Options{SecretStore: store}) + runtime, err := muxengine.New(muxengine.Options{SecretStore: store}) if err != nil { t.Fatal(err) } s := NewSessionWithClient(gateway.NewFromEngine(runtime).ChatClient(), "anthropic", "claude-sonnet-4-6", "sys", nil, true) if s.supportsNativeCompaction() { - t.Fatal("expected no support before Flux has a credential") + t.Fatal("expected no support before Mux has a credential") } if err := store.Set(ctx, credentials.AccountForEnv("ANTHROPIC_API_KEY"), "sk-test"); err != nil { t.Fatal(err) } if !s.supportsNativeCompaction() { - t.Fatal("expected support from Flux's injected credential store") + t.Fatal("expected support from Mux's injected credential store") } } diff --git a/internal/engine/context_governor.go b/internal/engine/context_governor.go index a49510b28..82680261d 100644 --- a/internal/engine/context_governor.go +++ b/internal/engine/context_governor.go @@ -4,12 +4,12 @@ import ( "context" "fmt" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/engine/token" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" - "github.com/GrayCodeAI/rho/internal/engine/ctxmgr" - modelPkg "github.com/GrayCodeAI/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/engine/ctxmgr" + modelPkg "github.com/GrayCodeAI/decoder/internal/provider/routing" ) const ( diff --git a/internal/engine/context_governor_test.go b/internal/engine/context_governor_test.go index 293d61929..970ef6652 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/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("RHO_CACHE_DIR", t.TempDir()) + t.Setenv("DECODER_CACHE_DIR", t.TempDir()) in := strings.Repeat("x", toolOutputSpillMinChars+100) got := maybeSpillToolOutput(in, "Bash", "call-1") if !strings.Contains(got, "/scratch/") { @@ -51,7 +51,7 @@ func TestMaybeSpillToolOutput_LargeSpills(t *testing.T) { func TestManageContextBeforeTurn_CollapseOnly(t *testing.T) { s := NewSession("", "test-model", "sys", nil) - s.Persistence().SetRawMessages([]types.FluxMessage{ + s.Persistence().SetRawMessages([]types.MuxMessage{ {Role: "user", ToolResults: []types.ToolResult{{Content: "err", IsError: true}}}, {Role: "user", ToolResults: []types.ToolResult{{Content: "err", IsError: true}}}, }) diff --git a/internal/engine/control/backtrack.go b/internal/engine/control/backtrack.go index f2af7ab10..3f6ce3549 100644 --- a/internal/engine/control/backtrack.go +++ b/internal/engine/control/backtrack.go @@ -4,17 +4,17 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // DecisionPoint captures a point in the conversation where the agent made a // choice that can potentially be rolled back if it leads to failure. type DecisionPoint struct { TurnIndex int - Description string // what was decided - Alternatives []string // other options available - Outcome string // "success", "failure", or "" (pending) - Messages []types.FluxMessage // conversation state at this point + Description string // what was decided + Alternatives []string // other options available + Outcome string // "success", "failure", or "" (pending) + Messages []types.MuxMessage // conversation state at this point } // BacktrackEngine records decision points during agent execution and provides @@ -36,9 +36,9 @@ func NewBacktrackEngine() *BacktrackEngine { // RecordDecision saves a decision point with the current conversation state. // If the number of recorded points exceeds the maximum, the oldest point is // removed. -func (be *BacktrackEngine) RecordDecision(turnIdx int, desc string, alternatives []string, msgs []types.FluxMessage) { +func (be *BacktrackEngine) RecordDecision(turnIdx int, desc string, alternatives []string, msgs []types.MuxMessage) { // Copy messages to avoid mutation from caller - snapshot := make([]types.FluxMessage, len(msgs)) + snapshot := make([]types.MuxMessage, len(msgs)) copy(snapshot, msgs) // Copy alternatives @@ -110,12 +110,12 @@ func (be *BacktrackEngine) GenerateRetryPrompt(dp *DecisionPoint) string { // RestoreState returns the conversation messages captured at the given decision // point, representing the state just before (not including) the failed // decision. This allows the agent to retry from that point. -func (be *BacktrackEngine) RestoreState(dp *DecisionPoint) []types.FluxMessage { +func (be *BacktrackEngine) RestoreState(dp *DecisionPoint) []types.MuxMessage { if dp == nil { return nil } // Return a copy to prevent mutation - restored := make([]types.FluxMessage, len(dp.Messages)) + restored := make([]types.MuxMessage, len(dp.Messages)) copy(restored, dp.Messages) return restored } diff --git a/internal/engine/control/backtrack_test.go b/internal/engine/control/backtrack_test.go index d35fb500e..1c4e9f130 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestNewBacktrackEngine(t *testing.T) { @@ -23,7 +23,7 @@ func TestNewBacktrackEngine(t *testing.T) { func TestBacktrackEngine_RecordAndFind(t *testing.T) { be := NewBacktrackEngine() - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "Fix the bug"}, {Role: "assistant", Content: "I'll try approach A"}, } @@ -61,7 +61,7 @@ func TestBacktrackEngine_RecordAndFind(t *testing.T) { func TestBacktrackEngine_GenerateRetryPrompt(t *testing.T) { be := NewBacktrackEngine() - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "Refactor the function"}, } @@ -94,7 +94,7 @@ func TestBacktrackEngine_GenerateRetryPrompt(t *testing.T) { func TestBacktrackEngine_RestoreState(t *testing.T) { be := NewBacktrackEngine() - originalMsgs := []types.FluxMessage{ + originalMsgs := []types.MuxMessage{ {Role: "user", Content: "Step one"}, {Role: "assistant", Content: "Done with step one"}, {Role: "user", Content: "Step two"}, diff --git a/internal/engine/control/stall_detector.go b/internal/engine/control/stall_detector.go index 8115fdf45..cc1513dc3 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 714918435..690560681 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 ca3a0a1d7..074600361 100644 --- a/internal/engine/control_plane_test.go +++ b/internal/engine/control_plane_test.go @@ -6,10 +6,10 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestWorkModePlanFiltersToolsAndBash(t *testing.T) { @@ -42,19 +42,19 @@ func TestWorkModePlanFiltersToolsAndBash(t *testing.T) { } } -func TestLazyFluxToolsAndPromote(t *testing.T) { +func TestLazyMuxToolsAndPromote(t *testing.T) { reg := tool.NewRegistry(tool.FileReadTool{}, tool.ImpactTool{}) reg.EnableLazyModelSurface([]string{"Read"}) - flux := reg.FluxTools() - if len(flux) != 1 || flux[0].Name != "Read" { - t.Fatalf("FluxTools = %#v, want only Read", flux) + mux := reg.MuxTools() + if len(mux) != 1 || mux[0].Name != "Read" { + t.Fatalf("MuxTools = %#v, want only Read", mux) } if !reg.PromoteModelTool("Impact") { t.Fatal("promote Impact failed") } - flux = reg.FluxTools() - if len(flux) != 2 { - t.Fatalf("after promote FluxTools len = %d", len(flux)) + mux = reg.MuxTools() + if len(mux) != 2 { + t.Fatalf("after promote MuxTools len = %d", len(mux)) } } diff --git a/internal/engine/conversation_adapter.go b/internal/engine/conversation_adapter.go index 08c39bb2d..0ef9a58fd 100644 --- a/internal/engine/conversation_adapter.go +++ b/internal/engine/conversation_adapter.go @@ -28,8 +28,8 @@ type ConversationState struct { Summary string } -// ConversationManager manages a conversation lifecycle, bridging flux's -// conversation management into rho's chat session flow. It is safe for +// ConversationManager manages a conversation lifecycle, bridging mux's +// conversation management into decoder'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 678857bab..f5cfb1f4d 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 rho engine. See ../../docs/plans/engine-refactor-plan.md. +// for the decoder 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 d38653b3a..99e8beeb8 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/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/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 a920c7883..599a032ca 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/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/provider/routing" ) const testProvider = "anthropic" diff --git a/internal/engine/cost/cost_table.go b/internal/engine/cost/cost_table.go index cd9cf9988..1c734315c 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/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/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 46ee3ba7f..78f638473 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/rho/internal/observability" - "github.com/GrayCodeAI/rho/internal/storage" + analytics "github.com/GrayCodeAI/decoder/internal/observability" + "github.com/GrayCodeAI/decoder/internal/storage" ) type CostTracker struct { diff --git a/internal/engine/cost/cost_tracker_test.go b/internal/engine/cost/cost_tracker_test.go index 6e9b595be..24d8db4ab 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/rho/internal/observability" - "github.com/GrayCodeAI/rho/internal/testutil" + analytics "github.com/GrayCodeAI/decoder/internal/observability" + "github.com/GrayCodeAI/decoder/internal/testutil" ) func TestCostTracker_NewAndRecord(t *testing.T) { diff --git a/internal/engine/council.go b/internal/engine/council.go index 27f2ae91f..e740e1f0f 100644 --- a/internal/engine/council.go +++ b/internal/engine/council.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - routing "github.com/GrayCodeAI/rho/internal/provider/routing" + routing "github.com/GrayCodeAI/decoder/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 aa8610e4c..13580d018 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/rho/internal/mathutil" + "github.com/GrayCodeAI/decoder/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 66b64526a..acfc6d1cd 100644 --- a/internal/engine/ctxmgr/context_collapse.go +++ b/internal/engine/ctxmgr/context_collapse.go @@ -4,19 +4,19 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) // CollapseRepeatedMessages finds and collapses similar consecutive messages to // save context tokens. It collapses: // - 3+ consecutive tool_results with similar content into a summary // - Repeated error messages into a count -func CollapseRepeatedMessages(msgs []types.FluxMessage) []types.FluxMessage { +func CollapseRepeatedMessages(msgs []types.MuxMessage) []types.MuxMessage { if len(msgs) < 3 { return msgs } - result := make([]types.FluxMessage, 0, len(msgs)) + result := make([]types.MuxMessage, 0, len(msgs)) i := 0 for i < len(msgs) { @@ -30,7 +30,7 @@ func CollapseRepeatedMessages(msgs []types.FluxMessage) []types.FluxMessage { runLen := j - i if runLen >= 2 { errText := extractErrorText(msgs[i]) - result = append(result, types.FluxMessage{ + result = append(result, types.MuxMessage{ Role: msgs[i].Role, Content: fmt.Sprintf("[Error repeated %d times: %s]", runLen, errText), ToolResults: msgs[i].ToolResults, @@ -52,7 +52,7 @@ func CollapseRepeatedMessages(msgs []types.FluxMessage) []types.FluxMessage { result = append(result, msgs[i]) toolName := toolResultSource(msgs[i]) collapsed := runLen - 2 - result = append(result, types.FluxMessage{ + result = append(result, types.MuxMessage{ Role: "user", Content: fmt.Sprintf("[Similar output from %s — %d results collapsed]", toolName, collapsed), ToolResults: []types.ToolResult{{ @@ -76,7 +76,7 @@ func CollapseRepeatedMessages(msgs []types.FluxMessage) []types.FluxMessage { // isSimilarToolResult checks whether two tool_result messages are similar // enough to collapse. Two results are similar if they come from the same tool // and their content shares the same first line or prefix (up to 100 chars). -func isSimilarToolResult(a, b types.FluxMessage) bool { +func isSimilarToolResult(a, b types.MuxMessage) bool { if len(a.ToolResults) == 0 || len(b.ToolResults) == 0 { return false } @@ -88,7 +88,7 @@ func isSimilarToolResult(a, b types.FluxMessage) bool { } // toolResultSource extracts the tool name from a tool_result message. -func toolResultSource(msg types.FluxMessage) string { +func toolResultSource(msg types.MuxMessage) string { if len(msg.ToolResults) > 0 && msg.ToolResults[0].ToolUseID != "" { return msg.ToolResults[0].ToolUseID } @@ -108,7 +108,7 @@ func contentPrefix(s string, n int) string { } // isErrorMessage returns true if a message appears to be an error. -func isErrorMessage(msg types.FluxMessage) bool { +func isErrorMessage(msg types.MuxMessage) bool { if len(msg.ToolResults) > 0 && msg.ToolResults[0].IsError { return true } @@ -117,7 +117,7 @@ func isErrorMessage(msg types.FluxMessage) bool { } // extractErrorText extracts the error text from an error message. -func extractErrorText(msg types.FluxMessage) string { +func extractErrorText(msg types.MuxMessage) string { if len(msg.ToolResults) > 0 && msg.ToolResults[0].IsError { return msg.ToolResults[0].Content } diff --git a/internal/engine/ctxmgr/context_collapse_test.go b/internal/engine/ctxmgr/context_collapse_test.go index a8700ad3c..14cb9bafa 100644 --- a/internal/engine/ctxmgr/context_collapse_test.go +++ b/internal/engine/ctxmgr/context_collapse_test.go @@ -3,11 +3,11 @@ package ctxmgr import ( "testing" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestCollapseRepeatedMessages_NoCollapse(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hello"}, {Role: "assistant", Content: "hi"}, {Role: "user", Content: "bye"}, @@ -19,7 +19,7 @@ func TestCollapseRepeatedMessages_NoCollapse(t *testing.T) { } func TestCollapseRepeatedMessages_ToolResults(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "run tests", ToolResults: []types.ToolResult{{ToolUseID: "Bash", Content: "PASS: all tests passed\ndetails..."}}}, {Role: "user", Content: "run tests", ToolResults: []types.ToolResult{{ToolUseID: "Bash", Content: "PASS: all tests passed\nother details"}}}, {Role: "user", Content: "run tests", ToolResults: []types.ToolResult{{ToolUseID: "Bash", Content: "PASS: all tests passed\nmore details"}}}, @@ -33,7 +33,7 @@ func TestCollapseRepeatedMessages_ToolResults(t *testing.T) { } func TestCollapseRepeatedMessages_Errors(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t1", Content: "connection refused", IsError: true}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t2", Content: "connection refused", IsError: true}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "t3", Content: "connection refused", IsError: true}}}, @@ -48,7 +48,7 @@ func TestCollapseRepeatedMessages_Errors(t *testing.T) { } func TestCollapseRepeatedMessages_MixedContent(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hello"}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "Bash", Content: "ok\ndetails"}}}, {Role: "user", ToolResults: []types.ToolResult{{ToolUseID: "Bash", Content: "ok\nother"}}}, @@ -62,7 +62,7 @@ func TestCollapseRepeatedMessages_MixedContent(t *testing.T) { } func TestCollapseRepeatedMessages_ShortInput(t *testing.T) { - msgs := []types.FluxMessage{ + msgs := []types.MuxMessage{ {Role: "user", Content: "hi"}, } result := CollapseRepeatedMessages(msgs) diff --git a/internal/engine/ctxmgr/context_decay.go b/internal/engine/ctxmgr/context_decay.go index f7a87f5d3..965757c4f 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 f4dd56ff7..f4058c175 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 e0708eb4b..683804ec4 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 4fa3d9770..f5edb7077 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) func TestNewContextVisualizer(t *testing.T) { diff --git a/internal/engine/ctxmgr/incremental.go b/internal/engine/ctxmgr/incremental.go index b8caef574..3ffe85a73 100644 --- a/internal/engine/ctxmgr/incremental.go +++ b/internal/engine/ctxmgr/incremental.go @@ -4,11 +4,11 @@ import ( "fmt" "time" - "github.com/GrayCodeAI/rho/internal/systemcontext" + "github.com/GrayCodeAI/decoder/internal/systemcontext" ) // Section defines a dynamic, incrementally-rendered system-prompt section. It -// is the rho-host integration of the systemcontext package: each section is a +// is the decoder-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 "rho" + return "decoder" } 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 rho already uses for each. It is a +// a session, wired to the same loaders decoder 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/developer_features_test.go b/internal/engine/developer_features_test.go index f1a64199b..9d0028187 100644 --- a/internal/engine/developer_features_test.go +++ b/internal/engine/developer_features_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine/compact" + "github.com/GrayCodeAI/decoder/internal/engine/compact" ) func TestBackgroundRunner_Delegate(t *testing.T) { diff --git a/internal/engine/diff/diff_staging.go b/internal/engine/diff/diff_staging.go index 983b4284b..e2c7e71e5 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/internal/ui/icons" ) // StagingArea provides a local staging area for agent edits, allowing review diff --git a/internal/engine/directive_scanner.go b/internal/engine/directive_scanner.go index 7fe0eed33..13d365472 100644 --- a/internal/engine/directive_scanner.go +++ b/internal/engine/directive_scanner.go @@ -9,7 +9,7 @@ import ( "strings" ) -// Directive is a parsed rho: comment from source code. +// Directive is a parsed decoder: comment from source code. type Directive struct { File string Line int @@ -17,9 +17,9 @@ type Directive struct { Context string } -var rhoDirectivePattern = regexp.MustCompile(`(?i)(?://|#|--|/\*)\s*rho:\s*(.+?)(?:\s*\*/)?$`) +var decoderDirectivePattern = regexp.MustCompile(`(?i)(?://|#|--|/\*)\s*decoder:\s*(.+?)(?:\s*\*/)?$`) -// ScanDirectives finds all `// rho: ` comments in source files. +// ScanDirectives finds all `// decoder: ` 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 == ".rho" { + if name == ".git" || name == "node_modules" || name == "vendor" || name == ".decoder" { 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 := rhoDirectivePattern.FindStringSubmatch(line) + matches := decoderDirectivePattern.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 rho: comment asks for. Remove the rho: comment after implementing." + "Implement what the decoder: comment asks for. Remove the decoder: comment after implementing." } diff --git a/internal/engine/dry_run_test.go b/internal/engine/dry_run_test.go index 35cbe5a8d..91a0a747a 100644 --- a/internal/engine/dry_run_test.go +++ b/internal/engine/dry_run_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" ) // TestDryRun_DeniesEverythingRegardlessOfTierOrStage covers the dry-run diff --git a/internal/engine/elision.go b/internal/engine/elision.go index d2701c7b1..f35b128dd 100644 --- a/internal/engine/elision.go +++ b/internal/engine/elision.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/GrayCodeAI/rho/internal/engine/token" + "github.com/GrayCodeAI/decoder/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 b3822dd19..b9bc645e7 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -4,7 +4,7 @@ import ( "context" "os" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) const ( @@ -106,9 +106,9 @@ func (s *Session) compact(ctx context.Context) { return // nothing to compact } - keep := make([]types.FluxMessage, 0, len(raw)-(cutEnd-cutStart)+1) + keep := make([]types.MuxMessage, 0, len(raw)-(cutEnd-cutStart)+1) keep = append(keep, raw[:cutStart]...) - keep = append(keep, types.FluxMessage{ + keep = append(keep, types.MuxMessage{ Role: "user", Content: "[Earlier conversation compacted to save context.]", }) diff --git a/internal/engine/engine_full_loop_test.go b/internal/engine/engine_full_loop_test.go index 8da1a8735..afe6e7204 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) func TestEngine_FullLoop_TextOnly(t *testing.T) { @@ -69,14 +69,14 @@ func TestEngine_FullLoop_MultiTurn(t *testing.T) { func TestEngine_FullLoop_ToolUse(t *testing.T) { mc := newMockClient( - &types.FluxResponse{ + &types.MuxResponse{ FinishReason: "tool_use", ToolCalls: []types.ToolCall{{ ID: "call_1", Name: "Read", Arguments: map[string]interface{}{"file_path": "/nonexistent"}, }}, - Usage: &types.FluxUsage{PromptTokens: 50, CompletionTokens: 30}, + Usage: &types.MuxUsage{PromptTokens: 50, CompletionTokens: 30}, }, mockTextResponse("I read the file and here's what I found."), ) diff --git a/internal/engine/engine_integration_test.go b/internal/engine/engine_integration_test.go index cfb174fb5..b205331aa 100644 --- a/internal/engine/engine_integration_test.go +++ b/internal/engine/engine_integration_test.go @@ -5,15 +5,15 @@ import ( "testing" "time" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/engine/cost" + "github.com/GrayCodeAI/decoder/internal/engine/cost" - contracts "github.com/GrayCodeAI/rho/internal/contracts/policy" - "github.com/GrayCodeAI/rho/internal/types" + contracts "github.com/GrayCodeAI/decoder/internal/contracts/policy" + "github.com/GrayCodeAI/decoder/internal/types" - "github.com/GrayCodeAI/rho/internal/session" - "github.com/GrayCodeAI/rho/internal/tool" + "github.com/GrayCodeAI/decoder/internal/session" + "github.com/GrayCodeAI/decoder/internal/tool" ) // ────────────────────────────────────────────────────────────────────────────── @@ -70,14 +70,14 @@ func TestIntegration_FullSessionFlow(t *testing.T) { // Simulate the engine receiving an assistant text reply + tool call + tool result // by manually building the message sequence that agentLoop would produce. - sess.Persistence().SetRawMessages(append(sess.Persistence().RawMessages(), types.FluxMessage{ + sess.Persistence().SetRawMessages(append(sess.Persistence().RawMessages(), types.MuxMessage{ Role: "assistant", Content: "Sure, let me check that file.", ToolUse: []types.ToolCall{ {ID: "tc-1", Name: "Bash", Arguments: map[string]interface{}{"command": "echo hello"}}, }, })) - sess.Persistence().SetRawMessages(append(sess.Persistence().RawMessages(), types.FluxMessage{ + sess.Persistence().SetRawMessages(append(sess.Persistence().RawMessages(), types.MuxMessage{ Role: "user", ToolResults: []types.ToolResult{{ ToolUseID: "tc-1", @@ -85,7 +85,7 @@ func TestIntegration_FullSessionFlow(t *testing.T) { IsError: false, }}, })) - sess.Persistence().SetRawMessages(append(sess.Persistence().RawMessages(), types.FluxMessage{ + sess.Persistence().SetRawMessages(append(sess.Persistence().RawMessages(), types.MuxMessage{ Role: "assistant", Content: "The command ran successfully and returned 'hello'.", })) diff --git a/internal/engine/engine_stage2_test_helpers.go b/internal/engine/engine_stage2_test_helpers.go index 68c49d029..3d88ad8f5 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/rho/internal/provider/routing" + "github.com/GrayCodeAI/decoder/internal/provider/routing" ) const testProvider = "anthropic" diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go index 7a0474157..0c560be7b 100644 --- a/internal/engine/engine_test.go +++ b/internal/engine/engine_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/engine/cost" + "github.com/GrayCodeAI/decoder/internal/engine/cost" ) func TestPermissionMemoryAlwaysAllow(t *testing.T) { diff --git a/internal/engine/engine_token_helpers.go b/internal/engine/engine_token_helpers.go index 4ed037a04..6921d4762 100644 --- a/internal/engine/engine_token_helpers.go +++ b/internal/engine/engine_token_helpers.go @@ -3,10 +3,10 @@ package engine import ( "strings" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/types" ) -func isRecentToolHeavy(messages []types.FluxMessage) bool { +func isRecentToolHeavy(messages []types.MuxMessage) bool { const lookback = 3 toolTurns := 0 assistantSeen := 0 @@ -25,7 +25,7 @@ func isRecentToolHeavy(messages []types.FluxMessage) bool { return assistantSeen >= lookback && toolTurns == assistantSeen } -func isTextQuestion(messages []types.FluxMessage) bool { +func isTextQuestion(messages []types.MuxMessage) bool { var lastUserMsg string for i := len(messages) - 1; i >= 0; i-- { msg := messages[i] @@ -53,7 +53,7 @@ func isTextQuestion(messages []types.FluxMessage) bool { return strings.HasSuffix(strings.TrimSpace(lower), "?") } -func classifyPromptForBudget(messages []types.FluxMessage) string { +func classifyPromptForBudget(messages []types.MuxMessage) string { if isRecentToolHeavy(messages) { return "tool" } diff --git a/internal/engine/errs/aliases.go b/internal/engine/errs/aliases.go index f5e9e5d17..e7121c323 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 rho engine. +// patterns, and recovery for the decoder 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 f07bfa829..c6d310c20 100644 --- a/internal/engine/errs/error_context.go +++ b/internal/engine/errs/error_context.go @@ -419,7 +419,7 @@ var builtinErrorPatterns = map[string]*ErrorHelp{ }, }, - "rho_old_str_not_found": { + "decoder_old_str_not_found": { 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.", @@ -436,7 +436,7 @@ var builtinErrorPatterns = map[string]*ErrorHelp{ AutoFix: "Re-read the target file and retry with the exact current content", }, - "rho_file_too_large": { + "decoder_file_too_large": { 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.", @@ -453,7 +453,7 @@ var builtinErrorPatterns = map[string]*ErrorHelp{ AutoFix: "Use head_tail or line-range reads to process the file in parts", }, - "rho_budget_exceeded": { + "decoder_budget_exceeded": { 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.", @@ -464,12 +464,12 @@ var builtinErrorPatterns = map[string]*ErrorHelp{ "Check for loops that may be inflating token usage", }, Examples: []string{ - "rho --budget 10.00 # set a higher budget", + "decoder --budget 10.00 # set a higher budget", "/compact # reduce context size", }, }, - "rho_tool_not_found": { + "decoder_tool_not_found": { 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.", @@ -484,7 +484,7 @@ var builtinErrorPatterns = map[string]*ErrorHelp{ }, }, - "rho_path_boundary_violation": { + "decoder_path_boundary_violation": { Pattern: regexp.MustCompile(`(path boundary violation|operation not permitted outside allowed path)`), Title: "Path boundary violation", Explanation: "The operation was blocked because it attempted to access a resource outside the allowed project paths.", @@ -492,7 +492,7 @@ var builtinErrorPatterns = map[string]*ErrorHelp{ "Check which paths are allowed by the project policy", "Request permission for the specific operation", "Verify the file is within the project directory", - "Check Rho permission settings", + "Check Decoder permission 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 2aa87d507..fa9f12fd7 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_RhoOldStrNotFound(t *testing.T) { +func TestEnrich_DecoderOldStrNotFound(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_RhoOldStrNotFound(t *testing.T) { } } -func TestEnrich_RhoFileTooLarge(t *testing.T) { +func TestEnrich_DecoderFileTooLarge(t *testing.T) { ec := NewErrorContext() enriched := ec.Enrich("cannot read: file too large (50MB)") if enriched == nil { @@ -302,7 +302,7 @@ func TestEnrich_RhoFileTooLarge(t *testing.T) { } } -func TestEnrich_RhoBudgetExceeded(t *testing.T) { +func TestEnrich_DecoderBudgetExceeded(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 b6bff566f..aa14ecaf3 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/rho/internal/storage" + "github.com/GrayCodeAI/decoder/internal/storage" ) type ErrorPattern struct { diff --git a/internal/engine/errs/error_recovery.go b/internal/engine/errs/error_recovery.go index 69a8fe307..465c09e92 100644 --- a/internal/engine/errs/error_recovery.go +++ b/internal/engine/errs/error_recovery.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/textutil" + "github.com/GrayCodeAI/decoder/internal/textutil" ) type ErrorRecovery struct { diff --git a/internal/engine/event_bus.go b/internal/engine/event_bus.go index 299c0b1b1..c42cddf13 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 rho components. +// EventBus is a lightweight pub/sub system for decoupling decoder 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 dfb475a2b..d92f82c60 100644 --- a/internal/engine/execution_graph_observations.go +++ b/internal/engine/execution_graph_observations.go @@ -7,13 +7,13 @@ import ( "strings" "time" - fluxengine "github.com/GrayCodeAI/flux/engine" - fluxgraph "github.com/GrayCodeAI/flux/graph" - 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" + graphcontracts "github.com/GrayCodeAI/decoder/internal/contracts/graph" + policycontracts "github.com/GrayCodeAI/decoder/internal/contracts/policy" + "github.com/GrayCodeAI/decoder/internal/engine/token" + "github.com/GrayCodeAI/decoder/internal/graphjournal" + "github.com/GrayCodeAI/decoder/internal/types" + muxengine "github.com/GrayCodeAI/mux/engine" + muxgraph "github.com/GrayCodeAI/mux/graph" ) 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 = "rho." + strings.TrimSpace(stage) + verdict.Source = "decoder." + strings.TrimSpace(stage) if !allowed { verdict = policycontracts.Deny(reason, strings.TrimSpace(stage)) - verdict.Source = "rho." + strings.TrimSpace(stage) + verdict.Source = "decoder." + 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{}{ @@ -271,10 +271,10 @@ func (s *Session) usageCanProceed() (bool, string) { return tracker.CanProceed() } -func (s *Session) recordFluxOperationObservation( +func (s *Session) recordMuxOperationObservation( provider, model, finishReason, content string, toolCallCount int, - usage *types.FluxUsage, + usage *types.MuxUsage, ) { sessionID := s.executionGraphSessionID() if sessionID == "" || usage == nil { @@ -290,19 +290,19 @@ func (s *Session) recordFluxOperationObservation( } observedAt := time.Now().UTC() route := types.ResolvedRoute{Provider: provider, Model: model} - export, err := fluxengine.BuildOperationsGraph(fluxengine.OperationsGraphInput{ + export, err := muxengine.BuildOperationsGraph(muxengine.OperationsGraphInput{ Route: &route, Usage: usage, FinishReason: finishReason, Content: content, ToolCallCount: toolCallCount, ObservedAt: observedAt, - Scope: fluxgraph.Scope{RepositoryID: repositoryID}, + Scope: muxgraph.Scope{RepositoryID: repositoryID}, CorrelationID: sessionID, }) if err == nil { err = graphjournal.AppendRuntimeGraph( - sessionID, "", "model-generation", "flux", + sessionID, "", "model-generation", "mux", toContractNodes(export.Nodes), toContractEdges(export.Edges), toContractEvents(export.Events), observedAt, ) } @@ -314,11 +314,11 @@ func (s *Session) recordFluxOperationObservation( } } -// The following helpers convert Flux's vendored graph contract types into -// Rho's contracts/graph contract types. The definitions are byte-identical, so +// The following helpers convert Mux's vendored graph contract types into +// Decoder'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 []fluxgraph.Node) []graphcontracts.Node { +func toContractNodes(nodes []muxgraph.Node) []graphcontracts.Node { out := make([]graphcontracts.Node, len(nodes)) for i, n := range nodes { out[i] = toContractNode(n) @@ -326,7 +326,7 @@ func toContractNodes(nodes []fluxgraph.Node) []graphcontracts.Node { return out } -func toContractNode(n fluxgraph.Node) graphcontracts.Node { +func toContractNode(n muxgraph.Node) graphcontracts.Node { return graphcontracts.Node{ ID: n.ID, Kind: graphcontracts.NodeKind(n.Kind), @@ -338,7 +338,7 @@ func toContractNode(n fluxgraph.Node) graphcontracts.Node { } } -func toContractEdges(edges []fluxgraph.Edge) []graphcontracts.Edge { +func toContractEdges(edges []muxgraph.Edge) []graphcontracts.Edge { out := make([]graphcontracts.Edge, len(edges)) for i, e := range edges { out[i] = toContractEdge(e) @@ -346,7 +346,7 @@ func toContractEdges(edges []fluxgraph.Edge) []graphcontracts.Edge { return out } -func toContractEdge(e fluxgraph.Edge) graphcontracts.Edge { +func toContractEdge(e muxgraph.Edge) graphcontracts.Edge { return graphcontracts.Edge{ ID: e.ID, Kind: graphcontracts.EdgeKind(e.Kind), @@ -360,7 +360,7 @@ func toContractEdge(e fluxgraph.Edge) graphcontracts.Edge { } } -func toContractEvents(events []fluxgraph.Event) []graphcontracts.Event { +func toContractEvents(events []muxgraph.Event) []graphcontracts.Event { out := make([]graphcontracts.Event, len(events)) for i, ev := range events { out[i] = toContractEvent(ev) @@ -368,7 +368,7 @@ func toContractEvents(events []fluxgraph.Event) []graphcontracts.Event { return out } -func toContractEvent(ev fluxgraph.Event) graphcontracts.Event { +func toContractEvent(ev muxgraph.Event) graphcontracts.Event { return graphcontracts.Event{ ID: ev.ID, Type: graphcontracts.EventType(ev.Type), @@ -382,15 +382,15 @@ func toContractEvent(ev fluxgraph.Event) graphcontracts.Event { } } -func toContractRef(r fluxgraph.Ref) graphcontracts.Ref { +func toContractRef(r muxgraph.Ref) graphcontracts.Ref { return graphcontracts.Ref{Kind: graphcontracts.NodeKind(r.Kind), ID: r.ID} } -func toContractScope(s fluxgraph.Scope) graphcontracts.Scope { +func toContractScope(s muxgraph.Scope) graphcontracts.Scope { return graphcontracts.Scope{TenantID: s.TenantID, ProjectID: s.ProjectID, RepositoryID: s.RepositoryID} } -func toContractProvenance(p fluxgraph.Provenance) graphcontracts.Provenance { +func toContractProvenance(p muxgraph.Provenance) graphcontracts.Provenance { evidence := make([]graphcontracts.ArtifactRef, len(p.Evidence)) for i, a := range p.Evidence { evidence[i] = graphcontracts.ArtifactRef{URI: a.URI, Digest: a.Digest, MediaType: a.MediaType} diff --git a/internal/engine/execution_graph_observations_test.go b/internal/engine/execution_graph_observations_test.go index 88706be76..a05a5ffb6 100644 --- a/internal/engine/execution_graph_observations_test.go +++ b/internal/engine/execution_graph_observations_test.go @@ -6,12 +6,12 @@ import ( "strings" "testing" - "github.com/GrayCodeAI/rho/internal/engine/safety" + "github.com/GrayCodeAI/decoder/internal/engine/safety" - "github.com/GrayCodeAI/rho/internal/graphjournal" - "github.com/GrayCodeAI/rho/internal/token" - "github.com/GrayCodeAI/rho/internal/tool" - "github.com/GrayCodeAI/rho/internal/types" + "github.com/GrayCodeAI/decoder/internal/graphjournal" + "github.com/GrayCodeAI/decoder/internal/token" + "github.com/GrayCodeAI/decoder/internal/tool" + "github.com/GrayCodeAI/decoder/internal/types" ) type graphVerifyTool struct{} @@ -27,7 +27,7 @@ func (graphVerifyTool) Execute(context.Context, json.RawMessage) (string, error) } func TestToolExecutionAutomaticallyRecordsPolicyAndVerification(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry(graphVerifyTool{})) sess.SetPersistID("graph-runtime-session") @@ -64,7 +64,7 @@ func TestToolExecutionAutomaticallyRecordsPolicyAndVerification(t *testing.T) { } func TestShrikeCompressionObservationIsPrivacySafe(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("token-runtime-session") sess.recordCompressionObservation( @@ -91,7 +91,7 @@ func TestShrikeCompressionObservationIsPrivacySafe(t *testing.T) { } func TestShrikeRedactionObservationIsPrivacySafe(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("token-redaction-session") sess.recordRedactionObservation( @@ -118,7 +118,7 @@ func TestShrikeRedactionObservationIsPrivacySafe(t *testing.T) { } func TestShrikeUsageBudgetObservationTracksAndProjectsAuthoritativeUsage(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) + t.Setenv("DECODER_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) sess.SetPersistID("shrike-usage-session") if err := sess.SetMaxBudgetUSD(1); err != nil { @@ -217,19 +217,19 @@ func TestDrainAlertsSurfacesHourlyWarning(t *testing.T) { } } -func TestFluxOperationObservationIsPrivacySafe(t *testing.T) { - t.Setenv("RHO_STATE_DIR", t.TempDir()) +func TestMuxOperationObservationIsPrivacySafe(t *testing.T) { + t.Setenv("DECODER_STATE_DIR", t.TempDir()) sess := NewSession("test", "test", "system", tool.NewRegistry()) - sess.SetPersistID("flux-runtime-session") - sess.recordFluxOperationObservation( + sess.SetPersistID("mux-runtime-session") + sess.recordMuxOperationObservation( "private-provider", "private/model", "stop", "private generated content", 2, - &types.FluxUsage{PromptTokens: 100, CompletionTokens: 20, TotalTokens: 120}, + &types.MuxUsage{PromptTokens: 100, CompletionTokens: 20, TotalTokens: 120}, ) - entries, err := graphjournal.Load("flux-runtime-session") + entries, err := graphjournal.Load("mux-runtime-session") if err != nil { t.Fatalf("graphjournal.Load() error = %v", err) } @@ -242,7 +242,7 @@ func TestFluxOperationObservationIsPrivacySafe(t *testing.T) { } for _, secret := range []string{"private-provider", "private/model", "private generated content"} { if strings.Contains(string(payload), secret) { - t.Fatalf("Flux graph observation leaked %q", secret) + t.Fatalf("Mux graph observation leaked %q", secret) } } } diff --git a/internal/engine/experiment_loop.go b/internal/engine/experiment_loop.go index 7d3227774..0205bb6c8 100644 --- a/internal/engine/experiment_loop.go +++ b/internal/engine/experiment_loop.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/GrayCodeAI/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 index 4973e3023..e63f4c387 100644 --- a/internal/engine/external_content.go +++ b/internal/engine/external_content.go @@ -3,7 +3,7 @@ package engine import ( "strings" - "github.com/GrayCodeAI/rho/internal/permissions" + "github.com/GrayCodeAI/decoder/internal/permissions" ) // externalContentSource maps a tool name to the untrusted content source used diff --git a/internal/engine/extract_targets_test.go b/internal/engine/extract_targets_test.go index 9210fa3cf..b307b48e1 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/rho/internal/types" + "github.com/GrayCodeAI/decoder/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 55c070b51..4b8291042 100644 --- a/internal/engine/git/git_context.go +++ b/internal/engine/git/git_context.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/GrayCodeAI/rho/internal/gitcmd" + "github.com/GrayCodeAI/decoder/internal/gitcmd" ) // GitContext provides git-aware context enrichment for files and sessions. diff --git a/internal/engine/git/git_provider.go b/internal/engine/git/git_provider.go index 9f36dec68..eb3657d70 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 ee5fcee2e..ba261f3ab 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/rho/internal/ui/icons" + "github.com/GrayCodeAI/decoder/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 0c2cad011..a68fa3094 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":"rho","url":"https://github.com/GrayCodeAI/rho"}` + jsonStr := `{"owner":{"login":"GrayCodeAI"},"name":"decoder","url":"https://github.com/GrayCodeAI/decoder"}` owner, repo, provider := parseGHRepoView(jsonStr) if owner != "GrayCodeAI" { t.Errorf("owner = %q, want %q", owner, "GrayCodeAI") } - if repo != "rho" { - t.Errorf("repo = %q, want %q", repo, "rho") + if repo != "decoder" { + t.Errorf("repo = %q, want %q", repo, "decoder") } 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/rho.git + url = git@github.com:GrayCodeAI/decoder.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 != "rho" { - t.Errorf("repo = %q, want %q", repo, "rho") + if repo != "decoder" { + t.Errorf("repo = %q, want %q", repo, "decoder") } 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 5f84d186f..9661f5aac 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 // rho/agent- when OnDefault + Suggested string // decoder/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("rho/agent-%s", time.Now().Format("20060102-150405")) + info.Suggested = fmt.Sprintf("decoder/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 rho/agent-* branch when currently +// EnsureAgentBranch creates and checks out a decoder/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("rho/agent-%d", time.Now().Unix()) + name = fmt.Sprintf("decoder/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 (rho/agent-*) + // #nosec G204 -- fixed git subcommand; branch name is generated internally (decoder/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 74eccd56b..4702fcb51 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, "rho/agent-") { + if !strings.HasPrefix(name, "decoder/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 f41a637d1..c3ee0de6a 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, ".rhohints"), []byte("Use Go idioms\nPrefer table-driven tests"), 0o644) + os.WriteFile(filepath.Join(dir, ".decoderhints"), []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, ".rhohints"), []byte("hint"), 0o644) + os.WriteFile(filepath.Join(dir, ".decoderhints"), []byte("hint"), 0o644) h := NewHintsLoader() h.LoadHints(dir) diff --git a/internal/engine/hints_loader.go b/internal/engine/hints_loader.go index 53023991d..2efeec81c 100644 --- a/internal/engine/hints_loader.go +++ b/internal/engine/hints_loader.go @@ -6,8 +6,8 @@ import ( "strings" ) -// HintsFilenames are the files rho auto-loads for project context. -var HintsFilenames = []string{".rhohints", "AGENTS.md"} +// HintsFilenames are the files decoder auto-loads for project context. +var HintsFilenames = []string{".decoderhints", "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 44827dcf6..94045dfaf 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 [rho:%s] %s", commentPrefix, a.Type, a.Content) + commentLine := fmt.Sprintf("%s [decoder:%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 { } } -// rhoAnnotationRe matches rho annotation comment lines. -var rhoAnnotationRe = regexp.MustCompile(`^\s*(//|#|/\*|