@bridge_gpt/mcp-server 0.2.23 → 0.2.24
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/README.md +69 -17
- package/build/commands.generated.js +2 -2
- package/build/decision-page-template.js +9 -4
- package/build/docs.generated.js +5 -0
- package/build/index.js +762 -279
- package/build/init.js +29 -0
- package/build/install-bridge.js +480 -76
- package/build/readme.generated.js +1 -1
- package/build/sfcc/log-gate.js +85 -0
- package/build/sfcc/log-query.js +170 -0
- package/build/sfcc/register.js +10 -0
- package/build/sfcc/setup-status.js +33 -3
- package/build/version.generated.js +1 -1
- package/{CONDUCTOR.md → docs/CONDUCTOR.md} +2 -2
- package/docs/install/github-app.md +189 -0
- package/docs/install/mcp-tool-integrations.md +305 -0
- package/docs/install/sfcc-integration.md +140 -0
- package/package.json +5 -5
- package/public/js/main.min.js +46 -1
- package/public/js/main.min.js.map +1 -1
- package/smoke-test/SMOKE-TEST.md +3 -2
package/build/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var VERSION;
|
|
|
19
19
|
var init_version_generated = __esm({
|
|
20
20
|
"src/version.generated.ts"() {
|
|
21
21
|
"use strict";
|
|
22
|
-
VERSION = "0.2.
|
|
22
|
+
VERSION = "0.2.24";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -5236,9 +5236,9 @@ async function withRepoFetchLock(repoKey, fn) {
|
|
|
5236
5236
|
}
|
|
5237
5237
|
}
|
|
5238
5238
|
async function fetchAndResolveBaseSha(deps, baseBranch) {
|
|
5239
|
-
const
|
|
5240
|
-
if (
|
|
5241
|
-
return { ok: false, error: `Invalid base branch '${baseBranch}': ${
|
|
5239
|
+
const validationError2 = validateBranchName(baseBranch);
|
|
5240
|
+
if (validationError2) {
|
|
5241
|
+
return { ok: false, error: `Invalid base branch '${baseBranch}': ${validationError2}` };
|
|
5242
5242
|
}
|
|
5243
5243
|
const repoKey = normalizeRepoKey(deps.cwd);
|
|
5244
5244
|
return withRepoFetchLock(repoKey, async () => {
|
|
@@ -14389,7 +14389,7 @@ var init_supervisor_runtime = __esm({
|
|
|
14389
14389
|
// src/index.ts
|
|
14390
14390
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14391
14391
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14392
|
-
import { z as
|
|
14392
|
+
import { z as z16 } from "zod";
|
|
14393
14393
|
import { writeFile as writeFile12, mkdir as mkdir12, readFile as readFile13, stat as stat9, rename as rename3, chmod as chmod3, unlink as unlink3, mkdtemp as mkdtemp3, rm as rm3, readdir as readdir3, open as open2 } from "fs/promises";
|
|
14394
14394
|
import path33 from "path";
|
|
14395
14395
|
import os15 from "os";
|
|
@@ -15122,7 +15122,7 @@ var INSTRUCTIONS = {
|
|
|
15122
15122
|
init_version_generated();
|
|
15123
15123
|
|
|
15124
15124
|
// src/readme.generated.ts
|
|
15125
|
-
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\n**Were you sent a bootstrap invite?** Then you don\'t need an API key or the web UI\nat all \u2014 run the command your operator gave you:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@latest install-bridge --invite\n```\n\nThat one-liner is deliberately **secret-free**: the CLI prompts for the bootstrap\ninvite token with **echo suppressed**, and sends it only in the request body. It\ncreates your project and mints your own admin API key in a single command.\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 \u2014 **`--invite` is the one exception** (below). The key is\n **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\n#### Bootstrap-invite onboarding (`--invite`)\n\nWith a **bootstrap invite** there is no pre-existing key and no web UI: this is the\none mode where `install-bridge` **creates** the project and its first admin key\ninstead of consuming one. Run `install-bridge --invite` and it:\n\n1. **Prompts for the bootstrap invite token** with echo suppressed (the default \u2014\n see below).\n2. **Generates your `key_secret`** (32 CSPRNG bytes) and **fsyncs it locally**\n *before* contacting the server. If that write fails the run aborts and the\n invite is **not** spent.\n3. **Redeems the invite** \u2014 `POST /setup/bootstrap` with the token, repo name, and\n `key_secret` in the **body** \u2014 which creates the project and mints your admin\n key. (This replaces the connectivity ping: there is no key to ping with yet.)\n4. **Verifies the newly-minted key**, then writes the per-host MCP config.\n5. **Promotes the credential** to `bapi:<repo>` and **opens the agent session** \u2014\n the same Steps 3\u20135 as the normal flow.\n\nBecause the locally-saved `key_secret` is the only proof that can replay a\nredemption, a re-run after a network failure is safe: it re-sends the *same* secret\nand gets back the *same* project and key. If the repo name you chose is already\ntaken (names are globally unique) the server rolls back \u2014 your invite is untouched \u2014\nand the CLI asks for a different name and retries with the same token.\n\n**The delivered one-liner is secret-free, by design.** "A copy/paste one-liner" and\n"the token never touches shell history" are contradictory, so the token is *not* in\nthe command: the CLI asks for it, and it travels only in the request body.\n`--invite <token>`, `--invite=<token>`, and `BAPI_INVITE` still work for\n**scripting only** \u2014 and both forms **expose the token to your shell history and to\nthe process list**. Prefer the prompt.\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 With `--invite` it also never calls the exchange endpoint and never generates or\n stores a secret.\n- `--force` \u2014 overwrite an existing real `BAPI_API_KEY` in a host config, or in the\n credential store, without 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- `--invite [token]` \u2014 redeem a bootstrap invite (mutually exclusive with\n `--api-key`). Omit the value to get the hidden prompt.\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 plus an automatic alternate-model second opinion, then evaluates findings and produces a decision page to accept/reject them. The backend executor now owns all review round orchestration (including the second-opinion round) server-side; pass `--rounds=1` for a cheaper single-pass review, `--rounds=2` to force the full second-opinion review, or omit `--rounds` to let the backend\'s difficulty-adaptive review policy decide.\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` request a cheaper single-pass review (no second-opinion round) while preserving downstream evaluation and decision-capture work \xB7 `--rounds=2` force the full second-opinion review \xB7 omit `--rounds` to let the backend\'s difficulty-adaptive review policy decide (it falls back to a full second-opinion review when adaptive routing is disabled or the ticket\'s difficulty cannot be resolved).\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 \xB7 `--workflow implement|review-and-implement` selects which slash command each spawned worktree runs (default `implement`, byte-identical to today; `review-and-implement` runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` in the same session) \xB7 `--rounds=1|2` review-only, forwarded to the review phase, valid only with `--workflow review-and-implement`.\n\n**2b. Review and Start**\n- **What it does:** Spawns one worktree per ticket, each running review then (after a per-ticket human proceed/halt gate) implementation \u2014 the chained `review \u2192 gate \u2192 implement` composition.\n- **When it\'s useful:** (Refinement | Implementation | Automation) The **recommended front door** for "review these tickets, then implement the ones that pass," starting from existing ticket keys (unlike `/full-automation`, which only accepts an idea).\n- **How to use it:** `/review-and-start BAPI-248 BAPI-250` (single or multiple keys flow through the identical code path).\n- **Flags:** `--auto` a single chain-level flag that auto-approves both the review and the implementation phase of every spawned session \xB7 `--rounds=1|2` forwarded to the review phase \xB7 `--agent`, `--base-branch`, `--max-parallel`, `--dry-run` mirror `/start-tickets`.\n- Under the hood, this is a thin shim over `start-tickets --workflow review-and-implement <KEYS>` \u2014 the lower-level launcher seam documented above; the halt-gate decision logic lives in the spawned `/review-and-implement` session, never in this command or the CLI.\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- `custom_object_definition_attribute_create` \u2014 **write** (sandbox only): create an attribute definition on a known custom object type via `PUT /custom_object_definitions/{type}/attribute_definitions/{id}`. TYPE creation is never attempted (the type must pre-exist). Echoes paste-ready OCAPI grant JSON on 403.\n- `custom_object_definition_attribute_update` \u2014 **write** (sandbox only): update an attribute definition via an ETag-conditional `PATCH \u2026/attribute_definitions/{id}`; surfaces 409/412 conflicts and echoes grant JSON on 403.\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- `site_preference_values_set` \u2014 **write** (sandbox only): set custom preference values via `PATCH /site_preferences/preference_groups/{group}/sandbox` with a flat map of `c_`-prefixed ids to string/number/boolean/string[] values. A bad group returns 404 `CustomPreferenceGroupNotFoundException`; echoes grant JSON on 403.\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`. For existing ticket keys, `/review-and-start` (see [Usage Documentation \u2192 Review and Start](#tier-1--regularly-useful)) is the recommended enriched front door over this CLI\'s `--workflow review-and-implement` seam; using the CLI directly (below) remains the advanced/lower-level path.\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| `--workflow implement\\|review-and-implement` | `implement` | Slash command each spawned worktree runs. `implement` is byte-identical to today\'s `/implement-ticket <KEY> [--auto]`. `review-and-implement` spawns `/review-and-implement <KEY> [--auto] [--rounds=<n>]`, which runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` in the same session. `--auto` applies to the selected workflow as a whole. |\n| `--rounds 1\\|2` | unset | Review round count forwarded to the review phase. Review-only \u2014 valid only with `--workflow review-and-implement`. |\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### `setup-epic`\n\nBootstraps an Epic Conductor v2 run in one command \u2014 creates the epic run, stores the plan DAG, and approves it:\n\n```\nnpx -y @bridge_gpt/mcp-server setup-epic --epic-key <KEY> --plan-file <path-to-epic-plan.dag.json>\n```\n\nThe plan sidecar is produced by the `decompose-epic` pipeline step. `setup-epic` validates it locally (unique ticket keys, resolvable dependency references, acyclicity) before sending anything, so a malformed plan fails legibly instead of as a bare HTTP 400. It is **idempotent**: re-running it on an epic that already has a live run reuses that run rather than minting a second one. `--dry-run` validates and previews the calls without mutating anything; `--json` emits a single machine-readable result object.\n\nOnce the plan is approved, the **server-side reconciler** picks the run up within ~30s. To execute claimed jobs on your machine, run `executor`:\n\n```\nnpx -y @bridge_gpt/mcp-server executor --repo <name>\n```\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 the v2 architecture (server-side reconciler + local executor), `setup-epic`, `conductor install-git-hooks`, the supervisor `done_gate_config` / `auto_merge_enabled` settings, and the observability stream \u2014 lives in **[CONDUCTOR.md](./CONDUCTOR.md)**.\n\n> The v1 `conductor epic-tick` command is **frozen** \u2014 it throws on every invocation. There is nothing to schedule locally, and `conductor doctor` flags any epic-tick schedule left over from an earlier release so you can cancel it.\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 **59 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`, `visual_diff` (deterministic pixel diff of a rendered URL vs a design comp)\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: clarifying questions + critique plus an automatic second-opinion pass, then evaluation and decision capture. The backend owns round orchestration; pass `--rounds=1` for a single-pass review or `--rounds=2` to force the full second-opinion review, or omit `--rounds` to let the backend decide adaptively. | `/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';
|
|
15125
|
+
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](./docs/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 that runs `/install-bridge` to derive and\napply the remaining config, presents a **capability report** (what you can use now\nand what you\'ll unlock), and closes by asking whether to index the repository. It\ndoes **not** automatically run `/learn-repository` or index without your consent \u2014\nboth remain available as separate steps. The only inputs are an **API key**\n(generate one on the Bridge API web UI **Security** page) and a **repo name** \u2014\neverything else is derived. Add `--dry-run` to preview every step without writing,\npinging, or spawning anything.\n\n**Were you sent a bootstrap invite?** Then you don\'t need an API key or the web UI\nat all \u2014 run the command your operator gave you:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@latest install-bridge --invite\n```\n\nThat one-liner is deliberately **secret-free**: the CLI prompts for the bootstrap\ninvite token with **echo suppressed**, and sends it only in the request body. It\ncreates your project and mints your own admin API key in a single command.\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 and apply\n the remaining config fields from your codebase, presents the capability report\n (Connected / Not yet connected / Tools you can use now / Tools you\'ll unlock /\n Recommended next step), and closes with one optional `[Y/n] Index repository\n now?` question. It does not chain `/learn-repository` and never indexes without\n consent; run `/learn-repository` and `/parse-repository` yourself when you want\n them.\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 \u2014 **`--invite` is the one exception** (below). The key is\n **never printed or logged**.\n- **Repo name:** `--repo <name>` and `BAPI_REPO_NAME` remain the deterministic\n short-circuits and compatibility fallbacks \u2014 when either is set it is used\n directly, with no network round-trip. When **neither** is set, a compatible\n server resolves the unique repository from your existing API key automatically\n (a read-only lookup), so you don\'t have to supply it. If the server is older,\n the key can\'t be uniquely resolved, or the lookup fails, `install-bridge` falls\n back to an inferred default you confirm interactively (and requires `--repo`\n when stdin is non-interactive). Whatever name is used MUST match the\n server-side repository registration.\n\n#### Self-serve email onboarding (`--email`) \u2014 no account, no key, no invite\n\nThe primary path for a **first-time user with nothing yet** \u2014 no Bridge account,\nno API key, and no pre-issued invite. Run:\n\n```bash\nnpx -y @bridge_gpt/mcp-server@latest install-bridge --email you@example.com\n```\n\n`install-bridge` requests a brand-new Bridge workspace for that email, receives an\ninvite token, and then feeds it into the **exact same** persist-before-exchange\nbootstrap protocol as `--invite` below \u2014 so the project is created and your first\nadmin key is minted in one command. The minted token is used internally and **never\nshown**.\n\nThe email may instead come from the `BAPI_SIGNUP_EMAIL` environment variable or a\n**visible** interactive prompt (email is not a secret, so it is echoed as you type \u2014\nunlike the API key and the invite token, which use a hidden prompt). It is still\n**never written to a log line**. No email verification is performed and no message\nis sent to the address \u2014 it only labels your new workspace. `--email` is mutually\nexclusive with `--api-key` and `--invite`.\n\n#### Bootstrap-invite onboarding (`--invite`)\n\nWith a **bootstrap invite** you were already given, there is no pre-existing key and\nno web UI: like `--email` above, this mode **creates** the project and its first\nadmin key instead of consuming one. Run `install-bridge --invite` and it:\n\n1. **Prompts for the bootstrap invite token** with echo suppressed (the default \u2014\n see below).\n2. **Generates your `key_secret`** (32 CSPRNG bytes) and **fsyncs it locally**\n *before* contacting the server. If that write fails the run aborts and the\n invite is **not** spent.\n3. **Redeems the invite** \u2014 `POST /setup/bootstrap` with the token, repo name, and\n `key_secret` in the **body** \u2014 which creates the project and mints your admin\n key. (This replaces the connectivity ping: there is no key to ping with yet.)\n4. **Verifies the newly-minted key**, then writes the per-host MCP config.\n5. **Promotes the credential** to `bapi:<repo>` and **opens the agent session** \u2014\n the same Steps 3\u20135 as the normal flow.\n\nBecause the locally-saved `key_secret` is the only proof that can replay a\nredemption, a re-run after a network failure is safe: it re-sends the *same* secret\nand gets back the *same* project and key. If the repo name you chose is already\ntaken (names are globally unique) the server rolls back \u2014 your invite is untouched \u2014\nand the CLI asks for a different name and retries with the same token.\n\n**The delivered one-liner is secret-free, by design.** "A copy/paste one-liner" and\n"the token never touches shell history" are contradictory, so the token is *not* in\nthe command: the CLI asks for it, and it travels only in the request body.\n`--invite <token>`, `--invite=<token>`, and `BAPI_INVITE` still work for\n**scripting only** \u2014 and both forms **expose the token to your shell history and to\nthe process list**. Prefer the prompt.\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 With `--invite` it also never calls the exchange endpoint and never generates or\n stores a secret.\n- `--force` \u2014 overwrite an existing real `BAPI_API_KEY` in a host config, or in the\n credential store, without 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- `--email <addr>` \u2014 self-serve signup: create a new workspace from just an email\n (mutually exclusive with `--api-key` and `--invite`). Falls back to\n `BAPI_SIGNUP_EMAIL`, then a visible prompt. Visible input, not a secret; still\n never logged.\n- `--invite [token]` \u2014 redeem a bootstrap invite (mutually exclusive with\n `--api-key` and `--email`). Omit the value to get the hidden prompt.\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 plus an automatic alternate-model second opinion, then evaluates findings and produces a decision page to accept/reject them. The backend executor now owns all review round orchestration (including the second-opinion round) server-side; pass `--rounds=1` for a cheaper single-pass review, `--rounds=2` to force the full second-opinion review, or omit `--rounds` to let the backend\'s difficulty-adaptive review policy decide.\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` request a cheaper single-pass review (no second-opinion round) while preserving downstream evaluation and decision-capture work \xB7 `--rounds=2` force the full second-opinion review \xB7 omit `--rounds` to let the backend\'s difficulty-adaptive review policy decide (it falls back to a full second-opinion review when adaptive routing is disabled or the ticket\'s difficulty cannot be resolved).\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 \xB7 `--workflow implement|review-and-implement` selects which slash command each spawned worktree runs (default `implement`, byte-identical to today; `review-and-implement` runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` in the same session) \xB7 `--rounds=1|2` review-only, forwarded to the review phase, valid only with `--workflow review-and-implement`.\n\n**2b. Review and Start**\n- **What it does:** Spawns one worktree per ticket, each running review then (after a per-ticket human proceed/halt gate) implementation \u2014 the chained `review \u2192 gate \u2192 implement` composition.\n- **When it\'s useful:** (Refinement | Implementation | Automation) The **recommended front door** for "review these tickets, then implement the ones that pass," starting from existing ticket keys (unlike `/full-automation`, which only accepts an idea).\n- **How to use it:** `/review-and-start BAPI-248 BAPI-250` (single or multiple keys flow through the identical code path).\n- **Flags:** `--auto` a single chain-level flag that auto-approves both the review and the implementation phase of every spawned session \xB7 `--rounds=1|2` forwarded to the review phase \xB7 `--agent`, `--base-branch`, `--max-parallel`, `--dry-run` mirror `/start-tickets`.\n- Under the hood, this is a thin shim over `start-tickets --workflow review-and-implement <KEYS>` \u2014 the lower-level launcher seam documented above; the halt-gate decision logic lives in the spawned `/review-and-implement` session, never in this command or the CLI.\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\nFor a step-by-step OCAPI client setup guide (including the Business Manager permissions grant), see [docs/install/sfcc-integration.md](./docs/install/sfcc-integration.md).\n\n<details>\n<summary><strong>Setup</strong></summary>\n\nThe two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always available. The read tools, the write tools, and `sfcc_log_query` must be enabled with a profile (step 3). Changing `BRIDGE_MCP_PROFILE` requires an MCP client restart.\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, AM (OCAPI) token acquisition, and the independent **SFCC Log Query (WebDAV)** capability that gates `sfcc_log_query`.\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- `custom_object_definition_attribute_create` \u2014 **write** (sandbox only): create an attribute definition on a known custom object type via `PUT /custom_object_definitions/{type}/attribute_definitions/{id}`. TYPE creation is never attempted (the type must pre-exist). Echoes paste-ready OCAPI grant JSON on 403.\n- `custom_object_definition_attribute_update` \u2014 **write** (sandbox only): update an attribute definition via an ETag-conditional `PATCH \u2026/attribute_definitions/{id}`; surfaces 409/412 conflicts and echoes grant JSON on 403.\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- `site_preference_values_set` \u2014 **write** (sandbox only): set custom preference values via `PATCH /site_preferences/preference_groups/{group}/sandbox` with a flat map of `c_`-prefixed ids to string/number/boolean/string[] values. A bad group returns 404 `CustomPreferenceGroupNotFoundException`; echoes grant JSON on 403.\n\n**On-demand log query** (needs the `sfcc` profile)\n- `sfcc_log_query` \u2014 query redacted, filtered SFCC logs on demand. `environment` and `time_range` (`start`/`end`) are **required** \u2014 production, "all environments", and an open-ended period are never inferred. The tool calls a Bridge backend endpoint that runs the pull \u2192 redaction \u2192 filter pipeline server-side and returns scoped, redacted findings; **WebDAV credentials, retrieval, redaction, and filtering all stay server-side and single-sourced.** It holds no credentials of its own.\n - **Guardrails.** Selection is bounded by log-file `prefixes` (max 5), the time range, a scanned-entry cap (`max_entries`, \u2264 2000), a finding cap, and a per-snippet length cap. High-volume prefix classes (`info`, `jobs`, `debug`, `customdebug`) impose a **stricter 6-hour** max range (vs. 24h for the error class) because `info-*` runs ~1 MB/day versus `error-*` at ~13 KB median \u2014 a wide window over a high-volume prefix is **rejected**, never silently narrowed.\n - **Response order.** Resolved scope (`environment`, `time_range`, `applied_prefixes`) and cap `status` first, redacted `findings` second, retrieval/truncation `metadata` last.\n - **Statuses & errors.** `ready`, `no_matching_findings`, `results_truncated`; plus `VALIDATION_ERROR` (bad/oversized scope, caught before any network call), `NOT_CONFIGURED` (503 \u2014 the log capability isn\'t set up; run `sfcc_setup_status`, whose step 6 reports it), and `BAD_GATEWAY`/`SERVICE_UNAVAILABLE` on a retrieval/backend failure.\n - **Auth is separate from OCAPI.** Log retrieval uses **HTTP Basic auth** \u2014 a Business Manager username + a **40-character WebDAV access key** \u2014 *not* the OCAPI Account Manager OAuth token the other SFCC tools use. A valid AM bearer token 401s on `/Logs`. `sfcc_setup_status` step 5 (AM/OCAPI token) and step 6 (WebDAV log access) are independent: one can be green while the other is not.\n - **Local / air-gapped fallback.** The primary path above is the only path this tool takes. For air-gapped development, the documented fallback is Salesforce\'s own **`@salesforce/b2c-dx-mcp`** (`logs_list_files`, configured from `dw.json`) \u2014 it is vendor-maintained and reads log files over WebDAV, so it is strictly less work than shelling the B2C CLI (`b2c logs get --since <window> --search <q> --json`). It is **not** the primary path because its credentials live client-side and its output has **not** passed Bridge\'s redaction/filter. If you use it, its output must be treated as raw: route it back through the same server-side Python `LogSource` composition and `RedactionPort`/T3 filter workflow \u2014 never paste or relay unredacted `b2c-dx-mcp` or CLI output to an LLM.\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`. For existing ticket keys, `/review-and-start` (see [Usage Documentation \u2192 Review and Start](#tier-1--regularly-useful)) is the recommended enriched front door over this CLI\'s `--workflow review-and-implement` seam; using the CLI directly (below) remains the advanced/lower-level path.\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| `--workflow implement\\|review-and-implement` | `implement` | Slash command each spawned worktree runs. `implement` is byte-identical to today\'s `/implement-ticket <KEY> [--auto]`. `review-and-implement` spawns `/review-and-implement <KEY> [--auto] [--rounds=<n>]`, which runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` in the same session. `--auto` applies to the selected workflow as a whole. |\n| `--rounds 1\\|2` | unset | Review round count forwarded to the review phase. Review-only \u2014 valid only with `--workflow review-and-implement`. |\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](./docs/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### `setup-epic`\n\nBootstraps an Epic Conductor v2 run in one command \u2014 creates the epic run, stores the plan DAG, and approves it:\n\n```\nnpx -y @bridge_gpt/mcp-server setup-epic --epic-key <KEY> --plan-file <path-to-epic-plan.dag.json>\n```\n\nThe plan sidecar is produced by the `decompose-epic` pipeline step. `setup-epic` validates it locally (unique ticket keys, resolvable dependency references, acyclicity) before sending anything, so a malformed plan fails legibly instead of as a bare HTTP 400. It is **idempotent**: re-running it on an epic that already has a live run reuses that run rather than minting a second one. `--dry-run` validates and previews the calls without mutating anything; `--json` emits a single machine-readable result object.\n\nOnce the plan is approved, the **server-side reconciler** picks the run up within ~30s. To execute claimed jobs on your machine, run `executor`:\n\n```\nnpx -y @bridge_gpt/mcp-server executor --repo <name>\n```\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 the v2 architecture (server-side reconciler + local executor), `setup-epic`, `conductor install-git-hooks`, the supervisor `done_gate_config` / `auto_merge_enabled` settings, and the observability stream \u2014 lives in **[CONDUCTOR.md](./docs/CONDUCTOR.md)**.\n\n> The v1 `conductor epic-tick` command is **frozen** \u2014 it throws on every invocation. There is nothing to schedule locally, and `conductor doctor` flags any epic-tick schedule left over from an earlier release so you can cancel it.\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 **59 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`, `visual_diff` (deterministic pixel diff of a rendered URL vs a design comp)\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: clarifying questions + critique plus an automatic second-opinion pass, then evaluation and decision capture. The backend owns round orchestration; pass `--rounds=1` for a single-pass review or `--rounds=2` to force the full second-opinion review, or omit `--rounds` to let the backend decide adaptively. | `/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';
|
|
15126
15126
|
|
|
15127
15127
|
// src/update-check.ts
|
|
15128
15128
|
init_version_generated();
|
|
@@ -15468,7 +15468,7 @@ var COMMANDS = {
|
|
|
15468
15468
|
"full-automation.md": '---\nschedulable: true\narguments: {"positionals":[],"flags":[{"name":"ideaFile","flag":"--idea-file","type":"string","required":true},{"name":"auto","flag":"--auto","type":"boolean"}]}\n---\n\nRun the end-to-end full-automation chain (idea-to-ticket \u2192 review-ticket \u2192 start-tickets) via the server-side chain orchestrator.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command drives Phase A\'s server-side full-automation chain. The only orchestration tools you may drive are `run_full_automation` and `resume_full_automation`; any other Bridge API MCP call you make must be one a server `agent_task` instruction explicitly directs. The server owns all orchestration \u2014 ticket creation, review fan-out, and the start-tickets handoff. Do NOT enrich, re-implement, or second-guess any of that work on the client side.\n\n## Stage 0 \u2014 Parse arguments\n\n1. Tokenize `$ARGUMENTS` on whitespace. Recognize the following position-independent flags. Each flag supports both the space form (`--flag value`) and the equals form (`--flag=value`) where a value is taken:\n - `--idea <text>` / `--idea=<text>`\n - `--idea-file <path>` / `--idea-file=<path>`\n - `--auto`\n - `--require-approval`\n - `--scheduled-at <ISO-8601>` / `--scheduled-at=<ISO-8601>`\n - `--chain-run-id <UUID>` / `--chain-run-id=<UUID>`\n - `--max-children N` / `--max-children=N`\n - `--allow-duplicate`\n\n2. Value-consumption rules:\n - `--idea` (space form) consumes every subsequent token until the next recognized flag \u2014 the idea may contain spaces.\n - `--idea-file`, `--scheduled-at`, `--chain-run-id`, and `--max-children` each consume exactly one value token (the immediately following token, or the text after `=`).\n - `--auto`, `--require-approval`, and `--allow-duplicate` are boolean toggles and consume no value.\n\n3. Free-form idea: all non-flag tokens become the free-form `idea` text **only when both `--idea` and `--idea-file` are absent**. Join those tokens back together preserving order and trim surrounding whitespace. When `--idea` or `--idea-file` is present, there must be no leftover non-flag tokens: reject any stray non-flag token (for example, text following `--idea=<text>` or following the `--idea-file <path>` value) before any MCP tool call rather than silently dropping it.\n\n4. Reject **unknown flags** (any token beginning with `--` that is not one of the recognized flags above) before making any MCP tool call. Stop and report the offending flag.\n\n5. Reject **combined `--idea` and `--idea-file`** before making any MCP tool call:\n ```text\n Provide exactly one of --idea or --idea-file; do not pass both.\n ```\n\n6. Missing-input rule: unless `--chain-run-id` is present, an idea is required. If `--chain-run-id` is absent **and** no idea was supplied (no `--idea`, no `--idea-file`, and no free-form idea tokens), stop immediately and display exactly:\n ```text\n Usage: /full-automation (--idea "<text>" | --idea-file <path> | <free-form idea>) [--require-approval] [--scheduled-at <ISO-8601>] [--chain-run-id <UUID>] [--max-children N] [--allow-duplicate]\n ```\n\n7. `--chain-run-id` is the resume path and does **not** require any idea content \u2014 when it is present, skip the missing-input check above and proceed to resume.\n\n8. `--idea-file` is forwarded as a path. The skill must **not** read the file contents locally; the server resolves the file.\n\n9. Resolve the derived values:\n - `auto_approve` defaults to `true` (full automation is hands-off by default). It is `false` **only** when `--require-approval` is present. `--auto` is accepted but redundant (a no-op that restates the default), and `--scheduled-at` likewise runs hands-off. When `--require-approval` is present, the chain pauses at external-mutation and review-decision gates for confirmation.\n - `max_children` is the parsed positive integer when `--max-children` is present; otherwise omit it entirely so the server default applies.\n - `allow_duplicate` is `true` only when `--allow-duplicate` is present; otherwise omit it.\n\n## Stage 1 \u2014 Drift-check gate\n\nThis gate runs immediately after parsing and **before any MCP tool call**.\n\n1. If `--scheduled-at` is absent, skip this entire stage.\n2. Compute `delta_seconds = now_utc - scheduled_at` (both in UTC).\n3. If `delta_seconds <= 60`, proceed silently to Stage 2.\n4. If `delta_seconds > 60`, present this prompt verbatim (substituting the bracketed values):\n ```text\n Scheduled at <T-iso> UTC; running now at <now-iso> UTC (<\u0394 human-readable> late). The laptop was likely asleep or unavailable at the scheduled time. Confirm to proceed with the chain, or cancel.\n ```\n Offer the user the choices: `[Confirm] / [Cancel]`.\n5. On `Confirm`, proceed to Stage 2.\n6. On `Cancel`, print this message verbatim and stop:\n ```text\n Chain cancelled by user (drift confirmation declined). No Jira tickets created.\n ```\n When the user cancels, `run_full_automation` must **not** be called.\n7. The 60-second threshold is fixed and must not be made configurable.\n\n## Stage 2 \u2014 Run or resume the chain\n\nThe chain is driven entirely by the server-side orchestrator. Announce progress using each envelope\'s `preamble`, preserving its `Stage N of M \u2014 <title>` shape.\n\n### Stage 2a \u2014 Start (when `--chain-run-id` is absent)\n\nCall **only** `run_full_automation`. Build the payload, **omitting** any optional value that was not provided (never send `null` or empty strings):\n```json\n{\n "idea": "<resolved inline/free-form idea, when provided>",\n "idea_file": "<idea-file path, when provided>",\n "auto_approve": "<resolved boolean>",\n "scheduled_at": "<scheduled-at value, when provided>",\n "max_children": "<parsed integer, when provided>",\n "allow_duplicate": "<true, when provided>"\n}\n```\n\n### Stage 2b \u2014 Resume (when `--chain-run-id` is present)\n\nCall **only** `resume_full_automation` first, with:\n```json\n{\n "chain_run_id": "<UUID>",\n "agent_result": "Manual resume requested from /full-automation --chain-run-id."\n}\n```\n\n### Stage 2c \u2014 Envelope loop\n\nFor each envelope returned by `run_full_automation` / `resume_full_automation`, dispatch on `status` / `next_action.kind`:\n\n- `status: "failed"` \u2192 stop chain progression and render the final report (Stage 3) with the failure status. Do **not** advance to any later stage.\n- `status: "completed"` or `next_action.kind: "complete"` \u2192 render the final report (Stage 3).\n- `status: "needs_agent_task"` with `next_action.kind: "agent_task"` \u2192 display the envelope `preamble`, perform the agent task exactly as the `next_action.instruction` directs, then call `resume_full_automation` with `chain_run_id` set to the envelope\'s `chain_run_id` and `agent_result` set to the resulting text. Loop back and process the next envelope.\n\nSpecial case \u2014 the stage-3 handoff: when the agent-task instruction names a `/start-tickets ...` command, invoke that slash command in **this same session**, summarize the outcome in one line, and pass that one-line summary as `agent_result` to `resume_full_automation`.\n\nConstraints:\n- On your own initiative, the skill must **not** call any Bridge API MCP tool other than `run_full_automation` / `resume_full_automation` \u2014 in particular, never independently drive orchestration (`run_pipeline`, `resume_pipeline`, `get_pipeline_recipe`) or enrich tickets (`get_ticket`, `update_ticket_description`, etc.). **However, when a `needs_agent_task` instruction returned by the server explicitly directs you to call a specific Bridge API MCP tool** (for example an orchestrator-directed `get_tickets`, `create_ticket`, `attachment`, or `track_ticket`), **you must invoke that tool exactly as instructed** \u2014 performing an orchestrator-directed agent task is not re-orchestrating.\n- If a v1 envelope unexpectedly returns `next_action.kind: "mcp_call"`, stop with a clear protocol error instead of bypassing the server-side orchestrator:\n ```text\n Protocol error: chain returned next_action.kind "mcp_call", which is out of scope for /full-automation v1. Stopping.\n ```\n\n## Stage 3 \u2014 Final report\n\nWhen the chain completes or fails, render this skeleton verbatim:\n\n```markdown\n## Full Automation Complete\n\nChain run: <chain_run_id>\nIdea: <first 80 chars of idea>...\nStages:\n 1. idea-to-ticket: <stages[0].summary>\n 2. review-ticket: <stages[1].summary>\n 3. start-tickets: <stages[2].summary>\n\nTotal Jira tickets created: N\nTotal worktrees spawned: M\nStatus: Success / Failed at stage N \u2014 <reason>\n```\n\n- Stage summaries come from the chain envelope or manifest when present.\n- When the completed envelope does not include full stage objects, use the summaries already surfaced in the prior `preamble` text rather than calling additional tools.\n- A stage-1 `too_vague_to_ticket` failure must render the upstream halt reason and set `Status: Failed at stage 1 \u2014 <reason>`.\n- Failed chains must not advance to later stages after a failed envelope is received.\n',
|
|
15469
15469
|
"idea-to-ticket.md": 'Convert a short human idea into a Jira ticket (or Epic plus child tickets) via the server-side idea-to-ticket pipeline.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command is recipe-driven. Do not call MCP tools directly other than `get_pipeline_recipe` \u2014 the recipe determines which tools to call and with what parameters.\n\n## Stage 0 \u2014 Parse arguments\n\n1. Tokenize `$ARGUMENTS` on whitespace. Recognize the following position-independent flags (any other tokens are part of the idea):\n - `--allow-duplicate`\n - `--max-children=N` where `N` is a positive integer\n - `--auto`\n\n Flags are optional. Treat absence of a flag as the default; never pass empty-string or null as a placeholder.\n\n2. Everything that is not a recognized flag is the free-form `idea` text. Join those tokens back together preserving order. Trim surrounding whitespace.\n\n3. If the resulting `idea` is empty, stop immediately and display:\n ```\n Usage: /idea-to-ticket <idea> [--allow-duplicate] [--max-children=N] [--auto]\n ```\n\n## Stage 1 \u2014 Derive pipeline variables\n\n4. Derive `slug` from the first 6-8 meaningful words of the idea: lowercase, kebab-case, strip non-alphanumeric characters except hyphens, and truncate to roughly 60 characters. Skip stop-words such as "the", "a", "an" when picking the 6-8 meaningful words.\n\n5. Derive `run_id` as `<YYYYMMDD-HHMMSS>-<short-uuid>` using the current UTC time and a short UUID suffix (8 hex chars is enough). The combination of `slug` and `run_id` uniquely identifies this run\'s artifact directory.\n\n6. Derive the boolean-as-string variables:\n - `allow_duplicate` is `"true"` if `--allow-duplicate` was present, otherwise `"false"`.\n - `auto_approve_external` is `"true"` if `--auto` was present, otherwise `"false"`.\n - `max_children` is the integer following `--max-children=` as a string, or `"10"` when the flag is absent.\n\n## Stage 2 \u2014 Call the recipe\n\n7. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `"idea-to-ticket"`\n - `variables`: `{ "idea": "<idea>", "slug": "<slug>", "run_id": "<run_id>", "allow_duplicate": "<allow_duplicate>", "auto_approve_external": "<auto_approve_external>", "max_children": "<max_children>" }`\n\n Do NOT pass `docs_dir` or `idea_hash` in variables \u2014 both are auto-injected by the pipeline system (`docs_dir` from `BAPI_DOCS_DIR`; `idea_hash` is a stable hash derived from the `idea`).\n\n If the tool returns an error, stop and report the failure.\n\n8. 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\n The recipe drives the ordered stages for you \u2014 do not invoke them directly. In order they are: preflight-and-readiness \u2192 research-decision \u2192 execute-research \u2192 duplicate-and-context-scan \u2192 screen-and-resolve \u2192 frame-goals-and-nfrs \u2192 **comp-analysis** (a gated, backend-safe perception step that maps any attached/referenced design comp to existing components, templates, SCSS/CSS tokens, and routes before drafting; it short-circuits for backend-only or no-comp work) \u2192 draft-and-critique \u2192 upload-and-track.\n\n## Stage 3 \u2014 Final summary\n\n9. After all steps complete, display a summary:\n ```\n ## Pipeline Complete\n\n **Idea**: <first 80 characters of idea>...\n **Slug**: <slug>\n **Run directory**: <docs_dir>/idea-to-ticket/<slug>-<run_id>/\n **Steps executed**: N of M\n **Status**: Success / Failed at step N\n ```\n',
|
|
15470
15470
|
"implement-ticket.md": '# Implement 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\n## CONDUCTOR_MESSAGE_RELAY ##\n\nIf you were launched under the Conductor (your environment carries a conductor run/worker identity), cooperatively poll for supervisor guidance while you work: at natural checkpoints \u2014 after reading context, before major code changes, after major implementation chunks, while polling CI checks during the post-PR correction loop, and before your final response \u2014 call the `check_messages` MCP tool. The tool reads and acknowledges any messages addressed to you, and acknowledged messages are not redelivered, so a later call returns only new guidance. This is cooperative polling only \u2014 it is not prompt injection and never mutates a live session. If the tool or conductor identity is unavailable, continue the task without derailing.\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\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`.\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]\n Usage: /implement-ticket <ticket_key> [--auto]\n ```\n\n2. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `"implement-ticket"`\n - `variables`: `{ "ticket_key": "<ticket_key>" }`\n - `auto_approve`: `true` \u2014 only when `--auto` was passed; otherwise omit this field entirely.\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\n---\n\n# Worker scope discipline and clean exit (Conductor auto mode)\n\nThese rules apply only when you were launched under the Conductor in auto mode (`--auto`); a normal interactive `/implement-ticket` run is unaffected.\n\n## Declared file-scope discipline (N-2)\n\nIf your environment carries `BAPI_CONDUCTOR_DECLARED_TOUCHED_FILES_JSON` (a JSON array of the ticket\'s declared touched files), stay within that declared file boundary: do **not** create, modify, or delete files outside the declared set. Those out-of-scope files typically belong to a sibling ticket, and editing them risks a merge conflict or a silent clobber of the sibling\'s merged work. The pre-PR file-scope guard (run at the PR-creation step) will warn about any out-of-scope diff \u2014 treat that warning as a signal to re-check your scope, not as a blocker. When the variable is absent, empty, or invalid there is no declared boundary and this rule does not apply.\n\n## Clean session exit (D2)\n\nAfter the final pipeline step completes, cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers \u2014 but **only when no follow-up remains that you still own**. Do **not** exit while any of the following is true:\n\n- there are unresolved CI failures you are still correcting (the post-PR CI-correction loop in the CI-monitoring step still owns work),\n- review changes were requested and you have not yet addressed them,\n- there is a merge conflict on your PR that you still own,\n- you have unpushed local commits.\n\nExit only after your final branch state is pushed, the done-gate / CI-monitoring workflow required by the recipe has completed, and no CI/review follow-up remains. A clean `SessionEnd` is both the correct terminal lifecycle signal and the point at which the worker should exit.\n',
|
|
15471
|
-
"install-bridge.md": 'Bootstrap Bridge API project configuration from the local codebase via the easy-install manifest.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command takes no arguments; any `$ARGUMENTS` text is ignored.\n\nCommand contract version: **2**. The install manifest returns a `command_contract_version`; Stage 2\ncompares it to this number to detect a stale scaffolded copy of this command.\n\nThis command performs a one-time "easy install" of Bridge API project configuration. It reads the\nserver-owned install manifest once, derives values for the **unset** bootstrap fields from the local\ncodebase, asks for human approval of the project description, and applies everything in a single\natomic call. The server owns all skip-if-set, conflict, and confirmation semantics \u2014 this command\nnever makes its own skip-if-set decisions.\n\nThis command is portable across Claude Code, Cursor, and Copilot: it only drives the Bridge API MCP\ntools (`get_my_role`, `get_install_manifest`, `apply_install_manifest`).\n\nIf any stage fails, stop immediately and report which stage failed and why.\n\n## Stage 1 \u2014 Admin preflight\n\n1. Call the `get_my_role` MCP tool (no parameters). Inspect the response:\n - If `source` is `"legacy"`: proceed (legacy keys are permitted).\n - Else if `role` is `"admin"`: proceed.\n - Otherwise (a non-admin `user_access` key): stop immediately and display:\n ```\n Admin role required to apply install configuration. Ask a project admin to run /install-bridge,\n or use an admin API key.\n ```\n\n## Stage 2 \u2014 Read the manifest (once)\n\n1. Call the `get_install_manifest` MCP tool exactly once.\n2. Keep the returned `snapshot_token` verbatim \u2014 you must pass the SAME token to the apply call in\n Stage 5. Do not call `get_install_manifest` again before applying; a fresh read would invalidate\n the token you are about to use.\n3. The manifest contains ordered `groups` of bootstrap fields (each with `field_name`, `is_set`,\n `current_value`, `guidance`, `examples`, `validation_summary`, `requires_confirmation`), a list of\n `deferred_fields`, a `next_step`, `done_criteria`, a `command_contract_version`, and the\n `snapshot_token`.\n4. Compare the manifest\'s `command_contract_version` to this command\'s contract version (2, stated at\n the top of this file). If the manifest\'s version is HIGHER, this scaffolded command copy is stale:\n warn the user to refresh it by running `npx -y @bridge_gpt/mcp-server --init` in the project root,\n then proceed conservatively \u2014 wherever the manifest\'s `guidance`, `next_step`, or `done_criteria`\n differ from these instructions, follow the manifest.\n\n## Stage 3 \u2014 Derive values for UNSET bootstrap fields only\n\n1. Consider ONLY fields whose `is_set` is `false`. Never re-derive or overwrite a field that is\n already set \u2014 the server will also skip already-set fields, but you should not even propose them.\n2. For each unset field, derive a value from the local codebase following that field\'s `guidance`\n (e.g. infer `working_in` from frameworks/manifests, `version_control_system` from the git remote,\n testing stacks from test config and dependencies, directory fields from the repo layout).\n3. Omit any field you are uncertain about or that does not apply \u2014 leaving it unset is always\n acceptable. Do NOT guess.\n4. Specifically, do NOT invent a `version` when the platform has no meaningful version concept; leave\n it unset.\n5. Before deriving `version_control_system` or `base_branch`, confirm the git repository root is THIS\n project\'s root: `git rev-parse --show-toplevel` must resolve to the project directory itself. If\n the git root is an ancestor directory (the project is nested inside another repository), OMIT both\n fields and note why in the final report \u2014 deriving them would describe the parent repository, not\n this project.\n6. If you find real evidence for TWO OR MORE plausible values for a field (e.g. configs for multiple\n test frameworks, multiple frameworks in a monorepo), treat the value as ambiguous and omit it\n rather than picking one.\n7. For the "Automation policy" group (`selected_mcp_slugs`): propose MCP validation manuals only from\n clear platform markers, following the field\'s manifest guidance (e.g. SFCC cartridges \u2192\n `b2c-commerce-developer`; a Playwright config \u2192 `playwright-mcp`; PWA Kit markers \u2192\n `pwa-kit-mcp`). This field requires human confirmation (Stage 4). Omit it entirely when no manual\n clearly applies \u2014 never propose a slug on weak evidence.\n\n## Stage 4 \u2014 Human approval for confirmation-requiring fields\n\nSome manifest fields carry `requires_confirmation: true` (currently `project_description` and\n`selected_mcp_slugs`). These are never applied on derivation alone \u2014 each needs explicit human\napproval.\n\n1. If `project_description` is unset and you can draft a concise description from the codebase,\n present the proposed `project_description` text to the human and ask for explicit approval before\n including it.\n2. If you derived a `selected_mcp_slugs` list in Stage 3, present the proposed slugs and the platform\n evidence for each, and ask for approval in the SAME batched question round as the description.\n3. Include a confirmation-requiring field in the apply payload ONLY as\n `{ "value": <approved value>, "confirmed": true }`, and only after the human approves it. If the\n human does not approve a field, omit that field entirely.\n4. If no human response can be obtained (e.g. a non-interactive / headless session), do NOT stall and\n do NOT abandon the install: proceed to Stage 5 with every confirmation-requiring field omitted,\n and report them as "pending human input" in the final summary. The other derived fields must\n still be applied \u2014 unapproved fields never block them.\n\n## Stage 5 \u2014 Apply (one call)\n\n1. Make exactly ONE call to the `apply_install_manifest` MCP tool, passing:\n - `snapshot_token`: the exact token from Stage 2.\n - `fields`: a map of the derived fields. Scalar fields may be passed directly\n (e.g. `"base_branch": "main"`); confirmation-requiring fields (e.g. `project_description`,\n `selected_mcp_slugs`) must use the `{ "value": ..., "confirmed": true }` object form from\n Stage 4.\n2. Do not implement any client-side skip-if-set, conflict, or confirmation logic \u2014 the server owns\n all of that and returns the outcome buckets.\n3. The apply is partial-tolerant: fields that fail server-side validation are returned in the\n `rejected` bucket while the valid fields still commit. A rejected field is therefore reported,\n not fatal \u2014 do NOT retry the whole apply just because one field was rejected.\n\n## Stage 6 \u2014 Persist the routing credential\n\nThe Bash-spawned `start-tickets` CLI runs in a DIFFERENT runtime surface than this MCP server, so a\nkey that lives only in `.mcp.json` / `.cursor/mcp.json` is invisible to it and difficulty\u2192model\nrouting silently degrades. This final stage persists the already-validated key into the user-scoped\ncredential store (`~/.config/bridge/credentials.json`) so shell-spawned CLI commands can resolve it.\n\n1. Call the `persist_routing_credential` MCP tool with `repo_name` set to the repository name for\n this install (the configured `BAPI_REPO_NAME`). Pass `repo_name` ONLY \u2014 this tool resolves the\n API key inside the MCP server process. NEVER pass or display `BAPI_API_KEY` (nor `api_key`,\n `apiKey`, `secret`, or `token`); the key value must not appear in the tool call or in any output.\n2. On success, tell the user the credential was stored, echoing the returned `target` and `path`,\n e.g. `Stored routing credential for bapi:<repo_name> at <path>`.\n3. On failure, do NOT block the install \u2014 show the rest of the install summary anyway, then tell the\n user the routing credential could not be stored. They can rerun `/install-bridge` after setting\n `BAPI_API_KEY` in the environment, or run\n `npx -y @bridge_gpt/mcp-server credentials migrate-agent-config --write-credentials` to migrate an\n existing key. Until then, `start-tickets` model routing fails open (assumes a hard ticket and\n defaults to premium/Opus when available) \u2014 this is the MOST EXPENSIVE model tier, so unexplained\n cost is the symptom of leaving this unfixed. Recommend verifying credential resolution afterwards\n with `npx -y @bridge_gpt/mcp-server doctor`.\n\n## Stage 7 \u2014 Summarize the outcome\n\nBegin the summary with an explicit applied count: "Applied N of M derivable fields" (M = the unset\nbootstrap fields you derived values for). An install that applied 0 fields must say so loudly \u2014 the\ninstall is NOT complete until the apply call reports applied fields.\n\nThen summarize the six buckets from the apply response for the user:\n- `applied` \u2014 fields written this run.\n- `skipped` \u2014 fields already set (left untouched).\n- `conflict` \u2014 fields that changed since the manifest was read (re-run /install-bridge to retry).\n- `needs_confirmation` \u2014 fields awaiting explicit confirmation (e.g. project_description).\n- `rejected` \u2014 fields that failed validation or were not bootstrap-eligible (the other fields still\n applied; fix or drop the rejected values before any retry).\n- `deferred` \u2014 fields intentionally not auto-written by install (owned by /learn-repository or set\n deliberately).\n\nAfter the buckets, report the **integrations checklist** from the manifest\'s `integrations` list\n(read in Stage 2): one line per integration showing `label` and configured / NOT configured, and for\neach unconfigured one, its `required_for` items and the `configure_in` pointer. STRICT INVARIANT:\nyou DIRECT the human to configure integrations \u2014 you never ask for, accept, echo, or transport an\nintegration credential (API token, access token, webhook secret) in any form; a human enters them in\nthe setup UI. If the manifest had no `integrations` key, say the checklist was unavailable this run.\n\n## Stage 8 \u2014 Offer the next steps\n\n1. Offer to continue with `/learn-repository` to populate the deeper instruction-tier configuration\n (architecture, review, testing, and correctness standards), matching the manifest\'s `next_step`.\n2. After `/learn-repository` completes (or if the human declines it), offer to start **repository\n indexing**: ask ONE consent question ("Start repository indexing now? It runs as a background job\n and makes the codebase searchable for Bridge API\'s agents."). On yes, call the `parse_repository`\n MCP tool once. Report that the job was QUEUED and that progress can be checked with\n `get_parse_status` (or `/check-parse-status`) \u2014 do NOT poll it to completion in this session.\n Indexing is deliberately last so the applied `exclude_directories` / `custom_directories` values\n scope it correctly. If no human response can be obtained (non-interactive session), skip indexing\n and list it as a pending next step in the final report \u2014 never start it without consent.\n\n## Stage 9 \u2014 Offer CI follow-up configuration (only when CI is detected)\n\nThis stage is separate from the single apply call: it writes at most ONE field via the\n`config_field` MCP tool. `ci_followup_config` is REPLACE-NOT-AUGMENT \u2014 leaving it NULL already means\nsafe poll-only defaults, so "skip" is always a correct answer.\n\n1. Gate: only offer this when Stage 3 found real CI evidence (e.g. `.github/workflows/*.yml`,\n `bitbucket-pipelines.yml`). With no CI detected, skip this stage and note "no CI detected \u2014 CI\n follow-up not offered" in the final report.\n2. Ask ONE question, referencing the discovered CI by name, with exactly these preset profiles\n (default: skip):\n - **observe** \u2014 poll CI results only, never attempt fixes:\n `{"strategy": "poll_only", "max_iterations": 1, "max_minutes": 10, "instructions": ""}`\n - **self-heal** \u2014 bounded fix-and-iterate loop on the automation\'s own PRs:\n `{"strategy": "fix_and_iterate", "max_iterations": 3, "max_minutes": 45, "instructions": ""}`\n - **skip** (default) \u2014 leave `ci_followup_config` unset (NULL = baseline poll-only behavior).\n3. Do NOT offer or compose a `custom` strategy during install \u2014 free-form CI follow-up instructions\n are a deliberate later act (setup UI or a direct `config_field` update).\n4. When the human picks a profile, call the `config_field` MCP tool once (operation `"update"`,\n `field_name: "ci_followup_config"`, `value`: the profile\'s JSON object). If the server rejects the\n value, retry ONCE with a corrected payload; if it is rejected again, stop, show the proposed JSON\n to the human, and leave the field unset.\n5. Never select a profile in a non-interactive session \u2014 skip and list it as a pending next step. An\n unattended install must never enable fix-and-iterate behavior.\n\n## Return\n\nReport the admin check result, the "Applied N of M" count, the\napplied/skipped/conflict/needs_confirmation/rejected field names, any fields omitted for\nnested-repository or ambiguity reasons, the approval outcome for each confirmation-requiring field\n(approved / declined / pending human input), whether a stale-command warning was raised (manifest\n`command_contract_version` higher than this command\'s), whether the routing credential was persisted\n(the returned `target` and `path`, or the non-blocking failure remediation), the CI follow-up outcome\n(profile written / skipped / no CI detected / pending), and the recommended next\nstep (`/learn-repository`).\n',
|
|
15471
|
+
"install-bridge.md": 'Bootstrap Bridge API project configuration from the local codebase via the easy-install manifest.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command takes no arguments; any `$ARGUMENTS` text is ignored.\n\nCommand contract version: **2**. The install manifest returns a `command_contract_version`; Stage 2\ncompares it to this number to detect a stale scaffolded copy of this command.\n\nThis command performs a one-time "easy install" of Bridge API project configuration. It reads the\nserver-owned install manifest once, derives values for the **unset** bootstrap fields from the local\ncodebase, asks for human approval of the project description, applies everything in a single atomic\ncall, and then presents a **capability report** derived from a fresh read-after-write manifest read.\nThe server owns all skip-if-set, conflict, and confirmation semantics \u2014 this command never makes its\nown skip-if-set decisions \u2014 and the server owns all tool locked/unlocked membership; this command\nformats the server\'s contract and never recomputes it from prose.\n\nThis command is portable across Claude Code, Cursor, and Copilot: it only drives the Bridge API MCP\ntools (`get_my_role`, `get_install_manifest`, `apply_install_manifest`).\n\nIf any stage fails, stop immediately and report which stage failed and why.\n\n**Install-spawn context.** When the spawning instruction explicitly says this `/install-bridge` run is\nin the "install-spawn context" (it was launched by the `install-bridge` CLI\'s fresh agent session),\nStage 8 and Stage 9 are SKIPPED and the single closing interaction is the index-consent question the\nspawn prompt owns. When you invoke `/install-bridge` directly (manual invocation), Stages 8 and 9\nrun normally.\n\n## Stage 1 \u2014 Admin preflight\n\n1. Call the `get_my_role` MCP tool (no parameters). Inspect the response:\n - If `source` is `"legacy"`: proceed (legacy keys are permitted).\n - Else if `role` is `"admin"`: proceed.\n - Otherwise (a non-admin `user_access` key): stop immediately and display:\n ```\n Admin role required to apply install configuration. Ask a project admin to run /install-bridge,\n or use an admin API key.\n ```\n\n## Stage 2 \u2014 Read the manifest (once, pre-apply)\n\n1. Call the `get_install_manifest` MCP tool exactly once here.\n2. Keep the returned `snapshot_token` verbatim \u2014 you must pass the SAME token to the apply call in\n Stage 5. Do not call `get_install_manifest` again before applying; a fresh read would invalidate\n the token you are about to use. (Stage 7 performs a SEPARATE, later read-after-write manifest read\n for the current capability status \u2014 that is deliberate and does not reuse this token.)\n3. The manifest contains ordered `groups` of bootstrap fields (each with `field_name`, `is_set`,\n `current_value`, `guidance`, `examples`, `validation_summary`, `requires_confirmation`), a list of\n `deferred_fields`, a `next_step`, `done_criteria`, a `command_contract_version`, and the\n `snapshot_token`. It also carries the additive capability fields (`integrations`, `configured`,\n `learned`, `indexed`, `locked_tools`, `unlocked_tools`) \u2014 but ignore those here; the accurate\n capability status is the post-apply read in Stage 7.\n4. Compare the manifest\'s `command_contract_version` to this command\'s contract version (2, stated at\n the top of this file). If the manifest\'s version is HIGHER, this scaffolded command copy is stale:\n warn the user to refresh it by running `npx -y @bridge_gpt/mcp-server --init` in the project root,\n then proceed conservatively \u2014 wherever the manifest\'s `guidance`, `next_step`, or `done_criteria`\n differ from these instructions, follow the manifest.\n\n## Stage 3 \u2014 Derive values for UNSET bootstrap fields only\n\n1. Consider ONLY fields whose `is_set` is `false`. Never re-derive or overwrite a field that is\n already set \u2014 the server will also skip already-set fields, but you should not even propose them.\n2. For each unset field, derive a value from the local codebase following that field\'s `guidance`\n (e.g. infer `working_in` from frameworks/manifests, `version_control_system` from the git remote,\n testing stacks from test config and dependencies, directory fields from the repo layout).\n3. Omit any field you are uncertain about or that does not apply \u2014 leaving it unset is always\n acceptable. Do NOT guess.\n4. Specifically, do NOT invent a `version` when the platform has no meaningful version concept; leave\n it unset.\n5. Before deriving `version_control_system` or `base_branch`, confirm the git repository root is THIS\n project\'s root: `git rev-parse --show-toplevel` must resolve to the project directory itself. If\n the git root is an ancestor directory (the project is nested inside another repository), OMIT both\n fields and note why in the final report \u2014 deriving them would describe the parent repository, not\n this project.\n6. If you find real evidence for TWO OR MORE plausible values for a field (e.g. configs for multiple\n test frameworks, multiple frameworks in a monorepo), treat the value as ambiguous and omit it\n rather than picking one.\n7. For the "Automation policy" group (`selected_mcp_slugs`): propose MCP validation manuals only from\n clear platform markers, following the field\'s manifest guidance (e.g. SFCC cartridges \u2192\n `b2c-commerce-developer`; a Playwright config \u2192 `playwright-mcp`; PWA Kit markers \u2192\n `pwa-kit-mcp`). This field requires human confirmation (Stage 4). Omit it entirely when no manual\n clearly applies \u2014 never propose a slug on weak evidence.\n\n## Stage 4 \u2014 Human approval for confirmation-requiring fields\n\nSome manifest fields carry `requires_confirmation: true` (currently `project_description` and\n`selected_mcp_slugs`). These are never applied on derivation alone \u2014 each needs explicit human\napproval.\n\n1. If `project_description` is unset and you can draft a concise description from the codebase,\n present the proposed `project_description` text to the human and ask for explicit approval before\n including it.\n2. If you derived a `selected_mcp_slugs` list in Stage 3, present the proposed slugs and the platform\n evidence for each, and ask for approval in the SAME batched question round as the description.\n3. Include a confirmation-requiring field in the apply payload ONLY as\n `{ "value": <approved value>, "confirmed": true }`, and only after the human approves it. If the\n human does not approve a field, omit that field entirely.\n4. If no human response can be obtained (e.g. a non-interactive / headless session), do NOT stall and\n do NOT abandon the install: proceed to Stage 5 with every confirmation-requiring field omitted,\n and report them as "pending human input" in the final summary. The other derived fields must\n still be applied \u2014 unapproved fields never block them.\n\n## Stage 5 \u2014 Apply (one call)\n\n1. Make exactly ONE call to the `apply_install_manifest` MCP tool, passing:\n - `snapshot_token`: the exact token from Stage 2.\n - `fields`: a map of the derived fields. Scalar fields may be passed directly\n (e.g. `"base_branch": "main"`); confirmation-requiring fields (e.g. `project_description`,\n `selected_mcp_slugs`) must use the `{ "value": ..., "confirmed": true }` object form from\n Stage 4.\n2. Do not implement any client-side skip-if-set, conflict, or confirmation logic \u2014 the server owns\n all of that and returns the outcome buckets.\n3. The apply is partial-tolerant: fields that fail server-side validation are returned in the\n `rejected` bucket while the valid fields still commit. A rejected field is therefore reported,\n not fatal \u2014 do NOT retry the whole apply just because one field was rejected.\n\n## Stage 6 \u2014 Persist the routing credential\n\nThe Bash-spawned `start-tickets` CLI runs in a DIFFERENT runtime surface than this MCP server, so a\nkey that lives only in `.mcp.json` / `.cursor/mcp.json` is invisible to it and difficulty\u2192model\nrouting silently degrades. This final stage persists the already-validated key into the user-scoped\ncredential store (`~/.config/bridge/credentials.json`) so shell-spawned CLI commands can resolve it.\n\n1. Call the `persist_routing_credential` MCP tool with `repo_name` set to the repository name for\n this install (the configured `BAPI_REPO_NAME`). Pass `repo_name` ONLY \u2014 this tool resolves the\n API key inside the MCP server process. NEVER pass or display `BAPI_API_KEY` (nor `api_key`,\n `apiKey`, `secret`, or `token`); the key value must not appear in the tool call or in any output.\n2. On success, tell the user the credential was stored, echoing the returned `target` and `path`,\n e.g. `Stored routing credential for bapi:<repo_name> at <path>`.\n3. On failure, do NOT block the install \u2014 show the rest of the install summary anyway, then tell the\n user the routing credential could not be stored. They can rerun `/install-bridge` after setting\n `BAPI_API_KEY` in the environment, or run\n `npx -y @bridge_gpt/mcp-server credentials migrate-agent-config --write-credentials` to migrate an\n existing key. Until then, `start-tickets` model routing fails open (assumes a hard ticket and\n defaults to premium/Opus when available) \u2014 this is the MOST EXPENSIVE model tier, so unexplained\n cost is the symptom of leaving this unfixed. Recommend verifying credential resolution afterwards\n with `npx -y @bridge_gpt/mcp-server doctor`.\n\n## Stage 7 \u2014 Summarize the outcome, then present the capability report\n\nFirst, begin with an explicit applied count: "Applied N of M derivable fields" (M = the unset\nbootstrap fields you derived values for). An install that applied 0 fields must say so loudly \u2014 the\ninstall is NOT complete until the apply call reports applied fields.\n\nThen summarize the six buckets from the apply response for the user:\n- `applied` \u2014 fields written this run.\n- `skipped` \u2014 fields already set (left untouched).\n- `conflict` \u2014 fields that changed since the manifest was read (re-run /install-bridge to retry).\n- `needs_confirmation` \u2014 fields awaiting explicit confirmation (e.g. project_description).\n- `rejected` \u2014 fields that failed validation or were not bootstrap-eligible (the other fields still\n applied; fix or drop the rejected values before any retry).\n- `deferred` \u2014 fields intentionally not auto-written by install (owned by /learn-repository or set\n deliberately).\n\n### Read-after-write: fetch current capability status\n\nAfter the bucket summary, call the `get_install_manifest` MCP tool ONCE MORE. This post-apply read\nreflects the configuration you just wrote, so its capability fields are current (the Stage-2 read was\npre-apply and is stale for this purpose). This read does not need the snapshot token. Use ONLY this\npost-write response for the capability report below.\n\nThe response carries: the `integrations` checklist (each item `label`, `is_configured`,\n`required_for`, `configure_in`), the separate `configured` / `learned` / `indexed` readiness values,\nand the server-computed `locked_tools` / `unlocked_tools` arrays. Each tool entry is exactly\n`{tool, effect, missing, semantics}`: `effect` is `BLOCK` (unavailable) or `DEGRADE` (usable now,\nbut without codebase context); `missing` lists the server-computed dependency identifiers (integration\nids such as `github_app` / `vcs_access_token`, and `code_index`); `semantics` is `all_of` or `any_of`\nand you MUST preserve it verbatim \u2014 never recompute membership yourself, and cite\n`docs/mcp-tool-integrations.md` only for the human explanation of a gate, never to recalculate it.\n\nIf the post-write response has no capability fields at all (no `integrations` / `locked_tools` /\n`unlocked_tools` keys, e.g. the additive enrichment was omitted), print exactly:\n`No capability status is available yet; configuration can still continue.` and skip the five sections.\n\nOtherwise render exactly these five sections, in this order, with these exact headings:\n\n**Connected \u2713**\n\n- List each configured integration\'s `label` from the post-write `integrations` checklist, with a\n restrained `\u2713` marker. (Do not let the `\u2713` markers dominate the report.)\n\n**Not yet connected \u2717**\n\n- List each unconfigured integration: its `label`, its `required_for` items, and the exact\n `configure_in` pointer. Do NOT include `setup_instructions` content \u2014 the pointer is the only\n configuration direction you emit. STRICT INVARIANT: you DIRECT the human to the setup UI; you never\n ask for, accept, echo, or transport an integration credential (API token, access token, webhook\n secret) in any form.\n\n**Tools you can use now**\n\n- Render each `unlocked_tools` entry with an explicit `BLOCK` or `DEGRADE` text label (do not rely on\n color). Describe a `DEGRADE` tool as "available with reduced/no codebase context" \u2014 never as failed\n or unavailable. Entries here with a non-empty `missing` list are still usable; state the caveat.\n\n**Tools you\'ll unlock**\n\n- Group `locked_tools` by each missing gating integration so the human can scan by "what would I\n configure to unlock these". Preserve the server\'s `all_of` / `any_of` semantics in plain text, e.g.\n "requires VCS and a successful code index" (`all_of` with `code_index`) or "requires GitHub App or\n VCS access token" (`any_of`). A tool with multiple missing integrations may appear under more than\n one group, but keep its complete server-provided relationship intact. Cite\n `docs/mcp-tool-integrations.md` briefly for each gate\'s human "why" \u2014 but do not recompute membership\n from it.\n\n**Recommended next step + why**\n\n- Make this section visually strongest through ordering and concise wording. Choose the single most\n valuable next action using this deterministic priority based only on the server output:\n 1. If any `locked_tools` entry is missing a VCS integration (`github_app` / `vcs_access_token`),\n recommend connecting VCS in the setup UI first (via `configure_in`).\n 2. Else if any `locked_tools` entry is missing `code_index`, recommend running repository indexing\n (`/parse-repository`) next.\n 3. Else if `learned` is false, recommend running `/learn-repository` to populate the deeper\n instruction-tier configuration.\n- If `indexed` is `null` (unknown), include this exact warning:\n `Index status could not be confirmed\u2014check again before relying on codebase-grounded tools.`\n\n## Stage 8 \u2014 Offer the next steps\n\n**Skip this entire stage in the install-spawn context** (the spawning instruction identifies it); the\nspawn prompt owns the single closing index-consent question there. On direct manual `/install-bridge`\ninvocation, run it normally:\n\n1. Offer to continue with `/learn-repository` to populate the deeper instruction-tier configuration\n (architecture, review, testing, and correctness standards), matching the manifest\'s `next_step`.\n2. After `/learn-repository` completes (or if the human declines it), offer to start **repository\n indexing**: ask ONE consent question ("Start repository indexing now? It runs as a background job\n and makes the codebase searchable for Bridge API\'s agents."). On yes, call the `parse_repository`\n MCP tool once. Report that the job was QUEUED and that progress can be checked with\n `get_parse_status` (or `/check-parse-status`) \u2014 do NOT poll it to completion in this session.\n Indexing is deliberately last so the applied `exclude_directories` / `custom_directories` values\n scope it correctly. If no human response can be obtained (non-interactive session), skip indexing\n and list it as a pending next step in the final report \u2014 never start it without consent.\n\n## Stage 9 \u2014 Offer CI follow-up configuration (only when CI is detected)\n\n**Skip this entire stage in the install-spawn context** (the spawning instruction identifies it), so\nthe index-consent question the spawn prompt owns remains the sole closing interaction. On direct\nmanual `/install-bridge` invocation, run it normally.\n\nThis stage is separate from the single apply call: it writes at most ONE field via the\n`config_field` MCP tool. `ci_followup_config` is REPLACE-NOT-AUGMENT \u2014 leaving it NULL already means\nsafe poll-only defaults, so "skip" is always a correct answer.\n\n1. Gate: only offer this when Stage 3 found real CI evidence (e.g. `.github/workflows/*.yml`,\n `bitbucket-pipelines.yml`). With no CI detected, skip this stage and note "no CI detected \u2014 CI\n follow-up not offered" in the final report.\n2. Ask ONE question, referencing the discovered CI by name, with exactly these preset profiles\n (default: skip):\n - **observe** \u2014 poll CI results only, never attempt fixes:\n `{"strategy": "poll_only", "max_iterations": 1, "max_minutes": 10, "instructions": ""}`\n - **self-heal** \u2014 bounded fix-and-iterate loop on the automation\'s own PRs:\n `{"strategy": "fix_and_iterate", "max_iterations": 3, "max_minutes": 45, "instructions": ""}`\n - **skip** (default) \u2014 leave `ci_followup_config` unset (NULL = baseline poll-only behavior).\n3. Do NOT offer or compose a `custom` strategy during install \u2014 free-form CI follow-up instructions\n are a deliberate later act (setup UI or a direct `config_field` update).\n4. When the human picks a profile, call the `config_field` MCP tool once (operation `"update"`,\n `field_name: "ci_followup_config"`, `value`: the profile\'s JSON object). If the server rejects the\n value, retry ONCE with a corrected payload; if it is rejected again, stop, show the proposed JSON\n to the human, and leave the field unset.\n5. Never select a profile in a non-interactive session \u2014 skip and list it as a pending next step. An\n unattended install must never enable fix-and-iterate behavior.\n\n## Return\n\nReport the admin check result, the "Applied N of M" count, the\napplied/skipped/conflict/needs_confirmation/rejected field names, any fields omitted for\nnested-repository or ambiguity reasons, the approval outcome for each confirmation-requiring field\n(approved / declined / pending human input), whether a stale-command warning was raised (manifest\n`command_contract_version` higher than this command\'s), whether the routing credential was persisted\n(the returned `target` and `path`, or the non-blocking failure remediation), the five-section\ncapability report (Connected \u2713 / Not yet connected \u2717 / Tools you can use now / Tools you\'ll unlock /\nRecommended next step + why) from the post-apply read-after-write manifest read, the CI follow-up\noutcome (profile written / skipped / no CI detected / pending / skipped in install-spawn context), and\nthe recommended next step.\n',
|
|
15472
15472
|
"learn-repository.md": 'Learn and document all configuration fields for the repository by running parallel research agents.\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. This command takes no arguments.\n\n2. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `"learn-repository"`\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 **Steps executed**: N of M\n **Status**: Success / Failed at step N\n ```\n',
|
|
15473
15473
|
"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",
|
|
15474
15474
|
"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',
|
|
@@ -15480,7 +15480,7 @@ var COMMANDS = {
|
|
|
15480
15480
|
"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"},{"name":"noRefreshBase","flag":"--no-refresh-base","type":"boolean"},{"name":"baseBranch","flag":"--base-branch","type":"string"},{"name":"baseSha","flag":"--base-sha","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 - An optional position-independent `--no-refresh-base` flag.\n - An optional position-independent `--base-branch=<branch>` argument.\n - An optional position-independent `--base-sha=<sha>` argument.\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, leave `rounds` unset (omitted) so the backend\'s difficulty-adaptive review policy can decide the review shape when enabled for this repo; when adaptive routing is disabled, unavailable, or the ticket\'s difficulty cannot be resolved, the backend falls back to a full premium second-opinion review. The presence of a `--no-refresh-base` token sets `no_refresh_base` to `true`. A token matching `--base-branch=<branch>` sets `base_branch` to `<branch>`. A token matching `--base-sha=<sha>` sets `base_sha` to `<sha>`.\n\n `--auto`, `--rounds`, `--no-refresh-base`, `--base-branch`, and `--base-sha` are all independent and may be supplied in any combination.\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] [--no-refresh-base] [--base-branch=BRANCH] [--base-sha=SHA]\n Usage: /review-ticket <ticket_key> [--auto] [--rounds=1|2] [--no-refresh-base] [--base-branch=BRANCH] [--base-sha=SHA]\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 (omit to let the backend decide adaptively; default falls back to a full review)\n Usage: /review-ticket <ticket_key> [--auto] [--rounds=1|2] [--no-refresh-base] [--base-branch=BRANCH] [--base-sha=SHA]\n ```\n\n2. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `"review-ticket"`\n - `variables`: `{ "ticket_key": "<ticket_key>", "base_branch": "<base_branch or "">", "base_sha": "<base_sha or "">", "no_refresh_base": "<"true" if --no-refresh-base was passed, else "">" }`\n - `auto_approve`: `true` \u2014 only when `--auto` was passed; otherwise omit this field entirely.\n - `rounds`: `1` \u2014 only when `--rounds=1` was explicitly passed on the command; `2` \u2014 only when `--rounds=2` was explicitly passed. When `--rounds` was NOT supplied, omit `rounds` entirely (do not pass `rounds: null`) so the backend\'s difficulty-adaptive review policy can choose the review shape when enabled for this repo. An explicit `rounds` value is forwarded to the backend and forces the review shape: `--rounds=1` requests a single-pass review, and `--rounds=2` forces the full second-opinion review even when adaptive routing is enabled. Do NOT translate `rounds` into `skip_steps` \u2014 the backend executor now owns all round orchestration (including any second-opinion rounds), so the recipe carries a single `request_ticket_review` step and you never pass `skip_steps` for round control.\n\n Example combined-mode payload (`--rounds=1 --auto --base-branch=develop`):\n ```json\n {\n "pipeline": "review-ticket",\n "variables": { "ticket_key": "PROJ-123", "base_branch": "develop", "base_sha": "", "no_refresh_base": "" },\n "auto_approve": true,\n "rounds": 1\n }\n ```\n\n Example explicit full-review payload (`--rounds=2`), which forces the full second-opinion review even if adaptive routing is enabled for this repo:\n ```json\n {\n "pipeline": "review-ticket",\n "variables": { "ticket_key": "PROJ-123", "base_branch": "", "base_sha": "", "no_refresh_base": "" },\n "rounds": 2\n }\n ```\n\n Example adaptive payload (no `--rounds`), which lets the backend policy executor decide the review shape (falling back to a full premium review when adaptive routing is disabled or the ticket\'s difficulty cannot be resolved):\n ```json\n {\n "pipeline": "review-ticket",\n "variables": { "ticket_key": "PROJ-123", "base_branch": "", "base_sha": "", "no_refresh_base": "" }\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',
|
|
15481
15481
|
"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"},{"name":"noRefreshBase","flag":"--no-refresh-base","type":"boolean"},{"name":"baseBranch","flag":"--base-branch","type":"string"}]}\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>]`. By default `--rounds` is omitted so the backend routes each review by difficulty (difficulty-adaptive review); pass an explicit `--rounds=1|2` (globally or per ticket) to force the review shape. Unlike `/start-tickets`, it creates no Worktrunk worktrees \u2014 but it now requires `git` on PATH: the parent process fetches `origin/<base_branch>` once and pins a single `base_sha` for the whole batch before spawning tabs (BAPI-474), so every spawned review grounds its codebase evaluation against the same freshly-fetched base tree. Pass `--no-refresh-base` to skip this and restore the prior git-free, in-place-grounded behavior.\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` (single-pass review).\n - `full`, `two-pass`, `rounds=2`, or `--rounds=2` \u2192 `rounds=2` (full second-opinion review).\n - **omitted rounds \u2192 `adaptive`**: when no rounds mode is given for a ticket, do NOT choose a round count \u2014 leave it adaptive so the backend\'s difficulty-adaptive review policy decides the shape. `adaptive` is a distinct mode from `1` and `2`.\n - `--auto` and `--rounds` are independent: both may apply to the same ticket.\n\n The backend executor now owns all review round orchestration (including any second-opinion rounds) server-side \u2014 there is no client-side step to skip, so this command never sends `skip_steps` and never translates `--rounds=1` into skipping a second-opinion step. An explicit `--rounds` value forwarded to each spawned `/review-ticket` forces the review shape (`1` = single pass, `2` = full second-opinion review). A spawned `/review-ticket` invoked without any `--rounds` (the default) lets the backend\'s difficulty-adaptive review policy decide the shape (falling back to a full premium second-opinion review when adaptive routing is disabled, unavailable, or the ticket\'s difficulty cannot be resolved). This batch command therefore forwards **no** `--rounds` by default; it only forwards `--rounds` when the caller explicitly supplies a rounds mode (globally via `--rounds`, or per ticket via `--review`).\n\n - **Homogeneous modes**: when all tickets share the same auto and rounds mode, translate into global `--auto` (if all auto) and, only when all tickets share the same *explicit* rounds value, global `--rounds=1|2`. When all tickets are adaptive (no rounds given), omit `--rounds` entirely \u2014 do not synthesize a default.\n\n - **Heterogeneous modes**: when tickets differ in auto or rounds mode, translate into repeatable `--review KEY=auto,rounds=N` overrides. Only emit a `rounds=N` subtoken for tickets given an explicit `1`/`2`; adaptive tickets carry no `rounds` subtoken (a bare `--review KEY=auto` if they are auto, or no override at all). Do NOT set global `--auto` when only some tickets are auto-approved, and do NOT set global `--rounds` when only some tickets have an explicit rounds value.\n\n - **Pass-through flags**: collect `--dry-run`, `--max-parallel N`, `--agent claude|cursor-agent`, `--model VALUE`, `--no-refresh-base`, and `--base-branch 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] [--no-refresh-base] [--base-branch BRANCH] 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` or `git-wt` \u2014 but it now requires `git` on PATH (BAPI-474): before spawning any tabs, the parent process fetches `origin/<base_branch>` once and pins a single `base_sha` for the whole batch, so a mid-batch `origin` advance can never mix bases within one run. Pass `--no-refresh-base` to skip the fetch and restore the prior git-free, in-place-grounded behavior.\n- Prerequisites: macOS `osascript` + `git`, Windows `wt.exe` or PowerShell + `git`, Linux `tmux` + `git` (git is not required when `--no-refresh-base` is passed).\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|adaptive> agent=<agent> model=<alias|default> status=<status>`) and render as a markdown table (`rounds=adaptive` means the backend chose the shape by difficulty):\n\n```\n| Ticket | Auto | Rounds | Agent | Model | Status |\n|----------|-------|----------|--------|---------|---------|\n| BAPI-1 | false | adaptive | 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',
|
|
15482
15482
|
"run-tests.md": 'Run the project\'s full test suite (unit and E2E) using the project-configured test stacks, triage failures, fix test-code issues, and produce a structured health-check report.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command discovers how to run tests by reading per-project configuration from the Bridge API, not from hardcoded paths. Stages run only when the project has the corresponding stack configured.\n\n## Stage 0 \u2014 Argument Parsing and Setup\n\n1. **Parse `$ARGUMENTS`** for optional flags. Supported flags:\n - `--skip-e2e` \u2014 skip the E2E test stage even if an E2E stack is configured (e.g., when no local server is running)\n - `--unit-only` \u2014 shorthand that implies `--skip-e2e`\n\n Resolve flags to boolean variables:\n - Start with: `run_unit = true`, `run_e2e = true`\n - If `--unit-only` is present: set `run_e2e = false`\n - If `--skip-e2e` is present: set `run_e2e = false`\n - Unknown flags: note them in the final report as "Unrecognized flag ignored" but do not fail\n\n2. **Generate a run timestamp** using the current date and time in `YYYY-MM-DD-HH-MM` format (e.g., `2026-03-10-14-35`). Store this as `run_timestamp`. Both output documents will use this value.\n\nThis stage has no failure conditions \u2014 proceed to Stage 1.\n\n## Stage 1 \u2014 Resolve Project Config via MCP\n\nRead the per-project test setup from the Bridge database. Every subsequent stage is driven by what these calls return.\n\n1. **Resolve docs directory**: Call the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\n2. **Read unit-test stack**: Call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to `unit_testing_stack`. Store the returned value as `unit_stack` (may be null/empty).\n\n3. **Read unit-test instructions**: Call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to `unit_testing_instructions`. Store the returned value as `unit_instructions` (may be null/empty).\n\n4. **Read E2E stack**: Call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to `e2e_testing_stack`. Store as `e2e_stack`.\n\n5. **Read E2E instructions**: Call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to `e2e_testing_instructions`. Store as `e2e_instructions`.\n\n6. **Compute configuration booleans**:\n - `unit_configured` = `true` if either `unit_stack` or `unit_instructions` is a non-empty string; otherwise `false`\n - `e2e_configured` = `true` if either `e2e_stack` or `e2e_instructions` is a non-empty string; otherwise `false`\n\n7. **Create the output directory**:\n ```\n mkdir -p {docs_dir}/testing/\n ```\n If this fails, stop immediately and report: `Cannot create output directory {docs_dir}/testing/ \u2014 check permissions.`\n\nIf any MCP call fails (e.g., the API is unreachable or returns 4xx/5xx), stop immediately and report which call failed. Do not fall back to hardcoded commands \u2014 the whole point of this command is that test setup lives in config.\n\n## Stage 2 \u2014 Unit / Standard Tests\n\nIf `run_unit` is `false`, skip this stage and record: `Unit tests: SKIPPED \u2014 run_unit was set to false (this should not happen in normal use; report as a bug).`\n\nIf `unit_configured` is `false`, skip and record:\n```\nUnit tests: SKIPPED \u2014 no unit_testing_stack or unit_testing_instructions configured for this repo. Configure via /learn-unit-testing or the project setup UI before running /run-tests.\n```\n\nOtherwise:\n\n1. Read `unit_instructions` carefully. It is the source of truth for **how to run unit tests in this repo** \u2014 runner binary, paths, environment activation, sub-suites (if the project distinguishes "unit" from "integration", both belong in this stage), and any flags. Pair it with `unit_stack` (a short label, e.g., `Pytest`, `Jest + React Testing Library`) for context.\n\n2. **Derive the test command(s)**: Extract the literal shell commands the instructions describe. If the instructions describe multiple sub-suites (e.g., a fast unit batch and a slower integration batch), plan to run each as a **separate batch** in the order described. Do not invent runners or paths that the instructions do not mention.\n\n3. **If the instructions do not specify any runnable command**, skip and record:\n ```\n Unit tests: SKIPPED \u2014 unit_testing_instructions does not describe how to invoke tests; please update via /learn-unit-testing.\n ```\n\n4. **Run each batch sequentially** in the terminal. **Continue to the next batch even if the current one has failures.** Capture the full output of each batch, including the runner\'s summary line (e.g., `47 passed, 3 failed in 12.4s` or `Tests: 5 failed, 22 passed`).\n\n5. For each failing test, apply the **Triage Logic** (below), then record the result.\n\n## Stage 3 \u2014 E2E Tests\n\nIf `run_e2e` is `false`, skip this stage and record: `E2E tests: SKIPPED \u2014 --skip-e2e or --unit-only flag was set.`\n\nIf `e2e_configured` is `false`, skip and record:\n```\nE2E tests: SKIPPED \u2014 no e2e_testing_stack or e2e_testing_instructions configured (the project may not have an E2E suite).\n```\n\nOtherwise:\n\n1. Read `e2e_instructions`. It is the source of truth for the E2E runner, spec paths, browser config, and any prerequisites. Pair with `e2e_stack` for context.\n\n2. **Detect server prerequisites**: If `e2e_instructions` indicates that a local server must be running (look for explicit cues such as "server", "running", "localhost", "started", "dev server", a URL, or a port number) and describes a readiness check, perform that check exactly as described. If the instructions describe a server prerequisite but do not describe a check, attempt the check the instructions imply (e.g., curl the URL the instructions mention) and skip the stage if it fails:\n ```\n E2E tests: SKIPPED \u2014 e2e_testing_instructions describe a server prerequisite that wasn\'t met. Start the server per the instructions and re-run.\n ```\n\n3. **Derive the test command(s)** from the instructions, including any spec-directory batching the instructions specify.\n\n4. **If the instructions do not specify any runnable command**, skip and record:\n ```\n E2E tests: SKIPPED \u2014 e2e_testing_instructions does not describe how to invoke tests; please update via /learn-e2e-testing.\n ```\n\n5. **Run each batch sequentially** in the terminal. **Continue to the next batch even if the current one has failures.** Capture the full output and summary line of each batch.\n\n6. For each failing test, apply the **Triage Logic** (below), then record the result.\n\n## Triage Logic\n\nFor every failing test, examine the test file and the code it tests. Classify as ONE of the following:\n\n### TEST-CODE ISSUE \u2014 fix it directly\n\nClassify as a test-code issue if ANY of the following applies:\n- The test asserts against a hardcoded value that no longer matches current behavior (outdated mock data)\n- The test imports or calls a function that was renamed, moved, or removed\n- The test asserts on a response field that was restructured\n- The test expects a specific error message string that has since changed\n- A fixture references a removed table column, model field, or schema member\n\n**Action**: Apply a minimal, targeted fix to the test file only. Then re-run just that failing test, using the runner described in the relevant instructions field (`unit_instructions` for unit-test failures, `e2e_instructions` for E2E failures). Adapt the runner invocation that the instructions provide to target a single test, following whatever convention the instructions or stack idiomatically use.\n\nIf the re-run **still fails** after your fix, do not make further edits \u2014 escalate to implementation-code issue instead and revert your change.\n\n### IMPLEMENTATION-CODE ISSUE (or UNCERTAIN) \u2014 document, do not fix\n\nClassify as an implementation issue if ANY of the following applies:\n- The production function raises an unexpected exception\n- A handler returns the wrong status code or response shape for a documented behavior\n- Business logic produces incorrect output that the test correctly asserts against\n- You are not confident the test is wrong\n\n**Action**: Do NOT modify any file outside the test directories described in `unit_testing_instructions` / `e2e_testing_instructions`. When in doubt about whether a path is test-only, treat it as production code and escalate. Record the failure in the implementation-issues document for the user to triage.\n\n## Stage 4 \u2014 Write Output Documents\n\n### Document 1: Test Run Report (always write this)\n\nWrite to: `{docs_dir}/testing/test-run-{run_timestamp}.md`\n\n```markdown\n# Test Run: {run_timestamp}\n\n## Configuration\n- Unit stack: {unit_stack or "not configured"}\n- E2E stack: {e2e_stack or "not configured"}\n- Unit tests: RUN | SKIPPED \u2014 (reason)\n- E2E tests: RUN | SKIPPED \u2014 (reason)\n\n## Unit Tests\n**Stack**: {unit_stack or "not configured"}\n**Result**: X passed, Y failed (sum across batches)\n\n### Batch 1: `<command>`\n**Result**: X passed, Y failed\n**Fixes applied**:\n- `path/to/test_file`: brief description of what was fixed\n- (or "none" if no fixes were needed)\n\n### Batch 2: `<command>`\n...\n\n**Failures escalated as implementation issues**: N\n\n## E2E Tests\n**Stack**: {e2e_stack or "not configured"}\n**Result**: X passed, Y failed (sum across batches)\n\n### Batch 1: `<command>`\n**Result**: X passed, Y failed\n**Fixes applied**: ...\n\n### Batch 2: `<command>`\n...\n\n**Failures escalated as implementation issues**: N\n\n## Overall Summary\n- Total test fixes applied: N\n- Suspected implementation issues found: N\n- Implementation issues document: {docs_dir}/testing/implementation-issues-{run_timestamp}.md\n (or "not created \u2014 no issues found")\n```\n\n### Document 2: Implementation Issues (only write if issues were found)\n\nIf at least one failure was escalated as an implementation-code issue, write to:\n`{docs_dir}/testing/implementation-issues-{run_timestamp}.md`\n\n```markdown\n# Suspected Implementation Issues: {run_timestamp}\n\nThese test failures were NOT fixed. They may indicate bugs in production code.\nA developer should investigate each item before merging.\n\n## Issue 1\n- **Test**: `path/to/test_file::test_function_name`\n- **Tier**: unit | e2e\n- **Failure message**: (paste the key assertion or exception line)\n- **Why not fixed**: (brief reasoning, e.g., "production function raises KeyError on valid input")\n\n## Issue 2\n...\n```\n\nIf no implementation issues were found, do NOT create this file.\n\n## Final Output\n\nAfter writing all documents, print this summary:\n\n```\nTest run complete: {run_timestamp}\nReport saved to: {docs_dir}/testing/test-run-{run_timestamp}.md\nImplementation issues: {docs_dir}/testing/implementation-issues-{run_timestamp}.md (if applicable)\nNo suspected implementation issues found. (if none)\n```\n',
|
|
15483
|
-
"scan-test-coverage.md": 'Scan recently shipped tickets from git history and report which features have or could gain integration tests, and which can only be smoke tested.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nScan the git history for recently shipped tickets and, for each shipped feature, determine whether it already has an integration test, whether it *could* gain one (per this repo\'s conventions \u2014 a test that genuinely executes the system end-to-end via real database operations, real LLM calls, or real FastAPI routing), and \u2014 where integration testing is not possible \u2014 how it could be smoke tested so it still genuinely executes the system.\n\nThis is an **investigation and discovery** command. Describe features (citing code) and *how* they would be tested at a high level. Do **not** design tests in detail, build or edit any tests, or modify feature code. Orchestrate this run in the main thread, and **fan out one subagent per shipped feature** for the per-feature investigation.\n\nThe report is written to a **durable, committed** directory (`docs/test-coverage/`), and a marker file records when the analysis last ran so subsequent runs only inspect git history since the last run.\n\n## Stage 0 \u2014 Parse Arguments and Resolve Analysis Window\n\n1. Read `$ARGUMENTS`. All flags are optional and default-safe. If a flag is malformed, ignore it and add a warning:\n - `--since=YYYY-MM-DD` \u2014 override the window start date.\n - `--full` \u2014 ignore the marker and use a default lookback of 6 months.\n - `--limit=N` \u2014 cap the number of features investigated (parse `N` as an integer; ignore if not a valid integer).\n With no arguments, run **incrementally** from the marker.\n\n2. Set the durable directory to `docs/test-coverage/` (relative to the repo root) and the marker file to `docs/test-coverage/STATE.md`. This command deliberately does **not** use `get_docs_dir` \u2014 its default (`docs/tmp`) is ephemeral, and this report must be durable.\n\n3. Read `docs/test-coverage/STATE.md` if it exists. It records two values: `last_run_utc` (an ISO-8601 UTC timestamp) and `last_analyzed_commit` (a git commit SHA).\n\n4. Resolve the analysis window with this precedence:\n - If `--since=YYYY-MM-DD` was given, use `git log --since=<date>`.\n - Else if `STATE.md` provides `last_analyzed_commit`, use the commit range `<last_analyzed_commit>..HEAD`.\n - Else (first run, no marker), default to `git log --since=<3 months ago>` (mirrors the `/scan-tickets` default of 3 months). Format the date as `YYYY-MM-DD`. Example: if today is 2026-07-07, the default `--since` is `2026-04-07`.\n - `--full` overrides the above and uses a 6-month lookback (`--since=<6 months ago>`).\n\n5. Robustness of the marker: capture `head_sha` by running `git rev-parse HEAD`, and capture the current UTC timestamp now. These become the **new** marker values, but only write them after the report is successfully produced (Stage 4). If a stored `last_analyzed_commit` is not present in history (e.g. a rebase/rewrite), fall back to `git log --since=<the date part of last_run_utc>` and add a warning noting the fallback.\n\n6. Initialize tracking variables:\n - `features` = [] (one entry per shipped feature)\n - `warnings` = [] (per-item failures and fallbacks; the run never aborts on these)\n\n7. Display the resolved window, e.g. "Analyzing shipped features in `<range or --since date>` (HEAD = {head_sha})".\n\n## Stage 1 \u2014 Collect Shipped Features from Git History\n\n1. List merged commits in the resolved window with:\n ```bash\n git log <range> --first-parent --pretty=format:"%H|%h|%ad|%s" --date=short\n ```\n `--first-parent` yields roughly one entry per squashed PR merge.\n\n2. For each commit, extract the ticket key by matching `^BAPI-[0-9]+` against the subject. Group commits by ticket key. Commits with no ticket prefix (e.g. `Fix 500 on ...`) each become a standalone feature labeled as an "untracked change".\n\n3. For each group, collect the changed-file footprint across its commit(s) using `git show --stat <sha>` or `git diff --name-only`. This file footprint is the primary input to the per-feature investigation.\n\n4. Best-effort enrichment: for each ticket key, call the `get_ticket` MCP tool to fetch the ticket summary. This is **fail-open** \u2014 Jira tokens can be expired \u2014 so on any error, add a warning and continue without the summary. Do not abort.\n\n5. Build a `features` entry per group: `{ticket_key, subject, commit_shas, changed_files, jira_summary?}`. If `--limit=N` was given, keep only the first `N` features (most recent first).\n\n6. Display: "Found {count} shipped features to investigate."\n\n7. If `git log` returns no commits, skip to Stage 4 and write a report noting an empty window (and still refresh the marker).\n\n## Stage 2 \u2014 Investigate Each Feature (fan out subagents)\n\nFor each feature in `features`, launch an **Explore** subagent (batch several in parallel). Give each subagent the feature\'s `ticket_key`, `subject`, `changed_files`, and `jira_summary`, and instruct it to do read-only investigation only \u2014 no edits, no test design, no solutioning \u2014 and to return a structured finding.\n\nEach subagent must:\n\n1. Read the changed files and describe what the feature does in 2\u20134 sentences, with concrete `file:line` citations.\n\n2. Identify the feature\'s runtime surface \u2014 one or more of: real database operations (`postgres_client` / a DAL in `api/library/db/`), real LLM calls (`src/python/llms/ai_client.py`, `async_send_message_to_ai`), real FastAPI routing (a route handler under `api/routes/`), an MCP tool (`mcp_server/`), a shell-spawned / CLI flow, a frontend / Playwright surface, or pure logic / config / docs / tests.\n\n3. Check whether an **integration test already exists**: search `tests/integration/` for a mirror path or for references to the changed modules/functions. The reliable classifier is a path under `tests/integration/` plus `@pytest.mark.integration` or reliance on the `--run-integration` flag (conventions in `docs/claude/testing-integration.md`). Cite any test found.\n\n4. Classify the feature into exactly one `bucket`:\n - **`has_integration_test`** \u2014 already covered end-to-end; cite the existing integration test file.\n - **`integration_testable`** \u2014 no test yet, but the feature exercises real DB / LLM / routing and fits an existing `tests/integration/<area>/` pattern. Give a **high-level** approach only: which real entrypoint to call, which backend it would exercise, and the relevant cost/guard note (the gpt-5-nano override via `INTEGRATION_TEST_MODEL`; the local-DB `skipif` guard; `save_to_db=False`). Cite the entrypoint in code.\n - **`smoke_only`** \u2014 genuine end-to-end execution is possible but not as an automated integration test (e.g. MCP tool behavior inside a host, cross-platform terminal spawning, a headless agent session, or browser E2E). Describe how to smoke test it so it **genuinely executes the system**, citing the relevant runbook: `mcp_server/smoke-test/SMOKE-TEST.md`, `tests/mcp/`, `docs/claude/runbooks/self-install-smoke-test.md`, `docs/claude/runbooks/start-tickets-smoke-test.md`, or Playwright (`tests/playwright/`, which needs a running server plus `npm run build`).\n - **`not_testable`** \u2014 nothing to execute end-to-end (docs-only, a wording/comment change, pure config, or a test-only change); state why.\n\n5. Return a structured finding with these fields: `ticket_key`, `subject`, `description_with_cites`, `surface`, `bucket`, `existing_test`, `approach`, `why_not`.\n\nCollect all findings. If a per-feature subagent fails, add a warning and continue \u2014 never abort the whole run.\n\n## Stage 3 \u2014 Classify and Synthesize\n\n1. Deduplicate features that span multiple commits (merge by `ticket_key`).\n\n2. Sort each finding into the two required report sections:\n - **Section 1 \u2014 Integration Testing (covered or addable):** findings with `bucket` `has_integration_test` (sub-group "Already covered") or `integration_testable` (sub-group "Could be added").\n - **Section 2 \u2014 Not Integration-Testable:** findings with `bucket` `smoke_only` (sub-group "Smoke-testable \u2014 how") or `not_testable` (sub-group "Not testable \u2014 why").\n\n## Stage 4 \u2014 Write the Report and Update the Marker\n\n1. Create the `docs/test-coverage/` directory if it does not exist. Choose the report path `docs/test-coverage/REPORT-<YYYYMMDD>.md`; if a same-day file already exists, append `-<HHMMSS>` to avoid clobbering it.\n\n2. Write the report with this layout:\n - A title and a metadata block: generated-at UTC timestamp; the analysis window (`<from sha or since-date>` \u2192 `HEAD <head_sha>`); the feature count; and per-bucket tallies.\n - **Section 1 \u2014 Integration Testing: Covered or Addable.** One `### BAPI-NNN \u2014 <subject>` heading per feature, each with **What shipped** (with `file:line` citations), **Current coverage** (cite the existing integration test, or state "none"), and **How it could be integration tested (high level)**.\n - **Section 2 \u2014 Not Integration-Testable.** One heading per feature with the same feature description, plus **Why not integration-testable**, and \u2014 for `smoke_only` features \u2014 **How to smoke test (genuinely execute the system)** with the runbook citation.\n - A **Warnings** section listing each warning as a bullet \u2014 only if `warnings` is non-empty.\n\n3. **Only after** the report file is written successfully, update the marker `docs/test-coverage/STATE.md` with the new `last_run_utc` (the UTC timestamp captured in Stage 0) and `last_analyzed_commit` set to `head_sha`. This date/commit marker is what makes the next run incremental. If the report write fails, do not touch `STATE.md`.\n\n## Final Report\n\nPrint a short summary to chat:\n\n```\n**Test-coverage scan complete**\n\n* Features analyzed: {count}\n* Already covered by integration tests: {n_has}\n* Integration-testable (could be added): {n_addable}\n* Smoke-only: {n_smoke}\n* Not testable: {n_none}\n\nReport: docs/test-coverage/REPORT-<YYYYMMDD>.md\nMarker updated: last_analyzed_commit = {head_sha}\n```\n\nIf `warnings` is non-empty, add a "Warnings:" section listing each warning as a bullet. If there are no warnings, omit that section.\n',
|
|
15483
|
+
"scan-test-coverage.md": 'Scan recently shipped tickets from git history and report which features have or could gain integration tests, and which can only be smoke tested.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nScan the git history for recently shipped tickets and, for each shipped feature, determine whether it already has an integration test, whether it *could* gain one (per this repo\'s conventions \u2014 a test that genuinely executes the system end-to-end via real database operations, real LLM calls, or real FastAPI routing), and \u2014 where integration testing is not possible \u2014 how it could be smoke tested so it still genuinely executes the system.\n\nThis is an **investigation and discovery** command. Describe features (citing code) and *how* they would be tested at a high level. Do **not** design tests in detail, build or edit any tests, or modify feature code. Orchestrate this run in the main thread, and **fan out one subagent per shipped feature** for the per-feature investigation.\n\nThe report is written to a **durable, committed** directory (`docs/test-coverage/`), and a marker file records when the analysis last ran so subsequent runs only inspect git history since the last run.\n\n## Stage 0 \u2014 Parse Arguments and Resolve Analysis Window\n\n1. Read `$ARGUMENTS`. All flags are optional and default-safe. If a flag is malformed, ignore it and add a warning:\n - `--since=YYYY-MM-DD` \u2014 override the window start date.\n - `--full` \u2014 ignore the marker and use a default lookback of 6 months.\n - `--limit=N` \u2014 cap the number of features investigated (parse `N` as an integer; ignore if not a valid integer).\n With no arguments, run **incrementally** from the marker.\n\n2. Set the durable directory to `docs/test-coverage/` (relative to the repo root) and the marker file to `docs/test-coverage/STATE.md`. This command deliberately does **not** use `get_docs_dir` \u2014 its default (`docs/tmp`) is ephemeral, and this report must be durable.\n\n3. Read `docs/test-coverage/STATE.md` if it exists. It records two values: `last_run_utc` (an ISO-8601 UTC timestamp) and `last_analyzed_commit` (a git commit SHA).\n\n4. Resolve the analysis window with this precedence:\n - If `--since=YYYY-MM-DD` was given, use `git log --since=<date>`.\n - Else if `STATE.md` provides `last_analyzed_commit`, use the commit range `<last_analyzed_commit>..HEAD`.\n - Else (first run, no marker), default to `git log --since=<3 months ago>` (mirrors the `/scan-tickets` default of 3 months). Format the date as `YYYY-MM-DD`. Example: if today is 2026-07-07, the default `--since` is `2026-04-07`.\n - `--full` overrides the above and uses a 6-month lookback (`--since=<6 months ago>`).\n\n5. Robustness of the marker: capture `head_sha` by running `git rev-parse HEAD`, and capture the current UTC timestamp now. These become the **new** marker values, but only write them after the report is successfully produced (Stage 4). If a stored `last_analyzed_commit` is not present in history (e.g. a rebase/rewrite), fall back to `git log --since=<the date part of last_run_utc>` and add a warning noting the fallback.\n\n6. Initialize tracking variables:\n - `features` = [] (one entry per shipped feature)\n - `warnings` = [] (per-item failures and fallbacks; the run never aborts on these)\n\n7. Display the resolved window, e.g. "Analyzing shipped features in `<range or --since date>` (HEAD = {head_sha})".\n\n## Stage 1 \u2014 Collect Shipped Features from Git History\n\n1. List merged commits in the resolved window with:\n ```bash\n git log <range> --first-parent --pretty=format:"%H|%h|%ad|%s" --date=short\n ```\n `--first-parent` yields roughly one entry per squashed PR merge.\n\n2. For each commit, extract the ticket key by matching `^BAPI-[0-9]+` against the subject. Group commits by ticket key. Commits with no ticket prefix (e.g. `Fix 500 on ...`) each become a standalone feature labeled as an "untracked change".\n\n3. For each group, collect the changed-file footprint across its commit(s) using `git show --stat <sha>` or `git diff --name-only`. This file footprint is the primary input to the per-feature investigation.\n\n4. Best-effort enrichment: for each ticket key, call the `get_ticket` MCP tool to fetch the ticket summary. This is **fail-open** \u2014 Jira tokens can be expired \u2014 so on any error, add a warning and continue without the summary. Do not abort.\n\n5. Build a `features` entry per group: `{ticket_key, subject, commit_shas, changed_files, jira_summary?}`. If `--limit=N` was given, keep only the first `N` features (most recent first).\n\n6. Display: "Found {count} shipped features to investigate."\n\n7. If `git log` returns no commits, skip to Stage 4 and write a report noting an empty window (and still refresh the marker).\n\n## Stage 2 \u2014 Investigate Each Feature (fan out subagents)\n\nFor each feature in `features`, launch an **Explore** subagent (batch several in parallel). Give each subagent the feature\'s `ticket_key`, `subject`, `changed_files`, and `jira_summary`, and instruct it to do read-only investigation only \u2014 no edits, no test design, no solutioning \u2014 and to return a structured finding.\n\nEach subagent must:\n\n1. Read the changed files and describe what the feature does in 2\u20134 sentences, with concrete `file:line` citations.\n\n2. Identify the feature\'s runtime surface \u2014 one or more of: real database operations (`postgres_client` / a DAL in `api/library/db/`), real LLM calls (`src/python/llms/ai_client.py`, `async_send_message_to_ai`), real FastAPI routing (a route handler under `api/routes/`), an MCP tool (`mcp_server/`), a shell-spawned / CLI flow, a frontend / Playwright surface, or pure logic / config / docs / tests.\n\n3. Check whether an **integration test already exists**: search `tests/integration/` for a mirror path or for references to the changed modules/functions. The reliable classifier is a path under `tests/integration/` plus `@pytest.mark.integration` or reliance on the `--run-integration` flag (conventions in `docs/claude/testing-integration.md`). Cite any test found.\n\n4. Classify the feature into exactly one `bucket`:\n - **`has_integration_test`** \u2014 already covered end-to-end; cite the existing integration test file.\n - **`integration_testable`** \u2014 no test yet, but the feature exercises real DB / LLM / routing and fits an existing `tests/integration/<area>/` pattern. Give a **high-level** approach only: which real entrypoint to call, which backend it would exercise, and the relevant cost/guard note (the gpt-5-nano override via `INTEGRATION_TEST_MODEL`; the local-DB `skipif` guard; `save_to_db=False`). Cite the entrypoint in code.\n - **`smoke_only`** \u2014 genuine end-to-end execution is possible but not as an automated integration test (e.g. MCP tool behavior inside a host, cross-platform terminal spawning, a headless agent session, or browser E2E). Describe how to smoke test it so it **genuinely executes the system**, citing the relevant runbook: the MCP smoke-test runbook under `mcp_server/smoke-test/`, `tests/mcp/`, `docs/claude/runbooks/self-install-smoke-test.md`, `docs/claude/runbooks/start-tickets-smoke-test.md`, or Playwright (`tests/playwright/`, which needs a running server plus `npm run build`).\n - **`not_testable`** \u2014 nothing to execute end-to-end (docs-only, a wording/comment change, pure config, or a test-only change); state why.\n\n5. Return a structured finding with these fields: `ticket_key`, `subject`, `description_with_cites`, `surface`, `bucket`, `existing_test`, `approach`, `why_not`.\n\nCollect all findings. If a per-feature subagent fails, add a warning and continue \u2014 never abort the whole run.\n\n## Stage 3 \u2014 Classify and Synthesize\n\n1. Deduplicate features that span multiple commits (merge by `ticket_key`).\n\n2. Sort each finding into the two required report sections:\n - **Section 1 \u2014 Integration Testing (covered or addable):** findings with `bucket` `has_integration_test` (sub-group "Already covered") or `integration_testable` (sub-group "Could be added").\n - **Section 2 \u2014 Not Integration-Testable:** findings with `bucket` `smoke_only` (sub-group "Smoke-testable \u2014 how") or `not_testable` (sub-group "Not testable \u2014 why").\n\n## Stage 4 \u2014 Write the Report and Update the Marker\n\n1. Create the `docs/test-coverage/` directory if it does not exist. Choose the report path `docs/test-coverage/REPORT-<YYYYMMDD>.md`; if a same-day file already exists, append `-<HHMMSS>` to avoid clobbering it.\n\n2. Write the report with this layout:\n - A title and a metadata block: generated-at UTC timestamp; the analysis window (`<from sha or since-date>` \u2192 `HEAD <head_sha>`); the feature count; and per-bucket tallies.\n - **Section 1 \u2014 Integration Testing: Covered or Addable.** One `### BAPI-NNN \u2014 <subject>` heading per feature, each with **What shipped** (with `file:line` citations), **Current coverage** (cite the existing integration test, or state "none"), and **How it could be integration tested (high level)**.\n - **Section 2 \u2014 Not Integration-Testable.** One heading per feature with the same feature description, plus **Why not integration-testable**, and \u2014 for `smoke_only` features \u2014 **How to smoke test (genuinely execute the system)** with the runbook citation.\n - A **Warnings** section listing each warning as a bullet \u2014 only if `warnings` is non-empty.\n\n3. **Only after** the report file is written successfully, update the marker `docs/test-coverage/STATE.md` with the new `last_run_utc` (the UTC timestamp captured in Stage 0) and `last_analyzed_commit` set to `head_sha`. This date/commit marker is what makes the next run incremental. If the report write fails, do not touch `STATE.md`.\n\n## Final Report\n\nPrint a short summary to chat:\n\n```\n**Test-coverage scan complete**\n\n* Features analyzed: {count}\n* Already covered by integration tests: {n_has}\n* Integration-testable (could be added): {n_addable}\n* Smoke-only: {n_smoke}\n* Not testable: {n_none}\n\nReport: docs/test-coverage/REPORT-<YYYYMMDD>.md\nMarker updated: last_analyzed_commit = {head_sha}\n```\n\nIf `warnings` is non-empty, add a "Warnings:" section listing each warning as a bullet. If there are no warnings, omit that section.\n',
|
|
15484
15484
|
"scan-tickets.md": '$ARGUMENTS\n\n---\n\n# Instructions\n\nSynchronize recently-updated Jira tickets with the local `tickets` database table and backfill missing workflow state timestamps. Perform all work directly in the main thread.\n\n## Stage 0 \u2014 Parse Arguments and Calculate Date\n\n1. Read the value of `$ARGUMENTS`. If it is empty, whitespace-only, or not a valid integer, default `months_back` to `3`. If it contains multiple tokens, extract only the first token and attempt to parse it as an integer. If parsing fails, default to `3`.\n\n2. Calculate `updated_since` by subtracting `months_back` months from today\'s date. Format the result as `YYYY-MM-DD`. Example: if today is 2026-03-07 and `months_back` is 3, then `updated_since` is 2025-12-07.\n\n3. Display the parsed values: "Scanning tickets updated since {updated_since} (months_back = {months_back})"\n\n4. Initialize the following tracking variables:\n - `tickets_scanned` = 0 (total tickets fetched from Jira)\n - `newly_tracked` = 0 (tickets inserted into database for the first time)\n - `state_updated_list` = [] (list of objects with ticket key and fields updated)\n - `warnings` = [] (list of warning strings for any per-ticket failures)\n\n## Stage 1 \u2014 Fetch All Tickets from Jira\n\n1. Initialize an empty list `all_tickets` and set `offset` to `0`.\n\n2. Enter a pagination loop:\n - Call the `get_tickets` MCP tool with: `updated_since` set to the calculated date, `limit` set to `100`, and `offset` set to the current offset value.\n - Parse the JSON response. The response contains a `tickets` array of ticket objects. Each ticket object has a `ticket_number` field (the Jira key, e.g., `BAPI-42`), along with `summary`, `status`, `issue_type`, `assignee`, and `updated_at`.\n - Append all tickets from the response\'s `tickets` array to `all_tickets`.\n - If the number of tickets returned in this page equals `100`, increment `offset` by `100` and repeat the loop.\n - If fewer than `100` tickets are returned, exit the loop.\n\n3. Set `tickets_scanned` to the length of `all_tickets`.\n\n4. Display: "Fetched {tickets_scanned} tickets from Jira. Processing..."\n\n5. If the `get_tickets` call fails at any point during pagination, **stop** and report the error. Do not proceed to Stage 2.\n\n## Stage 2 \u2014 Track Each Ticket\n\n1. Iterate over each ticket in `all_tickets`. For each ticket:\n - Call the `track_ticket` MCP tool with `ticket_number` set to the ticket\'s `ticket_number` field. If the ticket object includes a `summary` field, pass it as the `description` parameter.\n - Inspect the response message. If the response indicates the ticket was newly created/inserted (look for words like "created" or "inserted" in the message, as opposed to "already exists" or "updated"), increment `newly_tracked` by 1.\n - If the `track_ticket` call fails for this ticket, add a warning to the `warnings` list (e.g., "Warning: Failed to track ticket {ticket_number}: {error}") and **continue** to the next ticket. Do not abort the scan.\n\n2. Display a brief progress indicator every 25 tickets, e.g., "Tracked {N} of {tickets_scanned} tickets..."\n\n## Stage 3 \u2014 Detect and Backfill Workflow State\n\nDisplay: "Checking workflow state for {tickets_scanned} tickets..."\n\nIterate over each ticket in `all_tickets`. For each ticket (referenced by its `ticket_number` field), perform the following sub-steps. Wrap the entire per-ticket block in error handling: if the `get_ticket_state` call or the subsequent `update_ticket_state` call fails for a ticket, add a warning to `warnings` and continue to the next ticket.\n\n**Sub-step 4a \u2014 Retrieve current state**: Call the `get_ticket_state` MCP tool with `ticket_number` set to the ticket\'s key. The response contains:\n\n- Five timestamp fields (each is a timestamp string or null): `clarify_called`, `clarify_answered`, `critique_called`, `critique_answered`, `plan_generated`\n- Three boolean artifact flags: `has_clarifying_questions`, `has_critique`, `has_plan`\n\nIf the call returns a 404 or any error, add a warning to `warnings` and continue to the next ticket.\n\n**Sub-step 4b \u2014 Build fields_to_update list**: Initialize an empty `fields_to_update` list, then apply the following rules:\n\n- If `has_clarifying_questions` is `true` AND `clarify_called` is null -> add `"clarify_called"` to `fields_to_update`\n- If `has_clarifying_questions` is `true` AND `clarify_answered` is null -> add `"clarify_answered"` to `fields_to_update`\n- If `has_critique` is `true` AND `critique_called` is null -> add `"critique_called"` to `fields_to_update`\n- If `has_critique` is `true` AND `critique_answered` is null -> add `"critique_answered"` to `fields_to_update`\n- If `has_plan` is `true` AND `plan_generated` is null -> add `"plan_generated"` to `fields_to_update`\n\n**Sub-step 4c \u2014 Call update_ticket_state if needed**: If `fields_to_update` is non-empty, call the `update_ticket_state` MCP tool with `ticket_number` set to the ticket\'s key and `fields` set to the `fields_to_update` array. If this succeeds, add an entry to `state_updated_list` recording the ticket key and the list of fields that were set. If `update_ticket_state` fails, add a warning to `warnings` and continue.\n\nDisplay a progress indicator every 25 tickets that includes the current ticket key, e.g., "Checked state for {TICKET-KEY} ({N} of {tickets_scanned} tickets)"\n\n## Stage 4 \u2014 Report Summary\n\n1. Calculate `state_updated_count` as the length of `state_updated_list`.\n\n2. Display the summary:\n\n ```\n **Scan complete**\n\n * Tickets scanned: {tickets_scanned}\n * Newly tracked: {newly_tracked}\n * State updated: {state_updated_count}\n ```\n\n3. If `state_updated_list` is non-empty, display a section titled "Updated tickets:" with one bullet per ticket showing the ticket key and the comma-separated list of fields that were set. Example:\n\n ```\n Updated tickets:\n * BAPI-101: clarify_called, clarify_answered\n * BAPI-105: critique_called, critique_answered, plan_generated\n ```\n\n4. If the `warnings` list is non-empty, display a section titled "Warnings:" listing each warning string as a bullet. Example:\n\n ```\n Warnings:\n * Warning: Failed to track ticket BAPI-99: Connection timeout\n * Warning: State query failed for BAPI-112: SQL error\n ```\n\n5. If there are no warnings, do not display the "Warnings:" section.\n',
|
|
15485
15485
|
"start-tickets.md": '---\nschedulable: true\narguments: {"positionals":[{"name":"ticketKeys","type":"string","required":true,"variadic":true}],"flags":[{"name":"auto","flag":"--auto","type":"boolean"},{"name":"agent","flag":"--agent","type":"string"},{"name":"workflow","flag":"--workflow","type":"string"},{"name":"rounds","flag":"--rounds","type":"string"},{"name":"baseBranch","flag":"--base-branch","type":"string"},{"name":"maxParallel","flag":"--max-parallel","type":"string"},{"name":"dryRun","flag":"--dry-run","type":"boolean"}]}\n---\n\n# Start Tickets: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes one or more Jira ticket keys (e.g., `BAPI-248 BAPI-250`) and invokes the packaged `@bridge_gpt/mcp-server` CLI subcommand `start-tickets`, which creates a Worktrunk worktree for each key and opens one tab/session per worktree running the **selected agent** \u2014 Claude Code (`claude`) by default, or Cursor Agent (`cursor-agent`) via `--agent` \u2014 in a macOS Terminal/iTerm tab, a Windows Terminal tab (or PowerShell fallback window), or a detached Linux tmux session, chosen automatically by platform. It replaces Parts 2\u20135 of `docs/claude/parallel-worktrees.md` with a single command.\n\nBecause the orchestration ships inside the `@bridge_gpt/mcp-server` npm package (not a repo-local script), this command works for every consumer \u2014 including projects that installed the package via `--init`.\n\nFor existing ticket keys, `/review-and-start <KEYS>` is the **recommended front door**: it supplies the same connectivity check and branch enrichment as this command, then drives this same packaged CLI with `--workflow review-and-implement` so each worktree reviews the ticket before implementing it. Using `start-tickets --workflow review-and-implement` directly (documented below) remains available as the lower-level launcher seam.\n\nStage 0 and Stage 1 are critical (stop on failure). Stage 2 is non-critical (per-ticket enrichment failures fall back to the default branch and continue). Stage 3 is critical (propagate the packaged CLI\'s exit code).\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline that spawns N parallel Worktrunk worktrees and selected-agent sessions (Claude Code by default) via the packaged CLI. Execute all stages in sequence directly in the main thread.\n\n## Stage 0 \u2014 Argument Parsing and Connectivity Check\n\n1. **Parse `$ARGUMENTS`** into ticket keys, pass-through flags, and branch overrides:\n - **Ticket keys**: every whitespace-separated token matching `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). If zero keys are found, stop immediately and display:\n\n ```\n No ticket keys found in arguments. Expected one or more keys like BAPI-248.\n Usage: /start-tickets [flags] <KEY> [KEY ...] (e.g., /start-tickets BAPI-248 BAPI-250)\n ```\n\n - **Pass-through flags**: collect any of `--agent <name>` (and the equals form `--agent=<name>`), `--terminal terminal|iterm`, `--dry-run`, `--auto`, `--no-refresh-main`, `--base-branch <branch>` (and the equals form `--base-branch=<branch>`), and `--max-parallel N` that the user supplied. These are forwarded verbatim to the CLI in Stage 3. `--auto` makes each spawned agent run the selected workflow\'s slash command with `--auto` (hands-off); omit it to keep the spawned agents interactive.\n - **Selected agent**: track a `selected_agent` variable that defaults to `claude`. If the user passed `--agent <name>` / `--agent=<name>`, validate the value against the supported agents `claude` and `cursor-agent`, set `selected_agent` to it, and reject any other (malformed/unsupported) `--agent` value before proceeding. The agent is not auto-detected from the host editor \u2014 the user selects it explicitly (default `claude`).\n - **Selected workflow**: track a `selected_workflow` variable that defaults to `implement`. If the user passed `--workflow <value>` or `--workflow=<value>`, validate it against the two allowed values `implement` and `review-and-implement`, set `selected_workflow`, and reject any other value with the allowlist in the error. `implement` (the default) preserves today\'s behavior byte-for-byte \u2014 each spawned worktree runs `/implement-ticket <KEY> [--auto]`. `review-and-implement` spawns `/review-and-implement <KEY> [--auto] [--rounds=<n>]` instead, which runs `/review-ticket` then, after a per-ticket halt gate, `/implement-ticket` inside the same session. A single chain-level `--auto` applies to the selected workflow as a whole \u2014 under `review-and-implement` it auto-approves both the review and the implementation phase.\n - **Review rounds**: track a `review_rounds` value that defaults to unset. If the user passed `--rounds <n>` or `--rounds=<n>`, normalize it to `--rounds=1` or `--rounds=2` (reject any other value). `--rounds` is **review-only**: reject it (after parsing all flags, so flag order does not matter) if the final `selected_workflow` is not `review-and-implement`.\n - **User-supplied base branch**: track a `user_supplied_base_branch` boolean that defaults to `false`. If the user passed `--base-branch <branch>` or `--base-branch=<branch>`, set the boolean to `true` and capture the value. A user-supplied `--base-branch` value **takes precedence** over any value resolved from Bridge API config in Stage 2. Validate the user-supplied value before proceeding: after trimming surrounding whitespace it must be non-empty, at most 255 characters, must not start with `-`, and must not contain ASCII control characters (`0x00`\u2013`0x1F` or `0x7F`); reject any malformed value with a clear error.\n - **User branch overrides**: collect any user-supplied repeatable `--branch KEY=BRANCH` flags. A user-provided override always takes precedence over Stage 2 enrichment for that key.\n - Reject malformed input before proceeding: if a token looks like a flag but is not one of the supported flags, or a ticket key does not match `[A-Z]+-[0-9]+`, or a `--branch` value is not `KEY=BRANCH`, or `--agent` names an agent other than `claude`/`cursor-agent`, or `--workflow` names anything other than `implement`/`review-and-implement`, or `--rounds` is used outside `review-and-implement` or names anything other than `1`/`2`, or `--base-branch` fails the validation rules above, stop and report the malformed argument.\n\n2. **Connectivity check**: Call the `ping` MCP tool (no parameters). If the ping fails or does not return `"status": "ok"`, stop immediately and display:\n\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\nThis stage is **critical** \u2014 stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 \u2014 Acknowledge CLI Pre-flight\n\nThe packaged CLI runs its own per-platform pre-flight checks and then fetches `origin` and fast-forwards the local **configured base branch** (the value resolved in Stage 2 below, or `main` when none is configured) from `origin/<base>` so the new worktrees are based on an up-to-date base. The historical flag `--no-refresh-main` still controls this behavior \u2014 the flag name is preserved for backward compatibility, but it now skips refresh of whatever base branch resolves (default `main`). The required commands depend on the OS:\n\n- **macOS**: `wt`, `git`, `osascript`.\n- **Windows**: `git-wt`, `git`, Git for Windows / Git Bash (Worktrunk runs its `pre-start` / `post-start` hooks via Git Bash), and Windows Terminal **or** PowerShell.\n- **Linux**: `wt`, `git`, `tmux`.\n\nOn **Windows** the Worktrunk binary is `git-wt` (its winget alias), which is a different tool from Windows Terminal\'s `wt.exe`: the CLI uses `git-wt` to **create worktrees** and `wt.exe` to **open a tab**, and never conflates the two. On **Linux** the CLI opens one detached `tmux` session per ticket (a window is added if that ticket\'s session already exists); attach later with `tmux attach -t <session>`. An unsupported OS (not macOS/Windows/Linux) fails fast with a clear "unsupported platform" message.\n\nThis stage simply notes that the CLI will fail fast if any prerequisite is missing or if local `main` has diverged from `origin/main` \u2014 you do not need to verify anything separately here, and you must not run any pre-flight commands yourself. When the CLI\'s pre-flight fails it now hints the user to run the read-only diagnostics command `npx -y @bridge_gpt/mcp-server doctor`, which reports found/missing for every prerequisite on the current OS \u2014 the pre-flight set plus `uv` plus the selected agent\'s command \u2014 and prints the manual install command for each missing one. `doctor` is strictly read-only and never installs anything; never run install commands automatically on the user\'s behalf. The CLI does not call any Bridge API tools; all credential-bearing work (branch enrichment in Stage 2) stays in this command. Proceed to Stage 2.\n\nThe packaged CLI also performs **secret-free Bridge API MCP provisioning** inside each created worktree: synchronously after the worktree is created and **before the agent tab/session is opened**, it writes both `.mcp.json` (Claude Code) and `.cursor/mcp.json` (Cursor) pointing at the `mcp-invoke` shim. These registrations are **secret-free** \u2014 they contain no `env` block and no API key, because the shim resolves credentials at runtime. If a spawned agent (or difficulty\u2192model routing) reports missing Bridge API credentials, fix it by rerunning `/install-bridge` (its final stage persists the routing credential), by running `npx -y @bridge_gpt/mcp-server credentials migrate-agent-config --write-credentials` to migrate a key that lives only in `.mcp.json` / `.cursor/mcp.json`, or by adding a `bapi:<repo>` entry to the user-scoped credentials file (`~/.config/bridge/credentials.json`) \u2014 never by putting `BAPI_API_KEY` into the worktree `.mcp.json` or `.cursor/mcp.json` (that env is invisible to the Bash-spawned CLI).\n\nThis stage is **critical** in the sense that the CLI will abort if its pre-flight fails; you will see the error in Stage 3\'s output and must surface it.\n\n## Stage 2 \u2014 Resolve Base Branch + Enrich Branch Names (best-effort)\n\n### Stage 2a \u2014 Resolve configured `base_branch`\n\nThe CLI must be told which branch to cut new worktrees from. Resolution order:\n\n1. If `user_supplied_base_branch` from Stage 0 is `true`, **skip the config-field lookup entirely** and use the user-supplied value. The user\'s explicit `--base-branch` always wins; never call `config_field` for `base_branch` in that case.\n2. Otherwise, call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to `base_branch` (do not pass any other parameters; the tool resolves the repository from the MCP server\'s configured `BAPI_REPO_NAME`).\n3. Parse the response. Treat the result as the **configured base branch** only when the response is a JSON object whose `value` field is a non-empty string after trimming surrounding whitespace.\n4. Treat **all** of the following as "unset" \u2014 emit a single-line warning like `Warning: base_branch is unset; CLI will default to main` and **omit** the `--base-branch` flag entirely from the Stage 3 command (the CLI\'s own default is `main`):\n - `value` is `null`.\n - `value` is an empty string or a whitespace-only string.\n - The endpoint returns HTTP `400` (invalid field \u2014 happens before the registry includes `base_branch`).\n - The tool returns a network error, timeout, or non-JSON parse failure.\n - Any other lookup failure.\n5. When the configured value is usable, capture it in a `resolved_base_branch` variable. **Do not** stop the pipeline on a lookup failure; fall through to the CLI default.\n\nWhen forwarding `resolved_base_branch` into the Bash invocation in Stage 3, **shell-escape it safely**: replace every literal single quote `\'` in the value with the four-character sequence `\'\\\'\'`, then wrap the entire resulting string in single quotes (so the final argument looks like `\'<escaped-value>\'`). This is the standard POSIX single-quote escaping rule and is **mandatory** because `base_branch` is admin-configurable data that gets interpolated into a Bash command string; any unescaped single quote would otherwise break out of the surrounding quotes. Pass `--base-branch \'<escaped-value>\'` to the CLI as a single argv element \u2014 never expand the value unquoted into the command line.\n\n### Stage 2b \u2014 Enrich Branch Names\n\nBranch enrichment happens here, in the command, **before** invoking the CLI \u2014 the `get_ticket` MCP tool runs inside the MCP server process, which holds the Bridge API credentials the shell-spawned CLI does not have. For each parsed ticket key that does **not** already have a user-provided `--branch` override:\n\n1. Call the `get_ticket` MCP tool with `ticket_number` set to the key and `save_locally` set to `false`.\n2. From the response, extract the `summary` field. Slugify it: lowercase the string, replace every run of non-alphanumeric characters (`[^a-z0-9]+`) with a single dash `-`, trim leading and trailing dashes, and truncate to at most `40` characters (cutting at a dash boundary if possible).\n3. The enriched branch name is `feature/<KEY>-<slug>`. Example: `BAPI-248` with summary `"Add PR rating pre-evaluation step"` becomes `feature/BAPI-248-add-pr-rating-pre-evaluation-step` (trimmed at 40 chars).\n4. If the `get_ticket` call fails for a particular key (404, network error, missing summary) or produces an empty slug, emit a single-line warning like `Warning: could not enrich BAPI-248, falling back to feature/BAPI-248` and let the CLI apply its default `feature/<KEY>` for that key only. Do NOT stop the pipeline.\n5. Build a list of `--branch <KEY>=<BRANCH>` arguments \u2014 one entry per key whose enrichment succeeded \u2014 and merge it with any user-provided overrides from Stage 0. **Do not** call `get_ticket` for keys that already have a user-provided override; those overrides win.\n\nThis stage is **non-critical** \u2014 warnings are acceptable, the pipeline continues with the fallback default for any key that fails. Do not call the Bridge API from the CLI itself; the CLI never has credentials.\n\n## Stage 3 \u2014 Invoke the Packaged CLI\n\nUse the **Bash tool** to invoke the packaged CLI. Build the command line as:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets <pass-through-flags> <base-branch-flag> <branch-overrides> <ticket-keys>\n```\n\nWhere:\n- `<pass-through-flags>` are the supported flags collected in Stage 0 (`--agent`, `--terminal`, `--dry-run`, `--auto`, `--no-refresh-main`, `--max-parallel`), forwarded verbatim. Forward `--agent <name>` only if the user supplied it; otherwise omit it and the CLI defaults to `claude`. Forward `--auto` only if the user supplied it.\n- Forward `--workflow <selected_workflow>` only when the user explicitly passed `--workflow`; otherwise omit it and the CLI defaults to `implement`. Forward the normalized `--rounds=<n>` from Stage 0 only when the user supplied it (which Stage 0 already guarantees is only possible under `review-and-implement`).\n- `<base-branch-flag>` is `--base-branch \'<escaped-value>\'` (single-quoted using the Stage 2a escaping rule) **only when** the user supplied `--base-branch` in Stage 0 **or** Stage 2a\'s `config_field` lookup returned a non-empty configured value. When the configured value is unset / lookup fails / user did not supply one, **omit this flag entirely** so the CLI\'s own default (`main`) takes effect.\n- `<branch-overrides>` is the list of `--branch KEY=BRANCH` flags assembled in Stage 2 (enrichment results merged with user overrides; omit any key whose enrichment failed and had no user override).\n- `<ticket-keys>` is the original list of ticket keys parsed in Stage 0, space-separated and in the original order.\n\nExample for two tickets after successful enrichment, throttled to 2 concurrent worktrees:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets \\\n --max-parallel 2 \\\n --branch BAPI-248=feature/BAPI-248-add-pr-rating-pre-evaluation-step \\\n --branch BAPI-250=feature/BAPI-250-deep-research-durability \\\n BAPI-248 BAPI-250\n```\n\nExample launching Cursor Agent instead of the default Claude Code:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248\n```\n\nExample cutting worktrees from a non-`main` base (either user-supplied via `--base-branch develop` in Stage 0 or resolved from Bridge API config in Stage 2a):\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --base-branch develop BAPI-248\n```\n\nExample using the lower-level review-and-implement workflow directly (the `/review-and-start` command is the recommended front door for this; this form is documented here as the advanced launcher seam it drives):\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --workflow review-and-implement --auto --rounds=2 BAPI-248\n```\n\nPass through the CLI\'s stdout and stderr to the user verbatim. If the CLI exits non-zero, treat that as a critical failure: report the exit code and the CLI\'s error output, and stop.\n\nThis stage is **critical** \u2014 propagate any non-zero exit from the packaged CLI.\n\n## Stage 4 \u2014 Final Report\n\nOnce the CLI exits 0, parse its `Summary` section (one stable line per ticket in the form `KEY branch=BRANCH status=STATUS`, with an optional trailing `path=PATH`) and reformat it as a markdown table:\n\n```\n| Ticket | Branch | Status |\n|----------|-----------------------------------------------------|----------|\n| BAPI-248 | feature/BAPI-248-add-pr-rating-pre-evaluation-step | spawned |\n| BAPI-250 | feature/BAPI-250-deep-research-durability | spawned |\n```\n\nStatus values are `dry-run`, `spawned`, `create-failed`, and `spawn-failed`. This table (and the report as a whole) describes **worktree/spawn status only** \u2014 it must never claim that review or implementation itself has completed; that work happens later, independently, inside each spawned session.\n\nCompute `spawned_command` from `selected_workflow`: `/implement-ticket <KEY>` when `implement` (the default), or `/review-and-implement <KEY>` when `review-and-implement`. Append `--auto` when the user passed it, and (workflow `review-and-implement` only) append the normalized `--rounds=<n>` when the user supplied `--rounds`. End the report with the worktree-first explanation, rendered for the tracked `selected_agent` and `spawned_command`. When `selected_agent` is `claude` (the default):\n\n```\nThe CLI created/switched each Worktrunk worktree first (throttled by --max-parallel),\nthen opened one tab/session per successful worktree (macOS Terminal/iTerm tab, Windows\nTerminal tab or PowerShell window, or Linux tmux session). Each one runs\n`claude \'<spawned_command>\'` inside its already-created worktree, which launches\nClaude Code with the starter prompt as its first message. Switch to each tab \u2014 or on\nLinux run `tmux attach -t <session>` \u2014 to monitor.\n```\n\nWhen `selected_agent` is `cursor-agent`, render the same explanation but with the Cursor handoff \u2014 do **not** claim it launches Claude Code:\n\n```\nThe CLI created/switched each Worktrunk worktree first (throttled by --max-parallel),\nthen opened one tab/session per successful worktree (macOS Terminal/iTerm tab, Windows\nTerminal tab or PowerShell window, or Linux tmux session). Each one runs\n`cursor-agent \'<spawned_command>\'` inside its already-created worktree, which\nlaunches Cursor Agent with the starter prompt as its first message. Switch to each\ntab \u2014 or on Linux run `tmux attach -t <session>` \u2014 to monitor.\n```\n\nThe spawned command is identical for both agents; only the launched agent binary differs. Under `review-and-implement`, each spawned session independently runs `/review-ticket`, pauses at its own per-ticket halt gate (unless chain-level `--auto` was passed), and only then runs `/implement-ticket` \u2014 do not report that review or implementation succeeded from this parent session.\n\nIf the CLI reported any `create-failed` or `spawn-failed` statuses, or Stage 2 emitted any enrichment warnings, list them under a `Warnings:` heading at the bottom of the report. If there were none, omit that section.\n\nSee `docs/claude/parallel-worktrees.md` for the deep-dive runbook and the Worktrunk verification result behind this worktree-first model.\n\n## Difficulty-Based Implementation-Model Routing\n\nBefore launching the interactive agent for each ticket, the packaged CLI selects an\nimplementation **model tier** from the ticket\'s `difficulty` rating (1-10) and injects\nit as a `--model` flag at the agent spawn boundary. This happens entirely inside the\nCLI \u2014 it is **not** part of the server-side `/implement-ticket` recipe, because the\nmodel an interactive agent session uses is fixed at the moment the process is launched.\n\n- **Tier ladder (fixed):** `difficulty 1-2 \u2192 cheap`, `3-5 \u2192 basic`, `6+ \u2192 premium`.\n- **Separation of concerns:** the Python backend returns only the coarse tier\n (`cheap`/`basic`/`premium`) via `GET /jira/tickets/{KEY}/model-tier`; difficulty is\n computed on demand and cached when absent. The TypeScript CLI alone maps a tier to\n the agent-specific model alias (`claude`: `haiku`/`sonnet`/`opus`; `cursor-agent`:\n version-suffixed strings validated against `cursor-agent --list-models`).\n- **Per-repo config:**\n - `difficulty_model_routing_enabled` \u2014 boolean, **default ON**. Set to `false` to\n disable routing for a repo (the CLI then omits `--model`).\n - `difficulty_model_tier_overrides` \u2014 a JSON object mapping a tier name to a model\n alias (e.g. `{"premium": "opus"}`), **not** raw CLI arguments. Only `cheap`,\n `basic`, and `premium` keys are accepted; aliases must match `^[A-Za-z0-9._:-]+$`.\n- **Fail-open:** routing never aborts a spawn. Credential, network, config, or\n no-tier routing failures **assume a hard ticket and default to the premium/Opus\n tier** when the selected agent supports a valid premium alias; routing being\n disabled (`difficulty_model_routing_enabled = false`) or an agent that does not\n support `--model` instead omit `--model` so the agent runs on its own default\n model. Each degraded case is surfaced as exactly one secret-free, per-ticket\n routing-diagnostic line, never a hard failure.\n\n### Model routing credential\n\nDifficulty\u2192model routing needs Bridge API credentials, and the shell-spawned\n`start-tickets` CLI is a **different runtime surface** from the MCP server: a\n`BAPI_API_KEY` that lives only in `.mcp.json` / `.cursor/mcp.json` is visible to\nthe MCP server but **not** to the Bash-spawned CLI, so routing silently degrades.\nThe durable source of truth both runtimes can resolve is the user-scoped store\n`~/.config/bridge/credentials.json`, keyed `bapi:<repo>`. If a routing-diagnostic\nline reports the credential is missing (e.g. difficulty resolves as `?`), fix it\nby any one of:\n\n1. Rerun `/install-bridge` \u2014 its final stage now persists the validated routing\n credential into `~/.config/bridge/credentials.json` via the\n `persist_routing_credential` tool.\n2. Migrate a key that lives **only** in `.mcp.json` / `.cursor/mcp.json` into the\n user-scoped store with the consent-gated, one-shot command:\n\n ```\n npx -y @bridge_gpt/mcp-server credentials migrate-agent-config --write-credentials\n ```\n\n3. Manually add `BAPI_API_KEY` under the `bapi:<repo>` target in the user-scoped\n store `~/.config/bridge/credentials.json`.\n\nNever put `BAPI_API_KEY` into a worktree `.mcp.json` / `.cursor/mcp.json` as a fix \u2014\nthat env is invisible to the spawned CLI.\n\n## Conductor observability (opt-in via `--conductor`, BAPI-394)\n\nConductor is **opt-in**. By default `start-tickets` spawns the plain\n`cd <worktree> && <agent> \'/implement-ticket <KEY> [--auto]\'` \u2014 no\n`BAPI_CONDUCTOR_*` env, no supervisor window, and no message-relay instruction.\nPass `--conductor` (e.g. `/start-tickets --conductor BAPI-123`) to enable the\nConductor system below.\n\nWith `--conductor`, a run mints a single conductor `run_id` and attributes each\nworker\'s lifecycle events by `worker_id`, ticket key, and worktree path, and a\nsupervisor peer tab is opened. When the selected agent is **Claude Code**, the CLI\ninjects a conductor lifecycle hook into each created worktree\'s\n`.claude/settings.local.json` so the spawned session emits local `run.started` /\n`run.stopped` / `agent.notification` (and, when\n`BAPI_CONDUCTOR_ENABLE_PRE_TOOL_USE=1`, `tool.intent`) events into the local\nconductor ledger. These hooks apply **only** when the selected agent is Claude\nCode; other agents (e.g. `cursor-agent`) still participate in the run-level\n`run.started` event but receive no per-worktree Claude hook. Inspect the ledger\nwith the `conductor` CLI (e.g. `conductor doctor`). Conductor observability is\nbest-effort and never blocks or aborts a spawn.\n\nAlso under `--conductor`, each worker is launched with an explicit instruction to\ncall the `check_messages` MCP tool at checkpoints, so the supervisor can pass it\ntyped guidance mid-run (BAPI-397). Delivery is **cooperative** \u2014 the worker polls\nand acknowledges messages and they are never injected into a running session.\n(Epic-tick dispatch always runs with conductor enabled, independent of this\nuser-facing flag.)\n',
|
|
15486
15486
|
"teach-bridge.md": 'Update a Bridge API configuration field via a natural-language teaching.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command takes a natural-language teaching (e.g., "use data-testid selectors in Playwright tests") and updates the appropriate Bridge API configuration field. The teaching is auto-classified to the correct field, merged with existing content as actionable AI instructions, and uploaded after user confirmation.\n\n`$ARGUMENTS` is required \u2014 it is the teaching text. If `$ARGUMENTS` is empty, show:\n\n```\nUsage: /teach-bridge <teaching>\n\nExamples:\n /teach-bridge use data-testid selectors in Playwright tests\n /teach-bridge always validate input DTOs with Pydantic before passing to service layer\n /teach-bridge prefer composition over inheritance for service classes\n```\n\nIf any stage fails, stop immediately and report which stage failed and why.\n\n## Stage 0 \u2014 Preflight\n\n1. **Validate arguments**: If `$ARGUMENTS` is empty or contains only whitespace, display the usage instructions above and stop.\n\n2. **Admin check**: Call the `get_my_role` MCP tool (no parameters). Inspect the response:\n - If `role` is `"admin"` OR `source` is `"legacy"`: proceed normally.\n - Otherwise: stop immediately and display:\n ```\n Admin access required. Your API key has role "<role>" (source: <source>).\n Only admin keys and legacy shared keys can update configuration fields.\n Contact your project administrator to request admin access.\n ```\n\nIf this stage fails, stop immediately and report the error. Do not proceed to Stage 1.\n\n## Stage 1 \u2014 Classify\n\n1. **List available fields**: Call the `config_field` MCP tool with `operation` set to `"list"` (no other parameters). This returns all available configuration field names with descriptions.\n\n2. **Evaluate the teaching**: Compare the user\'s teaching (`$ARGUMENTS`) against each field\'s description to determine which field it applies to.\n\n3. **Handle classification outcomes**:\n - **Clear single match**: If one field is clearly the best target, proceed to Stage 2 with that field.\n - **Multiple plausible matches**: If 2-3 fields are equally plausible, present them to the user with their descriptions and ask which one to update. Wait for user input before proceeding.\n - **No confident match**: If you cannot confidently map the teaching to any field, ask the user to elaborate or specify which field they intend. Wait for user input before proceeding.\n\n## Stage 2 \u2014 Merge\n\n1. **Read current value**: Call the `config_field` MCP tool with `operation` set to `"get"` and `field_name` set to the selected field from Stage 1. Capture the current value, description, and examples from the response.\n\n2. **Draft the update**:\n - **If the field is currently null or empty**: Compose initial content from the teaching. Rephrase the user\'s input as imperative, agent-facing instructions (e.g., convert "I want you to use data-testid" to "Always use `data-testid` attributes for Playwright element locators"). Do not use the user\'s exact conversational text.\n - **If the field has existing content**: Merge the teaching into the existing value at the most appropriate location. Rephrase as imperative, agent-facing instructions. Preserve the existing structure and formatting.\n\n3. **Handle contradictions**: If the teaching contradicts existing instructions in the field, present both the existing instruction and the new teaching side-by-side and ask the user which should take precedence. Wait for user input before proceeding.\n\n## Stage 3 \u2014 Confirm and Upload\n\n1. **Show the proposed update**: Display to the user:\n - **Field**: The name of the field being updated\n - **Change summary**: A brief description of what was added or changed\n - **Full proposed value**: The complete new value for the field (not just the diff)\n\n2. **Wait for confirmation**: Ask the user to confirm, request edits, or abort.\n\n3. **On confirmation**: Call the `config_field` MCP tool with:\n - `operation`: `"update"`\n - `field_name`: the selected field name\n - `value`: the full merged value (pass inline, do not use `file_path`)\n\n Display a success message confirming the update.\n\n4. **On rejection**: Ask the user what they\'d like to change. If they provide edits, revise the proposed value and show it again. If they abort, stop without making any changes.\n',
|
|
@@ -15578,6 +15578,11 @@ Fail-open posture: a degraded regression-check run still yields a report, with t
|
|
|
15578
15578
|
}
|
|
15579
15579
|
};
|
|
15580
15580
|
|
|
15581
|
+
// src/docs.generated.ts
|
|
15582
|
+
var DOCS = {
|
|
15583
|
+
"docs/mcp-tool-integrations.md": '# MCP tool integrations \u2014 the human "why" behind the capability report\n\nThis catalog is **explanatory prose only**. It exists so the `/install-bridge`\ncapability report can cite a human-readable "why" for each gate. It is **not** a\nsource of truth for gating: the server computes every `locked_tools` /\n`unlocked_tools` membership decision itself and the agent must never recompute a\ntool\'s dependencies from this document.\n\n**Authoritative source of gating.** The enforced rules \u2014 which tools are blocked,\nwhich are degraded, and what each requires \u2014 live in\n`api/library/vcs/vcs_route_operations.py`:\n\n- `VCS_ROUTE_REQUIREMENTS` \u2014 routes that **BLOCK** (are unavailable) without a\n VCS connection.\n- `VCS_ROUTE_WARNINGS` \u2014 routes that **DEGRADE** (stay usable, but without\n codebase context) without a VCS connection.\n- `NEVER_GATED_ROUTE_KEYS` \u2014 routes that are never gated on any integration.\n- `INDEX_REQUIRED_ROUTE_KEYS`, `INDEX_REQUIRED_BRAINSTORM_MODES`,\n `CREATE_DOC_CODEBASE_CONTEXT_DOC_TYPES`, `CREATE_DOC_WARN_DOC_TYPES` \u2014 the\n conditional "requires a successful code index" dimension.\n- The resolver helpers `get_required_vcs_operation()`, `get_warn_vcs_operation()`,\n and `requires_successful_index()` are the authoritative functions that decide a\n case. The capability report is derived from these; this catalog explains them.\n\n## Reading the capability report\n\nEach tool entry the server returns has the exact shape\n`{tool, effect, missing, semantics}`:\n\n- **`effect`**\n - **`BLOCK`** \u2014 the tool is **unavailable** until every listed dependency is\n met. It will refuse to run without them.\n - **`DEGRADE`** \u2014 the tool is **usable right now**, but **without codebase\n context** (it cannot ground its output in your repository). Connecting the\n listed dependency upgrades it from "works blind" to "works with full context".\n A `DEGRADE` tool is never "failed".\n- **`missing`** \u2014 the server-computed dependency identifiers still needed:\n integration ids such as `github_app` / `vcs_access_token`, and the synthetic\n `code_index` (a successful repository index).\n- **`semantics`**\n - **`all_of`** \u2014 every id in `missing` is required.\n - **`any_of`** \u2014 the VCS-provider candidates in `missing` are alternatives:\n **either** `github_app` **or** `vcs_access_token` satisfies the VCS\n requirement (this is the "provider unknown" case). When `code_index` also\n appears, it remains separately required \u2014 `semantics` describes only the VCS\n provider candidates, and a code index is always mandatory in addition.\n\nThe three readiness dimensions `configured` / `learned` / `indexed` are reported\nindependently. `indexed` may be `true`, `false`, or `null` \u2014 a `null` means the\nindex status could not be confirmed and must **not** be read as "indexed".\n\n## The integrations\n\n| Integration id | What it is | What it unlocks |\n| --- | --- | --- |\n| `jira` | Jira API access | Ticket reads/writes, estimation and review automations, status transitions. |\n| `github_app` | GitHub App installation | Pull requests, code review, and private-repo parsing on GitHub projects. |\n| `vcs_access_token` | VCS access token | Pull requests, code review, and private-repo parsing on Bitbucket projects. |\n| `vcs_webhook` | VCS webhook secret | Merge webhooks and CI follow-up triggers. |\n| `code_index` | A successful repository index | Codebase-grounded planning, architecture, reimplementation, and technical/discovery brainstorms. Produced by `/parse-repository`. |\n\nA project\'s `github_app` **or** `vcs_access_token` provides the VCS connection;\nwhich one applies depends on the project\'s version-control system. When the\nproject\'s provider is unknown, either credential satisfies the requirement \u2014 the\nreport expresses that as `semantics: any_of`.\n\n## The gates, by capability\n\n### Pull requests and CI (BLOCK on VCS)\n\nTools like `create_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, and\n`materialize_fresh_base` are **unavailable** (`BLOCK`) until a VCS connection is\nconfigured. They act directly on the version-control host, so without a\nconnection there is nothing for them to talk to.\n\n### Repository indexing and maps (BLOCK on VCS)\n\n`parse_repository` and `regenerate_directory_map` need a VCS connection to read\nthe repository. They **BLOCK** until VCS is connected.\n\n### Codebase-grounded generation (BLOCK on VCS **and** a code index)\n\nPlanning and architecture tools \u2014 `generate_plan_direct`,\n`generate_architecture_direct`, `request_reimplement_context`,\n`code_writer_generate_plan`, `code_writer_generate_architecture`, and\n`create_doc` for **TDD** / **architecture** documents \u2014 ground their output in\nyour indexed codebase. They **BLOCK** until BOTH a VCS connection AND a\nsuccessful code index exist (`all_of`, with `code_index` in `missing`).\n\n### Brainstorms (BLOCK on a code index, mode-dependent)\n\n`request_brainstorm` in **technical** or **discovery** mode searches your indexed\ncodebase, so it **BLOCK**s on `code_index`. **Design**-mode brainstorming never\nqueries the index and is never gated.\n\n### Document generation that DEGRADEs (usable without codebase context)\n\nTools like `generate_prd_direct`, `generate_fsd_direct`,\n`code_writer_generate_fsd`, `generate_clarifying_questions_direct`,\n`generate_ticket_critique_direct`, `generate_ticket_review_direct`, and\n`create_doc` for **PRD** / **FSD** documents **DEGRADE** rather than block: they\nrun today from the ticket alone, and connecting VCS simply lets them ground their\noutput in your codebase. They always appear under "Tools you can use now", with a\nreduced-context caveat when the VCS connection is missing.\n\n### Never gated\n\nSetup and bootstrap tools (`ping`, `config_field`, `get_install_manifest`,\n`apply_install_manifest`, `get_my_role`, `persist_routing_credential`,\n`get_docs_dir`, and the bootstrap-invite exchange) are always available \u2014 they\nare how you configure everything else.\n'
|
|
15584
|
+
};
|
|
15585
|
+
|
|
15581
15586
|
// src/init.ts
|
|
15582
15587
|
init_version_generated();
|
|
15583
15588
|
|
|
@@ -15934,6 +15939,29 @@ Slash commands: scaffolded ${total} commands into ${commandDirs.length} director
|
|
|
15934
15939
|
if (overwrittenFiles.size > 0) console.log(` Overwritten (content changed): ${overwrittenFiles.size}`);
|
|
15935
15940
|
if (skippedFiles.size > 0) console.log(` Skipped (unchanged): ${skippedFiles.size}`);
|
|
15936
15941
|
console.log(` ${dirNames}`);
|
|
15942
|
+
const docsWritten = /* @__PURE__ */ new Set();
|
|
15943
|
+
const docsSkipped = /* @__PURE__ */ new Set();
|
|
15944
|
+
for (const [relTarget, content] of Object.entries(DOCS)) {
|
|
15945
|
+
const target = path5.join(cwd, relTarget);
|
|
15946
|
+
await mkdir2(path5.dirname(target), { recursive: true });
|
|
15947
|
+
try {
|
|
15948
|
+
const existing = await readFile3(target, "utf-8");
|
|
15949
|
+
if (existing === content) {
|
|
15950
|
+
docsSkipped.add(relTarget);
|
|
15951
|
+
continue;
|
|
15952
|
+
}
|
|
15953
|
+
} catch {
|
|
15954
|
+
}
|
|
15955
|
+
await writeFile2(target, content, "utf-8");
|
|
15956
|
+
docsWritten.add(relTarget);
|
|
15957
|
+
}
|
|
15958
|
+
const docsTotal = Object.keys(DOCS).length;
|
|
15959
|
+
if (docsTotal > 0) {
|
|
15960
|
+
console.log(`
|
|
15961
|
+
Documentation: scaffolded ${docsTotal} doc asset${docsTotal === 1 ? "" : "s"}`);
|
|
15962
|
+
if (docsWritten.size > 0) console.log(` Written: ${[...docsWritten].join(", ")}`);
|
|
15963
|
+
if (docsSkipped.size > 0) console.log(` Skipped (unchanged): ${[...docsSkipped].join(", ")}`);
|
|
15964
|
+
}
|
|
15937
15965
|
const agentWritten = /* @__PURE__ */ new Set();
|
|
15938
15966
|
const agentSkipped = /* @__PURE__ */ new Set();
|
|
15939
15967
|
const agentOverwritten = /* @__PURE__ */ new Set();
|
|
@@ -18996,11 +19024,11 @@ function resolveExecutorJobBaseBranch(job, fallbackBaseBranch) {
|
|
|
18996
19024
|
error: "job payload base_branch is present but is not a string branch name."
|
|
18997
19025
|
};
|
|
18998
19026
|
}
|
|
18999
|
-
const
|
|
19000
|
-
if (
|
|
19027
|
+
const validationError2 = validateBranchName(raw);
|
|
19028
|
+
if (validationError2) {
|
|
19001
19029
|
return {
|
|
19002
19030
|
ok: false,
|
|
19003
|
-
error: `job payload base_branch is not a valid branch name: ${
|
|
19031
|
+
error: `job payload base_branch is not a valid branch name: ${validationError2}`
|
|
19004
19032
|
};
|
|
19005
19033
|
}
|
|
19006
19034
|
return { ok: true, baseBranch: raw };
|
|
@@ -21591,7 +21619,7 @@ function buildPrewarmArgs() {
|
|
|
21591
21619
|
function buildPrewarmCommandPreview() {
|
|
21592
21620
|
return `npx ${buildPrewarmArgs().join(" ")}`;
|
|
21593
21621
|
}
|
|
21594
|
-
var INSTALL_BRIDGE_AGENT_PROMPT = "
|
|
21622
|
+
var INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command in the install-spawn context (tell the command it is running in the install-spawn context so it SKIPS its Stage 8 and Stage 9 offers \u2014 this session's only closing interaction is the single indexing question below). Do NOT run /learn-repository. Do NOT call parse_repository (or otherwise start indexing) before the capability report and explicit consent below. Complete the command's read-after-write five-section capability report first: 'Connected \u2713', 'Not yet connected \u2717', 'Tools you can use now', 'Tools you'll unlock', and 'Recommended next step + why'. Only AFTER that report is fully presented, ask exactly one question using this visible prompt: '[Y/n] Index repository now?'. Only an explicit affirmative answer (e.g. 'y'/'yes') starts indexing; a blank answer, a negative answer, EOF, an unavailable interaction, and any non-interactive/headless run all resolve to NO. On an affirmative answer: call the parse_repository MCP tool exactly once, describe the accepted job as QUEUED, and direct later progress checks to get_parse_status or /check-parse-status (do NOT poll it to completion). If parse_repository returns a blocking refusal or error, do NOT claim the job was queued \u2014 report the sanitized result and leave indexing pending. On NO (or any unavailable/non-interactive resolution): do not index; print the exact copy-paste continuation command '/parse-repository' on its own line and state that indexing remains pending. Never request, echo, or transport any credential \u2014 only ever direct the human to the setup UI via the command's configure_in pointer. End with an explicit summary line stating how many config fields the apply_install_manifest call applied (e.g. 'Applied 8 of 9 derived fields') and whether indexing was queued or left pending \u2014 if 0 fields were applied, say so loudly and explain what is still pending.";
|
|
21595
21623
|
var DEFAULT_BAPI_BASE_URL2 = "https://bridgegpt-api.com";
|
|
21596
21624
|
var DEFAULT_BAPI_DOCS_DIR = "docs/tmp";
|
|
21597
21625
|
function getInstallBridgeUsage() {
|
|
@@ -21602,7 +21630,7 @@ function getInstallBridgeUsage() {
|
|
|
21602
21630
|
"One-command Bridge API project bootstrap. Scaffolds the project, writes the",
|
|
21603
21631
|
"per-host MCP config with your credentials, verifies connectivity, persists the",
|
|
21604
21632
|
"routing credential, then opens a fresh agent session to derive the remaining",
|
|
21605
|
-
"config and
|
|
21633
|
+
"config, present a capability report, and offer optional repository indexing.",
|
|
21606
21634
|
"",
|
|
21607
21635
|
"Inputs (the only two irreducible ones):",
|
|
21608
21636
|
" --api-key <key> Bridge API key. Falls back to the BAPI_API_KEY env var,",
|
|
@@ -21611,18 +21639,35 @@ function getInstallBridgeUsage() {
|
|
|
21611
21639
|
" key, it does not create one (--invite is the one exception:",
|
|
21612
21640
|
" it CREATES the project and its first admin key). NEVER",
|
|
21613
21641
|
" printed or logged.",
|
|
21614
|
-
" --repo <name> Repository name.
|
|
21615
|
-
"
|
|
21616
|
-
"
|
|
21617
|
-
"
|
|
21618
|
-
" is
|
|
21619
|
-
"
|
|
21642
|
+
" --repo <name> Repository name. --repo and BAPI_REPO_NAME still take",
|
|
21643
|
+
" priority and short-circuit before any network call. When",
|
|
21644
|
+
" neither is set, a compatible server resolves the unique",
|
|
21645
|
+
" repository from your existing API key automatically; if the",
|
|
21646
|
+
" server is older, the key is unresolvable, or resolution",
|
|
21647
|
+
" fails, it falls back to an inferred default you confirm",
|
|
21648
|
+
" interactively (and to a required --repo when stdin is",
|
|
21649
|
+
" non-interactive). MUST match the server-side repo",
|
|
21650
|
+
" registration (it keys the credential store as bapi:<repo>).",
|
|
21651
|
+
" With --invite it is the name your NEW project is created",
|
|
21652
|
+
" under (globally unique).",
|
|
21653
|
+
"",
|
|
21654
|
+
"Self-serve onboarding (no account, no API key, no pre-issued invite):",
|
|
21655
|
+
" --email <addr> Create a brand-new Bridge workspace from just an email \u2014",
|
|
21656
|
+
" the primary path for a first-time user with nothing yet.",
|
|
21657
|
+
" It requests a fresh workspace for that email, then creates",
|
|
21658
|
+
" the project and mints your own admin API key in one command.",
|
|
21659
|
+
" Falls back to the BAPI_SIGNUP_EMAIL env var, then a visible",
|
|
21660
|
+
" interactive prompt. The email is NOT a secret (it is shown",
|
|
21661
|
+
" as you type), but it is never printed to a log. Mutually",
|
|
21662
|
+
" exclusive with --api-key and --invite. No email verification",
|
|
21663
|
+
" is performed and no message is sent to the address \u2014 it only",
|
|
21664
|
+
" labels the new workspace.",
|
|
21620
21665
|
"",
|
|
21621
21666
|
"Bootstrap-invite onboarding (no web UI, no pre-existing key):",
|
|
21622
|
-
" --invite [token] Redeem a bootstrap invite
|
|
21623
|
-
" your own admin API key in one command.
|
|
21624
|
-
" with --api-key (in this mode
|
|
21625
|
-
" consumed).",
|
|
21667
|
+
" --invite [token] Redeem a bootstrap invite you were already given: creates",
|
|
21668
|
+
" the project and mints your own admin API key in one command.",
|
|
21669
|
+
" Mutually exclusive with --api-key and --email (in this mode",
|
|
21670
|
+
" the key is created, not consumed).",
|
|
21626
21671
|
"",
|
|
21627
21672
|
" Run it WITHOUT a value \u2014 `install-bridge --invite` \u2014 and the",
|
|
21628
21673
|
" token is read from an interactive prompt with echo",
|
|
@@ -21652,8 +21697,10 @@ function getInstallBridgeUsage() {
|
|
|
21652
21697
|
"",
|
|
21653
21698
|
"Environment: BAPI_BASE_URL (default https://bridgegpt-api.com) and BAPI_DOCS_DIR",
|
|
21654
21699
|
"(default docs/tmp) are read from the environment with the shown fallbacks.",
|
|
21655
|
-
"
|
|
21656
|
-
"
|
|
21700
|
+
"BAPI_SIGNUP_EMAIL supplies the self-serve signup email non-interactively (it is",
|
|
21701
|
+
"visible input, not a secret). BAPI_INVITE supplies the bootstrap-invite token",
|
|
21702
|
+
"non-interactively (scripting only \u2014 it is exposed to shell history; prefer the",
|
|
21703
|
+
"prompt)."
|
|
21657
21704
|
].join("\n");
|
|
21658
21705
|
}
|
|
21659
21706
|
function parseInstallBridgeArgs(argv) {
|
|
@@ -21666,8 +21713,10 @@ function parseInstallBridgeArgs(argv) {
|
|
|
21666
21713
|
let dryRun = false;
|
|
21667
21714
|
let agentName = DEFAULT_AGENT_NAME;
|
|
21668
21715
|
let invite;
|
|
21716
|
+
let email;
|
|
21669
21717
|
let inviteSupplied = false;
|
|
21670
21718
|
let apiKeySupplied = false;
|
|
21719
|
+
let emailSupplied = false;
|
|
21671
21720
|
const readValue = (arg, flag, i) => {
|
|
21672
21721
|
if (arg.startsWith(`${flag}=`)) {
|
|
21673
21722
|
return { value: arg.slice(flag.length + 1), nextIndex: i };
|
|
@@ -21708,6 +21757,17 @@ function parseInstallBridgeArgs(argv) {
|
|
|
21708
21757
|
}
|
|
21709
21758
|
continue;
|
|
21710
21759
|
}
|
|
21760
|
+
if (arg === "--email" || arg.startsWith("--email=")) {
|
|
21761
|
+
const r = readValue(arg, "--email", i);
|
|
21762
|
+
if ("error" in r) return { status: "error", message: r.error };
|
|
21763
|
+
if (r.value.trim().length === 0) {
|
|
21764
|
+
return { status: "error", message: "--email requires a non-empty value." };
|
|
21765
|
+
}
|
|
21766
|
+
email = r.value.trim();
|
|
21767
|
+
emailSupplied = true;
|
|
21768
|
+
i = r.nextIndex;
|
|
21769
|
+
continue;
|
|
21770
|
+
}
|
|
21711
21771
|
if (arg === "--repo" || arg.startsWith("--repo=")) {
|
|
21712
21772
|
const r = readValue(arg, "--repo", i);
|
|
21713
21773
|
if ("error" in r) return { status: "error", message: r.error };
|
|
@@ -21742,9 +21802,21 @@ function parseInstallBridgeArgs(argv) {
|
|
|
21742
21802
|
message: "--invite and --api-key are mutually exclusive: a bootstrap invite creates your API key, it does not consume an existing one."
|
|
21743
21803
|
};
|
|
21744
21804
|
}
|
|
21805
|
+
if (emailSupplied && apiKeySupplied) {
|
|
21806
|
+
return {
|
|
21807
|
+
status: "error",
|
|
21808
|
+
message: "--email and --api-key are mutually exclusive: self-serve signup creates your API key, it does not consume an existing one."
|
|
21809
|
+
};
|
|
21810
|
+
}
|
|
21811
|
+
if (emailSupplied && inviteSupplied) {
|
|
21812
|
+
return {
|
|
21813
|
+
status: "error",
|
|
21814
|
+
message: "--email and --invite are mutually exclusive: use --email for self-serve signup (no pre-issued invite), or --invite to redeem an invite you already have."
|
|
21815
|
+
};
|
|
21816
|
+
}
|
|
21745
21817
|
return {
|
|
21746
21818
|
status: "ok",
|
|
21747
|
-
options: { apiKey, repo, force, dryRun, agentName, invite, inviteMode: inviteSupplied }
|
|
21819
|
+
options: { apiKey, repo, force, dryRun, agentName, invite, inviteMode: inviteSupplied, email }
|
|
21748
21820
|
};
|
|
21749
21821
|
}
|
|
21750
21822
|
function promptSecretViaReadline(promptText, input = process.stdin, output = process.stderr) {
|
|
@@ -21790,11 +21862,16 @@ function promptLineViaReadline(promptText) {
|
|
|
21790
21862
|
});
|
|
21791
21863
|
});
|
|
21792
21864
|
}
|
|
21865
|
+
function sanitizePrewarmEnv(env) {
|
|
21866
|
+
const sanitized = { ...env };
|
|
21867
|
+
delete sanitized.BAPI_API_KEY;
|
|
21868
|
+
delete sanitized.BAPI_INVITE;
|
|
21869
|
+
delete sanitized.BAPI_SIGNUP_EMAIL;
|
|
21870
|
+
return sanitized;
|
|
21871
|
+
}
|
|
21793
21872
|
function spawnPrewarmDefault(command, args, env) {
|
|
21794
21873
|
return new Promise((resolve2) => {
|
|
21795
|
-
const sanitizedEnv =
|
|
21796
|
-
delete sanitizedEnv.BAPI_API_KEY;
|
|
21797
|
-
delete sanitizedEnv.BAPI_INVITE;
|
|
21874
|
+
const sanitizedEnv = sanitizePrewarmEnv(env);
|
|
21798
21875
|
try {
|
|
21799
21876
|
const child = spawn6(command, args, {
|
|
21800
21877
|
shell: false,
|
|
@@ -21819,6 +21896,7 @@ function spawnPrewarmDefault(command, args, env) {
|
|
|
21819
21896
|
}
|
|
21820
21897
|
function createDefaultInstallBridgeDeps() {
|
|
21821
21898
|
const isTTY = Boolean(process.stdin.isTTY);
|
|
21899
|
+
const productionFetch = (...args) => fetch(...args);
|
|
21822
21900
|
return {
|
|
21823
21901
|
env: process.env,
|
|
21824
21902
|
cwd: process.cwd(),
|
|
@@ -21843,7 +21921,8 @@ function createDefaultInstallBridgeDeps() {
|
|
|
21843
21921
|
randomBytes: (size) => cryptoRandomBytes(size),
|
|
21844
21922
|
promptSecret: isTTY ? promptSecretViaReadline : void 0,
|
|
21845
21923
|
promptLine: isTTY ? promptLineViaReadline : void 0,
|
|
21846
|
-
fetch:
|
|
21924
|
+
fetch: productionFetch,
|
|
21925
|
+
resolveRepoViaServer: (baseUrl, apiKey) => resolveRepoViaServer(productionFetch, baseUrl, apiKey),
|
|
21847
21926
|
spawnPrewarm: spawnPrewarmDefault,
|
|
21848
21927
|
runInit,
|
|
21849
21928
|
upsertCredential: upsertBapiCredential,
|
|
@@ -21897,14 +21976,48 @@ async function resolveInviteToken(options, deps) {
|
|
|
21897
21976
|
error: "A bootstrap invite token is required. Pass --invite <token> or set the BAPI_INVITE environment variable (no interactive terminal is available to prompt for it). Note that both forms expose the token to your shell history and process list \u2014 prefer running 'install-bridge --invite' interactively."
|
|
21898
21977
|
};
|
|
21899
21978
|
}
|
|
21900
|
-
async function
|
|
21901
|
-
if (typeof options.
|
|
21902
|
-
return { ok: true, value: options.
|
|
21979
|
+
async function resolveSignupEmail(options, deps) {
|
|
21980
|
+
if (typeof options.email === "string" && options.email.trim().length > 0) {
|
|
21981
|
+
return { ok: true, value: options.email.trim() };
|
|
21903
21982
|
}
|
|
21904
|
-
const fromEnv = deps.env.
|
|
21983
|
+
const fromEnv = deps.env.BAPI_SIGNUP_EMAIL;
|
|
21905
21984
|
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
|
|
21906
21985
|
return { ok: true, value: fromEnv.trim() };
|
|
21907
21986
|
}
|
|
21987
|
+
if (deps.isTTY && deps.promptLine) {
|
|
21988
|
+
const entered = (await deps.promptLine("Email for Bridge workspace setup: ")).trim();
|
|
21989
|
+
if (entered.length > 0) {
|
|
21990
|
+
return { ok: true, value: entered };
|
|
21991
|
+
}
|
|
21992
|
+
return { ok: false, error: "No email entered." };
|
|
21993
|
+
}
|
|
21994
|
+
return {
|
|
21995
|
+
ok: false,
|
|
21996
|
+
error: "An email is required to create a Bridge workspace. Pass --email <addr> or set the BAPI_SIGNUP_EMAIL environment variable (no interactive terminal is available to prompt for it)."
|
|
21997
|
+
};
|
|
21998
|
+
}
|
|
21999
|
+
function resolveInstallBridgeOnboardingBranch(options, env) {
|
|
22000
|
+
const inviteMode = options.inviteMode === true || (env.BAPI_INVITE ?? "").trim().length > 0;
|
|
22001
|
+
if (inviteMode) return { kind: "need-key", method: "bootstrap-invite" };
|
|
22002
|
+
const emailMode = (options.email ?? "").trim().length > 0 || (env.BAPI_SIGNUP_EMAIL ?? "").trim().length > 0;
|
|
22003
|
+
if (emailMode) return { kind: "need-key", method: "self-serve" };
|
|
22004
|
+
return { kind: "have-key" };
|
|
22005
|
+
}
|
|
22006
|
+
function resolveConfiguredRepoName(options, env) {
|
|
22007
|
+
if (typeof options.repo === "string" && options.repo.trim().length > 0) {
|
|
22008
|
+
return options.repo.trim();
|
|
22009
|
+
}
|
|
22010
|
+
const fromEnv = env.BAPI_REPO_NAME;
|
|
22011
|
+
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
|
|
22012
|
+
return fromEnv.trim();
|
|
22013
|
+
}
|
|
22014
|
+
return void 0;
|
|
22015
|
+
}
|
|
22016
|
+
async function resolveRepoName(options, deps) {
|
|
22017
|
+
const configured = resolveConfiguredRepoName(options, deps.env);
|
|
22018
|
+
if (configured !== void 0) {
|
|
22019
|
+
return { ok: true, value: configured };
|
|
22020
|
+
}
|
|
21908
22021
|
if (!deps.isTTY || !deps.promptLine) {
|
|
21909
22022
|
return {
|
|
21910
22023
|
ok: false,
|
|
@@ -22021,11 +22134,10 @@ async function verifyConnectivity(deps, baseUrl, repoName, apiKey) {
|
|
|
22021
22134
|
headers: { "X-API-Key": apiKey },
|
|
22022
22135
|
signal: AbortSignal.timeout(1e4)
|
|
22023
22136
|
});
|
|
22024
|
-
} catch
|
|
22025
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
22137
|
+
} catch {
|
|
22026
22138
|
return {
|
|
22027
22139
|
ok: false,
|
|
22028
|
-
message: `Could not reach the Bridge API at ${baseUrl}
|
|
22140
|
+
message: `Could not reach the Bridge API at ${baseUrl}. Check BAPI_BASE_URL and your network.`
|
|
22029
22141
|
};
|
|
22030
22142
|
}
|
|
22031
22143
|
if (resp.ok) return { ok: true };
|
|
@@ -22046,6 +22158,34 @@ async function verifyConnectivity(deps, baseUrl, repoName, apiKey) {
|
|
|
22046
22158
|
message: `Connectivity check failed (HTTP ${resp.status}). Verify your repo, API key, and BAPI_BASE_URL.`
|
|
22047
22159
|
};
|
|
22048
22160
|
}
|
|
22161
|
+
function buildResolveRepoUrl(baseUrl) {
|
|
22162
|
+
return `${baseUrl.replace(/\/+$/, "")}/setup/resolve-repo`;
|
|
22163
|
+
}
|
|
22164
|
+
async function resolveRepoViaServer(fetchImpl, baseUrl, apiKey) {
|
|
22165
|
+
const url = buildResolveRepoUrl(baseUrl);
|
|
22166
|
+
let resp;
|
|
22167
|
+
try {
|
|
22168
|
+
resp = await fetchImpl(url, {
|
|
22169
|
+
headers: { "X-API-Key": apiKey },
|
|
22170
|
+
signal: AbortSignal.timeout(1e4)
|
|
22171
|
+
});
|
|
22172
|
+
} catch {
|
|
22173
|
+
return { status: "error" };
|
|
22174
|
+
}
|
|
22175
|
+
if (resp.status === 404) return { status: "not-deployed" };
|
|
22176
|
+
if (resp.status === 409) return { status: "unresolved" };
|
|
22177
|
+
if (!resp.ok) return { status: "error" };
|
|
22178
|
+
let body;
|
|
22179
|
+
try {
|
|
22180
|
+
body = await resp.json();
|
|
22181
|
+
} catch {
|
|
22182
|
+
return { status: "error" };
|
|
22183
|
+
}
|
|
22184
|
+
const repoName = body?.repo_name;
|
|
22185
|
+
const validated = validateRepoName(repoName);
|
|
22186
|
+
if (!validated.ok) return { status: "error" };
|
|
22187
|
+
return { status: "resolved", repoName: validated.value };
|
|
22188
|
+
}
|
|
22049
22189
|
var BOOTSTRAP_KEY_SECRET_BYTES = 32;
|
|
22050
22190
|
function generateBootstrapKeySecret(randomBytes3) {
|
|
22051
22191
|
return randomBytes3(BOOTSTRAP_KEY_SECRET_BYTES).toString("base64url");
|
|
@@ -22112,6 +22252,45 @@ async function exchangeBootstrapInvite(deps, baseUrl, token, repoName, keySecret
|
|
|
22112
22252
|
message: `The bootstrap exchange failed (HTTP ${resp.status}). Verify BAPI_BASE_URL and try again.`
|
|
22113
22253
|
};
|
|
22114
22254
|
}
|
|
22255
|
+
var BOOTSTRAP_INVITE_TOKEN_PREFIX = "bapi_inv_";
|
|
22256
|
+
function buildSelfServeMintUrl(baseUrl) {
|
|
22257
|
+
return `${baseUrl.replace(/\/+$/, "")}/setup/bootstrap/self-serve`;
|
|
22258
|
+
}
|
|
22259
|
+
async function mintSelfServeInvite(deps, baseUrl, email) {
|
|
22260
|
+
const url = buildSelfServeMintUrl(baseUrl);
|
|
22261
|
+
let resp;
|
|
22262
|
+
try {
|
|
22263
|
+
resp = await deps.fetch(url, {
|
|
22264
|
+
method: "POST",
|
|
22265
|
+
headers: { "Content-Type": "application/json" },
|
|
22266
|
+
body: JSON.stringify({ invitee_email: email }),
|
|
22267
|
+
signal: AbortSignal.timeout(1e4)
|
|
22268
|
+
});
|
|
22269
|
+
} catch (err) {
|
|
22270
|
+
void err;
|
|
22271
|
+
return { ok: false, category: "failed" };
|
|
22272
|
+
}
|
|
22273
|
+
if (resp.ok) {
|
|
22274
|
+
let token;
|
|
22275
|
+
try {
|
|
22276
|
+
const body = await resp.json();
|
|
22277
|
+
token = body?.token;
|
|
22278
|
+
} catch {
|
|
22279
|
+
return { ok: false, category: "failed" };
|
|
22280
|
+
}
|
|
22281
|
+
if (typeof token !== "string" || token.trim().length === 0 || !token.startsWith(BOOTSTRAP_INVITE_TOKEN_PREFIX)) {
|
|
22282
|
+
return { ok: false, category: "failed" };
|
|
22283
|
+
}
|
|
22284
|
+
return { ok: true, token };
|
|
22285
|
+
}
|
|
22286
|
+
if (resp.status === 429) {
|
|
22287
|
+
return { ok: false, category: "rate-limited" };
|
|
22288
|
+
}
|
|
22289
|
+
if (resp.status === 400 || resp.status === 422) {
|
|
22290
|
+
return { ok: false, category: "invalid" };
|
|
22291
|
+
}
|
|
22292
|
+
return { ok: false, category: "failed" };
|
|
22293
|
+
}
|
|
22115
22294
|
var BOOTSTRAP_INVITE_LOST_SECRET_MESSAGE = [
|
|
22116
22295
|
"The Bridge API rejected the bootstrap invite (HTTP 401).",
|
|
22117
22296
|
"",
|
|
@@ -22132,8 +22311,8 @@ var BOOTSTRAP_INVITE_REJECTED_MESSAGE = "The Bridge API rejected the bootstrap i
|
|
|
22132
22311
|
function buildDryRunPreview(plan) {
|
|
22133
22312
|
if (plan.bootstrapInvite) return buildBootstrapDryRunPreview(plan);
|
|
22134
22313
|
return [
|
|
22135
|
-
"install-bridge --dry-run (no writes, no network, no spawns)",
|
|
22136
|
-
`Repo name: ${plan.repoName}`,
|
|
22314
|
+
plan.attemptedServerResolution ? "install-bridge --dry-run (one read-only repository-resolution GET may already have occurred; no writes, no state-changing requests, no spawns)" : "install-bridge --dry-run (no writes, no network, no spawns)",
|
|
22315
|
+
`Repo name: ${plan.repoName}${plan.attemptedServerResolution ? " (resolved server-side from your API key)" : ""}`,
|
|
22137
22316
|
`Base URL (ping): ${plan.baseUrl}`,
|
|
22138
22317
|
`Docs dir: ${plan.docsDir}`,
|
|
22139
22318
|
`Agent: ${plan.agentName}`,
|
|
@@ -22153,14 +22332,23 @@ function buildDryRunPreview(plan) {
|
|
|
22153
22332
|
}
|
|
22154
22333
|
function buildBootstrapDryRunPreview(plan) {
|
|
22155
22334
|
const pendingTarget = `bootstrap-pending:${plan.repoName}`;
|
|
22335
|
+
const header = plan.selfServeSignup ? "install-bridge --email --dry-run (no writes, no network, no spawns, no account created, no secret generated)" : "install-bridge --invite --dry-run (no writes, no network, no spawns, no secret generated)";
|
|
22336
|
+
const repoLine = plan.selfServeSignup ? `Repo name: ${plan.repoName} (created by the self-serve exchange; globally unique)` : `Repo name: ${plan.repoName} (created by the exchange; globally unique)`;
|
|
22337
|
+
const selfServeStep = plan.selfServeSignup ? [
|
|
22338
|
+
"Step 2\xB7pre \u2014 self-serve signup (PREVIEWED, SKIPPED in --dry-run): no Bridge workspace",
|
|
22339
|
+
" signup is requested, no mint call is made, and no email is sent or transmitted;",
|
|
22340
|
+
" a real run would request a fresh workspace for your email and receive an invite",
|
|
22341
|
+
" token, which then feeds the SAME redemption protocol below."
|
|
22342
|
+
] : [];
|
|
22156
22343
|
return [
|
|
22157
|
-
|
|
22158
|
-
|
|
22344
|
+
header,
|
|
22345
|
+
repoLine,
|
|
22159
22346
|
`Base URL: ${plan.baseUrl}`,
|
|
22160
22347
|
`Docs dir: ${plan.docsDir}`,
|
|
22161
22348
|
`Agent: ${plan.agentName}`,
|
|
22162
22349
|
"",
|
|
22163
22350
|
"Step 1 \u2014 scaffold (runInit): commands, agents, pipelines, .bridge/config, secret-free MCP placeholders.",
|
|
22351
|
+
...selfServeStep,
|
|
22164
22352
|
`Step 2a \u2014 generate key_secret (32 CSPRNG bytes) and fsync it to ${pendingTarget} at ${plan.credentialStorePath}`,
|
|
22165
22353
|
" BEFORE the exchange. If that write fails the run ABORTS and no invite is spent.",
|
|
22166
22354
|
`Step 2b \u2014 redeem the bootstrap invite (replaces the pre-flight ping \u2014 there is no key yet):`,
|
|
@@ -22224,6 +22412,9 @@ function buildManualHostInstructions(entry, editors) {
|
|
|
22224
22412
|
}
|
|
22225
22413
|
async function runInstallBridgeCli(argv, overrides = {}) {
|
|
22226
22414
|
const deps = { ...createDefaultInstallBridgeDeps(), ...overrides };
|
|
22415
|
+
if (!overrides.resolveRepoViaServer) {
|
|
22416
|
+
deps.resolveRepoViaServer = (baseUrl2, apiKey2) => resolveRepoViaServer(deps.fetch, baseUrl2, apiKey2);
|
|
22417
|
+
}
|
|
22227
22418
|
const { log, errorLog } = deps;
|
|
22228
22419
|
const parsed = parseInstallBridgeArgs(argv);
|
|
22229
22420
|
if (parsed.status === "help") {
|
|
@@ -22237,10 +22428,20 @@ async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
22237
22428
|
return 1;
|
|
22238
22429
|
}
|
|
22239
22430
|
const options = parsed.options;
|
|
22240
|
-
const
|
|
22431
|
+
const branch = resolveInstallBridgeOnboardingBranch(options, deps.env);
|
|
22432
|
+
const bootstrapInviteMode = branch.kind === "need-key";
|
|
22433
|
+
const selfServeSignupMode = branch.kind === "need-key" && branch.method === "self-serve";
|
|
22241
22434
|
let apiKey = "";
|
|
22242
22435
|
let inviteToken = "";
|
|
22243
|
-
|
|
22436
|
+
let signupEmail = "";
|
|
22437
|
+
if (selfServeSignupMode) {
|
|
22438
|
+
const emailResult = await resolveSignupEmail(options, deps);
|
|
22439
|
+
if (!emailResult.ok) {
|
|
22440
|
+
errorLog(`Error: ${emailResult.error}`);
|
|
22441
|
+
return 1;
|
|
22442
|
+
}
|
|
22443
|
+
signupEmail = emailResult.value;
|
|
22444
|
+
} else if (bootstrapInviteMode) {
|
|
22244
22445
|
const inviteResult = await resolveInviteToken(options, deps);
|
|
22245
22446
|
if (!inviteResult.ok) {
|
|
22246
22447
|
errorLog(`Error: ${inviteResult.error}`);
|
|
@@ -22255,22 +22456,42 @@ async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
22255
22456
|
}
|
|
22256
22457
|
apiKey = keyResult.value;
|
|
22257
22458
|
}
|
|
22258
|
-
const
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
}
|
|
22263
|
-
let repoName = repoResult.value;
|
|
22459
|
+
const baseUrl = deps.env.BAPI_BASE_URL ?? DEFAULT_BAPI_BASE_URL2;
|
|
22460
|
+
const docsDir = deps.env.BAPI_DOCS_DIR ?? DEFAULT_BAPI_DOCS_DIR;
|
|
22461
|
+
let repoName;
|
|
22462
|
+
let attemptedServerResolution = false;
|
|
22264
22463
|
if (bootstrapInviteMode) {
|
|
22265
|
-
const
|
|
22464
|
+
const repoResult = await resolveRepoName(options, deps);
|
|
22465
|
+
if (!repoResult.ok) {
|
|
22466
|
+
errorLog(`Error: ${repoResult.error}`);
|
|
22467
|
+
return 1;
|
|
22468
|
+
}
|
|
22469
|
+
const validated = validateRepoName(repoResult.value);
|
|
22266
22470
|
if (!validated.ok) {
|
|
22267
22471
|
errorLog(`Error: invalid repo name \u2014 ${validated.error}.`);
|
|
22268
22472
|
return 1;
|
|
22269
22473
|
}
|
|
22270
22474
|
repoName = validated.value;
|
|
22475
|
+
} else {
|
|
22476
|
+
const configured = resolveConfiguredRepoName(options, deps.env);
|
|
22477
|
+
if (configured !== void 0) {
|
|
22478
|
+
repoName = configured;
|
|
22479
|
+
} else {
|
|
22480
|
+
attemptedServerResolution = true;
|
|
22481
|
+
log("Resolving repository\u2026");
|
|
22482
|
+
const resolution = await deps.resolveRepoViaServer(baseUrl, apiKey);
|
|
22483
|
+
if (resolution.status === "resolved") {
|
|
22484
|
+
repoName = resolution.repoName;
|
|
22485
|
+
} else {
|
|
22486
|
+
const repoResult = await resolveRepoName(options, deps);
|
|
22487
|
+
if (!repoResult.ok) {
|
|
22488
|
+
errorLog(`Error: ${repoResult.error}`);
|
|
22489
|
+
return 1;
|
|
22490
|
+
}
|
|
22491
|
+
repoName = repoResult.value;
|
|
22492
|
+
}
|
|
22493
|
+
}
|
|
22271
22494
|
}
|
|
22272
|
-
const baseUrl = deps.env.BAPI_BASE_URL ?? DEFAULT_BAPI_BASE_URL2;
|
|
22273
|
-
const docsDir = deps.env.BAPI_DOCS_DIR ?? DEFAULT_BAPI_DOCS_DIR;
|
|
22274
22495
|
const agent = resolveAgentSpec(options.agentName) ?? resolveAgentSpec(DEFAULT_AGENT_NAME);
|
|
22275
22496
|
const spawnCommand = deps.buildShellCommand(
|
|
22276
22497
|
agent,
|
|
@@ -22296,7 +22517,9 @@ async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
22296
22517
|
pingUrl: buildPingUrl(baseUrl, repoName),
|
|
22297
22518
|
prewarmCommand: buildPrewarmCommandPreview(),
|
|
22298
22519
|
spawnCommand,
|
|
22299
|
-
...bootstrapInviteMode ? { bootstrapInvite: true, exchangeUrl: buildBootstrapExchangeUrl(baseUrl) } : {}
|
|
22520
|
+
...bootstrapInviteMode ? { bootstrapInvite: true, exchangeUrl: buildBootstrapExchangeUrl(baseUrl) } : {},
|
|
22521
|
+
...selfServeSignupMode ? { selfServeSignup: true } : {},
|
|
22522
|
+
...attemptedServerResolution ? { attemptedServerResolution: true } : {}
|
|
22300
22523
|
};
|
|
22301
22524
|
if (options.dryRun) {
|
|
22302
22525
|
for (const line of buildDryRunPreview(plan)) log(line);
|
|
@@ -22337,6 +22560,23 @@ async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
22337
22560
|
await deps.runInit(deps.cwd);
|
|
22338
22561
|
let inviteFingerprint = "";
|
|
22339
22562
|
if (bootstrapInviteMode) {
|
|
22563
|
+
if (selfServeSignupMode) {
|
|
22564
|
+
log("Step 2/5 \u2014 requesting Bridge self-serve setup\u2026");
|
|
22565
|
+
const mint = await mintSelfServeInvite(deps, baseUrl, signupEmail);
|
|
22566
|
+
if (!mint.ok) {
|
|
22567
|
+
if (mint.category === "rate-limited") {
|
|
22568
|
+
errorLog("Error: Self-serve setup is temporarily rate limited. Try again later.");
|
|
22569
|
+
} else if (mint.category === "invalid") {
|
|
22570
|
+
errorLog(
|
|
22571
|
+
"Error: Self-serve setup could not be requested. Check the email value and try again."
|
|
22572
|
+
);
|
|
22573
|
+
} else {
|
|
22574
|
+
errorLog("Error: Unable to complete self-serve setup. Check connectivity and retry.");
|
|
22575
|
+
}
|
|
22576
|
+
return 1;
|
|
22577
|
+
}
|
|
22578
|
+
inviteToken = mint.token;
|
|
22579
|
+
}
|
|
22340
22580
|
inviteFingerprint = fingerprintBootstrapInvite(inviteToken);
|
|
22341
22581
|
log("Step 2/5 \u2014 redeeming the bootstrap invite\u2026");
|
|
22342
22582
|
let prepared = await deps.prepareBootstrapPending(
|
|
@@ -22507,7 +22747,7 @@ async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
22507
22747
|
);
|
|
22508
22748
|
}
|
|
22509
22749
|
}
|
|
22510
|
-
log(`Step 5/5 \u2014 opening a ${agent.name} session for /install-bridge +
|
|
22750
|
+
log(`Step 5/5 \u2014 opening a ${agent.name} session for /install-bridge configuration + capability report\u2026`);
|
|
22511
22751
|
const terminal = detectTerminal(void 0, deps.env);
|
|
22512
22752
|
const spawnResult = await deps.spawnTerminalTab(deps.startTicketsDeps, terminal, spawnCommand, {
|
|
22513
22753
|
key: "install",
|
|
@@ -22515,16 +22755,16 @@ async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
22515
22755
|
});
|
|
22516
22756
|
if (!spawnResult.ok) {
|
|
22517
22757
|
errorLog(
|
|
22518
|
-
`Warning: setup steps completed, but the agent session could not be opened (${spawnResult.error}). The project is NOT configured yet \u2014 run /install-bridge
|
|
22758
|
+
`Warning: setup steps completed, but the agent session could not be opened (${spawnResult.error}). The project is NOT configured yet \u2014 run /install-bridge manually in this project to derive and apply the config fields and see the capability report, then choose whether to run /parse-repository to index the repository.`
|
|
22519
22759
|
);
|
|
22520
22760
|
return 0;
|
|
22521
22761
|
}
|
|
22522
22762
|
log("");
|
|
22523
22763
|
log(
|
|
22524
|
-
`install-bridge setup steps complete. A fresh ${agent.name} session is now
|
|
22764
|
+
`install-bridge setup steps complete. A fresh ${agent.name} session is now applying configuration, presenting the capability report, and ending with one indexing-consent question.`
|
|
22525
22765
|
);
|
|
22526
22766
|
log(
|
|
22527
|
-
"NOTE: the install is not finished until that session's apply reports applied fields \u2014 it will pause to ask you to approve the project description. Verify afterwards on the project's Get Started page (install status panel) or via the session's 'Applied N of M' summary."
|
|
22767
|
+
"NOTE: the install is not finished until that session's apply reports applied fields \u2014 it will pause to ask you to approve the project description, and it will close by asking '[Y/n] Index repository now?'. Verify afterwards on the project's Get Started page (install status panel) or via the session's 'Applied N of M' summary."
|
|
22528
22768
|
);
|
|
22529
22769
|
return 0;
|
|
22530
22770
|
}
|
|
@@ -23512,7 +23752,7 @@ function registerConductorTools(registerTool2) {
|
|
|
23512
23752
|
}
|
|
23513
23753
|
|
|
23514
23754
|
// src/sfcc/register.ts
|
|
23515
|
-
import { z as
|
|
23755
|
+
import { z as z14 } from "zod";
|
|
23516
23756
|
|
|
23517
23757
|
// src/sfcc/config.ts
|
|
23518
23758
|
var SFCC_VERSIONS = ["sfra", "pwakit", "sitegenesis", "storefrontnext", "hybrid"];
|
|
@@ -23893,9 +24133,30 @@ async function sfccSetupStatusTool(deps) {
|
|
|
23893
24133
|
tokenStatus = `\u2717 ${msg}`;
|
|
23894
24134
|
}
|
|
23895
24135
|
}
|
|
23896
|
-
lines.push(`5. AM Token: ${tokenStatus}`);
|
|
24136
|
+
lines.push(`5. AM Token (OCAPI): ${tokenStatus}`);
|
|
24137
|
+
let logQueryStatus = "\u2014 Skipped (Bridge API not configured)";
|
|
24138
|
+
if (apiKeyOk && repoOk) {
|
|
24139
|
+
try {
|
|
24140
|
+
const url = deps.buildGetUrl("/sfcc/logs/capability", { repo_name: deps.repoName });
|
|
24141
|
+
const resp = await fetch(url, { headers: await deps.getGetHeaders() });
|
|
24142
|
+
if (!resp.ok) {
|
|
24143
|
+
logQueryStatus = `\u2717 Could not read (Bridge API ${resp.status})`;
|
|
24144
|
+
} else {
|
|
24145
|
+
const body = await resp.json();
|
|
24146
|
+
if (body?.configured === true) {
|
|
24147
|
+
logQueryStatus = "\u2713 Configured (WebDAV log access ready)";
|
|
24148
|
+
} else {
|
|
24149
|
+
const msg = typeof body?.message === "string" ? body.message : "Not configured";
|
|
24150
|
+
logQueryStatus = `\u2717 ${msg}`;
|
|
24151
|
+
}
|
|
24152
|
+
}
|
|
24153
|
+
} catch (err) {
|
|
24154
|
+
logQueryStatus = `\u2717 Resolution error: ${err instanceof Error ? err.message : String(err)}`;
|
|
24155
|
+
}
|
|
24156
|
+
}
|
|
24157
|
+
lines.push(`6. SFCC Log Query (WebDAV): ${logQueryStatus}`);
|
|
23897
24158
|
lines.push(
|
|
23898
|
-
"\nRun `check_permissions` to probe OCAPI access once steps 1\u20135 are all green."
|
|
24159
|
+
"\nRun `check_permissions` to probe OCAPI access once steps 1\u20135 are all green. Step 6 (log/WebDAV access) is independent and gates `sfcc_log_query`."
|
|
23899
24160
|
);
|
|
23900
24161
|
return {
|
|
23901
24162
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
@@ -25421,6 +25682,172 @@ function registerSfccWriteTools(registerTool2, deps) {
|
|
|
25421
25682
|
registerSitePreferenceWriteTools(registerTool2, { gateDeps: deps.gateDeps });
|
|
25422
25683
|
}
|
|
25423
25684
|
|
|
25685
|
+
// src/sfcc/log-query.ts
|
|
25686
|
+
import { z as z13 } from "zod";
|
|
25687
|
+
|
|
25688
|
+
// src/sfcc/log-gate.ts
|
|
25689
|
+
function jsonResult2(payload) {
|
|
25690
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
25691
|
+
}
|
|
25692
|
+
function notConfigured2(failureClass, message, limits) {
|
|
25693
|
+
return jsonResult2({
|
|
25694
|
+
error: "NOT_CONFIGURED",
|
|
25695
|
+
status: 503,
|
|
25696
|
+
failure_class: failureClass,
|
|
25697
|
+
message,
|
|
25698
|
+
...limits !== void 0 ? { limits } : {}
|
|
25699
|
+
});
|
|
25700
|
+
}
|
|
25701
|
+
function withSfccLogGate(deps, handler) {
|
|
25702
|
+
return async (args) => {
|
|
25703
|
+
let body;
|
|
25704
|
+
try {
|
|
25705
|
+
const url = deps.buildGetUrl("/sfcc/logs/capability", {
|
|
25706
|
+
repo_name: deps.repoName
|
|
25707
|
+
});
|
|
25708
|
+
const resp = await fetch(url, { headers: await deps.getGetHeaders() });
|
|
25709
|
+
if (!resp.ok) {
|
|
25710
|
+
return jsonResult2({
|
|
25711
|
+
error: resp.status === 401 || resp.status === 403 ? "UNAUTHORIZED" : "SERVICE_UNAVAILABLE",
|
|
25712
|
+
status: resp.status,
|
|
25713
|
+
message: "Could not read the SFCC log capability from Bridge API (/jira/sfcc/logs/capability). Ensure your Bridge API key is set and the repo is authorized. Run sfcc_setup_status for a full diagnostic."
|
|
25714
|
+
});
|
|
25715
|
+
}
|
|
25716
|
+
body = await resp.json();
|
|
25717
|
+
} catch {
|
|
25718
|
+
return jsonResult2({
|
|
25719
|
+
error: "BAD_GATEWAY",
|
|
25720
|
+
status: 502,
|
|
25721
|
+
message: "Could not reach Bridge API to resolve the SFCC log capability. Check that BAPI_BASE_URL points to a running Bridge API instance."
|
|
25722
|
+
});
|
|
25723
|
+
}
|
|
25724
|
+
if (body?.configured !== true) {
|
|
25725
|
+
const failureClass = typeof body?.failure_class === "string" ? body.failure_class : "not_configured";
|
|
25726
|
+
const message = typeof body?.message === "string" ? body.message : "SFCC on-demand log queries are not configured for this repository. Run sfcc_setup_status for a diagnostic.";
|
|
25727
|
+
return notConfigured2(failureClass, message, body?.limits);
|
|
25728
|
+
}
|
|
25729
|
+
return handler(args);
|
|
25730
|
+
};
|
|
25731
|
+
}
|
|
25732
|
+
|
|
25733
|
+
// src/sfcc/log-query.ts
|
|
25734
|
+
var MAX_QUERY_RANGE_HOURS = 24;
|
|
25735
|
+
var HIGH_VOLUME_MAX_RANGE_HOURS = 6;
|
|
25736
|
+
var HIGH_VOLUME_PREFIXES = /* @__PURE__ */ new Set(["info", "jobs", "debug", "customdebug"]);
|
|
25737
|
+
var MAX_SELECTED_PREFIXES = 5;
|
|
25738
|
+
var MAX_MAX_ENTRIES = 2e3;
|
|
25739
|
+
var SUPPORTED_ENVIRONMENTS = ["production", "staging", "development"];
|
|
25740
|
+
var PREFIX_RE = /^[a-z][a-z0-9]*$/;
|
|
25741
|
+
function jsonResult3(payload) {
|
|
25742
|
+
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
25743
|
+
}
|
|
25744
|
+
function validationError(message) {
|
|
25745
|
+
return jsonResult3({ error: "VALIDATION_ERROR", status: 400, message });
|
|
25746
|
+
}
|
|
25747
|
+
var inputSchema = z13.object({
|
|
25748
|
+
environment: z13.enum(SUPPORTED_ENVIRONMENTS).describe(
|
|
25749
|
+
"REQUIRED environment to scope the query to. Deliberately required so a query can never fan across every environment at once."
|
|
25750
|
+
),
|
|
25751
|
+
time_range: z13.object({
|
|
25752
|
+
start: z13.string().datetime({ offset: true }).describe("Inclusive ISO-8601 UTC start."),
|
|
25753
|
+
end: z13.string().datetime({ offset: true }).describe("Exclusive ISO-8601 UTC end (after start).")
|
|
25754
|
+
}).strict().describe(
|
|
25755
|
+
"REQUIRED bounded window. No open-ended or inferred period is ever assumed."
|
|
25756
|
+
),
|
|
25757
|
+
prefixes: z13.array(z13.string().regex(PREFIX_RE, "prefix must be a letter followed by letters/digits")).max(MAX_SELECTED_PREFIXES).optional().describe(
|
|
25758
|
+
`Optional log-file prefix selection (max ${MAX_SELECTED_PREFIXES}). Empty = the shipped error-class defaults. High-volume prefixes (info/jobs/debug/customdebug) impose a stricter ${HIGH_VOLUME_MAX_RANGE_HOURS}h max time range.`
|
|
25759
|
+
),
|
|
25760
|
+
max_entries: z13.number().int().min(1).max(MAX_MAX_ENTRIES).optional().describe(`Optional per-query entry-scan cap (1..${MAX_MAX_ENTRIES}).`)
|
|
25761
|
+
});
|
|
25762
|
+
function semanticCheck(args) {
|
|
25763
|
+
const start = Date.parse(args.time_range.start);
|
|
25764
|
+
const end = Date.parse(args.time_range.end);
|
|
25765
|
+
if (Number.isNaN(start) || Number.isNaN(end)) {
|
|
25766
|
+
return "time_range.start and time_range.end must be valid ISO-8601 timestamps.";
|
|
25767
|
+
}
|
|
25768
|
+
if (!(start < end)) {
|
|
25769
|
+
return "time_range.start must be strictly before time_range.end.";
|
|
25770
|
+
}
|
|
25771
|
+
const prefixes = args.prefixes ?? [];
|
|
25772
|
+
const hasHighVolume = prefixes.some((p) => HIGH_VOLUME_PREFIXES.has(p));
|
|
25773
|
+
const maxHours = hasHighVolume ? HIGH_VOLUME_MAX_RANGE_HOURS : MAX_QUERY_RANGE_HOURS;
|
|
25774
|
+
const spanHours = (end - start) / 36e5;
|
|
25775
|
+
if (spanHours > maxHours) {
|
|
25776
|
+
return `time_range spans ${spanHours.toFixed(1)}h but the maximum for this prefix selection is ${maxHours}h. environment and time_range are required, and the range is capped, to prevent broad cross-environment or open-ended log scans.`;
|
|
25777
|
+
}
|
|
25778
|
+
if (new Set(prefixes).size !== prefixes.length) {
|
|
25779
|
+
return "prefixes must be unique.";
|
|
25780
|
+
}
|
|
25781
|
+
return null;
|
|
25782
|
+
}
|
|
25783
|
+
function buildHandler(deps) {
|
|
25784
|
+
return async (rawArgs) => {
|
|
25785
|
+
const parsed = inputSchema.safeParse(rawArgs);
|
|
25786
|
+
if (!parsed.success) {
|
|
25787
|
+
return validationError(parsed.error.issues.map((i) => i.message).join("; "));
|
|
25788
|
+
}
|
|
25789
|
+
const args = parsed.data;
|
|
25790
|
+
const semanticFailure = semanticCheck(args);
|
|
25791
|
+
if (semanticFailure) return validationError(semanticFailure);
|
|
25792
|
+
const body = {
|
|
25793
|
+
repo_name: deps.repoName,
|
|
25794
|
+
environment: args.environment,
|
|
25795
|
+
time_range: { start: args.time_range.start, end: args.time_range.end },
|
|
25796
|
+
prefixes: args.prefixes ?? [],
|
|
25797
|
+
...args.max_entries !== void 0 ? { max_entries: args.max_entries } : {}
|
|
25798
|
+
};
|
|
25799
|
+
let resp;
|
|
25800
|
+
try {
|
|
25801
|
+
const url = deps.buildGetUrl("/sfcc/logs/query", { repo_name: deps.repoName });
|
|
25802
|
+
resp = await fetch(url, {
|
|
25803
|
+
method: "POST",
|
|
25804
|
+
headers: await deps.getPostHeaders(),
|
|
25805
|
+
body: JSON.stringify(body)
|
|
25806
|
+
});
|
|
25807
|
+
} catch {
|
|
25808
|
+
return jsonResult3({
|
|
25809
|
+
error: "BAD_GATEWAY",
|
|
25810
|
+
status: 502,
|
|
25811
|
+
message: "Could not reach Bridge API to run the SFCC log query. Check that BAPI_BASE_URL points to a running Bridge API instance."
|
|
25812
|
+
});
|
|
25813
|
+
}
|
|
25814
|
+
const text = await resp.text();
|
|
25815
|
+
if (!resp.ok) {
|
|
25816
|
+
let detail = text;
|
|
25817
|
+
try {
|
|
25818
|
+
detail = JSON.parse(text);
|
|
25819
|
+
} catch {
|
|
25820
|
+
}
|
|
25821
|
+
return jsonResult3({
|
|
25822
|
+
error: resp.status >= 500 ? "SERVICE_UNAVAILABLE" : "REQUEST_FAILED",
|
|
25823
|
+
status: resp.status,
|
|
25824
|
+
detail
|
|
25825
|
+
});
|
|
25826
|
+
}
|
|
25827
|
+
return { content: [{ type: "text", text }] };
|
|
25828
|
+
};
|
|
25829
|
+
}
|
|
25830
|
+
function registerSfccLogQueryTool(registerTool2, deps) {
|
|
25831
|
+
const gated = withSfccLogGate(
|
|
25832
|
+
{ buildGetUrl: deps.buildGetUrl, getGetHeaders: deps.getGetHeaders, repoName: deps.repoName },
|
|
25833
|
+
buildHandler(deps)
|
|
25834
|
+
);
|
|
25835
|
+
registerTool2(
|
|
25836
|
+
"sfcc_log_query",
|
|
25837
|
+
{
|
|
25838
|
+
description: "Query redacted, filtered SFCC logs on demand, scoped to a REQUIRED environment and time_range. Runs pull\u2192redact\u2192filter on the Bridge backend; entries, time range, and log-file prefixes are capped (high-volume prefixes get a stricter range). Returns a NOT_CONFIGURED 503 when the log capability isn't set up \u2014 run sfcc_setup_status.",
|
|
25839
|
+
inputSchema,
|
|
25840
|
+
annotations: {
|
|
25841
|
+
readOnlyHint: true,
|
|
25842
|
+
destructiveHint: false,
|
|
25843
|
+
idempotentHint: true,
|
|
25844
|
+
openWorldHint: true
|
|
25845
|
+
}
|
|
25846
|
+
},
|
|
25847
|
+
gated
|
|
25848
|
+
);
|
|
25849
|
+
}
|
|
25850
|
+
|
|
25424
25851
|
// src/sfcc/register.ts
|
|
25425
25852
|
function registerSfccTools(registerTool2, deps) {
|
|
25426
25853
|
const gateDeps = {
|
|
@@ -25432,7 +25859,7 @@ function registerSfccTools(registerTool2, deps) {
|
|
|
25432
25859
|
"sfcc_setup_status",
|
|
25433
25860
|
{
|
|
25434
25861
|
description: "Report on every SFCC prerequisite: Bridge API key, repo name, version config, dw.json presence/uniqueness, and AM token acquisition. Always-registered; returns status without requiring full SFCC configuration to be complete.",
|
|
25435
|
-
inputSchema:
|
|
25862
|
+
inputSchema: z14.object({}),
|
|
25436
25863
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
25437
25864
|
},
|
|
25438
25865
|
buildSfccSetupStatusHandler(
|
|
@@ -25450,8 +25877,8 @@ function registerSfccTools(registerTool2, deps) {
|
|
|
25450
25877
|
"check_permissions",
|
|
25451
25878
|
{
|
|
25452
25879
|
description: "Probe SFCC OCAPI access via GET /system_object_definitions. On 200: reports OK and the detected OCAPI version. On 401/403: prints the exact OCAPI Settings JSON to paste in Business Manager (split read-only vs. write/import grants).",
|
|
25453
|
-
inputSchema:
|
|
25454
|
-
instance:
|
|
25880
|
+
inputSchema: z14.object({
|
|
25881
|
+
instance: z14.string().optional().describe("Explicit sandbox hostname to use instead of dw.json auto-detection.")
|
|
25455
25882
|
}),
|
|
25456
25883
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
25457
25884
|
},
|
|
@@ -25474,6 +25901,12 @@ function registerSfccTools(registerTool2, deps) {
|
|
|
25474
25901
|
gateDeps,
|
|
25475
25902
|
getDocsDir: deps.getDocsDir
|
|
25476
25903
|
});
|
|
25904
|
+
registerSfccLogQueryTool(registerTool2, {
|
|
25905
|
+
buildGetUrl: deps.buildGetUrl,
|
|
25906
|
+
getGetHeaders: deps.getGetHeaders,
|
|
25907
|
+
getPostHeaders: deps.getPostHeaders,
|
|
25908
|
+
repoName: deps.repoName
|
|
25909
|
+
});
|
|
25477
25910
|
}
|
|
25478
25911
|
}
|
|
25479
25912
|
|
|
@@ -26928,128 +27361,128 @@ init_pr_ci_producer();
|
|
|
26928
27361
|
import { generateDecisionPageHtml } from "./decision-page-template.js";
|
|
26929
27362
|
|
|
26930
27363
|
// src/decision-page-schema.ts
|
|
26931
|
-
import { z as
|
|
26932
|
-
var ActionableItemSchema =
|
|
26933
|
-
id:
|
|
27364
|
+
import { z as z15 } from "zod";
|
|
27365
|
+
var ActionableItemSchema = z15.object({
|
|
27366
|
+
id: z15.string().min(1).regex(
|
|
26934
27367
|
/^[A-Za-z0-9_-]+$/,
|
|
26935
27368
|
"id must contain only letters, digits, hyphens, or underscores"
|
|
26936
27369
|
),
|
|
26937
|
-
question:
|
|
26938
|
-
original_question:
|
|
27370
|
+
question: z15.string().min(1),
|
|
27371
|
+
original_question: z15.string().optional().describe(
|
|
26939
27372
|
"Optional display-only field: the clarifying question or critique point as originally raised; soft cap ~30 words. Omit it (or pass an empty string) for non-review callers \u2014 the renderer omits the section when it is absent or blank."
|
|
26940
27373
|
),
|
|
26941
|
-
why_it_matters:
|
|
26942
|
-
recommendation_explanation:
|
|
26943
|
-
codebase_evidence:
|
|
27374
|
+
why_it_matters: z15.string().min(1).describe("Concrete one-sentence impact of this decision; soft cap ~40 words."),
|
|
27375
|
+
recommendation_explanation: z15.string().min(1).describe("Why the recommended branch is the best choice; soft cap ~60 words."),
|
|
27376
|
+
codebase_evidence: z15.string().optional().describe(
|
|
26944
27377
|
"Optional display-only field: combined Assessment paragraph and Codebase Evidence bullet list. Rendered as escaped plain text inside a closed-by-default <details> block, which is omitted when this field is absent or blank."
|
|
26945
27378
|
),
|
|
26946
|
-
source:
|
|
27379
|
+
source: z15.string().optional().describe(
|
|
26947
27380
|
`Optional source reference from the combined review-and-resolution doc, e.g. 'Clarifying Q3 (prior round, weak concurrence)'. When absent the rendered card emits data-source="".`
|
|
26948
27381
|
),
|
|
26949
|
-
recommendation_index:
|
|
26950
|
-
options:
|
|
26951
|
-
option_consequences:
|
|
27382
|
+
recommendation_index: z15.number().int().min(0).describe("0-based index of the recommended option in the options array"),
|
|
27383
|
+
options: z15.array(z15.string().min(1)).min(2).max(4).describe("Option labels from the decision tree branches. Values are auto-generated. Must have 2\u20134 entries."),
|
|
27384
|
+
option_consequences: z15.array(z15.string().min(1)).min(2).max(4).describe(
|
|
26952
27385
|
"Behavioral consequence per branch, parallel to options. Must have 2\u20134 entries; length must equal options.length."
|
|
26953
27386
|
)
|
|
26954
27387
|
}).superRefine((item, ctx) => {
|
|
26955
27388
|
if (item.option_consequences.length !== item.options.length) {
|
|
26956
27389
|
ctx.addIssue({
|
|
26957
|
-
code:
|
|
27390
|
+
code: z15.ZodIssueCode.custom,
|
|
26958
27391
|
path: ["option_consequences"],
|
|
26959
27392
|
message: `option_consequences length (${item.option_consequences.length}) must match options length (${item.options.length}).`
|
|
26960
27393
|
});
|
|
26961
27394
|
}
|
|
26962
27395
|
if (item.recommendation_index >= item.options.length) {
|
|
26963
27396
|
ctx.addIssue({
|
|
26964
|
-
code:
|
|
27397
|
+
code: z15.ZodIssueCode.custom,
|
|
26965
27398
|
path: ["recommendation_index"],
|
|
26966
27399
|
message: `recommendation_index (${item.recommendation_index}) is out of bounds (${item.options.length} options).`
|
|
26967
27400
|
});
|
|
26968
27401
|
}
|
|
26969
27402
|
});
|
|
26970
|
-
var DecisionPageLabelsSchema =
|
|
26971
|
-
title:
|
|
26972
|
-
intro:
|
|
26973
|
-
section_heading:
|
|
26974
|
-
improvements_heading:
|
|
27403
|
+
var DecisionPageLabelsSchema = z15.object({
|
|
27404
|
+
title: z15.string().optional().describe('Overrides the page <title>/<h1> lead text (default "Review Decisions").'),
|
|
27405
|
+
intro: z15.string().optional().describe("Overrides the actionable-page intro copy shown when there are decisions."),
|
|
27406
|
+
section_heading: z15.string().optional().describe('Overrides the decision cards <h2> (default "Review Decisions").'),
|
|
27407
|
+
improvements_heading: z15.string().optional().describe('Overrides the confirmed-improvements <h2> (default "Confirmed Improvements").')
|
|
26975
27408
|
});
|
|
26976
|
-
var SystemGoalNfrSchema =
|
|
26977
|
-
category:
|
|
27409
|
+
var SystemGoalNfrSchema = z15.object({
|
|
27410
|
+
category: z15.string().min(1).describe(
|
|
26978
27411
|
"Canonical NFR category, e.g. security/privacy, performance/latency, reliability/failure-modes, observability/auditability, accessibility/UX, data-integrity/migration, compatibility, operability/config, compliance/SOC2, rollout/reversibility."
|
|
26979
27412
|
),
|
|
26980
|
-
requirement:
|
|
26981
|
-
implication:
|
|
27413
|
+
requirement: z15.string().min(1).describe("The non-functional requirement itself."),
|
|
27414
|
+
implication: z15.string().min(1).describe(
|
|
26982
27415
|
"What this requirement changes about the implementation. Required \u2014 drop the NFR rather than emit boilerplate without an implication."
|
|
26983
27416
|
),
|
|
26984
|
-
status:
|
|
27417
|
+
status: z15.enum(["confirmed", "assumed", "open"]).describe(
|
|
26985
27418
|
"confirmed = explicitly stated or observable in code; assumed = low-risk and reversible default; open = unresolved (also surface as an actionable_items card)."
|
|
26986
27419
|
)
|
|
26987
27420
|
});
|
|
26988
|
-
var SystemGoalsSchema =
|
|
26989
|
-
business_goal:
|
|
26990
|
-
desired_end_state:
|
|
26991
|
-
system_behavior:
|
|
26992
|
-
nfrs:
|
|
27421
|
+
var SystemGoalsSchema = z15.object({
|
|
27422
|
+
business_goal: z15.string().min(1).describe("The business goal this work serves."),
|
|
27423
|
+
desired_end_state: z15.string().min(1).describe("The end-state the system should reach."),
|
|
27424
|
+
system_behavior: z15.string().min(1).describe("How the system must behave / complete its task (quality attributes in prose)."),
|
|
27425
|
+
nfrs: z15.array(SystemGoalNfrSchema).optional().default([])
|
|
26993
27426
|
});
|
|
26994
|
-
var ImplementationOrderItemSchema =
|
|
26995
|
-
title:
|
|
26996
|
-
depends_on:
|
|
26997
|
-
recommended_after:
|
|
26998
|
-
rationale:
|
|
27427
|
+
var ImplementationOrderItemSchema = z15.object({
|
|
27428
|
+
title: z15.string().min(1).describe("Short title of the slice / child ticket."),
|
|
27429
|
+
depends_on: z15.array(z15.string().min(1)).optional().default([]).describe("Hard prerequisites (titles or keys) that must land first."),
|
|
27430
|
+
recommended_after: z15.array(z15.string().min(1)).optional().default([]).describe("Soft sequencing preferences \u2014 not hard blockers."),
|
|
27431
|
+
rationale: z15.string().min(1).describe("Why this slice sits at this point in the order.")
|
|
26999
27432
|
});
|
|
27000
27433
|
var DecisionPageInputShape = {
|
|
27001
|
-
ticket_key:
|
|
27002
|
-
artifact_type:
|
|
27434
|
+
ticket_key: z15.string().describe("Jira ticket key, e.g. BAPI-123"),
|
|
27435
|
+
artifact_type: z15.enum(["review_decisions", "pre_ticket_planning"]).optional().default("review_decisions").describe(
|
|
27003
27436
|
'Which flavor of page to render. "review_decisions" (default) is the ticket-review decision-capture page and is unaffected by the planning fields. "pre_ticket_planning" additionally renders the read-only system_goals and implementation_order sections for pre-ticket epic/task framing.'
|
|
27004
27437
|
),
|
|
27005
27438
|
system_goals: SystemGoalsSchema.optional().describe(
|
|
27006
27439
|
"pre_ticket_planning only: read-only business goal, desired end-state, system behavior, and classified NFRs. Unresolved (open) NFRs should ALSO be passed as actionable_items so the human can decide them."
|
|
27007
27440
|
),
|
|
27008
|
-
implementation_order:
|
|
27441
|
+
implementation_order: z15.array(ImplementationOrderItemSchema).optional().describe(
|
|
27009
27442
|
"pre_ticket_planning epic surfaces only: read-only recommended implementation order (hard depends_on vs soft recommended_after). No Jira links are created from this."
|
|
27010
27443
|
),
|
|
27011
|
-
output_subdir:
|
|
27444
|
+
output_subdir: z15.string().optional().default("review").describe(
|
|
27012
27445
|
'Optional docs-relative subdirectory to write the page under (default "review"). Validated strictly: no absolute paths, backslashes, ".." segments, null bytes, or encoded path tokens.'
|
|
27013
27446
|
),
|
|
27014
|
-
output_filename:
|
|
27447
|
+
output_filename: z15.string().optional().describe(
|
|
27015
27448
|
'Optional output filename (default "${ticket_key}-decisions.html"). Must end with .html and contain no path separators; the .html suffix is required and never auto-appended.'
|
|
27016
27449
|
),
|
|
27017
27450
|
labels: DecisionPageLabelsSchema.optional().describe(
|
|
27018
27451
|
"Optional presentation-label overrides (title, intro, section_heading, improvements_heading). Presentation-only; does not change data-testid hooks or the submitted JSON shape."
|
|
27019
27452
|
),
|
|
27020
|
-
actionable_items:
|
|
27453
|
+
actionable_items: z15.array(ActionableItemSchema).optional().default([]).describe(
|
|
27021
27454
|
"Actionable review decisions sourced from the combined review-and-resolution document. 'None of these' is auto-appended by the renderer and must not appear in options."
|
|
27022
27455
|
),
|
|
27023
|
-
clear_improvements:
|
|
27024
|
-
|
|
27025
|
-
id:
|
|
27456
|
+
clear_improvements: z15.array(
|
|
27457
|
+
z15.object({
|
|
27458
|
+
id: z15.string().min(1).describe(
|
|
27026
27459
|
"Stable identifier for the improvement. Stored for the rewrite/capture step but intentionally not rendered to the user."
|
|
27027
27460
|
),
|
|
27028
|
-
title:
|
|
27029
|
-
action:
|
|
27030
|
-
confidence:
|
|
27031
|
-
source:
|
|
27461
|
+
title: z15.string().min(1),
|
|
27462
|
+
action: z15.string().min(1),
|
|
27463
|
+
confidence: z15.string().min(1),
|
|
27464
|
+
source: z15.string().min(1).describe(
|
|
27032
27465
|
"Source reference from the evaluation. Stored for the rewrite/capture step but intentionally not rendered to the user \u2014 the confirmed-improvements list shows title/confidence/action only."
|
|
27033
27466
|
)
|
|
27034
27467
|
})
|
|
27035
27468
|
).optional().default([]).describe("Confirmed improvements displayed as informational list, not submitted.")
|
|
27036
27469
|
};
|
|
27037
|
-
var DecisionPageInputSchema =
|
|
27470
|
+
var DecisionPageInputSchema = z15.object(DecisionPageInputShape);
|
|
27038
27471
|
var DecisionPageLeanInputShape = {
|
|
27039
|
-
ticket_key:
|
|
27040
|
-
artifact_type:
|
|
27472
|
+
ticket_key: z15.string().describe("Jira ticket key, e.g. BAPI-123"),
|
|
27473
|
+
artifact_type: z15.enum(["review_decisions", "pre_ticket_planning"]).optional().default("review_decisions").describe(
|
|
27041
27474
|
'Which flavor of page to render. "review_decisions" (default) or "pre_ticket_planning" (adds system_goals and implementation_order sections).'
|
|
27042
27475
|
),
|
|
27043
|
-
output_subdir:
|
|
27476
|
+
output_subdir: z15.string().optional().default("review").describe(
|
|
27044
27477
|
'Optional docs-relative subdirectory to write the page under (default "review"). No absolute paths, backslashes, ".." segments, null bytes, or encoded path tokens.'
|
|
27045
27478
|
),
|
|
27046
|
-
output_filename:
|
|
27479
|
+
output_filename: z15.string().optional().describe(
|
|
27047
27480
|
'Optional output filename (default "${ticket_key}-decisions.html"). Must end with .html; no path separators.'
|
|
27048
27481
|
),
|
|
27049
27482
|
labels: DecisionPageLabelsSchema.optional().describe(
|
|
27050
27483
|
"Optional presentation-label overrides (title, intro, section_heading, improvements_heading)."
|
|
27051
27484
|
),
|
|
27052
|
-
content:
|
|
27485
|
+
content: z15.record(z15.string(), z15.unknown()).optional().describe(
|
|
27053
27486
|
"Contains deferred heavy payloads like actionable_items or system_goals."
|
|
27054
27487
|
)
|
|
27055
27488
|
};
|
|
@@ -29620,10 +30053,10 @@ function buildEstimateEpicErrorEnvelope(code, message, extras) {
|
|
|
29620
30053
|
return JSON.stringify({ error: code, message, ...extras ?? {} }, null, 2);
|
|
29621
30054
|
}
|
|
29622
30055
|
async function runEstimateEpic(input, deps) {
|
|
29623
|
-
const
|
|
29624
|
-
if (
|
|
30056
|
+
const validationError2 = validateEstimateEpicInput(input);
|
|
30057
|
+
if (validationError2) {
|
|
29625
30058
|
return {
|
|
29626
|
-
content: [{ type: "text", text: buildEstimateEpicErrorEnvelope("VALIDATION_ERROR",
|
|
30059
|
+
content: [{ type: "text", text: buildEstimateEpicErrorEnvelope("VALIDATION_ERROR", validationError2) }]
|
|
29627
30060
|
};
|
|
29628
30061
|
}
|
|
29629
30062
|
const payload = { repo_name: deps.repoName };
|
|
@@ -30119,11 +30552,11 @@ Note: Both file_path and ${textLabel} were provided. file_path content was used.
|
|
|
30119
30552
|
};
|
|
30120
30553
|
}
|
|
30121
30554
|
}
|
|
30122
|
-
async function pollForResult(getUrl, timeoutMs, label) {
|
|
30555
|
+
async function pollForResult(getUrl, timeoutMs, label, schedule) {
|
|
30123
30556
|
const startTime = Date.now();
|
|
30124
|
-
let
|
|
30557
|
+
let waitMs = schedule ? schedule.initialDelayMs : 15e3;
|
|
30125
30558
|
while (true) {
|
|
30126
|
-
await new Promise((resolve2) => setTimeout(resolve2,
|
|
30559
|
+
await new Promise((resolve2) => setTimeout(resolve2, waitMs));
|
|
30127
30560
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
30128
30561
|
if (Date.now() - startTime >= timeoutMs) {
|
|
30129
30562
|
return {
|
|
@@ -30140,8 +30573,10 @@ async function pollForResult(getUrl, timeoutMs, label) {
|
|
|
30140
30573
|
const text = await handleResponse(resp);
|
|
30141
30574
|
return { ok: isOk, text };
|
|
30142
30575
|
}
|
|
30143
|
-
if (
|
|
30144
|
-
|
|
30576
|
+
if (schedule) {
|
|
30577
|
+
waitMs = schedule.intervalMs;
|
|
30578
|
+
} else if (Date.now() - startTime > 6e4) {
|
|
30579
|
+
waitMs = 3e4;
|
|
30145
30580
|
}
|
|
30146
30581
|
}
|
|
30147
30582
|
}
|
|
@@ -30158,8 +30593,11 @@ var TICKET_ARTIFACTS = {
|
|
|
30158
30593
|
saveSubdir: "plans",
|
|
30159
30594
|
filename: (n) => `${n}-plan.md`,
|
|
30160
30595
|
requestErrorPrefix: "Failed to request plan generation: ",
|
|
30161
|
-
confirmationText: (n) => `Plan generation requested for ${n}. Processing typically takes
|
|
30162
|
-
pollLabel: (n) => `Plan generation for ${n}
|
|
30596
|
+
confirmationText: (n) => `Plan generation requested for ${n}. Processing typically takes 10-15 minutes. Use get_plan with ticket_number "${n}" to retrieve the plan once processing completes.`,
|
|
30597
|
+
pollLabel: (n) => `Plan generation for ${n}`,
|
|
30598
|
+
// Plans take 10-15 min; don't poll during the first 3 minutes, then poll
|
|
30599
|
+
// once a minute. (Faster artifacts keep the default 15s→30s cadence.)
|
|
30600
|
+
pollSchedule: { initialDelayMs: 18e4, intervalMs: 6e4 }
|
|
30163
30601
|
},
|
|
30164
30602
|
architecture: {
|
|
30165
30603
|
kind: "single",
|
|
@@ -30291,7 +30729,12 @@ async function requestTicketArtifact(type, args) {
|
|
|
30291
30729
|
}
|
|
30292
30730
|
if (args.wait_for_result) {
|
|
30293
30731
|
const getUrl = buildGetUrl(config.getEndpoint(args.ticket_number), { repo_name: REPO_NAME });
|
|
30294
|
-
const result = await pollForResult(
|
|
30732
|
+
const result = await pollForResult(
|
|
30733
|
+
getUrl,
|
|
30734
|
+
9e5,
|
|
30735
|
+
config.pollLabel(args.ticket_number),
|
|
30736
|
+
config.pollSchedule
|
|
30737
|
+
);
|
|
30295
30738
|
if (!result.ok) {
|
|
30296
30739
|
return { content: [{ type: "text", text: result.text }] };
|
|
30297
30740
|
}
|
|
@@ -30594,16 +31037,16 @@ var registerTool = ((name, config, handler) => {
|
|
|
30594
31037
|
return toolHandle;
|
|
30595
31038
|
});
|
|
30596
31039
|
var commonFields = {
|
|
30597
|
-
ticket_number:
|
|
30598
|
-
repo_name:
|
|
30599
|
-
save_locally:
|
|
30600
|
-
wait_for_result:
|
|
31040
|
+
ticket_number: z16.string(),
|
|
31041
|
+
repo_name: z16.string().optional(),
|
|
31042
|
+
save_locally: z16.boolean().optional().default(true),
|
|
31043
|
+
wait_for_result: z16.boolean().optional().default(false).describe(
|
|
30601
31044
|
"When true, blocks and polls until ready, returning full content directly. When false (default), returns immediately with confirmation/handle. Use the corresponding get_* tool to retrieve results."
|
|
30602
31045
|
),
|
|
30603
|
-
second_opinion:
|
|
31046
|
+
second_opinion: z16.string().optional().describe(
|
|
30604
31047
|
"Provider routing override for THIS request. NOT the standalone second_opinion tool. Takes precedence over provider."
|
|
30605
31048
|
),
|
|
30606
|
-
provider:
|
|
31049
|
+
provider: z16.string().optional().describe(
|
|
30607
31050
|
"Use a specific LLM provider (openai, anthropic, gemini) without triggering second-opinion semantics."
|
|
30608
31051
|
)
|
|
30609
31052
|
};
|
|
@@ -30613,6 +31056,7 @@ if (ACTIVE_GROUPS.has("conductor")) {
|
|
|
30613
31056
|
registerSfccTools(registerTool, {
|
|
30614
31057
|
buildGetUrl,
|
|
30615
31058
|
getGetHeaders,
|
|
31059
|
+
getPostHeaders,
|
|
30616
31060
|
repoName: REPO_NAME,
|
|
30617
31061
|
getResolvedApiKey,
|
|
30618
31062
|
getDocsDir,
|
|
@@ -30666,13 +31110,13 @@ registerTool(
|
|
|
30666
31110
|
},
|
|
30667
31111
|
description: "Use to get an immediate, ad hoc independent critique on a plan or analysis you already have. Returns the responding model's reply text plus the resolved provider. This does NOT create or retrieve a Bridge artifact (use request_* tools for that).",
|
|
30668
31112
|
inputSchema: {
|
|
30669
|
-
prompt:
|
|
31113
|
+
prompt: z16.string().describe(
|
|
30670
31114
|
"The complete, self-contained brief to send to the second-opinion model. Include the full plan, recommendation, analysis, or question you want challenged, plus enough context for the responder to evaluate it independently. This is sent as the user message; the server constructs the system prompt."
|
|
30671
31115
|
),
|
|
30672
|
-
provider:
|
|
31116
|
+
provider: z16.enum(["anthropic", "openai", "gemini"]).describe(
|
|
30673
31117
|
"LLM provider family for the second opinion. Choose a family DIFFERENT from the one you are running on so the response is genuinely independent."
|
|
30674
31118
|
),
|
|
30675
|
-
model:
|
|
31119
|
+
model: z16.enum(["CHEAP_MODEL", "BASIC_MODEL", "PREMIUM_MODEL"]).describe(
|
|
30676
31120
|
"Model tier within the chosen provider. CHEAP_MODEL for quick sanity checks, BASIC_MODEL for focused reviews, PREMIUM_MODEL for serious architectural pushback."
|
|
30677
31121
|
)
|
|
30678
31122
|
}
|
|
@@ -30753,10 +31197,10 @@ registerTool(
|
|
|
30753
31197
|
},
|
|
30754
31198
|
description: "Generate an image from a text prompt using a provider image model. This tool spends provider credits on every call \u2014 cost scales with quality (low/medium/high). Defaults to low quality to minimize provider spend; increase quality only when fidelity matters. Returns native MCP image content (type: 'image') so the caller receives the image directly. The image is always also saved to the local BAPI_DOCS_DIR/images/ directory. Google Imagen outputs (provider='gemini') include an invisible SynthID watermark applied server-side by Google.",
|
|
30755
31199
|
inputSchema: {
|
|
30756
|
-
prompt:
|
|
30757
|
-
provider:
|
|
30758
|
-
quality:
|
|
30759
|
-
size:
|
|
31200
|
+
prompt: z16.string().min(1).max(8e3).describe("Text prompt sent to the image provider."),
|
|
31201
|
+
provider: z16.enum(["openai", "gemini"]).optional().default("openai").describe("Image provider. Defaults to 'openai' (gpt-image-2)."),
|
|
31202
|
+
quality: z16.enum(["low", "medium", "high"]).optional().default("low").describe("Image quality. Defaults to 'low' for cost control."),
|
|
31203
|
+
size: z16.enum(["1024x1024", "1024x1536", "1536x1024"]).optional().default("1024x1024").describe("Image dimensions. Defaults to '1024x1024'.")
|
|
30760
31204
|
}
|
|
30761
31205
|
},
|
|
30762
31206
|
async ({ prompt, provider, quality, size }) => {
|
|
@@ -30884,16 +31328,16 @@ registerTool(
|
|
|
30884
31328
|
},
|
|
30885
31329
|
description: "Deterministic pixel-fidelity oracle. Renders target_url headlessly at the comp size (viewport auto-matched, DPR 1), disables animation/font/caret jitter, then diffs vs a design comp (comp_ref: local path or Jira attachment) with AA tolerance. Returns mismatch_pct + diff_regions + a heatmap image; pass budget defaults to non-zero (2%), never 0%.",
|
|
30886
31330
|
inputSchema: {
|
|
30887
|
-
target_url:
|
|
30888
|
-
comp_ref:
|
|
31331
|
+
target_url: z16.string().min(1).describe("URL of the rendered page to screenshot (e.g. http://localhost:8000/...)."),
|
|
31332
|
+
comp_ref: z16.string().min(1).describe(
|
|
30889
31333
|
"The design comp: a local file path (absolute or relative to the project root), or a Jira attachment id/filename."
|
|
30890
31334
|
),
|
|
30891
|
-
viewport:
|
|
30892
|
-
width:
|
|
30893
|
-
height:
|
|
31335
|
+
viewport: z16.object({
|
|
31336
|
+
width: z16.number().int().positive(),
|
|
31337
|
+
height: z16.number().int().positive()
|
|
30894
31338
|
}).optional().describe("Explicit render viewport. Omit to auto-match the comp's intrinsic pixel dimensions."),
|
|
30895
|
-
mask_selectors:
|
|
30896
|
-
threshold:
|
|
31339
|
+
mask_selectors: z16.array(z16.string().min(1)).optional().describe("CSS selectors blacked out in BOTH images before diffing (dynamic/time-varying content)."),
|
|
31340
|
+
threshold: z16.number().positive().max(100).optional().default(2).describe("Pass budget as a percent of differing pixels (default 2%). Never 0%.")
|
|
30897
31341
|
}
|
|
30898
31342
|
},
|
|
30899
31343
|
async (args) => {
|
|
@@ -30950,16 +31394,16 @@ registerTool(
|
|
|
30950
31394
|
},
|
|
30951
31395
|
description: "Search for and list Jira tickets from the configured project. Filters by query text, status name, label, or date. Returns up to 'limit' tickets ordered by most recently updated. All data is fetched live from Jira. Use get_ticket to retrieve full details for a specific ticket.",
|
|
30952
31396
|
inputSchema: {
|
|
30953
|
-
query:
|
|
31397
|
+
query: z16.string().optional().describe(
|
|
30954
31398
|
`Free-text search string. Filters tickets via JQL text ~ '...' (searches summary, description, comments). Examples: "authentication error", "login page crash", "payment timeout"`
|
|
30955
31399
|
),
|
|
30956
|
-
status:
|
|
30957
|
-
labels:
|
|
31400
|
+
status: z16.string().optional().describe("Filter by Jira status name (e.g. 'To Do', 'In Progress', 'Done')"),
|
|
31401
|
+
labels: z16.string().optional().describe(
|
|
30958
31402
|
'Comma-separated Jira labels. Filters tickets via JQL labels in (...) (matches tickets carrying any of the given labels). Labels cannot contain spaces. Example: "bapi-idea-to-ticket-fa-1a2b3c"'
|
|
30959
31403
|
),
|
|
30960
|
-
limit:
|
|
30961
|
-
offset:
|
|
30962
|
-
updated_since:
|
|
31404
|
+
limit: z16.number().optional().default(20).describe("Maximum number of tickets to return (1-100, default 20)"),
|
|
31405
|
+
offset: z16.number().optional().default(0).describe("Number of results to skip for pagination (default 0)"),
|
|
31406
|
+
updated_since: z16.string().optional().describe("ISO date string (YYYY-MM-DD). Only return tickets updated on or after this date")
|
|
30963
31407
|
}
|
|
30964
31408
|
},
|
|
30965
31409
|
async ({ query, status, labels, limit, offset, updated_since }) => {
|
|
@@ -31095,18 +31539,18 @@ registerTool(
|
|
|
31095
31539
|
},
|
|
31096
31540
|
description: "Create a new Jira ticket in the configured project. Requires either description or file_path (or both \u2014 file_path takes precedence). Returns JSON with {ticket_key: 'PROJ-123', url: 'https://...'}. The ticket is created immediately in Jira \u2014 confirm details with the user before calling. The description field supports Jira markdown formatting. Pass parent_key ONLY when creating a child ticket under an existing Jira Epic; omit it for standalone tickets and for Epic parent creation itself.",
|
|
31097
31541
|
inputSchema: {
|
|
31098
|
-
summary:
|
|
31099
|
-
description:
|
|
31542
|
+
summary: z16.string().describe("Ticket title \u2014 keep under 100 characters"),
|
|
31543
|
+
description: z16.string().optional().describe(
|
|
31100
31544
|
"Required unless file_path is provided. Detailed description in markdown. Recommended structure: Summary (2-4 sentences), Requirements (bullet list with code file references), Acceptance Criteria (testable 'Done when...' statements)"
|
|
31101
31545
|
),
|
|
31102
|
-
file_path:
|
|
31546
|
+
file_path: z16.string().optional().describe(
|
|
31103
31547
|
"Path to a local markdown file whose contents will be used as the ticket description. If both file_path and description are provided, file_path takes precedence. The file must be UTF-8 encoded and under 1MB."
|
|
31104
31548
|
),
|
|
31105
|
-
issue_type:
|
|
31106
|
-
priority:
|
|
31107
|
-
labels:
|
|
31108
|
-
assignee:
|
|
31109
|
-
parent_key:
|
|
31549
|
+
issue_type: z16.string().describe("One of: 'Bug' (defect), 'Story' (user-facing feature), 'Task' (technical/infrastructure work)"),
|
|
31550
|
+
priority: z16.string().optional().describe("One of: 'Highest', 'High', 'Medium', 'Low', 'Lowest'. Omit to use Jira project default"),
|
|
31551
|
+
labels: z16.array(z16.string()).optional().describe("List of Jira labels to apply (e.g. ['frontend', 'tech-debt'])"),
|
|
31552
|
+
assignee: z16.string().optional().describe("Jira username or account ID of the assignee. Omit to leave unassigned"),
|
|
31553
|
+
parent_key: z16.string().optional().describe(
|
|
31110
31554
|
"Optional Jira Epic key to set as the parent of the newly created child issue. Omit for standalone tickets and Epic parent creation."
|
|
31111
31555
|
)
|
|
31112
31556
|
}
|
|
@@ -31205,7 +31649,7 @@ registerTool(
|
|
|
31205
31649
|
},
|
|
31206
31650
|
description: "Queue a background job to parse and index the repository for Bridge API's AI agents. The API only ENQUEUES the work; the CPU-bound parse runs in a separate process, so this returns immediately and never blocks. This should be run after major codebase changes so that plans and questions reflect the latest code. Returns 202 with {message: 'Repository parsing queued'} on success, or {message: 'Repository parsing already in progress'} if a job is already running. The job runs asynchronously \u2014 there is no completion callback; poll get_parse_status to observe when it reaches terminal success or terminal failure. For large repositories this may take several minutes. Confirm with the user before triggering.",
|
|
31207
31651
|
inputSchema: {
|
|
31208
|
-
directory_path:
|
|
31652
|
+
directory_path: z16.string().optional().describe(
|
|
31209
31653
|
"Subdirectory to scope the parse to (e.g. 'src/python'). Omit to parse the entire repository"
|
|
31210
31654
|
)
|
|
31211
31655
|
}
|
|
@@ -31282,14 +31726,14 @@ registerTool(
|
|
|
31282
31726
|
description: "Post a comment on a Jira ticket. The comment appears immediately in Jira. Supports markdown formatting. For long comments (over ~2000 characters), set attach_as_file to true \u2014 this attaches the comment as a .md file instead of posting inline, which avoids Jira's comment length limitations.\n\nTip: To generate plans, clarifying questions, or ticket critiques, use the dedicated request_plan_generation, request_clarifying_questions, or request_ticket_critique tools.",
|
|
31283
31727
|
inputSchema: {
|
|
31284
31728
|
ticket_number: commonFields.ticket_number,
|
|
31285
|
-
comment:
|
|
31286
|
-
file_path:
|
|
31729
|
+
comment: z16.string().optional().describe("Comment text in markdown format. Can include code blocks, lists, headings, etc. Optional if file_path is provided."),
|
|
31730
|
+
file_path: z16.string().optional().describe(
|
|
31287
31731
|
"Path to a local markdown file whose contents will be used as the comment. If both file_path and comment are provided, file_path takes precedence. The file must be UTF-8 encoded and under 1MB."
|
|
31288
31732
|
),
|
|
31289
|
-
attach_as_file:
|
|
31733
|
+
attach_as_file: z16.boolean().optional().default(false).describe(
|
|
31290
31734
|
"Set to true to attach the comment as a .md file instead of posting inline. Recommended for comments over 2000 characters"
|
|
31291
31735
|
),
|
|
31292
|
-
file_name:
|
|
31736
|
+
file_name: z16.string().optional().describe(
|
|
31293
31737
|
"Custom filename for the attached .md file (only used when attach_as_file is true). Defaults to {ticket_number}-comment.md if not provided. Example: 'PROJ-123-clarifying-questions.md'"
|
|
31294
31738
|
)
|
|
31295
31739
|
}
|
|
@@ -31329,8 +31773,8 @@ registerTool(
|
|
|
31329
31773
|
description: "Update the description of an existing Jira ticket. This is a direct, synchronous update that overwrites the existing description with the provided text. The description should be in markdown format \u2014 it will be automatically converted to Jira wiki markup. This does NOT create a new ticket. Use create_ticket for that. Returns a success message with the ticket number, or an error if the update fails.",
|
|
31330
31774
|
inputSchema: {
|
|
31331
31775
|
ticket_number: commonFields.ticket_number,
|
|
31332
|
-
description:
|
|
31333
|
-
file_path:
|
|
31776
|
+
description: z16.string().optional().describe("New description text in markdown format. Optional if file_path is provided. This will completely replace the existing description."),
|
|
31777
|
+
file_path: z16.string().optional().describe(
|
|
31334
31778
|
"Path to a local markdown file whose contents will be used as the new description. If both file_path and description are provided, file_path takes precedence. The file must be UTF-8 encoded and under 1MB."
|
|
31335
31779
|
)
|
|
31336
31780
|
}
|
|
@@ -31360,39 +31804,39 @@ registerTool(
|
|
|
31360
31804
|
openWorldHint: true
|
|
31361
31805
|
},
|
|
31362
31806
|
description: "Manages Jira attachments. Operations: upload, download, list. upload: text or image/png, image/jpeg, image/webp, image/gif (10 MB max), else rejected. download saves binary/image attachments (design comps) as raw bytes to a file_path inside the worktree/project root and reports the saved path; UTF-8 text attachments are returned inline.",
|
|
31363
|
-
inputSchema:
|
|
31364
|
-
|
|
31365
|
-
operation:
|
|
31807
|
+
inputSchema: z16.discriminatedUnion("operation", [
|
|
31808
|
+
z16.object({
|
|
31809
|
+
operation: z16.literal("upload"),
|
|
31366
31810
|
ticket_number: commonFields.ticket_number,
|
|
31367
|
-
file_path:
|
|
31811
|
+
file_path: z16.string().optional().describe(
|
|
31368
31812
|
"Path to a local file to upload. Binary uploads are restricted to the allowlisted image types image/png, image/jpeg, image/webp, image/gif, up to `10 MB`; other binaries such as PDFs and ZIPs are rejected as unsupported attachment types. Text uploads are up to `1 MB`. If both file_path and content are provided, file_path takes precedence."
|
|
31369
31813
|
),
|
|
31370
|
-
content:
|
|
31371
|
-
file_name:
|
|
31814
|
+
content: z16.string().max(1048576).optional().describe("Inline text content to upload (max `1 MB`). Optional if file_path is provided."),
|
|
31815
|
+
file_name: z16.string().optional().describe(
|
|
31372
31816
|
"Filename for the attachment in Jira. Defaults to the basename of file_path if provided, or {ticket_number}-attachment.md otherwise."
|
|
31373
31817
|
),
|
|
31374
|
-
link_type:
|
|
31818
|
+
link_type: z16.string().optional().describe(
|
|
31375
31819
|
"When provided, also syncs the content to Bridge API's tickets_links table. Known values: clarifying-questions.md, debugging-guidance.md, ticket-quality-critique.md, architecture-plan.md, fsd-plan.md, prd-plan.md. Cannot be used with binary file uploads."
|
|
31376
31820
|
),
|
|
31377
|
-
replace_existing:
|
|
31821
|
+
replace_existing: z16.boolean().optional().default(true).describe(
|
|
31378
31822
|
"When true (default), deletes any existing attachment with the same filename before uploading."
|
|
31379
31823
|
)
|
|
31380
31824
|
}).strict(),
|
|
31381
|
-
|
|
31382
|
-
operation:
|
|
31825
|
+
z16.object({
|
|
31826
|
+
operation: z16.literal("download"),
|
|
31383
31827
|
ticket_number: commonFields.ticket_number,
|
|
31384
|
-
attachment_id:
|
|
31828
|
+
attachment_id: z16.string().optional().describe(
|
|
31385
31829
|
"Jira attachment ID. Mutually exclusive with filename. For design/UI tickets, pass the attachment_id from the plan's DESIGN COMP CANDIDATES section to fetch the design comp."
|
|
31386
31830
|
),
|
|
31387
|
-
filename:
|
|
31388
|
-
file_path:
|
|
31831
|
+
filename: z16.string().optional().describe("Attachment filename. If multiple exist, returns the most recent. Mutually exclusive with attachment_id."),
|
|
31832
|
+
file_path: z16.string().optional().describe(
|
|
31389
31833
|
"Override the default save location (must stay within the project root/worktree). Pass a file_path when you need to open an image/design comp locally. If omitted, saves to {BAPI_DOCS_DIR}/attachments/{ticket_number}/{filename}."
|
|
31390
31834
|
)
|
|
31391
31835
|
}).strict(),
|
|
31392
|
-
|
|
31393
|
-
operation:
|
|
31836
|
+
z16.object({
|
|
31837
|
+
operation: z16.literal("list"),
|
|
31394
31838
|
ticket_number: commonFields.ticket_number,
|
|
31395
|
-
include_ai_generated:
|
|
31839
|
+
include_ai_generated: z16.boolean().optional().describe("Include AI-generated attachments in the list (default: false)")
|
|
31396
31840
|
}).strict()
|
|
31397
31841
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31398
31842
|
])
|
|
@@ -31544,7 +31988,7 @@ registerTool(
|
|
|
31544
31988
|
idempotentHint: false,
|
|
31545
31989
|
openWorldHint: true
|
|
31546
31990
|
},
|
|
31547
|
-
description: "START (or refresh) async generation of an implementation plan for a Jira ticket. This triggers an asynchronous background job \u2014 results are NOT immediate. Processing typically takes
|
|
31991
|
+
description: "START (or refresh) async generation of an implementation plan for a Jira ticket. This triggers an asynchronous background job \u2014 results are NOT immediate. Processing typically takes 10-15 minutes depending on ticket complexity and number of attachments. The matching get_plan tool retrieves the generated plan later (call get_plan with the same ticket_number) \u2014 unless you set wait_for_result, in which case this call blocks and returns the plan directly. Returns 202 if the request was accepted, 404 if the ticket does not exist in Jira, or 403 if the API key is unauthorized. Set wait_for_result to true to block until the result is ready (typically 10-15 minutes) instead of returning immediately.",
|
|
31548
31992
|
inputSchema: {
|
|
31549
31993
|
ticket_number: commonFields.ticket_number,
|
|
31550
31994
|
wait_for_result: commonFields.wait_for_result,
|
|
@@ -31568,9 +32012,9 @@ registerTool(
|
|
|
31568
32012
|
},
|
|
31569
32013
|
description: "Estimate a Jira Epic or ticket-key group via the epic estimation orchestrator. Exactly one of epic_key/ticket_keys required (never both). allow_partial allows a partial result on child failures (default: fail-closed). No mode field; source is inferred.",
|
|
31570
32014
|
inputSchema: {
|
|
31571
|
-
epic_key:
|
|
31572
|
-
ticket_keys:
|
|
31573
|
-
allow_partial:
|
|
32015
|
+
epic_key: z16.string().trim().min(1).optional().describe("Jira Epic key. Mutually exclusive with ticket_keys."),
|
|
32016
|
+
ticket_keys: z16.array(z16.string().trim().min(1)).min(1).optional().describe("Explicit ticket-key group. Mutually exclusive with epic_key."),
|
|
32017
|
+
allow_partial: z16.boolean().optional().describe("Partial estimate on child failures. Default: false (fail-closed).")
|
|
31574
32018
|
}
|
|
31575
32019
|
},
|
|
31576
32020
|
async (args) => {
|
|
@@ -31638,15 +32082,15 @@ registerTool(
|
|
|
31638
32082
|
description: "Use to start async generation of a design document (tdd, fsd, or prd) for a Jira ticket. Returns confirmation immediately (or the full document if wait_for_result is true). Use get_doc to retrieve. Generates and persists a retrievable artifact.",
|
|
31639
32083
|
inputSchema: {
|
|
31640
32084
|
ticket_number: commonFields.ticket_number,
|
|
31641
|
-
doc_type:
|
|
32085
|
+
doc_type: z16.enum(["tdd", "fsd", "prd"]).describe(
|
|
31642
32086
|
"Which design document to generate: 'tdd' (Technical Design Document, engineer audience), 'fsd' (Functional Specification Document, product/functional audience), or 'prd' (Product Requirements Document, product-requirements focused: problem, goals, success metrics)."
|
|
31643
32087
|
),
|
|
31644
32088
|
wait_for_result: commonFields.wait_for_result,
|
|
31645
32089
|
save_locally: commonFields.save_locally,
|
|
31646
|
-
second_opinion:
|
|
32090
|
+
second_opinion: z16.string().optional().describe(
|
|
31647
32091
|
"Provider routing override for THIS artifact-generation request (e.g. 'anthropic', 'openai', 'gemini'). When set, the artifact is generated by the named provider and, where supported, a cross-provider second-opinion pass is applied to this request only. Takes precedence over `provider` when both are set."
|
|
31648
32092
|
),
|
|
31649
|
-
provider:
|
|
32093
|
+
provider: z16.string().optional().describe(
|
|
31650
32094
|
"Pure provider switch \u2014 use a specific LLM provider (openai, anthropic, gemini) without triggering second-opinion semantics. If both provider and second_opinion are set, second_opinion takes precedence."
|
|
31651
32095
|
)
|
|
31652
32096
|
}
|
|
@@ -31667,7 +32111,7 @@ registerTool(
|
|
|
31667
32111
|
description: "RETRIEVE an already-generated design document for a Jira ticket, routed by doc_type. Use doc_type 'tdd' for the Technical Design Document, 'fsd' for the Functional Specification Document, or 'prd' for the Product Requirements Document. This tool only fetches an existing document \u2014 it does NOT start or trigger generation. If no document exists yet (or you need a fresh one), call `create_doc` first with the same doc_type. Returns the full document as markdown text \u2014 present it verbatim without summarizing. Returns a 404 / not-found response when no document is ready yet \u2014 that means generation has not run, not that this tool failed.",
|
|
31668
32112
|
inputSchema: {
|
|
31669
32113
|
ticket_number: commonFields.ticket_number,
|
|
31670
|
-
doc_type:
|
|
32114
|
+
doc_type: z16.enum(["tdd", "fsd", "prd"]).describe(
|
|
31671
32115
|
"Which design document to retrieve: 'tdd' (Technical Design Document), 'fsd' (Functional Specification Document), or 'prd' (Product Requirements Document)."
|
|
31672
32116
|
),
|
|
31673
32117
|
save_locally: commonFields.save_locally
|
|
@@ -31756,12 +32200,12 @@ registerTool(
|
|
|
31756
32200
|
save_locally: commonFields.save_locally,
|
|
31757
32201
|
second_opinion: commonFields.second_opinion,
|
|
31758
32202
|
provider: commonFields.provider,
|
|
31759
|
-
rounds:
|
|
31760
|
-
|
|
31761
|
-
|
|
31762
|
-
|
|
31763
|
-
|
|
31764
|
-
|
|
32203
|
+
rounds: z16.union([
|
|
32204
|
+
z16.literal(1),
|
|
32205
|
+
z16.literal(2),
|
|
32206
|
+
z16.literal("1"),
|
|
32207
|
+
z16.literal("2"),
|
|
32208
|
+
z16.literal("")
|
|
31765
32209
|
]).optional().describe(
|
|
31766
32210
|
"Review rounds (1=single pass, 2=full second-opinion). Omit for backend adaptive routing."
|
|
31767
32211
|
)
|
|
@@ -31824,7 +32268,7 @@ registerTool(
|
|
|
31824
32268
|
description: "Write/update Bridge API's DATABASE lifecycle-tracking record for a ticket ONLY. This registers the ticket in Bridge's own database so workflow state timestamps (critique, clarify, plan, implement) can be tracked. It does NOT edit anything in Jira: it does not change the Jira summary, description, comments, attachments, or status. If the ticket is already tracked, this is a safe no-op \u2014 it upserts the description and repo_name without error. After create_ticket, this is the correct next step when you want Bridge to track that ticket's workflow timestamps / artifact state. For Jira mutations use a different tool instead: `update_ticket_description` to replace the Jira description, `add_comment` to post a Jira comment, and `update_jira_status` to move the Jira workflow status. The repo_name is automatically injected from the configured environment.",
|
|
31825
32269
|
inputSchema: {
|
|
31826
32270
|
ticket_number: commonFields.ticket_number,
|
|
31827
|
-
description:
|
|
32271
|
+
description: z16.string().optional().describe("Ticket description text. Optional \u2014 used to store a local copy of the description for reference.")
|
|
31828
32272
|
}
|
|
31829
32273
|
},
|
|
31830
32274
|
async ({ ticket_number, description }) => {
|
|
@@ -31854,7 +32298,7 @@ registerTool(
|
|
|
31854
32298
|
description: "Update workflow state timestamps on a tracked ticket. Each specified field is set to the current UTC timestamp on the server. Valid field names: 'critique_called', 'critique_answered', 'clarify_called', 'clarify_answered', 'plan_generated', 'implemented', 'reimplement_called'. The ticket must already be tracked (via track_ticket) or a 404 error is returned. Returns 400 if any field name is invalid. The repo_name is automatically injected from the configured environment.",
|
|
31855
32299
|
inputSchema: {
|
|
31856
32300
|
ticket_number: commonFields.ticket_number,
|
|
31857
|
-
fields:
|
|
32301
|
+
fields: z16.array(z16.string()).describe("List of state field names to set to the current UTC timestamp. Valid values: 'critique_called', 'critique_answered', 'clarify_called', 'clarify_answered', 'plan_generated', 'implemented', 'reimplement_called'")
|
|
31858
32302
|
}
|
|
31859
32303
|
},
|
|
31860
32304
|
async ({ ticket_number, fields }) => {
|
|
@@ -31931,8 +32375,8 @@ registerTool(
|
|
|
31931
32375
|
description: 'Transition a Jira ticket to a specified target status by executing a workflow transition. Provide either target_status (matched case-insensitively against available transitions) or transition_id (used directly). If transition_id is provided, it takes precedence over target_status. Pass target_status as "auto" to trigger server-side status resolution via LLM \u2014 the server determines the correct post-PR status automatically. If auto-resolve finds no match, returns status: skipped (not an error). Returns the from/to status on success, or an error listing available transitions if no match is found. The repo_name is automatically injected from the configured environment.',
|
|
31932
32376
|
inputSchema: {
|
|
31933
32377
|
ticket_number: commonFields.ticket_number,
|
|
31934
|
-
target_status:
|
|
31935
|
-
transition_id:
|
|
32378
|
+
target_status: z16.string().optional().describe('Target status name to transition to (case-insensitive match). Pass "auto" to resolve the target status server-side via LLM agent.'),
|
|
32379
|
+
transition_id: z16.string().optional().describe("Specific transition ID to execute (takes precedence over target_status)")
|
|
31936
32380
|
}
|
|
31937
32381
|
},
|
|
31938
32382
|
async ({ ticket_number, target_status, transition_id }) => {
|
|
@@ -31963,7 +32407,7 @@ registerTool(
|
|
|
31963
32407
|
description: "Ask an LLM agent to CHOOSE the project's post-PR target Jira status, and cache that choice per project. The agent selects the single workflow status that best represents 'code committed via PR but not yet tested.' Results are cached per-project \u2014 subsequent calls return the cached value unless force_rerun is true. This does NOT list all available transitions \u2014 use `get_jira_transitions` for the full transition list. This also does NOT move the ticket \u2014 use `update_jira_status` to actually perform the status transition. Requires a ticket_number to fetch available transitions from Jira. The repo_name is automatically injected from the configured environment.",
|
|
31964
32408
|
inputSchema: {
|
|
31965
32409
|
ticket_number: commonFields.ticket_number,
|
|
31966
|
-
force_rerun:
|
|
32410
|
+
force_rerun: z16.boolean().optional().describe("Set to true to bypass the cache and re-resolve the target status via LLM")
|
|
31967
32411
|
}
|
|
31968
32412
|
},
|
|
31969
32413
|
async ({ ticket_number, force_rerun }) => {
|
|
@@ -32009,8 +32453,14 @@ var VALID_CONFIG_FIELDS = [
|
|
|
32009
32453
|
BASE_BRANCH_CONFIG_FIELD,
|
|
32010
32454
|
"difficulty_model_routing_enabled",
|
|
32011
32455
|
"difficulty_model_tier_overrides",
|
|
32456
|
+
// BAPI-555 SFCC Log Monitor filter-rule overlay (deferred JSONB array policy field).
|
|
32457
|
+
"sfcc_log_filter_rules",
|
|
32012
32458
|
// BAPI-505 estimator calibration (deferred scalar, not a bootstrap field).
|
|
32013
32459
|
"ai_automation_level",
|
|
32460
|
+
// Jira-optional (local ticket backend) fields. jira_ticket_key is the local
|
|
32461
|
+
// mint prefix — REQUIRED before creating tickets in local mode.
|
|
32462
|
+
"ticket_backend_mode",
|
|
32463
|
+
"jira_ticket_key",
|
|
32014
32464
|
// BAPI-356 easy-install bootstrap fields (also exposed via the registry).
|
|
32015
32465
|
"working_in",
|
|
32016
32466
|
"version_control_system",
|
|
@@ -32030,35 +32480,36 @@ registerTool(
|
|
|
32030
32480
|
openWorldHint: true
|
|
32031
32481
|
},
|
|
32032
32482
|
description: "Manages Bridge API configuration fields. Operations: get, update, list.",
|
|
32033
|
-
inputSchema:
|
|
32034
|
-
|
|
32035
|
-
operation:
|
|
32036
|
-
field_name:
|
|
32483
|
+
inputSchema: z16.discriminatedUnion("operation", [
|
|
32484
|
+
z16.object({
|
|
32485
|
+
operation: z16.literal("get"),
|
|
32486
|
+
field_name: z16.string().describe(
|
|
32037
32487
|
`Read the current value and metadata for a config field. For install bootstrap, prefer get_install_manifest over many individual reads. Valid options: ${VALID_CONFIG_FIELDS}`
|
|
32038
32488
|
)
|
|
32039
32489
|
}).strict(),
|
|
32040
|
-
|
|
32041
|
-
operation:
|
|
32042
|
-
field_name:
|
|
32490
|
+
z16.object({
|
|
32491
|
+
operation: z16.literal("update"),
|
|
32492
|
+
field_name: z16.string().describe(
|
|
32043
32493
|
`The configuration field to update. Valid options: ${VALID_CONFIG_FIELDS}. Always call with operation: "get" first to read the current value. For install bootstrap, prefer apply_install_manifest over many individual updates. Returns 400 if the field name is invalid, 404 if no configuration row exists.`
|
|
32044
32494
|
),
|
|
32045
|
-
value:
|
|
32046
|
-
|
|
32047
|
-
|
|
32048
|
-
|
|
32049
|
-
|
|
32495
|
+
value: z16.union([
|
|
32496
|
+
z16.string(),
|
|
32497
|
+
z16.boolean(),
|
|
32498
|
+
z16.array(z16.string()),
|
|
32499
|
+
z16.array(z16.record(z16.string(), z16.unknown())),
|
|
32500
|
+
z16.record(z16.string(), z16.union([z16.string(), z16.null()]))
|
|
32050
32501
|
]).optional().describe(
|
|
32051
|
-
`The new value for the configuration field. Provide either value or file_path, not both. Most fields take a string; scalar boolean fields (e.g. allow_mutating_smoke_ops, difficulty_model_routing_enabled) take true/false. The selected_mcp_slugs field takes a JSON array of supported MCP validation manual slug strings (e.g. ["b2c-commerce-developer", "playwright-mcp", "pwa-kit-mcp"]) \u2014 pass an array of strings, not a comma-delimited string; an empty array clears the selection. The difficulty_model_tier_overrides field takes a JSON object mapping tier names ("cheap"/"basic"/"premium") to per-repo model aliases (e.g. {"premium": "opus"}) \u2014 pass an object, not a string; an empty object clears all overrides. The difficulty_model_routing_enabled field enables difficulty-based /start-tickets model routing (default ON); pass true/false. The base_branch field is a string/null field controlling the development base branch used by PR creation (/create-pr) and start-tickets worktree creation; an empty/null value clears it and automations fall back to 'main'. For string fields, omit both value and file_path to set the field to NULL (clearing it). Scalar boolean fields are NOT NULL and have no clear/null state: omitting the value writes false (matching the API-layer coercion), so pass true/false explicitly.`
|
|
32502
|
+
`The new value for the configuration field. Provide either value or file_path, not both. Most fields take a string; scalar boolean fields (e.g. allow_mutating_smoke_ops, difficulty_model_routing_enabled) take true/false. The selected_mcp_slugs field takes a JSON array of supported MCP validation manual slug strings (e.g. ["b2c-commerce-developer", "playwright-mcp", "pwa-kit-mcp"]) \u2014 pass an array of strings, not a comma-delimited string; an empty array clears the selection. The difficulty_model_tier_overrides field takes a JSON object mapping tier names ("cheap"/"basic"/"premium") to per-repo model aliases (e.g. {"premium": "opus"}) \u2014 pass an object, not a string; an empty object clears all overrides. The sfcc_log_filter_rules field takes a JSON array of SFCC filter-rule objects, each shaped {type, match, value, priority} (e.g. [{"type": "exclude", "match": "keyword", "value": "favicon", "priority": 500}]) \u2014 pass an array of objects, not a string; an empty array clears the overlay. The backend validates each rule. The difficulty_model_routing_enabled field enables difficulty-based /start-tickets model routing (default ON); pass true/false. The base_branch field is a string/null field controlling the development base branch used by PR creation (/create-pr) and start-tickets worktree creation; an empty/null value clears it and automations fall back to 'main'. For string fields, omit both value and file_path to set the field to NULL (clearing it). Scalar boolean fields are NOT NULL and have no clear/null state: omitting the value writes false (matching the API-layer coercion), so pass true/false explicitly.`
|
|
32052
32503
|
),
|
|
32053
|
-
file_path:
|
|
32504
|
+
file_path: z16.string().optional().describe(
|
|
32054
32505
|
"Path to a local file whose contents will be used as the new value. Useful for large configuration values like detailed review instructions. The file must be UTF-8 encoded and under 1MB. Not supported for scalar boolean fields like allow_mutating_smoke_ops."
|
|
32055
32506
|
),
|
|
32056
|
-
only_if_null:
|
|
32507
|
+
only_if_null: z16.boolean().optional().describe(
|
|
32057
32508
|
"Secondary conditional-write guard: when true, the field is updated only if its column is currently NULL (returns status 'skipped'/reason 'already_set' otherwise). Legal only for nullable columns (HTTP 422 otherwise). For easy install, prefer apply_install_manifest."
|
|
32058
32509
|
)
|
|
32059
32510
|
}).strict(),
|
|
32060
|
-
|
|
32061
|
-
operation:
|
|
32511
|
+
z16.object({
|
|
32512
|
+
operation: z16.literal("list")
|
|
32062
32513
|
}).strict()
|
|
32063
32514
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32064
32515
|
])
|
|
@@ -32129,6 +32580,31 @@ registerTool(
|
|
|
32129
32580
|
const text2 = await handleResponse(resp2);
|
|
32130
32581
|
return { content: [{ type: "text", text: text2 }] };
|
|
32131
32582
|
}
|
|
32583
|
+
const JSON_ARRAY_OF_OBJECTS_CONFIG_FIELDS = ["sfcc_log_filter_rules"];
|
|
32584
|
+
if (JSON_ARRAY_OF_OBJECTS_CONFIG_FIELDS.includes(field_name)) {
|
|
32585
|
+
if (file_path) {
|
|
32586
|
+
return {
|
|
32587
|
+
isError: true,
|
|
32588
|
+
content: [{
|
|
32589
|
+
type: "text",
|
|
32590
|
+
text: JSON.stringify({
|
|
32591
|
+
error: `'${field_name}' is a JSON array field; file_path updates are not supported. Pass value as an array of rule objects.`
|
|
32592
|
+
})
|
|
32593
|
+
}]
|
|
32594
|
+
};
|
|
32595
|
+
}
|
|
32596
|
+
const arrayOfObjectsValue = value === void 0 ? [] : value;
|
|
32597
|
+
const resp2 = await fetch(
|
|
32598
|
+
buildUrl(`/config-field/${encodeURIComponent(field_name)}`),
|
|
32599
|
+
{
|
|
32600
|
+
method: "PUT",
|
|
32601
|
+
headers: await getPostHeaders(),
|
|
32602
|
+
body: JSON.stringify(withGuard(arrayOfObjectsValue))
|
|
32603
|
+
}
|
|
32604
|
+
);
|
|
32605
|
+
const text2 = await handleResponse(resp2);
|
|
32606
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
32607
|
+
}
|
|
32132
32608
|
const BOOLEAN_CONFIG_FIELDS = ["allow_mutating_smoke_ops", "difficulty_model_routing_enabled"];
|
|
32133
32609
|
if (BOOLEAN_CONFIG_FIELDS.includes(field_name)) {
|
|
32134
32610
|
if (file_path) {
|
|
@@ -32238,7 +32714,7 @@ registerTool(
|
|
|
32238
32714
|
idempotentHint: true,
|
|
32239
32715
|
openWorldHint: true
|
|
32240
32716
|
},
|
|
32241
|
-
description: "Read the easy-install configuration manifest for the configured repository in one call. Returns ordered field groups (each bootstrap field with its current value, is_set flag, agent guidance, examples, and validation summary), a list of deferred fields (owned by /learn-repository or set deliberately), an integrations checklist (presence booleans only \u2014 credential values are never returned; direct humans to the setup UI, never transport secrets), a next_step pointer, done_criteria, a command_contract_version (compare it to the /install-bridge command's stated contract version to detect a stale scaffolded command copy), and a signed snapshot_token. Pass that exact snapshot_token to apply_install_manifest; tokens expire after 24 hours (re-read the manifest for a fresh one). Prefer this over many individual config_field reads during install bootstrap.",
|
|
32717
|
+
description: "Read the easy-install configuration manifest for the configured repository in one call. Returns ordered field groups (each bootstrap field with its current value, is_set flag, agent guidance, examples, and validation summary), a list of deferred fields (owned by /learn-repository or set deliberately), an integrations checklist (presence booleans only \u2014 credential values are never returned; direct humans to the setup UI, never transport secrets), a next_step pointer, done_criteria, a command_contract_version (compare it to the /install-bridge command's stated contract version to detect a stale scaffolded command copy), and a signed snapshot_token. Pass that exact snapshot_token to apply_install_manifest; tokens expire after 24 hours (re-read the manifest for a fresh one). Prefer this over many individual config_field reads during install bootstrap. The response also carries an additive, secret-free capability report (BAPI-617): the separate readiness dimensions configured / learned / indexed (indexed is true, false, or null \u2014 a null means the index status is indeterminate and must NOT be interpreted as successfully indexed), and the server-computed locked_tools / unlocked_tools arrays. Each tool entry has the exact shape {tool, effect, missing, semantics}: effect BLOCK means the tool is unavailable until its dependencies are met, DEGRADE means it is usable now but without codebase context; missing is the server-computed list of dependency identifiers; semantics is all_of or any_of and clients MUST preserve it (any_of means either VCS-provider credential satisfies the requirement). Clients format this contract for humans and never recompute tool membership themselves. This tool remains read-only and never registers or writes anything.",
|
|
32242
32718
|
inputSchema: {
|
|
32243
32719
|
save_locally: commonFields.save_locally
|
|
32244
32720
|
}
|
|
@@ -32268,10 +32744,10 @@ registerTool(
|
|
|
32268
32744
|
},
|
|
32269
32745
|
description: 'Apply easy-install configuration in one atomic call. Pass the snapshot_token returned by get_install_manifest plus a fields object. Each field value is either a scalar (e.g. "base_branch": "main") or an object (e.g. "project_description": {"value": "...", "confirmed": true}). Fields the manifest marks requires_confirmation (e.g. project_description, selected_mcp_slugs) MUST be passed as {value, confirmed: true} and only after explicit human approval. The server owns skip-if-set, conflict detection, and confirmation semantics and returns six buckets: applied, skipped, conflict, rejected, deferred, needs_confirmation. The apply is partial-tolerant: fields that fail validation (or are not bootstrap-eligible) land in the rejected bucket while the valid fields still commit \u2014 a rejected field is reported, not fatal, so do not retry the whole call for one rejection. HTTP 422 is reserved for snapshot-token problems (invalid, expired after 24h, or signed with a since-rotated API key): re-read the manifest and retry once with the fresh token.',
|
|
32270
32746
|
inputSchema: {
|
|
32271
|
-
snapshot_token:
|
|
32747
|
+
snapshot_token: z16.string().describe(
|
|
32272
32748
|
"The exact snapshot_token returned by get_install_manifest for this repository."
|
|
32273
32749
|
),
|
|
32274
|
-
fields:
|
|
32750
|
+
fields: z16.record(z16.string(), z16.any()).describe(
|
|
32275
32751
|
'Map of field_name to value. A value is either a scalar or an object {value, confirmed}. Pass project_description only as {value: "...", confirmed: true} after human approval.'
|
|
32276
32752
|
)
|
|
32277
32753
|
}
|
|
@@ -32318,7 +32794,7 @@ registerTool(
|
|
|
32318
32794
|
},
|
|
32319
32795
|
description: "Persist the ALREADY-VALIDATED Bridge API key for this repo into the user-scoped credential store (`~/.config/bridge/credentials.json`) under the target `bapi:<repo_name>`, so that Bash-spawned CLI features such as `start-tickets` (a different runtime surface than the MCP server) can resolve it for difficulty\u2192model routing. This is the final stage of `/install-bridge`. The key is resolved INSIDE the MCP server process (env-first, then the existing store) using the provided `repo_name` as the store identity \u2014 it is NEVER passed as a tool argument. Existing credentials are preserved; only `BAPI_API_KEY` for this repo is upserted. The response is secret-free (it reports ok/action/target/path only) and never echoes the key value.",
|
|
32320
32796
|
inputSchema: {
|
|
32321
|
-
repo_name:
|
|
32797
|
+
repo_name: z16.string().describe(
|
|
32322
32798
|
"The repository name to store the routing credential under (target `bapi:<repo_name>`). This is the ONLY input \u2014 do not pass the API key, a secret, or a token; the key is resolved inside the MCP server process."
|
|
32323
32799
|
)
|
|
32324
32800
|
}
|
|
@@ -32453,10 +32929,10 @@ registerTool(
|
|
|
32453
32929
|
},
|
|
32454
32930
|
description: "Use to start async deep research on a technical topic using AI-powered web search. Returns a task_id immediately (or the full report if wait_for_result is true). Use get_deep_research to retrieve. Generates and persists a retrievable artifact.",
|
|
32455
32931
|
inputSchema: {
|
|
32456
|
-
query:
|
|
32932
|
+
query: z16.string().describe(
|
|
32457
32933
|
"The research query. Be specific and detailed about what you need to learn. Good: 'What are the tradeoffs between Redis, Memcached, and DynamoDB DAX for caching in a Python FastAPI application serving 10k RPM, including connection pooling, serialization overhead, and failure modes?' Bad: 'caching options' (too vague \u2014 use a web search instead)"
|
|
32458
32934
|
),
|
|
32459
|
-
context:
|
|
32935
|
+
context: z16.string().optional().describe(
|
|
32460
32936
|
"Optional context to focus the research scope. Describe your current task, tech stack, and constraints. Example: 'I am building a FastAPI application that uses PostgreSQL and needs to implement real-time notifications. Focus on Python-specific solutions compatible with async frameworks.'"
|
|
32461
32937
|
),
|
|
32462
32938
|
ticket_number: commonFields.ticket_number.optional(),
|
|
@@ -32551,10 +33027,10 @@ registerTool(
|
|
|
32551
33027
|
},
|
|
32552
33028
|
description: "RETRIEVE the result of a previously submitted deep research request. This tool only fetches an existing/in-progress result \u2014 it does NOT start or trigger new research. If you have not submitted a research request yet (or you need a new one), call `request_deep_research` first; it starts the async research and this `get_deep_research` tool retrieves the result. Returns the full markdown research report if the task is completed, or a structured status response (still processing / failed / not-found) if the report is not ready yet \u2014 that means research has not finished, not that this tool failed. Use this after calling request_deep_research with wait_for_result=false.",
|
|
32553
33029
|
inputSchema: {
|
|
32554
|
-
task_id:
|
|
33030
|
+
task_id: z16.number().describe(
|
|
32555
33031
|
"The task ID returned by request_deep_research."
|
|
32556
33032
|
),
|
|
32557
|
-
query_slug:
|
|
33033
|
+
query_slug: z16.string().optional().describe(
|
|
32558
33034
|
"Optional slug derived from the original query, used for the saved filename. If omitted, the file is saved as 'research-{task_id}.md'."
|
|
32559
33035
|
),
|
|
32560
33036
|
save_locally: commonFields.save_locally
|
|
@@ -32676,32 +33152,32 @@ registerTool(
|
|
|
32676
33152
|
},
|
|
32677
33153
|
description: "Use to start an async brainstorm that fans out a task to opinion-provider LLMs. Returns a brainstorm_id immediately (or the full result envelope if wait_for_result is true). Use get_brainstorm to retrieve. Generates and persists a retrievable artifact.",
|
|
32678
33154
|
inputSchema: {
|
|
32679
|
-
task_description:
|
|
33155
|
+
task_description: z16.string().describe(
|
|
32680
33156
|
"Free-form description of the task to brainstorm about. Sent verbatim \u2014 this tool does NOT read task_description from a file."
|
|
32681
33157
|
),
|
|
32682
33158
|
repo_name: commonFields.repo_name,
|
|
32683
33159
|
ticket_number: commonFields.ticket_number.optional(),
|
|
32684
|
-
providers:
|
|
33160
|
+
providers: z16.array(z16.string()).optional().describe(
|
|
32685
33161
|
"Opinion-provider LLMs. Defaults to ['openai', 'gemini']. A single-provider request runs one opinion provider and returns that provider's markdown directly."
|
|
32686
33162
|
),
|
|
32687
|
-
concerns:
|
|
33163
|
+
concerns: z16.string().optional().describe(
|
|
32688
33164
|
"Optional caller-supplied concerns to surface to the brainstorm agents."
|
|
32689
33165
|
),
|
|
32690
33166
|
wait_for_result: commonFields.wait_for_result,
|
|
32691
33167
|
save_locally: commonFields.save_locally,
|
|
32692
|
-
prior_brainstorm_id:
|
|
33168
|
+
prior_brainstorm_id: z16.string().optional().describe(
|
|
32693
33169
|
"Optional brainstorm_id from an earlier brainstorm to refine. When provided, the prior brainstorm's completed opinion-provider markdowns are concatenated and supplied as prior context."
|
|
32694
33170
|
),
|
|
32695
|
-
mode:
|
|
33171
|
+
mode: z16.enum(["technical", "design", "discovery"]).optional().describe(
|
|
32696
33172
|
"Preferred brainstorm-mode selector for new callers. 'technical' (default) is the implementation/architecture brainstorm; 'design' is web-page/UI visual-direction ideation; 'discovery' generates grouped technical and business/stakeholder discovery questions for early/vague tasks. Takes precedence over the legacy boolean design field."
|
|
32697
33173
|
),
|
|
32698
|
-
design:
|
|
33174
|
+
design: z16.boolean().optional().describe(
|
|
32699
33175
|
'Legacy compatibility flag: set to true for web-page/UI design ideation focused on visual appeal and conversion. New callers should use mode: "design" instead. Omit this field when not requesting design mode; absent is treated as false.'
|
|
32700
33176
|
),
|
|
32701
|
-
lenses:
|
|
33177
|
+
lenses: z16.array(z16.string()).optional().describe(
|
|
32702
33178
|
"Optional reasoning lenses (e.g. 'simplicity', 'robustness', 'blast-radius') assigned one per provider, applies to technical/design modes only. Omitting this defaults to an automatic Simplicity + Extensibility pair."
|
|
32703
33179
|
),
|
|
32704
|
-
debate:
|
|
33180
|
+
debate: z16.boolean().optional().describe(
|
|
32705
33181
|
"Opt-in to trigger a second cross-examination debate round between providers (default off). When true, after round 1 completes each provider critiques the OTHER provider(s)' round-1 output, and the critique is appended to that provider's markdown under a '## Cross-examination' section."
|
|
32706
33182
|
)
|
|
32707
33183
|
}
|
|
@@ -32797,7 +33273,7 @@ registerTool(
|
|
|
32797
33273
|
},
|
|
32798
33274
|
description: "Use to retrieve the result envelope for a previously submitted brainstorm by brainstorm_id. Returns opinion-provider rows only (including error_kind for each row). Does NOT start a new brainstorm \u2014 use request_brainstorm first if none exists. Returns not-found when still processing.",
|
|
32799
33275
|
inputSchema: {
|
|
32800
|
-
brainstorm_id:
|
|
33276
|
+
brainstorm_id: z16.string().describe(
|
|
32801
33277
|
"The brainstorm_id (UUID) returned by request_brainstorm."
|
|
32802
33278
|
),
|
|
32803
33279
|
repo_name: commonFields.repo_name,
|
|
@@ -32840,10 +33316,10 @@ registerTool(
|
|
|
32840
33316
|
},
|
|
32841
33317
|
description: "Create a pull request on the configured VCS provider (GitHub or Bitbucket). Returns a structured response with {available, reason, action, detail}. If a PR already exists for the head branch, returns it with created=false. Capability issues (missing VCS config, API errors) return available=false, not errors. The repo_name is automatically injected from the configured environment.",
|
|
32842
33318
|
inputSchema: {
|
|
32843
|
-
head_branch:
|
|
32844
|
-
base_branch:
|
|
32845
|
-
title:
|
|
32846
|
-
body:
|
|
33319
|
+
head_branch: z16.string().describe("The source branch name for the pull request"),
|
|
33320
|
+
base_branch: z16.string().describe("The target/destination branch name for the pull request"),
|
|
33321
|
+
title: z16.string().describe("The title of the pull request"),
|
|
33322
|
+
body: z16.string().optional().describe("The description/body of the pull request")
|
|
32847
33323
|
}
|
|
32848
33324
|
},
|
|
32849
33325
|
async ({ head_branch, base_branch, title, body }) => {
|
|
@@ -32877,8 +33353,8 @@ var resolveCiChecksTool = registerTool(
|
|
|
32877
33353
|
},
|
|
32878
33354
|
description: "Discover and classify CI checks for the configured repository. Queries GitHub Check Runs + Commit Statuses APIs (or Bitbucket Build Statuses), then uses Branch Protection API or LLM to determine which checks are required for merging. Results are cached per-project \u2014 subsequent calls return cached config unless force_rerun is true. Returns {available, reason, action, detail} envelope. The repo_name is automatically injected from the configured environment.",
|
|
32879
33355
|
inputSchema: {
|
|
32880
|
-
commit_ref:
|
|
32881
|
-
force_rerun:
|
|
33356
|
+
commit_ref: z16.string().describe("Git commit SHA to discover checks for"),
|
|
33357
|
+
force_rerun: z16.boolean().optional().describe("Set to true to bypass cache and re-resolve CI checks")
|
|
32882
33358
|
}
|
|
32883
33359
|
},
|
|
32884
33360
|
async ({ commit_ref, force_rerun }) => {
|
|
@@ -32917,7 +33393,7 @@ var pollCiChecksTool = registerTool(
|
|
|
32917
33393
|
},
|
|
32918
33394
|
description: "Poll the current status of CI checks for a specific commit. Requires that resolve_ci_checks has been called first to populate the check configuration. Returns per-check status, all_complete, all_passed, and unknown_checks fields. For failed checks with detail_level 'full', includes annotations and/or log tails. Returns {available, reason, action, detail} envelope. The repo_name is automatically injected from the configured environment.",
|
|
32919
33395
|
inputSchema: {
|
|
32920
|
-
commit_ref:
|
|
33396
|
+
commit_ref: z16.string().describe("Git commit SHA to poll CI checks for")
|
|
32921
33397
|
}
|
|
32922
33398
|
},
|
|
32923
33399
|
async ({ commit_ref }) => {
|
|
@@ -33014,17 +33490,17 @@ registerTool(
|
|
|
33014
33490
|
},
|
|
33015
33491
|
description: "Retrieve a fully resolved pipeline recipe by name. Substitutes variables, resolves instruction file references to inline content, and returns an ordered array of executable steps. Each step is either an mcp_call (with tool name and params) or an agent_task (with instruction text). Use list_pipelines to discover available pipeline names first. Note: the 'docs_dir' variable is automatically set from BAPI_DOCS_DIR \u2014 callers should omit it.",
|
|
33016
33492
|
inputSchema: {
|
|
33017
|
-
pipeline:
|
|
33018
|
-
variables:
|
|
33493
|
+
pipeline: z16.string().describe("Pipeline name (e.g. 'review-ticket', 'implement-ticket')"),
|
|
33494
|
+
variables: z16.record(z16.string(), z16.string()).optional().describe(
|
|
33019
33495
|
"Key-value pairs for variable substitution (e.g. { ticket_key: 'BAPI-123' })"
|
|
33020
33496
|
),
|
|
33021
|
-
skip_steps:
|
|
33497
|
+
skip_steps: z16.array(z16.string()).optional().describe(
|
|
33022
33498
|
"Step tool names or descriptions to omit from the recipe"
|
|
33023
33499
|
),
|
|
33024
|
-
auto_approve:
|
|
33500
|
+
auto_approve: z16.boolean().optional().describe(
|
|
33025
33501
|
"When true, auto-approve all approval-gated steps (skips the commit/push pause for implement-ticket; skips the HTML decision page for review-ticket, picking each item's recommended option). Pass via this top-level parameter."
|
|
33026
33502
|
),
|
|
33027
|
-
rounds:
|
|
33503
|
+
rounds: z16.union([z16.literal(1), z16.literal(2)]).optional().describe(
|
|
33028
33504
|
"Round count (1|2); wins over adaptive routing. Omit for backend auto-routing."
|
|
33029
33505
|
)
|
|
33030
33506
|
}
|
|
@@ -33141,13 +33617,13 @@ registerTool(
|
|
|
33141
33617
|
},
|
|
33142
33618
|
description: 'Materialize a pinned origin/<base_branch> tree via git archive into a unique temp dir, without mutating the working tree, index, stash, or branches. Returns { base_sha, fresh_base_root }. base_branch precedence: param > config > "main". no_refresh_base: "true" skips the fetch, returning the local project root with base_sha "local-stale".',
|
|
33143
33619
|
inputSchema: {
|
|
33144
|
-
base_branch:
|
|
33620
|
+
base_branch: z16.string().optional().describe(
|
|
33145
33621
|
`Branch to fetch and materialize from origin. Defaults to the 'base_branch' config field, else "main".`
|
|
33146
33622
|
),
|
|
33147
|
-
base_sha:
|
|
33623
|
+
base_sha: z16.string().optional().describe(
|
|
33148
33624
|
"Pre-resolved commit SHA to materialize (skips the fetch+resolve step, e.g. a batch-pinned SHA from review-tickets)."
|
|
33149
33625
|
),
|
|
33150
|
-
no_refresh_base:
|
|
33626
|
+
no_refresh_base: z16.string().optional().describe(
|
|
33151
33627
|
'Pass "true" to skip fetch/materialization and fall back to the local project root as-is.'
|
|
33152
33628
|
)
|
|
33153
33629
|
}
|
|
@@ -33305,7 +33781,7 @@ registerTool(
|
|
|
33305
33781
|
},
|
|
33306
33782
|
description: "Remove a review workspace temp directory previously returned by materialize_fresh_base. Strictly namespace-scoped: refuses to delete any path outside the OS temp dir's 'bridge-review-' prefix.",
|
|
33307
33783
|
inputSchema: {
|
|
33308
|
-
fresh_base_root:
|
|
33784
|
+
fresh_base_root: z16.string().describe(
|
|
33309
33785
|
"The fresh_base_root path returned by a prior materialize_fresh_base call."
|
|
33310
33786
|
)
|
|
33311
33787
|
}
|
|
@@ -33376,14 +33852,14 @@ if (ACTIVE_GROUPS.has("pipeline-authoring")) {
|
|
|
33376
33852
|
},
|
|
33377
33853
|
description: "Execute a Bridge API pipeline by name. The orchestrator runs steps sequentially, dispatching mcp_call steps in-process and pausing on agent_task steps with a needs_agent_task envelope. Returns a unified envelope keyed on `status`: `completed` (terminal success with `results`), `needs_agent_task` (pause \u2014 read `instruction`, perform the task, then call `resume_pipeline` with the resulting string as `agent_result`), or `failed` (terminal error \u2014 check `error_code`: VALIDATION | NOT_FOUND | EXPIRED | REPO_MISMATCH | TOOL_ERROR). Paused runs auto-expire after an idle TTL (default 24 hours; override with `ttl_seconds`). The TTL is reset on every state transition.\n\nCall list_pipelines for the current resolved catalog of available pipeline names (bundled plus any custom user pipelines).",
|
|
33378
33854
|
inputSchema: {
|
|
33379
|
-
pipeline:
|
|
33380
|
-
variables:
|
|
33855
|
+
pipeline: z16.string().describe("Pipeline name (e.g. 'review-ticket', 'implement-ticket')"),
|
|
33856
|
+
variables: z16.record(z16.string(), z16.string()).optional().describe(
|
|
33381
33857
|
"Key-value pairs for variable substitution (e.g. { ticket_key: 'BAPI-123' }). Do NOT pass `auto_approve` here \u2014 use the top-level parameter."
|
|
33382
33858
|
),
|
|
33383
|
-
auto_approve:
|
|
33859
|
+
auto_approve: z16.union([z16.boolean(), z16.literal("true"), z16.literal("false")]).optional().describe(
|
|
33384
33860
|
"When true, approval-gated mcp_call steps execute directly. When false or omitted, the orchestrator synthesises a needs_agent_task pause so the agent can confirm with the user before resuming. Accepts boolean or 'true'/'false' strings for MCP clients that serialize booleans as strings."
|
|
33385
33861
|
),
|
|
33386
|
-
ttl_seconds:
|
|
33862
|
+
ttl_seconds: z16.number().int().positive().optional().describe(
|
|
33387
33863
|
"Override the default 24-hour idle TTL for this run. Must be a positive integer."
|
|
33388
33864
|
)
|
|
33389
33865
|
}
|
|
@@ -33411,8 +33887,8 @@ if (ACTIVE_GROUPS.has("pipeline-authoring")) {
|
|
|
33411
33887
|
},
|
|
33412
33888
|
description: "Resume a paused pipeline run with the result of the agent_task. Provide the `pipeline_run_id` returned by the prior needs_agent_task envelope, and the string the instruction's `## Return` section asked you to produce as `agent_result`. `agent_result` is always a string \u2014 do not wrap it in JSON unless the instruction explicitly asked you to serialize structured output. Returns the same unified envelope shape as `run_pipeline`.",
|
|
33413
33889
|
inputSchema: {
|
|
33414
|
-
pipeline_run_id:
|
|
33415
|
-
agent_result:
|
|
33890
|
+
pipeline_run_id: z16.string().describe("The pipeline_run_id returned by a prior needs_agent_task envelope"),
|
|
33891
|
+
agent_result: z16.string().describe(
|
|
33416
33892
|
"The string the paused instruction's ## Return section asked you to produce"
|
|
33417
33893
|
)
|
|
33418
33894
|
}
|
|
@@ -33440,7 +33916,7 @@ if (ACTIVE_GROUPS.has("pipeline-authoring")) {
|
|
|
33440
33916
|
},
|
|
33441
33917
|
description: "List recent pipeline runs for the configured repository, newest first. Returns metadata only \u2014 `resolved_recipe`, resolved params, instruction text, results, and agent outputs are intentionally excluded. Use this to recover a `pipeline_run_id` when an earlier needs_agent_task envelope is no longer in scope (e.g. after compaction or a client restart). Optionally filter by `status`: running | paused | completed | failed | expired.",
|
|
33442
33918
|
inputSchema: {
|
|
33443
|
-
status:
|
|
33919
|
+
status: z16.enum(["running", "paused", "completed", "failed", "expired"]).optional().describe("Optional status filter")
|
|
33444
33920
|
}
|
|
33445
33921
|
},
|
|
33446
33922
|
async (input) => {
|
|
@@ -33466,7 +33942,7 @@ if (ACTIVE_GROUPS.has("pipeline-authoring")) {
|
|
|
33466
33942
|
},
|
|
33467
33943
|
description: "Delete a pipeline run row (any status). Use this to discard orphaned `running` rows from a previous session that can't be resumed (resume_pipeline only accepts `paused`), to clean up after a failed run, or to remove a no-longer-needed paused session. Returns `{ status: 'completed', deleted: true, pipeline_run_id }` on success, or a `failed` envelope with error_code in (VALIDATION | NOT_FOUND | REPO_MISMATCH | TOOL_ERROR). Repo-scoped: the row's stored repo_name must match the caller's repo.",
|
|
33468
33944
|
inputSchema: {
|
|
33469
|
-
pipeline_run_id:
|
|
33945
|
+
pipeline_run_id: z16.string().describe("UUID of the pipeline run to delete.")
|
|
33470
33946
|
}
|
|
33471
33947
|
},
|
|
33472
33948
|
async (input) => {
|
|
@@ -33493,14 +33969,14 @@ registerTool(
|
|
|
33493
33969
|
},
|
|
33494
33970
|
description: "Run the full-automation chain for an idea: create ticket(s) (idea-to-ticket), review each created ticket (review-ticket fan-out), then emit the exact `/start-tickets ...` command for you to invoke in this same session. Returns the chain envelope keyed on `status`: `needs_agent_task` (perform the `next_action.instruction`, then call `resume_full_automation` with the result as `agent_result`), `completed`, or `failed` (check `error_code`: VALIDATION | NOT_FOUND | EXPIRED | REPO_MISMATCH | TOOL_ERROR). Provide the idea via `idea` or `idea_file` (mutually exclusive).",
|
|
33495
33971
|
inputSchema: {
|
|
33496
|
-
idea:
|
|
33497
|
-
idea_file:
|
|
33498
|
-
auto_approve:
|
|
33499
|
-
scheduled_at:
|
|
33500
|
-
max_children:
|
|
33501
|
-
allow_duplicate:
|
|
33502
|
-
agent:
|
|
33503
|
-
ttl_seconds:
|
|
33972
|
+
idea: z16.string().optional(),
|
|
33973
|
+
idea_file: z16.string().optional(),
|
|
33974
|
+
auto_approve: z16.union([z16.boolean(), z16.literal("true"), z16.literal("false")]).optional(),
|
|
33975
|
+
scheduled_at: z16.string().optional(),
|
|
33976
|
+
max_children: z16.number().int().positive().optional(),
|
|
33977
|
+
allow_duplicate: z16.boolean().optional(),
|
|
33978
|
+
agent: z16.enum(["claude"]).optional(),
|
|
33979
|
+
ttl_seconds: z16.number().int().positive().optional()
|
|
33504
33980
|
}
|
|
33505
33981
|
},
|
|
33506
33982
|
async (input) => {
|
|
@@ -33543,8 +34019,8 @@ registerTool(
|
|
|
33543
34019
|
},
|
|
33544
34020
|
description: "Resume a paused full-automation chain run. Provide the `chain_run_id` returned by the prior needs_agent_task envelope and the string the instruction asked you to produce as `agent_result`. Returns the same chain envelope shape as `run_full_automation`.",
|
|
33545
34021
|
inputSchema: {
|
|
33546
|
-
chain_run_id:
|
|
33547
|
-
agent_result:
|
|
34022
|
+
chain_run_id: z16.string(),
|
|
34023
|
+
agent_result: z16.string()
|
|
33548
34024
|
}
|
|
33549
34025
|
},
|
|
33550
34026
|
async (input) => {
|
|
@@ -33647,14 +34123,14 @@ registerTool(
|
|
|
33647
34123
|
inputSchema: DecisionPageLeanInputShape
|
|
33648
34124
|
},
|
|
33649
34125
|
async (input) => {
|
|
33650
|
-
const
|
|
34126
|
+
const validationError2 = (message) => ({
|
|
33651
34127
|
content: [{
|
|
33652
34128
|
type: "text",
|
|
33653
34129
|
text: JSON.stringify({ error: "VALIDATION_ERROR", status: 400, message })
|
|
33654
34130
|
}]
|
|
33655
34131
|
});
|
|
33656
34132
|
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(input.ticket_key)) {
|
|
33657
|
-
return
|
|
34133
|
+
return validationError2(`Invalid ticket_key "${input.ticket_key}": must start with a letter and contain only letters, digits, hyphens, or underscores.`);
|
|
33658
34134
|
}
|
|
33659
34135
|
const rawPayload = {
|
|
33660
34136
|
...input.content || {},
|
|
@@ -33668,8 +34144,8 @@ registerTool(
|
|
|
33668
34144
|
try {
|
|
33669
34145
|
parsed = DecisionPageInputSchema.parse(rawPayload);
|
|
33670
34146
|
} catch (err) {
|
|
33671
|
-
if (err instanceof
|
|
33672
|
-
return
|
|
34147
|
+
if (err instanceof z16.ZodError) {
|
|
34148
|
+
return validationError2(formatDecisionPageValidationError(err));
|
|
33673
34149
|
}
|
|
33674
34150
|
throw err;
|
|
33675
34151
|
}
|
|
@@ -33689,26 +34165,33 @@ registerTool(
|
|
|
33689
34165
|
const seenIds = /* @__PURE__ */ new Set();
|
|
33690
34166
|
for (const item of parsed.actionable_items) {
|
|
33691
34167
|
if (seenIds.has(item.id)) {
|
|
33692
|
-
return
|
|
34168
|
+
return validationError2(`Duplicate actionable_items id: "${item.id}"`);
|
|
33693
34169
|
}
|
|
33694
34170
|
seenIds.add(item.id);
|
|
33695
34171
|
const noneLabel = item.options.find((label) => label.toLowerCase() === "none of these");
|
|
33696
34172
|
if (noneLabel) {
|
|
33697
|
-
return
|
|
34173
|
+
return validationError2(`Item "${item.id}": option label "${noneLabel}" is reserved and auto-appended by the tool.`);
|
|
33698
34174
|
}
|
|
33699
34175
|
}
|
|
33700
34176
|
const seenCiIds = /* @__PURE__ */ new Set();
|
|
33701
34177
|
for (const ci of parsed.clear_improvements) {
|
|
33702
34178
|
if (seenCiIds.has(ci.id)) {
|
|
33703
|
-
return
|
|
34179
|
+
return validationError2(`Duplicate clear_improvements id: "${ci.id}"`);
|
|
33704
34180
|
}
|
|
33705
34181
|
seenCiIds.add(ci.id);
|
|
33706
34182
|
}
|
|
34183
|
+
const seenNfrCategories = /* @__PURE__ */ new Set();
|
|
34184
|
+
for (const nfr of parsed.system_goals?.nfrs ?? []) {
|
|
34185
|
+
if (seenNfrCategories.has(nfr.category)) {
|
|
34186
|
+
return validationError2(`Duplicate system_goals.nfrs category: "${nfr.category}"`);
|
|
34187
|
+
}
|
|
34188
|
+
seenNfrCategories.add(nfr.category);
|
|
34189
|
+
}
|
|
33707
34190
|
const outputSubdir = parsed.output_subdir ?? "review";
|
|
33708
34191
|
const outputFilename = parsed.output_filename ?? `${parsed.ticket_key}-decisions.html`;
|
|
33709
34192
|
const outputTarget = await resolveDecisionPageOutputTarget(outputSubdir, outputFilename);
|
|
33710
34193
|
if (!outputTarget.ok) {
|
|
33711
|
-
return
|
|
34194
|
+
return validationError2(outputTarget.message);
|
|
33712
34195
|
}
|
|
33713
34196
|
const projectRootForAssets = await getProjectRoot();
|
|
33714
34197
|
const pkgRoot = path33.resolve(path33.dirname(fileURLToPath3(import.meta.url)), "../");
|