@bridge_gpt/mcp-server 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,48 @@ This runs `npm i @bridge_gpt/mcp-server@latest`, prints a before/after version s
33
33
 
34
34
  The 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.
35
35
 
36
+ ### CLI subcommand: `start-tickets`
37
+
38
+ Beyond `--init` / `--upgrade`, the package ships a `start-tickets` subcommand that spawns one Worktrunk worktree + selected-agent session per Jira ticket. The agent defaults to **Claude Code** (`claude`) and is configurable via `--agent`. It is part of the **existing `bridge-api-mcp-server` bin** — not a separate binary — so it travels with the package to every consumer. It also backs the `/start-tickets` slash command.
39
+
40
+ ```
41
+ npx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]
42
+ ```
43
+
44
+ | Flag | Default | Meaning |
45
+ |---|---|---|
46
+ | `--agent claude\|cursor-agent` | `claude` | Agent command to launch in each worktree |
47
+ | `--terminal terminal\|iterm` | auto-detect via `$TERM_PROGRAM` | Override the macOS terminal app (honored on macOS only) |
48
+ | `--dry-run` | off | Print intended actions; create no worktrees, open no tabs (any OS) |
49
+ | `--branch KEY=BRANCH` | `feature/<KEY>` | Use a custom branch for that ticket (repeatable) |
50
+ | `--no-refresh-main` | off | Skip `git fetch origin main` + fast-forward of local `main` |
51
+ | `--max-parallel N` | `3` | Max worktrees created concurrently |
52
+ | `-h`, `--help` | — | Show usage |
53
+
54
+ Each `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>'` — `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:
55
+
56
+ ```
57
+ npx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248
58
+ ```
59
+
60
+ **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.
61
+
62
+ - **macOS** — opens a Terminal.app or iTerm tab via `osascript`.
63
+ - **Windows** — 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.
64
+ - **Linux** — 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.
65
+
66
+ Per-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` — Worktrunk's `pre-start` hook runs `uv`, but live preflight does not check it — 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).
67
+
68
+ ### CLI subcommand: `doctor`
69
+
70
+ The package also ships a strictly **read-only** `doctor` subcommand that diagnoses the `start-tickets` prerequisites for the current OS without changing anything:
71
+
72
+ ```
73
+ npx -y @bridge_gpt/mcp-server doctor [--agent <name>]
74
+ ```
75
+
76
+ It 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.
77
+
36
78
  ### 2. Generate an API Key
37
79
 
38
80
  1. Log in to [Bridge API](https://bridgegpt-api.com) and navigate to your project's **Security** page
@@ -195,6 +237,7 @@ Commands are slash commands you invoke from your AI assistant's chat. Most orche
195
237
  | `/review-ticket PROJ-123` | Two-round ticket quality review with critiques from multiple LLM providers |
196
238
  | `/run-tests` | Run the full test suite, triage failures, and classify issues as test bugs vs. implementation bugs |
197
239
  | `/scan-tickets` | Sync recently-updated Jira tickets and backfill workflow timestamps |
240
+ | `/start-tickets KEY [KEY ...]` | Spawn one Worktrunk worktree + selected-agent session per ticket via the packaged `start-tickets` CLI (Claude Code by default, `--agent cursor-agent` for Cursor; macOS Terminal/iTerm, Windows Terminal/PowerShell, or Linux tmux) |
198
241
  | `/teach-bridge` | Update a Bridge API configuration field via natural-language instructions |
199
242
  | `/update-ticket PROJ-123` | Fetch clarifying questions and critique, then rewrite the ticket description and push to Jira |
200
243
  | `/write-ticket` | Draft a new Jira ticket from a prompt and upload it |
@@ -350,3 +393,5 @@ If a custom pipeline has the same key as a built-in pipeline, the custom version
350
393
  | `BAPI_PROJECT_ROOT` | No | _(auto-set by --init)_ | Absolute path to project root. Anchors `BAPI_DOCS_DIR` and `BAPI_PIPELINES_DIR` resolution |
351
394
  | `BAPI_DOCS_DIR` | No | `docs/tmp` | Local directory for saving plans, critiques, and research reports |
352
395
  | `BAPI_PIPELINES_DIR` | No | `.bridge/pipelines` | Directory for user-defined custom pipeline JSON files |
396
+ | `BAPI_WORKTRUNK_BIN` | No | `wt` (`git-wt` on Windows) | Override the Worktrunk executable name/path used by `start-tickets` for nonstandard installs |
397
+ | `BAPI_TMUX_SESSION` | No | `bridge-start-tickets` | Override the tmux session-name prefix used by `start-tickets` on Linux |
@@ -0,0 +1,68 @@
1
+ /**
2
+ * agent-registry — the single source of truth for the agents the packaged
3
+ * `start-tickets` CLI (and the read-only `doctor` subcommand) know how to launch.
4
+ *
5
+ * BAPI-305 replaced the former module-level `AGENT_COMMAND = "claude"` swap point
6
+ * in `start-tickets.ts` with this registry so the agent name -> command mapping
7
+ * lives in exactly one place. New agents are added by extending `AGENT_REGISTRY`;
8
+ * no other production module hard-codes an agent command.
9
+ *
10
+ * The module is intentionally dependency-free (it imports nothing from
11
+ * `start-tickets.ts` / `start-tickets-prereqs.ts` / `doctor.ts`) so every other
12
+ * module can import it without risking a circular dependency.
13
+ */
14
+ /**
15
+ * The registry: the ONLY place mapping an agent name to its command/spec. Seeded
16
+ * with exactly `claude` (default) and `cursor-agent`. `as const satisfies` keeps
17
+ * the literal keys (so `AgentName` is a precise union) while type-checking each
18
+ * value against `AgentSpec`.
19
+ */
20
+ export const AGENT_REGISTRY = {
21
+ claude: {
22
+ name: "claude",
23
+ command: "claude",
24
+ promptArgStyle: "positional",
25
+ installHint: {
26
+ darwin: "npm install -g @anthropic-ai/claude-code",
27
+ linux: "npm install -g @anthropic-ai/claude-code",
28
+ win32: "npm install -g @anthropic-ai/claude-code",
29
+ },
30
+ authNote: "Claude Code authenticates interactively on first run — follow its login/auth prompt if asked.",
31
+ },
32
+ "cursor-agent": {
33
+ name: "cursor-agent",
34
+ command: "cursor-agent",
35
+ promptArgStyle: "positional",
36
+ installHint: {
37
+ darwin: "curl https://cursor.com/install -fsSL | bash",
38
+ linux: "curl https://cursor.com/install -fsSL | bash",
39
+ win32: "irm 'https://cursor.com/install?win32=true' | iex",
40
+ },
41
+ authNote: "Run cursor-agent login to authenticate; doctor checks PATH presence only, not login state.",
42
+ },
43
+ };
44
+ /** The default agent used when `--agent` is omitted. */
45
+ export const DEFAULT_AGENT_NAME = "claude";
46
+ /** Registry keys in deterministic (insertion) order. */
47
+ export function listAgentNames() {
48
+ return Object.keys(AGENT_REGISTRY);
49
+ }
50
+ /** Type guard: true only for a registered agent name. */
51
+ export function isAgentName(value) {
52
+ return listAgentNames().includes(value);
53
+ }
54
+ /**
55
+ * Resolve an agent name to its spec. An omitted name defaults to
56
+ * `DEFAULT_AGENT_NAME`; any unknown name (including `""` and wrong-case) returns
57
+ * `null` so callers can surface a validation error listing the valid names.
58
+ */
59
+ export function resolveAgentSpec(name) {
60
+ const resolved = name ?? DEFAULT_AGENT_NAME;
61
+ if (!isAgentName(resolved))
62
+ return null;
63
+ return AGENT_REGISTRY[resolved];
64
+ }
65
+ /** Comma-delimited valid agent names for CLI validation errors (`claude, cursor-agent`). */
66
+ export function formatValidAgentNames() {
67
+ return listAgentNames().join(", ");
68
+ }
@@ -17,6 +17,6 @@ export const COMMANDS = {
17
17
  "review-ticket.md": "# 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-approve` 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-approve` 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-approve]\n Usage: /review-ticket <ticket_key> [--auto-approve]\n ```\n\n2. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `\"review-ticket\"`\n - `variables`: `{ \"ticket_key\": \"<ticket_key>\" }`\n - `auto_approve`: `true` — only when `--auto-approve` 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",
18
18
  "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 — Argument Parsing and Setup\n\n1. **Parse `$ARGUMENTS`** for optional flags. Supported flags:\n - `--skip-e2e` — skip the E2E test stage even if an E2E stack is configured (e.g., when no local server is running)\n - `--unit-only` — 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 — proceed to Stage 1.\n\n## Stage 1 — 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 `get_config_field` MCP tool with `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 `get_config_field` MCP tool with `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 `get_config_field` MCP tool with `field_name` set to `e2e_testing_stack`. Store as `e2e_stack`.\n\n5. **Read E2E instructions**: Call the `get_config_field` MCP tool with `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/ — 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 — the whole point of this command is that test setup lives in config.\n\n## Stage 2 — Unit / Standard Tests\n\nIf `run_unit` is `false`, skip this stage and record: `Unit tests: SKIPPED — 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 — 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** — 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 — 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 — E2E Tests\n\nIf `run_e2e` is `false`, skip this stage and record: `E2E tests: SKIPPED — --skip-e2e or --unit-only flag was set.`\n\nIf `e2e_configured` is `false`, skip and record:\n```\nE2E tests: SKIPPED — 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 — 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 — 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 — 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 — escalate to implementation-code issue instead and revert your change.\n\n### IMPLEMENTATION-CODE ISSUE (or UNCERTAIN) — 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 — 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 — (reason)\n- E2E tests: RUN | SKIPPED — (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 — 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",
19
19
  "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 — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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",
20
- "start-tickets.md": "# Start Tickets: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes one or more Jira ticket keys (e.g., `BAPI-248 BAPI-250`), creates a Worktrunk worktree for each, and opens a new Terminal/iTerm tab running Claude Code in each worktree with a starter prompt prefilled. It replaces Parts 2–5 of `docs/claude/parallel-worktrees.md` with a single command.\n\nStage 0 and Stage 1 are critical (stop on failure). Stage 2 is non-critical (per-ticket failures fall back to the default branch and continue). Stage 3 is critical (propagate the wrapper script's exit code).\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline that spawns N parallel Worktrunk worktrees and Claude Code sessions. Execute all stages in sequence directly in the main thread.\n\n## Stage 0 — Argument Parsing and Connectivity Check\n\n1. **Parse `$ARGUMENTS`**: Extract every whitespace-separated token matching `[A-Z]+-\\d+` from `$ARGUMENTS`. Each match is a ticket key (e.g., `BAPI-248`). If zero matches, 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 <KEY> [KEY ...] (e.g., /start-tickets BAPI-248 BAPI-250)\n ```\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** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Acknowledge Environment Pre-flight\n\nThe wrapper script `scripts/start-tickets.sh` runs its own pre-flight checks (`wt`, `osascript`, `git` on PATH; inside a git checkout) and then fetches `origin` and fast-forwards local `main` so the new worktrees are based on up-to-date `main` (skippable via `--no-refresh-main`). This stage simply notes that the script will fail fast if any prerequisite is missing or if local `main` has diverged from `origin/main` — you do not need to verify anything separately here. Proceed to Stage 2.\n\nThis stage is **critical** in the sense that the script 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 — Enrich Branch Names (best-effort)\n\nFor each parsed ticket key, attempt to build a more descriptive branch name by fetching the ticket summary:\n\n1. For each key, 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), emit a single-line warning like `Warning: could not enrich BAPI-248, falling back to feature/BAPI-248` and use the default `feature/<KEY>` for that key only. Do NOT stop the pipeline.\n5. Build a list of `--branch <KEY>=<BRANCH>` arguments — one entry per key whose enrichment succeeded.\n\nThis stage is **non-critical** — warnings are acceptable, the pipeline continues with the fallback default for any key that fails.\n\n## Stage 3 — Spawn Worktrees and Tabs\n\nUse the **Bash tool** to invoke the wrapper script. Build the command line as:\n\n```\nscripts/start-tickets.sh <branch-overrides> <ticket-keys>\n```\n\nWhere:\n- `<branch-overrides>` is the list of `--branch KEY=BRANCH` flags built in Stage 2 (omit any key whose enrichment failed).\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:\n\n```\nscripts/start-tickets.sh \\\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\nPass through the script's stdout and stderr to the user verbatim. If the script exits non-zero, treat that as a critical failure: report the exit code and the script's error output, and stop.\n\nThis stage is **critical** — propagate any non-zero exit from the wrapper script.\n\n## Stage 4 — Final Report\n\nOnce `scripts/start-tickets.sh` exits 0, parse its `Summary` section (one line per ticket: `KEY branch=BRANCH status=STATUS`) 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\nEnd the report with these two lines:\n\n```\nEach new tab is running `wt switch -c -y -x claude <branch> -- '<starter prompt>'`,\nwhich creates the worktree, runs the project pre-start hook, and launches Claude\nwith the starter prompt as its first message. Switch to each tab to monitor.\n```\n\nIf Stage 2 emitted any warnings, list them under a `Warnings:` heading at the bottom of the report. If there were no warnings, omit that section.\n",
20
+ "start-tickets.md": "# 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** — Claude Code (`claude`) by default, or Cursor Agent (`cursor-agent`) via `--agent` — 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–5 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 — including projects that installed the package via `--init`.\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 — 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`, `--no-refresh-main`, and `--max-parallel N` that the user supplied. These are forwarded verbatim to the CLI in Stage 3.\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 — the user selects it explicitly (default `claude`).\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`, 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** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Acknowledge CLI Pre-flight\n\nThe packaged CLI runs its own per-platform pre-flight checks and then fetches `origin` and fast-forwards local `main` so the new worktrees are based on up-to-date `main` (skippable via `--no-refresh-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` — 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 — the pre-flight set plus `uv` plus the selected agent's command — 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\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 — Enrich Branch Names (best-effort)\n\nBranch enrichment happens here, in the command, **before** invoking the CLI — 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 — one entry per key whose enrichment succeeded — 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** — 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 — 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> <branch-overrides> <ticket-keys>\n```\n\nWhere:\n- `<pass-through-flags>` are the supported flags collected in Stage 0 (`--agent`, `--terminal`, `--dry-run`, `--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`.\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\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** — propagate any non-zero exit from the packaged CLI.\n\n## Stage 4 — 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`. End the report with the worktree-first explanation, rendered for the tracked `selected_agent`. 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 '/implement-ticket <KEY>'` inside its already-created worktree, which launches\nClaude Code with the starter prompt as its first message. Switch to each tab — or on\nLinux run `tmux attach -t <session>` — to monitor.\n```\n\nWhen `selected_agent` is `cursor-agent`, render the same explanation but with the Cursor handoff — 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 '/implement-ticket <KEY>'` inside its already-created worktree, which\nlaunches Cursor Agent with the starter prompt as its first message. Switch to each\ntab — or on Linux run `tmux attach -t <session>` — to monitor.\n```\n\nThe `/implement-ticket <KEY>` prompt is identical for both agents; only the launched command differs.\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",
21
21
  "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 — 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 — 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 — Classify\n\n1. **List available fields**: Call the `list_config_fields` MCP tool (no 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 — Merge\n\n1. **Read current value**: Call the `get_config_field` MCP tool with `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 — 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 `update_config_field` MCP tool with:\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"
22
22
  };
@@ -0,0 +1,172 @@
1
+ /**
2
+ * doctor — a strictly read-only, detect-and-instruct diagnostics subcommand for
3
+ * the packaged `start-tickets` automation (BAPI-305).
4
+ *
5
+ * npx -y @bridge_gpt/mcp-server doctor [--agent <name>]
6
+ *
7
+ * It probes every prerequisite `start-tickets` needs on the current OS — the live
8
+ * preflight set PLUS `uv` (a real gap: Worktrunk's pre-start hook runs `uv`, but
9
+ * live preflight does not check it) PLUS the selected agent's command — and prints
10
+ * found/missing plus the exact per-OS manual install command for each missing one.
11
+ *
12
+ * The audience is security-conscious/corporate, so the doctor is STRICTLY
13
+ * read-only: no `--fix`, no auto-install, no npm `postinstall`, no terminal
14
+ * spawning, no MCP server startup. It only ever runs read-only PATH probes
15
+ * (`which`/`where`, `bash --version`, `git rev-parse`) through the injected deps.
16
+ */
17
+ import { createDefaultStartTicketsDeps } from "./start-tickets.js";
18
+ import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
19
+ import { getDoctorPrereqDescriptors, probePrerequisite, } from "./start-tickets-prereqs.js";
20
+ /** User-facing usage text for the read-only `doctor` subcommand. */
21
+ export function getDoctorUsage() {
22
+ return [
23
+ "Usage:",
24
+ " npx -y @bridge_gpt/mcp-server doctor [--agent <name>]",
25
+ "",
26
+ "Read-only diagnostics for the start-tickets CLI. It only checks your",
27
+ "environment and prints manual install instructions — it does not install",
28
+ "anything, modify your system, or start the MCP server.",
29
+ "",
30
+ "Flags:",
31
+ " --agent claude|cursor-agent Agent to include in the prerequisite check (default: claude)",
32
+ " -h, --help Show this help",
33
+ "",
34
+ "Checks (for the current OS): the start-tickets preflight prerequisites plus",
35
+ "uv plus the selected agent's command.",
36
+ "",
37
+ "Exit code: 0 when all required prerequisites are present, non-zero otherwise.",
38
+ ].join("\n");
39
+ }
40
+ /**
41
+ * Parse argv strictly. Supports `--agent <name>` / `--agent=<name>`, `-h` /
42
+ * `--help`, and nothing else. `--fix` is explicitly rejected so the read-only
43
+ * contract is loud, not silent. Unknown flags and positional args are rejected.
44
+ */
45
+ export function parseDoctorArgs(argv) {
46
+ if (argv.includes("-h") || argv.includes("--help")) {
47
+ return { status: "help", usage: getDoctorUsage() };
48
+ }
49
+ let agentName = DEFAULT_AGENT_NAME;
50
+ for (let i = 0; i < argv.length; i++) {
51
+ const arg = argv[i];
52
+ if (arg === "--fix" || arg.startsWith("--fix=")) {
53
+ return {
54
+ status: "error",
55
+ message: "--fix is unsupported: doctor is strictly read-only and never installs or modifies anything. " +
56
+ "Run the printed install commands manually.",
57
+ };
58
+ }
59
+ if (arg === "--agent" || arg.startsWith("--agent=")) {
60
+ let value;
61
+ if (arg.startsWith("--agent=")) {
62
+ value = arg.slice("--agent=".length);
63
+ }
64
+ else {
65
+ if (i + 1 >= argv.length) {
66
+ return { status: "error", message: "--agent requires a value (an agent name)." };
67
+ }
68
+ i += 1;
69
+ value = argv[i];
70
+ }
71
+ if (!isAgentName(value)) {
72
+ return {
73
+ status: "error",
74
+ message: `Invalid --agent value: '${value}' (allowed agents: ${formatValidAgentNames()}).`,
75
+ };
76
+ }
77
+ agentName = value;
78
+ continue;
79
+ }
80
+ if (arg.startsWith("-")) {
81
+ return { status: "error", message: `Unknown flag: ${arg}` };
82
+ }
83
+ return {
84
+ status: "error",
85
+ message: `Unexpected positional argument: '${arg}'. doctor does not accept positional arguments.`,
86
+ };
87
+ }
88
+ return { status: "ok", options: { agentName } };
89
+ }
90
+ /**
91
+ * Resolve the selected agent, gather the doctor descriptor set (preflight + `uv`
92
+ * + selected agent), and probe EVERY descriptor without failing fast — so a
93
+ * missing earlier prerequisite never hides a later one. Returns a structured
94
+ * unsupported-platform result (no probes) for unsupported OSes; never throws.
95
+ */
96
+ export async function collectDoctorResults(deps, agentName) {
97
+ // agentName is type-guaranteed valid by the parser; resolve is defensive.
98
+ const agent = resolveAgentSpec(agentName) ?? resolveAgentSpec(DEFAULT_AGENT_NAME);
99
+ const descriptorsResult = getDoctorPrereqDescriptors(deps.platform, deps.env, agent);
100
+ if (!descriptorsResult.ok) {
101
+ return { ok: false, unsupported: true, error: descriptorsResult.error };
102
+ }
103
+ const results = [];
104
+ for (const descriptor of descriptorsResult.descriptors) {
105
+ results.push(await probePrerequisite(deps, descriptor));
106
+ }
107
+ return { ok: true, results };
108
+ }
109
+ /**
110
+ * Render the doctor report: platform + selected agent header, one found/missing
111
+ * line per prerequisite, the exact manual install hint for each missing one
112
+ * (framed as manual instructions only), and the selected agent's auth note
113
+ * (especially the `cursor-agent login` reminder). Pure formatting — no probing.
114
+ */
115
+ export function formatDoctorReport(platform, agent, collection) {
116
+ const lines = [
117
+ "start-tickets doctor (read-only diagnostics)",
118
+ `Platform: ${platform}`,
119
+ `Selected agent: ${agent.name} (command: ${agent.command})`,
120
+ "",
121
+ ];
122
+ if (!collection.ok) {
123
+ lines.push(`Platform '${platform}' is unsupported. start-tickets supports darwin, win32, and linux.`);
124
+ return lines.join("\n");
125
+ }
126
+ for (const result of collection.results) {
127
+ const status = result.found ? "FOUND " : "MISSING";
128
+ const detail = result.found && result.detail ? ` (${result.detail})` : "";
129
+ lines.push(`${status} ${result.label}${detail}`);
130
+ if (!result.found) {
131
+ lines.push(` To install manually: ${result.installHint}`);
132
+ }
133
+ if (result.authNote) {
134
+ lines.push(` Note: ${result.authNote}`);
135
+ }
136
+ }
137
+ const anyMissing = collection.results.some((r) => !r.found);
138
+ lines.push("");
139
+ lines.push(anyMissing
140
+ ? "Some prerequisites are missing — install the ones above manually, then re-run doctor."
141
+ : "All required prerequisites are present.");
142
+ return lines.join("\n");
143
+ }
144
+ /**
145
+ * CLI entry for the read-only `doctor` subcommand. Returns a process exit code.
146
+ * Help returns 0; parser errors return 1; otherwise it prints the report and
147
+ * returns 0 when every required prerequisite is present, non-zero when any is
148
+ * missing or the platform is unsupported. Never installs, mutates, spawns tabs,
149
+ * or starts the MCP server.
150
+ */
151
+ export async function runDoctorCli(argv, overrides = {}) {
152
+ const log = overrides.log ?? ((m) => console.log(m));
153
+ const errorLog = overrides.errorLog ?? ((m) => console.error(m));
154
+ const parsed = parseDoctorArgs(argv);
155
+ if (parsed.status === "help") {
156
+ log(parsed.usage);
157
+ return 0;
158
+ }
159
+ if (parsed.status === "error") {
160
+ errorLog(`Error: ${parsed.message}`);
161
+ errorLog("");
162
+ errorLog(getDoctorUsage());
163
+ return 1;
164
+ }
165
+ const deps = overrides.deps ?? createDefaultStartTicketsDeps();
166
+ const agent = resolveAgentSpec(parsed.options.agentName) ?? resolveAgentSpec(DEFAULT_AGENT_NAME);
167
+ const collection = await collectDoctorResults(deps, parsed.options.agentName);
168
+ log(formatDoctorReport(deps.platform, agent, collection));
169
+ if (!collection.ok)
170
+ return 1;
171
+ return collection.results.some((r) => !r.found) ? 1 : 0;
172
+ }