From 7e6d2e5060aa6cf4a8db544c0fce22acbe8b9838 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Wed, 4 Mar 2026 23:36:24 +0100 Subject: [PATCH 1/9] feat: add VS Code and Copilot CLI as editor targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for two new editors in `archgate init`: - VS Code (`--editor vscode`): configures .vscode/settings.json with the git marketplace URL and MCP server - Copilot CLI (`--editor copilot`): auto-installs via `copilot plugin install` or prints manual commands, configures .github/copilot/mcp.json Both use the same git-based plugin format already served by the plugins service — no backend changes needed. Includes documentation guides for both editors and sidebar entries. Co-Authored-By: Claude Opus 4.6 --- .../src/content/docs/guides/vscode-plugin.mdx | 63 ++----- src/commands/init.ts | 2 +- src/helpers/copilot-settings.ts | 67 +++++++- src/helpers/init-project.ts | 18 +- src/helpers/plugin-install.ts | 32 ++-- src/helpers/vscode-settings.ts | 156 ++++++++---------- 6 files changed, 176 insertions(+), 162 deletions(-) diff --git a/docs/src/content/docs/guides/vscode-plugin.mdx b/docs/src/content/docs/guides/vscode-plugin.mdx index 197034af..5e0b5a3d 100644 --- a/docs/src/content/docs/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/guides/vscode-plugin.mdx @@ -7,16 +7,12 @@ The Archgate VS Code plugin gives AI agents working in [VS Code](https://code.vi ## How it works -VS Code supports **agent plugins** installed from git-based marketplaces. The Archgate plugin is served from a git repository at `plugins.archgate.dev/archgate-vscode.git`. When you add this marketplace to your VS Code user settings, VS Code discovers and installs the plugin automatically. +VS Code supports **agent plugins** installed from git-based marketplaces. The Archgate plugin is served from a git repository at `plugins.archgate.dev/archgate.git`. When you add this marketplace to your VS Code settings, VS Code discovers and installs the plugin automatically. -The plugin is served in VS Code Copilot's native `.github/plugin/` manifest format, separate from the Claude Code `.claude-plugin/` format. +The plugin uses the same format as Claude Code plugins (`.claude-plugin/plugin.json`), which VS Code's agent plugin system recognizes natively. ## Installation -:::caution[VS Code version requirement] -Agent plugins require **VS Code 1.110 (February 2026 release) or later**. Earlier versions do not support git-based agent plugin marketplaces. Check your version with `code --version`. -::: - :::note[Beta access required] The VS Code plugin is currently in beta. [Sign up at plugins.archgate.dev](https://plugins.archgate.dev) to get access before following the steps below. ::: @@ -42,16 +38,8 @@ archgate init --editor vscode If you are already logged in, this command: 1. Creates the `.archgate/` governance directory (ADRs, lint rules) -2. Creates `.vscode/mcp.json` with the Archgate MCP server configuration -3. Adds the authenticated marketplace URL to your VS Code **user settings** - -The `chat.plugins.marketplaces` setting is application-scoped in VS Code, so it cannot be set per-workspace. The CLI automatically writes it to your user-level `settings.json`: - -| Platform | User settings path | -| -------- | ------------------------------------------------------- | -| Windows | `%APPDATA%\Code\User\settings.json` | -| macOS | `~/Library/Application Support/Code/User/settings.json` | -| Linux | `~/.config/Code/User/settings.json` | +2. Configures `.vscode/settings.json` with the authenticated marketplace URL +3. Registers the Archgate MCP server in `.vscode/settings.json` To explicitly request plugin installation: @@ -59,43 +47,27 @@ To explicitly request plugin installation: archgate init --editor vscode --install-plugin ``` -### Generated files +### Generated settings -The command creates or updates the following files: - -| File | Scope | Purpose | -| -------------------- | --------- | -------------------------------------------------------- | -| `.vscode/mcp.json` | Workspace | MCP server configuration so VS Code can access your ADRs | -| User `settings.json` | User | `chat.plugins.marketplaces` with the authenticated URL | - -If `.vscode/mcp.json` already exists, Archgate merges its configuration additively -- existing server entries are preserved. The user settings file is also merged additively -- existing settings are never overwritten. - -The workspace MCP configuration (`.vscode/mcp.json`): +The command adds the following to `.vscode/settings.json` (existing settings are preserved): ```json { - "servers": { - "archgate": { - "command": "archgate", - "args": ["mcp"] + "chat.plugins.marketplaces": [ + "https://:@plugins.archgate.dev/archgate.git" + ], + "mcp": { + "servers": { + "archgate": { + "command": "archgate", + "args": ["mcp"] + } } } } ``` -The user-level marketplace setting (added to your `settings.json`): - -```json -{ - "chat.plugins.marketplaces": [ - "https://:@plugins.archgate.dev/archgate-vscode.git" - ] -} -``` - -### Manual setup - -If you prefer not to let the CLI modify your user settings, you can add the marketplace URL manually. Open VS Code's user settings JSON (`Ctrl+Shift+P` → "Preferences: Open User Settings (JSON)") and add the `chat.plugins.marketplaces` entry shown above. You can find your authenticated URL by running `archgate login` and checking `~/.archgate/credentials`. +If `.vscode/settings.json` already exists, Archgate merges its settings additively -- existing entries are preserved. ## What the plugin provides @@ -159,7 +131,6 @@ The MCP server runs locally and reads directly from your `.archgate/adrs/` direc ## Tips -- **Commit `.vscode/mcp.json`** to share the MCP configuration with your team. -- **Each developer runs `archgate init --editor vscode`** after `archgate login` to configure their user-level marketplace URL. +- **Commit `.vscode/settings.json`** to share the marketplace and MCP configuration with your team. - **Run onboard once per project** to generate your initial ADRs from your actual codebase. - **Keep ADR rule files up to date** -- the agent enforces what the rules check for. diff --git a/src/commands/init.ts b/src/commands/init.ts index 50bfce12..874b383c 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -107,7 +107,7 @@ function printManualInstructions(editor: EditorTarget, detail?: string): void { console.log(` ${styleText("bold", "copilot plugin install")} ${detail}`); break; default: - // cursor auto-installs always — should not reach here + // vscode and cursor auto-install always — should not reach here break; } } diff --git a/src/helpers/copilot-settings.ts b/src/helpers/copilot-settings.ts index 68b2c70b..e8a4afdf 100644 --- a/src/helpers/copilot-settings.ts +++ b/src/helpers/copilot-settings.ts @@ -1,20 +1,77 @@ import { join } from "node:path"; import { existsSync, mkdirSync } from "node:fs"; +/** + * MCP server configuration that archgate injects for Copilot CLI. + * + * Copilot CLI uses the same `.github/copilot/mcp.json` format for MCP servers + * and supports git-based plugin repositories via `copilot plugin install`. + */ +export const ARCHGATE_COPILOT_MCP_CONFIG = { + mcpServers: { + archgate: { + command: "archgate", + args: ["mcp"], + }, + }, +} as const; + +type CopilotMcpConfig = Record; + +/** + * Pure, additive merge of archgate MCP server config into existing Copilot MCP config. + * + * - Preserves all existing MCP server entries + * - Adds the archgate server only if not already present + */ +export function mergeCopilotMcpConfig( + existing: CopilotMcpConfig, + archgate: typeof ARCHGATE_COPILOT_MCP_CONFIG +): CopilotMcpConfig { + const existingServers = + typeof existing.mcpServers === "object" && + existing.mcpServers !== null && + !Array.isArray(existing.mcpServers) + ? (existing.mcpServers as Record) + : {}; + + return { + ...existing, + mcpServers: { + ...existingServers, + ...archgate.mcpServers, + }, + }; +} + /** * Configure Copilot CLI settings for archgate integration. * - * Creates the `.github/copilot/` directory if it does not exist. - * Plugin installation is handled separately via `archgate init --install-plugin`. + * Creates/updates `.github/copilot/mcp.json` with archgate MCP server. * - * @returns Absolute path to the `.github/copilot/` directory. + * @returns Absolute path to the MCP config file. */ -export function configureCopilotSettings(projectRoot: string): string { +export async function configureCopilotSettings( + projectRoot: string +): Promise { const copilotDir = join(projectRoot, ".github", "copilot"); + const mcpConfigPath = join(copilotDir, "mcp.json"); + // Read existing MCP config or start with empty object + let existing: CopilotMcpConfig = {}; + if (existsSync(mcpConfigPath)) { + const content = await Bun.file(mcpConfigPath).text(); + existing = JSON.parse(content) as CopilotMcpConfig; + } + + const merged = mergeCopilotMcpConfig(existing, ARCHGATE_COPILOT_MCP_CONFIG); + + // Ensure directories exist if (!existsSync(copilotDir)) { mkdirSync(copilotDir, { recursive: true }); } - return copilotDir; + await Bun.write(mcpConfigPath, JSON.stringify(merged, null, 2) + "\n"); + + return mcpConfigPath; } diff --git a/src/helpers/init-project.ts b/src/helpers/init-project.ts index d37b4087..0c2b6e78 100644 --- a/src/helpers/init-project.ts +++ b/src/helpers/init-project.ts @@ -116,13 +116,15 @@ async function configureEditorSettings( case "cursor": return configureCursorSettings(projectRoot); case "vscode": { - // VS Code: marketplace URL to user settings if logged in + // VS Code needs the marketplace URL for settings — use a placeholder + // that gets replaced during plugin install if credentials exist. const { loadCredentials } = await import("./auth"); const creds = await loadCredentials(); - const marketplaceUrl = creds - ? (await import("./plugin-install")).buildVscodeMarketplaceUrl(creds) - : undefined; - return configureVscodeSettings(projectRoot, marketplaceUrl); + const { buildMarketplaceUrl } = await import("./plugin-install"); + const url = creds + ? buildMarketplaceUrl(creds) + : "https://plugins.archgate.dev/archgate.git"; + return configureVscodeSettings(projectRoot, url); } case "copilot": return configureCopilotSettings(projectRoot); @@ -156,12 +158,12 @@ async function tryInstallPlugin( } if (editor === "vscode") { - // VS Code marketplace URL is already added to user settings by configureEditorSettings. - // The --install-plugin flag is a no-op for VS Code since init handles everything. + const { installVscodePlugin } = await import("./plugin-install"); + await installVscodePlugin(projectRoot, credentials); return { installed: true, autoInstalled: true, - detail: "Marketplace URL added to VS Code user settings", + detail: "Configured marketplace URL in .vscode/settings.json", }; } diff --git a/src/helpers/plugin-install.ts b/src/helpers/plugin-install.ts index 035b9a2c..ffb4b976 100644 --- a/src/helpers/plugin-install.ts +++ b/src/helpers/plugin-install.ts @@ -2,7 +2,7 @@ * plugin-install.ts — Download and install the archgate plugin for supported editors. * * - Claude Code: auto-installs via `claude` CLI, or prints manual commands as fallback - * - VS Code: marketplace URL for manual user-settings configuration (application-scoped) + * - VS Code: configures .vscode/settings.json with marketplace URL (git-based plugin) * - Copilot CLI: auto-installs via `copilot` CLI, or prints manual commands as fallback * - Cursor: downloads cursor.tar.gz from the plugins service and extracts it */ @@ -44,16 +44,6 @@ export function buildMarketplaceUrl(credentials: StoredCredentials): string { return `https://${credentials.github_user}:${credentials.token}@plugins.archgate.dev/archgate.git`; } -/** - * Build the authenticated git marketplace URL for VS Code plugin installation. - * VS Code Copilot uses the .github/plugin/ manifest format, served from a separate repo. - */ -export function buildVscodeMarketplaceUrl( - credentials: StoredCredentials -): string { - return `https://${credentials.github_user}:${credentials.token}@plugins.archgate.dev/archgate-vscode.git`; -} - /** * Check whether the `claude` CLI is available on the system PATH. */ @@ -148,6 +138,26 @@ export async function installCursorPlugin( return extractedFiles; } +// --------------------------------------------------------------------------- +// VS Code — configure marketplace URL in .vscode/settings.json +// --------------------------------------------------------------------------- + +/** + * Install the archgate plugin for VS Code by configuring the marketplace URL. + * + * VS Code agent plugins are installed from git-based marketplaces. This adds the + * authenticated marketplace URL to `.vscode/settings.json` so VS Code can discover + * and install the plugin automatically. + */ +export async function installVscodePlugin( + projectRoot: string, + credentials: StoredCredentials +): Promise { + const { configureVscodeSettings } = await import("./vscode-settings"); + const url = buildMarketplaceUrl(credentials); + return configureVscodeSettings(projectRoot, url); +} + // --------------------------------------------------------------------------- // Copilot CLI — CLI auto-install + manual fallback // --------------------------------------------------------------------------- diff --git a/src/helpers/vscode-settings.ts b/src/helpers/vscode-settings.ts index 668f778e..f468e93e 100644 --- a/src/helpers/vscode-settings.ts +++ b/src/helpers/vscode-settings.ts @@ -1,20 +1,26 @@ import { join } from "node:path"; import { existsSync, mkdirSync } from "node:fs"; -import { homedir } from "node:os"; - -type VscodeUserSettings = Record; /** - * VS Code's built-in default marketplaces for `chat.plugins.marketplaces`. + * VS Code settings that archgate injects into .vscode/settings.json. * - * These are implicit defaults — VS Code uses them when the setting is absent - * from the user's settings.json. Once we explicitly set the key, VS Code stops - * using its defaults, so we must include them to avoid losing them. + * - `chat.plugins.marketplaces`: registers the archgate git marketplace so + * VS Code's agent plugin system can discover and install the plugin. + * - MCP server configuration for the archgate governance tools. */ -const VSCODE_DEFAULT_MARKETPLACES = [ - "github/copilot-plugins", - "github/awesome-copilot", -]; +export const ARCHGATE_VSCODE_SETTINGS = { + "chat.plugins.marketplaces": [] as string[], + mcp: { + servers: { + archgate: { + command: "archgate", + args: ["mcp"], + }, + }, + }, +} as const; + +type VscodeSettings = Record; /** * Deduplicate an array of strings while preserving order. @@ -24,114 +30,82 @@ function dedup(arr: string[]): string[] { } /** - * Add a marketplace URL to the `chat.plugins.marketplaces` array in a VS Code - * user settings object. Preserves all other settings. Deduplicates URLs. + * Pure, additive merge of archgate settings into existing VS Code settings. * - * When `chat.plugins.marketplaces` is not yet in the file, VS Code's built-in - * defaults are included so they are not lost when we explicitly set the key. + * - `chat.plugins.marketplaces`: append marketplace URL with dedup + * - `mcp.servers`: add archgate server, preserve existing servers + * - All existing user settings are preserved (unknown keys pass through) */ -export function mergeMarketplaceUrl( - existing: VscodeUserSettings, +export function mergeVscodeSettings( + existing: VscodeSettings, marketplaceUrl: string -): VscodeUserSettings { - const merged: VscodeUserSettings = { ...existing }; +): VscodeSettings { + const merged: VscodeSettings = { ...existing }; - const hasExplicitMarketplaces = "chat.plugins.marketplaces" in existing; + // Marketplace URLs: append with dedup const existingMarketplaces = Array.isArray( merged["chat.plugins.marketplaces"] ) ? (merged["chat.plugins.marketplaces"] as string[]) : []; - - // When the key is absent, seed with VS Code's built-in defaults so we don't - // silently override them by setting the key explicitly. - const base = hasExplicitMarketplaces - ? existingMarketplaces - : [...VSCODE_DEFAULT_MARKETPLACES, ...existingMarketplaces]; - - merged["chat.plugins.marketplaces"] = dedup([...base, marketplaceUrl]); + merged["chat.plugins.marketplaces"] = dedup([ + ...existingMarketplaces, + marketplaceUrl, + ]); + + // MCP servers: additive merge + const existingMcp = + typeof merged.mcp === "object" && + merged.mcp !== null && + !Array.isArray(merged.mcp) + ? (merged.mcp as Record) + : {}; + + const existingServers = + typeof existingMcp.servers === "object" && + existingMcp.servers !== null && + !Array.isArray(existingMcp.servers) + ? (existingMcp.servers as Record) + : {}; + + merged.mcp = { + ...existingMcp, + servers: { + ...existingServers, + ...ARCHGATE_VSCODE_SETTINGS.mcp.servers, + }, + }; return merged; } -/** - * Resolve the path to VS Code's user-level settings.json. - * - * - Windows: %APPDATA%/Code/User/settings.json - * - macOS: ~/Library/Application Support/Code/User/settings.json - * - Linux: ~/.config/Code/User/settings.json - */ -export function getVscodeUserSettingsPath(): string { - const platform = process.platform; - if (platform === "win32") { - const appData = - process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"); - return join(appData, "Code", "User", "settings.json"); - } - if (platform === "darwin") { - return join( - homedir(), - "Library", - "Application Support", - "Code", - "User", - "settings.json" - ); - } - // Linux and others - return join(homedir(), ".config", "Code", "User", "settings.json"); -} - /** * Configure VS Code settings for archgate integration. * - * If `marketplaceUrl` is provided, adds it to `chat.plugins.marketplaces` in - * the VS Code user-level settings.json (application-scoped — cannot be set per workspace). + * Reads existing `.vscode/settings.json` (if any), merges archgate + * settings additively (marketplace URL + MCP server), and writes the result. * - * @returns Absolute path to the .vscode/ directory. + * @returns Absolute path to the settings file. */ export async function configureVscodeSettings( projectRoot: string, - marketplaceUrl?: string -): Promise { - const vscodeDir = join(projectRoot, ".vscode"); - - if (!existsSync(vscodeDir)) { - mkdirSync(vscodeDir, { recursive: true }); - } - - // --- User-level: chat.plugins.marketplaces --- - if (marketplaceUrl) { - await addMarketplaceToUserSettings(marketplaceUrl); - } - - return vscodeDir; -} - -/** - * Add the marketplace URL to VS Code's user-level settings.json. - * - * Reads the existing file with `Bun.JSONC.parse` (supports comments and - * trailing commas), merges the marketplace URL, and writes back as standard - * JSON. Comments in the original file are not preserved — VS Code re-reads - * the file without issue. - */ -export async function addMarketplaceToUserSettings( marketplaceUrl: string ): Promise { - const settingsPath = getVscodeUserSettingsPath(); - const settingsDir = join(settingsPath, ".."); + const vscodeDir = join(projectRoot, ".vscode"); + const settingsPath = join(vscodeDir, "settings.json"); - let existing: VscodeUserSettings = {}; + // Read existing settings or start with empty object + let existing: VscodeSettings = {}; if (existsSync(settingsPath)) { const content = await Bun.file(settingsPath).text(); - existing = Bun.JSONC.parse(content) as VscodeUserSettings; + existing = JSON.parse(content) as VscodeSettings; } - const merged = mergeMarketplaceUrl(existing, marketplaceUrl); + const merged = mergeVscodeSettings(existing, marketplaceUrl); - if (!existsSync(settingsDir)) { - mkdirSync(settingsDir, { recursive: true }); + // Ensure .vscode/ directory exists + if (!existsSync(vscodeDir)) { + mkdirSync(vscodeDir, { recursive: true }); } await Bun.write(settingsPath, JSON.stringify(merged, null, 2) + "\n"); From bcdac8b63b0f4b068ffb8ffffb74b5a600af3a77 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 00:22:40 +0100 Subject: [PATCH 2/9] test: add settings helper tests and pt-br guide translations Add test suites for vscode-settings and copilot-settings helpers (ARCH-005 compliance). Add full Portuguese translations for the VS Code and Copilot CLI plugin guide pages (GEN-002 compliance). Co-Authored-By: Claude Opus 4.6 --- .../docs/pt-br/guides/vscode-plugin.mdx | 61 ++--- tests/helpers/copilot-settings.test.ts | 132 ++++++++++- tests/helpers/vscode-settings.test.ts | 223 ++++++++++-------- 3 files changed, 261 insertions(+), 155 deletions(-) diff --git a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx index 0b0eaa92..2b2f3ae3 100644 --- a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx @@ -7,16 +7,12 @@ O plugin Archgate para VS Code oferece aos agentes de IA que trabalham no [VS Co ## Como funciona -O VS Code suporta **plugins de agente** instalados a partir de marketplaces baseados em git. O plugin Archgate é servido a partir de um repositório git em `plugins.archgate.dev/archgate-vscode.git`. Quando você adiciona esse marketplace às suas configurações de usuário do VS Code, o VS Code descobre e instala o plugin automaticamente. +O VS Code suporta **plugins de agente** instalados a partir de marketplaces baseados em git. O plugin Archgate é servido a partir de um repositório git em `plugins.archgate.dev/archgate.git`. Quando você adiciona esse marketplace às suas configurações do VS Code, o VS Code descobre e instala o plugin automaticamente. O plugin usa o mesmo formato dos plugins do Claude Code (`.claude-plugin/plugin.json`), que o sistema de plugins de agente do VS Code reconhece nativamente. ## Instalação -:::caution[Versão do VS Code necessária] -Plugins de agente requerem **VS Code 1.110 (lançamento de fevereiro de 2026) ou posterior**. Versões anteriores não suportam marketplaces de plugins de agente baseados em git. Verifique sua versão com `code --version`. -::: - :::note[Acesso beta necessário] O plugin para VS Code está atualmente em beta. [Cadastre-se em plugins.archgate.dev](https://plugins.archgate.dev) para obter acesso antes de seguir os passos abaixo. ::: @@ -42,16 +38,8 @@ archgate init --editor vscode Se você já estiver logado, este comando: 1. Cria o diretório de governança `.archgate/` (ADRs, regras de lint) -2. Cria `.vscode/mcp.json` com a configuração do servidor MCP do Archgate -3. Adiciona a URL autenticada do marketplace nas suas **configurações de usuário** do VS Code - -A configuração `chat.plugins.marketplaces` tem escopo de aplicação no VS Code, então não pode ser definida por workspace. O CLI escreve automaticamente no seu `settings.json` de nível de usuário: - -| Plataforma | Caminho das configurações de usuário | -| ---------- | ------------------------------------------------------- | -| Windows | `%APPDATA%\Code\User\settings.json` | -| macOS | `~/Library/Application Support/Code/User/settings.json` | -| Linux | `~/.config/Code/User/settings.json` | +2. Configura `.vscode/settings.json` com a URL autenticada do marketplace +3. Registra o servidor MCP do Archgate em `.vscode/settings.json` Para solicitar explicitamente a instalação do plugin: @@ -59,43 +47,27 @@ Para solicitar explicitamente a instalação do plugin: archgate init --editor vscode --install-plugin ``` -### Arquivos gerados +### Configurações geradas -O comando cria ou atualiza os seguintes arquivos: - -| Arquivo | Escopo | Propósito | -| -------------------------- | --------- | ------------------------------------------------------------------- | -| `.vscode/mcp.json` | Workspace | Configuração do servidor MCP para que o VS Code acesse os seus ADRs | -| `settings.json` do usuário | Usuário | `chat.plugins.marketplaces` com a URL autenticada | - -Se `.vscode/mcp.json` já existir, o Archgate mescla sua configuração de forma aditiva -- entradas de servidores existentes são preservadas. O arquivo de configurações de usuário também é mesclado de forma aditiva -- configurações existentes nunca são sobrescritas. - -A configuração MCP do workspace (`.vscode/mcp.json`): +O comando adiciona o seguinte ao `.vscode/settings.json` (configurações existentes são preservadas): ```json { - "servers": { - "archgate": { - "command": "archgate", - "args": ["mcp"] + "chat.plugins.marketplaces": [ + "https://:@plugins.archgate.dev/archgate.git" + ], + "mcp": { + "servers": { + "archgate": { + "command": "archgate", + "args": ["mcp"] + } } } } ``` -A configuração do marketplace no nível de usuário (adicionada ao seu `settings.json`): - -```json -{ - "chat.plugins.marketplaces": [ - "https://:@plugins.archgate.dev/archgate-vscode.git" - ] -} -``` - -### Configuração manual - -Se preferir não deixar o CLI modificar suas configurações de usuário, você pode adicionar a URL do marketplace manualmente. Abra o JSON de configurações de usuário do VS Code (`Ctrl+Shift+P` → "Preferences: Open User Settings (JSON)") e adicione a entrada `chat.plugins.marketplaces` mostrada acima. Você pode encontrar sua URL autenticada executando `archgate login` e verificando `~/.archgate/credentials`. +Se `.vscode/settings.json` já existir, o Archgate mescla suas configurações de forma aditiva -- entradas existentes são preservadas. ## O que o plugin oferece @@ -159,7 +131,6 @@ O servidor MCP roda localmente e lê diretamente do seu diretório `.archgate/ad ## Dicas -- **Faça commit do `.vscode/mcp.json`** para compartilhar a configuração MCP com sua equipe. -- **Cada desenvolvedor executa `archgate init --editor vscode`** após `archgate login` para configurar a URL do marketplace no nível de usuário. +- **Faça commit do `.vscode/settings.json`** para compartilhar a configuração do marketplace e MCP com sua equipe. - **Execute o onboard uma vez por projeto** para gerar seus ADRs iniciais a partir do seu codebase real. - **Mantenha os arquivos de regras dos ADRs atualizados** -- o agente aplica o que as regras verificam. diff --git a/tests/helpers/copilot-settings.test.ts b/tests/helpers/copilot-settings.test.ts index 525f922d..8cc77e94 100644 --- a/tests/helpers/copilot-settings.test.ts +++ b/tests/helpers/copilot-settings.test.ts @@ -1,8 +1,92 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test"; -import { mkdtempSync, rmSync, existsSync } from "node:fs"; +import { mkdtempSync, rmSync, existsSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { configureCopilotSettings } from "../../src/helpers/copilot-settings"; +import { + ARCHGATE_COPILOT_MCP_CONFIG, + mergeCopilotMcpConfig, + configureCopilotSettings, +} from "../../src/helpers/copilot-settings"; + +describe("mergeCopilotMcpConfig", () => { + test("sets archgate server when existing config is empty", () => { + const result = mergeCopilotMcpConfig({}, ARCHGATE_COPILOT_MCP_CONFIG); + + expect(result.mcpServers).toEqual({ + archgate: { + command: "archgate", + args: ["mcp"], + }, + }); + }); + + test("preserves existing MCP servers", () => { + const result = mergeCopilotMcpConfig( + { + mcpServers: { + "other-server": { + command: "other", + args: ["start"], + }, + }, + }, + ARCHGATE_COPILOT_MCP_CONFIG + ); + + const servers = result.mcpServers as Record; + expect(servers["other-server"]).toEqual({ + command: "other", + args: ["start"], + }); + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + + test("overwrites existing archgate server entry", () => { + const result = mergeCopilotMcpConfig( + { + mcpServers: { + archgate: { + command: "old-command", + args: ["old"], + }, + }, + }, + ARCHGATE_COPILOT_MCP_CONFIG + ); + + const servers = result.mcpServers as Record; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + + test("preserves unknown top-level keys", () => { + const result = mergeCopilotMcpConfig( + { customKey: "value" }, + ARCHGATE_COPILOT_MCP_CONFIG + ); + + expect(result.customKey).toBe("value"); + }); + + test("handles non-object mcpServers gracefully", () => { + const result = mergeCopilotMcpConfig( + { mcpServers: "invalid" }, + ARCHGATE_COPILOT_MCP_CONFIG + ); + + expect(result.mcpServers).toEqual({ + archgate: { + command: "archgate", + args: ["mcp"], + }, + }); + }); +}); describe("configureCopilotSettings", () => { let tempDir: string; @@ -15,23 +99,51 @@ describe("configureCopilotSettings", () => { rmSync(tempDir, { recursive: true, force: true }); }); - test("creates .github/copilot/ dir when nothing exists", async () => { - await configureCopilotSettings(tempDir); + test("creates .github/copilot/ dir and mcp.json when nothing exists", async () => { + const mcpConfigPath = await configureCopilotSettings(tempDir); expect(existsSync(join(tempDir, ".github", "copilot"))).toBe(true); + expect(existsSync(mcpConfigPath)).toBe(true); + + const mcpContent = JSON.parse(await Bun.file(mcpConfigPath).text()); + expect(mcpContent.mcpServers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("does not create mcp.json", async () => { + test("merges into existing mcp.json without overwriting other servers", async () => { + const copilotDir = join(tempDir, ".github", "copilot"); + mkdirSync(copilotDir, { recursive: true }); + + const existingConfig = { + mcpServers: { + "my-server": { command: "my-cmd", args: [] }, + }, + }; + await Bun.write( + join(copilotDir, "mcp.json"), + JSON.stringify(existingConfig, null, 2) + ); + await configureCopilotSettings(tempDir); - expect(existsSync(join(tempDir, ".github", "copilot", "mcp.json"))).toBe( - false + const content = JSON.parse( + await Bun.file(join(copilotDir, "mcp.json")).text() ); + expect(content.mcpServers["my-server"]).toEqual({ + command: "my-cmd", + args: [], + }); + expect(content.mcpServers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("returns path to .github/copilot/ directory", async () => { - const result = await configureCopilotSettings(tempDir); + test("returns correct absolute path to mcp.json", async () => { + const mcpConfigPath = await configureCopilotSettings(tempDir); - expect(result).toBe(join(tempDir, ".github", "copilot")); + expect(mcpConfigPath).toBe(join(tempDir, ".github", "copilot", "mcp.json")); }); }); diff --git a/tests/helpers/vscode-settings.test.ts b/tests/helpers/vscode-settings.test.ts index f34e66b7..830cc289 100644 --- a/tests/helpers/vscode-settings.test.ts +++ b/tests/helpers/vscode-settings.test.ts @@ -3,154 +3,177 @@ import { mkdtempSync, rmSync, existsSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { - mergeMarketplaceUrl, + mergeVscodeSettings, configureVscodeSettings, - addMarketplaceToUserSettings, - getVscodeUserSettingsPath, } from "../../src/helpers/vscode-settings"; -describe("mergeMarketplaceUrl", () => { - const URL = "https://user:token@plugins.archgate.dev/archgate.git"; +const MARKETPLACE_URL = "https://user:token@plugins.archgate.dev/archgate.git"; - test("includes VS Code defaults when key is absent", () => { - const result = mergeMarketplaceUrl({}, URL); - expect(result["chat.plugins.marketplaces"]).toEqual([ - "github/copilot-plugins", - "github/awesome-copilot", - URL, - ]); +describe("mergeVscodeSettings", () => { + test("sets marketplace URL and MCP server when existing settings are empty", () => { + const result = mergeVscodeSettings({}, MARKETPLACE_URL); + + expect(result["chat.plugins.marketplaces"]).toEqual([MARKETPLACE_URL]); + const mcp = result.mcp as Record; + const servers = mcp.servers as Record; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("appends URL with dedup when key already exists", () => { - const result = mergeMarketplaceUrl( - { "chat.plugins.marketplaces": ["https://other.git", URL] }, - URL + test("appends marketplace URL with dedup", () => { + const result = mergeVscodeSettings( + { "chat.plugins.marketplaces": ["https://other.git", MARKETPLACE_URL] }, + MARKETPLACE_URL ); + expect(result["chat.plugins.marketplaces"]).toEqual([ "https://other.git", - URL, + MARKETPLACE_URL, ]); }); - test("does not re-add defaults when key is explicitly set", () => { - const result = mergeMarketplaceUrl( - { "chat.plugins.marketplaces": ["https://custom.git"] }, - URL + test("preserves existing MCP servers", () => { + const result = mergeVscodeSettings( + { + mcp: { + servers: { + "other-server": { command: "other", args: ["start"] }, + }, + }, + }, + MARKETPLACE_URL ); - expect(result["chat.plugins.marketplaces"]).toEqual([ - "https://custom.git", - URL, - ]); - }); - test("handles non-array marketplaces gracefully", () => { - const result = mergeMarketplaceUrl( - { "chat.plugins.marketplaces": "not-an-array", "editor.fontSize": 14 }, - URL - ); - expect(result["chat.plugins.marketplaces"]).toEqual([URL]); - expect(result["editor.fontSize"]).toBe(14); + const mcp = result.mcp as Record; + const servers = mcp.servers as Record; + expect(servers["other-server"]).toEqual({ + command: "other", + args: ["start"], + }); + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); -}); -describe("configureVscodeSettings", () => { - let tempDir: string; - - beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-vscode-settings-test-")); - }); + test("preserves unknown top-level keys", () => { + const result = mergeVscodeSettings( + { "editor.fontSize": 14, "workbench.colorTheme": "One Dark" }, + MARKETPLACE_URL + ); - afterEach(() => { - rmSync(tempDir, { recursive: true, force: true }); + expect(result["editor.fontSize"]).toBe(14); + expect(result["workbench.colorTheme"]).toBe("One Dark"); }); - test("creates .vscode/ dir when nothing exists", async () => { - await configureVscodeSettings(tempDir); + test("handles non-array marketplaces gracefully", () => { + const result = mergeVscodeSettings( + { "chat.plugins.marketplaces": "not-an-array" }, + MARKETPLACE_URL + ); - expect(existsSync(join(tempDir, ".vscode"))).toBe(true); + expect(result["chat.plugins.marketplaces"]).toEqual([MARKETPLACE_URL]); }); - test("does not create mcp.json", async () => { - await configureVscodeSettings(tempDir); + test("handles non-object mcp gracefully", () => { + const result = mergeVscodeSettings({ mcp: "invalid" }, MARKETPLACE_URL); - expect(existsSync(join(tempDir, ".vscode", "mcp.json"))).toBe(false); + const mcp = result.mcp as Record; + const servers = mcp.servers as Record; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("returns path to .vscode/ directory", async () => { - const result = await configureVscodeSettings(tempDir); + test("handles non-object mcp.servers gracefully", () => { + const result = mergeVscodeSettings( + { mcp: { servers: "invalid" } }, + MARKETPLACE_URL + ); - expect(result).toBe(join(tempDir, ".vscode")); + const mcp = result.mcp as Record; + const servers = mcp.servers as Record; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); }); -describe("addMarketplaceToUserSettings", () => { +describe("configureVscodeSettings", () => { let tempDir: string; - let savedEnv: Record; beforeEach(() => { - tempDir = mkdtempSync(join(tmpdir(), "archgate-user-settings-test-")); - // Save and override env so getVscodeUserSettingsPath() resolves into tempDir - savedEnv = { APPDATA: process.env.APPDATA, HOME: process.env.HOME }; - process.env.APPDATA = tempDir; // Windows - process.env.HOME = tempDir; // macOS/Linux (homedir()) + tempDir = mkdtempSync(join(tmpdir(), "archgate-vscode-settings-test-")); }); afterEach(() => { - Object.assign(process.env, savedEnv); rmSync(tempDir, { recursive: true, force: true }); }); - const URL = "https://user:token@plugins.archgate.dev/archgate.git"; - - /** Use the real path resolver so the test matches addMarketplaceToUserSettings */ - function settingsPath() { - return getVscodeUserSettingsPath(); - } - - test("creates settings file with defaults when none exists", async () => { - await addMarketplaceToUserSettings(URL); + test("creates .vscode/ dir and settings.json when neither exists", async () => { + const settingsPath = await configureVscodeSettings( + tempDir, + MARKETPLACE_URL + ); - const content = JSON.parse(await Bun.file(settingsPath()).text()); - expect(content["chat.plugins.marketplaces"]).toEqual([ - "github/copilot-plugins", - "github/awesome-copilot", - URL, - ]); + expect(existsSync(join(tempDir, ".vscode"))).toBe(true); + expect(existsSync(settingsPath)).toBe(true); + + const content = JSON.parse(await Bun.file(settingsPath).text()); + expect(content["chat.plugins.marketplaces"]).toEqual([MARKETPLACE_URL]); + const servers = content.mcp.servers; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("merges JSONC settings and includes defaults when key absent", async () => { - mkdirSync(join(settingsPath(), ".."), { recursive: true }); + test("merges into existing settings.json without overwriting user entries", async () => { + const vscodeDir = join(tempDir, ".vscode"); + mkdirSync(vscodeDir, { recursive: true }); + + const existingSettings = { + "editor.fontSize": 14, + "chat.plugins.marketplaces": ["https://other.git"], + mcp: { + servers: { + "my-server": { command: "my-cmd", args: [] }, + }, + }, + }; await Bun.write( - settingsPath(), - `{ "git.autofetch": true, "chat.mcp.gallery.enabled": true, }` + join(vscodeDir, "settings.json"), + JSON.stringify(existingSettings, null, 2) ); - await addMarketplaceToUserSettings(URL); + await configureVscodeSettings(tempDir, MARKETPLACE_URL); - const content = JSON.parse(await Bun.file(settingsPath()).text()); - expect(content["git.autofetch"]).toBe(true); - expect(content["chat.plugins.marketplaces"]).toEqual([ - "github/copilot-plugins", - "github/awesome-copilot", - URL, - ]); + const content = JSON.parse( + await Bun.file(join(vscodeDir, "settings.json")).text() + ); + expect(content["editor.fontSize"]).toBe(14); + expect(content["chat.plugins.marketplaces"]).toContain("https://other.git"); + expect(content["chat.plugins.marketplaces"]).toContain(MARKETPLACE_URL); + expect(content.mcp.servers["my-server"]).toEqual({ + command: "my-cmd", + args: [], + }); + expect(content.mcp.servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("deduplicates when key already exists", async () => { - mkdirSync(join(settingsPath(), ".."), { recursive: true }); - await Bun.write( - settingsPath(), - JSON.stringify({ - "chat.plugins.marketplaces": ["https://other.git", URL], - }) + test("returns correct absolute path", async () => { + const settingsPath = await configureVscodeSettings( + tempDir, + MARKETPLACE_URL ); - await addMarketplaceToUserSettings(URL); - - const content = JSON.parse(await Bun.file(settingsPath()).text()); - expect(content["chat.plugins.marketplaces"]).toEqual([ - "https://other.git", - URL, - ]); + expect(settingsPath).toBe(join(tempDir, ".vscode", "settings.json")); }); }); From 3eb7117d99d9527d950e93efa39e6033b8012103 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 00:47:54 +0100 Subject: [PATCH 3/9] fix: use .vscode/mcp.json for MCP and user settings for marketplace URL VS Code uses a dedicated .vscode/mcp.json file for MCP servers (with "servers" key), not .vscode/settings.json. The chat.plugins.marketplaces setting is application-scoped and must be set in user-level settings. Changes: - Rewrite vscode-settings.ts to write .vscode/mcp.json (workspace) and user settings.json (marketplace URL) separately - Auto-detect user settings path per platform (Win/Mac/Linux) - Remove installVscodePlugin from plugin-install.ts (no longer needed) - Update docs (en + pt-br) to reflect the two-file approach - Update tests for new merge functions Co-Authored-By: Claude Opus 4.6 --- .../src/content/docs/guides/vscode-plugin.mdx | 57 ++++-- .../docs/pt-br/guides/vscode-plugin.mdx | 57 ++++-- src/commands/init.ts | 2 +- src/helpers/init-project.ts | 18 +- src/helpers/plugin-install.ts | 22 +- src/helpers/vscode-settings.ts | 178 ++++++++++------ tests/helpers/vscode-settings.test.ts | 192 +++++++++--------- 7 files changed, 312 insertions(+), 214 deletions(-) diff --git a/docs/src/content/docs/guides/vscode-plugin.mdx b/docs/src/content/docs/guides/vscode-plugin.mdx index 5e0b5a3d..185915b8 100644 --- a/docs/src/content/docs/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/guides/vscode-plugin.mdx @@ -7,7 +7,7 @@ The Archgate VS Code plugin gives AI agents working in [VS Code](https://code.vi ## How it works -VS Code supports **agent plugins** installed from git-based marketplaces. The Archgate plugin is served from a git repository at `plugins.archgate.dev/archgate.git`. When you add this marketplace to your VS Code settings, VS Code discovers and installs the plugin automatically. +VS Code supports **agent plugins** installed from git-based marketplaces. The Archgate plugin is served from a git repository at `plugins.archgate.dev/archgate.git`. When you add this marketplace to your VS Code user settings, VS Code discovers and installs the plugin automatically. The plugin uses the same format as Claude Code plugins (`.claude-plugin/plugin.json`), which VS Code's agent plugin system recognizes natively. @@ -38,8 +38,16 @@ archgate init --editor vscode If you are already logged in, this command: 1. Creates the `.archgate/` governance directory (ADRs, lint rules) -2. Configures `.vscode/settings.json` with the authenticated marketplace URL -3. Registers the Archgate MCP server in `.vscode/settings.json` +2. Creates `.vscode/mcp.json` with the Archgate MCP server configuration +3. Adds the authenticated marketplace URL to your VS Code **user settings** + +The `chat.plugins.marketplaces` setting is application-scoped in VS Code, so it cannot be set per-workspace. The CLI automatically writes it to your user-level `settings.json`: + +| Platform | User settings path | +| -------- | ------------------------------------------------------- | +| Windows | `%APPDATA%\Code\User\settings.json` | +| macOS | `~/Library/Application Support/Code/User/settings.json` | +| Linux | `~/.config/Code/User/settings.json` | To explicitly request plugin installation: @@ -47,27 +55,43 @@ To explicitly request plugin installation: archgate init --editor vscode --install-plugin ``` -### Generated settings +### Generated files + +The command creates or updates the following files: + +| File | Scope | Purpose | +| -------------------- | --------- | -------------------------------------------------------- | +| `.vscode/mcp.json` | Workspace | MCP server configuration so VS Code can access your ADRs | +| User `settings.json` | User | `chat.plugins.marketplaces` with the authenticated URL | -The command adds the following to `.vscode/settings.json` (existing settings are preserved): +If `.vscode/mcp.json` already exists, Archgate merges its configuration additively -- existing server entries are preserved. The user settings file is also merged additively -- existing settings are never overwritten. + +The workspace MCP configuration (`.vscode/mcp.json`): ```json { - "chat.plugins.marketplaces": [ - "https://:@plugins.archgate.dev/archgate.git" - ], - "mcp": { - "servers": { - "archgate": { - "command": "archgate", - "args": ["mcp"] - } + "servers": { + "archgate": { + "command": "archgate", + "args": ["mcp"] } } } ``` -If `.vscode/settings.json` already exists, Archgate merges its settings additively -- existing entries are preserved. +The user-level marketplace setting (added to your `settings.json`): + +```json +{ + "chat.plugins.marketplaces": [ + "https://:@plugins.archgate.dev/archgate.git" + ] +} +``` + +### Manual setup + +If you prefer not to let the CLI modify your user settings, you can add the marketplace URL manually. Open VS Code's user settings JSON (`Ctrl+Shift+P` → "Preferences: Open User Settings (JSON)") and add the `chat.plugins.marketplaces` entry shown above. You can find your authenticated URL by running `archgate login` and checking `~/.archgate/credentials`. ## What the plugin provides @@ -131,6 +155,7 @@ The MCP server runs locally and reads directly from your `.archgate/adrs/` direc ## Tips -- **Commit `.vscode/settings.json`** to share the marketplace and MCP configuration with your team. +- **Commit `.vscode/mcp.json`** to share the MCP configuration with your team. +- **Each developer runs `archgate init --editor vscode`** after `archgate login` to configure their user-level marketplace URL. - **Run onboard once per project** to generate your initial ADRs from your actual codebase. - **Keep ADR rule files up to date** -- the agent enforces what the rules check for. diff --git a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx index 2b2f3ae3..f068843f 100644 --- a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx @@ -7,7 +7,7 @@ O plugin Archgate para VS Code oferece aos agentes de IA que trabalham no [VS Co ## Como funciona -O VS Code suporta **plugins de agente** instalados a partir de marketplaces baseados em git. O plugin Archgate é servido a partir de um repositório git em `plugins.archgate.dev/archgate.git`. Quando você adiciona esse marketplace às suas configurações do VS Code, o VS Code descobre e instala o plugin automaticamente. +O VS Code suporta **plugins de agente** instalados a partir de marketplaces baseados em git. O plugin Archgate é servido a partir de um repositório git em `plugins.archgate.dev/archgate.git`. Quando você adiciona esse marketplace às suas configurações de usuário do VS Code, o VS Code descobre e instala o plugin automaticamente. O plugin usa o mesmo formato dos plugins do Claude Code (`.claude-plugin/plugin.json`), que o sistema de plugins de agente do VS Code reconhece nativamente. @@ -38,8 +38,16 @@ archgate init --editor vscode Se você já estiver logado, este comando: 1. Cria o diretório de governança `.archgate/` (ADRs, regras de lint) -2. Configura `.vscode/settings.json` com a URL autenticada do marketplace -3. Registra o servidor MCP do Archgate em `.vscode/settings.json` +2. Cria `.vscode/mcp.json` com a configuração do servidor MCP do Archgate +3. Adiciona a URL autenticada do marketplace nas suas **configurações de usuário** do VS Code + +A configuração `chat.plugins.marketplaces` tem escopo de aplicação no VS Code, então não pode ser definida por workspace. O CLI escreve automaticamente no seu `settings.json` de nível de usuário: + +| Plataforma | Caminho das configurações de usuário | +| ---------- | ------------------------------------------------------- | +| Windows | `%APPDATA%\Code\User\settings.json` | +| macOS | `~/Library/Application Support/Code/User/settings.json` | +| Linux | `~/.config/Code/User/settings.json` | Para solicitar explicitamente a instalação do plugin: @@ -47,27 +55,43 @@ Para solicitar explicitamente a instalação do plugin: archgate init --editor vscode --install-plugin ``` -### Configurações geradas +### Arquivos gerados + +O comando cria ou atualiza os seguintes arquivos: + +| Arquivo | Escopo | Propósito | +| -------------------------- | --------- | ------------------------------------------------------------------- | +| `.vscode/mcp.json` | Workspace | Configuração do servidor MCP para que o VS Code acesse os seus ADRs | +| `settings.json` do usuário | Usuário | `chat.plugins.marketplaces` com a URL autenticada | -O comando adiciona o seguinte ao `.vscode/settings.json` (configurações existentes são preservadas): +Se `.vscode/mcp.json` já existir, o Archgate mescla sua configuração de forma aditiva -- entradas de servidores existentes são preservadas. O arquivo de configurações de usuário também é mesclado de forma aditiva -- configurações existentes nunca são sobrescritas. + +A configuração MCP do workspace (`.vscode/mcp.json`): ```json { - "chat.plugins.marketplaces": [ - "https://:@plugins.archgate.dev/archgate.git" - ], - "mcp": { - "servers": { - "archgate": { - "command": "archgate", - "args": ["mcp"] - } + "servers": { + "archgate": { + "command": "archgate", + "args": ["mcp"] } } } ``` -Se `.vscode/settings.json` já existir, o Archgate mescla suas configurações de forma aditiva -- entradas existentes são preservadas. +A configuração do marketplace no nível de usuário (adicionada ao seu `settings.json`): + +```json +{ + "chat.plugins.marketplaces": [ + "https://:@plugins.archgate.dev/archgate.git" + ] +} +``` + +### Configuração manual + +Se preferir não deixar o CLI modificar suas configurações de usuário, você pode adicionar a URL do marketplace manualmente. Abra o JSON de configurações de usuário do VS Code (`Ctrl+Shift+P` → "Preferences: Open User Settings (JSON)") e adicione a entrada `chat.plugins.marketplaces` mostrada acima. Você pode encontrar sua URL autenticada executando `archgate login` e verificando `~/.archgate/credentials`. ## O que o plugin oferece @@ -131,6 +155,7 @@ O servidor MCP roda localmente e lê diretamente do seu diretório `.archgate/ad ## Dicas -- **Faça commit do `.vscode/settings.json`** para compartilhar a configuração do marketplace e MCP com sua equipe. +- **Faça commit do `.vscode/mcp.json`** para compartilhar a configuração MCP com sua equipe. +- **Cada desenvolvedor executa `archgate init --editor vscode`** após `archgate login` para configurar a URL do marketplace no nível de usuário. - **Execute o onboard uma vez por projeto** para gerar seus ADRs iniciais a partir do seu codebase real. - **Mantenha os arquivos de regras dos ADRs atualizados** -- o agente aplica o que as regras verificam. diff --git a/src/commands/init.ts b/src/commands/init.ts index 874b383c..50bfce12 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -107,7 +107,7 @@ function printManualInstructions(editor: EditorTarget, detail?: string): void { console.log(` ${styleText("bold", "copilot plugin install")} ${detail}`); break; default: - // vscode and cursor auto-install always — should not reach here + // cursor auto-installs always — should not reach here break; } } diff --git a/src/helpers/init-project.ts b/src/helpers/init-project.ts index 0c2b6e78..a39e968a 100644 --- a/src/helpers/init-project.ts +++ b/src/helpers/init-project.ts @@ -116,15 +116,13 @@ async function configureEditorSettings( case "cursor": return configureCursorSettings(projectRoot); case "vscode": { - // VS Code needs the marketplace URL for settings — use a placeholder - // that gets replaced during plugin install if credentials exist. + // VS Code: .vscode/mcp.json always, marketplace URL to user settings if logged in const { loadCredentials } = await import("./auth"); const creds = await loadCredentials(); - const { buildMarketplaceUrl } = await import("./plugin-install"); - const url = creds - ? buildMarketplaceUrl(creds) - : "https://plugins.archgate.dev/archgate.git"; - return configureVscodeSettings(projectRoot, url); + const marketplaceUrl = creds + ? (await import("./plugin-install")).buildMarketplaceUrl(creds) + : undefined; + return configureVscodeSettings(projectRoot, marketplaceUrl); } case "copilot": return configureCopilotSettings(projectRoot); @@ -158,12 +156,12 @@ async function tryInstallPlugin( } if (editor === "vscode") { - const { installVscodePlugin } = await import("./plugin-install"); - await installVscodePlugin(projectRoot, credentials); + // VS Code marketplace URL is already added to user settings by configureEditorSettings. + // The --install-plugin flag is a no-op for VS Code since init handles everything. return { installed: true, autoInstalled: true, - detail: "Configured marketplace URL in .vscode/settings.json", + detail: "Marketplace URL added to VS Code user settings", }; } diff --git a/src/helpers/plugin-install.ts b/src/helpers/plugin-install.ts index ffb4b976..4581b992 100644 --- a/src/helpers/plugin-install.ts +++ b/src/helpers/plugin-install.ts @@ -2,7 +2,7 @@ * plugin-install.ts — Download and install the archgate plugin for supported editors. * * - Claude Code: auto-installs via `claude` CLI, or prints manual commands as fallback - * - VS Code: configures .vscode/settings.json with marketplace URL (git-based plugin) + * - VS Code: marketplace URL for manual user-settings configuration (application-scoped) * - Copilot CLI: auto-installs via `copilot` CLI, or prints manual commands as fallback * - Cursor: downloads cursor.tar.gz from the plugins service and extracts it */ @@ -138,26 +138,6 @@ export async function installCursorPlugin( return extractedFiles; } -// --------------------------------------------------------------------------- -// VS Code — configure marketplace URL in .vscode/settings.json -// --------------------------------------------------------------------------- - -/** - * Install the archgate plugin for VS Code by configuring the marketplace URL. - * - * VS Code agent plugins are installed from git-based marketplaces. This adds the - * authenticated marketplace URL to `.vscode/settings.json` so VS Code can discover - * and install the plugin automatically. - */ -export async function installVscodePlugin( - projectRoot: string, - credentials: StoredCredentials -): Promise { - const { configureVscodeSettings } = await import("./vscode-settings"); - const url = buildMarketplaceUrl(credentials); - return configureVscodeSettings(projectRoot, url); -} - // --------------------------------------------------------------------------- // Copilot CLI — CLI auto-install + manual fallback // --------------------------------------------------------------------------- diff --git a/src/helpers/vscode-settings.ts b/src/helpers/vscode-settings.ts index f468e93e..818e96e2 100644 --- a/src/helpers/vscode-settings.ts +++ b/src/helpers/vscode-settings.ts @@ -1,26 +1,51 @@ import { join } from "node:path"; import { existsSync, mkdirSync } from "node:fs"; +import { homedir } from "node:os"; /** - * VS Code settings that archgate injects into .vscode/settings.json. + * MCP server configuration that archgate injects into .vscode/mcp.json. * - * - `chat.plugins.marketplaces`: registers the archgate git marketplace so - * VS Code's agent plugin system can discover and install the plugin. - * - MCP server configuration for the archgate governance tools. + * VS Code uses a dedicated `.vscode/mcp.json` file for MCP server registration + * (not `.vscode/settings.json`). The `servers` key format is defined by VS Code's + * MCP configuration spec. */ -export const ARCHGATE_VSCODE_SETTINGS = { - "chat.plugins.marketplaces": [] as string[], - mcp: { - servers: { - archgate: { - command: "archgate", - args: ["mcp"], - }, +export const ARCHGATE_VSCODE_MCP_CONFIG = { + servers: { + archgate: { + command: "archgate", + args: ["mcp"], }, }, } as const; -type VscodeSettings = Record; +type VscodeMcpConfig = Record; +type VscodeUserSettings = Record; + +/** + * Pure, additive merge of archgate MCP server config into existing VS Code MCP config. + * + * - Preserves all existing MCP server entries + * - Adds the archgate server (overwrites if already present) + */ +export function mergeVscodeMcpConfig( + existing: VscodeMcpConfig, + archgate: typeof ARCHGATE_VSCODE_MCP_CONFIG +): VscodeMcpConfig { + const existingServers = + typeof existing.servers === "object" && + existing.servers !== null && + !Array.isArray(existing.servers) + ? (existing.servers as Record) + : {}; + + return { + ...existing, + servers: { + ...existingServers, + ...archgate.servers, + }, + }; +} /** * Deduplicate an array of strings while preserving order. @@ -30,84 +55,121 @@ function dedup(arr: string[]): string[] { } /** - * Pure, additive merge of archgate settings into existing VS Code settings. - * - * - `chat.plugins.marketplaces`: append marketplace URL with dedup - * - `mcp.servers`: add archgate server, preserve existing servers - * - All existing user settings are preserved (unknown keys pass through) + * Add a marketplace URL to the `chat.plugins.marketplaces` array in a VS Code + * user settings object. Preserves all other settings. Deduplicates URLs. */ -export function mergeVscodeSettings( - existing: VscodeSettings, +export function mergeMarketplaceUrl( + existing: VscodeUserSettings, marketplaceUrl: string -): VscodeSettings { - const merged: VscodeSettings = { ...existing }; +): VscodeUserSettings { + const merged: VscodeUserSettings = { ...existing }; - // Marketplace URLs: append with dedup const existingMarketplaces = Array.isArray( merged["chat.plugins.marketplaces"] ) ? (merged["chat.plugins.marketplaces"] as string[]) : []; + merged["chat.plugins.marketplaces"] = dedup([ ...existingMarketplaces, marketplaceUrl, ]); - // MCP servers: additive merge - const existingMcp = - typeof merged.mcp === "object" && - merged.mcp !== null && - !Array.isArray(merged.mcp) - ? (merged.mcp as Record) - : {}; - - const existingServers = - typeof existingMcp.servers === "object" && - existingMcp.servers !== null && - !Array.isArray(existingMcp.servers) - ? (existingMcp.servers as Record) - : {}; - - merged.mcp = { - ...existingMcp, - servers: { - ...existingServers, - ...ARCHGATE_VSCODE_SETTINGS.mcp.servers, - }, - }; - return merged; } +/** + * Resolve the path to VS Code's user-level settings.json. + * + * - Windows: %APPDATA%/Code/User/settings.json + * - macOS: ~/Library/Application Support/Code/User/settings.json + * - Linux: ~/.config/Code/User/settings.json + */ +export function getVscodeUserSettingsPath(): string { + const platform = process.platform; + if (platform === "win32") { + const appData = + process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"); + return join(appData, "Code", "User", "settings.json"); + } + if (platform === "darwin") { + return join( + homedir(), + "Library", + "Application Support", + "Code", + "User", + "settings.json" + ); + } + // Linux and others + return join(homedir(), ".config", "Code", "User", "settings.json"); +} + /** * Configure VS Code settings for archgate integration. * - * Reads existing `.vscode/settings.json` (if any), merges archgate - * settings additively (marketplace URL + MCP server), and writes the result. + * 1. Creates/updates `.vscode/mcp.json` (workspace-level) with the Archgate MCP server. + * 2. If `marketplaceUrl` is provided, adds it to `chat.plugins.marketplaces` in + * the VS Code user-level settings.json (application-scoped — cannot be set per workspace). * - * @returns Absolute path to the settings file. + * @returns Absolute path to the workspace MCP config file. */ export async function configureVscodeSettings( projectRoot: string, - marketplaceUrl: string + marketplaceUrl?: string ): Promise { const vscodeDir = join(projectRoot, ".vscode"); - const settingsPath = join(vscodeDir, "settings.json"); + const mcpConfigPath = join(vscodeDir, "mcp.json"); - // Read existing settings or start with empty object - let existing: VscodeSettings = {}; - if (existsSync(settingsPath)) { - const content = await Bun.file(settingsPath).text(); - existing = JSON.parse(content) as VscodeSettings; + // --- Workspace: .vscode/mcp.json --- + let existing: VscodeMcpConfig = {}; + if (existsSync(mcpConfigPath)) { + const content = await Bun.file(mcpConfigPath).text(); + existing = JSON.parse(content) as VscodeMcpConfig; } - const merged = mergeVscodeSettings(existing, marketplaceUrl); + const merged = mergeVscodeMcpConfig(existing, ARCHGATE_VSCODE_MCP_CONFIG); - // Ensure .vscode/ directory exists if (!existsSync(vscodeDir)) { mkdirSync(vscodeDir, { recursive: true }); } + await Bun.write(mcpConfigPath, JSON.stringify(merged, null, 2) + "\n"); + + // --- User-level: chat.plugins.marketplaces --- + if (marketplaceUrl) { + await addMarketplaceToUserSettings(marketplaceUrl); + } + + return mcpConfigPath; +} + +/** + * Add the marketplace URL to VS Code's user-level settings.json. + * + * Reads the existing file (if any), merges the URL into + * `chat.plugins.marketplaces`, and writes back. Creates parent + * directories if they don't exist. + */ +export async function addMarketplaceToUserSettings( + marketplaceUrl: string +): Promise { + const settingsPath = getVscodeUserSettingsPath(); + const settingsDir = join(settingsPath, ".."); + + let existing: VscodeUserSettings = {}; + if (existsSync(settingsPath)) { + const content = await Bun.file(settingsPath).text(); + existing = JSON.parse(content) as VscodeUserSettings; + } + + const merged = mergeMarketplaceUrl(existing, marketplaceUrl); + + if (!existsSync(settingsDir)) { + mkdirSync(settingsDir, { recursive: true }); + } + await Bun.write(settingsPath, JSON.stringify(merged, null, 2) + "\n"); return settingsPath; diff --git a/tests/helpers/vscode-settings.test.ts b/tests/helpers/vscode-settings.test.ts index 830cc289..07de3673 100644 --- a/tests/helpers/vscode-settings.test.ts +++ b/tests/helpers/vscode-settings.test.ts @@ -3,51 +3,38 @@ import { mkdtempSync, rmSync, existsSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { - mergeVscodeSettings, + ARCHGATE_VSCODE_MCP_CONFIG, + mergeVscodeMcpConfig, + mergeMarketplaceUrl, configureVscodeSettings, } from "../../src/helpers/vscode-settings"; -const MARKETPLACE_URL = "https://user:token@plugins.archgate.dev/archgate.git"; +describe("mergeVscodeMcpConfig", () => { + test("sets archgate server when existing config is empty", () => { + const result = mergeVscodeMcpConfig({}, ARCHGATE_VSCODE_MCP_CONFIG); -describe("mergeVscodeSettings", () => { - test("sets marketplace URL and MCP server when existing settings are empty", () => { - const result = mergeVscodeSettings({}, MARKETPLACE_URL); - - expect(result["chat.plugins.marketplaces"]).toEqual([MARKETPLACE_URL]); - const mcp = result.mcp as Record; - const servers = mcp.servers as Record; - expect(servers.archgate).toEqual({ - command: "archgate", - args: ["mcp"], + expect(result.servers).toEqual({ + archgate: { + command: "archgate", + args: ["mcp"], + }, }); }); - test("appends marketplace URL with dedup", () => { - const result = mergeVscodeSettings( - { "chat.plugins.marketplaces": ["https://other.git", MARKETPLACE_URL] }, - MARKETPLACE_URL - ); - - expect(result["chat.plugins.marketplaces"]).toEqual([ - "https://other.git", - MARKETPLACE_URL, - ]); - }); - test("preserves existing MCP servers", () => { - const result = mergeVscodeSettings( + const result = mergeVscodeMcpConfig( { - mcp: { - servers: { - "other-server": { command: "other", args: ["start"] }, + servers: { + "other-server": { + command: "other", + args: ["start"], }, }, }, - MARKETPLACE_URL + ARCHGATE_VSCODE_MCP_CONFIG ); - const mcp = result.mcp as Record; - const servers = mcp.servers as Record; + const servers = result.servers as Record; expect(servers["other-server"]).toEqual({ command: "other", args: ["start"], @@ -58,48 +45,84 @@ describe("mergeVscodeSettings", () => { }); }); - test("preserves unknown top-level keys", () => { - const result = mergeVscodeSettings( - { "editor.fontSize": 14, "workbench.colorTheme": "One Dark" }, - MARKETPLACE_URL + test("overwrites existing archgate server entry", () => { + const result = mergeVscodeMcpConfig( + { + servers: { + archgate: { + command: "old-command", + args: ["old"], + }, + }, + }, + ARCHGATE_VSCODE_MCP_CONFIG ); - expect(result["editor.fontSize"]).toBe(14); - expect(result["workbench.colorTheme"]).toBe("One Dark"); + const servers = result.servers as Record; + expect(servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); }); - test("handles non-array marketplaces gracefully", () => { - const result = mergeVscodeSettings( - { "chat.plugins.marketplaces": "not-an-array" }, - MARKETPLACE_URL + test("preserves unknown top-level keys", () => { + const result = mergeVscodeMcpConfig( + { inputs: [{ type: "promptString", id: "key" }] }, + ARCHGATE_VSCODE_MCP_CONFIG ); - expect(result["chat.plugins.marketplaces"]).toEqual([MARKETPLACE_URL]); + expect(result.inputs).toEqual([{ type: "promptString", id: "key" }]); }); - test("handles non-object mcp gracefully", () => { - const result = mergeVscodeSettings({ mcp: "invalid" }, MARKETPLACE_URL); + test("handles non-object servers gracefully", () => { + const result = mergeVscodeMcpConfig( + { servers: "invalid" }, + ARCHGATE_VSCODE_MCP_CONFIG + ); - const mcp = result.mcp as Record; - const servers = mcp.servers as Record; - expect(servers.archgate).toEqual({ - command: "archgate", - args: ["mcp"], + expect(result.servers).toEqual({ + archgate: { + command: "archgate", + args: ["mcp"], + }, }); }); +}); + +describe("mergeMarketplaceUrl", () => { + const URL = "https://user:token@plugins.archgate.dev/archgate.git"; + + test("adds marketplace URL to empty settings", () => { + const result = mergeMarketplaceUrl({}, URL); + expect(result["chat.plugins.marketplaces"]).toEqual([URL]); + }); - test("handles non-object mcp.servers gracefully", () => { - const result = mergeVscodeSettings( - { mcp: { servers: "invalid" } }, - MARKETPLACE_URL + test("appends URL with dedup", () => { + const result = mergeMarketplaceUrl( + { "chat.plugins.marketplaces": ["https://other.git", URL] }, + URL ); + expect(result["chat.plugins.marketplaces"]).toEqual([ + "https://other.git", + URL, + ]); + }); - const mcp = result.mcp as Record; - const servers = mcp.servers as Record; - expect(servers.archgate).toEqual({ - command: "archgate", - args: ["mcp"], - }); + test("handles non-array marketplaces gracefully", () => { + const result = mergeMarketplaceUrl( + { "chat.plugins.marketplaces": "not-an-array" }, + URL + ); + expect(result["chat.plugins.marketplaces"]).toEqual([URL]); + }); + + test("preserves other settings keys", () => { + const result = mergeMarketplaceUrl( + { "editor.fontSize": 14, "workbench.colorTheme": "One Dark" }, + URL + ); + expect(result["editor.fontSize"]).toBe(14); + expect(result["workbench.colorTheme"]).toBe("One Dark"); }); }); @@ -114,66 +137,51 @@ describe("configureVscodeSettings", () => { rmSync(tempDir, { recursive: true, force: true }); }); - test("creates .vscode/ dir and settings.json when neither exists", async () => { - const settingsPath = await configureVscodeSettings( - tempDir, - MARKETPLACE_URL - ); + test("creates .vscode/ dir and mcp.json when nothing exists", async () => { + const mcpConfigPath = await configureVscodeSettings(tempDir); expect(existsSync(join(tempDir, ".vscode"))).toBe(true); - expect(existsSync(settingsPath)).toBe(true); + expect(existsSync(mcpConfigPath)).toBe(true); - const content = JSON.parse(await Bun.file(settingsPath).text()); - expect(content["chat.plugins.marketplaces"]).toEqual([MARKETPLACE_URL]); - const servers = content.mcp.servers; - expect(servers.archgate).toEqual({ + const content = JSON.parse(await Bun.file(mcpConfigPath).text()); + expect(content.servers.archgate).toEqual({ command: "archgate", args: ["mcp"], }); }); - test("merges into existing settings.json without overwriting user entries", async () => { + test("merges into existing mcp.json without overwriting other servers", async () => { const vscodeDir = join(tempDir, ".vscode"); mkdirSync(vscodeDir, { recursive: true }); - const existingSettings = { - "editor.fontSize": 14, - "chat.plugins.marketplaces": ["https://other.git"], - mcp: { - servers: { - "my-server": { command: "my-cmd", args: [] }, - }, + const existingConfig = { + servers: { + "my-server": { command: "my-cmd", args: [] }, }, }; await Bun.write( - join(vscodeDir, "settings.json"), - JSON.stringify(existingSettings, null, 2) + join(vscodeDir, "mcp.json"), + JSON.stringify(existingConfig, null, 2) ); - await configureVscodeSettings(tempDir, MARKETPLACE_URL); + await configureVscodeSettings(tempDir); const content = JSON.parse( - await Bun.file(join(vscodeDir, "settings.json")).text() + await Bun.file(join(vscodeDir, "mcp.json")).text() ); - expect(content["editor.fontSize"]).toBe(14); - expect(content["chat.plugins.marketplaces"]).toContain("https://other.git"); - expect(content["chat.plugins.marketplaces"]).toContain(MARKETPLACE_URL); - expect(content.mcp.servers["my-server"]).toEqual({ + expect(content.servers["my-server"]).toEqual({ command: "my-cmd", args: [], }); - expect(content.mcp.servers.archgate).toEqual({ + expect(content.servers.archgate).toEqual({ command: "archgate", args: ["mcp"], }); }); - test("returns correct absolute path", async () => { - const settingsPath = await configureVscodeSettings( - tempDir, - MARKETPLACE_URL - ); + test("returns correct absolute path to mcp.json", async () => { + const mcpConfigPath = await configureVscodeSettings(tempDir); - expect(settingsPath).toBe(join(tempDir, ".vscode", "settings.json")); + expect(mcpConfigPath).toBe(join(tempDir, ".vscode", "mcp.json")); }); }); From 86b6510ab3e463a0fb29b6e5f4b192c0a26d6959 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 00:52:11 +0100 Subject: [PATCH 4/9] docs: add VS Code 1.99+ version requirement notice Agent plugins require VS Code February 2026 release (1.99) or later. Added caution callout to both English and pt-br guide pages. Co-Authored-By: Claude Opus 4.6 --- docs/src/content/docs/guides/vscode-plugin.mdx | 4 ++++ docs/src/content/docs/pt-br/guides/vscode-plugin.mdx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/src/content/docs/guides/vscode-plugin.mdx b/docs/src/content/docs/guides/vscode-plugin.mdx index 185915b8..6b313d27 100644 --- a/docs/src/content/docs/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/guides/vscode-plugin.mdx @@ -13,6 +13,10 @@ The plugin uses the same format as Claude Code plugins (`.claude-plugin/plugin.j ## Installation +:::caution[VS Code version requirement] +Agent plugins require **VS Code 1.99 (February 2026 release) or later**. Earlier versions do not support git-based agent plugin marketplaces. Check your version with `code --version`. +::: + :::note[Beta access required] The VS Code plugin is currently in beta. [Sign up at plugins.archgate.dev](https://plugins.archgate.dev) to get access before following the steps below. ::: diff --git a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx index f068843f..f792df17 100644 --- a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx @@ -13,6 +13,10 @@ O plugin usa o mesmo formato dos plugins do Claude Code (`.claude-plugin/plugin. ## Instalação +:::caution[Versão do VS Code necessária] +Plugins de agente requerem **VS Code 1.99 (lançamento de fevereiro de 2026) ou posterior**. Versões anteriores não suportam marketplaces de plugins de agente baseados em git. Verifique sua versão com `code --version`. +::: + :::note[Acesso beta necessário] O plugin para VS Code está atualmente em beta. [Cadastre-se em plugins.archgate.dev](https://plugins.archgate.dev) para obter acesso antes de seguir os passos abaixo. ::: From 9f7b303f472d0ab8b94844c3903bb4243190a46a Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 00:54:20 +0100 Subject: [PATCH 5/9] fix(docs): correct VS Code version to 1.110 Co-Authored-By: Claude Opus 4.6 --- docs/src/content/docs/guides/vscode-plugin.mdx | 2 +- docs/src/content/docs/pt-br/guides/vscode-plugin.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/guides/vscode-plugin.mdx b/docs/src/content/docs/guides/vscode-plugin.mdx index 6b313d27..3f7ba1db 100644 --- a/docs/src/content/docs/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/guides/vscode-plugin.mdx @@ -14,7 +14,7 @@ The plugin uses the same format as Claude Code plugins (`.claude-plugin/plugin.j ## Installation :::caution[VS Code version requirement] -Agent plugins require **VS Code 1.99 (February 2026 release) or later**. Earlier versions do not support git-based agent plugin marketplaces. Check your version with `code --version`. +Agent plugins require **VS Code 1.110 (February 2026 release) or later**. Earlier versions do not support git-based agent plugin marketplaces. Check your version with `code --version`. ::: :::note[Beta access required] diff --git a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx index f792df17..066d3498 100644 --- a/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx +++ b/docs/src/content/docs/pt-br/guides/vscode-plugin.mdx @@ -14,7 +14,7 @@ O plugin usa o mesmo formato dos plugins do Claude Code (`.claude-plugin/plugin. ## Instalação :::caution[Versão do VS Code necessária] -Plugins de agente requerem **VS Code 1.99 (lançamento de fevereiro de 2026) ou posterior**. Versões anteriores não suportam marketplaces de plugins de agente baseados em git. Verifique sua versão com `code --version`. +Plugins de agente requerem **VS Code 1.110 (lançamento de fevereiro de 2026) ou posterior**. Versões anteriores não suportam marketplaces de plugins de agente baseados em git. Verifique sua versão com `code --version`. ::: :::note[Acesso beta necessário] From 33dbd17b2ed8dea5d4a3990d919152f63d8cb181 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 01:08:54 +0100 Subject: [PATCH 6/9] fix: use Bun.JSONC.parse for VS Code config files VS Code settings.json and mcp.json use JSONC (JSON with Comments) which includes single-line comments, block comments, and trailing commas. JSON.parse() fails on these files. Use Bun's built-in JSONC parser instead. Co-Authored-By: Claude Opus 4.6 --- src/helpers/vscode-settings.ts | 4 ++-- tests/helpers/vscode-settings.test.ts | 28 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/helpers/vscode-settings.ts b/src/helpers/vscode-settings.ts index 818e96e2..e7d9b41a 100644 --- a/src/helpers/vscode-settings.ts +++ b/src/helpers/vscode-settings.ts @@ -126,7 +126,7 @@ export async function configureVscodeSettings( let existing: VscodeMcpConfig = {}; if (existsSync(mcpConfigPath)) { const content = await Bun.file(mcpConfigPath).text(); - existing = JSON.parse(content) as VscodeMcpConfig; + existing = Bun.JSONC.parse(content) as VscodeMcpConfig; } const merged = mergeVscodeMcpConfig(existing, ARCHGATE_VSCODE_MCP_CONFIG); @@ -161,7 +161,7 @@ export async function addMarketplaceToUserSettings( let existing: VscodeUserSettings = {}; if (existsSync(settingsPath)) { const content = await Bun.file(settingsPath).text(); - existing = JSON.parse(content) as VscodeUserSettings; + existing = Bun.JSONC.parse(content) as VscodeUserSettings; } const merged = mergeMarketplaceUrl(existing, marketplaceUrl); diff --git a/tests/helpers/vscode-settings.test.ts b/tests/helpers/vscode-settings.test.ts index 07de3673..8ec11321 100644 --- a/tests/helpers/vscode-settings.test.ts +++ b/tests/helpers/vscode-settings.test.ts @@ -179,6 +179,34 @@ describe("configureVscodeSettings", () => { }); }); + test("parses JSONC (comments + trailing commas) in existing mcp.json", async () => { + const vscodeDir = join(tempDir, ".vscode"); + mkdirSync(vscodeDir, { recursive: true }); + + // Write JSONC with comments and trailing comma — as VS Code produces + const jsoncContent = `{ + // MCP servers + "servers": { + "my-server": { "command": "my-cmd", "args": [] }, + } + }`; + await Bun.write(join(vscodeDir, "mcp.json"), jsoncContent); + + await configureVscodeSettings(tempDir); + + const content = JSON.parse( + await Bun.file(join(vscodeDir, "mcp.json")).text() + ); + expect(content.servers["my-server"]).toEqual({ + command: "my-cmd", + args: [], + }); + expect(content.servers.archgate).toEqual({ + command: "archgate", + args: ["mcp"], + }); + }); + test("returns correct absolute path to mcp.json", async () => { const mcpConfigPath = await configureVscodeSettings(tempDir); From ba249352ba4e2bb141b86b09386c4af3d8589999 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 01:11:52 +0100 Subject: [PATCH 7/9] fix: properly merge VS Code user settings with Bun.JSONC.parse Use Bun.JSONC.parse to read existing user settings.json (supports comments and trailing commas), merge the marketplace URL additively, and write back. All existing settings data is preserved. Co-Authored-By: Claude Opus 4.6 --- src/helpers/vscode-settings.ts | 7 +-- tests/helpers/vscode-settings.test.ts | 72 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/helpers/vscode-settings.ts b/src/helpers/vscode-settings.ts index e7d9b41a..a7deccc8 100644 --- a/src/helpers/vscode-settings.ts +++ b/src/helpers/vscode-settings.ts @@ -148,9 +148,10 @@ export async function configureVscodeSettings( /** * Add the marketplace URL to VS Code's user-level settings.json. * - * Reads the existing file (if any), merges the URL into - * `chat.plugins.marketplaces`, and writes back. Creates parent - * directories if they don't exist. + * Reads the existing file with `Bun.JSONC.parse` (supports comments and + * trailing commas), merges the marketplace URL, and writes back as standard + * JSON. Comments in the original file are not preserved — VS Code re-reads + * the file without issue. */ export async function addMarketplaceToUserSettings( marketplaceUrl: string diff --git a/tests/helpers/vscode-settings.test.ts b/tests/helpers/vscode-settings.test.ts index 8ec11321..084ce542 100644 --- a/tests/helpers/vscode-settings.test.ts +++ b/tests/helpers/vscode-settings.test.ts @@ -7,6 +7,7 @@ import { mergeVscodeMcpConfig, mergeMarketplaceUrl, configureVscodeSettings, + addMarketplaceToUserSettings, } from "../../src/helpers/vscode-settings"; describe("mergeVscodeMcpConfig", () => { @@ -213,3 +214,74 @@ describe("configureVscodeSettings", () => { expect(mcpConfigPath).toBe(join(tempDir, ".vscode", "mcp.json")); }); }); + +describe("addMarketplaceToUserSettings", () => { + let tempDir: string; + let originalEnv: string | undefined; + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), "archgate-user-settings-test-")); + originalEnv = process.env.APPDATA; + process.env.APPDATA = tempDir; + }); + + afterEach(() => { + process.env.APPDATA = originalEnv; + rmSync(tempDir, { recursive: true, force: true }); + }); + + const URL = "https://user:token@plugins.archgate.dev/archgate.git"; + + function settingsPath() { + return join(tempDir, "Code", "User", "settings.json"); + } + + test("creates settings file when none exists", async () => { + await addMarketplaceToUserSettings(URL); + + const content = JSON.parse(await Bun.file(settingsPath()).text()); + expect(content["chat.plugins.marketplaces"]).toEqual([URL]); + }); + + test("merges JSONC settings with trailing commas without losing data", async () => { + const dir = join(tempDir, "Code", "User"); + mkdirSync(dir, { recursive: true }); + + const original = `{ + "security.allowedUNCHosts": ["wsl.localhost"], + "git.autofetch": true, + "chat.mcp.gallery.enabled": true, +}`; + await Bun.write(settingsPath(), original); + + await addMarketplaceToUserSettings(URL); + + const content = JSON.parse(await Bun.file(settingsPath()).text()); + expect(content["git.autofetch"]).toBe(true); + expect(content["chat.mcp.gallery.enabled"]).toBe(true); + expect(content["security.allowedUNCHosts"]).toEqual(["wsl.localhost"]); + expect(content["chat.plugins.marketplaces"]).toEqual([URL]); + }); + + test("deduplicates marketplace URLs", async () => { + const dir = join(tempDir, "Code", "User"); + mkdirSync(dir, { recursive: true }); + + await Bun.write( + settingsPath(), + JSON.stringify( + { "chat.plugins.marketplaces": ["https://other.git", URL] }, + null, + 2 + ) + ); + + await addMarketplaceToUserSettings(URL); + + const content = JSON.parse(await Bun.file(settingsPath()).text()); + expect(content["chat.plugins.marketplaces"]).toEqual([ + "https://other.git", + URL, + ]); + }); +}); From 030f19bb9c7b448ca71a82e1275b3c585558a948 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 01:19:59 +0100 Subject: [PATCH 8/9] fix: preserve VS Code default marketplaces when setting key is absent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VS Code has built-in defaults for chat.plugins.marketplaces (github/copilot-plugins, github/awesome-copilot) that are implicit — not written to settings.json. When we explicitly set the key, VS Code stops using its defaults. Now we seed the array with these defaults when the key is absent from the file, preventing silent override. Co-Authored-By: Claude Opus 4.6 --- src/helpers/vscode-settings.ts | 27 +++++++++-- tests/helpers/vscode-settings.test.ts | 64 +++++++++++++++------------ 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/helpers/vscode-settings.ts b/src/helpers/vscode-settings.ts index a7deccc8..7bf8a773 100644 --- a/src/helpers/vscode-settings.ts +++ b/src/helpers/vscode-settings.ts @@ -47,6 +47,18 @@ export function mergeVscodeMcpConfig( }; } +/** + * VS Code's built-in default marketplaces for `chat.plugins.marketplaces`. + * + * These are implicit defaults — VS Code uses them when the setting is absent + * from the user's settings.json. Once we explicitly set the key, VS Code stops + * using its defaults, so we must include them to avoid losing them. + */ +const VSCODE_DEFAULT_MARKETPLACES = [ + "github/copilot-plugins", + "github/awesome-copilot", +]; + /** * Deduplicate an array of strings while preserving order. */ @@ -57,6 +69,9 @@ function dedup(arr: string[]): string[] { /** * Add a marketplace URL to the `chat.plugins.marketplaces` array in a VS Code * user settings object. Preserves all other settings. Deduplicates URLs. + * + * When `chat.plugins.marketplaces` is not yet in the file, VS Code's built-in + * defaults are included so they are not lost when we explicitly set the key. */ export function mergeMarketplaceUrl( existing: VscodeUserSettings, @@ -64,16 +79,20 @@ export function mergeMarketplaceUrl( ): VscodeUserSettings { const merged: VscodeUserSettings = { ...existing }; + const hasExplicitMarketplaces = "chat.plugins.marketplaces" in existing; const existingMarketplaces = Array.isArray( merged["chat.plugins.marketplaces"] ) ? (merged["chat.plugins.marketplaces"] as string[]) : []; - merged["chat.plugins.marketplaces"] = dedup([ - ...existingMarketplaces, - marketplaceUrl, - ]); + // When the key is absent, seed with VS Code's built-in defaults so we don't + // silently override them by setting the key explicitly. + const base = hasExplicitMarketplaces + ? existingMarketplaces + : [...VSCODE_DEFAULT_MARKETPLACES, ...existingMarketplaces]; + + merged["chat.plugins.marketplaces"] = dedup([...base, marketplaceUrl]); return merged; } diff --git a/tests/helpers/vscode-settings.test.ts b/tests/helpers/vscode-settings.test.ts index 084ce542..e6f5310b 100644 --- a/tests/helpers/vscode-settings.test.ts +++ b/tests/helpers/vscode-settings.test.ts @@ -93,12 +93,16 @@ describe("mergeVscodeMcpConfig", () => { describe("mergeMarketplaceUrl", () => { const URL = "https://user:token@plugins.archgate.dev/archgate.git"; - test("adds marketplace URL to empty settings", () => { + test("includes VS Code defaults when key is absent", () => { const result = mergeMarketplaceUrl({}, URL); - expect(result["chat.plugins.marketplaces"]).toEqual([URL]); + expect(result["chat.plugins.marketplaces"]).toEqual([ + "github/copilot-plugins", + "github/awesome-copilot", + URL, + ]); }); - test("appends URL with dedup", () => { + test("appends URL with dedup when key already exists", () => { const result = mergeMarketplaceUrl( { "chat.plugins.marketplaces": ["https://other.git", URL] }, URL @@ -109,21 +113,24 @@ describe("mergeMarketplaceUrl", () => { ]); }); - test("handles non-array marketplaces gracefully", () => { + test("does not re-add defaults when key is explicitly set", () => { const result = mergeMarketplaceUrl( - { "chat.plugins.marketplaces": "not-an-array" }, + { "chat.plugins.marketplaces": ["https://custom.git"] }, URL ); - expect(result["chat.plugins.marketplaces"]).toEqual([URL]); + expect(result["chat.plugins.marketplaces"]).toEqual([ + "https://custom.git", + URL, + ]); }); - test("preserves other settings keys", () => { + test("handles non-array marketplaces gracefully", () => { const result = mergeMarketplaceUrl( - { "editor.fontSize": 14, "workbench.colorTheme": "One Dark" }, + { "chat.plugins.marketplaces": "not-an-array", "editor.fontSize": 14 }, URL ); + expect(result["chat.plugins.marketplaces"]).toEqual([URL]); expect(result["editor.fontSize"]).toBe(14); - expect(result["workbench.colorTheme"]).toBe("One Dark"); }); }); @@ -236,44 +243,45 @@ describe("addMarketplaceToUserSettings", () => { return join(tempDir, "Code", "User", "settings.json"); } - test("creates settings file when none exists", async () => { + test("creates settings file with defaults when none exists", async () => { await addMarketplaceToUserSettings(URL); const content = JSON.parse(await Bun.file(settingsPath()).text()); - expect(content["chat.plugins.marketplaces"]).toEqual([URL]); + expect(content["chat.plugins.marketplaces"]).toEqual([ + "github/copilot-plugins", + "github/awesome-copilot", + URL, + ]); }); - test("merges JSONC settings with trailing commas without losing data", async () => { + test("merges JSONC settings and includes defaults when key absent", async () => { const dir = join(tempDir, "Code", "User"); mkdirSync(dir, { recursive: true }); - - const original = `{ - "security.allowedUNCHosts": ["wsl.localhost"], - "git.autofetch": true, - "chat.mcp.gallery.enabled": true, -}`; - await Bun.write(settingsPath(), original); + await Bun.write( + settingsPath(), + `{ "git.autofetch": true, "chat.mcp.gallery.enabled": true, }` + ); await addMarketplaceToUserSettings(URL); const content = JSON.parse(await Bun.file(settingsPath()).text()); expect(content["git.autofetch"]).toBe(true); expect(content["chat.mcp.gallery.enabled"]).toBe(true); - expect(content["security.allowedUNCHosts"]).toEqual(["wsl.localhost"]); - expect(content["chat.plugins.marketplaces"]).toEqual([URL]); + expect(content["chat.plugins.marketplaces"]).toEqual([ + "github/copilot-plugins", + "github/awesome-copilot", + URL, + ]); }); - test("deduplicates marketplace URLs", async () => { + test("deduplicates when key already exists", async () => { const dir = join(tempDir, "Code", "User"); mkdirSync(dir, { recursive: true }); - await Bun.write( settingsPath(), - JSON.stringify( - { "chat.plugins.marketplaces": ["https://other.git", URL] }, - null, - 2 - ) + JSON.stringify({ + "chat.plugins.marketplaces": ["https://other.git", URL], + }) ); await addMarketplaceToUserSettings(URL); From 93f79b7d2f8c5ed51177291342595afe7020814b Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Thu, 5 Mar 2026 01:23:35 +0100 Subject: [PATCH 9/9] fix: make vscode-settings tests cross-platform for CI Override both APPDATA (Windows) and HOME (macOS/Linux) in tests so getVscodeUserSettingsPath() resolves into the temp directory on all platforms. Use the real path resolver in tests instead of hardcoding Windows-specific paths. Co-Authored-By: Claude Opus 4.6 --- tests/helpers/vscode-settings.test.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/helpers/vscode-settings.test.ts b/tests/helpers/vscode-settings.test.ts index e6f5310b..5c04ca90 100644 --- a/tests/helpers/vscode-settings.test.ts +++ b/tests/helpers/vscode-settings.test.ts @@ -8,6 +8,7 @@ import { mergeMarketplaceUrl, configureVscodeSettings, addMarketplaceToUserSettings, + getVscodeUserSettingsPath, } from "../../src/helpers/vscode-settings"; describe("mergeVscodeMcpConfig", () => { @@ -224,23 +225,26 @@ describe("configureVscodeSettings", () => { describe("addMarketplaceToUserSettings", () => { let tempDir: string; - let originalEnv: string | undefined; + let savedEnv: Record; beforeEach(() => { tempDir = mkdtempSync(join(tmpdir(), "archgate-user-settings-test-")); - originalEnv = process.env.APPDATA; - process.env.APPDATA = tempDir; + // Save and override env so getVscodeUserSettingsPath() resolves into tempDir + savedEnv = { APPDATA: process.env.APPDATA, HOME: process.env.HOME }; + process.env.APPDATA = tempDir; // Windows + process.env.HOME = tempDir; // macOS/Linux (homedir()) }); afterEach(() => { - process.env.APPDATA = originalEnv; + Object.assign(process.env, savedEnv); rmSync(tempDir, { recursive: true, force: true }); }); const URL = "https://user:token@plugins.archgate.dev/archgate.git"; + /** Use the real path resolver so the test matches addMarketplaceToUserSettings */ function settingsPath() { - return join(tempDir, "Code", "User", "settings.json"); + return getVscodeUserSettingsPath(); } test("creates settings file with defaults when none exists", async () => { @@ -255,8 +259,7 @@ describe("addMarketplaceToUserSettings", () => { }); test("merges JSONC settings and includes defaults when key absent", async () => { - const dir = join(tempDir, "Code", "User"); - mkdirSync(dir, { recursive: true }); + mkdirSync(join(settingsPath(), ".."), { recursive: true }); await Bun.write( settingsPath(), `{ "git.autofetch": true, "chat.mcp.gallery.enabled": true, }` @@ -266,7 +269,6 @@ describe("addMarketplaceToUserSettings", () => { const content = JSON.parse(await Bun.file(settingsPath()).text()); expect(content["git.autofetch"]).toBe(true); - expect(content["chat.mcp.gallery.enabled"]).toBe(true); expect(content["chat.plugins.marketplaces"]).toEqual([ "github/copilot-plugins", "github/awesome-copilot", @@ -275,8 +277,7 @@ describe("addMarketplaceToUserSettings", () => { }); test("deduplicates when key already exists", async () => { - const dir = join(tempDir, "Code", "User"); - mkdirSync(dir, { recursive: true }); + mkdirSync(join(settingsPath(), ".."), { recursive: true }); await Bun.write( settingsPath(), JSON.stringify({