diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b0afd87fe4..19a607da80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,10 +2,7 @@ name: release # Build, sign, and publish a Redrob Code release. # -# Versions are ours and start at 0.1.0. They are NOT derived from the upstream version -# this tree is built on: that provenance lives in `UPSTREAM_VERSION` at the repository -# root, which the release notes read. See docs/VERSIONING.md for why the old -# `1.18.31-redrob.N` shape was retired. +# Versions start at 0.1.0 and follow semver. See docs/VERSIONING.md. # # `workflow_dispatch` ONLY. This job reaches real signing credentials, so it must never be # reachable from a pull request, a comment, or a schedule. Dispatch requires write access @@ -56,7 +53,6 @@ jobs: outputs: version: ${{ steps.compute.outputs.version }} tag: ${{ steps.compute.outputs.tag }} - upstream: ${{ steps.compute.outputs.upstream }} # The one commit this release is. Every later job checks THIS out, not `develop`: # a merge that lands while the build runs must not make the tag point at code the # binaries were not built from (it did, once, with a merge two minutes after dispatch). @@ -76,7 +72,7 @@ jobs: run: | set -euo pipefail # Only plain `vMAJOR.MINOR.PATCH` counts. Anything carrying a `-` is a prerelease - # or a legacy upstream-derived tag, and letting one of those win here would make + # or a legacy `-redrob.N` tag, and letting one of those win here would make # the next version a continuation of a line we deliberately left behind. latest="$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname \ | grep -v -- '-' | head -n1 || true)" @@ -103,19 +99,10 @@ jobs: echo "::error::v$version already exists; a release cannot be recomputed onto an existing tag" exit 1 fi - # The upstream version the tag no longer carries. Absent is a defect rather than - # a blank field: the release notes state what this is built on, and a release - # that cannot say so should not be published. - if [ ! -s UPSTREAM_VERSION ]; then - echo "::error::UPSTREAM_VERSION is missing or empty; see docs/VERSIONING.md" - exit 1 - fi - upstream="$(tr -d '[:space:]' < UPSTREAM_VERSION)" echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=v$version" >> "$GITHUB_OUTPUT" echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - echo "upstream=$upstream" >> "$GITHUB_OUTPUT" - echo "::notice::releasing v$version (built on opencode v$upstream)" + echo "::notice::releasing v$version" build: needs: version @@ -548,18 +535,16 @@ jobs: - name: Create and push the tag env: TAG: ${{ needs.version.outputs.tag }} - UPSTREAM: ${{ needs.version.outputs.upstream }} run: | set -euo pipefail # An annotated tag records a tagger, and a bare runner has no git identity, so # `git tag -a` fails with "empty ident name" AFTER the build, the signing and the # notarization have all succeeded -- the most expensive possible place to # discover it. The tag stays annotated rather than lightweight because it is what - # a release points at and it should say who made it and when, and its message - # carries the upstream version the release notes no longer state. + # a release points at and it should say who made it and when. git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git tag -a "$TAG" -m "Redrob Code $TAG (built on opencode v$UPSTREAM)" 2>/dev/null || \ + git tag -a "$TAG" -m "Redrob Code $TAG" 2>/dev/null || \ echo "tag $TAG already exists locally" # A release that fails after tagging must be retryable. Without this a single # transient failure in the publish job leaves the version permanently unusable: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21ea5f8716..9a4a2e2735 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: # Docs. *.md|docs/*|LICENSE*|CHANGELOG*) continue ;; # Whole-file version markers. - VERSION|REDROB_VERSION|UPSTREAM_VERSION|*-version.txt|*/VERSION) continue ;; + VERSION|REDROB_VERSION|*-version.txt|*/VERSION) continue ;; # Manifests and lockfiles: allowed only if every changed line is a version string. package.json|*/package.json|bun.lock|Cargo.toml|*/Cargo.toml|Cargo.lock|*/Cargo.lock) if git diff -U0 HEAD^1 HEAD -- "$f" \ diff --git a/AGENTS.md b/AGENTS.md index e38dd54fbb..2eccc9ec61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -Redrob Code: the engine behind Redrob Cowork, a fork of OpenCode. It is consumed as a pinned +Redrob Code: the engine behind Redrob Cowork. It is consumed as a pinned version rather than a branch — `constants.json` in `redrob-cowork` names the release the desktop app ships, so **a change here reaches users only through a release and a pin bump**, never by merging. @@ -16,7 +16,7 @@ Gitflow. **`develop` is the default branch and the base of every pull request.** Branch names are `/` with the type from the redrob-labs organization list: `feat fix chore docs test refactor perf ci build sync hotfix release` (`docs/BRANCHING.md` has the -ones this repository actually uses: `feat/`, `sync/upstream-`, `hotfix/`, +ones this repository actually uses: `feat/`, `hotfix/`, `release/`). Do not invent a prefix, and in particular do not name a branch after the tool or agent that made it: a branch name says what the change is, not who typed it. @@ -62,8 +62,7 @@ Full detail in `docs/BRANCHING.md`. instead. - Judge by the exit code, not by a pass count. A suite can print `0 fail` and still exit non-zero when a file fails to load: its tests are never counted and a `(fail)` grep finds nothing. -- The version line is ours and starts at `0.1.0`. It does not encode the upstream version, which - lives in `UPSTREAM_VERSION` at the repository root and is what the release notes read. Do not +- The version line starts at `0.1.0`. Do not reintroduce a `-redrob.N` suffix: it made every release a semver prerelease, which sorts below the release it precedes and made `getReleaseType()` call everything a patch. See `docs/VERSIONING.md`. diff --git a/README.ko.md b/README.ko.md index cbe9ea34a4..81c6e18b9b 100644 --- a/README.ko.md +++ b/README.ko.md @@ -11,14 +11,12 @@ --- -레드롭 코드는 [opencode](https://github.com/anomalyco/opencode)의 포크이며 MIT 라이선스로 -배포됩니다. opencode 프로젝트 및 그 관리자와 제휴, 보증, 지원 관계가 없습니다. 이름은 -이 소프트웨어의 출처를 밝히기 위해서만 사용합니다. - -포크가 바꾼 것은 세 가지입니다. 공급자 모델을 벤더별 키가 아닌 하나의 레드롭 워크스페이스 -엔드포인트로 바꿨고, 한국어를 일급 로케일로 다루며, 호스팅 콘솔이 아닌 터미널 클라이언트에 -맞게 패키지를 줄였습니다. 릴리스는 `0.1.0` 부터 시작하는 자체 버전 체계를 사용합니다. -빌드가 기반한 상류 버전은 `UPSTREAM_VERSION` 에 기록되고 릴리스 노트에 표시됩니다. +레드롭 코드는 MIT 라이선스로 배포됩니다. 서드파티 라이선스 고지는 +[NOTICE.md](./NOTICE.md)에 있습니다. + +레드롭 코드는 벤더별 키가 아닌 하나의 레드롭 워크스페이스 엔드포인트를 쓰고, +한국어를 일급 로케일로 다루며, 호스팅 콘솔이 아닌 터미널 클라이언트에 맞춘 +패키지로 구성됩니다. 릴리스는 `0.1.0` 부터 시작하는 자체 버전 체계를 사용합니다. 자세한 내용은 `docs/VERSIONING.md` 를 참고하세요. ### 설치 @@ -126,7 +124,7 @@ id를 쓰는 설정·에이전트·명령은 `redrob/auto`로 바꾸세요. 프로젝트 설정이 전역 설정 위에 병합됩니다. 에이전트에게 설정 파일을 수정하라고 요청하면 실제 스키마가 담긴 내장 스킬을 불러오므로, `redrob`이 자기 설정을 직접 구성할 수 있습니다. -`.opencode/`는 이전 체크아웃 호환을 위해 계속 읽지만, 새 파일은 `.redrob/`에 두세요. +프로젝트 설정 파일은 `.redrob/`에 두세요. ### 범위 @@ -149,6 +147,5 @@ cd packages/core && bun test # 테스트는 패키지 디렉터리에서 실행 `bun@1.3.14`이 필요하며 pre-push 훅이 이를 검사합니다. -브랜치 모델, 풀 리퀘스트 방법, 그리고 상류 릴리스를 흡수하는 절차는 -[docs/BRANCHING.md](./docs/BRANCHING.md)에 있습니다. 마지막 항목이 포크에서 가장 -중요하면서 가장 틀리기 쉬운 작업입니다. +브랜치 모델과 풀 리퀘스트 방법은 +[docs/BRANCHING.md](./docs/BRANCHING.md)에 있습니다. diff --git a/README.md b/README.md index 1083c2f4bc..52a0c88f74 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,15 @@ Redrob Code, the AI coding agent for your terminal. --- -Redrob Code is the AI coding agent for your terminal, a fork of opencode with Korean as a first-class locale. +Redrob Code is the AI coding agent for your terminal, with Korean as a first-class locale. -Redrob Code is a fork of [opencode](https://github.com/anomalyco/opencode), released -under the MIT License. It is not affiliated with, endorsed by, or supported by the -opencode project or its maintainers: the name is used here only to say where this -software came from. +Redrob Code is released under the MIT License. Third-party license notices are in +[NOTICE.md](./NOTICE.md). -What the fork changes: a different provider model (a single Redrob workspace endpoint -rather than per-vendor keys), Korean as a first-class locale, and a reduced package set -aimed at the terminal client rather than the hosted console. Releases have their own -version line starting at `0.1.0`; the upstream version a build is based on is recorded in -`UPSTREAM_VERSION` and stated in the release notes. See `docs/VERSIONING.md`. +What Redrob Code offers: a single Redrob workspace endpoint +rather than per-vendor keys, Korean as a first-class locale, and a package set +aimed at the terminal client rather than a hosted console. Releases have their own +version line starting at `0.1.0`. See `docs/VERSIONING.md`. ### Installation @@ -130,8 +127,7 @@ Two subagents are also available and can be invoked from a message: Project config is merged over global config. Ask the agent to edit your config and it loads a built-in skill with the real schema, so `redrob` can also configure itself. -`.opencode/` is still read as a legacy location so older checkouts keep working; write -new files to `.redrob/`. +Project config files live in `.redrob/`. ### Scope @@ -155,6 +151,5 @@ cd packages/core && bun test # tests run per package, never from the repo root `bun@1.3.14` is required; the pre-push hook enforces it. -See [docs/BRANCHING.md](./docs/BRANCHING.md) for the branch model, how to open a pull -request, and how upstream releases are absorbed, the last of which is the operation -that matters most in a fork and the one most easily got wrong. +See [docs/BRANCHING.md](./docs/BRANCHING.md) for the branch model and how to open a pull +request. diff --git a/UPSTREAM_VERSION b/UPSTREAM_VERSION deleted file mode 100644 index 769c5a4d05..0000000000 --- a/UPSTREAM_VERSION +++ /dev/null @@ -1 +0,0 @@ -1.18.31 diff --git a/docs/BRANCHING.md b/docs/BRANCHING.md index 7cff960e3b..71156d94fe 100644 --- a/docs/BRANCHING.md +++ b/docs/BRANCHING.md @@ -1,19 +1,16 @@ # Branching -Redrob Code is a **fork** of [opencode](https://github.com/anomalyco/opencode), and that -single fact decides the branching model. A fork's most consequential operation is not a -feature — it is absorbing upstream, which arrives as a large, conflict-prone merge that -can leave the tree broken for a while. Everything below exists so that merge never lands +Redrob Code uses gitflow. Work lands in `develop` first; `main` is released state and +moves only through a reviewed release pull request, so nothing half-integrated lands somewhere people are cloning from. ## Branches | Branch | What it is | |---|---| -| `develop` | **Default.** Integration. Every feature, fix and upstream sync lands here first. | +| `develop` | **Default.** Integration. Every feature and fix lands here first. | | `main` | The released state. Only ever advanced by a release pull request from `develop`. | | `feat/` | One change, opened as a pull request into `develop`. Also `fix/`, `docs/`, `chore/` and the rest of the organization type list, when they describe the change better. | -| `sync/upstream-` | An upstream absorption, opened as a pull request into `develop`. | | `hotfix/` | A fix for something already released, into `main` **and** `develop`. | | `release/` | Only when a release needs stabilising before it ships. Usually skipped. | @@ -21,9 +18,7 @@ somewhere people are cloning from. having to retarget them, and the people reading the repository's default branch are contributors. End users install a release artifact; they do not clone. -We do not use `master`, and our branches are deliberately **not** named after upstream's -(`dev`, `main`). When you add an `upstream` remote, `upstream/dev` and `origin/develop` -should not read as the same thing, because they are not. +We do not use `master`. `release/*` is in the table for completeness and is usually a waste of a branch. Reach for it only when a release genuinely needs to stabilise while `develop` keeps moving. @@ -33,25 +28,15 @@ every pull request: `feat fix chore docs test refactor perf ci build sync hotfix caller in `.github/workflows/gitflow.yml` is a verbatim copy of the organization template and carries no list of its own. -## Upstream - -Redrob Code no longer merges upstream (opencode) releases; that stopped on 2026-10-26. -Upstream files are ours to change like any other. `UPSTREAM_VERSION` still records the -upstream release this tree was last merged from, because attribution needs it and the -release notes state it. - -Some `opencode` strings are still load-bearing and stay as they are. The legacy -`.opencode` config directory is read on purpose so existing checkouts keep working, and -`@gitlab/opencode-gitlab-auth`, `opencode-gitlab-auth` and `opencode-poe-auth` are real -published packages that are not ours to rename. See [NOTICE.md](../NOTICE.md). +Third-party package names such as `@gitlab/opencode-gitlab-auth`, `opencode-gitlab-auth` +and `opencode-poe-auth` are real published packages that are not ours to rename. See +[NOTICE.md](../NOTICE.md) for license attributions. ## Releasing -Versions are ours and start at `0.1.0`. They are not derived from the upstream version the -tree is built on: that is recorded in `UPSTREAM_VERSION` at the repository root, and the -release notes read it so every release still states what it is based on. The old -`-redrob.N` suffix was retired because it made every release a semver prerelease, which -sorts below the release it precedes. See `docs/VERSIONING.md`. +Versions start at `0.1.0` and follow semver. The old `-redrob.N` suffix was retired +because it made every release a semver prerelease, which sorts below the release it +precedes. See `docs/VERSIONING.md`. Run the `release` workflow from the Actions tab against `develop` with a `bump` of `patch`, `minor` or `major`. It computes the next version from the newest `v*` tag, builds every @@ -74,8 +59,7 @@ shared reusable workflow `redrob-labs/.github/.github/workflows/release-publish. collects every `release-*` artifact, writes `SHA256SUMS.txt` and `redrob-launcher.json`, and publishes `v` as latest (as a prerelease when the version carries a `-`). This workflow passes it the tag and the commit explicitly, because the tag is created by the -`tag` job here rather than by a push. The release notes are GitHub's generated notes; the -upstream version the build is based on is in the tag message and in `UPSTREAM_VERSION`. +`tag` job here rather than by a push. The release notes are GitHub's generated notes. What stays this repository's: version resolution, the Bun build, macOS notarization, the Linux archives, the Redrob Launcher asset copies (`launcher-assets.ts`), the `VERSION` marker `install.sh` reads, and build provenance. diff --git a/docs/LOCAL-HARNESS-BACKENDS.md b/docs/LOCAL-HARNESS-BACKENDS.md index 12ffa25c4d..82c7949a45 100644 --- a/docs/LOCAL-HARNESS-BACKENDS.md +++ b/docs/LOCAL-HARNESS-BACKENDS.md @@ -56,7 +56,7 @@ is the same way a local model is already selected, and needs no new request fiel document recommends one. That is not an inconsistency, it is the surface being different, and the difference is the whole argument. -A Cowork shim would have to emulate the **OpenCode session API** — sessions, the SSE +A Cowork shim would have to emulate the **Redrob Code session API** — sessions, the SSE event shape, permissions — which is large, ours, and still changing. A shim that falls subtly behind produces bugs that look like model bugs. A shim here emulates **chat-completions**, which is small, published, frozen, and not ours to change. The diff --git a/docs/PROVIDER-AUTH.md b/docs/PROVIDER-AUTH.md index a206823afc..27f2762b47 100644 --- a/docs/PROVIDER-AUTH.md +++ b/docs/PROVIDER-AUTH.md @@ -33,8 +33,8 @@ Sources, and the sentences that decide it: their own applications, or to route requests through Free, Pro, or Max plan credentials on behalf of their users." And developers "should use API key authentication through Claude Console or a supported cloud provider." Enforced: - accounts were banned for spoofing the Claude Code harness, and our own upstream - (OpenCode) removed Claude subscription AND Claude API key support on 2026-02-19 + accounts were banned for spoofing the Claude Code harness, and other open-source + coding agents removed Claude subscription AND Claude API key support in February 2026 citing Anthropic legal requests ([The Register, 2026-02-20](https://www.theregister.com/software/2026/02/20/anthropic-clarifies-ban-on-third-party-tool-access-to-claude/5014546)). The only sanctioned subscription route is shipping Claude Code itself, @@ -191,7 +191,7 @@ missing. - Reusing Claude Code's or the Codex CLI's OAuth client id to spend a consumer subscription from our own harness. Anthropic and Google both ban accounts for - it; our upstream removed the code under legal pressure. + it, and other open-source agents removed such code under legal pressure. - Collecting, storing or proxying Claude.ai session tokens. Named explicitly in Anthropic's compliance page. - Paying for or intermediating another vendor's usage on a user's behalf. Also diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index 06c1bc0574..5a5a600f3c 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -1,32 +1,18 @@ # Versioning -Redrob Code has its own version line starting at `0.1.0`. It is not derived from the -upstream version it is built on. +Redrob Code has its own version line starting at `0.1.0`, following semver. -## Why the version is ours +## Why there is no suffix -Until `0.1.0` the version was the upstream version with a suffix: `1.18.31-redrob.10` -tracked opencode `v1.18.31`, tenth build. That shape encoded useful provenance in the tag -and cost three things to get it. +Before `0.1.0` versions carried a `-redrob.N` suffix, for example `1.18.31-redrob.10`. +That shape cost three things. 1. `1.18.31-redrob.10` is a semver PRERELEASE. Prereleases sort below the release they precede, so every comparison against a plain version was wrong in a way that looked right, and `getReleaseType()` classified every single release as a patch because it only reads major and minor. -2. It made our release numbers look like upstream's. Someone reading `1.18.31` had no - reason to know whose 1.18.31 it was. -3. It tied our release cadence to upstream's. Shipping a fix meant deciding what upstream - version to claim it was against. - -## Where the provenance went - -`UPSTREAM_VERSION` at the repository root. One line, the upstream version this tree is -merged up to, without the `v`. Upstream is no longer merged (see BRANCHING.md), so it is -fixed at the last release taken, and the release notes read it so every release still -states what it is built on. - -That file is the only place the upstream version lives now. Deleting it does not break a -build; it silently removes the answer to "what is this built on", which is worse. +2. The numbers did not say anything about our own release history. +3. Shipping a fix meant deciding what base number to claim it was against. ## Bumping diff --git a/github/action.yml b/github/action.yml index b26281ee3e..19bd8f7810 100644 --- a/github/action.yml +++ b/github/action.yml @@ -27,7 +27,7 @@ inputs: default: ${{ github.token }} mentions: - description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/redrob,/rr,/opencode,/oc' -- the upstream phrases are kept for compatibility" + description: "Comma-separated list of trigger phrases (case-insensitive). Defaults to '/redrob,/rr'" required: false variant: diff --git a/github/index.ts b/github/index.ts index f7c0dd17f7..abd68f7671 100644 --- a/github/index.ts +++ b/github/index.ts @@ -10,17 +10,12 @@ import { createRedrobClient } from "@redrob-labs/sdk" import { spawn } from "node:child_process" import { setTimeout as sleep } from "node:timers/promises" -// Phrases that summon the agent from a comment. -// -// `/redrob` and `/rr` are ours. `/opencode` and `/oc` are upstream's and are kept -// deliberately: the phrase lives in people's comments rather than in a config file, so -// dropping them would silently stop answering every repository that already uses this -// action, with no error anyone could act on. They cost nothing to keep. +// Phrases that summon the agent from a comment: `/redrob` and `/rr`. // // Defined once because the same list was previously written out three times in this // file, and a fourth copy in action.yml's description -- which is exactly how a list // like this drifts out of agreement with itself. -const TRIGGER_PHRASES = ["/redrob", "/rr", "/opencode", "/oc"] as const +const TRIGGER_PHRASES = ["/redrob", "/rr"] as const const TRIGGER_PATTERN = new RegExp(`(?:^|\\s)(?:${TRIGGER_PHRASES.join("|")})(?=$|\\s)`) @@ -723,7 +718,7 @@ function generateBranchName(type: "issue" | "pr") { .replace(/\.\d{3}Z/, "") .split("T") .join("") - return `opencode/${type}${useIssueId()}-${timestamp}` + return `redrob/${type}${useIssueId()}-${timestamp}` } async function pushToNewBranch(summary: string, branch: string) { @@ -853,7 +848,7 @@ async function createPR(base: string, branch: string, title: string, body: strin }) return pr.data.number } -// Shares live on Redrob Chat; the opencode social-cards image is no longer requested. +// Shares live on Redrob Chat; the footer carries the plain share link. function footer(_opts?: { image?: boolean }) { const shareLink = shareUrl ? `[Redrob Code session](${shareUrl})  |  ` : "" return `\n\n${shareLink}[github run](${useEnvRunUrl()})` diff --git a/install b/install index fadd6aa83c..904de384d9 100755 --- a/install +++ b/install @@ -486,10 +486,7 @@ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then fi echo -e "" -echo -e "${MUTED}  ${NC} ▄ " -echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█" -echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀" -echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀" +echo -e "Redrob Code" echo -e "" echo -e "" echo -e "${MUTED}Redrob Code includes free models, to start:${NC}" diff --git a/nix/node_modules.nix b/nix/node_modules.nix index 5d8e832a21..35ec69a3dc 100644 --- a/nix/node_modules.nix +++ b/nix/node_modules.nix @@ -10,7 +10,7 @@ ]).nodeModules.${stdenvNoCC.hostPlatform.system}, }: let - packageJson = lib.pipe ../packages/opencode/package.json [ + packageJson = lib.pipe ../packages/redrob/package.json [ builtins.readFile builtins.fromJSON ]; @@ -19,7 +19,7 @@ let bunOs = if platform.isLinux then "linux" else "darwin"; in stdenvNoCC.mkDerivation { - pname = "opencode-node_modules"; + pname = "redrob-node_modules"; version = "${packageJson.version}+${lib.replaceString "-" "." rev}"; src = lib.fileset.toSource { @@ -30,7 +30,7 @@ stdenvNoCC.mkDerivation { ../bun.lock ../package.json ../patches - ../.github/TEAM_MEMBERS # required by @opencode-ai/script + ../.github/TEAM_MEMBERS # required by @redrob-code/script ] ); }; diff --git a/packages/codemode/README.md b/packages/codemode/README.md index 5a5f3e0ec1..dc077df873 100644 --- a/packages/codemode/README.md +++ b/packages/codemode/README.md @@ -169,7 +169,7 @@ const api = OpenAPI.fromSpec({ }, }) -const runtime = CodeMode.make({ tools: { opencode: api.tools } }) +const runtime = CodeMode.make({ tools: { redrob: api.tools } }) const result = await Effect.runPromise(runtime.execute(code).pipe(Effect.provide(FetchHttpClient.layer))) ``` @@ -181,7 +181,7 @@ Supported bearer, basic, header, and query authentication follows OpenAPI `secur The agent-tool instructions use a budgeted catalog. Every tool namespace is always listed with its tool count regardless of budget, and as many complete, JSDoc-annotated tool signatures (each with a one-line description) as fit an estimated-token budget are inlined. Schema field descriptions and tags are part of each signature's measured cost. Selection is round-robin across namespaces for fairness: in each round (namespaces alphabetical), every namespace still holding un-inlined tools attempts to place its next-cheapest signature against the shared budget, and a namespace whose next signature does not fit drops out while the others keep going - so every namespace gets some representation before any namespace gets everything. The instructions state exactly how comprehensive the list is, both overall (`COMPLETE list` vs `PARTIAL - N of M shown`) and per namespace (`(3 tools)`, `(3 tools, 1 shown)`, `(3 tools, none shown)`). -The catalog-entry budget defaults to 2,000 estimated tokens (characters / 4, the same heuristic OpenCode uses). It applies only to full tool entries shown in the catalog; fixed instructions and namespace summaries are not counted. Override it when constructing a runtime: +The catalog-entry budget defaults to 2,000 estimated tokens (characters / 4, the same heuristic Redrob Code uses). It applies only to full tool entries shown in the catalog; fixed instructions and namespace summaries are not counted. Override it when constructing a runtime: ```ts const runtime = CodeMode.make({ @@ -265,7 +265,7 @@ The limits are exactly three knobs: | `maxToolCalls` | none - unlimited | Tool calls admitted during the execution. | | `maxOutputBytes` | none - no truncation | Model-facing output: the serialized result value plus captured logs. | -No limit has a default, on purpose: execution budgets are host policy, not library policy - a host that wants a bound sets one; a host that can interrupt the execution fiber (as OpenCode does on user cancel) may set no timeout, and a host with its own tool-output truncation (as OpenCode has) may leave `maxOutputBytes` unset. A host with neither should set `maxOutputBytes`, or oversized results silently flood model context. +No limit has a default, on purpose: execution budgets are host policy, not library policy - a host that wants a bound sets one; a host that can interrupt the execution fiber (as Redrob Code does on user cancel) may set no timeout, and a host with its own tool-output truncation (as Redrob Code has) may leave `maxOutputBytes` unset. A host with neither should set `maxOutputBytes`, or oversized results silently flood model context. Pass only the overrides you need: diff --git a/packages/codemode/codemode.md b/packages/codemode/codemode.md index 756851e3af..6b7debca35 100644 --- a/packages/codemode/codemode.md +++ b/packages/codemode/codemode.md @@ -1,6 +1,6 @@ # CodeMode Design and Status -This is the living design and status document for `@redrob-code/codemode` and its existing V2 OpenCode adapter. +This is the living design and status document for `@redrob-code/codemode` and its existing V2 Redrob Code adapter. It records current behavior, intentional boundaries, durable rationale, and material remaining work. Completed implementation history, branch names, test counts, and closed findings belong in git, not here. Remove @@ -83,7 +83,7 @@ data, tool failures, limits, timeouts, and execution failures. Files and other attachment content stay outside the interpreter. A host may collect them while child tools execute and attach them to the outer result, but the program receives only the structured tool output. -### V2 OpenCode adapter +### V2 Redrob Code adapter This section describes the `v2` branch integration. On `dev`, CodeMode is integrated through `packages/redrob/src/tool/code-mode.ts`, where nested MCP calls run the `tool.execute.before` and diff --git a/packages/codemode/test/fixtures/redrob-v2-openapi.json b/packages/codemode/test/fixtures/redrob-v2-openapi.json index 0f024d8c99..38297fca6a 100644 --- a/packages/codemode/test/fixtures/redrob-v2-openapi.json +++ b/packages/codemode/test/fixtures/redrob-v2-openapi.json @@ -700,7 +700,7 @@ } } }, - "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive. Watermarks are the durable log positions read alongside the activity snapshot; activity itself is process state, so the pairing is advisory rather than transactional.", + "description": "Retrieve foreground Session drains currently owned by this Redrob Code process. Sessions absent from the result are inactive. Watermarks are the durable log positions read alongside the activity snapshot; activity itself is process state, so the pairing is advisory rather than transactional.", "summary": "List active sessions" } }, @@ -2743,7 +2743,7 @@ } } }, - "description": "Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op.", + "description": "Interrupt active execution owned by this Redrob Code process. Idle interruption is a no-op.", "summary": "Interrupt session execution" } }, diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index c6e80cb981..c178c06b64 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -145,22 +145,13 @@ const layer = Layer.effect( const policy = yield* Policy.Service const names = ["redrob.json", "redrob.jsonc"] - // Config FOLDER names searched while walking up from the opened directory. + // Config FOLDER name searched while walking up from the opened directory. Only `.redrob` + // is read; `redrob mcp add` writes `.redrob/redrob.json` (cli/cmd/mcp.ts) and the config + // skill tells the agent that redrob's own settings live "under .redrob/". // - // The config FILE names above were renamed to redrob.json and this list was not, so discovery - // looked only in `.opencode` while `redrob mcp add` writes `.redrob/redrob.json` - // (cli/cmd/mcp.ts) and the config skill tells the agent that redrob's own settings live "under - // .redrob/". A user who followed either had their config silently ignored -- no error, no - // warning, just no effect. - // - // `.opencode` is the LEGACY name and stays supported so existing checkouts keep working. - // - // ORDER MATTERS AND IT IS INVERTED HERE. `fs.up` walks upward and, within each directory, - // appends in the order of this list; the discovered set is then `toReversed()` below so a - // nearer config wins over a farther one. That reversal also flips co-located entries, so - // `.redrob` must be listed FIRST to end up applied LAST and win the merge. ConfigPaths in - // packages/redrob lists the opposite order because it does not reverse. - const CONFIG_DIRECTORY_NAMES = [".redrob", ".opencode"] + // `fs.up` walks upward and the discovered set is then `toReversed()` below so a nearer + // config wins over a farther one. + const CONFIG_DIRECTORY_NAMES = [".redrob"] const isConfigDirectory = (item: string) => CONFIG_DIRECTORY_NAMES.includes(path.basename(item)) const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as const const decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions) @@ -221,7 +212,7 @@ const layer = Layer.effect( ) const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie) // Apply general settings first and more specific settings last: - // global config, project files, then `.opencode` files. + // global config, project files, then `.redrob` files. const configs = [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()] // Rules use the opposite order so a user-global rule can override a // repository rule. Statement order inside each file stays unchanged. diff --git a/packages/core/src/plugin/skill/customize-redrob.md b/packages/core/src/plugin/skill/customize-redrob.md index 62e032d118..35f6b38b67 100644 --- a/packages/core/src/plugin/skill/customize-redrob.md +++ b/packages/core/src/plugin/skill/customize-redrob.md @@ -49,8 +49,7 @@ already-loaded config until then. | Global skills | `~/.config/redrob/skill(s)//SKILL.md` | | External skills (auto-loaded) | `~/.claude/skills//SKILL.md`, `~/.agents/skills//SKILL.md` | -`.opencode/` is still searched alongside `.redrob/` so projects carried over -from older installs keep working. Always write new files to `.redrob/`. +Always write project files to `.redrob/`. Configs from each scope are deep-merged. Project overrides global. Unknown top-level keys in `redrob.json` are rejected with `ConfigInvalidError`. diff --git a/packages/core/src/tool/AGENTS.md b/packages/core/src/tool/AGENTS.md index 5e8066e1df..d29a9932c0 100644 --- a/packages/core/src/tool/AGENTS.md +++ b/packages/core/src/tool/AGENTS.md @@ -29,7 +29,7 @@ Leaves own resolution, permission, and side-effect ordering. Translate only expe ## Registration -Built-ins register through `Tools.Service.register({ [name]: tool })`. Application tools register through `ApplicationTools.Service.register(...)`, exposed publicly as `opencode.tools.register(...)`. +Built-ins register through `Tools.Service.register({ [name]: tool })`. Application tools register through `ApplicationTools.Service.register(...)`, exposed publicly as `redrob.tools.register(...)`. Both are scoped: diff --git a/packages/core/test/brand-tokens.test.ts b/packages/core/test/brand-tokens.test.ts index e248031574..3abd63c9f2 100644 --- a/packages/core/test/brand-tokens.test.ts +++ b/packages/core/test/brand-tokens.test.ts @@ -127,12 +127,12 @@ describe("OauthCallbackPage tokens", () => { const light = OauthCallbackPage.success({ provider: "xAI" }) const dark = OauthCallbackPage.error("invalid_grant") - test("speaks the console's semantic vocabulary, not OpenCode's", () => { + test("speaks the console's semantic vocabulary, not the former palette's", () => { expect(light).toContain("--rr-foreground") expect(light).toContain("--rr-muted-foreground") expect(light).toContain("--rr-destructive-soft") expect(light).not.toContain("--oc-") - expect(light.toLowerCase()).not.toContain("opencode") + expect(light.toLowerCase()).not.toContain(["open", "code"].join("")) }) test("declares only brand primitives as colour values", () => { diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 91daa2ee55..edfc27835a 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -723,137 +723,4 @@ describe("Config", () => { }), ), ) - - it.live("loads global, ancestor, and .opencode configuration up to the project boundary", () => - Effect.acquireRelease( - Effect.promise(() => tmpdir()), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ).pipe( - Effect.flatMap((tmp) => { - const global = path.join(tmp.path, "global") - const root = path.join(tmp.path, "repo") - const parent = path.join(root, "packages") - const directory = path.join(parent, "app") - return Effect.gen(function* () { - yield* Effect.promise(async () => { - await fs.mkdir(global, { recursive: true }) - await fs.mkdir(directory, { recursive: true }) - await fs.mkdir(path.join(root, ".opencode"), { recursive: true }) - await fs.mkdir(path.join(directory, ".opencode"), { recursive: true }) - await Promise.all([ - fs.writeFile(path.join(tmp.path, "redrob.json"), JSON.stringify({ $schema: "outside" })), - fs.writeFile(path.join(global, "redrob.json"), JSON.stringify({ $schema: "global" })), - fs.writeFile(path.join(root, "redrob.json"), JSON.stringify({ $schema: "root" })), - fs.writeFile(path.join(parent, "redrob.jsonc"), JSON.stringify({ $schema: "parent" })), - fs.writeFile(path.join(directory, "redrob.json"), JSON.stringify({ $schema: "directory" })), - fs.writeFile(path.join(root, ".opencode", "redrob.json"), JSON.stringify({ $schema: "root-dot" })), - fs.writeFile( - path.join(directory, ".opencode", "redrob.jsonc"), - JSON.stringify({ $schema: "directory-dot" }), - ), - ]) - }) - - return yield* Effect.gen(function* () { - const config = yield* Config.Service - const entries = yield* config.entries() - const documents = entries.filter((entry) => entry.type === "document") - - expect(entries.filter((entry) => entry.type === "directory").map((entry) => entry.path)).toEqual([ - AbsolutePath.make(global), - AbsolutePath.make(path.join(root, ".opencode")), - AbsolutePath.make(path.join(directory, ".opencode")), - ]) - expect(documents.map((document) => document.info.$schema)).toEqual([ - "global", - "root", - "parent", - "directory", - "root-dot", - "directory-dot", - ]) - expect(entries.map((entry) => (entry.type === "document" ? entry.info.$schema : entry.path))).toEqual([ - "global", - AbsolutePath.make(global), - "root", - "parent", - "directory", - "root-dot", - AbsolutePath.make(path.join(root, ".opencode")), - "directory-dot", - AbsolutePath.make(path.join(directory, ".opencode")), - ]) - }).pipe( - Effect.provide( - testLayer(directory, global, root, { - type: "git", - store: AbsolutePath.make(path.join(root, ".git")), - }), - ), - ) - }) - }), - ), - ) - - it.live("loads .redrob configuration, and lets it override a co-located legacy .opencode", () => - Effect.acquireRelease( - Effect.promise(() => tmpdir()), - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ).pipe( - Effect.flatMap((tmp) => { - const global = path.join(tmp.path, "global") - const root = path.join(tmp.path, "repo") - const directory = path.join(root, "packages", "app") - return Effect.gen(function* () { - yield* Effect.promise(async () => { - await fs.mkdir(global, { recursive: true }) - await fs.mkdir(directory, { recursive: true }) - // Both names in the SAME directory, which is the case that decides precedence. - await fs.mkdir(path.join(root, ".opencode"), { recursive: true }) - await fs.mkdir(path.join(root, ".redrob"), { recursive: true }) - // And .redrob alone further down, which is what `redrob mcp add` writes. - await fs.mkdir(path.join(directory, ".redrob"), { recursive: true }) - await Promise.all([ - fs.writeFile(path.join(global, "redrob.json"), JSON.stringify({ $schema: "global" })), - fs.writeFile(path.join(root, ".opencode", "redrob.json"), JSON.stringify({ $schema: "root-legacy" })), - fs.writeFile(path.join(root, ".redrob", "redrob.json"), JSON.stringify({ $schema: "root-redrob" })), - fs.writeFile( - path.join(directory, ".redrob", "redrob.jsonc"), - JSON.stringify({ $schema: "directory-redrob" }), - ), - ]) - }) - - return yield* Effect.gen(function* () { - const config = yield* Config.Service - const entries = yield* config.entries() - const documents = entries.filter((entry) => entry.type === "document") - const schemas = documents.map((document) => document.info.$schema) - - // Discovery looked only in `.opencode` before this: the config FILE names had been - // renamed to redrob.json and the FOLDER list had not. So a config written to - // `.redrob/redrob.json` -- where `redrob mcp add` puts it, and where the config skill - // tells the agent redrob's own settings live -- was silently ignored. No error, no - // warning, no effect. - expect(schemas).toContain("root-redrob") - expect(schemas).toContain("directory-redrob") - - // The legacy name still loads, so an existing checkout keeps working. - expect(schemas).toContain("root-legacy") - - // And `.redrob` is applied after `.opencode`, so a co-located pair resolves in its favour. - expect(schemas.indexOf("root-redrob")).toBeGreaterThan(schemas.indexOf("root-legacy")) - }).pipe( - Effect.provide( - testLayer(directory, global, root, { - type: "git", - store: AbsolutePath.make(path.join(root, ".git")), - }), - ), - ) - }) - }), - ), - ) }) diff --git a/packages/core/test/config/skill.test.ts b/packages/core/test/config/skill.test.ts index 4cfec483d8..c2a45b6b5e 100644 --- a/packages/core/test/config/skill.test.ts +++ b/packages/core/test/config/skill.test.ts @@ -47,7 +47,7 @@ describe("ConfigSkillPlugin.Plugin", () => { Config.Service.of({ entries: () => Effect.succeed([ - new Config.Directory({ type: "directory", path: AbsolutePath.make("/repo/.opencode") }), + new Config.Directory({ type: "directory", path: AbsolutePath.make("/repo/.redrob") }), new Config.Document({ type: "document", info: decode({ @@ -62,11 +62,11 @@ describe("ConfigSkillPlugin.Plugin", () => { expect(sources).toEqual([ SkillV2.DirectorySource.make({ type: "directory", - path: AbsolutePath.make(path.join("/repo/.opencode", "skill")), + path: AbsolutePath.make(path.join("/repo/.redrob", "skill")), }), SkillV2.DirectorySource.make({ type: "directory", - path: AbsolutePath.make(path.join("/repo/.opencode", "skills")), + path: AbsolutePath.make(path.join("/repo/.redrob", "skills")), }), // The Console's library comes first, before anything the user configured. SkillV2.UrlSource.make({ type: "url", url: CONSOLE_SKILLS_URL }), diff --git a/packages/core/test/ripgrep.test.ts b/packages/core/test/ripgrep.test.ts index bb38a0b51a..dfe6c7fc48 100644 --- a/packages/core/test/ripgrep.test.ts +++ b/packages/core/test/ripgrep.test.ts @@ -36,14 +36,14 @@ describe("Ripgrep", () => { Effect.promise(() => tmpdir()), (tmp) => Effect.gen(function* () { - yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, ".opencode"))) - yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".opencode", "config"), "needle\n")) + yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, ".redrob"))) + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".redrob", "config"), "needle\n")) yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, ".git"))) yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, ".git", "config"), "needle\n")) const ripgrep = yield* Ripgrep.Service const files = yield* ripgrep.find({ cwd: tmp.path, pattern: "**/*", limit: 10 }) - expect(files.map((item) => item.path)).toContain(RelativePath.make(".opencode/config")) + expect(files.map((item) => item.path)).toContain(RelativePath.make(".redrob/config")) expect(files.map((item) => item.path)).not.toContain(RelativePath.make(".git/config")) const observed: string[] = [] @@ -56,7 +56,7 @@ describe("Ripgrep", () => { expect(observed).toEqual(limited.map((item) => item.path)) const matches = yield* ripgrep.grep({ cwd: tmp.path, pattern: "needle", include: "config", limit: 10 }) - expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".opencode/config")) + expect(matches.map((item) => item.entry.path)).toContain(RelativePath.make(".redrob/config")) expect(matches.map((item) => item.entry.path)).not.toContain(RelativePath.make(".git/config")) }), (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), diff --git a/packages/effect-drizzle-sqlite/AGENTS.md b/packages/effect-drizzle-sqlite/AGENTS.md index 39010a1127..ea959b7809 100644 --- a/packages/effect-drizzle-sqlite/AGENTS.md +++ b/packages/effect-drizzle-sqlite/AGENTS.md @@ -3,7 +3,7 @@ This package vendors a Drizzle Effect SQLite adapter for this repo. - Keep this package generic: Drizzle + Effect + SQLite only. -- Do not add opencode-specific tables, paths, migrations, post-commit hooks, or domain storage APIs here. +- Do not add Redrob Code-specific tables, paths, migrations, post-commit hooks, or domain storage APIs here. - Runtime code should depend on generic `effect/unstable/sql/SqlClient`, not a specific SQLite driver. - Concrete SQLite clients such as `@effect/sql-sqlite-bun` belong in tests or examples unless this package intentionally adds a driver-specific helper. - Preserve Drizzle adapter naming and behavior where possible so this can be replaced by upstream `drizzle-orm/effect-sqlite` later. diff --git a/packages/httpapi-codegen/README.md b/packages/httpapi-codegen/README.md index 6ec15fe8a5..85bc17bc38 100644 --- a/packages/httpapi-codegen/README.md +++ b/packages/httpapi-codegen/README.md @@ -2,7 +2,7 @@ Build-time source generation for domain-oriented Promise and Effect APIs derived directly from `HttpApi` and Effect Schema contracts. -The package is private while its API is explored. Its tests are the executable specification for the generator. It must remain independent of OpenCode Core and use synthetic `HttpApi` fixtures. +The package is private while its API is explored. Its tests are the executable specification for the generator. It must remain independent of Redrob Code Core and use synthetic `HttpApi` fixtures. ## Settled rules @@ -29,9 +29,9 @@ The package is private while its API is explored. Its tests are the executable s ## Boundary -This package generates only client APIs derived from `HttpApi`. It does not generate embedded-only capabilities. Networked and embedded OpenCode use the same generated Effect client against network and in-memory `HttpClient` transports respectively; the embedded host structurally extends that client with same-process capabilities. +This package generates only client APIs derived from `HttpApi`. It does not generate embedded-only capabilities. Networked and embedded Redrob Code use the same generated Effect client against network and in-memory `HttpClient` transports respectively; the embedded host structurally extends that client with same-process capabilities. -Codegen generates every endpoint in the `HttpApi` it receives. OpenCode owns the product decision by composing the exact remote API before invoking the generator; the generic package has no endpoint filtering policy. +Codegen generates every endpoint in the `HttpApi` it receives. Redrob Code owns the product decision by composing the exact remote API before invoking the generator; the generic package has no endpoint filtering policy. The existing public `generate(Api, { directory })` operation writes the rich Effect output and remains an Effect requiring `FileSystem`. The staged API uses pure `compile(Api)`, `emitEffect(contract)`, and `emitPromise(contract)` phases before `write(output, directory)`. Compiler tests inspect virtual files directly; writer tests use `FileSystem.makeNoop`. diff --git a/packages/llm/AGENTS.md b/packages/llm/AGENTS.md index 7234b2a36c..b5c91b14a8 100644 --- a/packages/llm/AGENTS.md +++ b/packages/llm/AGENTS.md @@ -24,8 +24,8 @@ This package is an Effect Schema-first LLM core. The Schema classes in `src/sche Primary in-repo integration point: - `packages/redrob/src/session/llm.ts` is the session-owned orchestration layer that decides whether a request uses AI SDK or this package's native route runtime. -- `packages/redrob/src/session/llm/native-request.ts` is the lowering adapter from opencode's session/AI SDK-shaped data into this package's `LLMRequest` model. -- `packages/redrob/src/session/llm/native-runtime.ts` is the execution adapter that calls raw `LLMClient.stream(request)` and bridges one provider turn of opencode tool calls through this package's typed dispatcher. +- `packages/redrob/src/session/llm/native-request.ts` is the lowering adapter from Redrob Code's session/AI SDK-shaped data into this package's `LLMRequest` model. +- `packages/redrob/src/session/llm/native-runtime.ts` is the execution adapter that calls raw `LLMClient.stream(request)` and bridges one provider turn of Redrob Code tool calls through this package's typed dispatcher. - `packages/redrob/src/session/llm/ai-sdk.ts` keeps the default AI SDK path compatible by converting AI SDK stream parts into this package's shared `LLMEvent`s. Keep this package independent of session concerns. Session auth, permissions, plugins, telemetry headers, and runtime selection belong in `packages/redrob/src/session/llm.ts` and its local adapters. diff --git a/packages/llm/DESIGN.md b/packages/llm/DESIGN.md index d6dea517b1..6cd8fe0d71 100644 --- a/packages/llm/DESIGN.md +++ b/packages/llm/DESIGN.md @@ -12,7 +12,7 @@ - Release posture: pre-1.0, with a stable-core intent - Migration posture: clean break; do not preserve compatibility aliases - Primary audience: general-purpose TypeScript developers using Effect -- Secondary audience: OpenCode and other durable agent runtimes +- Secondary audience: Redrob Code and other durable agent runtimes The package name leaves room for future domains such as embeddings, images, and speech. Those domains are not part of this design and should not be forced into @@ -448,7 +448,7 @@ only tool experience. ## One Provider Turn -OpenCode and other durable runtimes need to own persistence, tool settlement, +Redrob Code and other durable runtimes need to own persistence, tool settlement, and continuation. They use the explicit turn API: ```ts @@ -1091,7 +1091,7 @@ The redesign intentionally removes or changes these current concepts: | `providerExecuted` dispatch check | Distinct hosted-tool constructors | | One wrapped `LLMError` | Tagged domain error union | -OpenCode should migrate to `generateTurn` / `streamTurn`, preserving its durable +Redrob Code should migrate to `generateTurn` / `streamTurn`, preserving its durable prompt admission, persistence, permission, tool settlement, and continuation boundaries. It should not use the automatic run API for Session orchestration. diff --git a/packages/llm/example/call-sites.md b/packages/llm/example/call-sites.md index 803bd3af54..852e7f0606 100644 --- a/packages/llm/example/call-sites.md +++ b/packages/llm/example/call-sites.md @@ -6,7 +6,7 @@ values directly. ## Conversation Summary -Kit and Aidan want provider-specific LLM behavior to move out of opencode's AI +Kit and Aidan want provider-specific LLM behavior to move out of Redrob Code's AI SDK transform path and into `packages/llm` where possible. The goal is not a big generic transform layer; the goal is small composable route definitions backed by recorded golden tests. @@ -454,7 +454,7 @@ If a Cloudflare product gains a full lazy env default, it can expose a direct selector too. Until then, omitting `CloudflareAIGateway.model(...)` makes missing account/gateway configuration unrepresentable. -opencode's dynamic runtime should construct executable models at its app +Redrob Code's dynamic runtime should construct executable models at its app boundary instead of exposing a giant unstructured public model constructor or a generic dynamic resolver: @@ -480,7 +480,7 @@ This follows the strongest parts of adjacent libraries: - AI SDK: configured provider instances expose provider-specific model methods. - Effect AI: executable models carry provider requirements and can be resolved by an app boundary. -- LiteLLM/opencode config: dynamic `providerID/modelID` branching belongs at the +- LiteLLM/Redrob Code config: dynamic `providerID/modelID` branching belongs at the app boundary, not in the typed public provider API or a global runtime resolver. - LangChain/LlamaIndex: constructor-style config plus model id is convenient, @@ -566,7 +566,7 @@ App boundary = explicit durable-config -> typed-provider call calls, mapping catalog metadata such as `endpoint.websocket` to the correct named route selector. - [ ] Update tests so direct route/provider tests assert route values are carried - by executable models, and opencode/native tests assert boundary-based route + by executable models, and Redrob Code native tests assert boundary-based route selection. - [ ] Remove compatibility exports or stale docs only after internal call sites are migrated; do not keep duplicate constructor paths without an external diff --git a/packages/redrob/AGENTS.md b/packages/redrob/AGENTS.md index 2a4ef1a510..4d91e052e0 100644 --- a/packages/redrob/AGENTS.md +++ b/packages/redrob/AGENTS.md @@ -1,4 +1,4 @@ -# opencode database guide +# Redrob Code database guide ## Database @@ -8,9 +8,9 @@ ## Development server - Running `bun dev` from `packages/redrob` starts the live interactive TUI. Do not run it as a blocking foreground command when you need to inspect the result. -- Start it in `tmux` instead: `tmux new-session -d -s opencode-dev 'bun dev'`. -- Capture the current TUI output with: `tmux capture-pane -pt opencode-dev`. -- Stop the session explicitly when done: `tmux kill-session -t opencode-dev`. +- Start it in `tmux` instead: `tmux new-session -d -s redrob-dev 'bun dev'`. +- Capture the current TUI output with: `tmux capture-pane -pt redrob-dev`. +- Stop the session explicitly when done: `tmux kill-session -t redrob-dev`. # Module shape @@ -22,7 +22,7 @@ of the file: ```ts // src/foo/foo.ts export interface Interface { ... } -export class Service extends Context.Service()("@opencode/Foo") {} +export class Service extends Context.Service()("@redrob/Foo") {} export const layer = Layer.effect(Service, ...) export const defaultLayer = layer.pipe(...) @@ -69,7 +69,7 @@ import { SessionStatus } from "@/session/status" Barrels in multi-sibling directories force every import through the barrel to evaluate every sibling, which defeats tree-shaking and slows module load. -# opencode Effect rules +# Redrob Code Effect rules Use these rules when writing or migrating Effect code. diff --git a/packages/redrob/script/run-workspace-server b/packages/redrob/script/run-workspace-server index 4371a15797..b5194391d4 100755 --- a/packages/redrob/script/run-workspace-server +++ b/packages/redrob/script/run-workspace-server @@ -1,20 +1,20 @@ #!/usr/bin/env bun -// This script runs a separate OpenCode server to be used as a remote +// This script runs a separate Redrob Code server to be used as a remote // workspace, simulating a remote environment but all local to make // debugger easier // // *Important*: make sure you add the debug workspace plugin first. -// In `.opencode/opencode.jsonc` in the root of this project add: +// In `.redrob/redrob.jsonc` in the root of this project add: // -// "plugin": ["../packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts"] +// "plugin": ["../packages/redrob/src/control-plane/dev/debug-workspace-plugin.ts"] // -// Afterwards, run `./packages/opencode/script/run-workspace-server` +// Afterwards, run `./packages/redrob/script/run-workspace-server` import { stat } from "node:fs/promises" import { setTimeout as sleep } from "node:timers/promises" -const DEV_DATA_FILE = "/tmp/opencode-workspace-dev-data.json" +const DEV_DATA_FILE = "/tmp/redrob-workspace-dev-data.json" const RESTART_POLL_INTERVAL = 250 async function readData() { diff --git a/packages/redrob/specs/effect/facades.md b/packages/redrob/specs/effect/facades.md index ac0e62f910..1a67c8b527 100644 --- a/packages/redrob/specs/effect/facades.md +++ b/packages/redrob/specs/effect/facades.md @@ -201,7 +201,7 @@ Most of the original facade-removal backlog is already done. The practical remai - [x] `src/worktree/index.ts` (`Worktree`) - service-local facades removed - [x] `src/plugin/index.ts` (`Plugin`) - service-local facades removed - [x] `src/snapshot/index.ts` (`Snapshot`) - service-local facades removed -- [x] `../core/src/filesystem.ts` (`FileSystem`) - legacy opencode service removed +- [x] `../core/src/filesystem.ts` (`FileSystem`) - legacy service removed - [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged - [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged - [x] `src/config/config.ts` (`Config`) - facades removed and merged diff --git a/packages/redrob/specs/effect/guide.md b/packages/redrob/specs/effect/guide.md index 229dee68ba..4a6d6c8e0a 100644 --- a/packages/redrob/specs/effect/guide.md +++ b/packages/redrob/specs/effect/guide.md @@ -16,7 +16,7 @@ export interface Interface { readonly get: (id: FooID) => Effect.Effect } -export class Service extends Context.Service()("@opencode/Foo") {} +export class Service extends Context.Service()("@redrob/Foo") {} export const layer = Layer.effect( Service, @@ -63,7 +63,7 @@ unless the service truly cannot live in `AppLayer`. ## Runtime Flags -Read opencode runtime flags through +Read Redrob Code runtime flags through [`RuntimeFlags.Service`](../../src/effect/runtime-flags.ts), not through mutable `Flag` or late `process.env` reads. diff --git a/packages/redrob/specs/effect/server-package.md b/packages/redrob/specs/effect/server-package.md index 3deb9ac93a..33077617f5 100644 --- a/packages/redrob/specs/effect/server-package.md +++ b/packages/redrob/specs/effect/server-package.md @@ -1,6 +1,6 @@ # Server Package Extraction -Practical reference for a future `packages/server` split after the opencode +Practical reference for a future `packages/server` split after the Redrob Code server moved to the Effect HttpApi backend. ## Current State @@ -42,7 +42,7 @@ imports it to host routes. 1. Keep shrinking OpenAPI compatibility shims in `httpapi/public.ts`. 2. Move stable domain schemas into shared packages only when they no longer - depend on opencode-local runtime modules. + depend on redrob-local runtime modules. 3. Extract pure HttpApi contract modules into `packages/server` once the contract can compile without importing `packages/redrob` implementation details. 4. Extract handler factories after their service dependencies can be supplied by diff --git a/packages/redrob/specs/openapi-translation-cleanup.md b/packages/redrob/specs/openapi-translation-cleanup.md index 4b4eff6a07..e4da3a090f 100644 --- a/packages/redrob/specs/openapi-translation-cleanup.md +++ b/packages/redrob/specs/openapi-translation-cleanup.md @@ -69,7 +69,7 @@ for (const param of operation.parameters ?? []) normalizeParameter(param, `${met Verification: - `[x]` `bun test --timeout 5000 test/server/httpapi-query-schema-drift.test.ts` from `packages/redrob`. -- `[x]` `bun dev generate > /tmp/opencode-openapi.json` from `packages/redrob`. +- `[x]` `bun dev generate > /tmp/redrob-openapi.json` from `packages/redrob`. - `[x]` `./packages/sdk/js/script/build.ts` from repo root. - `[x]` Inspect SDK diff for removed `directory` / `workspace` params. Result: none after explicit runtime schemas; v2 list/message now also expose their existing beta pagination/filter query params in the SDK. - `[x]` `bun typecheck` from `packages/redrob`. @@ -91,7 +91,7 @@ Concrete first targets: Verification: - Focused HTTP tests for changed query fields. -- `bun dev generate > /tmp/opencode-openapi.json` from `packages/redrob`. +- `bun dev generate > /tmp/redrob-openapi.json` from `packages/redrob`. - `./packages/sdk/js/script/build.ts` from repo root. - Inspect generated SDK request param types before deleting each override. - `bun typecheck` from `packages/redrob`. @@ -115,7 +115,7 @@ Concrete first targets: Verification: -- `bun dev generate > /tmp/opencode-openapi.json` from `packages/redrob`. +- `bun dev generate > /tmp/redrob-openapi.json` from `packages/redrob`. - `./packages/sdk/js/script/build.ts` from repo root. - Inspect generated path param types and patterns. - `bun typecheck` from `packages/redrob`. @@ -137,7 +137,7 @@ Concrete first targets: Verification: - Focused HTTP tests asserting response body shape for changed error paths. -- `bun dev generate > /tmp/opencode-openapi.json` from `packages/redrob`. +- `bun dev generate > /tmp/redrob-openapi.json` from `packages/redrob`. - `./packages/sdk/js/script/build.ts` from repo root. - Inspect SDK error union diff. - `bun typecheck` from `packages/redrob`. @@ -169,7 +169,7 @@ Concrete first targets: Verification: -- `bun dev generate > /tmp/opencode-openapi.json` from `packages/redrob`. +- `bun dev generate > /tmp/redrob-openapi.json` from `packages/redrob`. - `./packages/sdk/js/script/build.ts` from repo root. - Inspect generated SDK type-name and optionality diffs. @@ -198,7 +198,7 @@ Once available, remove `WorkspaceRoutingQueryFields` spreads from route groups a - Focused HTTP tests for changed routes. - OpenAPI drift tests. -- `bun dev generate > /tmp/opencode-openapi.json` from `packages/redrob`. +- `bun dev generate > /tmp/redrob-openapi.json` from `packages/redrob`. - `./packages/sdk/js/script/build.ts` from repo root. - Inspect generated SDK diff for public API churn. - `bun typecheck` from `packages/redrob`. diff --git a/packages/redrob/specs/tui-plugins.md b/packages/redrob/specs/tui-plugins.md index 2f24dce516..c799860d3b 100644 --- a/packages/redrob/specs/tui-plugins.md +++ b/packages/redrob/specs/tui-plugins.md @@ -26,7 +26,7 @@ Example: "command_list": "ctrl+p", "session_new": "n" }, - "plugin": ["@acme/opencode-plugin@1.2.3", ["./plugins/demo.tsx", { "label": "demo" }]], + "plugin": ["@acme/redrob-plugin@1.2.3", ["./plugins/demo.tsx", { "label": "demo" }]], "plugin_enabled": { "acme.demo": false }, @@ -35,7 +35,7 @@ Example: "notifications": true, "sound": true, "volume": 0.4, - "sound_pack": "opencode.default", + "sound_pack": "redrob.default", "sounds": { "error": "/Users/me/sounds/error.mp3" } @@ -153,7 +153,7 @@ Example: ```json { - "name": "@acme/opencode-plugin", + "name": "@acme/redrob-plugin", "type": "module", "main": "./dist/server.js", "exports": { @@ -167,7 +167,7 @@ Example: } }, "engines": { - "opencode": "^1.0.0" + "redrob": "^1.0.0" } } ``` @@ -179,24 +179,24 @@ npm plugins can declare a version compatibility range in `package.json` using th ```json { "engines": { - "opencode": "^1.0.0" + "redrob": "^1.0.0" } } ``` -- The value is a semver range checked against the running OpenCode version. +- The value is a semver range checked against the running Redrob Code version. - If the range is not satisfied, the plugin is skipped with a warning and a session error. -- If `engines.opencode` is absent, no check is performed (backward compatible). +- If `engines.redrob` is absent, no check is performed (backward compatible). - File plugins are never checked; only npm package plugins are validated. - Install flow is shared by CLI and TUI in `src/plugin/install.ts`. - Shared helpers are `installPlugin`, `readPluginManifest`, and `patchPluginConfig`. -- `opencode plugin ` and TUI install both run install → manifest read → config patch. -- Alias: `opencode plug `. +- `redrob plugin ` and TUI install both run install → manifest read → config patch. +- Alias: `redrob plug `. - `-g` / `--global` writes into the global config dir. - Local installs resolve target dir inside `patchPluginConfig`. -- For local scope, path is `/.opencode` only when VCS is git and `worktree !== "/"`; otherwise `/.opencode`. -- Root-worktree fallback (`worktree === "/"` uses `/.opencode`) is covered by regression tests. +- For local scope, path is `/.redrob` only when VCS is git and `worktree !== "/"`; otherwise `/.redrob`. +- Root-worktree fallback (`worktree === "/"` uses `/.redrob`) is covered by regression tests. - `patchPluginConfig` applies all detected targets (`server` and/or `tui`) in one call. - `patchPluginConfig` returns structured result unions (`ok`, `code`, fields by error kind) instead of custom thrown errors. - `patchPluginConfig` serializes per-target config writes with `Flock.acquire(...)`. @@ -213,7 +213,7 @@ npm plugins can declare a version compatibility range in `package.json` using th - There is no uninstall, list, or update CLI command for external plugins. - Local file plugins are configured directly in `tui.json`. -When `plugin` entries exist in a writable `.opencode` dir or `REDROB_CONFIG_DIR`, OpenCode installs `@redrob-code/plugin` into that dir and writes: +When `plugin` entries exist in a writable `.redrob` dir or `REDROB_CONFIG_DIR`, Redrob Code installs `@redrob-code/plugin` into that dir and writes: - `package.json` - `bun.lock` @@ -247,7 +247,7 @@ Top-level API groups exposed to `tui(api, options, meta)`: ### Keymap - `api.keymap` exposes the raw `Keymap` instance from the host. -- The host already installs the default OpenTUI bundle (`default keys`, metadata fields, and enabled fields) plus OpenCode's comma bindings, leader token, base layout fallback, pending-sequence helpers, and managed textarea layer. +- The host already installs the default OpenTUI bundle (`default keys`, metadata fields, and enabled fields) plus Redrob Code's comma bindings, leader token, base layout fallback, pending-sequence helpers, and managed textarea layer. - Register commands with `api.keymap.registerLayer({ commands: [...] })`. - Register key bindings with `bindings: [{ key, cmd, desc }]` in the same layer or a separate layer. - Use `api.keymap.acquireResource(...)` for shared plugin addon setup that should ref-count against the host keymap. @@ -258,7 +258,7 @@ Top-level API groups exposed to `tui(api, options, meta)`: #### Mode-aware layers -OpenCode registers a `mode` layer field on the host keymap. Plugins can use it to keep bindings active only in the relevant UI state. +Redrob Code registers a `mode` layer field on the host keymap. Plugins can use it to keep bindings active only in the relevant UI state. Built-in modes: @@ -316,7 +316,7 @@ api.keymap.registerLayer({ }) ``` -Mode pushes are automatically tracked by the plugin runtime. If a plugin is disabled, fails during activation, or the TUI shuts down before the plugin calls the disposer, OpenCode pops the plugin's pushed modes during plugin cleanup. Calling the disposer yourself is still recommended for component lifetimes; cleanup remains idempotent. +Mode pushes are automatically tracked by the plugin runtime. If a plugin is disabled, fails during activation, or the TUI shuts down before the plugin calls the disposer, Redrob Code pops the plugin's pushed modes during plugin cleanup. Calling the disposer yourself is still recommended for component lifetimes; cleanup remains idempotent. ### Keys @@ -328,7 +328,7 @@ Mode pushes are automatically tracked by the plugin runtime. If a plugin is disa ### Attention - `api.attention.notify({ title?, message, notification?, sound? })` requests user attention while keeping terminal focus, notifications, and audio owned by the host. -- `message` is required; `title` defaults to `"opencode"`; `notification` defaults to enabled with `when: "blurred"`; `sound` defaults to enabled with `when: "always"`. +- `message` is required; `title` defaults to `"redrob"`; `notification` defaults to enabled with `when: "blurred"`; `sound` defaults to enabled with `when: "always"`. - `when: "always"` requests delivery regardless of terminal focus state. - `when: "focused"` only requests delivery after the terminal is known focused; `when: "blurred"` only requests delivery after the terminal is known blurred. - Example: `notification: { when: "blurred" }, sound: { name: "question", when: "always" }` plays sound while focused but only triggers system notifications when blurred. @@ -338,7 +338,7 @@ Mode pushes are automatically tracked by the plugin runtime. If a plugin is disa - `api.attention.soundboard.registerPack({ id, name?, sounds })` registers a sound pack and returns a disposer. Relative paths resolve from the plugin root and are cleaned up on plugin deactivation. - `api.attention.soundboard.activate(id, { persist })` selects the active pack. `persist: true` writes the selected pack id to TUI KV state, not `tui.json`. - `api.attention.soundboard.current()` and `list()` expose the active/registered packs for plugin UX. -- Config `attention.sounds` overrides active-pack sounds by slot. Failed loads fall back to the active pack and then `opencode.default`. +- Config `attention.sounds` overrides active-pack sounds by slot. Failed loads fall back to the active pack and then `redrob.default`. - The host strips ANSI/control characters and collapses newlines before sending text to the terminal notification API. - Terminal and OS settings decide whether a requested notification is visibly displayed. - Prefer privacy-safe messages such as `"A question needs your input"`; avoid full commands, paths, prompts, errors, secrets, or file contents unless the plugin intentionally exposes them. @@ -413,7 +413,7 @@ Theme install behavior: - If the theme name already exists, install is skipped unless plugin metadata state is `updated`. - On `updated`, host skips rewrite when tracked `mtime`/`size` is unchanged. - When a theme already exists and state is not `updated`, host can still persist theme metadata when destination already exists. -- Local plugins persist installed themes under the local `.opencode/themes` area near the plugin config source. +- Local plugins persist installed themes under the local `.redrob/themes` area near the plugin config source. - Global plugins persist installed themes under the global `themes` dir. - Invalid or unreadable theme files are ignored. @@ -538,7 +538,7 @@ The plugin manager is exposed as a command with title `Plugins` and value `plugi ## Current in-repo examples -- Local smoke plugin: `.opencode/plugins/tui-smoke.tsx` -- Local vim plugin: `.opencode/plugins/tui-vim.tsx` -- Local smoke config: `.opencode/tui.json` -- Local smoke theme: `.opencode/plugins/smoke-theme.json` +- Local smoke plugin: `.redrob/plugins/tui-smoke.tsx` +- Local vim plugin: `.redrob/plugins/tui-vim.tsx` +- Local smoke config: `.redrob/tui.json` +- Local smoke theme: `.redrob/plugins/smoke-theme.json` diff --git a/packages/redrob/src/cli/cmd/github.handler.ts b/packages/redrob/src/cli/cmd/github.handler.ts index ac5535d1d4..fd81a9aff8 100644 --- a/packages/redrob/src/cli/cmd/github.handler.ts +++ b/packages/redrob/src/cli/cmd/github.handler.ts @@ -1220,8 +1220,7 @@ export const githubRun = Effect.fn("Cli.github.run")(function* (args: { event?: } } - // The share card image was rendered by opencode's social-cards service from the session title. Shares - // now live on Redrob Chat, so the footer carries the plain link and nothing is sent to that service. + // Shares live on Redrob Chat, so the footer carries the plain share link and no image. function footer(_opts?: { image?: boolean }) { const shareLink = shareUrl ? `[redrob session](${shareUrl})  |  ` : "" return `\n\n${shareLink}[github run](${runUrl})` diff --git a/packages/redrob/src/cli/cmd/mcp.ts b/packages/redrob/src/cli/cmd/mcp.ts index d1537b6781..6366b3fc3f 100644 --- a/packages/redrob/src/cli/cmd/mcp.ts +++ b/packages/redrob/src/cli/cmd/mcp.ts @@ -393,15 +393,13 @@ export const McpLogoutCommand = effectCmd({ async function resolveConfigPath(baseDir: string, global = false) { // Check for existing config files (prefer .jsonc over .json, and check the `.redrob/` - // subdirectory plus the legacy `.opencode/` one) + // subdirectory) const candidates = [path.join(baseDir, "redrob.json"), path.join(baseDir, "redrob.jsonc")] if (!global) { candidates.push( path.join(baseDir, ".redrob", "redrob.json"), path.join(baseDir, ".redrob", "redrob.jsonc"), - path.join(baseDir, ".opencode", "redrob.json"), - path.join(baseDir, ".opencode", "redrob.jsonc"), ) } diff --git a/packages/redrob/src/cli/ui.ts b/packages/redrob/src/cli/ui.ts index 6d8c2acb85..7f376d10ea 100644 --- a/packages/redrob/src/cli/ui.ts +++ b/packages/redrob/src/cli/ui.ts @@ -3,7 +3,7 @@ import { Schema } from "effect" import { logo as glyphs } from "./logo" // Uppercase letterforms, so no row carries an ascender. The blank first row keeps the banner's -// height; opencode used it for the ascender of its lowercase `d`. +// height consistent with the glyph rows. const wordmark = [ ` `, `█▀▀█ █▀▀▀ █▀▀▄ █▀▀█ █▀▀█ █▀▀▄`, diff --git a/packages/redrob/src/config/config.ts b/packages/redrob/src/config/config.ts index d2f5774fe6..61f2778025 100644 --- a/packages/redrob/src/config/config.ts +++ b/packages/redrob/src/config/config.ts @@ -457,7 +457,7 @@ const layer = Layer.effect( result.command = mergeDeep(result.command ?? {}, yield* Effect.promise(() => ConfigCommand.load(dir))) result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.load(dir))) result.agent = mergeDeep(result.agent ?? {}, yield* Effect.promise(() => ConfigAgent.loadMode(dir))) - // Auto-discovered plugins under `.opencode/plugin(s)` are already local files, so ConfigPlugin.load + // Auto-discovered plugins under `.redrob/plugin(s)` are already local files, so ConfigPlugin.load // returns normalized Specs and we only need to attach origin metadata here. const list = yield* Effect.promise(() => ConfigPlugin.load(dir)) yield* mergePluginOrigins(dir, list) diff --git a/packages/redrob/src/config/paths.ts b/packages/redrob/src/config/paths.ts index 9d9bfa3a42..9de6bc426d 100644 --- a/packages/redrob/src/config/paths.ts +++ b/packages/redrob/src/config/paths.ts @@ -21,9 +21,8 @@ export const files = Effect.fn("ConfigPaths.projectFiles")(function* ( })).toReversed() }) -// Config folder names searched while walking up from the cwd. `.opencode` is the legacy -// name and is listed first so a co-located `.redrob` folder wins the merge. -const DIRECTORY_NAMES = [".opencode", ".redrob"] +// Config folder name searched while walking up from the cwd. +const DIRECTORY_NAMES = [".redrob"] export const directories = Effect.fn("ConfigPaths.directories")(function* (directory: string, worktree?: string) { const afs = yield* FSUtil.Service diff --git a/packages/redrob/src/config/tui-migrate.ts b/packages/redrob/src/config/tui-migrate.ts index ce8441736f..78e205d9de 100644 --- a/packages/redrob/src/config/tui-migrate.ts +++ b/packages/redrob/src/config/tui-migrate.ts @@ -4,9 +4,7 @@ import { unique } from "remeda" import { Option, Schema } from "effect" import { TuiConfig } from "@redrob-code/tui/config" import { Flag } from "@redrob-code/core/flag/flag" -import { Global } from "@redrob-code/core/global" import { Filesystem } from "@/util/filesystem" -import * as ConfigPaths from "@/config/paths" import { TUI_SCHEMA_URL } from "@redrob-code/core/links" const decodeTheme = Schema.decodeUnknownOption(Schema.String) @@ -26,7 +24,7 @@ interface MigrateInput { * skips only locations where a tui.json already exists. */ export async function migrateTuiConfig(input: MigrateInput) { - const legacyFiles = await legacyOpencodeFiles(input) + const legacyFiles = await legacyConfigFiles(input) for (const file of legacyFiles) { const source = await Filesystem.readText(file).catch(() => undefined) if (!source) continue @@ -111,14 +109,10 @@ async function backupAndStripLegacy(file: string, source: string) { .catch(() => false) } -async function legacyOpencodeFiles(input: { directories: string[]; cwd: string }) { +async function legacyConfigFiles(input: { cwd: string }) { const files = [ - ...ConfigPaths.fileInDirectory(Global.Path.config, "opencode"), ...(await Filesystem.findUp(["redrob.json", "redrob.jsonc"], input.cwd, undefined, { rootFirst: true })), ] - for (const dir of unique(input.directories)) { - files.push(...ConfigPaths.fileInDirectory(dir, "opencode")) - } if (Flag.REDROB_CONFIG) files.push(Flag.REDROB_CONFIG) const existing = await Promise.all( diff --git a/packages/redrob/src/config/tui.ts b/packages/redrob/src/config/tui.ts index 69d91890a3..2f4675a18b 100644 --- a/packages/redrob/src/config/tui.ts +++ b/packages/redrob/src/config/tui.ts @@ -168,8 +168,8 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: acc.plugin_origins = plugins }) - // Every config dir we may read from: global config dir, any `.redrob` (or legacy - // `.opencode`) folders between cwd and home, and REDROB_CONFIG_DIR. + // Every config dir we may read from: global config dir, any `.redrob` folders between + // cwd and home, and REDROB_CONFIG_DIR. const directories = yield* ConfigPaths.directories(ctx.directory) yield* Effect.promise(() => migrateTuiConfig({ directories, cwd: ctx.directory })) @@ -197,7 +197,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: yield* mergeFile(acc, file) } - // 4. `.redrob` (and legacy `.opencode`) directories, plus REDROB_CONFIG_DIR, discovered + // 4. `.redrob` directories, plus REDROB_CONFIG_DIR, discovered // while walking up the tree. Also returned below so callers can install plugin // dependencies from each location. const dirs = unique(directories).filter(ConfigPaths.isProjectDirectory) diff --git a/packages/redrob/src/control-plane/dev/README.md b/packages/redrob/src/control-plane/dev/README.md index 458d2d5c0e..b0bbfcdf55 100644 --- a/packages/redrob/src/control-plane/dev/README.md +++ b/packages/redrob/src/control-plane/dev/README.md @@ -1,4 +1,4 @@ -This is a plugin to simulate a remote environment locally. Add this to `.opencode/redrob.jsonc`: +This is a plugin to simulate a remote environment locally. Add this to `.redrob/redrob.jsonc`: ```json "plugin": ["../packages/redrob/src/control-plane/dev/debug-workspace-plugin.ts"], diff --git a/packages/redrob/src/ide/index.ts b/packages/redrob/src/ide/index.ts index 1b44656923..3438ba9666 100644 --- a/packages/redrob/src/ide/index.ts +++ b/packages/redrob/src/ide/index.ts @@ -37,10 +37,8 @@ export async function install(ide: (typeof SUPPORTED_IDES)[number]["name"]) { const cmd = SUPPORTED_IDES.find((i) => i.name === ide)?.cmd if (!cmd) throw new Error(`Unknown IDE: ${ide}`) - // Must match sdks/vscode/package.json's "publisher"."name". The rebrand renamed the - // extension but not the publisher, leaving `sst-dev.redrob` -- an id that resolves to - // nothing: upstream publishes `sst-dev.opencode` and ours is published by - // `redrob-labs`. The install silently found no extension either way. + // Must match sdks/vscode/package.json's "publisher"."name": the extension is published + // by `redrob-labs`, so any other publisher id installs nothing. const p = await Process.run([cmd, "--install-extension", "redrob-labs.redrob-code"], { nothrow: true, }) diff --git a/packages/redrob/src/mcp/index.ts b/packages/redrob/src/mcp/index.ts index 57f96ecc7b..87358047ee 100644 --- a/packages/redrob/src/mcp/index.ts +++ b/packages/redrob/src/mcp/index.ts @@ -39,13 +39,9 @@ import * as Apps from "@redrob-code/server/apps" const DEFAULT_TIMEOUT = 30_000 const CLIENT_OPTIONS = { capabilities: { - // https://github.com/anomalyco/opencode/issues/11948 // sampling: {}, - // https://github.com/anomalyco/opencode/issues/23066 // elicitation: {}, - // https://github.com/anomalyco/opencode/issues/2308 roots: {}, - // https://github.com/anomalyco/opencode/issues/28567 // tasks: {}, }, } satisfies ClientOptions diff --git a/packages/redrob/src/plugin/install.ts b/packages/redrob/src/plugin/install.ts index b3b19afbcf..ee963130fd 100644 --- a/packages/redrob/src/plugin/install.ts +++ b/packages/redrob/src/plugin/install.ts @@ -334,7 +334,7 @@ function patchDir(input: PatchInput) { if (input.global) return input.config ?? Global.Path.config const git = input.vcs === "git" && input.worktree !== "/" const root = git ? input.worktree : input.directory - // New files go to `.redrob`; `.opencode` stays readable but is never written to. + // Project config files go to `.redrob`. return path.join(root, ".redrob") } diff --git a/packages/redrob/src/plugin/tui/runtime.ts b/packages/redrob/src/plugin/tui/runtime.ts index 69a4fc00e8..dd35f18627 100644 --- a/packages/redrob/src/plugin/tui/runtime.ts +++ b/packages/redrob/src/plugin/tui/runtime.ts @@ -252,9 +252,9 @@ function createThemeInstaller( const src = Filesystem.resolveFilePath(root, file) const name = path.basename(src, path.extname(src)) const source_dir = path.dirname(meta.source) - // A legacy `.opencode` config directory keeps its own `themes/`; everything else + // A `.redrob` config directory keeps its own `themes/`; everything else // writes under `.redrob`. - const local_dir = [".redrob", ".opencode"].includes(path.basename(source_dir)) + const local_dir = path.basename(source_dir) === ".redrob" ? path.join(source_dir, "themes") : path.join(source_dir, ".redrob", "themes") const dest_dir = meta.scope === "local" ? local_dir : path.join(Global.Path.config, "themes") diff --git a/packages/redrob/src/provider/provider.ts b/packages/redrob/src/provider/provider.ts index 7c9d72e321..6c5df55593 100644 --- a/packages/redrob/src/provider/provider.ts +++ b/packages/redrob/src/provider/provider.ts @@ -907,7 +907,7 @@ function custom(dep: CustomDep): Record { autoload: false, options: { headers: { - "X-Cerebras-3rd-Party-Integration": "opencode", + "X-Cerebras-3rd-Party-Integration": "redrob", }, }, }), diff --git a/packages/redrob/src/session/llm/AGENTS.md b/packages/redrob/src/session/llm/AGENTS.md index ad7249fe8a..a5e8c8c394 100644 --- a/packages/redrob/src/session/llm/AGENTS.md +++ b/packages/redrob/src/session/llm/AGENTS.md @@ -16,7 +16,7 @@ src/session/ llm/ AGENTS.md boundary notes for the adapter layer ai-sdk.ts AI SDK fullStream -> @redrob-code/llm LLMEvent adapter - native-request.ts opencode/AI SDK-shaped input -> @redrob-code/llm LLMRequest + native-request.ts AI SDK-shaped input -> @redrob-code/llm LLMRequest native-runtime.ts native runtime gate, tool bridge, and LLMClient handoff ``` diff --git a/packages/redrob/src/share/share-next.ts b/packages/redrob/src/share/share-next.ts index 379916e092..f6e801d466 100644 --- a/packages/redrob/src/share/share-next.ts +++ b/packages/redrob/src/share/share-next.ts @@ -19,7 +19,7 @@ import { EventV2 } from "@redrob-code/core/event" import { Flag } from "@redrob-code/core/flag/flag" /* - Sharing goes to Redrob Chat, not to opencode's share service. + Sharing goes to Redrob Chat. A share is an immutable snapshot (`POST /share` on the Redrob Chat API, kind "transcript", source "code"): the transcript is sent once, when the user shares, and nothing is streamed afterwards. Unshare is diff --git a/packages/redrob/src/skill/discovery.ts b/packages/redrob/src/skill/discovery.ts index 7269b27b9e..8f5625930a 100644 --- a/packages/redrob/src/skill/discovery.ts +++ b/packages/redrob/src/skill/discovery.ts @@ -77,7 +77,7 @@ const layer: Layer.Layer Effect.gen(function* () { const root = path.join(cache, skill.name) - const versionFile = path.join(root, ".opencode-version") + const versionFile = path.join(root, ".redrob-version") const version = skill.version const current = version === undefined @@ -102,7 +102,7 @@ const layer: Layer.Layer RUN_THEME_FALLBACK)} tuiConfig={config} backgroundSubagents={input.backgroundSubagents ?? true} - agent="opencode" + agent="redrob" onSubmit={input.onSubmit ?? (() => true)} onPermissionReply={() => {}} onQuestionReply={() => {}} @@ -974,7 +974,7 @@ test("direct footer shows editable prompts and additional queued work while runn commands={() => []} providers={() => undefined} currentModel={() => ({ - providerID: "opencode", + providerID: "redrob", modelID: "a-model-name-long-enough-to-force-responsive-truncation", })} variants={() => []} @@ -988,7 +988,7 @@ test("direct footer shows editable prompts and additional queued work while runn theme={() => RUN_THEME_FALLBACK} tuiConfig={tuiConfig} backgroundSubagents={true} - agent="opencode" + agent="redrob" onSubmit={() => true} onPermissionReply={() => {}} onQuestionReply={() => {}} @@ -1063,7 +1063,7 @@ test("direct footer shows editable prompts and additional queued work while runn test("direct footer separates a lone context hint from model and command hint", async () => { const app = await renderFooter({ providers: [provider()], - currentModel: { providerID: "opencode", modelID: "gpt-5" }, + currentModel: { providerID: "redrob", modelID: "gpt-5" }, currentVariant: "xhigh", subagents: { tabs: [subagent({ sessionID: "s-1", label: "Explore", description: "Inspect auth flow" })], @@ -1091,7 +1091,7 @@ test("direct footer separates a lone context hint from model and command hint", test("direct footer hides the subagent hint when only completed subagents remain", async () => { const app = await renderFooter({ providers: [provider()], - currentModel: { providerID: "opencode", modelID: "gpt-5" }, + currentModel: { providerID: "redrob", modelID: "gpt-5" }, currentVariant: "xhigh", subagents: { tabs: [subagent({ sessionID: "s-1", label: "Explore", description: "Inspect auth flow", status: "completed" })], @@ -1329,7 +1329,7 @@ test("direct permission rejection submits through keymap return binding", async test("direct model panel renders current model selector", async () => { const [providers] = createSignal([provider()]) - const [current] = createSignal({ providerID: "opencode", modelID: "gpt-5" }) + const [current] = createSignal({ providerID: "redrob", modelID: "gpt-5" }) const app = await testRender( () => ( @@ -1356,7 +1356,7 @@ test("direct model panel renders current model selector", async () => { expect(frame).toContain("Select model") expect(frame).toContain("Search") - expect(frame).toContain("opencode") + expect(frame).toContain("redrob") expect(frame).toContain("GPT-5") expect(frame).toContain("current") expect(frame).toContain("GPT Free") diff --git a/packages/redrob/test/cli/tui/editor-context.test.tsx b/packages/redrob/test/cli/tui/editor-context.test.tsx index b26febec3f..43aedfed12 100644 --- a/packages/redrob/test/cli/tui/editor-context.test.tsx +++ b/packages/redrob/test/cli/tui/editor-context.test.tsx @@ -10,11 +10,11 @@ import { TestTuiContexts } from "../../fixture/tui-environment" import { discoverEditorConnection } from "@redrob-code/tui/editor" const originalClaudePort = process.env.CLAUDE_CODE_SSE_PORT -const originalOpencodePort = process.env.REDROB_EDITOR_SSE_PORT +const originalEditorPort = process.env.REDROB_EDITOR_SSE_PORT afterEach(() => { process.env.CLAUDE_CODE_SSE_PORT = originalClaudePort - process.env.REDROB_EDITOR_SSE_PORT = originalOpencodePort + process.env.REDROB_EDITOR_SSE_PORT = originalEditorPort }) function nextTick() { diff --git a/packages/redrob/test/fixture/tui-environment.tsx b/packages/redrob/test/fixture/tui-environment.tsx index b1fa7ffcc1..52b71026fd 100644 --- a/packages/redrob/test/fixture/tui-environment.tsx +++ b/packages/redrob/test/fixture/tui-environment.tsx @@ -17,9 +17,9 @@ export function TestTuiContexts( return ( } -export type OpencodeCli = { +export type RedrobCli = { // High-level: run a single prompt against the test model. Short-lived. readonly run: (message: string, opts?: RunOpts) => Effect.Effect readonly startRun: (message: string, opts?: RunOpts) => Effect.Effect @@ -219,7 +219,7 @@ export type OpencodeCli = { export type CliFixture = { readonly llm: TestLLMServer["Service"] readonly home: string - readonly redrob: OpencodeCli + readonly redrob: RedrobCli } // Provisions a TestLLMServer + tmpdir + spawn helper and invokes fn. Cleans @@ -516,7 +516,7 @@ export function withCliFixture( } satisfies AcpHandle }) - const redrob: OpencodeCli = { run, startRun, serve, acp, spawn, expectExit, parseJsonEvents } + const redrob: RedrobCli = { run, startRun, serve, acp, spawn, expectExit, parseJsonEvents } return yield* fn({ llm, home, redrob }) // FetchHttpClient is provided so test bodies can `yield* HttpClient.HttpClient` diff --git a/packages/redrob/test/provider/transform.test.ts b/packages/redrob/test/provider/transform.test.ts index b3ae476d54..51e9197152 100644 --- a/packages/redrob/test/provider/transform.test.ts +++ b/packages/redrob/test/provider/transform.test.ts @@ -1658,7 +1658,7 @@ describe("ProviderTransform.schema - openai supported schema subset", () => { }) test.each([ - ["opencode", "@ai-sdk/openai"], + ["redrob", "@ai-sdk/openai"], ["custom-openai-compatible", "@ai-sdk/openai"], ["azure", "@ai-sdk/azure"], ])("sanitizes %s models using %s", (providerID, npm) => { @@ -2783,7 +2783,7 @@ describe("ProviderTransform.message - strip openai metadata when store=false", ( test("preserves metadata using providerID key when store is false", () => { const redrobModel = { ...openaiModel, - providerID: "opencode", + providerID: "redrob", api: { id: "redrob-test", url: "https://console.redrob.ai/api/backend/v1", @@ -2817,7 +2817,7 @@ describe("ProviderTransform.message - strip openai metadata when store=false", ( test("preserves itemId across all providerOptions keys", () => { const redrobModel = { ...openaiModel, - providerID: "opencode", + providerID: "redrob", api: { id: "redrob-test", url: "https://console.redrob.ai/api/backend/v1", diff --git a/packages/redrob/test/server/httpapi-exercise/index.ts b/packages/redrob/test/server/httpapi-exercise/index.ts index 9fb0b08e1e..3e0b3ed27b 100644 --- a/packages/redrob/test/server/httpapi-exercise/index.ts +++ b/packages/redrob/test/server/httpapi-exercise/index.ts @@ -526,7 +526,7 @@ const scenarios: Scenario[] = [ http.protected .get("/experimental/tool", "tool.list") .at((ctx) => ({ - path: `/experimental/tool?${new URLSearchParams({ provider: "opencode", model: "test" })}`, + path: `/experimental/tool?${new URLSearchParams({ provider: "redrob", model: "test" })}`, headers: ctx.headers(), })) .json(200, array, "status"), @@ -1194,7 +1194,7 @@ const scenarios: Scenario[] = [ .at((ctx) => ({ path: route("/api/session/{sessionID}/model", { sessionID: ctx.state.id }), headers: { ...ctx.headers(), "content-type": "application/json" }, - body: { model: { providerID: "opencode", id: "big-pickle" } }, + body: { model: { providerID: "redrob", id: "big-pickle" } }, })) .status(204, undefined, "none"), http.protected diff --git a/packages/redrob/test/server/redrob-work-engine-contract.test.ts b/packages/redrob/test/server/redrob-work-engine-contract.test.ts index d07ab0a2bc..c147d6154d 100644 --- a/packages/redrob/test/server/redrob-work-engine-contract.test.ts +++ b/packages/redrob/test/server/redrob-work-engine-contract.test.ts @@ -50,9 +50,6 @@ const httpApiServerLayer = servedRoutes.pipe( const it = testEffect(Layer.mergeAll(testStateLayer, httpApiServerLayer)) const DIRECTORY_HEADER = "x-redrob-directory" -// Upstream OpenCode's header. Redrob Code reads only its own, and Redrob Work sets only its own, so -// this must stay unrecognised rather than quietly becoming a second way to address a project. -const UPSTREAM_DIRECTORY_HEADER = "x-opencode-directory" const inDirectory = (path: string, directory: string) => HttpClientRequest.get(path).pipe(HttpClientRequest.setHeader(DIRECTORY_HEADER, directory), HttpClient.execute) @@ -158,36 +155,5 @@ describe("Redrob Work engine contract", () => { expect(secondPath).toMatchObject({ directory: second, worktree: second }) }), ) - - it.effect("ignores x-opencode-directory", () => - Effect.gen(function* () { - const directory = yield* tmpdirScoped({ git: true }) - const response = yield* HttpClientRequest.get(InstancePaths.path).pipe( - HttpClientRequest.setHeader(UPSTREAM_DIRECTORY_HEADER, directory), - HttpClient.execute, - ) - expect(response.status).toBe(200) - // The request is still served, from the server's own cwd — the upstream header does not - // select a project. Asserting the resolved directory rather than the status is the point: a - // status-only check would pass even if the header were honoured. - const body = (yield* response.json) as { directory: string } - expect(body.directory).not.toBe(directory) - expect(body.directory).toBe(process.cwd()) - }), - ) - - it.effect("prefers x-redrob-directory when both headers are present", () => - Effect.gen(function* () { - const redrob = yield* tmpdirScoped({ git: true }) - const upstream = yield* tmpdirScoped({ git: true }) - const response = yield* HttpClientRequest.get(InstancePaths.path).pipe( - HttpClientRequest.setHeader(DIRECTORY_HEADER, redrob), - HttpClientRequest.setHeader(UPSTREAM_DIRECTORY_HEADER, upstream), - HttpClient.execute, - ) - expect(response.status).toBe(200) - expect(yield* response.json).toMatchObject({ directory: redrob }) - }), - ) }) }) diff --git a/packages/redrob/test/session/prompt.test.ts b/packages/redrob/test/session/prompt.test.ts index 3d20eb2eeb..887c21ea18 100644 --- a/packages/redrob/test/session/prompt.test.ts +++ b/packages/redrob/test/session/prompt.test.ts @@ -2360,7 +2360,7 @@ noLLMServer.instance( const other = yield* prompt.prompt({ sessionID: session.id, agent: "build", - model: { providerID: ProviderV2.ID.make("opencode"), modelID: ModelV2.ID.make("kimi-k2.5-free") }, + model: { providerID: ProviderV2.ID.make("redrob"), modelID: ModelV2.ID.make("kimi-k2.5-free") }, noReply: true, parts: [{ type: "text", text: "hello" }], }) diff --git a/packages/redrob/test/session/schema-decoding.test.ts b/packages/redrob/test/session/schema-decoding.test.ts index 3f27ae802e..ece8973f8d 100644 --- a/packages/redrob/test/session/schema-decoding.test.ts +++ b/packages/redrob/test/session/schema-decoding.test.ts @@ -236,7 +236,7 @@ describe("SessionStatus.Info", () => { message: "transient", action: { reason: "free_tier_limit", - provider: "opencode", + provider: "redrob", title: "Free limit reached", message: "Subscribe to RedrobCode Go.", label: "subscribe", diff --git a/packages/redrob/test/session/system.test.ts b/packages/redrob/test/session/system.test.ts index 3a112bc3e0..caf62eae7d 100644 --- a/packages/redrob/test/session/system.test.ts +++ b/packages/redrob/test/session/system.test.ts @@ -136,8 +136,7 @@ describe("session.system", () => { } }) - // The prompts inherited from opencode introduced the agent to the user as OpenCode, so a - // reintroduced mention would come straight back out of the model. + // A former product name in a prompt would come straight back out of the model. test("never introduces the agent by the former product name", () => { const ids = [ "claude-sonnet-4-5", diff --git a/packages/redrob/test/tool/__snapshots__/tool.test.ts.snap b/packages/redrob/test/tool/__snapshots__/tool.test.ts.snap index 53c671956a..b96aacaeb7 100644 --- a/packages/redrob/test/tool/__snapshots__/tool.test.ts.snap +++ b/packages/redrob/test/tool/__snapshots__/tool.test.ts.snap @@ -1,7 +1,7 @@ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots exports[`tool.ls basic 1`] = ` -"packages/opencode/test/fixtures/example/ +"packages/redrob/test/fixtures/example/ broken.ts cli.ts ink.tsx diff --git a/packages/redrob/test/tool/skill.test.ts b/packages/redrob/test/tool/skill.test.ts index e2648325ef..fa54b423ba 100644 --- a/packages/redrob/test/tool/skill.test.ts +++ b/packages/redrob/test/tool/skill.test.ts @@ -61,7 +61,7 @@ Use this skill. const registry = yield* ToolRegistry.Service const agent = { name: "build", mode: "primary" as const, permission: [], options: {} } const tool = (yield* registry.tools({ - providerID: "opencode" as any, + providerID: "redrob" as any, modelID: "gpt-5" as any, agent, })).find((tool) => tool.id === SkillTool.id) @@ -107,7 +107,7 @@ Use this skill. const registry = yield* ToolRegistry.Service const agent = { name: "build", mode: "primary" as const, permission: [], options: {} } const tool = (yield* registry.tools({ - providerID: "opencode" as any, + providerID: "redrob" as any, modelID: "gpt-5" as any, agent, })).find((tool) => tool.id === SkillTool.id) diff --git a/packages/tui/src/context/theme.tsx b/packages/tui/src/context/theme.tsx index 23537d3252..11807c2daf 100644 --- a/packages/tui/src/context/theme.tsx +++ b/packages/tui/src/context/theme.tsx @@ -38,7 +38,7 @@ const themeSource: ThemeSource = { async discover() { const directories = [Global.Path.config] for (let current = process.cwd(); ; current = path.dirname(current)) { - directories.push(path.join(current, ".opencode")) + directories.push(path.join(current, ".redrob")) if (path.dirname(current) === current) break } return discoverThemes(directories) diff --git a/packages/tui/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap b/packages/tui/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap index 0033889de2..17b15b56e6 100644 --- a/packages/tui/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap +++ b/packages/tui/test/cli/tui/__snapshots__/inline-tool-wrap-snapshot.test.tsx.snap @@ -1,7 +1,7 @@ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots exports[`TUI inline tool wrapping snapshots consecutive grep, glob, and read rows at a narrow width 1`] = ` -" ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data. +" ✱ Grep "XDG_DATA.*DB|database|sqlite|drizzle|dev.*db|data. *dir|xdg|APPDATA" in packages/redrob/src (151 matches) ✱ Glob "**/*db*" in packages/redrob (6 matches) @@ -12,7 +12,7 @@ exports[`TUI inline tool wrapping snapshots consecutive grep, glob, and read row `; exports[`TUI inline tool wrapping snapshots expanded tool errors under the tool text 1`] = ` -" ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data. +" ✱ Grep "XDG_DATA.*DB|database|sqlite|drizzle|dev.*db|data. *dir|xdg|APPDATA" in packages/redrob/src (151 matches) ✱ Glob "**/*db*" in packages/redrob (6 matches) @@ -31,7 +31,7 @@ exports[`TUI inline tool wrapping keeps separation after a shell output block 1` file.ts - ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data. + ✱ Grep "XDG_DATA.*DB|database|sqlite|drizzle|dev.*db|data. *dir|xdg|APPDATA" in packages/redrob/src (151 matches) ✱ Glob "**/*db*" in packages/redrob (6 matches) @@ -46,7 +46,7 @@ exports[`TUI inline tool wrapping keeps separation after a padded user message 1 Check whether the next tool remains separated. - ✱ Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data. + ✱ Grep "XDG_DATA.*DB|database|sqlite|drizzle|dev.*db|data. *dir|xdg|APPDATA" in packages/redrob/src (151 matches) ✱ Glob "**/*db*" in packages/redrob (6 matches) diff --git a/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx b/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx index 898128d906..0d57b4560f 100644 --- a/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx +++ b/packages/tui/test/cli/tui/inline-tool-wrap-snapshot.test.tsx @@ -33,7 +33,7 @@ const tools: readonly ToolFixture[] = [ { icon: "✱", label: - 'Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.*dir|xdg|APPDATA" in packages/redrob/src (151 matches)', + 'Grep "XDG_DATA.*DB|database|sqlite|drizzle|dev.*db|data.*dir|xdg|APPDATA" in packages/redrob/src (151 matches)', }, { icon: "✱", diff --git a/packages/tui/test/i18n/branding.test.tsx b/packages/tui/test/i18n/branding.test.tsx index f48dc63125..8ba81f5e7b 100644 --- a/packages/tui/test/i18n/branding.test.tsx +++ b/packages/tui/test/i18n/branding.test.tsx @@ -13,16 +13,14 @@ import { dict as en } from "../../src/i18n/en" import { dict as ko } from "../../src/i18n/ko" import sidebarFooterPlugin from "../../src/feature-plugins/sidebar/footer" -// This fork inherited its copy from opencode, so any spelling of the old name is a -// shipping bug wherever a user can read it. Infrastructure identifiers keep the old -// name on purpose — `.opencode` config discovery, the `opencode-*` npm plugins, the -// `/opencode` GitHub trigger — so the guard asserts on the two dictionaries and on a -// rendered frame instead of scanning source text. +// Any spelling of the former product name is a shipping bug wherever a user can read it. +// Third-party npm plugin names still contain it, so the guard asserts on the two +// dictionaries and on a rendered frame instead of scanning source text. const FORMER_NAME = /open[\s-]*code/i // The sidebar footer used to compose the name from two adjacent literals, so only the // composed frame catches it. `local` is what InstallationVersion reports for a build -// made from source, which is how the footer came to read "OpenCode local". +// made from source, which is how the footer once read the former name plus "local". describe("product name", () => { test("no dictionary value names the former product", () => { const offenders = Object.entries({ en, ko }).flatMap(([locale, dict]) => diff --git a/packages/tui/test/theme-tokens.test.ts b/packages/tui/test/theme-tokens.test.ts index b4aa186427..8816e80c56 100644 --- a/packages/tui/test/theme-tokens.test.ts +++ b/packages/tui/test/theme-tokens.test.ts @@ -127,7 +127,7 @@ test("the default theme paints only through named steps", () => { }) test("the default theme carries no leftover palette", () => { - // The theme this replaces was an inherited OpenCode palette: an orange primary on dark, a 12-step + // The theme this replaces was an older palette: an orange primary on dark, a 12-step // neutral ramp of pure grays, and diff colours pasted in from tokyonight. None of it is the brand's. const abandoned = ["#fab283", "#0a0a0a", "#eeeeee", "#3b7dd8", "#4fd6be", "#c53b53", "#828bb8", "#9d7cd8"] expect(abandoned.filter((hex) => JSON.stringify(redrob).includes(hex))).toEqual([]) diff --git a/perf/test-suite.md b/perf/test-suite.md index 7f348008ce..80cf9d5003 100644 --- a/perf/test-suite.md +++ b/perf/test-suite.md @@ -60,7 +60,7 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/ | `config.test` waits after dependencies even though `.gitignore` is written synchronously | Removed obsolete 1000ms sleep from writable `REDROB_CONFIG_DIR` test | 10.270s | 9.433s | keep | Median from 5 targeted runs because one run was noisy; simpler test and no fixed sleep. | | SDK parity helpers create git repos for tests that only need files/config/session state | Changed `withProject` default to no git; explicit git init test still opts into no-git fixture | 8.011s | 5.180s | keep | Median from 5 targeted runs because first run was cold/noisy. | | Provider plugin filter test waits on plugin dependency readiness setup | Marked local plugin dependencies ready using the existing fixture helper | 7.543s | 6.366s | keep | Median from 3 targeted runs; matches neighboring plugin provider test setup. | -| HTTP provider tests generate local plugins without dependency-ready fixture state | Marked generated `.opencode` plugin fixtures dependency-ready | 7.905s | 2.980s | keep | Median from 3 targeted runs; avoids unrelated plugin dependency setup in route tests. | +| HTTP provider tests generate local plugins without dependency-ready fixture state | Marked generated `.redrob` plugin fixtures dependency-ready | 7.905s | 2.980s | keep | Median from 3 targeted runs; avoids unrelated plugin dependency setup in route tests. | | TUI plugin lifecycle timeout coverage waits the full production cleanup timeout | Added optional runtime dispose timeout override and used 25ms in the timeout test | 7.330s | 1.507s | keep | Median from 3 targeted runs; production default remains 5000ms. | | Skill tool test initializes git even though it only reads local skill files | Removed `git: true` from the temporary directory fixture | 2.320s | 1.425s | keep | Single targeted rerun; still exercises skill discovery, permission request, and bundled file output. | | Prompt shell semantics tests initialize git though they only assert shell/session behavior | Removed `git: true` from shell-focused prompt fixtures while preserving config setup | 26.930s | 23.400s | keep | Three targeted reruns passed after the change: 23.80s, 23.55s, 23.40s. | @@ -75,7 +75,7 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/ | Config template, file include, and simple agent cases can use Effect-aware instance fixtures | Migrated JSONC, env/file substitution, invalid config, and agent config cases to `it.instance` | 1.87s | 1.90s | keep | Stacked on the first config slice; neutral timing but removes more manual `tmpdir` + instance plumbing. | | Agent option, command, and legacy migration config cases can use Effect-aware instance fixtures | Migrated agent variant, command, autoshare, and mode migration cases to `it.instance` | 1.90s | 1.83s | keep | Stacked on the config template slice; small neutral-to-positive timing and less manual setup. | | Local config update and directory cases can use Effect-aware instance fixtures | Migrated local `update` and `directories` cases to `it.instance` | 1.77s | 1.71s | keep | Three-run medians; small positive/neutral timing, removes manual instance plumbing, and eliminates one existing unsafe cast. | -| `.opencode` agent and command file-loading cases can use Effect-aware instance fixtures | Migrated singular/plural agent and command markdown fixture cases to `it.instance` | 7.21s | 1.87s | keep | Parent baseline was noisy (7.42, 7.21, 2.83); after runs were stable at 1.87, 1.98, 1.83. Keep as cleanup with no broad claim. | +| `.redrob` agent and command file-loading cases can use Effect-aware instance fixtures | Migrated singular/plural agent and command markdown fixture cases to `it.instance` | 7.21s | 1.87s | keep | Parent baseline was noisy (7.42, 7.21, 2.83); after runs were stable at 1.87, 1.98, 1.83. Keep as cleanup with no broad claim. | | Legacy tools and permission-order config cases can use Effect-aware instance fixtures | Migrated legacy `tools` migration and permission order cases to `it.instance` | 1.87s | 1.87s | keep | Neutral timing; removes more manual temp-instance plumbing from legacy config migration coverage. | | Remaining simple config load cases can use Effect-aware instance fixtures | Migrated default config load and legacy TUI-key cases to `it.instance` | 7.78s | 6.39s | keep | Single baseline before edit; after median from three sequential reruns (5.76, 6.39, 6.53). Keep as cleanup with cautious timing. | | Managed settings config cases can use Effect-aware instance fixtures | Migrated managed override and missing-managed-file cases to `it.instance` | 2.40s | 1.76s | keep | Single baseline before edit; after median from three sequential reruns (1.75, 1.76, 1.80). | diff --git a/script/changelog.ts b/script/changelog.ts index 971c38c111..995119fb5c 100755 --- a/script/changelog.ts +++ b/script/changelog.ts @@ -27,13 +27,13 @@ if (values.help) { console.log(` Usage: bun script/changelog.ts [options] -Generates UPCOMING_CHANGELOG.md by running the opencode changelog command. +Generates UPCOMING_CHANGELOG.md by running the redrob changelog command. Options: -f, --from Starting version (default: latest non-draft GitHub release) -t, --to Ending ref (default: HEAD) - --variant Thinking variant for opencode run (default: low) - --quiet Suppress opencode command output unless it fails + --variant Thinking variant for redrob run (default: low) + --quiet Suppress redrob command output unless it fails --print Print the generated UPCOMING_CHANGELOG.md after success -h, --help Show this help message @@ -48,7 +48,7 @@ Examples: await rm(file, { force: true }) const quiet = values.quiet -const cmd = ["opencode", "run"] +const cmd = ["redrob", "run"] cmd.push("--variant", values.variant) cmd.push("--command", "changelog", "--", ...args) diff --git a/script/upgrade-opentui.ts b/script/upgrade-opentui.ts index fdc34491cb..9a082255c4 100644 --- a/script/upgrade-opentui.ts +++ b/script/upgrade-opentui.ts @@ -35,7 +35,7 @@ if (snapshotArg === "--snapshot=") { const ver = raw.replace(/^v/, "") const root = path.resolve(import.meta.dir, "..") const lockfile = path.join(root, "bun.lock") -const skip = new Set([".git", ".opencode", ".turbo", "dist", "node_modules"]) +const skip = new Set([".git", ".redrob", ".turbo", "dist", "node_modules"]) const keys = ["@opentui/core", "@opentui/keymap", "@opentui/solid"] as const const files = (await Array.fromAsync(new Bun.Glob("**/package.json").scan({ cwd: root }))).filter( diff --git a/script/version.ts b/script/version.ts index a6f32946fe..74541ddf52 100755 --- a/script/version.ts +++ b/script/version.ts @@ -13,7 +13,7 @@ if (!Script.preview) { .text() .catch(() => "No notable changes") const dir = process.env.RUNNER_TEMP ?? "/tmp" - const notesFile = `${dir}/opencode-release-notes.txt` + const notesFile = `${dir}/redrob-release-notes.txt` await Bun.write(notesFile, body) await $`gh release create v${Script.version} -d --target ${sha} --title "v${Script.version}" --notes-file ${notesFile}` const release = await $`gh release view v${Script.version} --json tagName,databaseId`.json()