Skip to content

Add migration helper for Tabnine CLI to opencode - #1

Merged
Pavel-tabnine merged 15 commits into
mainfrom
feat/migration-helper
Aug 24, 2026
Merged

Add migration helper for Tabnine CLI to opencode#1
Pavel-tabnine merged 15 commits into
mainfrom
feat/migration-helper

Conversation

@Pavel-tabnine

@Pavel-tabnine Pavel-tabnine commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a migration_helper/ subtree that helps users move their Tabnine CLI (or Gemini CLI) configuration into opencode.

It contains two opencode skills that run as interactive wizards inside opencode itself, plus slash commands and a small bash installer:

  • migrate-from-tabnine-cli (/migrate) — migrates MCP servers, skills, subagents, slash commands, and the contents of Tabnine CLI extensions. Run once per target scope (global or project).
  • migrate-tabnine-context (/migrate-context) — migrates context/memory files (TABNINE.md, GEMINI.md, or custom context.fileName files) into opencode's AGENTS.md. Re-runnable per repository.

What's inside migration_helper/

migration_helper/
├── LICENSE                       # MIT, subtree-scoped
├── README.md                     # install (script or manual) + usage
├── install.sh                    # bash installer (executable)
├── commands/
│   ├── migrate.md                # /migrate slash command
│   └── migrate-context.md        # /migrate-context slash command
└── skills/
    ├── migrate-from-tabnine-cli/
    │   ├── SKILL.md              # main wizard skill
    │   └── references/
    │       ├── source-map.md     # where Tabnine CLI stores each thing
    │       └── mapping.md        # field-by-field translation rules
    └── migrate-tabnine-context/
        └── SKILL.md              # context-file wizard skill

What it migrates

MCP servers (including field renames opencode requires, e.g. envenvironment, and $VAR{env:VAR} value rewrites), skills, subagents, slash commands, extension contents, and context files. Fields with no opencode equivalent are dropped with a note in the wizard's summary. Nothing is deleted from the Tabnine CLI installation — both wizards are copy-and-translate flows, and neither overwrites an existing opencode file without asking.

Out of scope: OAuth tokens (users re-authenticate remote MCP servers on first use), Tabnine credentials, admin policy fields, hooks, themes, keybindings, and general settings.

Installer behaviour

./install.sh                 # global: ~/.config/opencode/
./install.sh --project       # project: ./.opencode/
./install.sh --overwrite     # overwrite existing files without prompting

For every file it wants to place:

  • Missing target → copy.
  • Identical target → skip (idempotent).
  • Different target → show a unified diff, prompt [s]kip / [o]verwrite / [r]ename (rename appends .bak-YYYYMMDD-HHMMSS).
  • No terminal to prompt on (e.g. piped/CI runs) → keep the existing file and say so, never overwrite.

Manual cp commands are documented as a fully supported alternative (recommended path on Windows).

Verification

  • bash -n install.sh passes; installer tested on macOS (system bash 3.2) and Debian: fresh install (6 files), idempotent re-run, --project, --overwrite, interactive skip/overwrite/rename, and non-interactive runs.
  • Both wizards exercised end-to-end inside opencode against sandboxed fixture homes covering: remote/local MCP servers (field renames, enablement, disabled + stale entries), skills (including name normalization and Gemini-specific-body flagging), agents (field translation, remote A2A skip), commands (all placeholder rewrites, nested namespacing), extension unpacking, Gemini-mode layout (~/.gemini), and context-file migration (fresh target, merge into existing AGENTS.md, repeat runs).
  • Migrated configs verified to boot opencode cleanly with the items registered; source trees verified byte-identical after every run.
  • Reference docs (source-map.md, mapping.md) verified against the Tabnine CLI and opencode sources.

Root README

One new section, ## Migrate from Tabnine CLI, links to migration_helper/README.md. Otherwise the root README is unchanged.

Licensing

migration_helper/LICENSE scopes this subtree to MIT with Copyright (c) 2026 Tabnine Ltd.. This is a documentation + shell subtree; MIT is the shortest, most permissive, best-known license for that shape. The root repo's proprietary posture (© Tabnine. All rights reserved.) is unchanged — only this subtree is MIT.

Adds migration_helper/ subtree with:

- skills/migrate-from-tabnine-cli/ — interactive wizard skill that
  discovers Tabnine CLI (or Gemini CLI) configuration on disk, asks
  the user per category what to migrate, and translates fields into
  opencode's schema. Includes reference docs for source paths and
  field-by-field translation rules.
- commands/migrate.md — /migrate slash command as an entry point to
  the wizard.
- install.sh — bash installer that copies the skill and command into
  either ~/.config/opencode/ (default) or ./.opencode/ (--project).
  Shows a diff and prompts skip/overwrite/rename on collisions.
  Supports --overwrite to bypass prompts.
- README.md — install (script or manual copy) and usage docs.
- LICENSE — MIT, subtree-scoped.

Root README gets one new section linking to migration_helper/README.md.
@Pavel-tabnine
Pavel-tabnine force-pushed the feat/migration-helper branch 2 times, most recently from f8d7d34 to f59fccc Compare August 2, 2026 11:44
Review-driven fixes to migrate-from-tabnine-cli, verified against
opencode 1.18.9 and tabnine-cli sources and via end-to-end wizard runs:

- MCP: env -> environment (opencode's key; env was silently ignored),
  keep cwd and per-server timeout (both supported), rewrite $VAR/${VAR}
  values to {env:VAR} unconditionally, back up opencode.json before merge
- Commands: single folder-mirroring namespacing policy, inline all three
  placeholder rewrites ({{args}}, !{...}, @{...}), no-placeholder rule
- Skills: defined gemini-ism body scan, defensive name normalization,
  duplicate-name semantics corrected (log-only warning, coin-flip winner)
- Discovery: A2A array-frontmatter bundles skipped up front, built-ins
  shown as "(built-in, skipped)", stale enablement entries ignored,
  extension-enablement respected
- source-map: settings precedence corrected (system tier wins last)
- Wizard UX: inline subagent/primary explanation, defined per-category
  scope override, no migrate-all shortcut

New: migrate-tabnine-context skill + /migrate-context command that
copies TABNINE.md/GEMINI.md context files into AGENTS.md, re-runnable
per repository, merge-or-skip on existing targets.

Installer: installs both skills, survives non-interactive runs
(default-skip when /dev/tty cannot be opened), accurate --overwrite help.
@Pavel-tabnine
Pavel-tabnine force-pushed the feat/migration-helper branch from f59fccc to b6f9b13 Compare August 3, 2026 10:26
… moves

The agent loader globs {agent,agents}/**/*.md, so both spellings load.
The skill called the singular form canonical while the agents docs use
the plural everywhere, which led to a migrated agent being written to
agent/ and then moved to agents/ after the fact.

Pin new agents to agents/ (matching the docs and 'opencode agent
create'), state that both are loaded, and keep an existing singular
folder as-is instead of consolidating. Add two core rules: no
filesystem changes after the Phase 4 summary, and report doc/skill
conflicts to the user instead of silently picking a side.
OPENCODE_CONFIG_DIR (set by the Tabnine wrapper) looked like it
redirected the global config root, which caused a false warning that
the migration had targeted the wrong directory. Tested by pointing the
variable at a scratch dir: 'debug paths' still reports
~/.config/opencode, and marker skills, agents, and mcp entries placed
in the override dir are ALSO loaded. It adds a root, it does not
replace one. Document that, and that installing into both roots
creates non-deterministic duplicates.

Add an optional Phase 5 that verifies a migration actually loaded via
'debug paths', 'debug skill', 'agent list', and 'debug agent'. Offered
in the summary, never run uninvited, and it cannot write.

Add an advisory frontmatter check for unquoted descriptions containing
a colon-space: strict YAML rejects them, current opencode builds parse
them, so report rather than alarm and only quote with approval.

Split the Gemini token grep into broken invocations vs path mentions.
The old bare 'gemini ' substring flagged prose like "upstream Gemini
CLI release" as a stale tool reference.
…claims

Verified against the Tabnine CLI and opencode sources now available
locally.

tools was documented as "drop, opencode uses permission instead".
Tabnine's tools is a YAML list of ALLOWED tool names
(evals/concurrency-safety.eval.ts:14), so dropping it granted the
migrated agent every tool including bash, write and edit — a privilege
escalation created by the migration. Translate it to a deny-by-default
permission block instead, with a name map (read_file -> read,
run_shell_command -> bash, ...) since the two systems name tools
differently. Flag the cases where the mapping unavoidably widens
privilege (replace -> edit gains write) and list unmapped names rather
than dropping them.

MCP timeout defaults differ by two orders of magnitude: Tabnine
MCP_DEFAULT_TIMEOUT_MSEC is 600000 (mcp-client.ts:103), opencode
defaults to 5000. Omitting the field silently cut slow servers to a
5-second budget, so write it explicitly when the source relied on the
default.

Corrected a false claim I introduced: opencode does not resolve
duplicate skill names "by a coin-flip". It logs duplicate skill name
with both locations and the last scanned copy wins — deterministic but
scan-order dependent and invisible in the UI. Fixed in three places
plus the README. Which of the two config roots is scanned last is
still unverified and is now stated as such.

Add three core rules covering untrusted input: path validation before
normalization (a lowercased ../../evil still escapes), never printing
or copying credential values from settings.json, and treating migrated
prompt bodies as data rather than instruction.

Also default the A2A remote-agent branch to skip-with-warning instead
of offering a bare two-option menu.
… budget

Adds the OWASP pre-mutation receipt this skill was missing. New Phase
2.5 resolves every selected item to an absolute destination and prints
a plan — target root and how it resolved, MCP endpoints being granted,
each planned write with a create/backup/overwrite/rename verb, and the
permission changes including any that widen privilege — then stops for
explicit approval. A category selection is no longer approval to
write. Phase 4 now reconciles actuals against that plan and names any
unplanned write, so a partial failure leaves a manifest instead of a
glob. Doubles as the dry-run mode.

Core rule 3 now requires a timestamped backup before overwriting
skills, agents and commands, matching what opencode.json already got;
overwrite was previously unrecoverable for those.

Collision handling was specified three different ways (rule 3 offered
skip/overwrite/rename, Phase 2 only warned, Phase 3 merged without
asking). Unified: Phase 2 flags, Phase 2.5 asks and records the verb,
opencode.json stays the documented exception.

QA pass on my own edits, all found by reading the result rather than
by the checks:
- "see core rule on privilege above" pointed at a rule that does not
  exist; now points at the tool map and states the risk inline.
- The unknown-frontmatter-key claim said such keys "silently do
  nothing". opencode passes them to the model provider as request
  options, so a stale key can reach the provider and be rejected.
- Phase 5's preamble asserted a restart was required and then
  explained why it was not. Rewritten: the commands are accurate
  immediately, the restart is for the user's running session.
- The Phase 1 inventory example printed "MCPs (3)" including a
  built-in, demonstrating the exact confusion the rule below it
  forbids.
- Duplicated Phase 1 lead-in, a stale "in this order" reference to a
  table that is gone, an orphaned Claude-skills paragraph left inside
  Phase 5, and a double blank line.
- "Worth verifying on disk" replaced with three explicit checks.

Moved verification and troubleshooting into
references/verification-and-recovery.md with a JIT trigger, since both
are only needed on request or on failure. Gave source-map.md a real
trigger and deleted the vague "see references/" pointer the rubrics
call out by name. Added a Gotchas section consolidating the facts that
were buried mid-paragraph.

SKILL.md measured with tiktoken cl100k_base rather than estimated:
5,176 tokens before, 4,936 after, against the 5,000 ceiling. 225 lines.
The README and the /migrate command still described the old flow, in
which selecting categories led straight to writing. Both now describe
the four steps, state that category selection does not authorize a
write, and note that asking for the plan alone previews a migration.

Also documented, since all three are user-visible: agent tool
restrictions are carried across as an opencode permission block and any
widened access is reported; MCP timeouts are written explicitly because
the two systems default 10 minutes versus 5 seconds; and existing files
are backed up before an approved overwrite.

Adds a Reference documents section covering the three files under
references/, a short section on confirming a migration loaded, and
corrects the OPENCODE_CONFIG_DIR note to say the directory is read in
addition to ~/.config/opencode rather than instead of it.
migrate-tabnine-context wrote files with no plan, no backup, and none
of the input-handling rules the main skill gained, despite appending to
an AGENTS.md the user already relies on.

Adds a write plan with explicit approval, a timestamped backup before
any merge, path validation for the context.fileName setting (it comes
from a settings file and becomes a path), a rule that source content is
data rather than instruction, and a rule against echoing file contents
since a context file may quote private material. The summary now
reconciles against the plan.

Records one verified difference from the main skill: the global
instruction file resolves from opencode's config root only, so
~/.config/opencode/AGENTS.md is the sole global target even when
OPENCODE_CONFIG_DIR is set. Skills and agents load from both
directories; AGENTS.md does not.

/migrate-context and the README updated to match.
The migration skill told the user either config root was a valid target
without qualification. That is wrong at the edges: if they target an
OPENCODE_CONFIG_DIR, their skills and agents load from there but a
global AGENTS.md never will, so /migrate-context cannot follow the same
choice. The skill now states the exception and says to raise it at the
moment the user picks the override root.

The context skill's note was passive. It now actively checks during
discovery for an AGENTS.md sitting inside an OPENCODE_CONFIG_DIR,
reports it as present but never loaded, and offers to merge its content
into ~/.config/opencode/AGENTS.md through the normal plan and backup
flow, leaving the original in place.
The skill mapped a Tabnine subdirectory context file to a sibling
AGENTS.md and said the two systems differ only in layout. They also
differ in when the file loads, which the mapping hid.

Verified in both sources. Tabnine loads a subdirectory's context on
demand when the agent touches that subtree
(loadJitSubdirectoryMemory), so packages/api/TABNINE.md applies even
from the repository root. opencode resolves instructions with an
upward glob from the session directory and has no on-demand loading,
so packages/api/AGENTS.md is inert unless opencode is started inside
packages/api. Both agree on the global file and on ancestor
directories.

Copying remains the default, since it is correct for anyone who opens
sessions in that directory, but the plan now states the limitation per
file and offers to fold the content into the project-root AGENTS.md
instead, noting that this widens scope to the whole repository. The
summary repeats which files are directory-scoped.

Also: opencode reads CLAUDE.md and CONTEXT.md natively alongside
AGENTS.md, so a context.fileName already set to one of those needs no
migration. The skill previously only recognised AGENTS.md.
Reverts the CLAUDE.md and CONTEXT.md mention added in 652af83. Those
are not Tabnine CLI context files and have no place in a migration
from it. The CLAUDE.md claim was also conditionally wrong, since
opencode only reads it when disableClaudeCodePrompt is false, so
reporting "no migration needed" would have been incorrect for a user
who has it disabled.

The sources are TABNINE.md, GEMINI.md in legacy Gemini mode, or
whatever context.fileName specifies, and the skill now says so
explicitly. AGENTS.md keeps its skip rule, since opencode always reads
it.
…d ones

Two gaps in the nested-file handling added in 652af83.

Discovery only searched the working directory and below. Tabnine also
walks upward from the session directory to the project root
(findUpwardGeminiFiles), so running the wizard from packages/api would
silently miss the repository-root context file — usually the most
important one. Discovery now searches upward as well, stopping at the
repository root or $HOME, and states that ancestor and cwd files
migrate cleanly because opencode loads from the session directory
upward. Only files below it carry the loading caveat.

The downward search can also match many files in a monorepo, which
would make the plan unreadable and the per-file prompt unusable. Above
roughly ten matches the wizard now groups them by depth with counts and
sizes, and offers to accept them as a group, as a group with named
exceptions, or individually. Grouping keeps the prompt readable without
removing the decision.
The migration source is Tabnine CLI, so the skill no longer branches
on the ORIG_GEMINI mode or scans .gemini/ paths. Removed from
discovery, from the context filename defaults, from the descriptions
of both skills, from both slash commands, from the README, and from
source-map.md. A user still on plain Gemini CLI is out of scope.

Grep patterns that flag Gemini-specific tooling inside migrated skill
bodies are kept — that is a semantic check on the copied content, not
a supported migration source.

Rewrote references/source-map.md to describe behaviour rather than
implementation. All packages/*.ts:line citations are gone. What Tabnine
CLI reads, in what order, and from where is stated as fact rather than
attributed to a source line the reader does not have. Applied the same
rule elsewhere (source-map pointer in SKILL.md, the note about the
Tabnine dev-mode env override).

README and both slash commands updated to match the narrower scope.
Re-checked two claims the skill makes about opencode's Tabnine plugin,
now that its source is on hand.

Confirmed: the plugin registers tabnine-context and tabnine-coaching
automatically. The names match. Never migrate them as mcp entries.

The disabled-in-Tabnine case was under-handled. If mcp-server-enablement
disables either, skipping the server does not carry the opt-out over —
the plugin re-enables it by default. Fixed by translating the opt-out
into plugin options rather than an mcp.<name>.enabled entry (which the
plugin does not read). SKILL.md carries a one-liner and points to
references/mapping.md, where the full table and an opencode.json plugin
tuple example live.

Corrected an imprecise auth claim. Old text: "the plugin already
provides Tabnine authentication, so the CLI credential files should not
be touched." True in outcome, wrong in reason — the plugin uses its own
credential path (~/.local/share/opencode/auth.json) and cannot read the
Tabnine CLI's credential files. The user must sign in to the plugin
separately after migrating. Updated in source-map.md and the README.
Fixes an outright contradiction and removes several duplications
surfaced by an independent review. Verified opencode's behaviour
against its own binary before editing.

Correctness. The skill claimed unknown agent frontmatter keys either
"reach the provider API and be rejected" (SKILL.md) or "silently do
nothing" (mapping.md). Neither is right. Verified by loading a probe
agent: unknown keys are collected into `options` and forwarded to the
model provider; most providers ignore what they don't recognise, some
reject the request. Both files now say that. Same probe confirmed that
a Tabnine `tools:` list triggers ConfigInvalidError, so translating it
into `permission` is not optional — the file otherwise fails to load.

Structure. "Phase 2.5" was an iteration scar. Renumbered 2.5 -> 3,
3 -> 4, 4 -> 5 across SKILL.md and every internal reference. Phase 5
is now an explicitly numbered sequence rather than an unordered
bullet list, and the context skill's Phase 1 gets its missing "step 4"
label. verification-and-recovery.md no longer cross-references
another file's rule number.

Menu defaults. The collision menus in both skills previously offered
skip/overwrite/rename or merge/skip with no default. Named defaults:
overwrite-with-backup for the main skill, merge-with-backup for the
context skill.

Path-safety parity. Both skills now cover the same threat model
(`..`, path separator, leading `/` or `~`, control character, symlink
out of root, check before normalisation).

Category list drift. The description, README, /migrate command, and
the four-step summary each listed migration categories in a different
order and sometimes dropped agents entirely. Canonicalised to
"MCP servers, skills, agents, slash commands, and extension contents"
everywhere.

Dedup. The plugin opt-out table (enableRemoteCodeSearch etc.) lived in
both mapping.md and source-map.md; source-map now points to the
mapping.md table. The frontmatter sanity check and the agent-folder-
preference paragraphs each drop from four sentences to two.
@Pavel-tabnine
Pavel-tabnine merged commit 0ae5c90 into main Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants