@bridge_gpt/mcp-server 0.2.13 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -14,7 +14,7 @@ var VERSION;
14
14
  var init_version_generated = __esm({
15
15
  "src/version.generated.ts"() {
16
16
  "use strict";
17
- VERSION = "0.2.13";
17
+ VERSION = "0.2.16";
18
18
  }
19
19
  });
20
20
 
@@ -1092,6 +1092,23 @@ function agentDescriptor(agent) {
1092
1092
  function uvDescriptor() {
1093
1093
  return commandDescriptor("uv", "uv", UV_INSTALL_HINTS);
1094
1094
  }
1095
+ function astGrepDescriptor() {
1096
+ return {
1097
+ id: "ast-grep",
1098
+ label: "ast-grep (or sg)",
1099
+ installHint: AST_GREP_INSTALL_HINTS,
1100
+ probe: async (deps) => {
1101
+ const found = await resolveFirstCommandOnPath(deps, ["ast-grep", "sg"]);
1102
+ return found ? { found: true, detail: `found on PATH (${found})` } : { found: false };
1103
+ }
1104
+ };
1105
+ }
1106
+ function lizardDescriptor() {
1107
+ return commandDescriptor("lizard", "lizard", LIZARD_INSTALL_HINTS);
1108
+ }
1109
+ function ripgrepDescriptor() {
1110
+ return commandDescriptor("rg", "ripgrep (rg)", RIPGREP_INSTALL_HINTS);
1111
+ }
1095
1112
  function credentialResolutionDescriptor() {
1096
1113
  return {
1097
1114
  id: "bapi-credentials",
@@ -1170,7 +1187,10 @@ function getDoctorOnlyPrereqDescriptors(_platform, _env, agent) {
1170
1187
  uvDescriptor(),
1171
1188
  agentDescriptor(agent),
1172
1189
  credentialResolutionDescriptor(),
1173
- worktreeMcpReachabilityDescriptor()
1190
+ worktreeMcpReachabilityDescriptor(),
1191
+ astGrepDescriptor(),
1192
+ lizardDescriptor(),
1193
+ ripgrepDescriptor()
1174
1194
  ];
1175
1195
  }
1176
1196
  function getDoctorPrereqDescriptors(platform, env, agent) {
@@ -1211,7 +1231,7 @@ async function enforcePreflightPrerequisites(deps) {
1211
1231
  }
1212
1232
  return { ok: true };
1213
1233
  }
1214
- var WORKTRUNK_BINARY_OVERRIDE_ENV, WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERSHELL_CANDIDATES, DEFAULT_WINDOWS_WORKTRUNK_BINARY, DEFAULT_POSIX_WORKTRUNK_BINARY, TMUX_COMMAND, GIT_FOR_WINDOWS_BASH_HINT, START_TICKETS_DOCTOR_COMMAND, WORKTRUNK_INSTALL_HINTS, GIT_INSTALL_HINTS, OSASCRIPT_INSTALL_HINTS, TMUX_INSTALL_HINTS, GIT_BASH_INSTALL_HINTS, WINDOWS_LAUNCHER_INSTALL_HINTS, GIT_WORK_TREE_INSTALL_HINTS, UV_INSTALL_HINTS, CREDENTIAL_RESOLUTION_HINT, CREDENTIAL_RESOLUTION_INSTALL_HINTS, WORKTREE_MCP_HINT, WORKTREE_MCP_INSTALL_HINTS;
1234
+ var WORKTRUNK_BINARY_OVERRIDE_ENV, WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERSHELL_CANDIDATES, DEFAULT_WINDOWS_WORKTRUNK_BINARY, DEFAULT_POSIX_WORKTRUNK_BINARY, TMUX_COMMAND, GIT_FOR_WINDOWS_BASH_HINT, START_TICKETS_DOCTOR_COMMAND, WORKTRUNK_INSTALL_HINTS, GIT_INSTALL_HINTS, OSASCRIPT_INSTALL_HINTS, TMUX_INSTALL_HINTS, GIT_BASH_INSTALL_HINTS, WINDOWS_LAUNCHER_INSTALL_HINTS, GIT_WORK_TREE_INSTALL_HINTS, UV_INSTALL_HINTS, AST_GREP_INSTALL_HINTS, LIZARD_INSTALL_HINTS, RIPGREP_INSTALL_HINTS, CREDENTIAL_RESOLUTION_HINT, CREDENTIAL_RESOLUTION_INSTALL_HINTS, WORKTREE_MCP_HINT, WORKTREE_MCP_INSTALL_HINTS;
1215
1235
  var init_start_tickets_prereqs = __esm({
1216
1236
  "src/start-tickets-prereqs.ts"() {
1217
1237
  "use strict";
@@ -1268,6 +1288,21 @@ var init_start_tickets_prereqs = __esm({
1268
1288
  linux: "curl -LsSf https://astral.sh/uv/install.sh | sh",
1269
1289
  win32: 'powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"'
1270
1290
  };
1291
+ AST_GREP_INSTALL_HINTS = {
1292
+ darwin: "uv tool install ast-grep-cli",
1293
+ linux: "uv tool install ast-grep-cli",
1294
+ win32: "uv tool install ast-grep-cli"
1295
+ };
1296
+ LIZARD_INSTALL_HINTS = {
1297
+ darwin: "uv tool install lizard",
1298
+ linux: "uv tool install lizard",
1299
+ win32: "uv tool install lizard"
1300
+ };
1301
+ RIPGREP_INSTALL_HINTS = {
1302
+ darwin: "brew install ripgrep",
1303
+ linux: "Install ripgrep with your distro package manager, e.g. apt install ripgrep",
1304
+ win32: "winget install BurntSushi.ripgrep.MSVC"
1305
+ };
1271
1306
  CREDENTIAL_RESOLUTION_HINT = 'Rerun /install-bridge to persist the routing credential, set BAPI_API_KEY in the environment, or add it under "bapi:<repo_name>" in ~/.config/bridge/credentials.json. To migrate a key that only lives in .mcp.json / .cursor/mcp.json, run: npx -y @bridge_gpt/mcp-server credentials migrate-agent-config --write-credentials.';
1272
1307
  CREDENTIAL_RESOLUTION_INSTALL_HINTS = {
1273
1308
  darwin: CREDENTIAL_RESOLUTION_HINT,
@@ -13067,7 +13102,7 @@ var INSTRUCTIONS = {
13067
13102
  init_version_generated();
13068
13103
 
13069
13104
  // src/readme.generated.ts
13070
- var README = '# @bridge_gpt/mcp-server\n\nMCP server for [Bridge API](https://bridgegpt-api.com) \u2014 exposes Jira integration endpoints as MCP tools for AI coding agents. Works with Claude Code, VS Code/Copilot, Cursor, Windsurf, and OpenAI Codex.\n\n> **New here?** Jump to [Usage Documentation](#usage-documentation) for what you can actually do with Bridge, grouped by how often you\'ll reach for it.\n\n## Getting Started\n\n### Quick start (one command)\n\nFrom your **project root**, run:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@latest install-bridge\n```\n\n`install-bridge` collapses the whole setup into a single command. It:\n\n1. **Scaffolds** the project (the same artifacts `--init` writes: slash commands,\n agents, `.bridge/pipelines/`, and secret-free MCP config placeholders).\n2. **Writes the per-host MCP config** (`.mcp.json` / `.cursor/mcp.json` /\n `.vscode/mcp.json`) with your real `BAPI_REPO_NAME` / `BAPI_API_KEY` /\n `BAPI_BASE_URL` / `BAPI_DOCS_DIR`, preserving any unrelated servers. The\n launcher it writes is pinned to the exact installed version so `npx` never\n silently reuses a stale local copy. (Windsurf and Codex are global configs it\n can\'t safely write \u2014 it prints copy-paste instructions for those.)\n3. **Verifies connectivity** against the Bridge API before persisting anything.\n4. **Persists your key** to the user-scoped credential store\n (`~/.config/bridge/credentials.json`, target `bapi:<repo>`) so shell-spawned\n tooling (e.g. `start-tickets`) can resolve it.\n5. **Opens a fresh agent session** that runs `/install-bridge` (to derive the\n remaining config fields from your codebase) and then `/learn-repository`.\n\nThe only inputs are an **API key** and a **repo name** (everything else is\nderived). Resolution order:\n\n- **API key:** `--api-key <key>` \u2192 `BAPI_API_KEY` env \u2192 an interactive (no-echo)\n prompt. Generate one first on the Bridge API web UI **Security** page (see\n [Generate an API Key](#2-generate-an-api-key)); the command consumes a key, it\n never mints one. The key is **never printed or logged**.\n- **Repo name:** `--repo <name>` \u2192 `BAPI_REPO_NAME` env \u2192 an inferred default you\n confirm interactively. It MUST match the server-side repository registration.\n\nUseful flags:\n\n- `--dry-run` \u2014 preview every step (scaffold targets, config files and keys with\n the key value **redacted**, the ping target, the credential store target, and\n the exact agent spawn command) without writing, pinging, or spawning anything.\n- `--force` \u2014 overwrite an existing real `BAPI_API_KEY` in a host config without\n prompting (re-running is otherwise non-destructive).\n- `--agent claude|cursor-agent` \u2014 which agent to launch for the agentic remainder\n (default `claude`).\n\nThat\'s it \u2014 once `install-bridge` finishes you\'re connected. If you prefer to do\nit by hand (or just want to understand each step), the manual flow below does the\nsame thing.\n\n### Manual Setup (Alternative)\n\n#### 1. Install the Package\n\nFrom your **project root**, install the MCP server and scaffold slash commands:\n\n```bash\nnpm i @bridge_gpt/mcp-server\nnpx -y @bridge_gpt/mcp-server --init\n```\n\n`--init` must be run from the directory containing your `package.json`. It:\n\n- Creates slash commands in `.claude/commands/` and `.cursor/commands/`\n- Detects existing MCP config files and sets `BAPI_PROJECT_ROOT` so local file output resolves correctly\n- Scaffolds `.bridge/pipelines/` for custom pipeline authoring\n\nRe-run `--init` after upgrading the package to get updated commands.\n\n#### 2. Generate an API Key\n\n1. Log in to [Bridge API](https://bridgegpt-api.com) and navigate to your project\'s **Security** page\n2. Click **Create New Key**\n3. Enter your email, an optional label (e.g., "MCP Server"), and select the **Admin** role\n4. Click **Create Key**\n5. **Copy the key immediately** \u2014 it will not be shown again\n\n#### 3. Configure the MCP Server\n\nAdd the following to your editor\'s MCP configuration file, pasting in the API key from step 2:\n\n<details>\n<summary><strong>Claude Code (.mcp.json)</strong></summary>\n\nThe `--init` command (step 1) detects Claude Code and creates a `.mcp.json` at your project root with placeholder values. Open it and replace `your-repo` and `your-api-key` with your actual values from step 2:\n\n```json\n{\n "mcpServers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n</details>\n\n<details>\n<summary><strong>VS Code / Copilot (.vscode/mcp.json)</strong></summary>\n\n```json\n{\n "servers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n</details>\n\n<details>\n<summary><strong>Cursor (.cursor/mcp.json)</strong></summary>\n\n```json\n{\n "mcpServers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n\n> If project-local config is not supported in your Cursor version, use `~/.cursor/config/mcp.json` instead.\n</details>\n\n<details>\n<summary><strong>Windsurf (~/.codeium/windsurf/mcp_config.json)</strong></summary>\n\nWindsurf only supports global MCP configuration.\n\n```json\n{\n "mcpServers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n</details>\n\n<details>\n<summary><strong>OpenAI Codex (~/.codex/config.toml)</strong></summary>\n\n```toml\n[mcp_servers.bridge-api]\ncommand = "npx"\nargs = ["-y", "@bridge_gpt/mcp-server"]\n\n[mcp_servers.bridge-api.env]\nBAPI_BASE_URL = "https://bridgegpt-api.com"\nBAPI_REPO_NAME = "your-repo"\nBAPI_API_KEY = "your-api-key"\nBAPI_DOCS_DIR = "docs/tmp"\n```\n\n> Codex users: set `BAPI_PROJECT_ROOT` manually in your config (see Environment Variables below).\n</details>\n\nAfter saving the config, restart your editor or reload the MCP server connection. Verify connectivity by asking your AI assistant to call the `ping` tool.\n\n#### 4. First-Time Setup: Teach Bridge Your Codebase\n\nIf you\'re the first person to install Bridge API on your project, run the `/learn-repository` slash command after completing setup. This analyzes your codebase\'s architecture, testing patterns, code review standards, and documentation conventions, then uploads the findings to Bridge API. This gives Bridge the context it needs to generate implementation plans, ticket critiques, and code reviews that are consistent with your project\'s actual architecture and conventions.\n\nYou only need to do this once per project \u2014 the learned standards persist for all team members.\n\n### Upgrading\n\nTo upgrade to the latest version and refresh all scaffolded artifacts in one step:\n\n```bash\nnpx -y @bridge_gpt/mcp-server --upgrade\n```\n\nThis runs `npm i @bridge_gpt/mcp-server@latest`, prints a before/after version summary, then re-runs the full `--init` scaffolding flow to update your slash commands, agents, and pipeline definitions.\n\nThe MCP server also checks for updates automatically on startup. If a newer version is available, you\'ll see a notice in your editor\'s MCP output logs with the upgrade command to run. This check is cached for 24 hours and never blocks server startup.\n\n## Usage Documentation\n\nThis is the Bridge API tooling worth knowing about as a software engineer \u2014 the things you\'d ask an agent to do \u2014 grouped by how often you would use them. Each entry covers **what it does**, **when it\'s useful** and **how to use it**. The behind-the-scenes plumbing is summarized at the end under [Extra Capabilities](#extra-capabilities), and a full enumeration lives in [Reference](#reference).\n\nWorking in a Salesforce B2C Commerce codebase? Bridge also ships read-only SFCC platform-introspection tools \u2014 see [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools).\n\nFor invocation, prefer the slash command \u2014 it\'s deterministic. A free-text example is shown only where natural-language phrasing reliably maps to the right automation; high-consequence or easily-misread automations show only the slash command on purpose.\n\n### Tier 1 \u2014 Regularly useful\n\nThese features are useful for most tickets.\n\n**1. Review Ticket**\n- **What it does:** Runs a full quality review of a ticket: clarifying questions + critique (initial round), an automatic alternate-model second opinion, then evaluates findings and produces a decision page to accept/reject them. The second-opinion pass is included by default and can be skipped with `--rounds=1`.\n- **When it\'s useful:** (Refinement) Right after a ticket is drafted, before anyone starts building \u2014 to surface gaps and tighten it.\n- **How to use it:** `/review-ticket BAPI-123` (command only \u2014 "review" as free text is easily mistaken for a freehand agent review).\n- **Flags:** `--auto` auto-accept findings / skip the approval gates \xB7 `--rounds=1` skip the automatic second-opinion review step while preserving downstream evaluation and decision-capture work (cheaper single-pass review). `--rounds=2` (default) runs the full two-round review.\n- **Multi-ticket fan-out:** `/review-tickets BAPI-123 BAPI-456` opens one terminal tab per ticket for parallel review with no worktrees (terminal launcher only \u2014 no `wt`/`git`). All `/review-ticket` flags apply; `--review KEY=auto,rounds=N` sets per-ticket overrides. Packaged CLI: `npx -y @bridge_gpt/mcp-server review-tickets KEY [KEY ...]`.\n\n**2. Start Tickets**\n- **What it does:** Creates one git worktree per ticket and spawns an agent session in each to implement them in parallel.\n- **When it\'s useful:** (Implementation | Automation) When you\'re ready to start building one or more refined tickets concurrently.\n- **How to use it:** `/start-tickets BAPI-248 BAPI-250` (see [CLI Subcommands](#cli-subcommands) for the full flag table and cross-platform behavior).\n- **Flags:** `--auto` skip the approval gates \xB7 `--base-branch <branch>` branch off something other than the default.\n\n**3. Brainstorm**\n- **What it does:** Fans your problem out to two different LLMs and returns their approaches directly. Runs in one of three modes, selected via `mode`: **`technical`** (default \u2014 implementation/architecture approaches), **`design`** (UI/UX and visual direction), or **`discovery`** (stakeholder discovery questions for early/vague tasks, grouped into `Technical Discovery Questions` and `Business / Stakeholder Discovery Questions` and tagged `[HUMAN]`/`[CODE]`/`[TICKET]`). Discovery needs no extra configuration. The legacy boolean `design=true` still works and maps to `mode: "design"`.\n- **When it\'s useful:** (Architecture | Refinement) Early, when you want a spread of approaches \u2014 `technical` for *how to build it*, `design` for *how it should look*, `discovery` for *what we still need to figure out* before a real ticket exists.\n- **How to use it:** ask your agent to brainstorm \u2014 *"Brainstorm approaches for adding rate limiting to the LLM client; fan it out to multiple models."* For a design pass: *"Run a design brainstorm for the evidence-freshness dashboard UI."* For early discovery: *"Run a discovery brainstorm \u2014 `request_brainstorm` with `mode: "discovery"` \u2014 for this vague request so we can collect the questions stakeholders need to answer first."*\n\n**4. Deep Research**\n- **What it does:** Runs multi-source, fact-checked web research on a technical topic and returns a cited report.\n- **When it\'s useful:** (Architecture | Refinement) When a decision hinges on outside knowledge (libraries, best practices, standards) you don\'t already have.\n- **How to use it:** `/bridge-research <question>`\n\n**5. Jira Ticket Writer**\n- **What it does:** An agent that drafts a well-structured Jira ticket from a plain description, applying your project\'s standards.\n- **When it\'s useful:** (Refinement) When you have an idea in your head and want a properly-formatted ticket draft without writing it by hand.\n- **How to use it:** `/write-ticket <description>` (or ask your agent) \u2014 *"Use the jira ticket writer to turn our conversation into a ticket."*\n- **Flags:** `--standards <path>` apply a specific standards file when drafting.\n\n**6. Upload Ticket**\n- **What it does:** Pushes a drafted ticket up to Jira as a real issue (the `create_ticket` capability); handles markdown and child tickets under an Epic.\n- **When it\'s useful:** (Refinement) The final step after drafting \u2014 to get the ticket into Jira so it can be tracked and worked.\n- **How to use it:** Ask your agent to create the ticket, and it should confirm before creating the live Jira issue.\n- **Options:** Describe the issue type (Bug / Story / Task / Epic) and, for a child under an Epic, the parent key.\n\n### Tier 2 \u2014 Occasionally useful\n\nThese features are good to know, but you probably won\'t use them every day.\n\n**1. Plan Ticket**\n- **What it does:** Generates a step-by-step implementation plan for a ticket, with references to real code files, and saves it locally.\n- **When it\'s useful:** (Refinement | Implementation) Once a ticket is solid and you want a concrete build plan before (or instead of) auto-implementing.\n- **How to use it:** `/plan-ticket BAPI-123`\n- **Flags:** `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check the plan with a second provider.\n\n**2. Clarify Ticket**\n- **What it does:** Generates clarifying questions for a ticket (or debugging guidance for bugs) and saves them locally.\n- **When it\'s useful:** (Refinement) When a ticket feels under-specified and you want the open questions made explicit.\n- **How to use it:** `/clarify-ticket BAPI-123` \u2014 *"Generate clarifying questions for BAPI-123"*\n- **Flags:** `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check with a second provider.\n\n**3. Critique Ticket**\n- **What it does:** Critiques a ticket\'s quality against your project standards and lists deviations + improvements.\n- **When it\'s useful:** (Refinement) When you want a quality gate on a ticket before it\'s worked.\n- **How to use it:** `/critique-ticket BAPI-123` \u2014 *"Critique BAPI-123 against our project standards and list what\'s missing or deviating."*\n- **Flags:** `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check with a second provider.\n\n**4. Create Doc**\n- **What it does:** Generates a design document for a ticket \u2014 a TDD (technical design, engineer audience), an FSD (functional spec, for product/design/QA), or a PRD (product requirements: problem, goals, success metrics) \u2014 and saves it locally.\n- **When it\'s useful:** (Architecture | Refinement) When a ticket needs a fuller design write-up before planning or implementation, in the shape that fits your audience.\n- **How to use it:** `/create-doc BAPI-123 --doc-type tdd` (or `fsd` / `prd`)\n- **Flags:** `--doc-type tdd|fsd|prd` which document to generate (required) \xB7 `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check with a second provider.\n\n**5. Explore Ticket**\n- **What it does:** Explores the codebase for a task and recommends implementation options or surfaces clarifying questions, with optional research.\n- **When it\'s useful:** (Architecture | Refinement) Before writing a ticket or plan, when you\'re unsure how a change would fit the existing code.\n- **How to use it:** `/explore-ticket <task>` \u2014 *"Explore the codebase for how we\'d add a Mistral LLM provider and recommend 2\u20133 implementation options."*\n\n**6. Second Opinion**\n- **What it does:** Gets an immediate critique of any text from a different model family \u2014 no artifact saved, just the reply.\n- **When it\'s useful:** (Architecture | Refinement | Implementation) Any time you want a quick sanity check on a plan, draft, or decision from a fresh perspective.\n- **How to use it:** ask your agent \u2014 *"Get a second opinion from Gemini on whether the BAPI-123 plan\'s migration step is safe to run against prod."*\n- **Options:** pick the provider (anthropic / openai / gemini) and the tier (cheap / basic / premium).\n\n**7. Generate Image**\n- **What it does:** Generates an image from a text prompt using a provider image model (OpenAI `gpt-image-2` by default, or Google Imagen) and returns the image directly. Spends provider credits on every call.\n- **When it\'s useful:** (Architecture | Refinement) When you want a quick visual \u2014 a UI mockup, diagram, or illustration \u2014 to anchor a design discussion or attach to a ticket.\n- **How to use it:** ask your agent \u2014 *"Generate an image of a dashboard showing SOC2 evidence freshness as a traffic-light grid."*\n- **Options:** `provider` openai (`gpt-image-2`) / gemini (Imagen \u2014 adds an invisible SynthID watermark) \xB7 `quality` low (default, cheapest) / medium / high \xB7 `size` 1024x1024 / 1024x1536 / 1536x1024. The image is always saved to `BAPI_DOCS_DIR/images/` and also returned inline.\n\n**8. Implement Ticket**\n- **What it does:** Full build for one ticket: generate a plan, write the code, commit, open a PR, and monitor CI.\n- **When it\'s useful:** (Implementation) When a ticket is ready and you want it taken from plan to open PR in one go.\n- **How to use it:** `/implement-ticket BAPI-123` (command only \u2014 "implement X" as free text almost always triggers a freehand build instead of the Bridge plan\u2192code\u2192PR\u2192CI pipeline).\n- **Flags:** `--auto` skip the approval gates (e.g. auto-commit/push).\n\n**9. Full Automation**\n- **What it does:** Drives the whole chain end-to-end: idea \u2192 ticket(s) \u2192 review each \u2192 spawn worktrees to implement.\n- **When it\'s useful:** (Automation) When you want to go from a raw idea to in-progress implementation with minimal hands-on steps.\n- **How to use it:** `/full-automation <idea>` (command only \u2014 creates tickets, spawns worktrees, and carries scheduling/`--max-children` flags that free text can\'t).\n- **Flags:** `--require-approval` toggle the approval gates, full automation runs end to end by default.\n\n**10. Idea to Ticket**\n- **What it does:** Turns a one-line idea into a Jira Task/Spike (or an Epic plus child tickets), with research, duplicate detection, and a critique pass built in.\n- **When it\'s useful:** (Refinement | Automation) When you have a rough idea and want a fully-formed, uploaded ticket without the manual draft-and-refine loop.\n- **How to use it:** `/idea-to-ticket <idea>`\n\n### Tier 3 \u2014 Now and then\n\nThese features are useful once in a while, but you probably won\'t need them everyday.\n\n**1. Reimplement Ticket**\n- **What it does:** Pulls in new context/attachments since the last pass and implements small follow-up changes on an already-built ticket.\n- **When it\'s useful:** (Implementation) After review feedback or new screenshots, when you need a targeted second pass rather than a fresh build.\n- **How to use it:** `/reimplement-ticket BAPI-123`\n\n**2. Run Tests**\n- **What it does:** Runs the unit and E2E suites and autonomously triages/fixes failures (via the test-correction agent).\n- **When it\'s useful:** (Implementation) After making changes, to confirm everything passes and auto-fix straightforward breakages.\n- **How to use it:** `/run-tests` (`--unit-only`, `--skip-e2e`)\n\n**3. Plan Epic**\n- **What it does:** Decomposes a large epic into sub-tasks with a structured exploration doc for each.\n- **When it\'s useful:** (Architecture | Refinement) When a feature is too big for one ticket and you need it broken down and scoped.\n- **How to use it:** `/plan-epic <epic>` \u2014 *"Decompose the epic \'migrate PayPal token storage off Custom Objects\' into sub-tasks with an exploration doc for each."*\n\n**4. Update Ticket**\n- **What it does:** Synthesizes a ticket\'s clarifying answers and critique into a rewritten description and pushes it to Jira.\n- **When it\'s useful:** (Refinement) After review, to fold the resolved questions and fixes back into the ticket itself.\n- **How to use it:** `/update-ticket BAPI-123` (command only \u2014 does a full overwrite of the live Jira description; "update" as free text is both vague and hard to reverse).\n\n**5. Get Ticket**\n- **What it does:** Retrieves the full details of a Jira ticket (summary, status, description, etc.).\n- **When it\'s useful:** (Refinement | Implementation) Any time you want the agent to read a ticket before acting on it.\n- **How to use it:** ask your agent \u2014 *"Pull up BAPI-123 and show me its description, status, and acceptance criteria."*\n\n**6. Write Comment**\n- **What it does:** Posts a comment on a Jira ticket (markdown; long ones can attach as a file).\n- **When it\'s useful:** (Refinement | Implementation) To leave context, status, or a decision trail on the ticket.\n- **How to use it:** ask your agent \u2014 *"Post a comment on BAPI-123: blocked on the expired Atlassian token \u2014 will retry after it\'s rotated."*\n\n**7. Download / Upload Attachment**\n- **What it does:** Pulls files off a Jira ticket to disk, or attaches a local file to a ticket.\n- **When it\'s useful:** (Refinement | Implementation) When a ticket has design files/logs you need locally, or you want to attach output back to it.\n- **How to use it:** ask your agent \u2014 *"Download the design mockups attached to BAPI-123 into my docs folder."* / *"Attach build-log.txt to BAPI-123."*\n\n**8. Learn Repository**\n- **What it does:** Researches and documents the repo\'s architecture, testing, review, and correctness standards, then saves them to Bridge for future agents.\n- **When it\'s useful:** (Setup/Learning) When onboarding a new repo, or after big changes, so Bridge\'s agents follow your conventions.\n- **How to use it:** `/learn-repository`\n\n**9. Teach Bridge**\n- **What it does:** Takes a plain-English instruction, figures out which standards field it belongs to, and merges it in (admin only).\n- **When it\'s useful:** (Setup/Learning) When you notice the agents missing a convention and want to correct it in one sentence.\n- **How to use it:** `/teach-bridge <teaching>` \u2014 *"Teach Bridge: always use data-testid selectors in E2E tests."*\n\n### Operational commands\n\nWorkflow commands you\'ll reach for during implementation and CI, beyond the tiers above:\n\n| Command | What it does |\n|---|---|\n| `/code-ticket PROJ-123` | Download the implementation plan and questions, then execute the plan inline |\n| `/commit-ticket PROJ-123` | Stage, commit, and push changes; transition Jira status; post a smoke-test comment |\n| `/create-pr PROJ-123` | Commit staged changes and open a pull request |\n| `/check-ci [PROJ-123]` | Monitor CI checks for the current branch, triage failures, apply fixes, and report results |\n| `/parse-repository` | Queue a background job to index the repository for Bridge AI agents |\n| `/check-parse-status` | Check whether a background repository parse job is still running |\n| `/scan-tickets` | Sync recently-updated Jira tickets and backfill workflow timestamps |\n\n> Commands are designed for Claude Code. Other editors may support slash commands differently \u2014 check your editor\'s documentation for how to invoke prompt files.\n\n### Extra Capabilities\n\nBehind-the-scenes capabilities an agent gains from the MCP tools \u2014 mostly invoked automatically by the commands above, rarely requested by name:\n\n- **Ship a PR end-to-end:** commit & push, open a pull request, transition the Jira status, and discover/poll CI checks (powers `commit-ticket`, `create-pr`, `check-ci`, `implement-ticket`).\n- **Architecture plan** for a ticket (design-level guidance, separate from the implementation plan).\n- **Index the codebase** so Bridge\'s agents can reason about it: queue/parse the repo, check parse status, regenerate the directory map.\n- **Read & tune project config/standards:** list/read/update config fields, fetch project standards, and the per-topic `learn-*` commands that populate them.\n- **Ticket lifecycle bookkeeping:** track tickets and backfill workflow-state timestamps (`scan-tickets`), search across tickets, read comments, list attachments.\n- **Pipeline machinery:** list/inspect pipeline recipes and run/resume/list/delete pipeline runs (the engine under the orchestration commands).\n- **Decision page** generation for capturing human review decisions as structured data.\n- **Connectivity & identity checks:** ping Bridge, check your role, resolve the local docs directory.\n- **Retrieve any generated artifact** (`get_*` for plans, critiques, questions, brainstorms, research, architecture) without regenerating it.\n- **Tiered-section execution telemetry** recording (internal measurement).\n\n## Salesforce B2C Commerce (SFCC) Tools\n\nSalesforce\'s official MCP server, `@salesforce/b2c-dx-mcp`, covers developer-experience tasks \u2014 cartridge deploy, step debugging, docs search, logs/MRT, and SCAPI Custom API scaffolding. As of its current toolset it has no way to *read* the platform\'s object model, custom objects, or site configuration \u2014 exactly the context an AI coding agent needs to write correct Commerce code and to inspect a sandbox before touching it. Bridge\'s SFCC tools install side-by-side with `b2c-dx-mcp` (they don\'t duplicate its surface) and close that gap with **read-only OCAPI Data API introspection** of system objects, custom object definitions, and site preferences.\n\n**v1 is read-only and developer-sandbox-only** \u2014 no writes, and non-sandbox instances are rejected. Credentials stay local (in `dw.json` or `SFCC_*` env vars) and are never sent to Bridge.\n\n<details>\n<summary><strong>Setup</strong></summary>\n\nThe two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always available. The seven read tools must be enabled with a profile (step 3).\n\n**Prerequisites:** a running SFCC **sandbox** and its hostname, plus an Account Manager API client (`client-id` + `client-secret`).\n\n**1. Set the repo `version` config field** to your SFCC project type \u2014 one of `sfra | pwakit | sitegenesis | storefrontnext | hybrid`. The call-time gate reads this; a non-SFCC value blocks every SFCC tool except `sfcc_setup_status`. Set it via your normal config path, the `config_field` MCP tool (operation `update`, field `version`), or the `/teach-bridge` skill.\n\n**2. Provide SFCC credentials.** Create a `dw.json` in your project root:\n\n```json\n{\n "hostname": "zzzz-001.sandbox.us01.dx.commercecloud.salesforce.com",\n "client-id": "<account-manager-client-id>",\n "client-secret": "<account-manager-client-secret>"\n}\n```\n\nAccepted key spellings: `hostname`/`host`, `client-id`/`clientId`/`client_id`, `client-secret`/`clientSecret`/`client_secret`. Prefer a single config \u2014 a multi-entry `configs[]` array forces you to pass an explicit `instance` on every call. `dw.json` is auto-added to git exclude and must never be committed. Alternatively, export `SFCC_HOSTNAME` / `SFCC_CLIENT_ID` / `SFCC_CLIENT_SECRET` in the MCP server environment.\n\n**3. Enable the read tools.** Add `sfcc` to `BRIDGE_MCP_PROFILE` in the MCP server `env` block (it is comma-separated; `full` also works), then **restart the MCP client**:\n\n```json\n"env": { "BRIDGE_MCP_PROFILE": "sfcc" }\n```\n\nWithout this, only the diagnostic tools are registered.\n\n**4. Verify and grant OCAPI access.** Ask your agent to run `sfcc_setup_status` (expect all checks \u2713), then `check_permissions`. If it reports HTTP 401/403, it prints the exact OCAPI Settings JSON to paste in Business Manager (**Administration \u2192 Site Development \u2192 Open Commerce API Settings \u2192 Data API** tab); save it there and re-run. Restart the MCP client after any credential or env change \u2014 a running session does not pick them up.\n\n</details>\n\n### Tools\n\nAll SFCC tools are read-only and target a developer sandbox. Oversized responses are automatically saved to `BAPI_DOCS_DIR/sfcc/` and previewed inline.\n\n**Diagnostics** (always available, no profile needed)\n- `sfcc_setup_status` \u2014 report on every prerequisite: Bridge API key, repo name, `version` config, `dw.json` presence/uniqueness, and AM token acquisition.\n- `check_permissions` \u2014 probe OCAPI access via `GET /system_object_definitions`; on 401/403, print the exact OCAPI Settings JSON to paste in Business Manager (split read-only vs. write/import grants).\n\n**System object model** (needs the `sfcc` profile)\n- `system_object_list` \u2014 list system object types (Product, Order, Customer, \u2026).\n- `system_object_get` \u2014 fetch one type\'s definition, optionally with its full attribute definitions (`expand_attribute_definitions`).\n- `system_object_attribute_search` \u2014 search a type\'s attribute definitions; prefer this over a full dump when hunting a specific `c_` custom attribute.\n\n**Custom object definitions** (needs the `sfcc` profile)\n- `custom_object_definition_list` \u2014 list custom object type definitions.\n- `custom_object_definition_get` \u2014 fetch an existing custom type with its key definition and attribute definitions/groups. Read-only \u2014 creating a custom object *type* isn\'t possible via OCAPI; that\'s a future v2 metadata-import capability.\n\n**Site preferences** (needs the `sfcc` profile; sandbox only)\n- `site_preference_get` \u2014 read a preference group\'s effective preferences.\n- `site_preference_search` \u2014 search/filter preferences within a group.\n\n## CLI Subcommands\n\nBeyond `--init` / `--upgrade`, the package ships operational subcommands of the **single `bridge-api-mcp-server` bin** (not separate binaries) \u2014 so they travel with the package to every consumer. See [Usage Documentation \u2192 Start Tickets](#tier-1--regularly-useful) for *when* to use `start-tickets`; this section is the full CLI reference.\n\n### `start-tickets`\n\nSpawns one Worktrunk worktree + selected-agent session per Jira ticket and backs the `/start-tickets` slash command. The agent defaults to **Claude Code** (`claude`) and is configurable via `--agent`.\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--agent claude\\|cursor-agent` | `claude` | Agent command to launch in each worktree |\n| `--terminal terminal\\|iterm` | auto-detect via `$TERM_PROGRAM` | Override the macOS terminal app (honored on macOS only) |\n| `--dry-run` | off | Print intended actions; create no worktrees, open no tabs (any OS) |\n| `--branch KEY=BRANCH` | `feature/<KEY>` | Use a custom branch for that ticket (repeatable) |\n| `--base-branch <BRANCH>` | `main` | Cut new worktrees from `<BRANCH>` and refresh `origin/<BRANCH>` instead of `main` |\n| `--no-refresh-main` | off (the configured base branch is refreshed) | Skip refresh of the configured base branch (default `main`). Historical flag name preserved for backward compatibility \u2014 despite the name, it now skips refresh of whatever `--base-branch` resolves to. |\n| `--max-parallel N` | `3` | Max worktrees created concurrently |\n| `--conductor` | off | Opt into the Conductor system (per-worker `BAPI_CONDUCTOR_*` env + Claude hook injection, a supervisor peer tab, and the `check_messages` message-relay prompt). **Default off** \u2014 a plain run spawns `cd <worktree> && <agent> \'/implement-ticket <KEY>\'`. |\n| `-h`, `--help` | \u2014 | Show usage |\n\nEach `KEY` must match `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). The CLI creates/switches each worktree up front (throttled by `--max-parallel`), then opens one tab/session per successful worktree running the selected agent\'s `\'/implement-ticket <KEY>\'` \u2014 `claude \'/implement-ticket <KEY>\'` by default, or `cursor-agent \'/implement-ticket <KEY>\'` with `--agent cursor-agent`. The `/implement-ticket <KEY>` prompt is unchanged for both agents. To launch Cursor Agent instead of Claude Code:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248\n```\n\n**Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket\'s `difficulty` (1-2 \u2192 cheap, 3-5 \u2192 basic, 6+ \u2192 premium) and injects it as a `--model` flag at the spawn boundary. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand); this CLI alone maps a tier to the agent-specific alias (`claude`: `haiku`/`sonnet`/`opus`; `cursor-agent`: version-suffixed strings validated against `cursor-agent --list-models`). It is gated per repo by `difficulty_model_routing_enabled` (default **ON**) with an optional `difficulty_model_tier_overrides` JSON map (tier \u2192 alias). Routing is **fail-open**: missing credentials, an evaluation failure/timeout, a backend `fallback`, an invalid/unavailable alias, an unadvertised Cursor model, or an agent without `--model` support all omit `--model` (the agent uses its default) and surface a per-ticket warning rather than failing the spawn. `--dry-run` does **not** fetch tiers or inject `--model`.\n\n**Conductor observability (opt-in via `--conductor`, BAPI-394).** Conductor is **off by default** \u2014 without `--conductor` no `BAPI_CONDUCTOR_*` env, supervisor tab, or message-relay prompt is produced. With `--conductor`, a run mints a single conductor `run_id` and emits one canonical `run.started` event into the local conductor ledger (`~/.config/bridge/events.db`), attributing each worker by `worker_id`, ticket key, and worktree path, and opens a supervisor peer tab. When the selected agent is **Claude Code**, the CLI also injects a conductor lifecycle hook into each created worktree\'s `.claude/settings.local.json` (preserving any existing hooks) so the spawned session streams local `run.started` / `run.stopped` / `agent.notification` (and, with `BAPI_CONDUCTOR_ENABLE_PRE_TOOL_USE=1`, `tool.intent`) events. Per-worker conductor identity is passed only via secret-free environment scoped to that one terminal/tab/session \u2014 no credentials are ever placed in the env, hook command, or run metadata. Override the gate/supervisor labels with `BAPI_CONDUCTOR_GATE_NAME` / `BAPI_CONDUCTOR_SUPERVISOR_MODE`. Inspect the stream with `conductor doctor`. Observability is best-effort: a conductor failure never blocks or aborts a spawn, and `--dry-run` performs no conductor side effects. (Epic-tick dispatch always enables conductor internally, independent of this flag.)\n\n**Cross-platform spawning.** The CLI routes spawning per platform; `--dry-run` previews the platform-correct command form on any OS. An unsupported `process.platform` (not `darwin`/`win32`/`linux`) fails fast with a clear "unsupported platform" message.\n\n- **macOS** \u2014 opens a Terminal.app or iTerm tab via `osascript`.\n- **Windows** \u2014 creates worktrees with **`git-wt`** (Worktrunk\'s winget alias) and opens a tab via **Windows Terminal (`wt.exe new-tab`)**, falling back to **`Start-Process powershell.exe`** when Windows Terminal is absent. Requires **Git for Windows / Git Bash** (Worktrunk runs its `pre-start` / `post-start` hooks via Git Bash). The Worktrunk binary (`git-wt`) and the tab launcher (`wt.exe`) are resolved independently and never conflated.\n- **Linux** \u2014 creates one detached **tmux** session per ticket (pane kept open after the agent exits); attach with `tmux attach -t <session>`. A missing `tmux` produces a clear, actionable error.\n\nPer-OS prerequisites: macOS `wt`, `git`, `osascript`; Windows `git-wt`, Git for Windows / Git Bash, Windows Terminal or PowerShell; Linux `wt`, `git`, `tmux`. Set `BAPI_WORKTRUNK_BIN` to override the Worktrunk executable name/path for nonstandard installs (`doctor` honors it too). The read-only `doctor` subcommand (below) additionally surfaces a missing `uv` \u2014 Worktrunk\'s `pre-start` hook runs `uv`, but live preflight does not check it \u2014 and the selected agent\'s command; run `doctor --agent cursor-agent` to also check `cursor-agent` (it prints `cursor-agent login` as an informational auth reminder).\n\n### `doctor`\n\nThe package also ships a strictly **read-only** `doctor` subcommand that diagnoses the `start-tickets` prerequisites for the current OS without changing anything:\n\n```\nnpx -y @bridge_gpt/mcp-server doctor [--agent <name>]\n```\n\nIt is **read-only**: it never installs anything, modifies your system, adds an npm `postinstall`, spawns a terminal, or starts the MCP server, and there is no `--fix`. For each prerequisite it prints found/missing and, when missing, the exact per-OS install command **as a manual instruction you run yourself**. The checked set is the `start-tickets` preflight prerequisites **plus `uv`** **plus the selected agent\'s command** (`claude` by default, or `cursor-agent` with `--agent cursor-agent`). The Worktrunk binary is probed via the resolved name (honoring `BAPI_WORKTRUNK_BIN`), not a hard-coded one. **Exit code:** `0` when all required prerequisites are present, non-zero when any is missing or the platform is unsupported. A failing `start-tickets` preflight now hints you to run `doctor` for an actionable diagnostics report.\n\n### `conductor install-git-hooks` (BAPI-395)\n\nInstalls local git hooks that opportunistically emit conductor git/PR/CI events into the local ledger:\n\n```\nconductor install-git-hooks [--json]\n```\n\nThe installed hooks are **local, unversioned, opportunistic, and bypassable**: they live in the worktree\'s git hooks directory (resolved via `git rev-parse --git-common-dir`), insert only a clearly-delimited managed block (preserving any existing user hook content), launch the producer **detached in the background** so a commit or ref update is never blocked, and tolerate every failure (`|| true`). A directory that is not a git worktree, or an existing hook that looks binary/unsafe, is left untouched and reported as a **degraded optional capability** \u2014 never a fatal error. The hooks installed are `post-commit` (emits `git.commit_created`) and `reference-transaction` (emits `worktree.changed` for committed ref updates).\n\nMissing hooks do **not** prevent PR/CI gate evaluation \u2014 `conductor doctor` reads hook presence and managed-snippet status **read-only** (a new `git hooks` section / `git_hooks` JSON object alongside the ledger report), and the `wait_for_done_gate` MCP tool drives CI polling and gate evaluation regardless of whether hooks are installed.\n\n#### `conductor_done_gate` config\n\nThe per-repo `conductor_done_gate` config field (read through the existing config-field route) defines the v1 done gate. It supports exactly one condition, `required_ci_checks_green`:\n\n```json\n{\n "enabled": true,\n "conditions": [\n { "type": "required_ci_checks_green", "required_checks": ["build", "test"] }\n ]\n}\n```\n\n`gate.met` is emitted (exactly once per `repo + pr_number + head_sha + effective config`) only when every listed required check is present, complete, and green for the bound PR head SHA. The gate **fails closed**: an unset, disabled (`enabled` not strictly `true`), malformed, empty, or unsupported config emits no `gate.met`.\n\n#### `conductor_auto_merge_enabled` config (C6 conditional auto-merge)\n\nWhen a worker\'s PR meets the done gate (`gate.met`), the supervisor can autonomously merge it \u2014 but **only** when the repo has explicitly opted in. The per-repo `conductor_auto_merge_enabled` config field (read through the same config-field route as `conductor_done_gate`) is the opt-in switch:\n\n```json\n{ "enabled": true }\n```\n\nA bare JSON boolean (`true`) is also accepted. **Auto-merge is disabled by default.** Behavior:\n\n- **Disabled / unset / malformed \u2192 dry-run.** Anything other than `true` or `{"enabled": true}` \u2014 including unset, `false`, `{"enabled": false}`, or any malformed value \u2014 fails **closed**: the supervisor records a `merge.dry_run` event and **no PR is ever merged**.\n- **Enabled \u2192 autonomous merge** when the gate is met and the deterministic guards pass.\n- **Kill-switch.** Set `conductor_auto_merge_enabled` to `false` or remove the field to immediately stop autonomous merges. The protected merge endpoint **independently re-enforces** the flag, so even a conductor that calls it cannot merge while the flag is off.\n\nMerge authority is **deterministic code, never an LLM**. The deterministic guards, all bound to **PR number + expected head SHA (never a branch name)**:\n\n- the per-repo enablement flag (off \u2192 dry-run),\n- the PR is still open,\n- the merge is bound to the PR number plus the expected head SHA \u2014 **head-SHA drift between gate evaluation and merge aborts the merge**,\n- required CI checks are **revalidated green immediately before merge**.\n\nIdempotency is crash-safe and race-safe: a TTL lease keyed by the deterministic action key `merge:{repo}:{pr}:{head_sha}:{gate}` is claimed before acting, and an existing `merge.succeeded` for that key is terminal \u2014 the supervisor never double-merges across a crash/restart or two racing instances. The conductor never holds VCS write credentials: it calls the protected Bridge API endpoint `POST /vcs/pull-requests/{pr_number}/merge`, which owns the privileged merge, and records the returned `merge.dry_run` / `merge.attempted` / `merge.succeeded` / `merge.failed` / `merge.pending_approval` events into the local ledger. `merge.failed` is **retryable** (a drifted head SHA produces a new action key); `merge.pending_approval` is **nonterminal** \u2014 the worker remains active until a human redeems the approval token and the server returns `merge.succeeded`. **`merge.succeeded` is the only terminal merge event.** The local SQLite conductor store uses schema version 5 (BAPI-413) to accommodate the `merge.pending_approval` type in the `events.type` CHECK constraint.\n\n## Custom Pipelines\n\nYou can create your own pipelines by adding JSON files to `.bridge/pipelines/`. Running `--init` scaffolds this directory with a `README.md` and an example pipeline to get you started.\n\nThe easiest way to write a custom pipeline is to describe what you want to automate to your AI coding agent and have it draft the JSON for you. The schema is straightforward, and agents like Claude Code understand it well \u2014 just describe the steps you want, and the agent will produce a working pipeline file.\n\n**What you can build:**\n\n- Any sequence of Bridge MCP tool calls and free-form agent tasks\n- Parameterized workflows using variables (e.g., `{ticket_key}`)\n- Approval gates that pause for user confirmation before sensitive steps\n- Per-step error handling \u2014 halt immediately or warn and continue\n\n**Ideas for custom pipelines:**\n\n- A standup pipeline that fetches your open tickets and summarizes their status\n- A ticket triage pipeline that runs critiques on a batch of new tickets\n- A pre-merge checklist that runs tests, checks linting, and posts a summary comment\n\n**Step types:**\n\n| Type | What it does |\n|---|---|\n| `mcp_call` | Calls an MCP tool with the given params |\n| `agent_task` | Gives the AI a free-form instruction (inline or from a file in `.bridge/instructions/`) |\n\nVariables are declared in the `variables` array and referenced as `{variable_name}` in params and instructions. Each step supports `on_error: "halt"` (default) or `"warn_and_continue"`, and `requires_approval: true` to pause before execution.\n\n**System variables:**\n\nTwo variables are automatically available in every pipeline without declaring them:\n\n| Variable | What it does |\n|---|---|\n| `{provider}` | Routes AI generation to a specific LLM provider (`openai`, `anthropic`, or `gemini`). Pass it through to any `request_*` tool param to control which provider handles that step. Omit it (or leave it empty) to use the project default. |\n| `{second_opinion}` | Runs AI generation through a different provider than the default, acting as a cross-check. Set to `"auto"` to let Bridge pick the second provider automatically. When set, it takes precedence over `{provider}`. Use this when you want two independent AI perspectives on the same task \u2014 for example, running clarifying questions and a critique twice (once with each provider) produces better results than a single pass. |\n\nSee `.bridge/pipelines/README.md` for the full schema reference.\n\nIf a custom pipeline has the same key as a built-in pipeline, the custom version takes precedence (a warning is logged at startup).\n\n## Smoke testing\n\nThe package ships a canonical, **opt-in** in-host smoke-test runbook at\n`smoke-test/SMOKE-TEST.md`. An AI agent running inside your host (Claude Code,\nCursor, Codex, Windsurf, or VS Code/Copilot) executes it to verify that the MCP\nserver actually works end-to-end *inside that host* \u2014 it calls the real tools and\nrecords a PASS/FAIL verdict for each one in a markdown report.\n\n- `smoke-test/SMOKE-TEST.md` **ships with the npm package** and is the\n **canonical** source of truth for the smoke test.\n- The smoke test **adds no MCP tool** and **does not change the registered\n tool surface** (the server still registers its existing 62 tools).\n- It is **opt-in**: default `--init` **does not scaffold `/smoke-test-mcp`**, so\n consumer command palettes are not polluted.\n\n### Running it\n\nYou have two options:\n\n1. **Copy the opt-in command manually.** Copy the packaged command stub into your\n host\'s command directory, then invoke `/smoke-test-mcp`:\n\n ```bash\n # Claude Code\n cp node_modules/@bridge_gpt/mcp-server/smoke-test/smoke-test-mcp.md .claude/commands/smoke-test-mcp.md\n # Cursor\n cp node_modules/@bridge_gpt/mcp-server/smoke-test/smoke-test-mcp.md .cursor/commands/smoke-test-mcp.md\n ```\n\n2. **Open the runbook directly.** Alternatively, open\n `smoke-test/SMOKE-TEST.md` and ask the host agent to execute it.\n\nReports are written to `<BAPI_DOCS_DIR>/smoke-test/REPORT-<host>-<timestamp>.md`.\n\n## Environment Variables\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `BAPI_BASE_URL` | Yes | `https://bridgegpt-api.com` | Bridge API base URL |\n| `BAPI_REPO_NAME` | Yes | _(none)_ | Jira project/repository identifier configured in Bridge API |\n| `BAPI_API_KEY` | Yes | _(none)_ | API key obtained from the Bridge API setup UI |\n| `BAPI_PROJECT_ROOT` | No | _(auto-set by --init)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution |\n| `BAPI_DOCS_DIR` | No | `docs/tmp` | Local directory for saving plans, critiques, and research reports |\n| `BAPI_PIPELINES_DIR` | No | `.bridge/pipelines` | Directory for user-defined custom pipeline JSON files |\n| `BAPI_WORKTRUNK_BIN` | No | `wt` (`git-wt` on Windows) | Override the Worktrunk executable name/path used by `start-tickets` for nonstandard installs |\n| `BAPI_TMUX_SESSION` | No | `bridge-start-tickets` | Override the tmux session-name prefix used by `start-tickets` on Linux |\n| `BAPI_MCP_UPGRADE_ADVICE_ENABLED` | No | _(enabled)_ | MCP-local opt-out for proactively surfacing upgrade advice in pipeline recipe preambles. Set to `false`/`0`/`no`/`off`/`disabled` to suppress. Disabling it does **not** change the `/jira/ping` response or server-side upgrade computation \u2014 it only gates the recipe-preamble convention |\n| `BRIDGE_MCP_PROFILE` | No | `core` | Startup-time tool registration profile \u2014 a **comma-separated** list of groups controlling which tool groups are registered when the server starts. `core` is always implicitly included. Valid groups: `core` (default \u2014 normal coding tools only), `conductor` (+ 8 conductor/event/supervisor tools), `pipeline-authoring` (+ 5 pipeline run/admin tools \u2014 `get_pipeline_recipe` is NOT gated; it stays in `core` because the recipe-driven slash commands depend on it), `sfcc` (+ the 7 heavy SFCC read tools \u2014 see [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools); the `sfcc_setup_status`/`check_permissions` diagnostics are always registered regardless), and `full` (shortcut that expands to every group). Example: `sfcc,conductor`. Unknown, blank, or malformed tokens are dropped (falling back to `core`). Dynamic mid-session switching via `tools/list_changed` is unsupported \u2014 groups are resolved once at process startup. Conductor/epic sessions launched via `start-tickets --conductor` automatically inject `BRIDGE_MCP_PROFILE=conductor` at the spawn boundary; a normal `start-tickets` run stays on `core`. |\n\n## Worktree credentials and the `mcp-invoke` shim\n\nWhen `start-tickets` creates a git worktree, it provisions a Bridge API MCP\nregistration into that worktree so Claude Code (`.mcp.json`) and Cursor\n(`.cursor/mcp.json`) can reach the server immediately. These registrations are\n**secret-free**: they contain no `env` block and no API key. Instead they point\nat an internal subcommand of the published single CLI bin, `mcp-invoke`:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@<VERSION> mcp-invoke --target bapi --project-root <ABS_WORKTREE_PATH>\n```\n\n`mcp-invoke` is not a separate binary \u2014 it is a positional subcommand of\n`bridge-api-mcp-server`. It resolves the repo identity from the absolute\n`--project-root` (the committed `.bridge/config` manifest, falling back to the\ngit common dir), resolves credentials from the home-directory credential store,\nand then spawns the real MCP server with that environment.\n\n### Credential store\n\nCredentials live outside the repository, keyed by `bapi:<repo_name>`:\n\n```json\n{\n "bapi:<repo_name>": {\n "BAPI_API_KEY": "..."\n }\n}\n```\n\nResolution order:\n\n1. `BAPI_API_KEY` in the parent environment (overrides the file entirely).\n2. `$XDG_CONFIG_HOME/bridge/credentials.json`, else `~/.config/bridge/credentials.json`.\n3. `~/.bridge/credentials.json` (only when the primary path is absent).\n\nOn POSIX systems, lock the file down so only you can read it:\n\n```bash\nchmod 600 ~/.config/bridge/credentials.json\n```\n\n`mcp-invoke` warns (but continues) if the file is group/world-readable, and it\nnever creates or initializes the credential file for you.\n\n### Populating the credential store\n\nThe same store also backs the shell-spawned `start-tickets` CLI (its\ndifficulty\u2192model routing runs in a Bash process that cannot see an `env` block in\n`.mcp.json` / `.cursor/mcp.json`), so the store must hold the key for routing to\nwork. Two supported paths write it for you:\n\n- **Install-time upsert.** `/install-bridge`\'s final stage persists the validated\n routing credential into `~/.config/bridge/credentials.json` (target\n `bapi:<repo>`) via the `persist_routing_credential` MCP tool \u2014 the tool resolves\n the key inside the MCP process and writes the store, so no secret crosses the\n wire.\n- **One-shot migration.** If a key currently lives only in `.mcp.json` /\n `.cursor/mcp.json`, migrate it into the user-scoped store with the consent-gated\n command (a compatibility aid, not a live fallback):\n\n ```bash\n npx -y @bridge_gpt/mcp-server credentials migrate-agent-config [--write-credentials]\n ```\n\n Run it without `--write-credentials` to preview; add the flag to write the store.\n\n## Reference\n\nThe full surface, for when you need the complete enumeration. Day-to-day, use [Usage Documentation](#usage-documentation) instead \u2014 you don\'t call MCP tools directly; you ask your AI assistant to perform a task, or compose tools into a pipeline.\n\n### MCP tools\n\nThe server registers **58 tools**. Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).\n\n- **Connectivity & identity** \u2014 `ping`, `get_my_role`, `get_docs_dir`\n- **Jira tickets** \u2014 `get_tickets`, `get_ticket`, `create_ticket`, `update_ticket_description`, `add_comment`, `get_comments`\n- **Attachments** \u2014 `attachment` (operations: `upload`, `download`, `list`)\n- **AI generation (request/get)** \u2014 `request_plan_generation`/`get_plan`, `request_architecture`/`get_architecture`, `create_doc`/`get_doc` (design docs by `doc_type`: tdd/fsd/prd), `request_prd`/`get_prd`, `request_clarifying_questions`/`get_clarifying_questions`, `request_ticket_critique`/`get_ticket_critique`, `request_ticket_review`, `request_reimplement_context`/`get_reimplement_context`, `request_brainstorm`/`get_brainstorm`, `request_deep_research`/`get_deep_research`\n- **Other AI** \u2014 `second_opinion`, `generate_image`, `generate_decision_page`\n- **Ticket lifecycle** \u2014 `track_ticket`, `update_ticket_state`, `get_ticket_state`\n- **Jira status** \u2014 `get_jira_transitions`, `update_jira_status`, `resolve_target_status`\n- **Repository & CI** \u2014 `parse_repository`, `get_parse_status`, `regenerate_directory_map`, `create_pull_request`, `resolve_ci_checks`, `poll_ci_checks`\n- **Pipelines & automation** \u2014 `list_pipelines`, `get_pipeline_recipe`, `run_pipeline`, `resume_pipeline`, `list_pipeline_runs`, `delete_pipeline_run`, `run_full_automation`, `resume_full_automation`\n- **Config** \u2014 `get_project_standards`, `config_field` (operations: `get`, `update`, `list`)\n\n### Bundled pipelines\n\nPipelines are declarative, multi-step workflows your AI agent executes step-by-step \u2014 each a JSON recipe chaining MCP tool calls and free-form agent tasks, with variable substitution, per-step error handling, and optional approval gates. You can also write your own (see [Custom Pipelines](#custom-pipelines)).\n\n| Pipeline | Description | Invoke with |\n|---|---|---|\n| `implement-ticket` | Generate a plan, execute the implementation, commit, open a PR, and monitor CI | `/implement-ticket PROJ-123` |\n| `review-ticket` | Full ticket quality review: initial clarifying questions + critique, automatic second-opinion pass (default), then evaluation and decision capture. Pass `--rounds=1` to skip the second-opinion step (`--rounds=2` is the default). | `/review-ticket PROJ-123` |\n| `idea-to-ticket` | Turn an idea into a Jira Task/Spike (or Epic + children) with research, dedup, and critique | `/idea-to-ticket "<idea>"` |\n| `plan-epic` | Decompose an epic into sub-tasks with a structured exploration doc for each | `/plan-epic "<epic>"` |\n| `full-automation` | Chain: idea \u2192 ticket(s) \u2192 review each \u2192 spawn worktrees to implement | `/full-automation "<idea>"` |\n| `pr-ticket` | Commit changes and open a pull request | `/create-pr PROJ-123` |\n| `check-ci-ticket` | Commit, open a PR, then monitor CI checks until they pass or fail | `/check-ci PROJ-123` |\n| `learn-repository` | Analyze codebase architecture, testing, review, and documentation standards, then upload to Bridge | `/learn-repository` |\n\n### Pipeline response envelope\n\n`run_pipeline`, `resume_pipeline`, and `list_pipeline_runs` share a unified envelope keyed on `status`:\n\n- `completed` \u2014 terminal success; `results` holds per-step output.\n- `needs_agent_task` \u2014 the orchestrator paused. Read `instruction`, perform the task, then call `resume_pipeline` with `pipeline_run_id` and a string `agent_result`.\n- `failed` \u2014 terminal failure. `error_code` is one of `VALIDATION`, `NOT_FOUND`, `EXPIRED`, `REPO_MISMATCH`, `TOOL_ERROR`.\n\nPaused runs auto-expire after an idle TTL (default 24 hours; override with `ttl_seconds`). The TTL is reset on every state transition. List output is metadata-only \u2014 it never includes resolved recipes, params, instructions, results, or agent outputs.\n';
13105
+ var README = '# @bridge_gpt/mcp-server\n\nThe Bridge MCP is an end-to-end accelerator for shipping code within SFCC, powered by [Bridge API](https://bridgegpt-api.com). Works with Claude Code, Github Copilot, Cursor, Windsurf, and OpenAI Codex.\n\n## Contents\n\n- [Getting Started](#getting-started)\n- [Usage Documentation](#usage-documentation)\n - [Tier 1 \u2014 Regularly useful](#tier-1--regularly-useful)\n - [Tier 2 \u2014 Occasionally useful](#tier-2--occasionally-useful)\n - [Tier 3 \u2014 Now and then](#tier-3--now-and-then)\n - [Operational commands](#operational-commands)\n - [Extra Capabilities](#extra-capabilities)\n- [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools)\n- [CLI Subcommands](#cli-subcommands)\n- [Custom Pipelines](#custom-pipelines)\n- [Environment Variables](#environment-variables)\n- [Worktree credentials and the `mcp-invoke` shim](#worktree-credentials-and-the-mcp-invoke-shim)\n- [Reference](#reference)\n\nFor advanced epic/multi-agent orchestration, see [CONDUCTOR.md](./CONDUCTOR.md).\n\n## Getting Started\n\n### Quick start\n\nFrom your **project root**, install and connect in one command:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@latest install-bridge\n```\n\n`install-bridge` scaffolds the project, writes your editor\'s MCP config with real\nvalues, verifies connectivity, persists your API key to the user-scoped credential\nstore, and opens a fresh agent session to finish setup (`/install-bridge` then\n`/learn-repository`). The only inputs are an **API key** (generate one on the Bridge\nAPI web UI **Security** page) and a **repo name** \u2014 everything else is derived. Add\n`--dry-run` to preview every step without writing, pinging, or spawning anything.\n\nTo upgrade later, run:\n\n```bash\nnpx -y @bridge_gpt/mcp-server upgrade\n```\n\n`upgrade` re-execs from `@latest`, refreshes all scaffolded artifacts (slash\ncommands, agents, pipelines), rewrites the version pin, and reconnects \u2014 also\navailable as the `/upgrade-bridge` slash command. (The legacy `--upgrade` flag still\nworks as a fallback.)\n\n<details>\n<summary><strong>Installation Instructions</strong></summary>\n\n#### What `install-bridge` does\n\n`install-bridge` collapses the whole setup into a single command. It:\n\n1. **Scaffolds** the project (the same artifacts `--init` writes: slash commands,\n agents, `.bridge/pipelines/`, and secret-free MCP config placeholders).\n2. **Writes the per-host MCP config** (`.mcp.json` / `.cursor/mcp.json` /\n `.vscode/mcp.json`) with your real `BAPI_REPO_NAME` / `BAPI_API_KEY` /\n `BAPI_BASE_URL` / `BAPI_DOCS_DIR`, preserving any unrelated servers. The\n launcher it writes is pinned to the exact installed version so `npx` never\n silently reuses a stale local copy. (Windsurf and Codex are global configs it\n can\'t safely write \u2014 it prints copy-paste instructions for those.)\n3. **Verifies connectivity** against the Bridge API before persisting anything.\n4. **Persists your key** to the user-scoped credential store\n (`~/.config/bridge/credentials.json`, target `bapi:<repo>`) so shell-spawned\n tooling (e.g. `start-tickets`) can resolve it.\n5. **Opens a fresh agent session** that runs `/install-bridge` (to derive the\n remaining config fields from your codebase) and then `/learn-repository`.\n\nThe only inputs are an **API key** and a **repo name** (everything else is\nderived). Resolution order:\n\n- **API key:** `--api-key <key>` \u2192 `BAPI_API_KEY` env \u2192 an interactive (no-echo)\n prompt. Generate one first on the Bridge API web UI **Security** page (see\n [Generate an API Key](#2-generate-an-api-key)); the command consumes a key, it\n never mints one. The key is **never printed or logged**.\n- **Repo name:** `--repo <name>` \u2192 `BAPI_REPO_NAME` env \u2192 an inferred default you\n confirm interactively. It MUST match the server-side repository registration.\n\nUseful flags:\n\n- `--dry-run` \u2014 preview every step (scaffold targets, config files and keys with\n the key value **redacted**, the ping target, the credential store target, and\n the exact agent spawn command) without writing, pinging, or spawning anything.\n- `--force` \u2014 overwrite an existing real `BAPI_API_KEY` in a host config without\n prompting (re-running is otherwise non-destructive).\n- `--agent claude|cursor-agent` \u2014 which agent to launch for the agentic remainder\n (default `claude`).\n\nThat\'s it \u2014 once `install-bridge` finishes you\'re connected. If you prefer to do\nit by hand (or just want to understand each step), the manual flow below does the\nsame thing.\n\n#### Manual Setup (Alternative)\n\n##### 1. Install the Package\n\nFrom your **project root**, install the MCP server and scaffold slash commands:\n\n```bash\nnpm i @bridge_gpt/mcp-server\nnpx -y @bridge_gpt/mcp-server --init\n```\n\n`--init` must be run from the directory containing your `package.json`. It:\n\n- Creates slash commands in `.claude/commands/` and `.cursor/commands/`\n- Detects existing MCP config files and sets `BAPI_PROJECT_ROOT` so local file output resolves correctly\n- Scaffolds `.bridge/pipelines/` for custom pipeline authoring\n\nRe-run `--init` after upgrading the package to get updated commands.\n\n##### 2. Generate an API Key\n\n1. Log in to [Bridge API](https://bridgegpt-api.com) and navigate to your project\'s **Security** page\n2. Click **Create New Key**\n3. Enter your email, an optional label (e.g., "MCP Server"), and select the **Admin** role\n4. Click **Create Key**\n5. **Copy the key immediately** \u2014 it will not be shown again\n\n##### 3. Configure the MCP Server\n\nAdd the following to your editor\'s MCP configuration file, pasting in the API key from step 2:\n\n<details>\n<summary><strong>Claude Code (.mcp.json)</strong></summary>\n\nThe `--init` command (step 1) detects Claude Code and creates a `.mcp.json` at your project root with placeholder values. Open it and replace `your-repo` and `your-api-key` with your actual values from step 2:\n\n```json\n{\n "mcpServers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n</details>\n\n<details>\n<summary><strong>VS Code / Copilot (.vscode/mcp.json)</strong></summary>\n\n```json\n{\n "servers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n</details>\n\n<details>\n<summary><strong>Cursor (.cursor/mcp.json)</strong></summary>\n\n```json\n{\n "mcpServers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n\n> If project-local config is not supported in your Cursor version, use `~/.cursor/config/mcp.json` instead.\n</details>\n\n<details>\n<summary><strong>Windsurf (~/.codeium/windsurf/mcp_config.json)</strong></summary>\n\nWindsurf only supports global MCP configuration.\n\n```json\n{\n "mcpServers": {\n "bridge-api": {\n "command": "npx",\n "args": ["-y", "@bridge_gpt/mcp-server"],\n "env": {\n "BAPI_BASE_URL": "https://bridgegpt-api.com",\n "BAPI_REPO_NAME": "your-repo",\n "BAPI_API_KEY": "your-api-key",\n "BAPI_DOCS_DIR": "docs/tmp"\n }\n }\n }\n}\n```\n</details>\n\n<details>\n<summary><strong>OpenAI Codex (~/.codex/config.toml)</strong></summary>\n\n```toml\n[mcp_servers.bridge-api]\ncommand = "npx"\nargs = ["-y", "@bridge_gpt/mcp-server"]\n\n[mcp_servers.bridge-api.env]\nBAPI_BASE_URL = "https://bridgegpt-api.com"\nBAPI_REPO_NAME = "your-repo"\nBAPI_API_KEY = "your-api-key"\nBAPI_DOCS_DIR = "docs/tmp"\n```\n\n> Codex users: set `BAPI_PROJECT_ROOT` manually in your config (see Environment Variables below).\n</details>\n\nAfter saving the config, restart your editor or reload the MCP server connection. Verify connectivity by asking your AI assistant to call the `ping` tool.\n\n##### 4. First-Time Setup: Teach Bridge Your Codebase\n\nIf you\'re the first person to install Bridge API on your project, run the `/learn-repository` slash command after completing setup. This analyzes your codebase\'s architecture, testing patterns, code review standards, and documentation conventions, then uploads the findings to Bridge API. This gives Bridge the context it needs to generate implementation plans, ticket critiques, and code reviews that are consistent with your project\'s actual architecture and conventions.\n\nYou only need to do this once per project \u2014 the learned standards persist for all team members.\n\n##### Upgrading (details)\n\nThe one-command `npx -y @bridge_gpt/mcp-server upgrade` (shown above) is the\nrecommended path. The legacy flag `npx -y @bridge_gpt/mcp-server --upgrade` still\nworks: it runs `npm i @bridge_gpt/mcp-server@latest`, prints a before/after version\nsummary, then re-runs the full `--init` scaffolding flow to update your slash\ncommands, agents, and pipeline definitions.\n\nThe MCP server also checks for updates automatically on startup. If a newer version is available, you\'ll see a notice in your editor\'s MCP output logs with the upgrade command to run. This check is cached for 24 hours and never blocks server startup.\n\n</details>\n\n## Usage Documentation\n\nThis is the Bridge API tooling worth knowing about as a software engineer \u2014 the things you\'d ask an agent to do \u2014 grouped by how often you would use them. Each entry covers **what it does**, **when it\'s useful** and **how to use it**. The behind-the-scenes plumbing is summarized at the end under [Extra Capabilities](#extra-capabilities), and a full enumeration lives in [Reference](#reference).\n\nWorking in a Salesforce B2C Commerce codebase? Bridge also ships read-only SFCC platform-introspection tools \u2014 see [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools).\n\nFor invocation, prefer the slash command \u2014 it\'s deterministic. A free-text example is shown only where natural-language phrasing reliably maps to the right automation; high-consequence or easily-misread automations show only the slash command on purpose.\n\n### Tier 1 \u2014 Regularly useful\n\nThese features are useful for most tickets.\n\n**1. Review Ticket**\n- **What it does:** Runs a full quality review of a ticket: clarifying questions + critique (initial round), an automatic alternate-model second opinion, then evaluates findings and produces a decision page to accept/reject them. The second-opinion pass is included by default and can be skipped with `--rounds=1`.\n- **When it\'s useful:** (Refinement) Right after a ticket is drafted, before anyone starts building \u2014 to surface gaps and tighten it.\n- **How to use it:** `/review-ticket BAPI-123` (command only \u2014 "review" as free text is easily mistaken for a freehand agent review).\n- **Flags:** `--auto` auto-accept findings / skip the approval gates \xB7 `--rounds=1` skip the automatic second-opinion review step while preserving downstream evaluation and decision-capture work (cheaper single-pass review). `--rounds=2` (default) runs the full two-round review.\n- **Multi-ticket fan-out:** `/review-tickets BAPI-123 BAPI-456` opens one terminal tab per ticket for parallel review with no worktrees (terminal launcher only \u2014 no `wt`/`git`). All `/review-ticket` flags apply; `--review KEY=auto,rounds=N` sets per-ticket overrides. Packaged CLI: `npx -y @bridge_gpt/mcp-server review-tickets KEY [KEY ...]`.\n\n**2. Start Tickets**\n- **What it does:** Creates one git worktree per ticket and spawns an agent session in each to implement them in parallel.\n- **When it\'s useful:** (Implementation | Automation) When you\'re ready to start building one or more refined tickets concurrently.\n- **How to use it:** `/start-tickets BAPI-248 BAPI-250` (see [CLI Subcommands](#cli-subcommands) for the full flag table and cross-platform behavior).\n- **Flags:** `--auto` skip the approval gates \xB7 `--base-branch <branch>` branch off something other than the default.\n\n**3. Brainstorm**\n- **What it does:** Fans your problem out to two different LLMs and returns their approaches directly. Runs in one of three modes, selected via `mode`: **`technical`** (default \u2014 implementation/architecture approaches), **`design`** (UI/UX and visual direction), or **`discovery`** (stakeholder discovery questions for early/vague tasks, grouped into `Technical Discovery Questions` and `Business / Stakeholder Discovery Questions` and tagged `[HUMAN]`/`[CODE]`/`[TICKET]`). Discovery needs no extra configuration. The legacy boolean `design=true` still works and maps to `mode: "design"`.\n- **When it\'s useful:** (Architecture | Refinement) Early, when you want a spread of approaches \u2014 `technical` for *how to build it*, `design` for *how it should look*, `discovery` for *what we still need to figure out* before a real ticket exists.\n- **How to use it:** ask your agent to brainstorm \u2014 *"Brainstorm approaches for adding rate limiting to the LLM client; fan it out to multiple models."* For a design pass: *"Run a design brainstorm for the evidence-freshness dashboard UI."* For early discovery: *"Run a discovery brainstorm \u2014 `request_brainstorm` with `mode: "discovery"` \u2014 for this vague request so we can collect the questions stakeholders need to answer first."*\n\n**4. Deep Research**\n- **What it does:** Runs multi-source, fact-checked web research on a technical topic and returns a cited report.\n- **When it\'s useful:** (Architecture | Refinement) When a decision hinges on outside knowledge (libraries, best practices, standards) you don\'t already have.\n- **How to use it:** `/bridge-research <question>`\n\n**5. Jira Ticket Writer**\n- **What it does:** An agent that drafts a well-structured Jira ticket from a plain description, applying your project\'s standards.\n- **When it\'s useful:** (Refinement) When you have an idea in your head and want a properly-formatted ticket draft without writing it by hand.\n- **How to use it:** `/write-ticket <description>` (or ask your agent) \u2014 *"Use the jira ticket writer to turn our conversation into a ticket."*\n- **Flags:** `--standards <path>` apply a specific standards file when drafting.\n\n**6. Upload Ticket**\n- **What it does:** Pushes a drafted ticket up to Jira as a real issue (the `create_ticket` capability); handles markdown and child tickets under an Epic.\n- **When it\'s useful:** (Refinement) The final step after drafting \u2014 to get the ticket into Jira so it can be tracked and worked.\n- **How to use it:** Ask your agent to create the ticket, and it should confirm before creating the live Jira issue.\n- **Options:** Describe the issue type (Bug / Story / Task / Epic) and, for a child under an Epic, the parent key.\n\n### Tier 2 \u2014 Occasionally useful\n\nThese features are good to know, but you probably won\'t use them every day.\n\n**1. Plan Ticket**\n- **What it does:** Generates a step-by-step implementation plan for a ticket, with references to real code files, and saves it locally.\n- **When it\'s useful:** (Refinement | Implementation) Once a ticket is solid and you want a concrete build plan before (or instead of) auto-implementing.\n- **How to use it:** `/plan-ticket BAPI-123`\n- **Flags:** `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check the plan with a second provider.\n\n**2. Clarify Ticket**\n- **What it does:** Generates clarifying questions for a ticket (or debugging guidance for bugs) and saves them locally.\n- **When it\'s useful:** (Refinement) When a ticket feels under-specified and you want the open questions made explicit.\n- **How to use it:** `/clarify-ticket BAPI-123` \u2014 *"Generate clarifying questions for BAPI-123"*\n- **Flags:** `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check with a second provider.\n\n**3. Critique Ticket**\n- **What it does:** Critiques a ticket\'s quality against your project standards and lists deviations + improvements.\n- **When it\'s useful:** (Refinement) When you want a quality gate on a ticket before it\'s worked.\n- **How to use it:** `/critique-ticket BAPI-123` \u2014 *"Critique BAPI-123 against our project standards and list what\'s missing or deviating."*\n- **Flags:** `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check with a second provider.\n\n**4. Create Doc**\n- **What it does:** Generates a design document for a ticket \u2014 a TDD (technical design, engineer audience), an FSD (functional spec, for product/design/QA), or a PRD (product requirements: problem, goals, success metrics) \u2014 and saves it locally.\n- **When it\'s useful:** (Architecture | Refinement) When a ticket needs a fuller design write-up before planning or implementation, in the shape that fits your audience.\n- **How to use it:** `/create-doc BAPI-123 --doc-type tdd` (or `fsd` / `prd`)\n- **Flags:** `--doc-type tdd|fsd|prd` which document to generate (required) \xB7 `--provider <name>` choose the model provider \xB7 `--second-opinion <provider>` cross-check with a second provider.\n\n**5. Explore Ticket**\n- **What it does:** Explores the codebase for a task and recommends implementation options or surfaces clarifying questions, with optional research.\n- **When it\'s useful:** (Architecture | Refinement) Before writing a ticket or plan, when you\'re unsure how a change would fit the existing code.\n- **How to use it:** `/explore-ticket <task>` \u2014 *"Explore the codebase for how we\'d add a Mistral LLM provider and recommend 2\u20133 implementation options."*\n\n**6. Second Opinion**\n- **What it does:** Gets an immediate critique of any text from a different model family \u2014 no artifact saved, just the reply.\n- **When it\'s useful:** (Architecture | Refinement | Implementation) Any time you want a quick sanity check on a plan, draft, or decision from a fresh perspective.\n- **How to use it:** ask your agent \u2014 *"Get a second opinion from Gemini on whether the BAPI-123 plan\'s migration step is safe to run against prod."*\n- **Options:** pick the provider (anthropic / openai / gemini) and the tier (cheap / basic / premium).\n\n**7. Generate Image**\n- **What it does:** Generates an image from a text prompt using a provider image model (OpenAI `gpt-image-2` by default, or Google Imagen) and returns the image directly. Spends provider credits on every call.\n- **When it\'s useful:** (Architecture | Refinement) When you want a quick visual \u2014 a UI mockup, diagram, or illustration \u2014 to anchor a design discussion or attach to a ticket.\n- **How to use it:** ask your agent \u2014 *"Generate an image of a dashboard showing SOC2 evidence freshness as a traffic-light grid."*\n- **Options:** `provider` openai (`gpt-image-2`) / gemini (Imagen \u2014 adds an invisible SynthID watermark) \xB7 `quality` low (default, cheapest) / medium / high \xB7 `size` 1024x1024 / 1024x1536 / 1536x1024. The image is always saved to `BAPI_DOCS_DIR/images/` and also returned inline.\n\n**8. Implement Ticket**\n- **What it does:** Full build for one ticket: generate a plan, write the code, commit, open a PR, and monitor CI.\n- **When it\'s useful:** (Implementation) When a ticket is ready and you want it taken from plan to open PR in one go.\n- **How to use it:** `/implement-ticket BAPI-123` (command only \u2014 "implement X" as free text almost always triggers a freehand build instead of the Bridge plan\u2192code\u2192PR\u2192CI pipeline).\n- **Flags:** `--auto` skip the approval gates (e.g. auto-commit/push).\n\n**9. Full Automation**\n- **What it does:** Drives the whole chain end-to-end: idea \u2192 ticket(s) \u2192 review each \u2192 spawn worktrees to implement.\n- **When it\'s useful:** (Automation) When you want to go from a raw idea to in-progress implementation with minimal hands-on steps.\n- **How to use it:** `/full-automation <idea>` (command only \u2014 creates tickets, spawns worktrees, and carries scheduling/`--max-children` flags that free text can\'t).\n- **Flags:** `--require-approval` toggle the approval gates, full automation runs end to end by default.\n\n**10. Idea to Ticket**\n- **What it does:** Turns a one-line idea into a Jira Task/Spike (or an Epic plus child tickets), with research, duplicate detection, and a critique pass built in.\n- **When it\'s useful:** (Refinement | Automation) When you have a rough idea and want a fully-formed, uploaded ticket without the manual draft-and-refine loop.\n- **How to use it:** `/idea-to-ticket <idea>`\n\n### Tier 3 \u2014 Now and then\n\nThese features are useful once in a while, but you probably won\'t need them everyday.\n\n**1. Reimplement Ticket**\n- **What it does:** Pulls in new context/attachments since the last pass and implements small follow-up changes on an already-built ticket.\n- **When it\'s useful:** (Implementation) After review feedback or new screenshots, when you need a targeted second pass rather than a fresh build.\n- **How to use it:** `/reimplement-ticket BAPI-123`\n\n**2. Run Tests**\n- **What it does:** Runs the unit and E2E suites and autonomously triages/fixes failures (via the test-correction agent).\n- **When it\'s useful:** (Implementation) After making changes, to confirm everything passes and auto-fix straightforward breakages.\n- **How to use it:** `/run-tests` (`--unit-only`, `--skip-e2e`)\n\n**3. Plan Epic**\n- **What it does:** Decomposes a large epic into sub-tasks with a structured exploration doc for each.\n- **When it\'s useful:** (Architecture | Refinement) When a feature is too big for one ticket and you need it broken down and scoped.\n- **How to use it:** `/plan-epic <epic>` \u2014 *"Decompose the epic \'migrate PayPal token storage off Custom Objects\' into sub-tasks with an exploration doc for each."*\n\n**4. Update Ticket**\n- **What it does:** Synthesizes a ticket\'s clarifying answers and critique into a rewritten description and pushes it to Jira.\n- **When it\'s useful:** (Refinement) After review, to fold the resolved questions and fixes back into the ticket itself.\n- **How to use it:** `/update-ticket BAPI-123` (command only \u2014 does a full overwrite of the live Jira description; "update" as free text is both vague and hard to reverse).\n\n**5. Get Ticket**\n- **What it does:** Retrieves the full details of a Jira ticket (summary, status, description, etc.).\n- **When it\'s useful:** (Refinement | Implementation) Any time you want the agent to read a ticket before acting on it.\n- **How to use it:** ask your agent \u2014 *"Pull up BAPI-123 and show me its description, status, and acceptance criteria."*\n\n**6. Write Comment**\n- **What it does:** Posts a comment on a Jira ticket (markdown; long ones can attach as a file).\n- **When it\'s useful:** (Refinement | Implementation) To leave context, status, or a decision trail on the ticket.\n- **How to use it:** ask your agent \u2014 *"Post a comment on BAPI-123: blocked on the expired Atlassian token \u2014 will retry after it\'s rotated."*\n\n**7. Download / Upload Attachment**\n- **What it does:** Pulls files off a Jira ticket to disk, or attaches a local file to a ticket.\n- **When it\'s useful:** (Refinement | Implementation) When a ticket has design files/logs you need locally, or you want to attach output back to it.\n- **How to use it:** ask your agent \u2014 *"Download the design mockups attached to BAPI-123 into my docs folder."* / *"Attach build-log.txt to BAPI-123."*\n\n**8. Learn Repository**\n- **What it does:** Researches and documents the repo\'s architecture, testing, review, and correctness standards, then saves them to Bridge for future agents.\n- **When it\'s useful:** (Setup/Learning) When onboarding a new repo, or after big changes, so Bridge\'s agents follow your conventions.\n- **How to use it:** `/learn-repository`\n\n**9. Teach Bridge**\n- **What it does:** Takes a plain-English instruction, figures out which standards field it belongs to, and merges it in (admin only).\n- **When it\'s useful:** (Setup/Learning) When you notice the agents missing a convention and want to correct it in one sentence.\n- **How to use it:** `/teach-bridge <teaching>` \u2014 *"Teach Bridge: always use data-testid selectors in E2E tests."*\n\n### Operational commands\n\nWorkflow commands you\'ll reach for during implementation and CI, beyond the tiers above:\n\n| Command | What it does |\n|---|---|\n| `/code-ticket PROJ-123` | Download the implementation plan and questions, then execute the plan inline |\n| `/commit-ticket PROJ-123` | Stage, commit, and push changes; transition Jira status; post a smoke-test comment |\n| `/create-pr PROJ-123` | Commit staged changes and open a pull request |\n| `/check-ci [PROJ-123]` | Monitor CI checks for the current branch, triage failures, apply fixes, and report results |\n| `/parse-repository` | Queue a background job to index the repository for Bridge AI agents |\n| `/check-parse-status` | Check whether a background repository parse job is still running |\n| `/scan-tickets` | Sync recently-updated Jira tickets and backfill workflow timestamps |\n\n> Commands are designed for Claude Code. Other editors may support slash commands differently \u2014 check your editor\'s documentation for how to invoke prompt files.\n\n### Extra Capabilities\n\nBehind-the-scenes capabilities an agent gains from the MCP tools \u2014 mostly invoked automatically by the commands above, rarely requested by name:\n\n- **Ship a PR end-to-end:** commit & push, open a pull request, transition the Jira status, and discover/poll CI checks (powers `commit-ticket`, `create-pr`, `check-ci`, `implement-ticket`).\n- **Architecture plan** for a ticket (design-level guidance, separate from the implementation plan).\n- **Index the codebase** so Bridge\'s agents can reason about it: queue/parse the repo, check parse status, regenerate the directory map.\n- **Read & tune project config/standards:** list/read/update config fields, fetch project standards, and the per-topic `learn-*` commands that populate them.\n- **Ticket lifecycle bookkeeping:** track tickets and backfill workflow-state timestamps (`scan-tickets`), search across tickets, read comments, list attachments.\n- **Pipeline machinery:** list/inspect pipeline recipes and run/resume/list/delete pipeline runs (the engine under the orchestration commands).\n- **Decision page** generation for capturing human review decisions as structured data.\n- **Connectivity & identity checks:** ping Bridge, check your role, resolve the local docs directory.\n- **Retrieve any generated artifact** (`get_*` for plans, critiques, questions, brainstorms, research, architecture) without regenerating it.\n- **Tiered-section execution telemetry** recording (internal measurement).\n\n## Salesforce B2C Commerce (SFCC) Tools\n\nSalesforce\'s official MCP server, `@salesforce/b2c-dx-mcp`, covers developer-experience tasks \u2014 cartridge deploy, step debugging, docs search, logs/MRT, and SCAPI Custom API scaffolding. As of its current toolset it has no way to *read* the platform\'s object model, custom objects, or site configuration \u2014 exactly the context an AI coding agent needs to write correct Commerce code and to inspect a sandbox before touching it. Bridge\'s SFCC tools install side-by-side with `b2c-dx-mcp` (they don\'t duplicate its surface) and close that gap with **read-only OCAPI Data API introspection** of system objects, custom object definitions, and site preferences.\n\n**v1 is read-only and developer-sandbox-only** \u2014 no writes, and non-sandbox instances are rejected. Credentials stay local (in `dw.json` or `SFCC_*` env vars) and are never sent to Bridge.\n\n<details>\n<summary><strong>Setup</strong></summary>\n\nThe two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always available. The seven read tools must be enabled with a profile (step 3).\n\n**Prerequisites:** a running SFCC **sandbox** and its hostname, plus an Account Manager API client (`client-id` + `client-secret`).\n\n**1. Set the repo `version` config field** to your SFCC project type \u2014 one of `sfra | pwakit | sitegenesis | storefrontnext | hybrid`. The call-time gate reads this; a non-SFCC value blocks every SFCC tool except `sfcc_setup_status`. Set it via your normal config path, the `config_field` MCP tool (operation `update`, field `version`), or the `/teach-bridge` skill.\n\n**2. Provide SFCC credentials.** Create a `dw.json` in your project root:\n\n```json\n{\n "hostname": "zzzz-001.sandbox.us01.dx.commercecloud.salesforce.com",\n "client-id": "<account-manager-client-id>",\n "client-secret": "<account-manager-client-secret>"\n}\n```\n\nAccepted key spellings: `hostname`/`host`, `client-id`/`clientId`/`client_id`, `client-secret`/`clientSecret`/`client_secret`. Prefer a single config \u2014 a multi-entry `configs[]` array forces you to pass an explicit `instance` on every call. `dw.json` is auto-added to git exclude and must never be committed. Alternatively, export `SFCC_HOSTNAME` / `SFCC_CLIENT_ID` / `SFCC_CLIENT_SECRET` in the MCP server environment.\n\n**3. Enable the read tools.** Add `sfcc` to `BRIDGE_MCP_PROFILE` in the MCP server `env` block (it is comma-separated; `full` also works), then **restart the MCP client**:\n\n```json\n"env": { "BRIDGE_MCP_PROFILE": "sfcc" }\n```\n\nWithout this, only the diagnostic tools are registered.\n\n**4. Verify and grant OCAPI access.** Ask your agent to run `sfcc_setup_status` (expect all checks \u2713), then `check_permissions`. If it reports HTTP 401/403, it prints the exact OCAPI Settings JSON to paste in Business Manager (**Administration \u2192 Site Development \u2192 Open Commerce API Settings \u2192 Data API** tab); save it there and re-run. Restart the MCP client after any credential or env change \u2014 a running session does not pick them up.\n\n</details>\n\n### Tools\n\nAll SFCC tools are read-only and target a developer sandbox. Oversized responses are automatically saved to `BAPI_DOCS_DIR/sfcc/` and previewed inline.\n\n**Diagnostics** (always available, no profile needed)\n- `sfcc_setup_status` \u2014 report on every prerequisite: Bridge API key, repo name, `version` config, `dw.json` presence/uniqueness, and AM token acquisition.\n- `check_permissions` \u2014 probe OCAPI access via `GET /system_object_definitions`; on 401/403, print the exact OCAPI Settings JSON to paste in Business Manager (split read-only vs. write/import grants).\n\n**System object model** (needs the `sfcc` profile)\n- `system_object_list` \u2014 list system object types (Product, Order, Customer, \u2026).\n- `system_object_get` \u2014 fetch one type\'s definition, optionally with its full attribute definitions (`expand_attribute_definitions`).\n- `system_object_attribute_search` \u2014 search a type\'s attribute definitions; prefer this over a full dump when hunting a specific `c_` custom attribute.\n\n**Custom object definitions** (needs the `sfcc` profile)\n- `custom_object_definition_attributes_get` \u2014 fetch attribute definitions for a known custom object type. OCAPI cannot enumerate custom object types, so `object_type` must already be known.\n- `custom_object_definition_attribute_search` \u2014 search attribute definitions within a known custom object type. Read-only \u2014 creating a custom object *type* isn\'t possible via OCAPI; that\'s a future v2 metadata-import capability.\n\n**Site preferences** (needs the `sfcc` profile; sandbox only)\n- `site_preference_get` \u2014 read a preference group\'s effective preferences.\n- `site_preference_search` \u2014 search/filter preferences within a group.\n\n## CLI Subcommands\n\nBeyond `--init` / `--upgrade`, the package ships operational subcommands of the **single `bridge-api-mcp-server` bin** (not separate binaries) \u2014 so they travel with the package to every consumer. See [Usage Documentation \u2192 Start Tickets](#tier-1--regularly-useful) for *when* to use `start-tickets`; this section is the full CLI reference.\n\n### `start-tickets`\n\nSpawns one Worktrunk worktree + selected-agent session per Jira ticket and backs the `/start-tickets` slash command. The agent defaults to **Claude Code** (`claude`) and is configurable via `--agent`.\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--agent claude\\|cursor-agent` | `claude` | Agent command to launch in each worktree |\n| `--terminal terminal\\|iterm` | auto-detect via `$TERM_PROGRAM` | Override the macOS terminal app (honored on macOS only) |\n| `--dry-run` | off | Print intended actions; create no worktrees, open no tabs (any OS) |\n| `--branch KEY=BRANCH` | `feature/<KEY>` | Use a custom branch for that ticket (repeatable) |\n| `--base-branch <BRANCH>` | `main` | Cut new worktrees from `<BRANCH>` and refresh `origin/<BRANCH>` instead of `main` |\n| `--no-refresh-main` | off (the configured base branch is refreshed) | Skip refresh of the configured base branch (default `main`). Historical flag name preserved for backward compatibility \u2014 despite the name, it now skips refresh of whatever `--base-branch` resolves to. |\n| `--max-parallel N` | `3` | Max worktrees created concurrently |\n| `--conductor` | off | Opt into the Conductor system (per-worker `BAPI_CONDUCTOR_*` env + Claude hook injection, a supervisor peer tab, and the `check_messages` message-relay prompt). **Default off** \u2014 a plain run spawns `cd <worktree> && <agent> \'/implement-ticket <KEY>\'`. |\n| `-h`, `--help` | \u2014 | Show usage |\n\nEach `KEY` must match `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). The CLI creates/switches each worktree up front (throttled by `--max-parallel`), then opens one tab/session per successful worktree running the selected agent\'s `\'/implement-ticket <KEY>\'` \u2014 `claude \'/implement-ticket <KEY>\'` by default, or `cursor-agent \'/implement-ticket <KEY>\'` with `--agent cursor-agent`. The `/implement-ticket <KEY>` prompt is unchanged for both agents. To launch Cursor Agent instead of Claude Code:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248\n```\n\n**Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket\'s `difficulty` (1-2 \u2192 cheap, 3-5 \u2192 basic, 6+ \u2192 premium) and injects it as a `--model` flag at the spawn boundary. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand); this CLI alone maps a tier to the agent-specific alias (`claude`: `haiku`/`sonnet`/`opus`; `cursor-agent`: version-suffixed strings validated against `cursor-agent --list-models`). It is gated per repo by `difficulty_model_routing_enabled` (default **ON**) with an optional `difficulty_model_tier_overrides` JSON map (tier \u2192 alias). Routing is **fail-open**: missing credentials, an evaluation failure/timeout, a backend `fallback`, an invalid/unavailable alias, an unadvertised Cursor model, or an agent without `--model` support all omit `--model` (the agent uses its default) and surface a per-ticket warning rather than failing the spawn. `--dry-run` does **not** fetch tiers or inject `--model`.\n\n**Conductor observability (opt-in via `--conductor`, BAPI-394).** Conductor is **off by default**. With `--conductor`, a run mints a conductor `run_id`, emits events into the local ledger (`~/.config/bridge/events.db`), opens a supervisor peer tab, and (for Claude Code) injects a secret-free lifecycle hook into each worktree; it also sets `BRIDGE_MCP_PROFILE=conductor` so workers get the conductor MCP tools. Observability is best-effort \u2014 a conductor failure never blocks a spawn, and `--dry-run` performs no conductor side effects. (Epic-tick dispatch always enables conductor internally.) See **[CONDUCTOR.md](./CONDUCTOR.md)** for the full observability, git-hook, done-gate, and auto-merge reference.\n\n**Cross-platform spawning.** The CLI routes spawning per platform; `--dry-run` previews the platform-correct command form on any OS. An unsupported `process.platform` (not `darwin`/`win32`/`linux`) fails fast with a clear "unsupported platform" message.\n\n- **macOS** \u2014 opens a Terminal.app or iTerm tab via `osascript`.\n- **Windows** \u2014 creates worktrees with **`git-wt`** (Worktrunk\'s winget alias) and opens a tab via **Windows Terminal (`wt.exe new-tab`)**, falling back to **`Start-Process powershell.exe`** when Windows Terminal is absent. Requires **Git for Windows / Git Bash** (Worktrunk runs its `pre-start` / `post-start` hooks via Git Bash). The Worktrunk binary (`git-wt`) and the tab launcher (`wt.exe`) are resolved independently and never conflated.\n- **Linux** \u2014 creates one detached **tmux** session per ticket (pane kept open after the agent exits); attach with `tmux attach -t <session>`. A missing `tmux` produces a clear, actionable error.\n\nPer-OS prerequisites: macOS `wt`, `git`, `osascript`; Windows `git-wt`, Git for Windows / Git Bash, Windows Terminal or PowerShell; Linux `wt`, `git`, `tmux`. Set `BAPI_WORKTRUNK_BIN` to override the Worktrunk executable name/path for nonstandard installs (`doctor` honors it too). The read-only `doctor` subcommand (below) additionally surfaces a missing `uv` \u2014 Worktrunk\'s `pre-start` hook runs `uv`, but live preflight does not check it \u2014 and the selected agent\'s command; run `doctor --agent cursor-agent` to also check `cursor-agent` (it prints `cursor-agent login` as an informational auth reminder).\n\n### `doctor`\n\nThe package also ships a strictly **read-only** `doctor` subcommand that diagnoses the `start-tickets` prerequisites for the current OS without changing anything:\n\n```\nnpx -y @bridge_gpt/mcp-server doctor [--agent <name>]\n```\n\nIt is **read-only**: it never installs anything, modifies your system, adds an npm `postinstall`, spawns a terminal, or starts the MCP server, and there is no `--fix`. For each prerequisite it prints found/missing and, when missing, the exact per-OS install command **as a manual instruction you run yourself**. The checked set is the `start-tickets` preflight prerequisites **plus `uv`** **plus the selected agent\'s command** (`claude` by default, or `cursor-agent` with `--agent cursor-agent`). The Worktrunk binary is probed via the resolved name (honoring `BAPI_WORKTRUNK_BIN`), not a hard-coded one. **Exit code:** `0` when all required prerequisites are present, non-zero when any is missing or the platform is unsupported. A failing `start-tickets` preflight now hints you to run `doctor` for an actionable diagnostics report.\n\n### Conductor (epic & multi-agent orchestration)\n\nConductor is an **opt-in, off-by-default** layer for epic supervision, inter-agent messaging, done-gate evaluation, local git-hook event producers, and conditional auto-merge. Its full reference \u2014 `conductor install-git-hooks`, the `conductor_done_gate` and `conductor_auto_merge_enabled` config fields, and the observability stream \u2014 lives in **[CONDUCTOR.md](./CONDUCTOR.md)**.\n\n## Custom Pipelines\n\nYou can create your own pipelines by adding JSON files to `.bridge/pipelines/`. Running `--init` scaffolds this directory with a `README.md` and an example pipeline to get you started.\n\nThe easiest way to write a custom pipeline is to describe what you want to automate to your AI coding agent and have it draft the JSON for you. The schema is straightforward, and agents like Claude Code understand it well \u2014 just describe the steps you want, and the agent will produce a working pipeline file.\n\n**What you can build:**\n\n- Any sequence of Bridge MCP tool calls and free-form agent tasks\n- Parameterized workflows using variables (e.g., `{ticket_key}`)\n- Approval gates that pause for user confirmation before sensitive steps\n- Per-step error handling \u2014 halt immediately or warn and continue\n\n**Ideas for custom pipelines:**\n\n- A standup pipeline that fetches your open tickets and summarizes their status\n- A ticket triage pipeline that runs critiques on a batch of new tickets\n- A pre-merge checklist that runs tests, checks linting, and posts a summary comment\n\n**Step types:**\n\n| Type | What it does |\n|---|---|\n| `mcp_call` | Calls an MCP tool with the given params |\n| `agent_task` | Gives the AI a free-form instruction (inline or from a file in `.bridge/instructions/`) |\n\nVariables are declared in the `variables` array and referenced as `{variable_name}` in params and instructions. Each step supports `on_error: "halt"` (default) or `"warn_and_continue"`, and `requires_approval: true` to pause before execution.\n\n**System variables:**\n\nTwo variables are automatically available in every pipeline without declaring them:\n\n| Variable | What it does |\n|---|---|\n| `{provider}` | Routes AI generation to a specific LLM provider (`openai`, `anthropic`, or `gemini`). Pass it through to any `request_*` tool param to control which provider handles that step. Omit it (or leave it empty) to use the project default. |\n| `{second_opinion}` | Runs AI generation through a different provider than the default, acting as a cross-check. Set to `"auto"` to let Bridge pick the second provider automatically. When set, it takes precedence over `{provider}`. Use this when you want two independent AI perspectives on the same task \u2014 for example, running clarifying questions and a critique twice (once with each provider) produces better results than a single pass. |\n\nSee `.bridge/pipelines/README.md` for the full schema reference.\n\nIf a custom pipeline has the same key as a built-in pipeline, the custom version takes precedence (a warning is logged at startup).\n\n## Environment Variables\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `BAPI_BASE_URL` | Yes | `https://bridgegpt-api.com` | Bridge API base URL |\n| `BAPI_REPO_NAME` | Yes | _(none)_ | Jira project/repository identifier configured in Bridge API |\n| `BAPI_API_KEY` | Yes | _(none)_ | API key obtained from the Bridge API setup UI |\n| `BAPI_PROJECT_ROOT` | No | _(auto-set by --init)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution |\n| `BAPI_DOCS_DIR` | No | `docs/tmp` | Local directory for saving plans, critiques, and research reports |\n| `BAPI_PIPELINES_DIR` | No | `.bridge/pipelines` | Directory for user-defined custom pipeline JSON files |\n| `BAPI_WORKTRUNK_BIN` | No | `wt` (`git-wt` on Windows) | Override the Worktrunk executable name/path used by `start-tickets` for nonstandard installs |\n| `BAPI_TMUX_SESSION` | No | `bridge-start-tickets` | Override the tmux session-name prefix used by `start-tickets` on Linux |\n| `BAPI_MCP_UPGRADE_ADVICE_ENABLED` | No | _(enabled)_ | MCP-local opt-out for proactively surfacing upgrade advice in pipeline recipe preambles. Set to `false`/`0`/`no`/`off`/`disabled` to suppress. Disabling it does **not** change the `/jira/ping` response or server-side upgrade computation \u2014 it only gates the recipe-preamble convention |\n| `CURSOR_API_KEY` | No | _(none)_ | API key used to authenticate `cursor-agent` when launching `start-tickets`/scheduled runs with `--agent cursor-agent`. Not needed for the default Claude Code agent |\n| `BRIDGE_MCP_PROFILE` | No | `core` | Startup-time tool registration profile \u2014 a **comma-separated** list of groups controlling which tool groups are registered when the server starts. `core` is always implicitly included. Valid groups: `core` (default \u2014 normal coding tools only), `conductor` (+ 8 conductor/event/supervisor tools), `pipeline-authoring` (+ 5 pipeline run/admin tools \u2014 `get_pipeline_recipe` is NOT gated; it stays in `core` because the recipe-driven slash commands depend on it), `sfcc` (+ the 7 heavy SFCC read tools \u2014 see [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools); the `sfcc_setup_status`/`check_permissions` diagnostics are always registered regardless), and `full` (shortcut that expands to every group). Example: `sfcc,conductor`. Unknown, blank, or malformed tokens are dropped (falling back to `core`). Dynamic mid-session switching via `tools/list_changed` is unsupported \u2014 groups are resolved once at process startup. Conductor/epic sessions launched via `start-tickets --conductor` automatically inject `BRIDGE_MCP_PROFILE=conductor` at the spawn boundary; a normal `start-tickets` run stays on `core`. |\n\n## Worktree credentials and the `mcp-invoke` shim\n\nWhen `start-tickets` creates a git worktree, it provisions a Bridge API MCP\nregistration into that worktree so Claude Code (`.mcp.json`) and Cursor\n(`.cursor/mcp.json`) can reach the server immediately. These registrations are\n**secret-free**: they contain no `env` block and no API key. Instead they point\nat an internal subcommand of the published single CLI bin, `mcp-invoke`:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@<VERSION> mcp-invoke --target bapi --project-root <ABS_WORKTREE_PATH>\n```\n\n`mcp-invoke` is not a separate binary \u2014 it is a positional subcommand of\n`bridge-api-mcp-server`. It resolves the repo identity from the absolute\n`--project-root` (the committed `.bridge/config` manifest, falling back to the\ngit common dir), resolves credentials from the home-directory credential store,\nand then spawns the real MCP server with that environment.\n\n### Credential store\n\nCredentials live outside the repository, keyed by `bapi:<repo_name>`:\n\n```json\n{\n "bapi:<repo_name>": {\n "BAPI_API_KEY": "..."\n }\n}\n```\n\nResolution order:\n\n1. `BAPI_API_KEY` in the parent environment (overrides the file entirely).\n2. `$XDG_CONFIG_HOME/bridge/credentials.json`, else `~/.config/bridge/credentials.json`.\n3. `~/.bridge/credentials.json` (only when the primary path is absent).\n\nOn POSIX systems, lock the file down so only you can read it:\n\n```bash\nchmod 600 ~/.config/bridge/credentials.json\n```\n\n`mcp-invoke` warns (but continues) if the file is group/world-readable, and it\nnever creates or initializes the credential file for you.\n\n### Populating the credential store\n\nThe same store also backs the shell-spawned `start-tickets` CLI (its\ndifficulty\u2192model routing runs in a Bash process that cannot see an `env` block in\n`.mcp.json` / `.cursor/mcp.json`), so the store must hold the key for routing to\nwork. Two supported paths write it for you:\n\n- **Install-time upsert.** `/install-bridge`\'s final stage persists the validated\n routing credential into `~/.config/bridge/credentials.json` (target\n `bapi:<repo>`) via the `persist_routing_credential` MCP tool \u2014 the tool resolves\n the key inside the MCP process and writes the store, so no secret crosses the\n wire.\n- **One-shot migration.** If a key currently lives only in `.mcp.json` /\n `.cursor/mcp.json`, migrate it into the user-scoped store with the consent-gated\n command (a compatibility aid, not a live fallback):\n\n ```bash\n npx -y @bridge_gpt/mcp-server credentials migrate-agent-config [--write-credentials]\n ```\n\n Run it without `--write-credentials` to preview; add the flag to write the store.\n\n## Reference\n\nThe full surface, for when you need the complete enumeration. Day-to-day, use [Usage Documentation](#usage-documentation) instead \u2014 you don\'t call MCP tools directly; you ask your AI assistant to perform a task, or compose tools into a pipeline.\n\n### MCP tools\n\nThe server exposes **58 documented tools** (enumerated below). What\'s actually registered in a session depends on `BRIDGE_MCP_PROFILE`: the default `core` profile loads a trimmed subset, and the conductor/pipeline-authoring/SFCC tools are added only under their respective profiles (see [Environment Variables](#environment-variables)). Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).\n\n- **Connectivity & identity** \u2014 `ping`, `get_my_role`, `get_docs_dir`\n- **Jira tickets** \u2014 `get_tickets`, `get_ticket`, `create_ticket`, `update_ticket_description`, `add_comment`, `get_comments`\n- **Attachments** \u2014 `attachment` (operations: `upload`, `download`, `list`)\n- **AI generation (request/get)** \u2014 `request_plan_generation`/`get_plan`, `request_architecture`/`get_architecture`, `create_doc`/`get_doc` (design docs by `doc_type`: tdd/fsd/prd), `request_prd`/`get_prd`, `request_clarifying_questions`/`get_clarifying_questions`, `request_ticket_critique`/`get_ticket_critique`, `request_ticket_review`, `request_reimplement_context`/`get_reimplement_context`, `request_brainstorm`/`get_brainstorm`, `request_deep_research`/`get_deep_research`\n- **Other AI** \u2014 `second_opinion`, `generate_image`, `generate_decision_page`\n- **Ticket lifecycle** \u2014 `track_ticket`, `update_ticket_state`, `get_ticket_state`\n- **Jira status** \u2014 `get_jira_transitions`, `update_jira_status`, `resolve_target_status`\n- **Repository & CI** \u2014 `parse_repository`, `get_parse_status`, `regenerate_directory_map`, `create_pull_request`, `resolve_ci_checks`, `poll_ci_checks`\n- **Pipelines & automation** \u2014 `list_pipelines`, `get_pipeline_recipe`, `run_pipeline`, `resume_pipeline`, `list_pipeline_runs`, `delete_pipeline_run`, `run_full_automation`, `resume_full_automation`\n- **Config** \u2014 `get_project_standards`, `config_field` (operations: `get`, `update`, `list`)\n\n### Bundled pipelines\n\nPipelines are declarative, multi-step workflows your AI agent executes step-by-step \u2014 each a JSON recipe chaining MCP tool calls and free-form agent tasks, with variable substitution, per-step error handling, and optional approval gates. You can also write your own (see [Custom Pipelines](#custom-pipelines)).\n\n| Pipeline | Description | Invoke with |\n|---|---|---|\n| `implement-ticket` | Generate a plan, execute the implementation, commit, open a PR, and monitor CI | `/implement-ticket PROJ-123` |\n| `review-ticket` | Full ticket quality review: initial clarifying questions + critique, automatic second-opinion pass (default), then evaluation and decision capture. Pass `--rounds=1` to skip the second-opinion step (`--rounds=2` is the default). | `/review-ticket PROJ-123` |\n| `idea-to-ticket` | Turn an idea into a Jira Task/Spike (or Epic + children) with research, dedup, and critique | `/idea-to-ticket "<idea>"` |\n| `plan-epic` | Decompose an epic into sub-tasks with a structured exploration doc for each | `/plan-epic "<epic>"` |\n| `full-automation` | Chain: idea \u2192 ticket(s) \u2192 review each \u2192 spawn worktrees to implement | `/full-automation "<idea>"` |\n| `pr-ticket` | Commit changes and open a pull request | `/create-pr PROJ-123` |\n| `check-ci-ticket` | Commit, open a PR, then monitor CI checks until they pass or fail | `/check-ci PROJ-123` |\n| `learn-repository` | Analyze codebase architecture, testing, review, and documentation standards, then upload to Bridge | `/learn-repository` |\n\n### Pipeline response envelope\n\n`run_pipeline`, `resume_pipeline`, and `list_pipeline_runs` share a unified envelope keyed on `status`:\n\n- `completed` \u2014 terminal success; `results` holds per-step output.\n- `needs_agent_task` \u2014 the orchestrator paused. Read `instruction`, perform the task, then call `resume_pipeline` with `pipeline_run_id` and a string `agent_result`.\n- `failed` \u2014 terminal failure. `error_code` is one of `VALIDATION`, `NOT_FOUND`, `EXPIRED`, `REPO_MISMATCH`, `TOOL_ERROR`.\n\nPaused runs auto-expire after an idle TTL (default 24 hours; override with `ttl_seconds`). The TTL is reset on every state transition. List output is metadata-only \u2014 it never includes resolved recipes, params, instructions, results, or agent outputs.\n';
13071
13106
 
13072
13107
  // src/update-check.ts
13073
13108
  init_version_generated();
@@ -13417,6 +13452,7 @@ var COMMANDS = {
13417
13452
  "parse-repository.md": "Queue a background job to parse and index the repository for Bridge API's AI agents.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 \u2014 Parse Arguments\n\nParse `$ARGUMENTS` for an optional `directory_path` argument (a subdirectory path to scope the parse to, e.g., `src/python`). If no argument is provided, the entire repository will be parsed. If `$ARGUMENTS` is provided but invalid (e.g., contains special characters that suggest it's not a path), report an error.\n\n## Step 2 \u2014 Queue Parse Job\n\nCall the `parse_repository` MCP tool with:\n- `directory_path`: set to the parsed `directory_path` from Step 1 if provided, otherwise omit the parameter\n\nIf the response indicates parsing is already in progress, display:\n\n```\nRepository parsing is already in progress. A previous parse job has not yet completed.\n\nRun `/check-parse-status` to monitor progress, or wait a few minutes and try again.\n```\n\nStop and do not proceed to the summary.\n\nIf the call fails or returns an error, stop immediately and display:\n\n```\nFailed to queue parse job: <error message from the tool>\n```\n\n## Summary\n\nOn successful queuing, display:\n\n```\nRepository parse job queued successfully.\n\nScope: <entire repository or directory_path if provided>\n\nProcessing typically takes several minutes for large repositories.\nRun `/check-parse-status` to monitor progress.\n```\n\nAfter the parse completes, AI-generated plans and clarifying questions will reflect the latest code changes.\n",
13418
13453
  "plan-epic.md": 'Plan an epic by decomposing it into sub-tasks with structured exploration documents.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command is recipe-driven. Do not call MCP tools directly -- the recipe determines which tools to call and with what parameters.\n\n## Stage 0 \u2014 Setup\n\n1. **Parse arguments**: Extract the input from `$ARGUMENTS`. Trim any surrounding whitespace. If the input is empty or whitespace-only, stop immediately and display:\n ```\n Usage: /plan-epic <description of the epic or Jira key>\n ```\n\n2. **Jira key detection**: If the input matches a Jira key pattern (`[A-Z]+-\\d+`), call the `get_ticket` MCP tool with that key to fetch the epic description. Use the ticket\'s description as the `epic_description`, and set `epic_key` to that Jira key. If the input does not match a Jira key, use the free-form text directly as the `epic_description` and set `epic_key` to an empty string `""` (there is no Jira epic to update). The recipe uses `epic_key` to decide whether to post the goals/NFRs + recommended implementation order as a comment on the epic.\n\n3. **Generate slug**: Create a kebab-case slug from the epic description \u2014 take the first 6-8 meaningful words, strip non-alphanumeric characters (except hyphens), lowercase, and truncate to 60 characters. This becomes the `epic_slug`.\n\n4. **Directory existence check**: Call the `get_docs_dir` MCP tool (no parameters) to get the docs directory path. Then run a terminal command to check if the directory `{docs_dir}/epic-plans/{epic_slug}` already exists:\n ```\n test -d {docs_dir}/epic-plans/{epic_slug} && echo "exists" || echo "not_found"\n ```\n If the directory exists, append `-{unix_timestamp}` to the `epic_slug` (e.g., `add-auth-provider-support-1710000000`).\n\n## Stage 1 \u2014 Execution\n\n5. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `"plan-epic"`\n - `variables`: `{ "epic_description": "<resolved_description>", "epic_slug": "<slug>", "epic_key": "<jira_key_or_empty_string>" }`\n\n Note: Do NOT pass `docs_dir` in variables \u2014 it is auto-injected by the pipeline system.\n\n If the tool returns an error, stop and report the failure.\n\n6. Read and strictly obey the `agent_instructions` field in the response. Execute each step in order, announcing each as **Step N of M: <description>**.\n\n7. After all steps complete, display a summary:\n ```\n ## Pipeline Complete\n\n **Epic**: <first 80 characters of epic_description>...\n **Slug**: <epic_slug>\n **Output**: <docs_dir>/epic-plans/<epic_slug>/overview.md\n **Steps executed**: N of M\n **Status**: Success / Failed at step N\n ```\n',
13419
13454
  "plan-ticket.md": 'Generate an implementation plan for a Jira ticket, wait for the result, and save it locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 \u2014 Parse Arguments\n\n1. **Parse `$ARGUMENTS`**: Extract a required `ticket_key`, an optional `--second-opinion` flag, and an optional `--provider` flag.\n - Split `$ARGUMENTS` on whitespace.\n - If `--second-opinion` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `second_opinion_value`.\n - If `--second-opinion` appears without a provider name following it (or is the last token), set `second_opinion_value = "auto"`.\n - If `--second-opinion` is absent, set `second_opinion_value = null`.\n - If `--provider` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `provider_value`.\n - If `--provider` appears without a valid provider name following it (or is the last token), stop immediately and report: "Usage error: --provider requires a provider name (openai, anthropic, or gemini)."\n - If `--provider` is absent, set `provider_value = null`.\n - If both `--second-opinion` and `--provider` are present, `--second-opinion` takes precedence (set `provider_value = null`).\n - The remaining token (after removing flags and their arguments) is the `ticket_key`.\n - If `ticket_key` is empty or missing, stop immediately and display:\n\n ```\n Usage: /plan-ticket <ticket_key> [--second-opinion [provider]] [--provider <name>] (e.g., /plan-ticket BAPI-150)\n ```\n\n## Step 2 \u2014 Resolve Docs Directory\n\nCall the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\n## Step 3 \u2014 Generate Plan\n\nCall the `request_plan_generation` MCP tool with:\n- `ticket_number`: the parsed `ticket_key`\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `second_opinion`: set to `second_opinion_value` if non-null; omit entirely if null\n- `provider`: set to `provider_value` if non-null; omit entirely if null\n\nThis step may take 1-5 minutes while the backend processes the plan.\n\nIf the tool returns an error, stop immediately and display:\n\n```\nPlan generation failed: <error message from the tool>\n```\n\n## Step 4 \u2014 Confirm Success\n\nDisplay a confirmation message:\n\n```\nPlan generated successfully for <ticket_key>\nSaved to: {docs_dir}/plans/<ticket_key>-plan.md\n```\n\n## Final Summary\n\nDisplay a summary block:\n\n```\n## Plan Generation Report\n\n- **Ticket**: <ticket_key>\n- **Plan Status**: Generated successfully\n- **Local File**: {docs_dir}/plans/<ticket_key>-plan.md\n```\n\nOn failure at any step, stop immediately, display which step failed and the error details, and do not proceed.\n',
13455
+ "regression-check.md": 'Run the deterministic regression-reviewer (lightweight mode) against a proposed code change and report its blast radius \u2014 which real call-sites, tests, mocks, or config the change does and doesn\'t account for.\n\n$ARGUMENTS\n\n---\n\n<!-- Platform coverage: this command reaches Cursor + Claude Code (the commands\n bundle is scaffolded to .claude/commands/ and .cursor/commands/ by --init).\n The companion `regression-reviewer` agent (agents/src/regression-reviewer.md)\n reaches Claude Code + GitHub Copilot. Union: Cursor, Copilot, and Claude\n Code all get this review, either via the command or the agent. -->\n\n# Instructions\n\nThis is the standalone diff/PR (or ticket-description) review entry point for the regression-reviewer (BAPI-460). It mirrors the `regression-reviewer` agent\'s orchestration exactly \u2014 same subcommand, same parsing, same report \u2014 so the same logical review behaves identically whether invoked as a Cursor command or a Claude Code agent.\n\n## Step 1 \u2014 Determine the Input Shape\n\nParse `$ARGUMENTS`:\n\n- If it looks like a git diff range, a ref, a PR number, or is empty (defaulting to the working tree vs. `HEAD`), treat this as a **diff/PR invocation**. Resolve a `--diff <range>` value when one is given (e.g. `main...HEAD`, a commit SHA range); omit `--diff` to use the default working-tree-vs-HEAD diff.\n- If it names specific function/class/symbol names (e.g. `--symbols resolve_db_params,SomeClass`, or prose describing a not-yet-diffed planned change), treat this as a **ticket-description invocation**. Extract the symbol names and pass them via `--symbols a,b,c`.\n\nIf neither a diff nor any extractable symbol names are available, stop and ask the user to provide one.\n\n## Step 2 \u2014 Run the Deterministic Core\n\nExecute exactly:\n\n```bash\nnpx -y @bridge_gpt/mcp-server regression-check --mode lightweight --json [--diff <range> | --symbols a,b,c]\n```\n\nDo NOT hand-roll your own `ast-grep`/`ripgrep` invocations or re-discover call-sites yourself \u2014 the subcommand owns that structural analysis.\n\n## Step 3 \u2014 Parse the Findings (Fail-Open)\n\nParse the JSON: `summary.symbols_analyzed`, `summary.truncated`, `summary.tools_used`, `summary.degraded_flags`, and the `findings` array (`symbol`, `file`, `definition_location`, `call_sites` (`count`, `by_file`), `broad_mentions`).\n\nIf `summary.degraded_flags` is non-empty (e.g. `ast-grep` or `ripgrep` was unavailable), do NOT treat the run as a failure. Proceed with whatever data IS present and call out each degraded section explicitly \u2014 never silently drop a gap. If `summary.truncated` is `true`, state that the symbol set was capped.\n\n## Step 4 \u2014 Synthesize Risk\n\nFor each symbol, compare `call_sites.count` against `broad_mentions.length`:\n- **Accounted for**: every real call-site and broad mention is either already touched by the change or clearly unaffected (e.g. a doc/comment mention).\n- **Not accounted for**: a real call-site, or an uninspected broad mention, sits in a file the proposed change does not touch. Name the specific file.\n\nRank "not accounted for" items by how directly they call the changed symbol (a real call-site outranks a textual mention).\n\n## Step 5 \u2014 Propose De-Risking (Diagnostic Synthesis, Not a Patch)\n\nFor each "not accounted for" item, name ONE of:\n- **Update the affected caller**: point to the exact `file:line` and describe what needs to change there.\n- **Add a compatibility/guard seam**: when updating every caller isn\'t right (e.g. a public API, a config key read elsewhere), describe the seam needed \u2014 not its full implementation.\n\nDo NOT implement the fix. State what needs to happen and where.\n\n## Final Output\n\nPrint this report to chat (no file write required):\n\n```markdown\n# Regression Review: [Symbol(s) / Change Description]\n\n**Mode**: lightweight\n**Input**: [--diff <range> | --symbols a,b,c]\n**Tools used**: [summary.tools_used, joined]\n**Degraded**: [list summary.degraded_flags, or "none"]\n**Symbols analyzed**: [summary.symbols_analyzed.length][ \u2014 TRUNCATED, capped at N if summary.truncated]\n\n## Summary\n\n[2-3 sentences: overall risk level, how many symbols are fully accounted for vs. not, and any degraded-tool caveats.]\n\n## Systems Accounted For / Not Accounted For\n\nRender as a `.data-table`-style markdown table (bold header row; left-aligned `Symbol` / `File` columns; tight \u2705/\u26A0\uFE0F status indicators):\n\n| Symbol | File | Real Call-Sites | Broad Mentions | Status |\n|---|---|---|---|---|\n| `helper` | `src/foo.py` | 3 | 4 | \u26A0\uFE0F Not accounted for |\n| `caller` | `src/foo.py` | 1 | 1 | \u2705 Accounted for |\n\n## De-Risking Guidance\n\n### 1. [Symbol / File]\n- **Issue**: [what\'s not accounted for, with file:line]\n- **Recommendation**: Update the affected caller at `file:line` | Add a compatibility/guard seam \u2014 [describe]\n\n[Continue for each not-accounted-for item]\n\n---\n\n*Generated by regression-check (lightweight mode). Structural findings via ast-grep + ripgrep; Pinecone semantic search not available.*\n```\n',
13420
13456
  "reimplement-ticket.md": "# Reimplement Ticket: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command retrieves the reimplement context for a previously-implemented Jira ticket via MCP, then implements follow-up changes inline. Use this for small follow-up requests on tickets that have already been through the plan+implement cycle.\n\nIf any critical stage fails (Stage 0 or Stage 1), stop immediately and report which stage failed and why.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline to implement follow-up changes on a Jira ticket using assembled reimplement context. Execute all stages in sequence.\n\n## Stage 0 \u2014 Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a required `ticket_key`, an optional `--second-opinion` flag, and an optional `--provider` flag.\n - Split `$ARGUMENTS` on whitespace.\n - If `--second-opinion` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `second_opinion_value`.\n - If `--second-opinion` appears without a provider name following it (or is the last token), set `second_opinion_value = \"auto\"`.\n - If `--second-opinion` is absent, set `second_opinion_value = null`.\n - If `--provider` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `provider_value`.\n - If `--provider` appears without a valid provider name following it (or is the last token), stop immediately and report: \"Usage error: --provider requires a provider name (openai, anthropic, or gemini).\"\n - If `--provider` is absent, set `provider_value = null`.\n - If both `--second-opinion` and `--provider` are present, `--second-opinion` takes precedence (set `provider_value = null`).\n - The remaining token (after removing flags and their arguments) is the `ticket_key`.\n - If `ticket_key` is empty or does not match the expected format (one or more uppercase letters, a hyphen, and one or more digits), stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /reimplement-ticket <ticket_key> [--second-opinion [provider]] [--provider <name>] (e.g., /reimplement-ticket BAPI-150)\n ```\n\nThis stage is **critical** \u2014 stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 \u2014 Request and Retrieve Reimplement Context\n\nCall the `request_reimplement_context` MCP tool with:\n- `ticket_number`: the parsed `ticket_key` from Stage 0\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `second_opinion`: set to `second_opinion_value` if it is non-null; omit the parameter entirely if `second_opinion_value` is null\n- `provider`: set to `provider_value` if it is non-null; omit the parameter entirely if `provider_value` is null\n\nIf the tool returns an error or 404 persists after polling, stop immediately and display:\n\n```\nFailed to retrieve reimplement context for <ticket_key>.\nThis may mean:\n- The ticket has not been previously processed by Bridge API\n- Background processing failed \u2014 check server logs\n- The ticket does not exist in Jira\n\nTry running /plan-ticket <ticket_key> first if this is a new ticket.\n```\n\nOn success, read and internalize the returned context markdown. This document contains:\n- A summary of changes (if applicable)\n- New/changed information since last processing (comments, description changes, attachments)\n- The original ticket description\n- The existing implementation plan (at the bottom, for reference only)\n\nThis stage is **critical** \u2014 stop immediately on failure. Do not proceed to Stage 2.\n\n## Stage 2 \u2014 Implement Follow-Up Changes\n\nExecute changes inline in this conversation. Work directly so the user can see all progress and approve tool calls.\n\nFollow these rules:\n\n1. **Focus on the new information.** The context document identifies what has changed since the last implementation. Focus your changes on addressing the new/changed requirements.\n2. **Reference the existing plan as supplementary guidance only.** The plan at the bottom of the context describes the original implementation, not the follow-up. Use it to understand the existing code structure, not as a step-by-step guide.\n3. **Make code changes** as directed by the new information.\n4. **Run tests and checks** to verify your changes don't break existing functionality.\n5. **Do NOT run `git commit` or `git push`.** Leave all changes uncommitted for developer review.\n6. **Scope guard**: If the follow-up changes are too large in scope (e.g., fundamentally restructuring the original implementation, touching more than 5-6 files, or requiring new infrastructure), stop and ask the user for guidance rather than attempting everything. Follow-up reimplementations should be small and targeted.\n7. **If a change is ambiguous or blocked**, note the issue clearly and continue with the next change rather than halting entirely.\n\nThis stage is **critical** \u2014 if a blocking error prevents further progress, stop and report the failure.\n\n## Stage 3 \u2014 Final Summary Report\n\nDisplay a structured report after all stages complete:\n\n```\n## Reimplement Complete\n\n**Ticket**: <ticket_key>\n\n**Changes Made**:\n- <brief summary of each change>\n\n**Developer Action Items**:\n- All changes are uncommitted. Review the changes with `git diff` before committing.\n- Run the project's test suite to verify nothing is broken before committing.\n\n**Warnings**:\n<If any issues arose during implementation (scope concerns, ambiguous requirements,\nfiles that couldn't be modified), list them here. If no warnings, omit this section.>\n```\n\n## Final Report\n\nOn success, display the structured report from Stage 3 confirming that the follow-up changes are complete.\n\nOn failure at any critical stage (Stage 0 or Stage 1), display which stage failed and the error details.\n",
13421
13457
  "review-ticket.md": '---\nschedulable: true\ninteractive: true\narguments: {"positionals":[{"name":"ticketKey","type":"string","required":true}],"flags":[{"name":"auto","flag":"--auto","type":"boolean"},{"name":"rounds","flag":"--rounds","type":"string"}]}\n---\n\n# Review Ticket\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command is recipe-driven. Do not call MCP tools directly -- the recipe determines which tools to call and with what parameters.\n\n1. Parse `$ARGUMENTS` to extract:\n - A required `ticket_key` matching the Jira key pattern (`[A-Z][A-Z0-9]+-\\d+`).\n - An optional position-independent `--auto` flag.\n - An optional position-independent `--rounds=<n>` argument, where `<n>` is `1` or `2`.\n\n Tokenize `$ARGUMENTS` on whitespace. The first token matching the Jira key pattern is the `ticket_key`; ignore any additional ticket-key tokens. The presence of a `--auto` token (anywhere in `$ARGUMENTS`) sets `auto_approve` to `true`. A token matching `--rounds=1` sets `rounds` to `1`; a token matching `--rounds=2` sets `rounds` to `2`. If no `--rounds` token is present, `rounds` defaults to `2` (full review).\n\n `--auto` and `--rounds` are independent and may be supplied together.\n\n If `$ARGUMENTS` is empty or contains no token matching the Jira key pattern, stop immediately and display:\n ```\n Invalid ticket key format. Expected: PROJ-123 [--auto] [--rounds=1|2]\n Usage: /review-ticket <ticket_key> [--auto] [--rounds=1|2]\n ```\n\n If a `--rounds` token is present but its value is not `1` or `2`, stop and display:\n ```\n Invalid --rounds value. Expected: --rounds=1 or --rounds=2 (default: 2)\n Usage: /review-ticket <ticket_key> [--auto] [--rounds=1|2]\n ```\n\n2. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `"review-ticket"`\n - `variables`: `{ "ticket_key": "<ticket_key>" }`\n - `auto_approve`: `true` \u2014 only when `--auto` was passed; otherwise omit this field entirely.\n - `skip_steps`: `["second-opinion-review"]` \u2014 only when `rounds` is `1`; otherwise omit `skip_steps` entirely (do not pass `skip_steps: []` or `skip_steps: null`).\n\n Example combined-mode payload (`--rounds=1 --auto`):\n ```json\n {\n "pipeline": "review-ticket",\n "variables": { "ticket_key": "PROJ-123" },\n "auto_approve": true,\n "skip_steps": ["second-opinion-review"]\n }\n ```\n\n If the tool returns an error, stop and report the failure.\n\n3. Read and strictly obey the `agent_instructions` field in the response. Execute each step in order, announcing each as **Step N of M: <description>**.\n\n4. After all steps complete, display a summary:\n ```\n ## Pipeline Complete\n\n **Ticket**: <ticket_key>\n **Steps executed**: N of M\n **Status**: Success / Failed at step N\n ```\n',
13422
13458
  "review-tickets.md": '---\nschedulable: true\ninteractive: true\narguments: {"positionals":[{"name":"ticketKeys","type":"string","required":true,"variadic":true}],"flags":[{"name":"auto","flag":"--auto","type":"boolean"},{"name":"rounds","flag":"--rounds","type":"string"},{"name":"review","flag":"--review","type":"string","repeatable":true},{"name":"agent","flag":"--agent","type":"string"},{"name":"model","flag":"--model","type":"string"},{"name":"maxParallel","flag":"--max-parallel","type":"string"},{"name":"dryRun","flag":"--dry-run","type":"boolean"}]}\n---\n\n# Review Tickets: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes one or more Jira ticket keys and invokes the packaged `@bridge_gpt/mcp-server` CLI subcommand `review-tickets`, which opens one terminal tab per ticket running the selected agent with `/review-ticket <KEY> [--auto] --rounds=<1|2>`. Unlike `/start-tickets`, it creates no Worktrunk worktrees and does not require `wt`, `git-wt`, or `git` \u2014 it only needs the terminal launcher prerequisite for your OS.\n\n---\n\n# Instructions\n\n## Stage 0 \u2014 Parse Arguments and Connectivity Check\n\n1. **Parse `$ARGUMENTS`** to extract ticket keys, review modes, and pass-through flags:\n\n - **Ticket keys**: every whitespace-separated token matching `[A-Z]+-[0-9]+` (e.g., `BAPI-1`). If zero keys are found, stop immediately and display:\n ```\n No ticket keys found. Expected one or more keys like BAPI-1.\n Usage: /review-tickets [flags] KEY [KEY ...]\n ```\n\n - **Review mode interpretation** (per ticket or global):\n - `auto` or `--auto` \u2192 per-ticket or global auto-approve flag.\n - `single-pass`, `one-pass`, `rounds=1`, or `--rounds=1` \u2192 `rounds=1`.\n - `full`, `two-pass`, `rounds=2`, `--rounds=2`, or omitted rounds \u2192 `rounds=2`.\n - `--auto` and `--rounds` are independent: both may apply to the same ticket.\n\n - **Homogeneous modes**: when all tickets share the same auto and rounds values, translate into global `--auto` (if all auto) and `--rounds=1|2` (if all rounds are the same).\n\n - **Heterogeneous modes**: when different tickets have different auto or rounds values, translate into repeatable `--review KEY=auto,rounds=N` overrides. Do NOT set global `--auto` when only some tickets are auto-approved.\n\n - **Pass-through flags**: collect `--dry-run`, `--max-parallel N`, `--agent claude|cursor-agent`, and `--model VALUE` if supplied, and forward verbatim to the CLI.\n\n2. **Connectivity check**: Call the `ping` MCP tool. If it fails or does not return `"status": "ok"`, stop immediately and display:\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor\'s MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n## Stage 1 \u2014 Invoke the Packaged CLI\n\nUse the **Bash tool** to invoke exactly one CLI invocation:\n\n```\nnpx -y @bridge_gpt/mcp-server review-tickets [--auto] [--rounds=1|2] [--review KEY=auto,rounds=N ...] [--agent <name>] [--model <alias>] [--max-parallel N] [--dry-run] KEY [KEY ...]\n```\n\n- `review-tickets` runs all tabs from the current repository cwd \u2014 it creates no worktrees.\n- The command never runs `wt`, `git-wt`, or `git` \u2014 only the terminal launcher is required.\n- Prerequisites: macOS `osascript`, Windows `wt.exe` or PowerShell, Linux `tmux`.\n\nPass through the CLI\'s stdout and stderr verbatim. If the CLI exits non-zero, treat it as a critical failure and report the exit code and error output.\n\n## Stage 2 \u2014 Final Report\n\nOnce the CLI exits 0, parse its `Summary:` lines (each shaped like `KEY auto=<true|false> rounds=<1|2> agent=<agent> model=<alias|default> status=<status>`) and render as a markdown table:\n\n```\n| Ticket | Auto | Rounds | Agent | Model | Status |\n|----------|-------|--------|--------|---------|---------|\n| BAPI-1 | false | 2 | claude | default | spawned |\n| BAPI-2 | true | 1 | claude | default | spawned |\n```\n\nRender any CLI `Warnings:` lines below the table. If there were none, omit the warnings section.\n',
@@ -13436,7 +13472,85 @@ var AGENTS = {
13436
13472
  "model": "opus",
13437
13473
  "color": "blue"
13438
13474
  },
13439
- "body": '\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user\'s problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the key source files thoroughly \u2014 do not skim\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific \u2014 reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` \u2014 `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` \u2014 [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n\n## Materials & Access\n\n[Trailing audit-trail section \u2014 always the LAST section of the draft. Inventory every material the ticket references, grouped by source. Use monospace backticks for file paths and other technical provenance. Redact any embedded secrets.]\n\n### Reachable Local Files\n\n- `path/to/local/file.ext` \u2014 [what it is; will be gathered and attached post-create]\n\n### External/Auth-Gated Links\n\n- [Name or purpose] \u2014 `https://example.com/...` (record-only; external/auth-gated)\n\n### Binary/Image Materials (Record-Only)\n\n- `path/to/screenshot.png` \u2014 [sanitized location/access note; not attached]\n```\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the "why" \u2014 what problem does this solve or what value does it add?\n- Mention the general technical approach if it\'s clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: "extend this function", "follow this pattern", "reuse this helper", "modify this configuration"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira\'s ADF has no native checkbox, so `- [ ]` renders as literal text)\n\n**Materials Completeness Inventory**:\n- After the draft is written, INVENTORY every material the ticket references: local file paths, URLs/links, named docs/designs, screenshots, and specs. This pass only INVENTORIES and RECORDS \u2014 it does NOT attach anything. The actual attachment of reachable local files happens post-create (after the Jira `ticket_key` exists) via a separate gather-and-attach step.\n- Classify each material by source using a scheme-based rule (no network probe required):\n - **Local filesystem paths** named in the ticket body are the only **low-risk** materials \u2014 eligible to be gathered and attached post-create.\n - Every **`http(s)` URI is external/auth-gated** \u2014 regardless of whether the user explicitly linked it (an explicitly-linked Confluence or Google Doc URL is still external/auth-gated) \u2014 and is **record-only** here.\n - **Binary/image materials** (screenshots, PDFs, etc.) are **record-only** \u2014 document them with sanitized location/access notes; do NOT attempt to attach them.\n- Write the trailing `## Materials & Access` section (the LAST section of the draft) grouping items under the sub-headings *Reachable Local Files*, *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*, using bulleted lists. Use monospace formatting (backticks) for technical provenance such as file paths.\n- **Redact secrets before writing anything**: before writing any URL or access note, sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. A location/access note must NEVER expose a plaintext secret.\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links. This "No images" rule applies strictly to inline images in the description body; it does NOT restrict the attachments produced by the Materials Completeness Inventory / gather-and-attach pass.\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace "should handle errors properly" with "should catch LLM provider timeouts and return a normalized error response with errorType \'TimeoutError\'"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you\'re unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn\'t recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Do NOT skip or abbreviate the research phase. The quality of the ticket depends entirely on the depth of your codebase understanding.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file \u2014 do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket\'s requirements align with those conventions.\n'
13475
+ "body": '\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user\'s problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the key source files thoroughly \u2014 do not skim\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific \u2014 reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` \u2014 `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` \u2014 [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n\n## Materials & Access\n\n[Trailing audit-trail section \u2014 always the LAST section of the draft. Inventory every material the ticket references, grouped by source. Use monospace backticks for file paths and other technical provenance. Redact any embedded secrets.]\n\n### Reachable Local Files\n\n- `path/to/local/file.ext` \u2014 [what it is; will be gathered and attached post-create]\n\n### External/Auth-Gated Links\n\n- [Name or purpose] \u2014 `https://example.com/...` (record-only; external/auth-gated)\n\n### Binary/Image Materials (Record-Only)\n\n- `path/to/screenshot.png` \u2014 [sanitized location/access note; not attached]\n```\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the "why" \u2014 what problem does this solve or what value does it add?\n- Mention the general technical approach if it\'s clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: "extend this function", "follow this pattern", "reuse this helper", "modify this configuration"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira\'s ADF has no native checkbox, so `- [ ]` renders as literal text)\n\n**Materials Completeness Inventory**:\n- After the draft is written, INVENTORY every material the ticket references: local file paths, URLs/links, named docs/designs, screenshots, and specs. This pass only INVENTORIES and RECORDS \u2014 it does NOT attach anything. The actual attachment of reachable local files happens post-create (after the Jira `ticket_key` exists) via a separate gather-and-attach step.\n- Classify each material by source using a scheme-based rule (no network probe required):\n - **Local filesystem paths** named in the ticket body are the only **low-risk** materials \u2014 eligible to be gathered and attached post-create.\n - Every **`http(s)` URI is external/auth-gated** \u2014 regardless of whether the user explicitly linked it (an explicitly-linked Confluence or Google Doc URL is still external/auth-gated) \u2014 and is **record-only** here.\n - **Binary/image materials** (screenshots, PDFs, etc.) are **record-only** \u2014 document them with sanitized location/access notes; do NOT attempt to attach them.\n- Write the trailing `## Materials & Access` section (the LAST section of the draft) grouping items under the sub-headings *Reachable Local Files*, *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*, using bulleted lists. Use monospace formatting (backticks) for technical provenance such as file paths.\n- **Redact secrets before writing anything**: before writing any URL or access note, sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. A location/access note must NEVER expose a plaintext secret.\n\n### Regression Completeness Pass (Gated)\n\nAfter the draft (including its `## Materials & Access` section) is written, run this pass. It is a non-blocking, **warn-not-halt** completeness check \u2014 it never blocks or fails ticket creation, and it never modifies the Requirements or Acceptance Criteria text directly.\n\n1. **Check the gate first.** Call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to `enable_regression_checks`. If the tool returns an error, `null`, or any value other than the literal string `"true"`, **skip this entire pass** \u2014 the draft is produced exactly as it would be without this section (byte-for-byte unchanged). The recommended default for this flag is OFF (unset) for safe rollout; only proceed past this step when it is explicitly `"true"`.\n\n2. **Derive the touched-symbol set.** From the draft\'s Requirements and *Relevant code* citations (or, if the ticket references an existing diff/PR, that diff/PR), extract the specific function/class/symbol names the proposed change touches.\n\n3. **Run the deterministic core.** Execute:\n ```bash\n npx -y @bridge_gpt/mcp-server regression-check --mode lightweight --json --symbols <derived,symbol,names>\n ```\n This is the same subcommand the standalone `regression-reviewer` agent and `regression-check` command use \u2014 do not hand-roll your own `ast-grep`/`ripgrep` discovery.\n\n4. **Fail-open on a degraded or failed run.** If the command errors, or `summary.degraded_flags` is non-empty, record that the pass ran degraded (or could not run) and proceed \u2014 the draft is still produced. Never halt ticket creation because this subcommand was unavailable.\n\n5. **Cross-check against Requirements + Acceptance Criteria.** Parse the JSON `findings` array (`symbol`, `call_sites.by_file`, `broad_mentions`). For each symbol, compare its real call-sites and broad mentions against what the draft\'s Requirements and Acceptance Criteria already cover. Flag any affected caller, migration, or contract (a file with a real call-site or an uninspected broad mention) that the criteria do NOT mention.\n\n6. **Record the flags \u2014 never rewrite Requirements/Acceptance Criteria.** Append a `[WARNING]` block immediately before the `## Materials & Access` section, listing each flagged item:\n ```markdown\n ## Regression Completeness Notes\n\n [WARNING] The following systems were not explicitly addressed in the Requirements or Acceptance Criteria above:\n - `path/to/affected_caller.py` \u2014 calls `changed_symbol` (N real call-sites); not mentioned in Requirements\n - `path/to/config.yml` \u2014 broad mention of `changed_symbol`; verify this reference is unaffected\n\n Degraded: [list summary.degraded_flags, or "none \u2014 full structural analysis ran"]\n ```\n If no flags were raised and the run was not degraded, write a single line instead: `Regression completeness pass: no unaddressed systems found.` If the run was degraded with zero findings either way, state that explicitly rather than implying a clean pass.\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links. This "No images" rule applies strictly to inline images in the description body; it does NOT restrict the attachments produced by the Materials Completeness Inventory / gather-and-attach pass.\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace "should handle errors properly" with "should catch LLM provider timeouts and return a normalized error response with errorType \'TimeoutError\'"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you\'re unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn\'t recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Do NOT skip or abbreviate the research phase. The quality of the ticket depends entirely on the depth of your codebase understanding.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file \u2014 do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket\'s requirements align with those conventions.\n'
13476
+ },
13477
+ "refactor-reviewer": {
13478
+ "frontmatter": {
13479
+ "name": "refactor-reviewer",
13480
+ "description": `Use this agent when you need to discover and propose refactor candidates in a Python or TypeScript codebase. Given a fuzzy quality direction \u2014 optionally scoped to an area, possibly undirected \u2014 this agent returns a high-level ranked list of refactor candidates. It is DIAGNOSTIC ONLY: each candidate names WHAT the issue is, explains WHY it is an issue, cites file:line evidence, and carries a severity/value signal for ranking. It never proposes fixes, refactor designs, or solutioning of any kind.
13481
+
13482
+ IMPORTANT DISTINCTION \u2014 this agent DISCOVERS and PROPOSES refactor candidates (what deserves refactoring and why) for Python/TypeScript codebases. It does NOT analyze the impact or blast radius of a refactor that has already been planned \u2014 that is a separate impact-analysis task (blast-radius / transitive dependency tracing for a proposed change). If the user is asking for impact or blast-radius analysis of a specific change, halt and let them know this agent is not the right tool for that request.
13483
+
13484
+ Examples:
13485
+
13486
+ <example>
13487
+ Context: The user wants a broad quality scan with no specific target \u2014 undirected run.
13488
+ user: "How can we best improve the reliability of our application?"
13489
+ assistant: "I'll launch the refactor-reviewer agent to scan the codebase for reliability risks \u2014 broad except blocks, missing timeouts on LLM/network calls, high-churn complex functions, and untested hotspots \u2014 and return a ranked list of candidates."
13490
+ <commentary>
13491
+ Fuzzy undirected run. The agent translates 'reliability' into concrete signals, scopes the scan via git churn and coupling hotspots, gathers deterministic evidence, and returns a diagnostic-only ranked report.
13492
+ </commentary>
13493
+ </example>
13494
+
13495
+ <example>
13496
+ Context: The user has a quality direction aimed at a specific architectural concern \u2014 directed run.
13497
+ user: "Identify coupling issues in the epic runtime modules."
13498
+ assistant: "I'll use the refactor-reviewer agent to scan the epic runtime area for high temporal coupling and co-change hotspots, then return a ranked list of coupling candidates with file:line evidence."
13499
+ <commentary>
13500
+ Fuzzy directed run. The agent maps 'coupling issues' to temporal-coupling and co-change signals, scopes to the epic runtime modules, runs the git temporal-coupling script, and produces a diagnostic-only ranked report.
13501
+ </commentary>
13502
+ </example>
13503
+
13504
+ <example>
13505
+ Context: The user scopes the analysis to a specific directory \u2014 area-scoped run.
13506
+ user: "Scan the mcp_server/src/conductor directory for complexity hotspots."
13507
+ assistant: "I'll launch the refactor-reviewer agent scoped to mcp_server/src/conductor to find complexity hotspots via lizard CCN scores and git churn, and produce a ranked diagnostic report."
13508
+ <commentary>
13509
+ Area-scoped run. The agent skips whole-repo hotspot seeding (scope is already given), runs lizard and git against the conductor directory only, confirms findings by reading the code, and returns a diagnostic-only ranked report.
13510
+ </commentary>
13511
+ </example>`,
13512
+ "model": "opus",
13513
+ "color": "orange"
13514
+ },
13515
+ "body": '\n## ROLE ##\n\nYou are an elite software technical analyst specializing in codebase archaeology and the identification of refactoring candidates. Your skill is translating vague quality directions into concrete, evidence-backed signals \u2014 finding the code that most deserves attention and explaining clearly why it deserves it. You rely on deterministic local tools (git, ripgrep, lizard) for measurement and on your language understanding for fuzzy\u2192signal translation, ranking, and the "why it\'s an issue" prose.\n\n## CONTEXT ##\n\nYou operate in Python and TypeScript codebases (this repo: FastAPI backend + Node.js MCP server). You discover and propose refactor candidates. You do NOT trace the impact of a refactor that has already been proposed \u2014 that is a different task (blast-radius / transitive dependency analysis of a proposed change).\n\n**Halt immediately** if the user\'s request is asking you to trace the impact or side effects of a specific change they already have planned. Let them know this agent is not the right tool for blast-radius or impact-analysis requests.\n\n## OBJECTIVE ##\n\nTranslate a fuzzy quality goal \u2014 possibly undirected, possibly scoped \u2014 into a ranked list of concrete refactor candidates. Each candidate must be grounded in deterministic evidence (metrics, git history, pattern matches) and confirmed by reading the actual source. The output is a diagnostic report: what the issue is, why it matters, where it lives. Never a fix. Never a design. Never a solution.\n\n---\n\n## METHODOLOGY ##\n\n### Phase 1: Interpret the Fuzzy Goal\n\n1. Read the user\'s direction carefully. Translate it into concrete code smells and measurable signals to hunt for. Examples:\n - "reliability" \u2192 broad `except:` / `except Exception` blocks, missing timeouts or retries on network/LLM calls, high-churn complex functions, untested hotspots.\n - "maintainability" \u2192 high cyclomatic complexity, long functions, deeply nested logic, duplicated logic patterns.\n - "coupling" \u2192 temporal coupling (files that change together), high fan-in/fan-out modules, circular dependencies.\n - "performance" \u2192 N+1-style loops over DB calls, synchronous blocking in async paths, large payloads serialized per-request.\n\n2. Capture any explicit area scoping the user provides (e.g., a directory, module name, or subsystem). If the user provides a scope, use it. If the run is **undirected or whole-repo**, proceed to hotspot scoping (below) before running expensive scans.\n\n3. **Hotspot Scoping (required for undirected or whole-repo runs):**\n To avoid scanning the entire repo blindly, first translate the goal into candidate hotspot areas. Use git churn and temporal coupling as seeds:\n - Run `git log --no-merges -n 500 --name-only --pretty=format:""` and count file change frequency to find high-churn files.\n - Run the temporal-coupling script (Phase 3) to find files that co-change frequently.\n - Seed your scan areas from the top-N churn files and highest-coupling pairs that relate to the goal\'s domain.\n - Scope all subsequent deterministic scans to those directories / modules. State explicitly which areas you scoped to and why.\n\n---\n\n### Phase 2: Map Goal to Code\n\nUse ripgrep to find concrete instances of the goal-specific smell patterns identified in Phase 1.\n\nExamples:\n- Reliability / broad exception handling: `rg "except Exception|except:" --type py`\n- Missing timeout parameters: `rg "requests\\.(get|post|put|delete)" --type py | grep -v timeout`\n- Missing retry decoration on LLM/network calls: `rg "async_send_message_to_ai|aiohttp" --type py`\n- High-complexity signals: run lizard (Phase 3) rather than trying to detect this with ripgrep alone.\n\n**Known Limitation / Dependency \u2014 Pinecone Semantic Code Search:**\nBridge does not currently expose a direct semantic-code-search MCP tool to agents (Pinecone retrieval is server-side only). Therefore, **ripgrep-only mapping is the current working fallback** for Phase 2. If and when Bridge ships a semantic-code-search MCP tool, adopt it here to broaden concept mapping beyond literal pattern matching. Until then, ripgrep is the primary and sole mapping tool. Note this limitation in the Summary section of your report.\n\n---\n\n### Phase 3: Gather Deterministic Evidence\n\nRun the following tools. All three are always required (subject to graceful degradation rules below).\n\n#### Tool 1: lizard \u2014 Complexity Analysis (single external dependency)\n\nlizard is the single permitted external dependency. It is pure-Python, pip/uv-installable, cross-OS, and covers both Python and JavaScript/TypeScript incl. React (.js/.jsx/.ts/.tsx) complexity (cyclomatic complexity number, CCN, plus function length).\n\n```bash\n# Install if not present (try pip first, uv if pip fails)\npip install lizard 2>/dev/null || uv pip install lizard\n\n# Run on Python source\nlizard src/python/ --CCN 10 -l python\n\n# Run on TypeScript source\nlizard mcp_server/src/ --CCN 10 -l javascript -l typescript\n```\n\nCollect: function name, file path, CCN score, function length. Flag anything with CCN > 10 as a candidate.\n\n**Note: `git` and `ripgrep` are baseline built-ins present in both Claude Code and Cursor. They are NOT counted against the tool budget. Only `lizard` is the external dependency.**\n\n**Duplication scanning (jscpd) is deliberately omitted** to keep the agent lightweight and single-dependency.\n\n**Graceful degradation \u2014 lizard unavailable:** If lizard is not installed and cannot be installed (no pip/uv access, or the user has indicated no installs), do NOT hard-fail. Instead:\n- Fall back to LLM-read complexity estimation: read candidate files and estimate complexity by inspection.\n- Append a note to the report: "\u26A0\uFE0F lizard unavailable \u2014 complexity estimates are LLM-read, not metric-grounded. Determinism is reduced."\n- Continue with all other phases.\n\n#### Tool 2: git log \u2014 Churn Analysis\n\nHigh-churn files are change hotspots. Run:\n\n```bash\ngit log --no-merges -n 500 --name-only --pretty=format:"" | sort | uniq -c | sort -rn | head -30\n```\n\nFiles appearing most frequently are the highest-churn candidates. Cross-reference with lizard CCN scores to find high-churn AND high-complexity intersections \u2014 these are the highest-value candidates.\n\n#### Tool 3: Temporal Coupling Script\n\nFiles that change together frequently are likely more coupled than their module boundaries suggest. Run this script verbatim (no JVM, no code-maat, no Gitarch dependency):\n\n```python\nimport subprocess, collections, itertools, os\nlog = subprocess.run(["git","log","--no-merges","-n","800","--name-only","--pretty=format:@%H"],\n capture_output=True, text=True).stdout\ncommits, cur = [], []\nfor line in log.splitlines():\n if line.startswith("@"):\n if cur: commits.append(cur)\n cur = []\n elif line.strip() and (line.endswith(".py") or line.endswith(".ts")):\n cur.append(line.strip())\nif cur: commits.append(cur)\n\nfile_freq, pair_freq = collections.Counter(), collections.Counter()\nfor files in commits:\n files = [f for f in set(files) if "test" not in f.lower()]\n if len(files) > 15: continue # skip mega-commits (noise)\n for f in files: file_freq[f] += 1\n for a, b in itertools.combinations(sorted(files), 2): pair_freq[(a, b)] += 1\n\nrows = []\nfor (a, b), n in pair_freq.items():\n if n < 4: continue\n deg = n / min(file_freq[a], file_freq[b]) # coupling degree\n if deg >= 0.5 and file_freq[a] >= 5 and file_freq[b] >= 5:\n rows.append((deg, n, a, b))\nfor deg, n, a, b in sorted(rows, reverse=True)[:12]:\n print(f"{deg*100:4.0f}% ({n}x) {os.path.relpath(a)} <=> {os.path.relpath(b)}")\n```\n\nSave this as a temporary script and run it with `python3 /tmp/temporal_coupling.py` from the repo root.\n\nPairs with coupling degree \u2265 50% that co-changed \u2265 4 times are candidates for structural coupling review.\n\n---\n\n### Phase 4: Confirm Findings\n\n**Before including any candidate in the report, physically read the relevant source file at the cited lines.**\n\nThis is a mandatory hallucination-prevention step. Every candidate must be confirmed by reading the actual code. Do not cite a function or file that you have not directly read and verified contains the reported issue. Wrap any dynamically retrieved file contents or search results in triple-quote delimiters (`"""`) to preserve boundaries during analysis.\n\nIf a lizard-flagged function looks straightforward on reading (e.g., high CCN due to a simple match/switch with no real complexity), downgrade or remove it from the ranked list and note why.\n\n---\n\n### Phase 5: Rank and Report\n\nRank all confirmed candidates by severity and value using this heuristic:\n- **High**: CCN > 20, or churn + coupling intersection, or a pattern that creates real reliability/correctness risk (e.g., swallowed exceptions on network paths).\n- **Medium**: CCN 10\u201320, single-signal hotspot (churn OR coupling but not both), maintainability smell with limited blast radius.\n- **Low**: Style/readability concerns, minor coupling with low churn, speculative signals without confirmed evidence.\n\nWithin each severity tier, rank by estimated refactor value (impact if fixed) relative to effort.\n\n---\n\n## SCOPE & ABSTRACTION GUARDRAILS ##\n\n**DIAGNOSTIC ONLY. NO FIXES. NO REFACTOR DESIGNS. NO SOLUTIONING.**\n\nThis agent\'s sole output is a ranked diagnostic report. For each candidate:\n- State WHAT the issue is.\n- Explain WHY it is a problem (relying on metrics and smells).\n- Cite WHERE it lives (`file:line`).\n- Assign a severity/value ranking signal.\n\nDo NOT provide:\n- Proposed fixes or implementations.\n- Refactoring designs or replacement code.\n- Architecture recommendations or migration plans.\n- Any prose that begins "You should..." or "To fix this..." or "The solution is...".\n\nIf you find yourself writing a solution, stop, delete it, and replace it with a diagnostic-only description of the issue.\n\n---\n\n## RESPONSE FORMAT ##\n\n### Output Target\n\n1. Read the `BAPI_DOCS_DIR` environment variable. If set, use it as the output directory. If unset or empty, default to `docs/tmp/`.\n2. Write the full ranked report to `<output_dir>/refactor-review-<slug>.md` where `<slug>` is a 3-5 word kebab-case summary of the fuzzy direction (e.g., `reliability-scan`, `conductor-complexity`, `epic-runtime-coupling`).\n3. Emit a concise chat summary (2-4 sentences) to the user stating: how many candidates were found, the top 1-2 findings, and the path to the written report. Do NOT dump the full report to chat.\n\n### Report Skeleton\n\nMirror this structure exactly when writing to disk:\n\n```markdown\n# Refactor Candidates: [Fuzzy Direction / Goal]\n\n**Scope**: [Area scoped to, or "Whole repo \u2014 hotspot areas: X, Y, Z"]\n**Tools run**: lizard [version | unavailable \u2014 LLM-read fallback used], git log, ripgrep\n**Commits analyzed**: [N]\n**Known gaps**: Pinecone semantic code search not available to this agent \u2014 ripgrep-only mapping used for Phase 2.\n\n## Summary\n\n[2-3 sentences: what was scanned, any fallback states used (e.g., lizard unavailable), and the high-level finding count by severity.]\n\n## Ranked Candidates\n\n### 1. [Candidate Name / Concept]\n\n- **Severity / Value**: High / Medium / Low\n- **What**: [Clear, specific description of the issue \u2014 function name, file, what property makes it a candidate]\n- **Why**: [Explanation of why this is problematic \u2014 cite metrics (CCN score, churn count, coupling degree), or smell pattern]\n- **Evidence**: `path/to/file.ext:line_number` \u2014 [brief context: e.g., "CCN 42, 187 lines, changed 23 times in last 500 commits"]\n\n### 2. [Next Candidate]\n\n- **Severity / Value**: ...\n- **What**: ...\n- **Why**: ...\n- **Evidence**: `path/to/file.ext:line_number` \u2014 ...\n\n[Continue for all confirmed candidates, ranked within severity tier by refactor value]\n\n---\n\n*Generated by refactor-reviewer. DIAGNOSTIC ONLY \u2014 no fixes or designs included.*\n```\n\n### Important Reminders\n\n- Always write the report file to disk. Do NOT just print it to chat.\n- Every `Evidence` entry must cite a real `file:line` you have personally verified by reading.\n- If lizard was unavailable, the Summary and per-candidate Why prose must note this and describe the LLM-read method used instead.\n- The Pinecone gap note appears in the header block of every report.\n'
13516
+ },
13517
+ "regression-reviewer": {
13518
+ "frontmatter": {
13519
+ "name": "regression-reviewer",
13520
+ "description": `Use this agent when you need the blast radius of a proposed code change made explicit before merging it \u2014 which real call-sites, tests, mocks, or config depend on the changed symbols, and what de-risking the change should include. Given a git diff/PR range or a ticket description naming the touched code, it runs the deterministic regression-check subcommand and turns the structural findings into a 'systems accounted for / not accounted for' report plus concrete de-risking guidance (update an affected caller, or add a compatibility/guard seam). It does NOT discover refactor candidates or rank code quality \u2014 that is a different agent's job.
13521
+
13522
+ Examples:
13523
+
13524
+ <example>
13525
+ Context: The user has a diff/PR ready for review and wants its blast radius checked before merge.
13526
+ user: "Here's the diff for PR #842 \u2014 can you check what this change might break?"
13527
+ assistant: "I'll use the Task tool to launch the regression-reviewer agent to run regression-check against this diff and report which real call-sites, tests, and config the change accounts for versus what it's missing."
13528
+ <commentary>
13529
+ A diff/PR is the native input shape for regression-check's lightweight mode \u2014 the agent runs the subcommand against the given range and synthesizes the structural findings into a risk report.
13530
+ </commentary>
13531
+ </example>
13532
+
13533
+ <example>
13534
+ Context: The user describes a planned change via a ticket description rather than a diff.
13535
+ user: "BAPI-471 wants to rename \`resolve_db_params\` \u2014 what's the blast radius before I touch it?"
13536
+ assistant: "Let me launch the regression-reviewer agent with the symbol \`resolve_db_params\` so it can run regression-check and report every real call-site, test, and config reference that change would need to account for."
13537
+ <commentary>
13538
+ No diff exists yet, so the agent derives the touched-symbol set from the ticket description and invokes regression-check with an explicit --symbols list instead of a --diff range.
13539
+ </commentary>
13540
+ </example>
13541
+
13542
+ <example>
13543
+ Context: ast-grep is not installed in the current environment.
13544
+ user: "Review the blast radius of my staged changes."
13545
+ assistant: "I'll run the regression-reviewer agent. If regression-check reports degraded call-site analysis (e.g. ast-grep missing), the agent will proceed with whatever data is available, clearly flag the gap, and still produce the de-risking report rather than failing."
13546
+ <commentary>
13547
+ Fail-open posture: a degraded regression-check run still yields a report, with the degradation called out explicitly rather than blocking the agent.
13548
+ </commentary>
13549
+ </example>`,
13550
+ "model": "opus",
13551
+ "color": "yellow"
13552
+ },
13553
+ "body": '\n<!-- Platform coverage: this agent reaches Claude Code + GitHub Copilot. The\n companion `regression-check` slash command (commands/src/regression-check.md)\n reaches Cursor + Claude Code. Union: Cursor, Copilot, and Claude Code all\n get this review, either via the agent or the command. -->\n\n## ROLE ##\n\nYou are a precise, evidence-driven regression analyst. Your job is to make the blast radius of a proposed code change explicit \u2014 which code genuinely depends on the symbols it touches, and what could regress if those dependents aren\'t accounted for. You do NOT discover call-sites by hand: a deterministic subcommand (`regression-check`) does that structurally via `ast-grep` and `ripgrep`. Your skill is synthesizing those structural findings into a clear, ranked risk report and naming concrete de-risking moves.\n\n## CONTEXT ##\n\nThis is **lightweight mode** \u2014 the default. It produces a *structural* report: real call-sites (ast-grep) versus the wider textual mention set (ripgrep, covering tests/mocks/strings/config). The gap between those two sets is the primary signal: a symbol with many text mentions but few real call-sites likely has callers, mocks, or config the change hasn\'t accounted for.\n\nYou do NOT discover or propose general refactor candidates (code-quality issues unrelated to a specific proposed change) \u2014 that is the `refactor-reviewer` agent\'s job. If the user is asking for a general quality scan rather than the impact of a specific change, halt and point them at `refactor-reviewer` instead.\n\nPinecone semantic code search is NOT available to you \u2014 `ast-grep` + `ripgrep` is the working layer, and this produces a full structural-only report on any repository, indexed or not.\n\n## OBJECTIVE ##\n\nGiven a proposed change (a git diff/PR range, or a ticket description naming the touched symbols), run `regression-check` and turn its structured findings into:\n1. A "systems accounted for / not accounted for" report.\n2. Concrete de-risking guidance: which affected caller to update, or where a compatibility/guard seam is needed.\n\n---\n\n## METHODOLOGY ##\n\n### Step 1: Determine the Input Shape\n\n- **Diff/PR invocation**: the user supplies (or you can resolve) a git diff range, a PR number, or "my staged changes" / "my current branch". Resolve this to a `--diff <range>` value when you can (e.g. `main...HEAD`, a commit SHA range, or omit `--diff` entirely to use the default working-tree-vs-HEAD diff).\n- **Ticket-description invocation**: no diff exists yet (the change is still planned). Read the ticket/requirement text and extract the specific function/class/symbol names it names as the target of the change. Pass them via `--symbols a,b,c`.\n\nIf neither a diff nor any extractable symbol names are available, halt and ask the user to provide one.\n\n### Step 2: Run the Deterministic Core\n\nExecute exactly:\n\n```bash\nnpx -y @bridge_gpt/mcp-server regression-check --mode lightweight --json [--diff <range> | --symbols a,b,c]\n```\n\nDo NOT hand-roll your own `ast-grep`/`ripgrep` invocations or re-discover call-sites yourself \u2014 the subcommand owns that structural analysis. Your job starts with its JSON output.\n\n### Step 3: Parse the Findings\n\nParse the JSON: `summary.symbols_analyzed`, `summary.truncated`, `summary.tools_used`, `summary.degraded_flags`, and the `findings` array (`symbol`, `file`, `definition_location`, `call_sites` (`count`, `by_file`), `broad_mentions`).\n\n**Fail-open**: if `summary.degraded_flags` is non-empty (e.g. `ast-grep` or `ripgrep` was unavailable), do NOT treat the run as a failure. Proceed with whatever data IS present, and call out each degraded section explicitly in the report\'s Summary \u2014 never silently omit the gap. A `[DEGRADED]` finding still tells you something (e.g. broad mentions are known but real call-sites are unknown); report it as such rather than discarding it. If `summary.truncated` is `true`, state that the symbol set was capped and name which symbols were analyzed.\n\n### Step 4: Synthesize Risk\n\nFor each symbol, compare `call_sites.count` against `broad_mentions.length`:\n- **Accounted for**: every real call-site and every broad mention is either already touched by the change or clearly unaffected by it (e.g. a doc/comment mention).\n- **Not accounted for**: a real call-site, or a broad mention not yet inspected, sits in a file the proposed change does not touch. Name the specific file.\n\nRank "not accounted for" items by how directly they call the changed symbol (a real call-site outranks a textual mention).\n\n### Step 5: Propose De-Risking \u2014 Diagnostic Synthesis, Not a Patch\n\nFor each "not accounted for" item, name ONE of:\n- **Update the affected caller**: the caller\'s usage will break or behave differently; point to the exact `file:line` (from `call_sites.by_file` / `broad_mentions`) and describe what needs to change there.\n- **Add a compatibility/guard seam**: when updating every caller isn\'t the right call (e.g. a public API, a config key still read elsewhere), describe the seam needed (a deprecation shim, a fallback default, a feature flag) \u2014 not the seam\'s full implementation.\n\nYou are NOT implementing the fix. State what needs to happen and where; leave the actual edit to the developer or a follow-up task.\n\n---\n\n## RESPONSE FORMAT ##\n\nProduce a chat report (no file write required) with these sections:\n\n```markdown\n# Regression Review: [Symbol(s) / Change Description]\n\n**Mode**: lightweight\n**Input**: [--diff <range> | --symbols a,b,c]\n**Tools used**: [summary.tools_used, joined]\n**Degraded**: [list summary.degraded_flags, or "none"]\n**Symbols analyzed**: [summary.symbols_analyzed.length][ \u2014 TRUNCATED, capped at N if summary.truncated]\n\n## Summary\n\n[2-3 sentences: overall risk level, how many symbols are fully accounted for vs. not, and any degraded-tool caveats from Step 3.]\n\n## Systems Accounted For / Not Accounted For\n\nRender as a `.data-table`-style markdown table (bold header row; left-aligned `Symbol` / `File` columns; tight \u2705/\u26A0\uFE0F status indicators) with one row per changed symbol:\n\n| Symbol | File | Real Call-Sites | Broad Mentions | Status |\n|---|---|---|---|---|\n| `helper` | `src/foo.py` | 3 | 4 | \u26A0\uFE0F Not accounted for |\n| `caller` | `src/foo.py` | 1 | 1 | \u2705 Accounted for |\n\n## De-Risking Guidance\n\n### 1. [Symbol / File]\n- **Issue**: [what\'s not accounted for, with file:line]\n- **Recommendation**: Update the affected caller at `file:line` | Add a compatibility/guard seam \u2014 [describe]\n\n[Continue for each not-accounted-for item]\n\n---\n\n*Generated by regression-reviewer (lightweight mode). Structural findings via ast-grep + ripgrep; Pinecone semantic search not available.*\n```\n\n### Important Reminders\n\n- Never re-discover call-sites by hand \u2014 always run `regression-check` first.\n- Never silently drop a degraded section \u2014 name it.\n- Never propose a full implementation \u2014 name the needed change and where it goes, not the code itself.\n'
13440
13554
  }
13441
13555
  };
13442
13556
 
@@ -15253,6 +15367,659 @@ async function runAgentCapabilitiesCli(argv, overrides = {}) {
15253
15367
  return hasFailureOrHang(collection) ? 1 : 0;
15254
15368
  }
15255
15369
 
15370
+ // src/regression-check.ts
15371
+ init_start_tickets();
15372
+ init_start_tickets_prereqs();
15373
+ var VALID_MODES = ["lightweight", "heavy"];
15374
+ var MAX_SYMBOLS_PER_RUN = 40;
15375
+ var AST_GREP_CANDIDATES = ["ast-grep", "sg"];
15376
+ function getRegressionCheckUsage() {
15377
+ return [
15378
+ "Usage:",
15379
+ " npx -y @bridge_gpt/mcp-server regression-check [--mode lightweight|heavy] [--diff <range>] [--symbols a,b,c] [--json]",
15380
+ "",
15381
+ "Deterministic blast-radius analysis for a proposed code change: extracts the",
15382
+ "symbols touched by a git diff (or an explicit --symbols list), then uses",
15383
+ "ast-grep to find their REAL structural call-sites and ripgrep for the wider",
15384
+ "set of textual mentions (tests/mocks/strings/config). Read-only \u2014 makes no",
15385
+ "code changes and no network calls.",
15386
+ "",
15387
+ "Flags:",
15388
+ " --mode lightweight|heavy Analysis depth (default: lightweight). lightweight",
15389
+ " traces a proposed diff's blast radius; heavy takes",
15390
+ " no diff and scans the whole repo for fragile,",
15391
+ " high-blast-radius locations (fan-in + temporal",
15392
+ " coupling + complexity), seeded from churn/coupling",
15393
+ " hotspots and bounded by a top-N cap.",
15394
+ " --diff <range> A git diff range/ref to analyze, lightweight mode only (default: HEAD,",
15395
+ " i.e. the working tree against HEAD)",
15396
+ " --symbols a,b,c Explicit comma-separated symbol names, bypassing",
15397
+ " diff parsing (searches every supported language)",
15398
+ " --json Emit machine-readable JSON instead of a human summary",
15399
+ " -h, --help Show this help",
15400
+ "",
15401
+ "Missing tools (ast-grep, ripgrep) degrade gracefully: the affected section is",
15402
+ "flagged [DEGRADED] and the command still exits 0. Run",
15403
+ "`npx -y @bridge_gpt/mcp-server doctor` to check tool availability.",
15404
+ "",
15405
+ "Exit code: 0 on a completed run (degraded or not); non-zero only for a usage",
15406
+ "error or an unreadable diff."
15407
+ ].join("\n");
15408
+ }
15409
+ function parseRegressionCheckArgs(argv) {
15410
+ if (argv.includes("-h") || argv.includes("--help")) {
15411
+ return { status: "help", usage: getRegressionCheckUsage() };
15412
+ }
15413
+ let mode = "lightweight";
15414
+ let diffRange;
15415
+ let symbols;
15416
+ let json = false;
15417
+ for (let i = 0; i < argv.length; i++) {
15418
+ const arg = argv[i];
15419
+ if (arg === "--json") {
15420
+ json = true;
15421
+ continue;
15422
+ }
15423
+ if (arg === "--mode" || arg.startsWith("--mode=")) {
15424
+ let value;
15425
+ if (arg.startsWith("--mode=")) {
15426
+ value = arg.slice("--mode=".length);
15427
+ } else {
15428
+ if (i + 1 >= argv.length) {
15429
+ return { status: "error", message: "--mode requires a value (lightweight or heavy)." };
15430
+ }
15431
+ value = argv[++i];
15432
+ }
15433
+ if (!VALID_MODES.includes(value)) {
15434
+ return {
15435
+ status: "error",
15436
+ message: `Invalid --mode value: '${value}' (allowed: ${VALID_MODES.join(", ")}).`
15437
+ };
15438
+ }
15439
+ mode = value;
15440
+ continue;
15441
+ }
15442
+ if (arg === "--diff" || arg.startsWith("--diff=")) {
15443
+ let value;
15444
+ if (arg.startsWith("--diff=")) {
15445
+ value = arg.slice("--diff=".length);
15446
+ } else {
15447
+ if (i + 1 >= argv.length) {
15448
+ return { status: "error", message: "--diff requires a value (a git diff range/ref)." };
15449
+ }
15450
+ value = argv[++i];
15451
+ }
15452
+ diffRange = value;
15453
+ continue;
15454
+ }
15455
+ if (arg === "--symbols" || arg.startsWith("--symbols=")) {
15456
+ let value;
15457
+ if (arg.startsWith("--symbols=")) {
15458
+ value = arg.slice("--symbols=".length);
15459
+ } else {
15460
+ if (i + 1 >= argv.length) {
15461
+ return { status: "error", message: "--symbols requires a value (comma-separated symbol names)." };
15462
+ }
15463
+ value = argv[++i];
15464
+ }
15465
+ symbols = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
15466
+ if (symbols.length === 0) {
15467
+ return { status: "error", message: "--symbols requires at least one non-empty symbol name." };
15468
+ }
15469
+ continue;
15470
+ }
15471
+ if (arg.startsWith("-")) {
15472
+ return { status: "error", message: `Unknown flag: ${arg}` };
15473
+ }
15474
+ return {
15475
+ status: "error",
15476
+ message: `Unexpected positional argument: '${arg}'. regression-check takes only flags.`
15477
+ };
15478
+ }
15479
+ return { status: "ok", options: { mode, diffRange, symbols, json } };
15480
+ }
15481
+ var SECRET_PATTERNS = [
15482
+ // key/token/secret/password/bearer = "<value>" style assignments.
15483
+ /\b[A-Za-z0-9_-]*(?:api[_-]?key|token|secret|password|bearer)[A-Za-z0-9_-]*\s*[:=]\s*['"]?[A-Za-z0-9_\-./+=]{8,}['"]?/gi,
15484
+ // Authorization: Bearer <value> headers.
15485
+ /\bBearer\s+[A-Za-z0-9._-]{10,}/g,
15486
+ // Common provider key prefixes (OpenAI/Anthropic-style sk-... tokens).
15487
+ /\bsk-[A-Za-z0-9_-]{16,}/g
15488
+ ];
15489
+ function redactSecrets2(text) {
15490
+ let out = text;
15491
+ for (const pattern of SECRET_PATTERNS) {
15492
+ out = out.replace(pattern, "[REDACTED_TOKEN]");
15493
+ }
15494
+ return out;
15495
+ }
15496
+ async function getDiffText(deps, diffRange) {
15497
+ const args = diffRange ? ["diff", diffRange] : ["diff", "HEAD"];
15498
+ const result = await deps.runCommand("git", args, { cwd: deps.cwd });
15499
+ if (result.exitCode !== 0) {
15500
+ return { ok: false, error: redactSecrets2(result.stderr.trim() || `git diff exited ${result.exitCode}`) };
15501
+ }
15502
+ return { ok: true, diff: redactSecrets2(result.stdout) };
15503
+ }
15504
+ var PY_DEF_RE = /^\s*(?:async\s+def|def|class)\s+([A-Za-z_][A-Za-z0-9_]*)/;
15505
+ var TS_DEF_RE = /^\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:function\s*\*?\s+([A-Za-z_$][\w$]*)|class\s+([A-Za-z_$][\w$]*)|(?:const|let)\s+([A-Za-z_$][\w$]*)\s*[:=])/;
15506
+ function languageForFile(file) {
15507
+ if (file.endsWith(".py")) return "python";
15508
+ if (file.endsWith(".ts") || file.endsWith(".tsx")) return "typescript";
15509
+ return null;
15510
+ }
15511
+ function extractChangedSymbolsFromDiff(diffText) {
15512
+ const symbols = [];
15513
+ let currentFile = null;
15514
+ let newLineNo = 0;
15515
+ for (const rawLine of diffText.split("\n")) {
15516
+ if (rawLine.startsWith("+++ ")) {
15517
+ const path27 = rawLine.slice(4).trim();
15518
+ currentFile = path27 === "/dev/null" ? null : path27.replace(/^b\//, "");
15519
+ continue;
15520
+ }
15521
+ if (rawLine.startsWith("--- ") || rawLine.startsWith("diff --git") || rawLine.startsWith("index ")) {
15522
+ continue;
15523
+ }
15524
+ const hunkMatch = rawLine.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
15525
+ if (hunkMatch) {
15526
+ newLineNo = parseInt(hunkMatch[1], 10);
15527
+ continue;
15528
+ }
15529
+ if (rawLine.startsWith("\\")) continue;
15530
+ if (!currentFile) continue;
15531
+ if (rawLine.startsWith("-")) continue;
15532
+ const isAdded = rawLine.startsWith("+");
15533
+ const content = isAdded ? rawLine.slice(1) : rawLine.startsWith(" ") ? rawLine.slice(1) : rawLine;
15534
+ if (isAdded) {
15535
+ const language = languageForFile(currentFile);
15536
+ if (language) {
15537
+ let name = null;
15538
+ if (language === "python") {
15539
+ const m = content.match(PY_DEF_RE);
15540
+ if (m) name = m[1];
15541
+ } else {
15542
+ const m = content.match(TS_DEF_RE);
15543
+ if (m) name = m[1] || m[2] || m[3] || null;
15544
+ }
15545
+ if (name) {
15546
+ symbols.push({ symbol: name, file: currentFile, language, line: newLineNo });
15547
+ }
15548
+ }
15549
+ }
15550
+ newLineNo += 1;
15551
+ }
15552
+ return symbols;
15553
+ }
15554
+ function capChangedSymbols(symbols, max = MAX_SYMBOLS_PER_RUN) {
15555
+ const deduped = [];
15556
+ const seen = /* @__PURE__ */ new Set();
15557
+ for (const s of symbols) {
15558
+ const key = `${s.file ?? ""}::${s.symbol}`;
15559
+ if (seen.has(key)) continue;
15560
+ seen.add(key);
15561
+ deduped.push(s);
15562
+ }
15563
+ if (deduped.length <= max) return { symbols: deduped, truncated: false };
15564
+ return { symbols: deduped.slice(0, max), truncated: true };
15565
+ }
15566
+ var AST_GREP_LANG = {
15567
+ python: "python",
15568
+ typescript: "typescript"
15569
+ };
15570
+ async function findCallSites(deps, astGrepBinary, symbol, language) {
15571
+ const languages = language ? [language] : Object.keys(AST_GREP_LANG);
15572
+ const byFile = {};
15573
+ let count = 0;
15574
+ for (const lang of languages) {
15575
+ const pattern = `${symbol}($$$ARGS)`;
15576
+ const result = await deps.runCommand(
15577
+ astGrepBinary,
15578
+ ["run", "--pattern", pattern, "--lang", AST_GREP_LANG[lang], "--json", "."],
15579
+ { cwd: deps.cwd }
15580
+ );
15581
+ if (result.exitCode >= 2) {
15582
+ return { ok: false, error: redactSecrets2(result.stderr.trim() || `ast-grep exited ${result.exitCode}`) };
15583
+ }
15584
+ let matches;
15585
+ try {
15586
+ matches = JSON.parse(result.stdout.trim() || "[]");
15587
+ } catch {
15588
+ return { ok: false, error: "ast-grep returned non-JSON output" };
15589
+ }
15590
+ if (!Array.isArray(matches)) {
15591
+ return { ok: false, error: "ast-grep JSON output was not an array" };
15592
+ }
15593
+ for (const m of matches) {
15594
+ const file = m && typeof m === "object" && typeof m.file === "string" ? m.file : "(unknown)";
15595
+ byFile[file] = (byFile[file] ?? 0) + 1;
15596
+ count += 1;
15597
+ }
15598
+ }
15599
+ return { ok: true, count, byFile };
15600
+ }
15601
+ async function findBroadMentions(deps, symbol) {
15602
+ const result = await deps.runCommand("rg", ["-l", "-w", "--", symbol, "."], { cwd: deps.cwd });
15603
+ if (result.exitCode >= 2) {
15604
+ return { ok: false, error: redactSecrets2(result.stderr.trim() || `ripgrep exited ${result.exitCode}`) };
15605
+ }
15606
+ const files = result.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
15607
+ return { ok: true, files };
15608
+ }
15609
+ function explicitSymbolsToChangedSymbols(symbolNames) {
15610
+ return symbolNames.map((symbol) => ({ symbol, file: null, language: null, line: null }));
15611
+ }
15612
+ async function runLightweightRegressionCheck(deps, options) {
15613
+ const degradedFlags = [];
15614
+ const toolsUsed = /* @__PURE__ */ new Set(["git"]);
15615
+ let changedSymbols;
15616
+ if (options.symbols && options.symbols.length > 0) {
15617
+ changedSymbols = explicitSymbolsToChangedSymbols(options.symbols);
15618
+ } else {
15619
+ const diffResult = await getDiffText(deps, options.diffRange);
15620
+ if (!diffResult.ok) {
15621
+ return { ok: false, error: `Unable to read git diff: ${diffResult.error}` };
15622
+ }
15623
+ changedSymbols = extractChangedSymbolsFromDiff(diffResult.diff);
15624
+ }
15625
+ const { symbols: capped, truncated } = capChangedSymbols(changedSymbols);
15626
+ const astGrepBinary = await resolveFirstCommandOnPath(deps, AST_GREP_CANDIDATES);
15627
+ if (!astGrepBinary) {
15628
+ degradedFlags.push("ast-grep (or sg) not found on PATH \u2014 call-site analysis skipped for all symbols.");
15629
+ } else {
15630
+ toolsUsed.add("ast-grep");
15631
+ }
15632
+ const ripgrepFound = await isCommandOnPath(deps, "rg");
15633
+ if (!ripgrepFound) {
15634
+ degradedFlags.push("ripgrep (rg) not found on PATH \u2014 broad-mention analysis skipped for all symbols.");
15635
+ } else {
15636
+ toolsUsed.add("ripgrep");
15637
+ }
15638
+ const findings = [];
15639
+ for (const sym of capped) {
15640
+ const finding = {
15641
+ symbol: sym.symbol,
15642
+ file: sym.file,
15643
+ language: sym.language,
15644
+ definitionLocation: sym.file && sym.line ? { file: sym.file, line: sym.line } : null,
15645
+ callSites: null,
15646
+ broadMentions: null
15647
+ };
15648
+ if (astGrepBinary) {
15649
+ const result = await findCallSites(deps, astGrepBinary, sym.symbol, sym.language);
15650
+ if (result.ok) {
15651
+ finding.callSites = { count: result.count, byFile: result.byFile };
15652
+ } else {
15653
+ degradedFlags.push(`ast-grep call-site search failed for '${sym.symbol}': ${result.error}`);
15654
+ }
15655
+ }
15656
+ if (ripgrepFound) {
15657
+ const result = await findBroadMentions(deps, sym.symbol);
15658
+ if (result.ok) {
15659
+ finding.broadMentions = result.files;
15660
+ } else {
15661
+ degradedFlags.push(`ripgrep mention search failed for '${sym.symbol}': ${result.error}`);
15662
+ }
15663
+ }
15664
+ findings.push(finding);
15665
+ }
15666
+ const report = {
15667
+ mode: "lightweight",
15668
+ summary: {
15669
+ symbolsAnalyzed: findings.map((f) => f.file ? `${f.file}:${f.symbol}` : f.symbol),
15670
+ truncated,
15671
+ toolsUsed: Array.from(toolsUsed),
15672
+ degradedFlags
15673
+ },
15674
+ findings
15675
+ };
15676
+ return { ok: true, report };
15677
+ }
15678
+ function formatRegressionCheckJson(report) {
15679
+ return JSON.stringify(
15680
+ {
15681
+ mode: report.mode,
15682
+ summary: {
15683
+ symbols_analyzed: report.summary.symbolsAnalyzed,
15684
+ truncated: report.summary.truncated,
15685
+ tools_used: report.summary.toolsUsed,
15686
+ degraded_flags: report.summary.degradedFlags
15687
+ },
15688
+ findings: report.findings.map((f) => ({
15689
+ symbol: f.symbol,
15690
+ file: f.file,
15691
+ language: f.language,
15692
+ definition_location: f.definitionLocation,
15693
+ call_sites: f.callSites ? { count: f.callSites.count, by_file: f.callSites.byFile } : null,
15694
+ broad_mentions: f.broadMentions
15695
+ }))
15696
+ },
15697
+ null,
15698
+ 2
15699
+ );
15700
+ }
15701
+ function formatRegressionCheckReport(report) {
15702
+ const lines = [
15703
+ `regression-check report (${report.mode} mode)`,
15704
+ "",
15705
+ `Symbols analyzed: ${report.summary.symbolsAnalyzed.length}${report.summary.truncated ? " [TRUNCATED]" : ""}`,
15706
+ `Tools used: ${report.summary.toolsUsed.length > 0 ? report.summary.toolsUsed.join(", ") : "none"}`,
15707
+ ""
15708
+ ];
15709
+ if (report.findings.length === 0) {
15710
+ lines.push("No changed symbols found.");
15711
+ }
15712
+ for (const f of report.findings) {
15713
+ const loc = f.definitionLocation ? `${f.definitionLocation.file}:${f.definitionLocation.line}` : "(unknown location)";
15714
+ lines.push(`${f.file ?? "(explicit)"}:${f.symbol} (def @ ${loc})`);
15715
+ if (f.callSites) {
15716
+ const byFile = Object.entries(f.callSites.byFile).map(([file, n]) => `${file}: ${n}`).join(", ");
15717
+ lines.push(` real call-sites: ${f.callSites.count}${byFile ? ` (${byFile})` : ""}`);
15718
+ } else {
15719
+ lines.push(" real call-sites: [DEGRADED] ast-grep unavailable or failed");
15720
+ }
15721
+ if (f.broadMentions) {
15722
+ const list = f.broadMentions.length > 0 ? ` (${f.broadMentions.join(", ")})` : "";
15723
+ lines.push(` broad mentions: ${f.broadMentions.length} file(s)${list}`);
15724
+ } else {
15725
+ lines.push(" broad mentions: [DEGRADED] ripgrep unavailable or failed");
15726
+ }
15727
+ lines.push("");
15728
+ }
15729
+ if (report.summary.degradedFlags.length > 0) {
15730
+ lines.push(`[DEGRADED] ${report.summary.degradedFlags.length} issue(s):`);
15731
+ for (const flag of report.summary.degradedFlags) lines.push(` - ${flag}`);
15732
+ } else {
15733
+ lines.push("No degradation \u2014 all tools ran successfully.");
15734
+ }
15735
+ return lines.join("\n");
15736
+ }
15737
+ function parseLizardCsvLine(line) {
15738
+ const fields = [];
15739
+ let cur = "";
15740
+ let inQuotes = false;
15741
+ for (const ch of line) {
15742
+ if (ch === '"') {
15743
+ inQuotes = !inQuotes;
15744
+ continue;
15745
+ }
15746
+ if (ch === "," && !inQuotes) {
15747
+ fields.push(cur);
15748
+ cur = "";
15749
+ continue;
15750
+ }
15751
+ cur += ch;
15752
+ }
15753
+ fields.push(cur);
15754
+ return fields;
15755
+ }
15756
+ function parseLizardCsv(csv) {
15757
+ const findings = [];
15758
+ for (const rawLine of csv.split("\n")) {
15759
+ const line = rawLine.trim();
15760
+ if (!line) continue;
15761
+ const fields = parseLizardCsvLine(line);
15762
+ if (fields.length < 11) continue;
15763
+ const [nloc, ccn, , , , , file, functionName, , startLine, endLine] = fields;
15764
+ const ccnNum = Number(ccn);
15765
+ const nlocNum = Number(nloc);
15766
+ const startLineNum = Number(startLine);
15767
+ const endLineNum = Number(endLine);
15768
+ if (![ccnNum, nlocNum, startLineNum, endLineNum].every(Number.isFinite)) continue;
15769
+ findings.push({
15770
+ functionName,
15771
+ file,
15772
+ ccn: ccnNum,
15773
+ nloc: nlocNum,
15774
+ startLine: startLineNum,
15775
+ endLine: endLineNum
15776
+ });
15777
+ }
15778
+ return findings;
15779
+ }
15780
+ async function analyzeComplexityWithLizard(files, deps) {
15781
+ if (files.length === 0) return { ok: true, findings: [] };
15782
+ const found = await isCommandOnPath(deps, "lizard");
15783
+ if (!found) return { ok: false, error: "lizard not found on PATH" };
15784
+ const result = await deps.runCommand("lizard", ["--csv", ...files], { cwd: deps.cwd });
15785
+ if (result.exitCode !== 0 && result.stdout.trim().length === 0) {
15786
+ return { ok: false, error: redactSecrets2(result.stderr.trim() || `lizard exited ${result.exitCode}`) };
15787
+ }
15788
+ return { ok: true, findings: parseLizardCsv(result.stdout) };
15789
+ }
15790
+ var TEMPORAL_COUPLING_SOURCE_EXT = /\.(py|ts|tsx)$/;
15791
+ var MIN_CO_OCCURRENCES = 4;
15792
+ var MIN_FILE_FREQUENCY = 5;
15793
+ var MIN_COUPLING_DEGREE = 0.5;
15794
+ var MAX_FILES_PER_COMMIT = 15;
15795
+ var MAX_PAIRS_RETURNED = 12;
15796
+ function computeTemporalCoupling(gitLogOutput, scopeFiles = []) {
15797
+ const scopeSet = scopeFiles.length > 0 ? new Set(scopeFiles) : null;
15798
+ const commits = [];
15799
+ let current = [];
15800
+ for (const line of gitLogOutput.split("\n")) {
15801
+ if (line.startsWith("@")) {
15802
+ if (current.length) commits.push(current);
15803
+ current = [];
15804
+ } else if (line.trim() && TEMPORAL_COUPLING_SOURCE_EXT.test(line.trim())) {
15805
+ current.push(line.trim());
15806
+ }
15807
+ }
15808
+ if (current.length) commits.push(current);
15809
+ const fileFreq = /* @__PURE__ */ new Map();
15810
+ const pairFreq = /* @__PURE__ */ new Map();
15811
+ for (const files of commits) {
15812
+ const uniq = Array.from(new Set(files)).filter((f) => !f.toLowerCase().includes("test"));
15813
+ if (uniq.length === 0 || uniq.length > MAX_FILES_PER_COMMIT) continue;
15814
+ if (scopeSet && !uniq.some((f) => scopeSet.has(f))) continue;
15815
+ for (const f of uniq) fileFreq.set(f, (fileFreq.get(f) ?? 0) + 1);
15816
+ const sorted = uniq.slice().sort();
15817
+ for (let i = 0; i < sorted.length; i++) {
15818
+ for (let j = i + 1; j < sorted.length; j++) {
15819
+ const key = `${sorted[i]}\0${sorted[j]}`;
15820
+ pairFreq.set(key, (pairFreq.get(key) ?? 0) + 1);
15821
+ }
15822
+ }
15823
+ }
15824
+ const rows = [];
15825
+ for (const [key, n] of pairFreq) {
15826
+ if (n < MIN_CO_OCCURRENCES) continue;
15827
+ const [a, b] = key.split("\0");
15828
+ const fa = fileFreq.get(a) ?? 0;
15829
+ const fb = fileFreq.get(b) ?? 0;
15830
+ const deg = n / Math.min(fa, fb);
15831
+ if (deg >= MIN_COUPLING_DEGREE && fa >= MIN_FILE_FREQUENCY && fb >= MIN_FILE_FREQUENCY) {
15832
+ rows.push({ fileA: a, fileB: b, coOccurrences: n, couplingDegree: deg });
15833
+ }
15834
+ }
15835
+ rows.sort((x, y) => y.couplingDegree - x.couplingDegree);
15836
+ return rows.slice(0, MAX_PAIRS_RETURNED);
15837
+ }
15838
+ async function analyzeTemporalCoupling(files, deps, options = {}) {
15839
+ const commitLimit = options.commitLimit ?? 800;
15840
+ const result = await deps.runCommand(
15841
+ "git",
15842
+ ["log", "--no-merges", "-n", String(commitLimit), "--name-only", "--pretty=format:@%H"],
15843
+ { cwd: deps.cwd }
15844
+ );
15845
+ if (result.exitCode !== 0) {
15846
+ return { ok: false, error: redactSecrets2(result.stderr.trim() || `git log exited ${result.exitCode}`) };
15847
+ }
15848
+ return { ok: true, pairs: computeTemporalCoupling(result.stdout, files) };
15849
+ }
15850
+ var HEAVY_CHURN_HOTSPOT_LIMIT = 30;
15851
+ var MAX_HEAVY_HOTSPOTS = 50;
15852
+ var MAX_HEAVY_SYMBOLS = MAX_SYMBOLS_PER_RUN;
15853
+ var CHURN_LOG_LIMIT = 500;
15854
+ async function analyzeChurnHotspots(deps, limit) {
15855
+ const result = await deps.runCommand(
15856
+ "git",
15857
+ ["log", "--no-merges", "-n", String(CHURN_LOG_LIMIT), "--name-only", "--pretty=format:"],
15858
+ { cwd: deps.cwd }
15859
+ );
15860
+ if (result.exitCode !== 0) {
15861
+ return { ok: false, error: redactSecrets2(result.stderr.trim() || `git log exited ${result.exitCode}`) };
15862
+ }
15863
+ const freq = /* @__PURE__ */ new Map();
15864
+ for (const rawLine of result.stdout.split("\n")) {
15865
+ const line = rawLine.trim();
15866
+ if (!line || !TEMPORAL_COUPLING_SOURCE_EXT.test(line)) continue;
15867
+ freq.set(line, (freq.get(line) ?? 0) + 1);
15868
+ }
15869
+ const sorted = Array.from(freq.entries()).sort((a, b) => b[1] - a[1]);
15870
+ return { ok: true, files: sorted.slice(0, limit).map(([file]) => file) };
15871
+ }
15872
+ async function resolveRepoRootDeps(deps) {
15873
+ const result = await deps.runCommand("git", ["rev-parse", "--show-toplevel"], { cwd: deps.cwd });
15874
+ const root = result.exitCode === 0 ? result.stdout.trim() : "";
15875
+ return root ? { ...deps, cwd: root } : deps;
15876
+ }
15877
+ async function runHeavyMode(deps) {
15878
+ const repoDeps = await resolveRepoRootDeps(deps);
15879
+ const degradedFlags = [];
15880
+ const toolsUsed = /* @__PURE__ */ new Set(["git"]);
15881
+ const churnResult = await analyzeChurnHotspots(repoDeps, HEAVY_CHURN_HOTSPOT_LIMIT);
15882
+ const churnFiles = churnResult.ok ? churnResult.files : [];
15883
+ if (!churnResult.ok) {
15884
+ degradedFlags.push(`git churn analysis failed: ${churnResult.error}`);
15885
+ }
15886
+ const couplingResult = await analyzeTemporalCoupling([], repoDeps);
15887
+ const couplingPartners = /* @__PURE__ */ new Map();
15888
+ const couplingFiles = [];
15889
+ if (couplingResult.ok) {
15890
+ for (const pair of couplingResult.pairs) {
15891
+ couplingFiles.push(pair.fileA, pair.fileB);
15892
+ couplingPartners.set(pair.fileA, (couplingPartners.get(pair.fileA) ?? 0) + 1);
15893
+ couplingPartners.set(pair.fileB, (couplingPartners.get(pair.fileB) ?? 0) + 1);
15894
+ }
15895
+ toolsUsed.add("git-log-temporal-coupling");
15896
+ } else {
15897
+ degradedFlags.push(`temporal coupling analysis failed: ${couplingResult.error}`);
15898
+ }
15899
+ const candidateFilesAll = Array.from(/* @__PURE__ */ new Set([...churnFiles, ...couplingFiles]));
15900
+ const fileCapApplied = candidateFilesAll.length > MAX_HEAVY_HOTSPOTS;
15901
+ const candidateFiles = fileCapApplied ? candidateFilesAll.slice(0, MAX_HEAVY_HOTSPOTS) : candidateFilesAll;
15902
+ const complexityResult = await analyzeComplexityWithLizard(candidateFiles, repoDeps);
15903
+ let candidateSymbols;
15904
+ if (complexityResult.ok) {
15905
+ if (candidateFiles.length > 0) toolsUsed.add("lizard");
15906
+ candidateSymbols = complexityResult.findings.map((f) => ({
15907
+ file: f.file,
15908
+ line: f.startLine,
15909
+ symbol: f.functionName,
15910
+ complexityScore: f.ccn
15911
+ }));
15912
+ } else {
15913
+ degradedFlags.push(`lizard complexity analysis failed: ${complexityResult.error}`);
15914
+ degradedFlags.push(
15915
+ "ast-grep fan-in skipped for these hotspots \u2014 no symbol-discovery fallback exists without lizard"
15916
+ );
15917
+ candidateSymbols = candidateFiles.map((file) => ({ file, line: 1, symbol: null, complexityScore: null }));
15918
+ }
15919
+ const symbolCapApplied = candidateSymbols.length > MAX_HEAVY_SYMBOLS;
15920
+ if (symbolCapApplied) {
15921
+ candidateSymbols = candidateSymbols.slice().sort((a, b) => (b.complexityScore ?? 0) - (a.complexityScore ?? 0)).slice(0, MAX_HEAVY_SYMBOLS);
15922
+ }
15923
+ const astGrepBinary = await resolveFirstCommandOnPath(repoDeps, AST_GREP_CANDIDATES);
15924
+ if (!astGrepBinary && candidateSymbols.some((s) => s.symbol)) {
15925
+ degradedFlags.push("ast-grep (or sg) not found on PATH \u2014 fan-in analysis skipped for all symbols.");
15926
+ }
15927
+ const findings = [];
15928
+ for (const candidate of candidateSymbols) {
15929
+ let fanInCount = null;
15930
+ if (astGrepBinary && candidate.symbol) {
15931
+ const language = languageForFile(candidate.file);
15932
+ const result = await findCallSites(repoDeps, astGrepBinary, candidate.symbol, language);
15933
+ if (result.ok) {
15934
+ fanInCount = result.count;
15935
+ toolsUsed.add("ast-grep");
15936
+ } else {
15937
+ degradedFlags.push(`ast-grep call-site search failed for '${candidate.symbol}': ${result.error}`);
15938
+ }
15939
+ }
15940
+ const couplingPartnerCount = couplingPartners.get(candidate.file) ?? 0;
15941
+ const fragilityRank = (fanInCount ?? 0) * 2 + couplingPartnerCount * 3 + (candidate.complexityScore ?? 0);
15942
+ findings.push({
15943
+ location: { file: candidate.file, line: candidate.line },
15944
+ signals: {
15945
+ fan_in_count: fanInCount,
15946
+ coupling_partners: couplingPartnerCount,
15947
+ complexity_score: candidate.complexityScore
15948
+ },
15949
+ fragility_rank: fragilityRank
15950
+ });
15951
+ }
15952
+ findings.sort((a, b) => b.fragility_rank - a.fragility_rank);
15953
+ return {
15954
+ mode: "heavy",
15955
+ summary: {
15956
+ hotspots_scanned: candidateFiles.length,
15957
+ cap_applied: fileCapApplied || symbolCapApplied,
15958
+ tools_used: Array.from(toolsUsed),
15959
+ degraded_flags: degradedFlags
15960
+ },
15961
+ findings
15962
+ };
15963
+ }
15964
+ function formatHeavyReportJson(report) {
15965
+ return JSON.stringify(report, null, 2);
15966
+ }
15967
+ function formatHeavyReportText(report) {
15968
+ const lines = [
15969
+ "regression-check report (heavy mode)",
15970
+ "",
15971
+ `Hotspots scanned: ${report.summary.hotspots_scanned}${report.summary.cap_applied ? " [CAPPED]" : ""}`,
15972
+ `Tools used: ${report.summary.tools_used.length > 0 ? report.summary.tools_used.join(", ") : "none"}`,
15973
+ ""
15974
+ ];
15975
+ if (report.findings.length === 0) {
15976
+ lines.push("No fragility findings.");
15977
+ }
15978
+ for (const f of report.findings) {
15979
+ lines.push(`${f.location.file}:${f.location.line} fragility_rank=${f.fragility_rank}`);
15980
+ const fanIn = f.signals.fan_in_count === null ? "[DEGRADED]" : String(f.signals.fan_in_count);
15981
+ const complexity = f.signals.complexity_score === null ? "[DEGRADED]" : String(f.signals.complexity_score);
15982
+ lines.push(` fan-in: ${fanIn} coupling partners: ${f.signals.coupling_partners} complexity: ${complexity}`);
15983
+ }
15984
+ lines.push("");
15985
+ if (report.summary.degraded_flags.length > 0) {
15986
+ lines.push(`[DEGRADED] ${report.summary.degraded_flags.length} issue(s):`);
15987
+ for (const flag of report.summary.degraded_flags) lines.push(` - ${flag}`);
15988
+ } else {
15989
+ lines.push("No degradation \u2014 all tools ran successfully.");
15990
+ }
15991
+ return lines.join("\n");
15992
+ }
15993
+ async function runRegressionCheckCli(argv, overrides = {}) {
15994
+ const log = overrides.log ?? ((m) => console.log(m));
15995
+ const errorLog = overrides.errorLog ?? ((m) => console.error(m));
15996
+ const parsed = parseRegressionCheckArgs(argv);
15997
+ if (parsed.status === "help") {
15998
+ log(parsed.usage);
15999
+ return 0;
16000
+ }
16001
+ if (parsed.status === "error") {
16002
+ errorLog(`Error: ${parsed.message}`);
16003
+ errorLog("");
16004
+ errorLog(getRegressionCheckUsage());
16005
+ return 1;
16006
+ }
16007
+ const deps = overrides.deps ?? createDefaultStartTicketsDeps();
16008
+ const { options } = parsed;
16009
+ if (options.mode === "heavy") {
16010
+ const report = await runHeavyMode(deps);
16011
+ log(options.json ? formatHeavyReportJson(report) : formatHeavyReportText(report));
16012
+ return 0;
16013
+ }
16014
+ const result = await runLightweightRegressionCheck(deps, options);
16015
+ if (!result.ok) {
16016
+ errorLog(`Error: ${result.error}`);
16017
+ return 1;
16018
+ }
16019
+ log(options.json ? formatRegressionCheckJson(result.report) : formatRegressionCheckReport(result.report));
16020
+ return 0;
16021
+ }
16022
+
15256
16023
  // src/install-bridge.ts
15257
16024
  import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir5, stat as stat6, rename, chmod, unlink } from "fs/promises";
15258
16025
  import { spawn as spawn3 } from "child_process";
@@ -18132,7 +18899,8 @@ function withSfccGate(deps, handler) {
18132
18899
  `Repo version '${version}' is not an SFCC version. Expected one of: ${SFCC_VERSIONS.join(", ")}. Update the version field in your Bridge API project settings.`
18133
18900
  );
18134
18901
  }
18135
- const explicitHostname = typeof args.instance === "string" ? args.instance : void 0;
18902
+ const rawInstance = typeof args.instance === "string" ? args.instance : void 0;
18903
+ const explicitHostname = rawInstance && rawInstance.includes(".") ? rawInstance : void 0;
18136
18904
  const credResult = await resolveSfccCredentials(explicitHostname);
18137
18905
  if (!credResult.ok) {
18138
18906
  return notConfigured(
@@ -18193,7 +18961,19 @@ var OCAPI_SETTINGS_READ_ONLY = (ocapiVersion) => JSON.stringify(
18193
18961
  },
18194
18962
  {
18195
18963
  resource_id: "/system_object_definitions/**",
18196
- methods: ["get"],
18964
+ methods: ["get", "post"],
18965
+ read_attributes: "(**)",
18966
+ write_attributes: "(**)"
18967
+ },
18968
+ {
18969
+ resource_id: "/site_preferences/**",
18970
+ methods: ["get", "post"],
18971
+ read_attributes: "(**)",
18972
+ write_attributes: "(**)"
18973
+ },
18974
+ {
18975
+ resource_id: "/custom_object_definitions/**",
18976
+ methods: ["get", "post"],
18197
18977
  read_attributes: "(**)",
18198
18978
  write_attributes: "(**)"
18199
18979
  }
@@ -18488,15 +19268,22 @@ var READ_ANNOTATIONS2 = {
18488
19268
  idempotentHint: true,
18489
19269
  openWorldHint: true
18490
19270
  };
18491
- var customObjectListInput = z4.object({
18492
- count: z4.number().optional().describe("Maximum number of custom object types to return."),
18493
- start: z4.number().optional().describe("Zero-based offset for paging.")
18494
- });
18495
- var customObjectGetInput = z4.object({
19271
+ var customObjectAttributesGetInput = z4.object({
18496
19272
  object_type: z4.string().describe(
18497
- 'Custom object type identifier, e.g. "GiftCertificate" or a custom type id starting with "c_".'
19273
+ 'Known custom object type identifier, e.g. a custom type id starting with "c_". OCAPI cannot enumerate custom object types \u2014 the type must already be known.'
18498
19274
  )
18499
19275
  });
19276
+ var customObjectAttributeSearchInput = z4.object({
19277
+ object_type: z4.string().describe(
19278
+ 'Known custom object type to search within, e.g. a custom type id starting with "c_". OCAPI cannot enumerate custom object types \u2014 the type must already be known.'
19279
+ ),
19280
+ query: z4.union([z4.string(), z4.record(z4.string(), z4.any())]).describe(
19281
+ "Search query. Pass a plain string for a text search across id and display_name, or a structured OCAPI query object (term_query, filtered_query, etc.)."
19282
+ ),
19283
+ start: z4.number().optional().describe("Zero-based offset for paging."),
19284
+ count: z4.number().optional().describe("Maximum number of results to return."),
19285
+ sorts: z4.array(z4.any()).optional().describe("Array of OCAPI sort descriptors.")
19286
+ });
18500
19287
  function safeTimestamp2() {
18501
19288
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
18502
19289
  }
@@ -18510,16 +19297,16 @@ async function saveAndReturn2(text, dir, filename) {
18510
19297
  const output = await truncateAndSaveIfNeeded(text, dir, filename);
18511
19298
  return textResult2(output);
18512
19299
  }
18513
- function buildCustomObjectListHandler(gateDeps, getDocsDir2) {
19300
+ function buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir2) {
18514
19301
  return withSfccGate(
18515
19302
  gateDeps,
18516
19303
  async (args, credentials) => {
18517
- const { count, start } = customObjectListInput.parse(args);
18518
- const queryParams = {};
18519
- if (count !== void 0) queryParams.count = String(count);
18520
- if (start !== void 0) queryParams.start = String(start);
18521
- const queryStr = Object.keys(queryParams).length > 0 ? "?" + new URLSearchParams(queryParams).toString() : "";
18522
- const result = await ocapiGet(`/custom_object_definitions${queryStr}`, credentials);
19304
+ const { object_type } = customObjectAttributesGetInput.parse(args);
19305
+ const encodedType = encodeURIComponent(object_type);
19306
+ const result = await ocapiGet(
19307
+ `/custom_object_definitions/${encodedType}/attribute_definitions`,
19308
+ credentials
19309
+ );
18523
19310
  if (!result.ok) {
18524
19311
  return textResult2(
18525
19312
  JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2)
@@ -18528,17 +19315,30 @@ function buildCustomObjectListHandler(gateDeps, getDocsDir2) {
18528
19315
  const normalized = normalizeOcapiBody(result.body);
18529
19316
  const text = JSON.stringify(normalized, null, 2);
18530
19317
  const dir = path22.join(await getDocsDir2(), "sfcc");
18531
- return saveAndReturn2(text, dir, `custom-object-def-list-${safeTimestamp2()}.json`);
19318
+ return saveAndReturn2(
19319
+ text,
19320
+ dir,
19321
+ `custom-object-def-attributes-${safeType2(object_type)}-${safeTimestamp2()}.json`
19322
+ );
18532
19323
  }
18533
19324
  );
18534
19325
  }
18535
- function buildCustomObjectGetHandler(gateDeps, getDocsDir2) {
19326
+ function buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir2) {
18536
19327
  return withSfccGate(
18537
19328
  gateDeps,
18538
19329
  async (args, credentials) => {
18539
- const { object_type } = customObjectGetInput.parse(args);
19330
+ const { object_type, query, start, count, sorts } = customObjectAttributeSearchInput.parse(args);
18540
19331
  const encodedType = encodeURIComponent(object_type);
18541
- const result = await ocapiGet(`/custom_object_definitions/${encodedType}`, credentials);
19332
+ const resolvedQuery = typeof query === "string" ? { text_query: { fields: ["id", "display_name"], search_phrase: query } } : query;
19333
+ const postBody = { query: resolvedQuery };
19334
+ if (start !== void 0) postBody.start = start;
19335
+ if (count !== void 0) postBody.count = count;
19336
+ if (sorts !== void 0) postBody.sorts = sorts;
19337
+ const result = await ocapiPost(
19338
+ `/custom_object_definitions/${encodedType}/attribute_definition_search`,
19339
+ postBody,
19340
+ credentials
19341
+ );
18542
19342
  if (!result.ok) {
18543
19343
  return textResult2(
18544
19344
  JSON.stringify({ error: "OCAPI error", status: result.status, body: result.body }, null, 2)
@@ -18550,7 +19350,7 @@ function buildCustomObjectGetHandler(gateDeps, getDocsDir2) {
18550
19350
  return saveAndReturn2(
18551
19351
  text,
18552
19352
  dir,
18553
- `custom-object-def-${safeType2(object_type)}-${safeTimestamp2()}.json`
19353
+ `custom-object-def-search-${safeType2(object_type)}-${safeTimestamp2()}.json`
18554
19354
  );
18555
19355
  }
18556
19356
  );
@@ -18558,22 +19358,22 @@ function buildCustomObjectGetHandler(gateDeps, getDocsDir2) {
18558
19358
  function registerSfccCustomObjectDefReadTools(registerTool2, deps) {
18559
19359
  const { gateDeps, getDocsDir: getDocsDir2 } = deps;
18560
19360
  registerTool2(
18561
- "custom_object_definition_list",
19361
+ "custom_object_definition_attributes_get",
18562
19362
  {
18563
- description: "List all custom object type definitions from the developer sandbox via GET /custom_object_definitions. Read-only introspection; type definitions cannot be created or updated via OCAPI (v2 metadata-import only). Optional `count` and `start` for paging. Oversized outputs auto-saved locally.",
18564
- inputSchema: customObjectListInput,
19363
+ description: "Retrieve attribute definitions for a KNOWN custom object type via GET /custom_object_definitions/{type}/attribute_definitions. OCAPI cannot enumerate types \u2014 object_type is required. Type creation is v2 metadata-import only, not OCAPI. Oversized payloads auto-saved locally.",
19364
+ inputSchema: customObjectAttributesGetInput,
18565
19365
  annotations: READ_ANNOTATIONS2
18566
19366
  },
18567
- buildCustomObjectListHandler(gateDeps, getDocsDir2)
19367
+ buildCustomObjectAttributesGetHandler(gateDeps, getDocsDir2)
18568
19368
  );
18569
19369
  registerTool2(
18570
- "custom_object_definition_get",
19370
+ "custom_object_definition_attribute_search",
18571
19371
  {
18572
- description: "Retrieve an EXISTING custom object type from the developer sandbox via GET /custom_object_definitions/{type}. Returns the type with key_definition and attribute_definitions/attribute_groups. Read-only introspection; type definitions cannot be created or updated via OCAPI (v2 metadata-import only). Oversized payloads auto-saved locally.",
18573
- inputSchema: customObjectGetInput,
19372
+ description: "Search attribute definitions within a KNOWN custom object type via POST /custom_object_definitions/{type}/attribute_definition_search. OCAPI cannot enumerate types \u2014 object_type is required. Pass a string or structured OCAPI query. Type creation is v2 metadata-import only. Oversized results auto-saved locally.",
19373
+ inputSchema: customObjectAttributeSearchInput,
18574
19374
  annotations: READ_ANNOTATIONS2
18575
19375
  },
18576
- buildCustomObjectGetHandler(gateDeps, getDocsDir2)
19376
+ buildCustomObjectAttributeSearchHandler(gateDeps, getDocsDir2)
18577
19377
  );
18578
19378
  }
18579
19379
 
@@ -22758,6 +23558,9 @@ async function dispatchCliSubcommand(argv) {
22758
23558
  if (argv[0] === "agent-capabilities") {
22759
23559
  return runAgentCapabilitiesCli(argv.slice(1));
22760
23560
  }
23561
+ if (argv[0] === "regression-check") {
23562
+ return runRegressionCheckCli(argv.slice(1));
23563
+ }
22761
23564
  if (argv[0] === "credentials") {
22762
23565
  return runCredentialsCli(argv.slice(1));
22763
23566
  }