From 5349ac4fdeea51294aad74f5a63bae50232d2fbe Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Sat, 30 May 2026 00:59:28 +0800 Subject: [PATCH 1/2] feat: add CodeBuddy target support Add CodeBuddy as a new installer target. CodeBuddy follows the same config layout as Claude Code, using .codebuddy/ directory and CODEBUDDY.md instead of .claude/ and CLAUDE.md. Closes https://github.com/colbymchenry/codegraph/issues/164 --- .../content/docs/reference/integrations.md | 1 + src/installer/targets/codebuddy.ts | 199 ++++++++++++++++++ src/installer/targets/registry.ts | 2 + src/installer/targets/types.ts | 2 +- 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/installer/targets/codebuddy.ts diff --git a/site/src/content/docs/reference/integrations.md b/site/src/content/docs/reference/integrations.md index 67e7b96e4..f71c0cf6e 100644 --- a/site/src/content/docs/reference/integrations.md +++ b/site/src/content/docs/reference/integrations.md @@ -8,6 +8,7 @@ The interactive installer auto-detects and configures each supported agent — w ## Supported agents - **Claude Code** +- **CodeBuddy** - **Cursor** - **Codex CLI** - **opencode** diff --git a/src/installer/targets/codebuddy.ts b/src/installer/targets/codebuddy.ts new file mode 100644 index 000000000..0153eabf5 --- /dev/null +++ b/src/installer/targets/codebuddy.ts @@ -0,0 +1,199 @@ +/** + * CodeBuddy target. Writes: + * + * - MCP server entry to `~/.codebuddy.json` (global = user scope, loads + * in every project) or `./.mcp.json` (local = project scope). + * - Permissions to `~/.codebuddy/settings.json` (global) or + * `./.codebuddy/settings.json` (local), gated on `autoAllow`. + * - Instructions to `~/.codebuddy/CODEBUDDY.md` (global) or + * `./.codebuddy/CODEBUDDY.md` (local). + * + * CodeBuddy follows the same config layout as Claude Code, with + * `.codebuddy` replacing `.claude` and `CODEBUDDY.md` replacing + * `CLAUDE.md`. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { + AgentTarget, + DetectionResult, + InstallOptions, + Location, + WriteResult, +} from './types'; +import { + getCodeGraphPermissions, + getMcpServerConfig, + jsonDeepEqual, + readJsonFile, + removeMarkedSection, + writeJsonFile, +} from './shared'; +import { + CODEGRAPH_SECTION_END, + CODEGRAPH_SECTION_START, +} from '../instructions-template'; + +function configDir(loc: Location): string { + return loc === 'global' + ? path.join(os.homedir(), '.codebuddy') + : path.join(process.cwd(), '.codebuddy'); +} +function mcpJsonPath(loc: Location): string { + // global → ~/.codebuddy.json (user scope: visible in every project). + // local → ./.mcp.json (project scope). + return loc === 'global' + ? path.join(os.homedir(), '.codebuddy.json') + : path.join(process.cwd(), '.mcp.json'); +} +function settingsJsonPath(loc: Location): string { + return path.join(configDir(loc), 'settings.json'); +} +function instructionsPath(loc: Location): string { + return path.join(configDir(loc), 'CODEBUDDY.md'); +} + +class CodeBuddyTarget implements AgentTarget { + readonly id = 'codebuddy' as const; + readonly displayName = 'CodeBuddy'; + readonly docsUrl = 'https://cnb.cool/codebuddy/codebuddy-code'; + + supportsLocation(_loc: Location): boolean { + return true; + } + + detect(loc: Location): DetectionResult { + const mcpPath = mcpJsonPath(loc); + const config = readJsonFile(mcpPath); + const alreadyConfigured = !!config.mcpServers?.codegraph; + const installed = loc === 'global' + ? fs.existsSync(configDir(loc)) || fs.existsSync(mcpPath) + : fs.existsSync(mcpPath) || fs.existsSync(configDir(loc)); + return { installed, alreadyConfigured, configPath: mcpPath }; + } + + install(loc: Location, opts: InstallOptions): WriteResult { + const files: WriteResult['files'] = []; + + // 1. MCP server entry + files.push(writeMcpEntry(loc)); + + // 2. Permissions (only when autoAllow) + if (opts.autoAllow) { + files.push(writePermissionsEntry(loc)); + } + + // 3. CODEBUDDY.md instructions — no longer written. The codegraph + // usage guidance now ships solely in the MCP server's `initialize` + // response. Strip any block a previous install left behind. + const instrCleanup = removeInstructionsEntry(loc); + if (instrCleanup.action === 'removed') files.push(instrCleanup); + + return { files }; + } + + uninstall(loc: Location): WriteResult { + const files: WriteResult['files'] = []; + + // 1. MCP server entry + const mcpPath = mcpJsonPath(loc); + const config = readJsonFile(mcpPath); + if (config.mcpServers?.codegraph) { + delete config.mcpServers.codegraph; + if (Object.keys(config.mcpServers).length === 0) { + delete config.mcpServers; + } + writeJsonFile(mcpPath, config); + files.push({ path: mcpPath, action: 'removed' }); + } else { + files.push({ path: mcpPath, action: 'not-found' }); + } + + // 2. Permissions + const settingsPath = settingsJsonPath(loc); + const settings = readJsonFile(settingsPath); + if (Array.isArray(settings.permissions?.allow)) { + const before = settings.permissions.allow.length; + settings.permissions.allow = settings.permissions.allow.filter( + (p: string) => !p.startsWith('mcp__codegraph__'), + ); + if (settings.permissions.allow.length !== before) { + if (settings.permissions.allow.length === 0) { + delete settings.permissions.allow; + } + if (Object.keys(settings.permissions).length === 0) { + delete settings.permissions; + } + writeJsonFile(settingsPath, settings); + files.push({ path: settingsPath, action: 'removed' }); + } else { + files.push({ path: settingsPath, action: 'not-found' }); + } + } else { + files.push({ path: settingsPath, action: 'not-found' }); + } + + // 3. Instructions — strip the legacy CodeGraph block if present. + files.push(removeInstructionsEntry(loc)); + + return { files }; + } + + printConfig(loc: Location): string { + const target = mcpJsonPath(loc); + const snippet = JSON.stringify({ mcpServers: { codegraph: getMcpServerConfig() } }, null, 2); + return `# Add to ${target}\n\n${snippet}\n`; + } + + describePaths(loc: Location): string[] { + return [mcpJsonPath(loc), settingsJsonPath(loc), instructionsPath(loc)]; + } +} + +export function writeMcpEntry(loc: Location): WriteResult['files'][number] { + const file = mcpJsonPath(loc); + const existing = readJsonFile(file); + const before = existing.mcpServers?.codegraph; + const after = getMcpServerConfig(); + + if (jsonDeepEqual(before, after)) { + return { path: file, action: 'unchanged' }; + } + const action: 'created' | 'updated' = before ? 'updated' : (fs.existsSync(file) ? 'updated' : 'created'); + if (!existing.mcpServers) existing.mcpServers = {}; + existing.mcpServers.codegraph = after; + writeJsonFile(file, existing); + return { path: file, action }; +} + +export function writePermissionsEntry(loc: Location): WriteResult['files'][number] { + const file = settingsJsonPath(loc); + const settings = readJsonFile(file); + const created = !fs.existsSync(file); + + if (!settings.permissions) settings.permissions = {}; + if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = []; + + const want = getCodeGraphPermissions(); + const before = [...settings.permissions.allow]; + for (const perm of want) { + if (!settings.permissions.allow.includes(perm)) { + settings.permissions.allow.push(perm); + } + } + if (jsonDeepEqual(before, settings.permissions.allow) && !created) { + return { path: file, action: 'unchanged' }; + } + writeJsonFile(file, settings); + return { path: file, action: created ? 'created' : 'updated' }; +} + +export function removeInstructionsEntry(loc: Location): WriteResult['files'][number] { + const file = instructionsPath(loc); + const action = removeMarkedSection(file, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END); + return { path: file, action }; +} + +export const codebuddyTarget: AgentTarget = new CodeBuddyTarget(); diff --git a/src/installer/targets/registry.ts b/src/installer/targets/registry.ts index 5e929d468..03e7a6b66 100644 --- a/src/installer/targets/registry.ts +++ b/src/installer/targets/registry.ts @@ -9,6 +9,7 @@ import { AgentTarget, Location, TargetId } from './types'; import { claudeTarget } from './claude'; +import { codebuddyTarget } from './codebuddy'; import { cursorTarget } from './cursor'; import { codexTarget } from './codex'; import { opencodeTarget } from './opencode'; @@ -19,6 +20,7 @@ import { kiroTarget } from './kiro'; export const ALL_TARGETS: readonly AgentTarget[] = Object.freeze([ claudeTarget, + codebuddyTarget, cursorTarget, codexTarget, opencodeTarget, diff --git a/src/installer/targets/types.ts b/src/installer/targets/types.ts index 4b3267e97..c28c8015e 100644 --- a/src/installer/targets/types.ts +++ b/src/installer/targets/types.ts @@ -19,7 +19,7 @@ export type Location = 'global' | 'local'; * lookup. New targets add a value here when they're added to the * registry. Keep these short and lowercase. */ -export type TargetId = 'claude' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro'; +export type TargetId = 'claude' | 'codebuddy' | 'cursor' | 'codex' | 'opencode' | 'hermes' | 'gemini' | 'antigravity' | 'kiro'; /** * Result of `target.detect(location)`. From fbee2beaece189f9efbd84f8f4b3038d2f0e0096 Mon Sep 17 00:00:00 2001 From: DevinZeng Date: Fri, 5 Jun 2026 18:53:00 +0800 Subject: [PATCH 2/2] docs: add CodeBuddy to all agent listings, fix global MCP path, explain CODEBUDDY.md write constraint (#529) --- README.md | 4 +- .../docs/getting-started/installation.md | 4 +- .../docs/getting-started/introduction.md | 2 +- .../docs/getting-started/quickstart.md | 2 +- site/src/content/docs/guides/indexing.md | 2 +- .../content/docs/reference/integrations.md | 36 ++++++++++ src/installer/index.ts | 2 +- src/installer/targets/codebuddy.ts | 65 ++++++++++++++++--- 8 files changed, 100 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 250b507af..43fce8e89 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # CodeGraph -### Supercharge Claude Code, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, and Kiro with Semantic Code Intelligence +### Supercharge Claude Code, CodeBuddy, Cursor, Codex, OpenCode, Hermes Agent, Gemini, Antigravity, and Claude with Semantic Code Intelligence **~16% cheaper · ~58% fewer tool calls · 100% local** @@ -17,6 +17,7 @@ [![Linux](https://img.shields.io/badge/Linux-supported-blue.svg)](#supported-platforms) [![Claude Code](https://img.shields.io/badge/Claude_Code-supported-blueviolet.svg)](#supported-agents) +[![CodeBuddy](https://img.shields.io/badge/CodeBuddy-supported-blueviolet.svg)](#supported-agents) [![Cursor](https://img.shields.io/badge/Cursor-supported-blueviolet.svg)](#supported-agents) [![Codex](https://img.shields.io/badge/Codex-supported-blueviolet.svg)](#supported-agents) [![opencode](https://img.shields.io/badge/opencode-supported-blueviolet.svg)](#supported-agents) @@ -601,6 +602,7 @@ the MCP server (which delivers its own usage guidance, so no instructions file is written): - **Claude Code** +- **CodeBuddy** - **Cursor** - **Codex CLI** - **opencode** diff --git a/site/src/content/docs/getting-started/installation.md b/site/src/content/docs/getting-started/installation.md index 4e8a0e2ce..ee0dda9a5 100644 --- a/site/src/content/docs/getting-started/installation.md +++ b/site/src/content/docs/getting-started/installation.md @@ -11,7 +11,7 @@ npx @colbymchenry/codegraph The installer will: -- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, and **Kiro**. +- Ask which agent(s) to configure — auto-detecting installed ones from **Claude Code**, **CodeBuddy**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity IDE**, and **Kiro**. - Prompt to install `codegraph` on your `PATH` (so agents can launch the MCP server). - Ask whether configs apply to all your projects or just this one. - Write each chosen agent's MCP server config plus an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~/.codex/AGENTS.md`). @@ -37,7 +37,7 @@ codegraph install --print-config codex # print snippet, no file wr ## 2. Restart your agent -Restart your agent (Claude Code / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load. +Restart your agent (Claude Code / CodeBuddy / Cursor / Codex CLI / opencode / Hermes Agent / Gemini CLI / Antigravity IDE / Kiro) for the MCP server to load. ## 3. Initialize projects diff --git a/site/src/content/docs/getting-started/introduction.md b/site/src/content/docs/getting-started/introduction.md index 055085865..713e3fd5f 100644 --- a/site/src/content/docs/getting-started/introduction.md +++ b/site/src/content/docs/getting-started/introduction.md @@ -5,7 +5,7 @@ description: What CodeGraph is, and why it makes AI coding agents faster and che CodeGraph is a **local-first code-intelligence tool**. It parses your codebase with [tree-sitter](https://tree-sitter.github.io/), stores every symbol, edge, and file in a local SQLite database, and exposes the result as a queryable **knowledge graph** — over the [Model Context Protocol (MCP)](/codegraph/reference/mcp-server/), a CLI, and a TypeScript library. -It exists to make AI coding agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — **answer structural questions without scanning files**. Instead of fanning out across `grep`, `glob`, and `Read` to reconstruct how code fits together, an agent queries a pre-built index and gets the answer in a handful of calls. +It exists to make AI coding agents — Claude Code, CodeBuddy, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, and Kiro — **answer structural questions without scanning files**. Instead of fanning out across `grep`, `glob`, and `Read` to reconstruct how code fits together, an agent queries a pre-built index and gets the answer in a handful of calls. ## Why it matters diff --git a/site/src/content/docs/getting-started/quickstart.md b/site/src/content/docs/getting-started/quickstart.md index 1d63bb489..f3da5eb5b 100644 --- a/site/src/content/docs/getting-started/quickstart.md +++ b/site/src/content/docs/getting-started/quickstart.md @@ -22,7 +22,7 @@ npx @colbymchenry/codegraph # zero-install, or: npm i -g @colbymchenry/codegraph ``` -CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro. +CodeGraph bundles its own runtime — nothing to compile, no native build, works the same everywhere. The interactive installer auto-configures your agent(s) — Claude Code, CodeBuddy, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, Antigravity IDE, Kiro. ## Initialize Projects diff --git a/site/src/content/docs/guides/indexing.md b/site/src/content/docs/guides/indexing.md index 7c0fd1ddc..2ece1ccee 100644 --- a/site/src/content/docs/guides/indexing.md +++ b/site/src/content/docs/guides/indexing.md @@ -24,7 +24,7 @@ codegraph sync # incremental — only changed files ## Stay fresh automatically -**You don't need to run `codegraph sync` by hand during an agent session.** When your agent (Claude Code, Cursor, Codex, opencode, Hermes, Gemini, Antigravity, Kiro) launches `codegraph serve --mcp`, three layers cooperate to keep the index in step with your code — and to never give the agent a quiet wrong answer in the small window between an edit and the next sync. +**You don't need to run `codegraph sync` by hand during an agent session.** When your agent (Claude Code, CodeBuddy, Cursor, Codex, opencode, Hermes, Gemini, Antigravity, Kiro) launches `codegraph serve --mcp`, three layers cooperate to keep the index in step with your code — and to never give the agent a quiet wrong answer in the small window between an edit and the next sync. ### 1. File watcher with debounced auto-sync (always on) diff --git a/site/src/content/docs/reference/integrations.md b/site/src/content/docs/reference/integrations.md index f71c0cf6e..cf3130165 100644 --- a/site/src/content/docs/reference/integrations.md +++ b/site/src/content/docs/reference/integrations.md @@ -27,6 +27,8 @@ If you'd rather wire it up yourself, install globally: npm install -g @colbymchenry/codegraph ``` +### Claude Code + Add the MCP server to `~/.claude.json`: ```json @@ -60,6 +62,40 @@ Optionally auto-allow the read-only tools in `~/.claude/settings.json`: } ``` +### CodeBuddy + +Add the MCP server to `~/.codebuddy/mcp.json`: + +```json +{ + "mcpServers": { + "codegraph": { + "type": "stdio", + "command": "codegraph", + "args": ["serve", "--mcp"] + } + } +} +``` + +Optionally auto-allow the read-only tools in `~/.codebuddy/settings.json`: + +```json +{ + "permissions": { + "allow": [ + "mcp__codegraph__codegraph_search", + "mcp__codegraph__codegraph_context", + "mcp__codegraph__codegraph_callers", + "mcp__codegraph__codegraph_callees", + "mcp__codegraph__codegraph_impact", + "mcp__codegraph__codegraph_node", + "mcp__codegraph__codegraph_status" + ] + } +} +``` + :::tip Cursor launches MCP subprocesses with the wrong working directory. The installer handles this for you by injecting a `--path` argument; if you wire Cursor up by hand, pass the project path explicitly. ::: diff --git a/src/installer/index.ts b/src/installer/index.ts index edd48ecaf..a1e7eb69a 100644 --- a/src/installer/index.ts +++ b/src/installer/index.ts @@ -317,7 +317,7 @@ export async function runUninstaller(opts: RunUninstallerOptions): Promise const sel = await clack.select({ message: 'Remove CodeGraph from all your projects, or just this one?', options: [ - { value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro' }, + { value: 'global' as const, label: 'All projects (global)', hint: '~/.claude, ~/.codebuddy, ~/.cursor, ~/.codex, ~/.config/opencode, ~/.hermes, ~/.gemini, ~/.kiro' }, { value: 'local' as const, label: 'Just this project (local)', hint: './.claude, ./.cursor, ./opencode.jsonc, ./.gemini, ./.kiro' }, ], initialValue: 'global' as const, diff --git a/src/installer/targets/codebuddy.ts b/src/installer/targets/codebuddy.ts index 0153eabf5..5dc0420c1 100644 --- a/src/installer/targets/codebuddy.ts +++ b/src/installer/targets/codebuddy.ts @@ -1,8 +1,8 @@ /** * CodeBuddy target. Writes: * - * - MCP server entry to `~/.codebuddy.json` (global = user scope, loads - * in every project) or `./.mcp.json` (local = project scope). + * - MCP server entry to `~/.codebuddy/mcp.json` (global = user scope, + * loads in every project) or `./.mcp.json` (local = project scope). * - Permissions to `~/.codebuddy/settings.json` (global) or * `./.codebuddy/settings.json` (local), gated on `autoAllow`. * - Instructions to `~/.codebuddy/CODEBUDDY.md` (global) or @@ -10,7 +10,18 @@ * * CodeBuddy follows the same config layout as Claude Code, with * `.codebuddy` replacing `.claude` and `CODEBUDDY.md` replacing - * `CLAUDE.md`. + * `CLAUDE.md`. The global MCP config lives inside `~/.codebuddy/` + * (not as a top-level dotfile). + * + * NOTE — why CODEBUDDY.md is still written (unlike Claude Code): + * CodeBuddy Code does not surface the MCP `initialize` response's + * `instructions` field to the model. Claude Code / Codex / Gemini / + * Cursor all receive the SERVER_INSTRUCTIONS playbook automatically + * via that field (issue #529 removed the MD writes for those targets). + * Until CodeBuddy supports `initialize` instructions natively, we must + * keep writing to CODEBUDDY.md — when that support lands, apply the + * same #529 migration pattern: drop `writeInstructionsEntry` from + * `install()` and add a self-healing `removeInstructionsEntry` cleanup. */ import * as fs from 'fs'; @@ -29,12 +40,14 @@ import { jsonDeepEqual, readJsonFile, removeMarkedSection, + replaceOrAppendMarkedSection, writeJsonFile, } from './shared'; import { CODEGRAPH_SECTION_END, CODEGRAPH_SECTION_START, } from '../instructions-template'; +import { SERVER_INSTRUCTIONS } from '../../mcp/server-instructions'; function configDir(loc: Location): string { return loc === 'global' @@ -42,10 +55,10 @@ function configDir(loc: Location): string { : path.join(process.cwd(), '.codebuddy'); } function mcpJsonPath(loc: Location): string { - // global → ~/.codebuddy.json (user scope: visible in every project). + // global → ~/.codebuddy/mcp.json (user scope: visible in every project). // local → ./.mcp.json (project scope). return loc === 'global' - ? path.join(os.homedir(), '.codebuddy.json') + ? path.join(os.homedir(), '.codebuddy', 'mcp.json') : path.join(process.cwd(), '.mcp.json'); } function settingsJsonPath(loc: Location): string { @@ -85,11 +98,24 @@ class CodeBuddyTarget implements AgentTarget { files.push(writePermissionsEntry(loc)); } - // 3. CODEBUDDY.md instructions — no longer written. The codegraph - // usage guidance now ships solely in the MCP server's `initialize` - // response. Strip any block a previous install left behind. - const instrCleanup = removeInstructionsEntry(loc); - if (instrCleanup.action === 'removed') files.push(instrCleanup); + // 3. CODEBUDDY.md instructions + // + // CodeBuddy Code does NOT surface the MCP `initialize` response's + // `instructions` field to the model, so the technique used for + // Claude Code / Codex / Gemini / Cursor (issue #529) does not work + // here. Those agents receive SERVER_INSTRUCTIONS automatically on + // every session via the MCP handshake; CodeBuddy silently discards + // that field today. + // + // As a result we must write the SERVER_INSTRUCTIONS playbook directly + // into CODEBUDDY.md so the agent knows to prefer codegraph tools over + // grep/find/Read exploration. + // + // If CodeBuddy adds first-class support for the MCP `initialize` + // instructions field in the future, this target should be updated to + // drop the CODEBUDDY.md write (and add a self-healing cleanup step, + // the same pattern used in the #529 migration for the other targets). + files.push(writeInstructionsEntry(loc)); return { files }; } @@ -190,6 +216,25 @@ export function writePermissionsEntry(loc: Location): WriteResult['files'][numbe return { path: file, action: created ? 'created' : 'updated' }; } +function codeGraphInstructionsBody(): string { + return ( + CODEGRAPH_SECTION_START + '\n' + + SERVER_INSTRUCTIONS + '\n' + + CODEGRAPH_SECTION_END + ); +} + +export function writeInstructionsEntry(loc: Location): WriteResult['files'][number] { + const file = instructionsPath(loc); + const body = codeGraphInstructionsBody(); + const raw = replaceOrAppendMarkedSection(file, body, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END); + // `appended` means the markers weren't found and the section was added + // at the end — map it to `updated` for the installer log line. + const action: WriteResult['files'][number]['action'] = + raw === 'appended' ? 'updated' : raw; + return { path: file, action }; +} + export function removeInstructionsEntry(loc: Location): WriteResult['files'][number] { const file = instructionsPath(loc); const action = removeMarkedSection(file, CODEGRAPH_SECTION_START, CODEGRAPH_SECTION_END);