@bridge_gpt/mcp-server 0.2.49 → 0.2.50
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 +24 -7
- package/build/base-ref.js +28 -3
- package/build/claude-review-workflow-drift-probe.js +130 -0
- package/build/claude-review-workflow-drift.js +173 -0
- package/build/claude-review-workflow.js +81 -16
- package/build/commands.generated.js +5 -5
- package/build/conductor/done-gate.js +25 -3
- package/build/conductor/install-doctor.js +65 -5
- package/build/conductor/latest-check-selector.js +170 -0
- package/build/conductor/local-merge.js +8 -6
- package/build/conductor-bin.js +1 -1
- package/build/{brainstorm-files.js → council-files.js} +15 -15
- package/build/decision-page-schema.js +1 -1
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +162 -4
- package/build/executor/worktree.js +46 -1
- package/build/index.js +92 -51
- package/build/init.js +9 -2
- package/build/install-bridge.js +60 -2
- package/build/install-reexec.js +47 -9
- package/build/pipelines.generated.js +1 -1
- package/build/plane/cli.js +12 -2
- package/build/plane/manifest.js +25 -1
- package/build/plane/member-roster.js +61 -7
- package/build/plane/preflight.js +24 -9
- package/build/plane/supervisor.js +77 -5
- package/build/plane/types.js +23 -3
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +2 -1
- package/build/stale-worktree-doctor.js +120 -0
- package/build/start-tickets-prereqs.js +70 -0
- package/build/start-tickets.js +91 -3
- package/build/version.generated.js +3 -2
- package/package.json +4 -2
- package/build/chain-orchestrator.js +0 -1457
- package/build/chain-utils.js +0 -68
- package/build/command-catalog.js +0 -376
- package/build/schedule-run.js +0 -1300
- package/build/schedule-store.js +0 -172
- package/build/scheduled-prompt.js +0 -115
- package/build/scheduler-backends/at-fallback.js +0 -139
- package/build/scheduler-backends/escaping.js +0 -143
- package/build/scheduler-backends/index.js +0 -72
- package/build/scheduler-backends/launchd.js +0 -225
- package/build/scheduler-backends/systemd-user.js +0 -250
- package/build/scheduler-backends/task-scheduler.js +0 -214
- package/build/scheduler-backends/types.js +0 -23
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
export const COMMANDS = {
|
|
4
4
|
"bridge-research.md": "Run multi-source, fact-checked web research via Bridge API and save a cited report locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 — Parse Arguments\n\nParse `$ARGUMENTS`:\n- The required `query` is the full text of `$ARGUMENTS` after removing any recognized flags.\n- An optional `--ticket <KEY>` flag captures a Jira ticket key (e.g., `BAPI-123`) to associate the research with a specific ticket. If `--ticket` appears, treat the immediately following token as the ticket key and remove both from the query.\n- If `$ARGUMENTS` is empty, or the query (after flag removal) is blank, stop immediately and display:\n\n```\nUsage: /bridge-research <question> [--ticket PROJ-123]\nExample: /bridge-research \"Best practices for rate limiting in FastAPI?\"\n```\n\n## Step 2 — Resolve Docs Directory\n\nCall the `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that path as `docs_dir`.\n\n## Step 3 — Run Research\n\nCall `request_deep_research` with:\n- `query`: the parsed question\n- `wait_for_result`: `true`\n- `ticket_number`: the value from `--ticket` if provided; omit this parameter entirely if not present\n\nThis step polls until the research completes (up to 15 minutes) and returns the full cited report directly. The tool appends a literal `Saved to <path>` line to its result — extract that line and store the path as `saved_path`.\n\nIf the tool returns an error, stop immediately and display:\n\n```\nResearch failed: <error message from the tool>\n```\n\n## Step 4 — Confirm\n\nDisplay a confirmation message:\n\n```\nResearch complete.\nSaved to: {saved_path}\n```\n\n## Final Summary\n\nDisplay a summary block:\n\n```\n## Bridge Research Report\n\n- **Query**: <query>\n- **Status**: Completed\n- **Local File**: {saved_path}\n```\n\nOn failure at any step, stop immediately, display which step failed and the error details, and do not proceed.\n",
|
|
5
5
|
"conduct-epic.md": "---\nschedulable: true\narguments: {\"positionals\":[{\"name\":\"epicKey\",\"type\":\"string\",\"required\":true}],\"flags\":[{\"name\":\"tickets\",\"flag\":\"--tickets\",\"type\":\"string\"},{\"name\":\"baseBranch\",\"flag\":\"--base-branch\",\"type\":\"string\"},{\"name\":\"checkpointPath\",\"flag\":\"--checkpoint-path\",\"type\":\"string\"}]}\n---\n\n# Conduct Epic: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command drives one multi-ticket epic from an approved ticket list to a finished `epic/<EPIC>` branch, one ticket at a time. It is the LLM half of the LLM-as-conductor pivot: there is no server-side reconciler here, no background worker, and no durable run row — the checkpoint file written by the packaged `conduct-epic` CLI plus the live state of GitHub *is* the entire memory of the loop.\n\nCadence is not an argument of this command. `/loop` owns the interval, this command owns exactly one reconcile-then-act step, and the two compose without either one holding state for the other.\n\nIt composes work that already exists rather than reimplementing it: `/review-and-start --auto --base-branch 'epic/<EPIC>' <KEY>` spawns each ticket's worker, the `merge_pull_request` MCP tool merges a green and approved pull request, `parse_repository` (`action: \"start\"`, then `action: \"status\"`) re-indexes the repository after each merge so the next ticket's plan sees its predecessor's code, and the packaged `conduct-epic` CLI (`init`, `status`, `checkpoint set`, `finish`, `spawn`) owns every durable file operation.\n\n---\n\n# Instructions\n\nYou are executing a 5-stage tick. Run the stages in order, take **exactly one** action from the Stage 3 detection table, write **exactly one** checkpoint in Stage 4, then stop. Do not loop internally, do not take a second action because the first one looked cheap, and do not carry assumptions from a previous tick — every tick reconciles from scratch.\n\nThe \"exactly one checkpoint\" rule has **three explicitly documented exemptions** and no others: the two print-only parks, `init_failed` (Stage 1) and `foreign_lock` (Stage 2), which stop before Stage 3; and the `all_done` tick (Row 1), which has no in-flight ticket to name in a `checkpoint set` command. Stage 4 states each one.\n\n## Stage 0 — Arguments and Ping\n\n1. **Parse `$ARGUMENTS`** into exactly one epic positional and the three optional flags. Accept no other input shape.\n\n - **`<EPIC>`**: exactly one positional token, which must match `[A-Z]+-[0-9]+` (e.g. `BAPI-798`). Zero epic positionals, more than one positional, or a positional that does not match the pattern is malformed input. Extra positionals are rejected rather than ignored.\n - **`--tickets <K1,K2,…>`** (and the equals form `--tickets=<K1,K2,…>`): a non-empty, comma-separated, **ordered** list of ticket keys. Preserve the caller's order exactly — it is the execution order of the epic. Every entry must match `[A-Z]+-[0-9]+` after trimming surrounding whitespace; reject a malformed key, an empty entry, and a duplicate key. This flag is required **only on the first tick** (see Stage 1); later ticks read the order from the checkpoint.\n - **`--base-branch <branch>`** (and the equals form `--base-branch=<branch>`): validated with the same rules as `/start-tickets` Stage 0 — 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`–`0x1F` or `0x7F`). It is the branch `epic/<EPIC>` is cut from at `init` time; it is not the pull-request base of a ticket, which is always `epic/<EPIC>`.\n - **`--checkpoint-path <path>`** (and the equals form `--checkpoint-path=<path>`): must be a non-empty string after trimming, checked **before** it is used as a path or interpolated into a CLI invocation. When omitted, the CLI's own default (`~/.config/bridge/conduct/<repo>/<EPIC>.json`) applies and `status` prints the resolved path.\n\n Reject malformed input before any side effect: an unsupported flag, a flag given without its value, a `--tickets` list that fails the rules above, a `--base-branch` value that fails validation, an empty `--checkpoint-path`, a missing epic, or an extra positional. On any of these, stop immediately and display:\n\n ```\n Invalid arguments.\n Usage: /conduct-epic [flags] <EPIC>\n <EPIC> required, matches [A-Z]+-[0-9]+ (e.g. BAPI-798)\n --tickets K1,K2,… ordered ticket keys; required only on the first tick\n --base-branch <branch> branch epic/<EPIC> is cut from (default: the repo base)\n --checkpoint-path <path> override the checkpoint file location\n ```\n\n2. **Connectivity check**: call the `ping` MCP tool with **no parameters**. If the call fails, or does not return `\"status\": \"ok\"`, stop immediately — before Stage 1 initialization, before any CLI invocation, and before any state is written — 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\n3. **Execution model.** This command is one tick; run it with `/loop 5m /conduct-epic <EPIC>`; each tick re-reads the checkpoint and GitHub, takes at most one action, and exits. `/loop` is the external driver that re-invokes this command — it is not an internal loop this command runs, and cadence is never an argument of this command.\n\n4. **Bash interpolation rule (global; applies to every Bash invocation in every stage).** Before interpolating any dynamic value — the epic key, a ticket key, a branch name, a checkpoint path, a prompt-file path, a JSON blob, a journal line — replace every `'` in the value with `'\\''`, then wrap the complete value in single quotes. Never expand a dynamic value unquoted, and never build a command by concatenating an unquoted variable. Credentials must never appear in a command argument, in printed output, in a journal line, or in a prompt file: the CLI and the MCP tools resolve their own credentials from the environment and the user-scoped credential store.\n\n5. **Packaged CLI launcher (`BAPI_MCP_CLI`); global, applies to every packaged-CLI invocation in every stage.** Resolve the launcher **once**, here in Stage 0, and reuse that one resolved value for the rest of the tick. Call it `<launcher>`.\n\n - Read the `BAPI_MCP_CLI` environment variable.\n - **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n - **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\n When the override is set, apply item 4's single-quote escaping rule to `<launcher>` before interpolating it into a Bash command string, keep every dynamic argument independently quoted rather than concatenated into the launcher value, and never put a credential or a credential-bearing environment assignment into it. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Initialize If No Checkpoint\n\nRun the first status probe through the **Bash tool**, forwarding `--checkpoint-path '<path>'` only when the user supplied it:\n\n```\n<launcher> conduct-epic status '<EPIC>' --json\n```\n\nA zero-exit response whose `checkpoint_exists` is `false` is the **only** condition under which initialization is allowed.\n\n- **`checkpoint_exists` is `false`** — this is the first tick. `--tickets` is required here, and **only** here: if it was not supplied, halt with the Stage 0 usage message and initialize nothing. On every later tick `--tickets` is optional and ignored, because the ordered list already lives in the checkpoint. Otherwise run, forwarding `--base-branch '<b>'` and `--checkpoint-path '<p>'` only when supplied:\n\n ```\n <launcher> conduct-epic init '<EPIC>' --tickets '<K1,K2,…>'\n ```\n\n Print the initialization preflight output **verbatim** — do not summarize it, do not suppress its announcements, and do not reorder it. `init` runs one preflight that lists every failure at once, and that listing is the operator's only diagnostic when it refuses.\n\n On a **non-zero** exit, `init_failed` is a **print-only park**: emit `NEEDS_HUMAN:init_failed` with the complete secret-free output as evidence, print exactly one bounded, secret-free stdout journal line describing this invocation, and stop the tick. Do **not** call `checkpoint set` and do not otherwise mutate durable state. There is nothing to write to: when initialization failed, no writable checkpoint may exist at all, and any checkpoint that does exist may be the unreadable one that caused the failure. Do not attempt a second initialization in the same tick and do not fall through to Stage 2.\n\n- **`checkpoint_exists` is `true`** — an epic that already has a checkpoint must **never** trigger `init`. The CLI deliberately refuses reinitialization (`already initialized`), so a retry is not a recovery path; it is a bug in the caller. Skip straight to Stage 2.\n\n- **The status command exits non-zero** (a corrupt or wrong-version checkpoint, for example) — treat it exactly like a failed init, including the print-only rule: preserve the secret-free stderr as evidence, emit `NEEDS_HUMAN:init_failed`, print one journal line, call no `checkpoint set`, and stop the tick. `status` never rewrites a checkpoint it could not read, so nothing has been damaged.\n\n## Stage 2 — Reconcile From Status JSON\n\nRun the status probe **again**, with the same conditional `--checkpoint-path '<path>'` forwarding:\n\n```\n<launcher> conduct-epic status '<EPIC>' --json\n```\n\nThis second response is the action snapshot. **This JSON object is the only evidence the tick acts on.** Worker claims are never trusted — a session that says \"CI passed\", \"review approved\", or \"PR merged\" has told you nothing this tick may use. Every one of those facts is re-derived here from GitHub and the server through `status`, and only from there.\n\nThe top-level contract is exactly: `ok`, `epic_key`, `epic_branch`, `checkpoint_path`, `checkpoint_exists`, `all_done`, `ticket`, `worktree_path`, `worktree_exists`, `branch_head`, `worker_commits_since_spawn`, `last_seen_head`, `last_state_change_at`, `stale_for_seconds`, `pr`, `merged_externally`, `ci`, `review`, `parse`, `deadlines`, `scope`, `lock`, `needs_human`, and `probe_errors`.\n\nThe nested objects the detection table reads are:\n\n- `ticket` — the in-flight ticket (the first entry that is not `done`, or `null` when `all_done`): `key`, `status` (`pending`, `in_progress`, `merged`, `done`, `needs_human`), `branch`, `pr_number`, `spawned_at`, `parse_requested_at`, `parse_requested_for_sha`, `review_verdictless_observations`, `review_verdictless_for_sha`, `respawns`, `conflict_attempts`, `counters.sessions_spawned`, `counters.plan_generations_observed`, `counters.merge_attempts`, and `journal`.\n - `review_verdictless_observations` is a **non-negative integer** and `review_verdictless_for_sha` is **a string or `null`**. They are Row 10's head-bound verdictless counter: the count is only meaningful for the head named beside it, and a count whose head does not equal `pr.head_sha` is spent evidence about code that no longer exists. Read them together or not at all.\n - `parse_requested_at` and `parse_requested_for_sha` are **each a string or `null`**. They are RETAINED for the audit trail of epics driven before the scope-status contract existed, and **no row reads them any more**: Row 5 asks the `scope` sub-object directly instead of reconstructing causality from a request timestamp. Do not write them and do not decide on them.\n - `journal` is the ticket's journal lines, **oldest-first, newest last**, exactly as stored. It is a human-readable audit trail and is **never** the source of a decision: it is capped at 50 lines and evicts oldest-first, so a marker searched for in it would silently vanish after roughly fifty wait ticks and the loop would re-request a parse it had already requested.\n- `pr` — `number`, `state` (`OPEN`, `MERGED`, `CLOSED`), `head_sha`, `base`, `mergeable`, `merge_state`, `updated_at`.\n- `ci` — `required`, `complete`, `stable_across_two_polls`, `head_sha`, and `checks` entries of `name`, `status`, `conclusion`, `required`.\n- `review` — `opted_in`, `source`, `available`, `verdict` (`approved`, `changes_requested`, `unknown`), `head_sha`, `verdictless_disposition`, `verdictless_ceiling`, `config_invalid`.\n - `verdictless_disposition` is `park`, `fail_open`, or `null`. **`null` means `park`** — it is what a condition that configured no disposition reports, and what an unreadable configuration reports. A value is only ever one of those three; the server-side parser refuses every other spelling outright rather than passing a partially honored one through.\n - `verdictless_ceiling` is the number of head-bound verdictless observations Row 10 makes before it decides. Read it from this snapshot and compare against it; never hard-code a bound.\n - `config_invalid` is `true` when the repository's `done_gate_config` exists but could not be read — a `malformed` or `invalid: …` parse. It arrives with `opted_in: true` and `available: false`, because an unreadable review policy is **not** an absent one: reading it as \"no review opt-in\" would merge on CI alone on the strength of a typo. There is no readable condition in that state, so `verdictless_disposition` is `null` and Row 10 parks.\n- `parse` — `status` (`idle`, `queued`, `in_progress`, `succeeded`, `failed`), `terminal`, `started_at`, and `finished_at`. The last two are each **a string or `null`** and are the ISO-8601 times of the current or last parse run. A `null` on either is unavailable evidence and **never** permits advancement — in particular, missing timestamps can never satisfy Row 5's causal check. There is no repository-wide index-branch override field: BAPI-847 retired that control plane, and an epic now gets its own index scope instead of taking the repository's index away.\n- `deadlines` — `soft_seconds`, `hard_seconds`, `elapsed_since_spawn_seconds` (defaults 3600 and 10800).\n- `scope` — the epic's index scope, read directly from the server: `scope_id`, `lifecycle_state`, `freshness_status`, `blocked_reason`, `required_commit_sha`, `indexed_commit_sha`, and `last_error`. It is `null` **only** when this epic declares no scope at all; that is not a probe failure and carries no `probe_errors` entry.\n - `freshness_status` is one of `fresh`, `pending`, `blocked`, `failed`, `unavailable`, `unobserved_advance`. **`fresh` is the only value that means the index covers this epic's merged code.** `pending` is a refresh still running. `blocked` is an epic advance the server REFUSED to index and will never resolve by waiting — `blocked_reason` names which refusal. `failed` is the scope's own generation failing. `unavailable` means the scope could not be read this tick, or its epic branch head could not be resolved, and is never equivalent to `fresh`. `unobserved_advance` means the epic branch has moved PAST `required_commit_sha` with no accepted observation explaining the move — the two watermarks agree, but at a commit the branch has left behind. It is the one non-`fresh` value this loop can repair itself.\n - `required_commit_sha` is the commit the scope must cover; `indexed_commit_sha` is the commit it actually has. **They are separate fields because they mean different things** — the required SHA moves the moment a merge is accepted, long before anything is indexed, so a required SHA equal to your merge commit is not evidence that your merge was indexed.\n- `lock` — `held_by_me`, `owner_pid`, `host`, `alive`.\n- `needs_human` — `null`, or `reason`, `evidence`, `at`.\n- `probe_errors` — entries of `probe` and `reason`.\n\nA failed probe leaves its sub-object `null` and is listed in `probe_errors`; it never fails the command. **A `null` sub-object is unavailable evidence, not a negative result.** Never infer a merge, an approval, a CI success, or a parse success from a `null` value, from a missing field, or from narrative output of any kind — an unavailable probe means \"wait for the next tick\", never \"proceed\".\n\n**`pr` is the one sub-object whose `null` has two distinct meanings, and `probe_errors` is what tells them apart:**\n\n- **`pr` is `null` and there is no `{probe: \"pr\"}` entry** — confirmed absence. `gh` was asked and answered that this branch has no pull request. This is the **normal** state of every tick between the first spawn and the moment the worker opens its pull request, it is a negative result the rows may act on, and Rows 6 and 7 exist precisely for it.\n- **`pr` is `null` and there IS a `{probe: \"pr\"}` entry** — unavailable evidence. `gh` could not answer: unauthenticated, rate-limited, offline, or output that did not parse. Treat it as \"wait for the next tick\" and never as absence; a pull request that exists but cannot be seen must not be reasoned about as one that does not exist.\n\nDo not collapse these two into \"no PR\". Reading an outage as absence is how the loop would respawn into, or abandon, a pull request that was there all along.\n\nTwo states stop the tick before any action is selected:\n\n- **Already parked.** If `needs_human` is not `null`, print the stable phrase `already parked`, followed by the persisted `reason`, the persisted string `evidence`, and the persisted `at` timestamp — then stop. Take no action this tick and write no checkpoint. A parked epic is a human's to unpark by editing the checkpoint (`needs_human` back to `null`, the ticket `status` back to `pending`/`in_progress`, counters adjusted if a budget is re-granted). Do not select a new recovery action on top of an existing one.\n- **Foreign lock.** If `lock.held_by_me` is `false` and `lock.alive` is `true`, another live process owns this epic. `foreign_lock` is a **print-only park**: emit `NEEDS_HUMAN:foreign_lock` carrying `lock.owner_pid` and `lock.host` as evidence, print one bounded, secret-free stdout journal line for this invocation, and stop. Do **not** call `checkpoint set`, spawn a session, merge a pull request, or start a parse while that lock is alive. The checkpoint belongs to the other live process; writing to it — even to record a park — is the two-authorities corruption the lock exists to prevent, and `checkpoint set` refuses a live foreign lock anyway.\n\n## Stage 3 — Detect and Take Exactly One Action\n\nEvaluate the rows below **strictly in written order, from top to bottom**. Evaluation stops at the first row whose condition matches; that row's action is the only action this tick performs, and control then proceeds directly to Stage 4. A later row is never \"also\" run because it happens to apply.\n\nOne row states a **forward-looking guard** in its own condition: Row 3 (`stalled`) matches only when no later action or fail-closed row would be selectable for this snapshot. That guard is part of Row 3's condition, not a departure from written order — the ordering rule still holds, and Row 3 simply does not match while a real action is available.\n\nEach row is marked **fail-open** (an uncertain or transient condition waits for the next tick) or **fail-closed** (the tick refuses to act and parks rather than guessing).\n\n### Row 1 — `all_done`: finish the epic and open its pull request\n\nWhen `all_done` is `true`, run `<launcher> conduct-epic finish '<EPIC>'` (forwarding `--checkpoint-path '<p>'` when supplied), then call the `create_pull_request` MCP tool with `head_branch` set to `epic/<EPIC>` and `base_branch` set to `main`. Assemble the `body` from the finish summary: the merged ticket pull requests and any skipped tickets. **Open the pull request; never merge it** — a human reviews and merges the epic into `main`. Then stop.\n\n**This tick writes no checkpoint and does not increment `counters.iterations`.** It is the third documented exemption from Stage 4's one-checkpoint-per-tick rule, and unlike the two print-only parks it reaches Stage 3. The reason is mechanical: `all_done` is `true` exactly when `ticket` is `null`, `checkpoint set` requires `--ticket <KEY>`, and there is no in-flight ticket to name. `finish` is this tick's durable act, and it is the last one the epic needs — so do not invent a ticket key to satisfy the rule, and do not write a checkpoint before or after `finish`.\n\n### Row 2 — Wrong base: do not touch a pull request that is not on the epic branch\n\nWhen `pr.base` is present and is not `epic/<EPIC>`, **do not touch the pull request** — no merge, no comment, no respawn. Select `NEEDS_HUMAN:wrong_base`, carrying the observed `pr.base`, `pr.number`, and the expected `epic/<EPIC>`. **Fail-closed**: only pull requests based on `epic/<EPIC>` are ever acted upon, and this row is evaluated before every work and recovery row precisely so a mis-based pull request cannot be merged, respawned into, or advanced by a later row.\n\n### Row 3 — Hard liveness: a stalled epic parks before it waits\n\nWhen `stale_for_seconds >= deadlines.hard_seconds` (default three hours, `10800`) **and no other row below is selectable this tick**, select `NEEDS_HUMAN:stalled`, carrying the observed `stale_for_seconds` and the `deadlines.hard_seconds` it exceeded. **Fail-closed**.\n\n**This row outranks wait rows only.** Before selecting it, check whether any of the following would otherwise be selectable for this snapshot; if any one of them would, take that row instead and do not park:\n\n- pending work (Row 4's first spawn),\n- Row 5's **action** branches only — branch 1's no-scope completion, branch 2's completion, branch 4's `unobserved_advance` guarded catch-up, branch 5's `shadow_stale_deadline` park, and branch 6's causal `parse_failed` park,\n- a targeted respawn (Rows 7, 9, and 11),\n- CI-red handling (Row 9) and review-remediation handling (Row 11),\n- conflict handling (Row 12),\n- ready-to-merge handling (Row 13),\n- a closed, unmerged pull request (Row 13a),\n- Row 10's **action** branch only — a verdictless review at or above `review.verdictless_ceiling`, whichever disposition it then applies. Row 10's below-ceiling branch is a wait and stays subordinate to this row, exactly as the old unbounded wait did.\n\n`stale_for_seconds` counts from the last observed head or status change, not from the last useful event — so an old but green and approved pull request accumulates staleness while being perfectly actionable. Parking that is the exact defect this guard removes. The row remains ahead of every wait row, because without it a wait would match forever and the epic would sit silent instead of asking for a human.\n\n**Row 5's wait branches are deliberately NOT in that list.** Branch 3 (a `pending` or `unavailable` scope) and branch 7 (nothing matched) are waits, and exempting them would mean a merged ticket whose refresh never starts waits forever with no human ever asked. They accumulate staleness like any other wait and park as `stalled` once `deadlines.hard_seconds` is exceeded.\n\n### Row 4 — Pending ticket: spawn the first worker\n\nWhen `ticket.status` is `pending`, spawn the ticket's session:\n\n```\n/review-and-start --auto --base-branch 'epic/<EPIC>' <KEY>\n```\n\nThen prepare the Stage 4 checkpoint values `spawned_at` (now, ISO-8601), `status=in_progress`, and `counters.sessions_spawned` = the Stage 2 value plus one.\n\n**Fail-closed**: refuse this spawn if the lock is foreign (Stage 2 has already parked in that case). The pull-request base of the spawned worker comes from BAPI-801's `BAPI_BASE_BRANCH` export — `/review-and-start --base-branch` forwards it into the spawned worker shell, and the worker's create-PR step resolves the base from it. That export is what makes the first pull request land on `epic/<EPIC>`; this loop never relies on it alone, because Row 2 independently re-checks the observed `pr.base` on every later tick.\n\n### Row 5 — Merged ticket: refresh the scope index, then mark done\n\nWhen `pr.state` is `MERGED`, or `merged_externally` is `true`, or `ticket.status` is `merged`, the ticket's code is on the epic branch. An **external merge is successful reconciliation, not an error** — a human who merged the pull request by hand did the loop's work for it, and `merged_externally` records exactly that.\n\n**The evidence this row acts on is `scope`, and only `scope`.** The epic's index scope is refreshed by the server the moment it observes the merge: it advances its own `required_commit_sha` to the merge commit and re-parses incrementally. So the question \"has this merge been indexed?\" is a question the scope can answer directly, and this row asks it instead of reconstructing an answer.\n\nThat is a deliberate replacement of the older mechanism. This row used to record the time it called `parse_repository` and the head SHA it called it for, then compare that timestamp against a repository-wide parse run's `started_at` / `finished_at` — because `parse.status` is repository-level and stays `succeeded` from any earlier parse of any earlier ticket, so \"succeeded\" alone proved nothing. Timestamp ordering was the only causality available. It is no longer needed, and inference is strictly worse than an answer: **do not call `parse_repository` from this row, and do not read `parse`, `ticket.parse_requested_at`, or `ticket.parse_requested_for_sha` as freshness evidence.** The server owns the refresh; this loop observes it.\n\nThis row is an **ordered state machine**, evaluated top to bottom, and the first matching branch is the tick's action:\n\n1. **`scope` is `null`** — this epic declares no index scope, so there is nothing to refresh and no freshness to establish. Call `update_jira_status` for the ticket with `target_status` set to the Jira `Done` state, and prepare `status=done`. Journal that the ticket completed with no declared scope. **Fail-open.** An epic that never had a scope must not be blocked by one.\n\n2. **`scope.freshness_status` is `fresh`, and `scope.indexed_commit_sha` equals `scope.required_commit_sha`, both non-null** — the scope's index provably covers the commit the server is holding it to. Only then call `update_jira_status` for the ticket with `target_status` set to the Jira `Done` state, and prepare `status=done`. Journal both observed watermarks.\n\n **Compare the scope's two watermarks against each other — never against `pr.head_sha` or `branch_head`.** Both of those are the *worker's* pre-merge branch tip: `pr.head_sha` is `headRefOid`, and `branch_head` is `git ls-remote` of the ticket's own branch. What lands on `epic/<EPIC>` is the merge commit GitHub creates, and that differs from the worker's tip under every merge strategy — merge, squash, and rebase alike. Comparing an indexed watermark against either one is therefore false essentially always, and a branch that waits on an always-false condition never marks anything done. For the same reason, do not invent a merge-commit field: the `scope` object carries exactly the seven fields named above, and none of them is one.\n\n The identity that IS causal runs between the scope's own two watermarks, and it is what replaces the old timestamp ordering. The server advances `required_commit_sha` the moment it observes this merge, and **only the parse** writes `indexed_commit_sha`; the two fields are owned by different writers precisely so their agreement means something. So `indexed == required` is the server's own statement that it has finished indexing everything it was asked to cover. A scope that finished refreshing for a **previous** ticket reads `fresh` too — but it reads it at that previous required commit, and the moment this merge is observed `required` moves ahead of `indexed` and `freshness_status` drops to `pending` until the re-parse lands. If either watermark is `null` the comparison cannot be made, so this branch does not match and the tick falls to branch 6 and waits.\n\n **The gap this used to see straight through is now detected and repaired.** A merge the server never observed at all leaves both watermarks agreeing at the *previous* commit — which reads exactly like a healthy scope from the row alone — and this branch used to mark the ticket done against an index that did not contain its code. That was not a narrow window that closed on its own; it was a permanent wrong answer whenever nothing observed the merge, and it is what made an unregistered merge webhook a silent correctness failure rather than a latency cost.\n\n The server now compares `required_commit_sha` against the epic branch's ACTUAL head and reports `unobserved_advance` instead of a false `fresh` (branch 4 below repairs it). So this branch's `fresh` is a stronger statement than it was: the pin is current with the branch *and* the index has caught up to the pin. There remains a genuinely narrow window — the seconds between a merge landing and the server observing it — in which the scope reads `pending` rather than `fresh`, which is a wait, not a wrong answer. If you nevertheless see a `done` whose journaled watermarks match the *previous* ticket's, that is a defect worth recording.\n\n3. **`scope.freshness_status` is `pending`, `unavailable`, or missing** — the refresh is still in flight, or the scope could not be read. Wait. Journal the observed `scope.lifecycle_state`, `scope.required_commit_sha`, and `scope.indexed_commit_sha`. Do not spawn anything and do not advance the next ticket. **An unread scope is never a fresh one**, and neither is one whose epic branch head could not be resolved — that also arrives here as `unavailable`, never as `unobserved_advance`.\n\n4. **`scope.freshness_status` is `unobserved_advance`** — the epic branch has moved past `required_commit_sha` and nothing observed the move. The index provably does not cover the epic's merged code, and unlike branch 3 nothing is in flight that will fix it. Attempt the **guarded catch-up exactly once this tick**:\n\n ```bash\n node mcp_server/build/index.js conduct-epic catch-up '<EPIC>' --json\n ```\n\n The catch-up replays the missed merges through the SAME AC-10 gate a merge webhook would have used. It has no force, override, or skip-guard flag, and it cannot make that gate say yes — it only makes the gate reachable for a merge nobody observed at the time. Then act on its `outcome`:\n\n - `repaired`, `parse_scheduled`, `already_current`, or `race_lost` — **wait**, exactly as branch 3 does. Do not mark the ticket done on the catch-up's own say-so: completion still requires branch 2's authoritative `fresh` on a later tick, read from `status`. Journal the outcome and the returned `required_commit_sha`.\n - `blocked`, `history_limit_exceeded`, `refused_lifecycle`, or `invalid_scope` — a **controlled refusal**. Select `NEEDS_HUMAN:shadow_stale_deadline`, carrying the catch-up `outcome` and its bounded `reason` (a controlled `advance_blocked_*` token for `blocked`) in the bounded `evidence` string. **Fail-closed.**\n - `unavailable` — the branch or the provider could not be read. **Wait**; this is the same \"we could not look\" condition branch 3 handles, and it often clears on its own.\n\n **Attempt it once per tick, never in a loop.** A second attempt in the same tick reads the same branch state and reaches the same verdict, and the operation is idempotent precisely so that a repeat is harmless rather than useful.\n\n `shadow_stale_deadline` is reused rather than given a new token, and that is deliberate: its operational meaning — *the index does not cover this epic's branch and waiting will not fix it* — is exactly true here, and the distinguishing detail lives in the bounded `evidence`. **Do not widen the closed `NEEDS_HUMAN` vocabulary.** It is eleven reasons and no others, nine of them persisted, and it is mirrored in TypeScript, so a twelfth token is a disproportionate blast radius for a distinction one evidence string already carries.\n\n **Scheduling a parse is not the same as moving the pin.** `parse_repository` — including a scope-addressed call — indexes whatever the scope is *already* pinned to; the parse subprocess is handed `required_commit_sha` and verifies against it. So a parse can never substitute for observation or catch-up, and calling one from this row would do nothing at all. Do not.\n\n5. **`scope.freshness_status` is `blocked`** — the server REFUSED to index this advance, and waiting will never change that. Select `NEEDS_HUMAN:shadow_stale_deadline`, with `scope.blocked_reason` as bounded string evidence, and state plainly in the evidence that **no epic advance was indexed**. **Fail-closed.**\n\n The controlled reasons and what each one means to a human:\n\n - `advance_blocked_base_merge` — the base branch was merged forward into the epic branch. The epic branch is pinned at its cut point; a base merge would move that pin.\n - `advance_blocked_unexpected_parent` — the merge commit does not descend directly from the branch head the scope pinned. Something other than a worker pull request landed on the branch.\n - `advance_blocked_history_changed` — the pinned head is gone from the branch's history. A force-push or rewrite.\n - `advance_blocked_unverifiable` — the advance could not be verified at all. Doubt blocks; it never indexes.\n\n **This park is immediate, and that is deliberate** — it is the one place the pilot escalates faster than v2. The v2 reconciler routes a blocked advance through the same `shadow.stale_deadline_seconds` clock it uses for an ordinary refresh hold, because its hold is anchored on a single durable episode timestamp that every hold reason shares. The pilot has no such episode and no typed `RunPolicy` deadline, and none of the four reasons above resolves by waiting, so waiting out a deadline would only delay a human by up to that deadline and change nothing else. Both conductors emit the **same** `shadow_stale_deadline` reason so one grep finds a refused advance either way; only the latency to the park differs. An operator comparing the two should expect the pilot to ask sooner, not to have asked for a different thing.\n\n6. **`scope.freshness_status` is `failed`** — the scope's own generation failed, which is a different problem from a refused advance. Select `NEEDS_HUMAN:parse_failed`, with `scope.lifecycle_state` and `scope.last_error` as bounded string evidence. **Fail-closed.**\n\n7. **None of branches 1–6 matched** — including a `fresh` scope whose indexed commit still trails its required commit, and a tick where either watermark is missing so no comparison can be made. Wait, and journal the observed scope fields. Neither advance nor park: hard liveness (Row 3) is what eventually escalates a wait that never resolves.\n\n**No next ticket is spawned until this one reaches `done`.** A merged ticket stays in flight until its scope is fresh for its own merge commit, so `ticket` still points at it and Row 4 cannot match for its successor — which is the whole point: the next ticket's review and plan must see this ticket's merged code.\n\n### Row 6 — Worktree working: wait\n\nWhen a worktree exists (`worktree_exists` is `true`), the pull request is **confirmed absent** (`pr` is `null` **and** `probe_errors` carries no `{probe: \"pr\"}` entry), and `worker_commits_since_spawn > 0`, the worker is making observable progress. Wait, and journal the observed `branch_head` and commit count. **Fail-open.**\n\nA `pr: null` accompanied by a PR probe error is unavailable evidence, not absence, and does not match this row — it falls through to Row 15 and waits.\n\n### Row 7 — Soft deadline with no progress: one targeted continuation\n\nWhen the pull request is **confirmed absent** (`pr` is `null` **and** no `{probe: \"pr\"}` entry), `worker_commits_since_spawn` is `0`, and `deadlines.elapsed_since_spawn_seconds >= deadlines.soft_seconds` (default one hour, `3600`), spend the single targeted respawn on kind `continue`, with the prompt:\n\n```\nBranch <b> for <KEY>: continue the existing plan; do not regenerate it; push when done\n```\n\nPrepare `respawns` = the Stage 2 value plus one. `respawns` is **one shared per-ticket budget**, not one allowance per row: Rows 7, 9, and 11 all spend the same single counter, so spending it here leaves nothing for a later CI fix or review fix on this ticket. The attempt **counts only if it pushed** — a later tick observing a non-null `branch_head` is the proof. A respawn that produces no push is a no-op, and a no-op respawn stops the loop rather than spinning: once the one targeted respawn is spent and the ticket still shows no pushed head, select `NEEDS_HUMAN:stalled`. **Fail-closed after one attempt**, which is what keeps a dead worker from being respawned without bound.\n\n### Row 8 — Pull request open, CI not settled: wait\n\nWhen a pull request is open and `ci.complete` is `false` **and no required check in `ci.checks` has already reached a terminal unsuccessful conclusion**, wait; or when `ci.complete` is `true` and green but `ci.stable_across_two_polls` is `false`, wait. **Fail-open.**\n\nThe boolean alone is not the condition. `ci.complete` is `false` both while checks are still running and once a required check has definitively failed, and those are opposite situations: the first is worth waiting on and the second never becomes green on its own. This row therefore covers pending and not-yet-stable checks **only** — a required check with a terminal unsuccessful conclusion is **not** consumed here and falls through to Row 9.\n\n### Row 9 — Pull request open, CI red: one targeted fix\n\nWhen a pull request is open, one or more required checks in `ci.checks` have a terminal unsuccessful conclusion, and there has been no new commit for over 60 minutes (`stale_for_seconds > 3600` is the authoritative no-new-commit duration), spend the single targeted respawn on kind `ci_fix`. Take the failing check names from `ci.checks` — the entries whose `required` is `true` — and use the prompt:\n\n```\nPR #N is red on <checks>: read the check annotations, fix, push; do not regenerate the plan\n```\n\nPrepare `respawns` = the Stage 2 value plus one; the attempt counts only if it pushed. A bare `/implement-ticket --auto` is **prohibited** here: it regenerates the plan, costs a full plan generation, and discards the failure detail the annotations already carry.\n\n`respawns` is **one shared per-ticket budget** across Rows 7, 9, and 11. A continuation respawn spent earlier on this ticket therefore leaves **no** CI-fix attempt: with the counter already at its limit, persistent red CI parks immediately as `NEEDS_HUMAN:ci_red` rather than getting a fix session of its own. Once the shared respawn is spent and CI is still red, select `NEEDS_HUMAN:ci_red` with the failing check names as bounded string evidence. **Fail-closed after one attempt.**\n\n### Row 10 — Review opted in and verdictless: count, then decide\n\nWhen `pr.state` is `OPEN`, `review.opted_in` is `true`, and the review is **verdictless for the current head** — that is, `review.available` is `false`, **or** `review.verdict` is neither `approved` nor `changes_requested` at `pr.head_sha` — the review has produced no usable answer for this code. Count the observation, then act on the count.\n\nThis row covers **both** verdictless shapes on purpose. `review.available` is `false` only when the review read itself failed. A reviewer that ran and died before publishing anything is a different shape: the read succeeds, `review.available` is `true`, and `review.verdict` is `unknown`. Both mean the same thing to this loop — no verdict exists for `pr.head_sha` — and a row that covered only the first would leave the second matching nothing at all.\n\n`changes_requested` at the current head is **explicitly excluded**, so Row 11 stays reachable: a reviewer that asked for changes produced a verdict, and that verdict is Row 11's business. A `changes_requested` verdict whose `review.head_sha` does not equal `pr.head_sha` is about code that no longer exists, so it is verdictless for the current head and does match here.\n\nThe `pr.state` is `OPEN` guard is load-bearing: without it a `CLOSED` pull request whose review is verdictless matches here, ahead of Row 13a, and the loop counts tick after tick on abandoned work instead of parking it.\n\n**Prepare the counter, bound to the current head.**\n\n- If `ticket.review_verdictless_for_sha` does **not** equal `pr.head_sha`, prepare `review_verdictless_observations` = `1` and `review_verdictless_for_sha` = `pr.head_sha`. **The counter resets on a new head.** Observations made against an abandoned head must never spend the budget belonging to the head that replaced it — a later push replaces the code the reviewer failed on, and the new code deserves its own full budget.\n- Otherwise prepare `review_verdictless_observations` = the Stage 2 value plus one, absolute, and leave `review_verdictless_for_sha` at `pr.head_sha`.\n\nWrite both prepared fields through the ordinary single `checkpoint set` for this tick, in every direction below — waiting, parking, and the waived merge alike.\n\n**Compare the prepared count with `review.verdictless_ceiling`**, which the Stage 2 snapshot carries. Compare two numbers read from the snapshot; never compare against a bound written into this prose.\n\n- **Below the ceiling** — wait one tick and journal the observation, naming the prepared count, the ceiling, and the observed `review.available` / `review.verdict`. This is today's behaviour, unchanged. This branch is a **wait**, so Row 3's hard-liveness park still outranks it exactly as it does now.\n- **At or above the ceiling** — apply `review.verdictless_disposition`. This branch is an **action**, so it outranks Row 3, and the ceiling is what an operator actually sees instead of a three-hour `stalled` that names the wrong failure.\n\n**At or above the ceiling, the disposition decides:**\n\n- **`park`** — the default, and the value used whenever `review.verdictless_disposition` is `null`, including when `review.config_invalid` is `true` (a review policy that could not be read carries no readable disposition, so it gets the safe one). Select `NEEDS_HUMAN:review_verdictless_ceiling_reached`, carrying the observed count, the ceiling, `pr.head_sha`, and `review.available` / `review.verdict` as bounded string evidence.\n- **`fail_open`** — treat the ticket as **review-opted-out for this tick** and fall through to Row 13's merge conditions. Row 13 still requires everything else it always required: `pr.state` is `OPEN`, `pr.base` is `epic/<EPIC>`, `ci.complete` is `true`, `ci.stable_across_two_polls` is `true` at `pr.head_sha`, and a non-conflicting pull request. **CI, not a verdict, is the whole of the evidence in that case** — journal `review_waived_verdictless_fail_open` and say the merge proceeded on stable CI evidence alone. Never journal, print, or record it as a review that passed or approved anything.\n\nAny value other than exactly `fail_open` resolves to `park`. There is no third direction, and an unreadable disposition is never treated as permission.\n\n### Row 11 — Changes requested for the current head: one targeted review fix\n\nWhen `pr.state` is `OPEN`, `review.verdict` is `changes_requested`, **and** `review.head_sha` equals `pr.head_sha`, spend the single targeted respawn on kind `review_fix`. The `OPEN` guard is what stops a `changes_requested` verdict left on a **closed** pull request's head from spending this ticket's one respawn on work nobody will merge — that snapshot belongs to Row 13a. The prompt carries the authoritative Stage 2 review evidence: the ticket key, the pull-request number, the reviewed head SHA, and the requested changes. A stale `review.head_sha` (one that does not equal `pr.head_sha`) is a verdict about code that no longer exists and never triggers this row. Prepare `respawns` = the Stage 2 value plus one.\n\n`respawns` is **one shared per-ticket budget** across Rows 7, 9, and 11. Any earlier continuation or CI-fix respawn on this ticket therefore leaves **no** review-fix attempt: with the counter already at its limit, requested changes on the current head park immediately. Once the shared respawn is spent and the verdict still stands for the current head, select `NEEDS_HUMAN:review_changes_requested`. **Fail-closed after one attempt.**\n\n### Row 12 — Conflicting pull request: at most two conflict sessions\n\nWhen `pr.state` is `OPEN` **and** either `pr.mergeable` is `CONFLICTING` or `pr.merge_state` is `DIRTY`, spawn a session of kind `conflict` with the prompt:\n\n```\nrebase onto origin/epic/<EPIC>, resolve, run tests, push\n```\n\nPrepare `conflict_attempts` = the Stage 2 value plus one. The conflict budget is **two** sessions and is counted separately from the single targeted respawn of Rows 7, 9, and 11 — a rebase is a different failure mode from a stalled or red worker. After the second conflict session, if the pull request is still `CONFLICTING`/`DIRTY`, select `NEEDS_HUMAN:conflict`. **Fail-closed after two attempts.**\n\nA **closed** pull request is frequently left `CONFLICTING`/`DIRTY` by GitHub, so without the `pr.state` is `OPEN` guard this row would match ahead of Row 13a and spend a rebase session resolving conflicts on a branch nobody will merge.\n\n### Row 13 — Ready to merge\n\nMerge only when **all** of the following hold on the fresh Stage 2 snapshot: the pull request is open (`pr.state` is `OPEN`); `pr.base` is `epic/<EPIC>`; `ci.complete` is `true` and `ci.stable_across_two_polls` is `true` for `ci.head_sha` equal to `pr.head_sha`; the pull request is not conflicting; and review is either opted out (`review.opted_in` is `false`), approved (`review.verdict` is `approved`) with `review.head_sha` equal to `pr.head_sha`, **or** waived by a `fail_open` verdictless ceiling reached in Row 10 this tick.\n\nWhen the waiver path is what reached this row, journal the shared token `review_waived_verdictless_fail_open` alongside the merge line and say plainly that the merge proceeded **on stable CI evidence alone**. That token is the same string the v2 conductor records for the same degradation, so one grep finds every merge that advanced without a verdict whichever conductor drove the epic. Never write it in language that claims the review passed or approved the pull request — it names what was missing, not what was satisfied.\n\nThen call the `merge_pull_request` MCP tool with exactly `pr_number` set to `pr.number` and `expected_head_sha` set to `pr.head_sha`. **The expected SHA is derived only from the fresh Stage 2 status** — never from the checkpoint, never from a worker's report, never from an earlier tick. The checkpoint deliberately stores no expected head; merge identity always comes from a freshly observed `pr.head_sha`. Prepare `counters.merge_attempts` = the Stage 2 value plus one for **every** invocation of the tool, successful or not.\n\nMap the returned envelope:\n\n- **`merged` is `true`** — the only success. It covers `outcome: merged` and `outcome: already_merged`, both of which carry that boolean. Prepare `status=merged` and top-level `counters.merges` = the Stage 2 value plus one.\n- **`outcome: refused` with `reason: head_sha_drift`** — the head moved under the merge. Journal the complete envelope (including `actual_head_sha`) and take a fresh status snapshot on the next tick. Never retry with the stale SHA.\n- **Outcome `lease_held`, `review_not_approved`, or `unknown`, or any envelope carrying `retry_hint: retry_later`** — journal it and wait for the next reconciliation tick.\n- **Outcome `dry_run`, `pending_approval`, `gate_unresolved`, `action_key_mismatch`, `review_unavailable`, `review_source_unsupported`, `error`, or any `refused` result carrying `retry_hint: needs_human`** — select `NEEDS_HUMAN:merge_blocked`. Preserve the **complete** envelope as the evidence, including `hint`, `actual_head_sha`, `ci_summary`, `paths`, and `http_status` whenever those are present; `hint` is usually the exact operator fix. **JSON-stringify that envelope into a bounded, secret-free string** — `evidence` is string data, never an object (see Stage 4).\n\n**Fail-closed**: only `merged: true` is success. A missing, `false`, or malformed `merged` value is never treated as a merge, no matter what `outcome` says alongside it.\n\n### Row 13a — Pull request closed without being merged\n\nWhen `pr.state` is `CLOSED` and the pull request was not merged, the ticket's work has been abandoned on GitHub and nothing this loop does can advance it. Select `NEEDS_HUMAN:merge_blocked`, with bounded string evidence that identifies `pr.state: CLOSED` along with `pr.number`. **Fail-closed** — a closed pull request is never respawned into, reopened, or merged by this loop.\n\n### Row 14 — Local-mode ticket operation refused\n\nWhen a ticket operation returns `409 UNSUPPORTED_IN_LOCAL_MODE`, tolerate it and journal it. The repository is running the local ticket backend, where that response is the documented terminal answer rather than a failure. It introduces **no** new parking reason. **Fail-open.**\n\n### Row 15 — No row matched: journal the snapshot and do nothing else\n\nWhen no row above matches, that is the tick's outcome, not a licence to improvise. Journal a concise summary of the Stage 2 snapshot, take **no** external action — no MCP tool call, no spawn, no merge, no parse — and change **no** row-specific checkpoint field. The single `checkpoint set` this tick writes therefore carries only the universal `counters.iterations` update and its one journal line.\n\nThis row exists because unmatched snapshots are real and reachable: a `stale_for_seconds` or `elapsed_since_spawn_seconds` that is `null` because nothing has been observed yet; a pull request whose CI is complete but not yet stable across two polls. Each of those is a legitimate \"wait for reality to move\" state, and a tick that improvised an action for it would be acting on evidence it does not have. **Fail-open.**\n\nAn open pull request awaiting a review whose `verdict` is still `unknown` is **no longer** one of these. Row 10 now matches that snapshot, counts it, and eventually decides — falling through to here would be the unbounded wait the ceiling exists to end.\n\n### Shared mechanics for every targeted session\n\nRows 7, 9, 11, and 12 spawn a session the same way. The four kinds are exactly `continue`, `ci_fix`, `review_fix`, and `conflict`.\n\n**First, write the prompt file** with the Write tool, at:\n\n```\n~/.config/bridge/conduct/<repo>/<EPIC>/prompts/<KEY>-<kind>-<n>.md\n```\n\nwhere `<EPIC>` and `<KEY>` are the validated keys, `<kind>` is one of the four kinds above, and `<n>` is the applicable absolute attempt number. **`<repo>` is the repository component of the resolved `checkpoint_path` that Stage 2's `status` returned** — read it from there rather than re-deriving it from credentials, from `BAPI_REPO_NAME`, or from anything remembered in conversation. `status` resolves that path itself, including any `--checkpoint-path` override and any `XDG_CONFIG_HOME` redirection, so it is the only value guaranteed to match where the CLI actually keeps this epic's state.\n\n**End every prompt with this exact wording**, so the spawned worker releases its worktree cleanly instead of lingering:\n\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 — 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```\n\n**Then spawn**, forwarding `--checkpoint-path '<p>'` only when supplied:\n\n```\n<launcher> conduct-epic spawn '<EPIC>' --ticket '<KEY>' --prompt-file '<path>'\n```\n\n`spawn` opens exactly **one** agent tab in the ticket's `worktree_path` running the prompt file's contents. It refuses when the worktree is missing, the prompt file is unreadable, or the lock is held by another live process.\n\n**The budgets are this command's job, not the CLI's.** `spawn` never checks them: it will happily open a fifth tab if asked. One targeted respawn **shared** across Rows 7, 9, and 11 — a single per-ticket `respawns` counter, not one allowance per row — and two conflict sessions in Row 12, are enforced here, by reading the Stage 2 `respawns` and `conflict_attempts` before choosing the row.\n\nAfter a **successful** spawn, prepare `counters.sessions_spawned` = the Stage 2 value plus one. If the spawn command itself fails, do **not** advance `respawns`, `conflict_attempts`, or `counters.sessions_spawned` — a session that never opened has consumed no budget.\n\nKeep credentials, raw environment values, and unrelated command output out of prompt files and out of the spawn command's arguments. The spawned agent resolves its own credentials.\n\n## Stage 4 — Checkpoint and Stop\n\nEvery tick that reaches this stage ends with **exactly one** checkpoint command and **exactly one** journal line. There are **three exemptions**, and they divide into two kinds:\n\n- **Two print-only parks, before Stage 3.** `init_failed` (Stage 1) and `foreign_lock` (Stage 2) stop the tick *before* Stage 3 and write nothing durable at all — they print their `NEEDS_HUMAN:` line and one stdout journal line and stop. Because they never reach Stage 3 they also never increment `counters.iterations`.\n- **The `all_done` tick, inside Stage 3.** Row 1 reaches Stage 3 but has **no in-flight ticket**: `all_done` is `true` exactly when `ticket` is `null`, and `checkpoint set` requires `--ticket <KEY>`. That tick runs `finish`, opens the epic pull request, writes **no** checkpoint, and — as the single stated exception to the rule below — does **not** increment `counters.iterations`.\n\nEvery other tick, including a Row 15 fallthrough, writes here. Run, forwarding `--checkpoint-path '<p>'` whenever the user supplied it:\n\n```\n<launcher> conduct-epic checkpoint set '<EPIC>' --ticket '<KEY>' --field <name> <absolute-value> … --journal '<line>'\n```\n\nRepeat `--field <name> <absolute-value>` once per changed field, and pass `--journal '<line>'` exactly once. Do not issue a second `checkpoint set` in the same tick, and do not split the fields across two invocations — one tick, one auditable write.\n\n**Every value is absolute, computed from the Stage 2 snapshot.** Relative or guessed increments are prohibited: the CLI stores what it is given, so a \"+1\" that was never resolved against a fresh read silently corrupts the count. Compute `n + 1` from the Stage 2 value for `counters.sessions_spawned`, `respawns`, `conflict_attempts`, `counters.merge_attempts`, `counters.iterations`, and `counters.merges`.\n\n`review_verdictless_observations` follows the same absolute rule with one addition: when `ticket.review_verdictless_for_sha` does not equal `pr.head_sha`, the absolute value is `1` rather than `n + 1`, because the counter is bound to a head and resets when the head moves. `review_verdictless_for_sha` is written as the observed `pr.head_sha`. Row 10 owns both fields; no other row writes them.\n\nInclude only the fields the selected row actually affected — typically some of `status`, `spawned_at`, `respawns`, `conflict_attempts`, `review_verdictless_observations`, `review_verdictless_for_sha`, `counters.sessions_spawned`, `counters.merge_attempts`, `counters.iterations`, and `counters.merges`.\n\n**`parse_requested_at` and `parse_requested_for_sha` are no longer written by any row.** The CLI still accepts them so an older checkpoint stays readable, but Row 5 now reads the `scope` sub-object — the server's own answer about whether this merge was indexed — rather than recording a request and timing it. Writing them would record evidence nothing reads.\n\n**`counters.iterations` increments exactly once for every tick that reaches Stage 3**, and it is written in that tick's single `checkpoint set` as the Stage 2 absolute value plus one. It is the one field every such tick updates, including a Row 15 fallthrough — which is why a fallthrough tick's checkpoint contains only `counters.iterations` and its journal line, with no status, retry, merge, or parking mutation. The two print-only parks never reach Stage 3 and so never increment it, and the `all_done` tick reaches Stage 3 but writes no checkpoint, so it does not increment it either.\n\n**Parking** adds two fields to the same single command:\n\n```\n--field status needs_human --field needs_human '{\"reason\":\"<reason>\",\"evidence\":\"<bounded secret-free JSON-stringified envelope or output>\",\"at\":\"<ISO-8601 timestamp>\"}'\n```\n\n**`evidence` is a JSON string, never an object.** The CLI's checkpoint schema accepts only `{reason: string, evidence: string, at: string}` and rejects anything else outright, so an object-valued `evidence` makes `checkpoint set` exit non-zero: the `NEEDS_HUMAN:` line prints, the park never persists, and the next tick repeats the failing action. When the evidence is structured — a merge envelope, a command's output — JSON-stringify it and escape every embedded quote and control character so the result is a single valid JSON string value. Keep it bounded and secret-free.\n\nThe `reason` is one of the closed list below and `at` is an ISO-8601 timestamp. Every `NEEDS_HUMAN:<reason>` line printed by a stage carries the **same** evidence that is persisted here — the printed line and the checkpoint never disagree.\n\nThe parking vocabulary is closed — **eleven reasons** and no others — and it has two partitions:\n\n- **Nine persisted reasons**, each written durably by the single `checkpoint set` above: `stalled`, `ci_red`, `review_changes_requested`, `merge_blocked`, `conflict`, `parse_failed`, `shadow_stale_deadline`, `wrong_base`, and `review_verdictless_ceiling_reached`. A persisted park is what makes the *next* tick report `already parked` and stop.\n - `shadow_stale_deadline` is the reason for both of Row 5's index refusals — branch 5's `blocked` scope, and branch 4's controlled guarded-catch-up refusal — and it is deliberately **the same token the v2 conductor parks under** for the same condition. One token covers all three because they mean one thing operationally: *the index does not cover this epic's branch and waiting will not fix it*. The distinguishing detail (which refusal, and which controlled `advance_blocked_*` token) travels in the bounded `evidence` string, which is exactly what a bounded evidence field is for and is far cheaper than a twelfth reason mirrored across TypeScript. Both conductors reaching for one string is what lets an operator grep for a refused epic advance without first working out which conductor drove the epic. It is distinct from `parse_failed`: `parse_failed` means the index generation broke, while `shadow_stale_deadline` means the index refused to accept the branch advance at all.\n - `review_verdictless_ceiling_reached` is Row 10's park, and it is **byte-identical to v2's own token** for the same reason `shadow_stale_deadline` is shared: one grep finds a verdictless ceiling whichever conductor drove the epic. Four alternatives were considered and rejected. `stalled` is the label this row exists to stop emitting — it says the worker died when what actually died was the reviewer. `merge_blocked` is wrong because the merge tool was never called, and its evidence table is built entirely around merge envelopes. `review_changes_requested` is factually false: nobody requested changes, nobody said anything. And a fresh `review_unavailable` token would collide with the merge tool's existing `review_unavailable` *outcome*, which Row 13 already maps to `merge_blocked` — two different conditions answering to one string is exactly the confusion a closed vocabulary exists to prevent.\n- **Two print-only reasons**, which are printed and journaled to stdout for the current invocation only and write nothing durable: `init_failed` and `foreign_lock`. Neither may call `checkpoint set`. A print-only park leaves no durable record, so it does not produce an `already parked` tick — the next tick reconciles from scratch and reports the condition again if it persists.\n\nDo not invent a new reason; a genuinely new failure mode is a change to this command and to the BAPI-805 runbook together.\n\nThe journal line is one line containing the ISO-8601 time, the selected action, and concise evidence. Print it **last**, after the checkpoint command has succeeded, so the operator's final line of output is the tick's durable record.\n\nEvery dynamic value in this stage follows the Stage 0 single-quote rule — the epic key, the ticket key, the checkpoint path, the `needs_human` JSON, and the journal line are each escaped (`'` → `'\\''`) and wrapped in single quotes. Credentials never appear in a checkpoint argument or in journal evidence.\n\n## Operational Guarantees\n\n- **Spec freshness is `/review-and-start`'s job, not a separate check.** Each ticket's review phase runs in a worktree cut from the current `epic/<EPIC>` tip, so its review and its plan already see every predecessor's merged code. This command runs no separate spec-freshness check and needs none.\n- **The checkpoint plus GitHub are the resume point.** Nothing relies on conversation memory. A sleeping laptop merely misses ticks; the next invocation reconciles from scratch and continues where reality actually is.\n- **This command never creates an `epic_run`.** It must never be combined with `setup-epic` on the same epic — the v2 conductor stays active there, and two authorities transitioning one epic is exactly the failure this pivot removes.\n- **`/loop 5m /conduct-epic <EPIC>` is the driver.** The operator runbook is BAPI-805's, not this file's.\n- **Recovery is bounded**: one targeted respawn *shared* across Rows 7, 9, and 11, and two conflict sessions, then park. There is no third chance and no escalating retry.\n- **The first spawn relies on BAPI-801's `BAPI_BASE_BRANCH` contract**, while every tick still independently verifies the observed `pr.base` (Row 2). The export makes the right thing happen; the check catches it when it does not.\n",
|
|
6
|
-
"council.md": "Convene a multi-perspective council on a task via Bridge API and save the resulting report locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 — Parse Arguments\n\nParse `$ARGUMENTS`. The supported invocation is exactly:\n\n```text\n/council <task description> [--mode technical|design|discovery|general] [--debate] [--lenses a,b] [--ticket PROJ-123]\n```\n\nParsing rules:\n\n- Keep every non-flag token in its original order; the joined result is the required `task_description`. Remove each recognized flag, and the value token that belongs to it, from that text.\n- `--mode <value>` accepts exactly `technical`, `design`, `discovery`, or `general`. When `--mode` is omitted, the selected mode is `technical`.\n- `--debate` is a valueless boolean flag. It takes no following token.\n- `--lenses <a,b>` takes one comma-separated value. Split it on commas and keep the non-empty entries as the `lenses` array.\n- `--ticket <KEY>` captures the immediately following token as the ticket key.\n- A missing value for `--mode`, `--lenses`, or `--ticket` — including a value position occupied by another recognized flag — is a validation failure. Never let the next flag become a flag's value.\n\nValidation must finish before any MCP tool call. Stop immediately, display the usage response below, and make no tool call when `$ARGUMENTS` is empty, when it contains only flags, when a flag that needs a value has none, or when `--mode` is given an unsupported value:\n\n```text\nUsage: /council <task description> [--mode technical|design|discovery|general] [--debate] [--lenses a,b] [--ticket PROJ-123]\nExample: /council \"How should we add rate limiting to the LLM client?\" --mode technical\n```\n\n## Step 2 — Resolve Docs Directory\n\nCall the `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that path as `docs_dir`. This is context only — do not slugify it, predict a filename from it, or otherwise construct a report path yourself.\n\n## Step 3 — Convene the Council\n\nBefore calling the tool, tell the user calmly what to expect:\n\n```text\nConvening the council. This commonly takes around 15 minutes, and may continue in the background if the client deadline expires.\n```\n\nThen call `request_council` with:\n\n- `task_description`: the parsed task text\n- `mode`: the selected mode\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `debate`: `true` — include this parameter **only** when `--debate` was supplied\n- `lenses`: the parsed array — include this parameter **only** when `--lenses` was supplied\n- `ticket_number`: the captured key — include this parameter **only** when `--ticket` was supplied\n\nOmit an optional parameter entirely rather than sending a placeholder: never send `debate` with a false value, never send an empty `lenses` array, and never send an empty `ticket_number` string. Do not send any other parameter — no `providers`, no `concerns`, no prior `
|
|
6
|
+
"council.md": "Convene a multi-perspective council on a task via Bridge API and save the resulting report locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 — Parse Arguments\n\nParse `$ARGUMENTS`. The supported invocation is exactly:\n\n```text\n/council <task description> [--mode technical|design|discovery|general] [--debate] [--lenses a,b] [--ticket PROJ-123]\n```\n\nParsing rules:\n\n- Keep every non-flag token in its original order; the joined result is the required `task_description`. Remove each recognized flag, and the value token that belongs to it, from that text.\n- `--mode <value>` accepts exactly `technical`, `design`, `discovery`, or `general`. When `--mode` is omitted, the selected mode is `technical`.\n- `--debate` is a valueless boolean flag. It takes no following token.\n- `--lenses <a,b>` takes one comma-separated value. Split it on commas and keep the non-empty entries as the `lenses` array.\n- `--ticket <KEY>` captures the immediately following token as the ticket key.\n- A missing value for `--mode`, `--lenses`, or `--ticket` — including a value position occupied by another recognized flag — is a validation failure. Never let the next flag become a flag's value.\n\nValidation must finish before any MCP tool call. Stop immediately, display the usage response below, and make no tool call when `$ARGUMENTS` is empty, when it contains only flags, when a flag that needs a value has none, or when `--mode` is given an unsupported value:\n\n```text\nUsage: /council <task description> [--mode technical|design|discovery|general] [--debate] [--lenses a,b] [--ticket PROJ-123]\nExample: /council \"How should we add rate limiting to the LLM client?\" --mode technical\n```\n\n## Step 2 — Resolve Docs Directory\n\nCall the `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that path as `docs_dir`. This is context only — do not slugify it, predict a filename from it, or otherwise construct a report path yourself.\n\n## Step 3 — Convene the Council\n\nBefore calling the tool, tell the user calmly what to expect:\n\n```text\nConvening the council. This commonly takes around 15 minutes, and may continue in the background if the client deadline expires.\n```\n\nThen call `request_council` with:\n\n- `task_description`: the parsed task text\n- `mode`: the selected mode\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `debate`: `true` — include this parameter **only** when `--debate` was supplied\n- `lenses`: the parsed array — include this parameter **only** when `--lenses` was supplied\n- `ticket_number`: the captured key — include this parameter **only** when `--ticket` was supplied\n\nOmit an optional parameter entirely rather than sending a placeholder: never send `debate` with a false value, never send an empty `lenses` array, and never send an empty `ticket_number` string. Do not send any other parameter — no `providers`, no `concerns`, no prior `council_id` to refine, and no lens pair of your own. Omitted `lenses` already defaults server-side; do not re-implement that default here.\n\n## Step 4 — Report the Outcome\n\nKeep the report status-first and compact: status, then the next action, then supporting detail such as the saved path, `council_id`, or mode.\n\n**Completed.** The tool appends a `Saved files:` block listing one `- <path>` line per saved report. Collect those lines as `saved_paths`; each entry is a `saved_path` reported by the tool. Display them before any optional task, mode, or `docs_dir` context, and never invent or predict a filename:\n\n```text\nCouncil complete.\nSaved to: {saved_path}\n```\n\n**Backgrounded.** A response that exceeded the client deadline but carries a `council_id` is a successful submission, not a failure. Do not display \"failed\", an error banner, or unrecoverable-error wording for it. Display the exact returned id and the recovery action:\n\n```text\nCouncil submitted and still running in the background.\nRetrieve it with `get_council` using {\"council_id\": \"<the exact id returned>\", \"save_locally\": true}.\n```\n\n**Not indexed.** When a `technical` or `discovery` request reports that the repository is not indexed, say so and name the workaround — those two modes are codebase-grounded and need an indexed repository, while `general` needs no index:\n\n```text\nThis repository is not indexed, and {mode} mode needs an indexed repository.\nRerun the same task with `--mode general`.\n```\n\n**Failed.** A tool error that carries no `council_id` is a genuine failure. Surface the tool's own actionable message, stop, and do not invent a retrieval handle:\n\n```text\nCouncil failed: <error message from the tool>\n```\n\n## Final Summary\n\nDisplay a summary block:\n\n```text\n## Council Report\n\n- **Saved to**: {saved_path}\n- **Task**: <task_description>\n- **Mode**: <selected mode>\n- **Status**: Completed\n```\n\nFor a backgrounded council, replace the saved-path line with the returned `council_id` and the `get_council` recovery action, and set the status to `Submitted — running in the background`.\n",
|
|
7
7
|
"create-doc.md": "Generate a design document (TDD, FSD, or PRD) for a Jira ticket, wait for the result, and save it locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 — Parse Arguments\n\n1. **Parse `$ARGUMENTS`**: Extract a required `ticket_key`, a required `--doc-type` flag, an optional `--second-opinion` flag, and an optional `--provider` flag.\n - Split `$ARGUMENTS` on whitespace.\n - If `--doc-type` appears followed by one of `tdd`, `fsd`, or `prd`, capture that as `doc_type`.\n - If `--doc-type` is absent, or is followed by anything other than `tdd`/`fsd`/`prd` (or is the last token), stop immediately and report: \"Usage error: --doc-type requires a document type (tdd, fsd, or prd).\"\n - If `--second-opinion` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `second_opinion_value`.\n - If `--second-opinion` appears without a provider name following it (or is the last token), set `second_opinion_value = \"auto\"`.\n - If `--second-opinion` is absent, set `second_opinion_value = null`.\n - If `--provider` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `provider_value`.\n - If `--provider` appears without a valid provider name following it (or is the last token), stop immediately and report: \"Usage error: --provider requires a provider name (openai, anthropic, or gemini).\"\n - If `--provider` is absent, set `provider_value = null`.\n - If both `--second-opinion` and `--provider` are present, `--second-opinion` takes precedence (set `provider_value = null`).\n - The remaining token (after removing flags and their arguments) is the `ticket_key`.\n\n2. **Validate ticket key format**: Confirm the ticket key matches the Jira key pattern `[A-Za-z][A-Za-z0-9]+-\\d+`. If it does not match (or `ticket_key` is empty or missing), stop immediately and display:\n\n ```\n Usage: /create-doc <ticket_key> --doc-type <tdd|fsd|prd> [--second-opinion [provider]] [--provider <name>] (e.g., /create-doc BAPI-150 --doc-type fsd)\n ```\n\n## Step 2 — Resolve Docs Directory\n\nCall the `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that path as `docs_dir`.\n\n## Step 3 — Generate Design Document\n\nCall the `create_doc` MCP tool with:\n- `ticket_number`: the validated `ticket_key`\n- `doc_type`: the parsed `doc_type` (`tdd`, `fsd`, or `prd`)\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `second_opinion`: set to `second_opinion_value` if non-null; omit entirely if null\n- `provider`: set to `provider_value` if non-null; omit entirely if null\n\nThis step may take 2-4 minutes while the backend processes the document.\n\nIf the tool returns an error, stop immediately and display:\n\n```\nDesign document generation failed: <error message from the tool>\n```\n\nIf generation did not finish, the document can be retrieved later with the `get_doc` MCP tool using the same `ticket_number` and `doc_type`.\n\n## Step 4 — Confirm Success\n\nResolve the local file path from `doc_type`:\n- `tdd` → `{docs_dir}/architecture/<ticket_key>-architecture-plan.md`\n- `fsd` → `{docs_dir}/fsd/<ticket_key>-fsd-plan.md`\n- `prd` → `{docs_dir}/prd/<ticket_key>-prd-plan.md`\n\nDisplay a confirmation message:\n\n```\nDesign document generated successfully for <ticket_key>\nSaved to: <local file path>\n```\n\n## Final Summary\n\nDisplay a summary block:\n\n```\n## Design Document Report\n\n- **Ticket**: <ticket_key>\n- **Doc Type**: <doc_type>\n- **Status**: Generated successfully\n- **Local File**: <local file path>\n```\n\nOn failure at any step, stop immediately, display which step failed and the error details, and do not proceed.\n",
|
|
8
8
|
"decision-page.md": "Turn open decisions from this conversation into an interactive HTML decision page, then fold the answers back in.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\n`$ARGUMENTS` is a free-form description of what needs deciding — a topic (\"how we handle rate limiting\"), a list of specific questions, or empty. It is **not** a Jira ticket key.\n\nThis command exists so a decision page can be reached in ordinary conversation, without running a larger automation. A decision page frames each open choice as a card — the question, why it matters, 2-4 concrete options with the consequence of each, and a recommendation — and renders it as a local HTML page the user submits from their browser. The submitted JSON comes back to you and the decisions become settled.\n\nUse it whenever a conversation has accumulated more open choices than are comfortable to settle in prose. Do not use it to ask one simple question — ask that directly.\n\nRun every stage in the main conversation so the user sees the framing as it happens. If a stage fails, say which one and why.\n\n## Stage 1 — Frame the decisions\n\n1. **Gather the candidates.** Take the decisions named in `$ARGUMENTS` plus any open choice raised earlier in this conversation and not yet settled. If `$ARGUMENTS` is empty, use the conversation alone. If you find nothing genuinely open, say so and stop — do not manufacture cards to fill a page.\n\n2. **Write one card per decision.** Each card needs:\n - `id`: a short stable id, e.g. `D-1`, `D-2`. Ids must be unique — a duplicate is rejected, because the id is the key the user's answer is reported under.\n - `question`: the decision itself, phrased as a question.\n - `options`: 2-4 concrete option labels. Do **not** include \"None of these\" or \"Ask about this\" — the renderer appends both automatically, and passing \"None of these\" yourself is rejected.\n - `option_consequences`: one consequence per option, **parallel to and the same length as** `options`. Say what actually follows from choosing it, not a restatement of the label.\n - `why_it_matters`: the concrete impact of getting this wrong.\n - `recommendation_explanation`: why the recommended option is best.\n - `recommendation_index`: the 0-based index of the recommended option, within range of `options`.\n - `codebase_evidence` (optional): your assessment plus `file:line` citations, shown collapsed behind a disclosure.\n\n Give a real recommendation on every card. If one option is obviously right, still supply the strongest alternative as a second option so the user can see what they are ruling out.\n\n3. **Show the list and let the user correct it.** Present the questions and options in chat before rendering anything. The user may add a decision you missed, drop one that is already settled, or reject your framing of a question. Apply their corrections, then proceed. This check is cheap; a page built on the wrong questions is not.\n\n## Stage 2 — Render the page\n\n1. **Pick a slug.** Derive a kebab-case slug from the topic — a few meaningful words, lowercase, non-alphanumerics stripped, at most 60 characters. It **must** match `/^[A-Za-z][A-Za-z0-9_-]*$/`; if it would start with a digit or hyphen, prefix it with `decisions-`. This slug is the `ticket_key`, which accepts any such slug and does not have to be a Jira key.\n\n2. **Call `generate_decision_page`** with the routing fields at the root and everything else nested under `content`. **The nesting is required** — `actionable_items`, `system_goals`, `clear_improvements`, and `implementation_order` passed at the root are silently dropped by the tool's lean input schema, and a call with no `content` at all is rejected.\n - `ticket_key`: the slug.\n - `artifact_type`: `review_decisions` (the default).\n - `output_subdir`: `decisions`.\n - `output_filename`: `{slug}-decisions.html`.\n - `labels`: optional presentation overrides — `title`, `intro`, `section_heading`. Set a `title` that names the topic, and an `intro` that says what agreeing to these choices commits the user to.\n - `content`: an object holding `actionable_items`.\n\n ```typescript\n interface DecisionPageContent {\n actionable_items: Array<{\n id: string; // e.g. \"D-1\"; must be unique\n question: string;\n why_it_matters: string;\n recommendation_explanation: string;\n options: string[]; // 2-4 labels (no \"None of these\" / \"Ask about this\")\n option_consequences: string[]; // same length as options\n recommendation_index: number; // 0-based within options\n codebase_evidence?: string; // optional: assessment + file:line citations\n }>;\n }\n ```\n\n Example call:\n ```json\n {\n \"ticket_key\": \"rate-limiting\",\n \"artifact_type\": \"review_decisions\",\n \"output_subdir\": \"decisions\",\n \"output_filename\": \"rate-limiting-decisions.html\",\n \"labels\": { \"title\": \"Rate Limiting Decisions\", \"section_heading\": \"Open Decisions\" },\n \"content\": {\n \"actionable_items\": [\n {\n \"id\": \"D-1\",\n \"question\": \"Where should the limit be enforced?\",\n \"why_it_matters\": \"Determines whether a burst is rejected before or after it reaches the database.\",\n \"recommendation_explanation\": \"Middleware keeps the limit in one place and protects every route without per-handler work.\",\n \"options\": [\"In middleware\", \"Per handler\"],\n \"option_consequences\": [\"One place to change; blunt for routes that need different budgets.\", \"Precise per route; every new route must remember to opt in.\"],\n \"recommendation_index\": 0,\n \"codebase_evidence\": \"api/routes/__init__.py:41 already composes shared dependencies for every router.\"\n }\n ]\n }\n }\n ```\n\n3. **When the decisions come with framing worth showing**, use `artifact_type: \"pre_ticket_planning\"` instead and add a `system_goals` object inside `content` (`business_goal`, `desired_end_state`, `system_behavior`, and optionally `acceptance_criteria` and `nfrs`). Those render read-only above the cards, each with its own agree / ask / disagree control. Use this when the user needs to see the goal the decisions serve in order to answer them; the plain `review_decisions` page is the right default otherwise.\n\n4. **Handle the response `status`:**\n - `decision_page_generated`: surface the returned `file_path` and go to Stage 3.\n - `no_decisions_needed`: no page was written because there was nothing to render. Tell the user, and do not proceed to Stage 3.\n - `VALIDATION_ERROR`: the message names the field and restates the expected shape. Fix the payload and retry once. If it fails again, report the message verbatim rather than guessing further.\n\nIf the tool fails outright, **output a highly visible warning** (e.g. **⚠ WARNING: The decision page could not be generated** in bold) and fall back to settling the decisions in chat, one at a time. Do not continue silently — the failure must be visible in your output.\n\n## Stage 3 — Capture the answers (stop and wait)\n\n1. **Direct the user to the page.** Give them the `file_path` and tell them to open it in their browser. Explain that they can accept a recommendation, pick another option, reject them all, or flag a card for discussion, and that they can ask you questions in chat before submitting.\n\n2. **Treat each message as a commit or a discussion turn.**\n - **Commit:** trim the message and try to parse the whole trimmed message as JSON. Treat it as a commit only when the result is an object with all three top-level fields: `ticket_key` (string), `decisions` (object), and `general_comment` (string). The first valid commit-shaped paste commits — do not over-validate the individual cards.\n - **Discussion:** anything else. Answer it, then keep waiting. If a JSON-shaped paste is missing one of the three fields, say which one rather than treating it as a freeform question.\n - **In-flight overrides:** if the user changes an answer in chat (\"go with per-handler for D-1\"), record it as an override. On commit, the submitted JSON is the baseline and your recorded overrides win; acknowledge each overridden card in one line.\n\n3. **Resolve every \"ask\" (hard rule).** After accepting a commit, find every item in `decisions` where `choice === \"ask\"`. For each, present the evidence and keep discussing until the user gives an explicit answer. Do not proceed while any `ask` is unresolved, and do not honor \"just skip those\" — an unanswered card is an unmade decision.\n\n4. **Handle \"None of these\".** A `choice` of `\"none\"` means every option you offered was wrong. Ask what the user would do instead and record their answer as the decision. Do not re-render the page for this.\n\n**You MUST stop and wait for the user here.** Do not assume answers, do not proceed on the recommendations, and do not move to Stage 4 until the user commits or explicitly declines. If they decline, say the decisions are unsettled and stop.\n\n## Stage 4 — Fold the answers back\n\n1. **Review the wider implications, then gate on a decision.** Build the review from the complete settled set: the submitted `decisions`, any in-flight overrides recorded during the conversation (these take precedence over the submission), every `\"none\"` answer together with the reason given for it, `general_comment`, and — where this surface tracks acceptance-criterion or NFR stances — those stances too. Do not start the review until every `ask` has an explicit recorded resolution and every in-flight override has been applied.\n\n Consider three fixed categories, regardless of whether a decision was framed as technical, user-facing, or business-oriented:\n - **Program / application** — architecture, code paths, operability, maintenance burden, and requirements imposed on other parts of the software.\n - **User** — end users, new users performing setup, operators, and developers, including prerequisites, setup friction, and additional steps.\n - **Business** — cost, adoption, support load, compliance, and reversibility.\n\n Emit only the categories with material second-order implications. For each included category, write at most four one-line bullets of about 25 words, each naming who or what is affected and how — never a restatement of the selected decision. Close with a line naming every considered category that was omitted, e.g. `Considered, nothing material: business.` — omit this closing line only when all three categories have material implications.\n\n If the review cannot be produced, report that in one line and continue without stalling the workflow or presenting the gate below.\n\n This review stays in chat: there is no document for this command to update.\n\n Then present the gate, verbatim: `Implications reviewed. Proceed, or name a decision to revisit.` Accept only a normalized `proceed`, `yes`, `y`, or `go` as a continuation token. Any other response names a decision to reopen: re-settle it in chat, record the new override, rerun the entire implications review against the changed settled set, and present the gate again.\n\n Literal `auto_approve = true` emits the review but skips this gate entirely; a missing or non-true `auto_approve` value follows the human-in-the-loop path above.\n\n2. **Restate every decision as settled**, in a short list: the question, the chosen answer, and — where the choice went against your recommendation or came from an override — one line on what changes as a result.\n\n3. **Carry `general_comment` as overarching guidance.** It applies across all the decisions, not to any one card. Say plainly how it changes the picture.\n\n4. **Name what these decisions now constrain.** One or two sentences on what is now fixed for the rest of the conversation. From here on, treat the settled answers as the contract — if later work would contradict one, say so and ask rather than quietly re-deciding.\n\nThere is no document to rewrite. The conversation is where the decisions live, unless the user asks you to record them somewhere.\n",
|
|
9
9
|
"estimate-epic.md": "Estimate an entire Jira Epic or an explicit ticket-key group via the shared epic estimation orchestrator.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\n`$ARGUMENTS` is either a Jira Epic key (e.g. `BAPI-518`) or an explicit `--tickets` key list — never both. This command calls the `estimate_epic` MCP tool, which delegates to the Bridge API epic estimation orchestrator, and renders the structured result.\n\nIf any step fails, stop immediately and report which step failed and why, preserving the user's originally entered epic key or ticket list in the report.\n\n## Step 1 — Parse Arguments\n\n1. **Parse `$ARGUMENTS`**: Extract exactly one key-source input, plus an optional `--allow-partial` flag.\n - Split `$ARGUMENTS` on whitespace.\n - If `--tickets` appears, every token after it (up to the next flag or end of input) is the explicit ticket-key list — this is the `ticket_keys` mode.\n - Otherwise, the first token matching the Jira key pattern (`[A-Z][A-Z0-9]+-\\d+`) is the `epic_key` — this is the epic mode.\n - `--allow-partial` may appear anywhere; if present, set `allow_partial_value = true`. If absent, omit `allow_partial` entirely (do not pass `false`).\n - Never resolve both an `epic_key` and a `ticket_keys` list from the same invocation — usage is one mode or the other.\n\n2. **Validate input**:\n - Usage forms: `/estimate-epic EPIC-KEY` or `/estimate-epic --tickets KEY-1 KEY-2 ...`, plus optional `--allow-partial`.\n - If neither an `epic_key` nor a `--tickets` list can be resolved, stop immediately and report:\n ```\n Usage: /estimate-epic EPIC-KEY [--allow-partial]\n /estimate-epic --tickets KEY-1 KEY-2 ... [--allow-partial]\n ```\n - If `--tickets` is present but followed by zero keys, stop immediately and report: \"`--tickets` requires at least one ticket key.\"\n - Do not invent or pass a `mode` parameter — there isn't one; the tool infers the source from whichever of `epic_key`/`ticket_keys` is supplied.\n\n## Step 2 — Call the Tool\n\nCall the `estimate_epic` MCP tool with:\n- `epic_key`: the resolved epic key — **only** when in epic mode. Omit entirely in ticket-key mode.\n- `ticket_keys`: the resolved ticket-key list — **only** when in ticket-key mode. Omit entirely in epic mode.\n- `allow_partial`: `allow_partial_value` if `--allow-partial` was passed; omit entirely otherwise (never pass `null`, an empty string, or an empty array for any absent field).\n\nNever pass both `epic_key` and `ticket_keys` in the same call.\n\nIf the tool returns an error envelope (a JSON object with an `error` field), stop and report the error message, preserving the epic key or ticket list the user originally entered.\n\n## Step 3 — Render the Result\n\nRender the successful result as a structured report — do not dump raw JSON by default:\n\n1. **Top**: the final estimate and its scale label (`estimate_label`) as the primary heading — this is the strongest element of the report.\n2. **Immediately after the summary**: `math_source`.\n3. **Next**: resolved child ticket keys (`child_ticket_keys`) and the per-child breakdown, presented compactly.\n4. **Only if non-empty**: a compact warning section listing `failed_child_keys` and `skipped_child_keys`.\n\nKeep the happy-path report concise and scannable. Use backticks for Jira keys and technical identifiers (e.g. `BAPI-518`).\n\n> Note: this tool does not accept a `recreate` parameter — the underlying epic estimation orchestrator (BAPI-522) always reuses cached child estimates and has no recreate knob to forward to.\n\n## Final Report\n\nOn successful completion, display a structured summary per Step 3 above. On failure, display the error message returned by the tool (or the usage error from Step 1), preserving the user's originally entered epic key or ticket list.\n",
|
|
@@ -13,19 +13,19 @@ export const COMMANDS = {
|
|
|
13
13
|
"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\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` — only when `--auto` was passed; otherwise omit this field entirely.\n\n If the tool returns an error, stop and report the failure.\n\n Retain the **complete** JSON response — every step, plus `total_steps`, `auto_approve`, and `execution_mode`. `execution_mode` is `\"inline\"` here: you are the executor, there is no server-side orchestrator, and each step's instruction branches on that value.\n\n3. Read and strictly obey the `agent_instructions` field in the response. Execute **every** step the response returned, in its resolved order, announcing each as **Step N of M: <description>** immediately before executing it. Traverse the whole array — never a fixed or remembered subset, which would silently omit the later steps.\n\n **A phase's durable-recording tool call is not the end of the command.** The `record_phase_result` and `record_checkpoint` tools return a success envelope; that envelope means the *current* `agent_task` step finished, nothing more. Continue immediately with the next step in the same turn. This applies in particular at the 3 → 4, 5 → 6, and 8 → 9 boundaries, which are where the recipe previously stopped: those instruction files used to end in a text envelope addressed to an orchestrator that does not exist inline, so the turn ended there and the run stalled with no error. None of these boundaries introduces an approval pause.\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 **Branch**: <selected branch>\n **PR**: <pull request URL>\n **last_commit_sha**: <latest pushed head SHA>\n **Verification**: <each bounded command run, with its observed outcome>\n **Correction commit**: <subject of the correction commit pushed after verification, or \"None\">\n **Unresolved findings**: <findings reported but not fixed, or \"None\">\n ```\n\n Rules for this summary:\n\n - **`last_commit_sha` is the latest *pushed* head**, not the commit that opened\n the PR. The pipeline opens the pull request before running its bounded\n verification, so a correction pushed afterwards moves the head — report the\n head as it stands after the final push.\n - **`Status` describes pipeline execution, not a merge verdict.** \"Success\" means\n the recipe's steps ran to completion; it does not mean CI passed, that the code\n review approved the change, or that the ticket is mergeable.\n - **Never self-declare a gate outcome.** Do not label the summary with claims such\n as \"CI passed\", \"checks green\", \"review approved\", or \"gate met\". Report the\n exact check and review states you observed instead — the `ci` and `code_review`\n gates are authoritative and the reconciler observes them independently.\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 — 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 — 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",
|
|
14
14
|
"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: **8**. 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 has three visibly labeled outcomes, chosen by the project's state (Stage 2 decides them\nin a fixed order from the manifest), never by the caller's role:\n\n- **JOIN MODE** (`configured == true`): the project is already set up, so this run proposes and\n applies ZERO configuration changes for ANY caller. A new teammate — including a non-admin \"member\"\n key — gets a graceful welcome and the concise capability report instead of an error. An eligible\n b2b admin is additionally offered the teammate-invite stage (Stage 11).\n- **GREENFIELD REPORT-ONLY** (the manifest's `greenfield_setup_path` field is set): this project\n already completed the greenfield setup path, so this run asks nothing and writes nothing — it\n reports and stops. See Stage 2.\n- **CLASSIFY** (neither of the above): Stage 2b decides whether this is a greenfield or an\n established repository. Established runs the original, unchanged derive → approve → apply → report\n flow (Stages 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10). Greenfield hands off to the packaged\n `greenfield-setup` recipe in this same session and then resumes reporting.\n\nThe stale-command rule and the server-authoritative `next_step` behavior are unchanged by all three:\na manifest version higher than this file's still warns, and Stage 8 still renders only what the\nserver put in `next_step.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 **concise capability report** derived from a fresh read-after-write manifest\nread. The server owns all skip-if-set, conflict, and confirmation semantics — this command never makes\nits own skip-if-set decisions — and the server owns the complete tool catalog and the bounded concise\nprojection over it, their grouping and ordering, and every gate and dependency relationship; this\ncommand formats the server's contract and never recomputes it from prose. Indexing is never a decision\nthis command makes or asks about: it starts automatically, gated entirely by server-side readiness (see\nStage 8).\n\nThis command is portable across Claude Code, Cursor, and Copilot: it only drives the Bridge API MCP\ntools (`ping`, `get_install_manifest`, `apply_install_manifest`, and — in the gated Stage 11\nonly — `invite_member`).\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, Stage 9, and Stage 10 are SKIPPED and the single closing interaction is the concise capability\nreport plus whatever conditional next step the manifest provided — the spawn prompt owns that same\n`next_step.command` rendering there, under the same non-empty rule. When you invoke\n`/install-bridge` directly (manual invocation), Stages 8, 9, and 10 run normally. Stage 11 is NOT part\nof that install-spawn skip set — it is independently gated (admin + b2b + interactive) and best-effort,\nso it may still run in the install-spawn context for an eligible admin.\n\nStage 6 is likewise NOT skipped in the install-spawn context — it still persists the routing\ncredential — but its SUCCESS output is silent there, because the `install-bridge` CLI has already told\nthe user where the credential landed and a second sentence is a duplicate. Silence covers every place\nthat success would otherwise be restated, including the Return contract, and it covers success ONLY: a\nStage 6 failure is still reported loudly and in full. JOIN MODE and manual invocation are outside this\nrule and report Stage 6 success exactly as they do today.\n\n## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the first packaged-CLI command this stage set names, and reuse that one resolved value throughout. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nThe packaged-CLI commands this command *prints for the user to run* (`--init`, `credentials migrate-agent-config`, `doctor`) are written below in their **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. When `BAPI_MCP_CLI` is set in this environment, print those remediation commands with the resolved `<launcher>` in place of that prefix so the advice matches the operator's actual setup. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\n## Stage 1 — Admin preflight (defer the permission decision until the manifest is read)\n\n1. Call the `ping` MCP tool (no parameters) and read `role` and `customer_type` from its first\n (JSON) content item. Retain both — later stages branch on them (Stage 2's mode decision uses\n `role`; Stage 11's invite gate uses `role` and `customer_type`). Both are best-effort and\n nullable: a `null` `role` means \"not determined\", which covers a legacy shared key as well as an\n unconfigured install-join context, and it never fails the ping.\n2. Classify the caller, but do NOT stop here — the member permission decision is DEFERRED until Stage 2\n has read the manifest and determined whether the project is already `configured`. A member must be\n allowed to continue at least far enough to read the manifest, because a member CAN join an\n already-configured project even though a member cannot configure a fresh one:\n - If `role` is `\"admin\"`, or `role` is `null` (not determined — e.g. a legacy shared key): the\n caller is configuration-capable (it may run the fresh-configuration flow when the project is\n unconfigured).\n - Otherwise (an explicit non-admin role such as `\"member\"`): the caller is join-only. It may proceed\n into JOIN MODE for a configured project, but must be refused if Stage 2 proves the project is not\n yet configured (it cannot configure a fresh repo).\n3. Preserve this exact refusal text for later use — it is emitted in Stage 2 ONLY when a non-admin\n member reaches a `configured == false` project:\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 — 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 — 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 — 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`, `tool_capabilities`, `workflows`, `concise_tool_capabilities`, `locked_tools`,\n `unlocked_tools`) — but ignore those here; the accurate capability status is the post-apply read in\n Stage 7. `tool_capabilities` is the COMPLETE catalog-backed report field (one entry per registered\n MCP tool, grouped and ordered by the server); `workflows` is the separate curated collection of\n non-MCP workflows (slash commands and packaged CLI subcommands, which are not registered MCP tools\n and never appear in `tool_capabilities`); `concise_tool_capabilities` is the ADDITIVE projection over\n BOTH of those that Stage 7 actually renders — a server-ordered array of exactly two availability\n sections, each `{id, name, tools}` holding mixed capability items (see Stage 7); `locked_tools` /\n `unlocked_tools` are LEGACY compatibility data covering only the VCS/index policy cases and are NOT\n the tool inventory.\n4. Compare the manifest's `command_contract_version` to this command's contract version (8, 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 — wherever the manifest's `guidance`, `next_step`, or `done_criteria`\n differ from these instructions, follow the manifest.\n5. **Decide the outcome from the manifest, in this exact order.** Evaluate these three branches in\n sequence and take the FIRST one that applies. The order is the contract: a configured project is\n never classified, and a completed greenfield project is never classified or re-onboarded.\n 1. `configured == true` → **JOIN MODE** (below).\n 2. the manifest's `greenfield_setup_path` field reports `is_set == true` → **GREENFIELD\n REPORT-ONLY** (below). Read that field from the ordinary `groups[].fields[]` list, the same way\n you read any other bootstrap field — there is no top-level copy of it. This is the completion\n marker for the greenfield path: the greenfield recipe writes it exactly once and the server\n treats it as skip-if-set, so a set value means \"this project already finished greenfield\n onboarding\".\n 3. neither of the above → **Stage 2b — Classify**.\n\n The branches:\n - **`configured == true` → JOIN MODE, for EVERY caller** (admin, legacy, or member). The project is\n already set up, so this run makes ZERO configuration changes. Emit a concise welcome — for\n example: \"This Bridge project is already configured. You're joining it as a new teammate; no\n configuration changes will be proposed or applied.\" Then SKIP Stages 3, 4, and 5 entirely (no\n field derivation, no project-description approval, no `apply_install_manifest` call, no\n `config_field` writes), run Stage 6 (persist the routing credential), and render the JOIN MODE\n branch of Stage 7 (the concise capability report, drawn directly from THIS Stage-2 manifest — no\n read-after-write). Then EVALUATE Stage 8 conditionally — it renders only when THIS Stage-2\n manifest's `next_step.command` is a non-empty string, and is silent otherwise — and SKIP Stages 9\n and 10. Then continue to the independently gated Stage 11.\n The server has already encoded recommendation eligibility (learned state AND whether this caller\n can run the command) in `next_step.command`, so do NOT reproduce a role gate or a learned-state\n gate here: a joining member simply receives an empty command and sees nothing.\n - **`greenfield_setup_path.is_set == true` → GREENFIELD REPORT-ONLY, for EVERY caller.** This\n project already completed the greenfield setup path, so this run performs NO classification, NO\n field derivation, NO interview, NO standards generation, NO `config_field` write, and NO\n `apply_install_manifest` call. Say so plainly — for example: \"This project already completed\n greenfield setup. Nothing to configure; here is where it stands.\" Then SKIP Stages 2b, 3, 4, and\n 5 entirely, run Stage 6 (persist the routing credential), and render the GREENFIELD REPORT-ONLY\n branch of Stage 7. That report is sourced from THIS Stage-2 manifest and there is deliberately no\n read-after-write call: this branch performs no setup write, so a second read would return the\n same bytes at the cost of a round trip. Then EVALUATE Stage 8 conditionally against THIS same\n Stage-2 manifest's `next_step` — the server decides whether to name `connect-github` or nothing —\n and SKIP Stages 9 and 10. Then continue to the independently gated Stage 11.\n In the install-spawn context the ordinary Stage 8–10 skip set still applies unchanged, and Stage\n 11 remains independently gated there as it is on every other path.\n This branch is reached for a member key too: like JOIN MODE it writes nothing, so the \"Admin role\n required\" refusal does not apply to it.\n - **`configured == false` and `greenfield_setup_path` is NOT set → apply the deferred Stage 1 role\n decision:**\n - `source == \"legacy\"` or `role == \"admin\"`: continue to **Stage 2b — Classify**, which decides\n between the established flow (Stages 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10, exactly as written and\n unchanged) and the greenfield handoff.\n - a non-admin `user_access` \"member\" key: stop immediately and display the exact refusal text\n preserved in Stage 1 (\"Admin role required to apply install configuration…\"). Do not classify,\n derive, apply, or persist anything.\n - **`configured` absent / indeterminate (neither `true` nor `false`) → do NOT treat it as `false`.**\n `configured` comes from a best-effort capability enrichment that can silently omit the key on a\n transient server-side probe failure, so a missing value is \"unknown\", not \"unconfigured\". Re-read\n the manifest ONCE (a fresh `get_install_manifest` call) to try to resolve it, and re-evaluate the\n ordered branches above against that refreshed response — it is the authoritative manifest from\n then on, including its `snapshot_token`, its `greenfield_setup_path` field, and its `next_step`.\n If `configured` is STILL absent:\n - `source == \"legacy\"` or `role == \"admin\"`: proceed, but NOT silently — first tell the user that\n configuration status could not be confirmed and that the run will continue anyway (the server\n owns skip-if-set, so an apply against an already-configured repo is a safe no-op). Then resume\n the ordered evaluation at branch 2: a set `greenfield_setup_path` still means GREENFIELD\n REPORT-ONLY, and only an unset one reaches Stage 2b.\n - a non-admin `user_access` \"member\" key: take the JOIN-MODE-safe path — render the welcome and the\n Stage 7 capability report (no config writes, no Stage 9/10 offers), evaluate Stage 8 under its\n normal non-empty-command rule, and note that configuration status could not be confirmed. Do NOT\n emit the hard \"Admin role required\" STOP: that refusal is reserved for a\n *definitive* `configured == false`, because treating an unknown state as unconfigured would\n re-introduce the very member hard-refusal this flow removes.\n\n## Stage 2b — Classify the repository (greenfield or established)\n\n**Run this stage ONLY when neither prior Stage 2 branch applied** — that is, the project is not\n`configured`, its `greenfield_setup_path` is not set, and the Stage 1 permission rules allow this\ncaller to configure the project (`source == \"legacy\"` or `role == \"admin\"`). JOIN MODE and GREENFIELD\nREPORT-ONLY never reach this stage, and a non-admin member was already refused above.\n\nThis stage decides ONE thing: is this a brand-new project that has no code to learn from yet\n(**greenfield**), or a real codebase (**established**)? It writes nothing.\n\n1. **Gather evidence from the local checkout**, excluding Bridge's own files from every count and from\n the \"is the tree empty\" judgement: `.mcp.json`, `.cursor/mcp.json`, `.claude/commands/`,\n `.cursor/commands/`, `.bridge/`, and the resolved `{docs_dir}` tree. Those exist because Bridge was\n installed, so counting them would make every Bridge-installed empty project look established.\n - **Scaffold evidence** (points toward greenfield): package manifests (`package.json`,\n `pyproject.toml`, `go.mod`, `Gemfile`, `*.csproj`), lockfiles, a README, generated or template\n configuration, an empty or near-empty `src/`, and a framework template's untouched entry point.\n - **Established evidence** (points toward established): substantive implementation files (real\n modules with real logic, not a generator's placeholder), meaningful tests, and a non-trivial local\n `git log`.\n2. **Established evidence dominates.** Any credible established evidence decides the answer, no matter\n how much scaffold evidence sits beside it — a real codebase that also has a `package.json` and a\n README is established. Scaffold evidence only decides the outcome when there is no credible\n established evidence at all.\n3. **Missing Git metadata is neutral.** No `.git` directory, no commits, or no remote is neither\n evidence nor a problem: a brand-new project legitimately has none of them, and so does a directory\n whose history lives elsewhere. Do not report it as an error or a warning, and do not let it push the\n decision on its own.\n4. **Print exactly one primary statement**, in this exact form:\n ```\n Classification: Greenfield — reply \"established\" to correct, or continue.\n ```\n or, for the other result:\n ```\n Classification: Established — reply \"greenfield\" to correct, or continue.\n ```\n Keep that line and its correction token the primary hierarchy. A compact scaffold-versus-established\n evidence summary may follow it — one or two short lines — but only when it genuinely explains the\n decision; never a file listing, a count table, or a diagnostic dump.\n5. **Accept a one-token correction for exactly one response turn.** Accept ONLY the trimmed, lowercased\n opposite token (`established` when you said Greenfield, `greenfield` when you said Established) and\n switch to it. Anything else — an unrelated answer, a blank response, an unavailable response,\n silence — continues with the original classification. Do not re-ask, do not re-explain, and do not\n enter a correction loop.\n6. **Never wait in a headless session.** When no interactive response can be obtained, do not pause for\n the correction turn at all, and classify conservatively: choose **greenfield** ONLY for an empty or\n clearly near-empty tree with no substantive implementation files, no meaningful tests, and no\n non-trivial history. Everything else is established. A wrong \"established\" costs a skippable\n derivation pass; a wrong \"greenfield\" would onboard a real codebase as if it had no code.\n7. **Established → run the existing flow unchanged.** Continue to Stage 3 and proceed exactly as\n written (Stages 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10). This stage changes nothing about derivation,\n approval, or apply behavior for an established repository.\n8. **Greenfield → hand off to the packaged `greenfield-setup` recipe, in this same session.**\n 1. Call the `get_pipeline_recipe` MCP tool with\n `{ \"pipeline\": \"greenfield-setup\", \"variables\": { \"repo_name\": \"<this repository's configured\n name>\" } }`. Pass `repo_name` explicitly — the server auto-supplies `docs_dir` but not\n `repo_name`. Do not pass `auto_approve`: greenfield onboarding asks the human real questions.\n 2. Follow the returned `agent_instructions` and execute EVERY returned step, in order, to\n completion, here in this same agent session — exactly as `/learn-repository` runs its recipe.\n Do not spawn a second agent for it, do not summarize the recipe instead of running it, and do\n not return to Stage 6 partway through.\n 3. The recipe owns the greenfield apply: its final task makes the one `apply_install_manifest` call\n (with the Stage-2 snapshot token) and then performs its own read-after-write\n `get_install_manifest`. **Require that read-after-write manifest as the recipe's final result\n before resuming.** If the recipe cannot provide it, report that the greenfield setup did not\n complete and stop — do not fabricate a report from the stale Stage-2 manifest, and do not\n re-apply.\n 4. Then resume at Stage 6 (persist the routing credential), render the greenfield-completion branch\n of Stage 7 from that read-after-write manifest, evaluate Stage 8 against that same manifest's\n `next_step`, SKIP Stages 9 and 10, and continue to the independently gated Stage 11.\n\n## Stage 3 — Derive values for UNSET bootstrap fields only\n\n**Skip this entire stage in JOIN MODE** (Stage 2 selected JOIN MODE because the manifest reported\n`configured == true`). JOIN MODE derives nothing — it proposes and applies zero configuration for every\ncaller. Run this stage only on the `configured == false` fresh-configuration path.\n\n1. Consider ONLY fields whose `is_set` is `false`. Never re-derive or overwrite a field that is\n already set — 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 — 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 — 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. Do NOT derive `selected_mcp_slugs` or `greenfield_setup_path`, even though both appear as unset\n bootstrap-eligible fields in the manifest. MCP validation manual selection is deferred to\n `/learn-repository`, which derives and confirms it with the codebase already researched. Install\n neither proposes nor applies this field. `greenfield_setup_path` is not derivable at all: it is the\n greenfield path's own completion marker and is written ONLY by the `greenfield-setup` recipe's final\n apply, which records whether the human took the interview or the defaults. Deriving it here would\n mark an established repository as greenfield-complete and permanently suppress its\n `/learn-repository` recommendation.\n\n## Stage 4 — Human approval for confirmation-requiring fields\n\n**Skip this entire stage in JOIN MODE** — there is nothing to derive, so there is nothing to approve.\nRun it only on the `configured == false` fresh-configuration path.\n\n`project_description` is the ONLY confirmation-requiring field install proposes. It carries\n`requires_confirmation: true` in the manifest, so it is never applied on derivation alone — it needs\nexplicit human approval. (`selected_mcp_slugs` also requires confirmation, but install does not\nderive it at all; `/learn-repository` asks for it. See Stage 3 step 7.)\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. Include `project_description` 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 it, omit the field entirely.\n3. 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 `project_description` omitted, and report it\n as \"pending human input\" in the final summary. The other derived fields must still be applied — an\n unapproved description never blocks them.\n\n## Stage 5 — Apply (one call)\n\n**Skip this entire stage in JOIN MODE** — JOIN MODE makes NO `apply_install_manifest` call and writes\nzero fields for every caller. **Skip it in GREENFIELD REPORT-ONLY** for the same reason, and **skip it\nafter a greenfield handoff**: the `greenfield-setup` recipe owns the single final\n`apply_install_manifest` call for that path, made with the Stage-2 snapshot token. Running this stage\nafterwards would be a second apply with a token the recipe already spent. Run it only on the\nestablished fresh-configuration path Stage 2b routed to Stage 3.\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\"`); an approved `project_description` must use the\n `{ \"value\": ..., \"confirmed\": true }` object form from Stage 4. Never include\n `selected_mcp_slugs` in this payload — install does not derive or apply it (Stage 3 step 7).\n2. Do not implement any client-side skip-if-set, conflict, or confirmation logic — 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 — do NOT retry the whole apply just because one field was rejected.\n\n## Stage 6 — Persist the routing credential\n\n**This stage runs on EVERY path** — JOIN MODE persists the routing credential too, so a joining\nteammate's shell-spawned CLI features (`start-tickets`) can resolve the key, and so do GREENFIELD\nREPORT-ONLY and a completed greenfield handoff, which reach this stage the same way. Its secret\nhandling, fail-open behavior, and install-spawn success-silence rule below are identical on all of\nthem — none of the greenfield paths changes any of it.\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→model\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 — 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>`. EXCEPTION — in the install-spawn\n context (and outside JOIN MODE), persist silently: still call the tool, but print nothing on\n success and do not restate the `target` or `path` in any later summary or in the Return contract.\n The CLI already reported the credential destination, so this sentence would be a duplicate. This\n exception is success-only; step 3's failure reporting is unchanged in every context.\n3. On failure, do NOT block the install — 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) — 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 — Summarize the outcome, then present the concise capability report\n\n**JOIN MODE branch (`configured == true`).** Do NOT print an applied count and do NOT fabricate apply\nbuckets — no apply happened. Instead state plainly that the project was already configured and that\nzero configuration changes were proposed or applied (the welcome from Stage 2). Then render the concise\ncapability report described in \"### Read-after-write\" below, with ONE difference: source it directly\nfrom the `concise_tool_capabilities` field of the Stage-2 manifest you already read — do NOT perform a\nread-after-write `get_install_manifest` call, because no write occurred and there is nothing to\nrefresh. Apply the same server-authority rendering rules (server section order, verbatim item copy,\nmalformed/missing fallback) verbatim. ALSO retain that Stage-2 manifest's `next_step` object: it is the\nauthoritative next-step source for this path, because the Stage-2 read is JOIN MODE's only manifest\nread. After the report, continue to Stage 8, which renders that `next_step` conditionally; an eligible\nadmin then proceeds to Stage 11. The rest of this stage (the applied-count line and six-bucket summary)\napplies ONLY to the `configured == false` fresh-configuration path.\n\n**Greenfield-completion branch (Stage 2b classified greenfield and the recipe ran).** Do NOT print an\napplied count and do NOT reproduce the six apply buckets: this command made no apply call — the recipe\ndid. State plainly that greenfield setup completed, and say which path the human took (the interview\nor the defaults) and what it produced: the standards fields written, and the deferred-VCS guide when\none was written. Then render the concise capability report described in \"### Read-after-write\" below,\nsourced EXCLUSIVELY from the read-after-write manifest the recipe returned as its final result — never\nfrom the stale Stage-2 manifest, and never from a fresh read of your own. Retain that same manifest's\n`next_step` object: it is the authoritative next-step source for this path, and Stage 8 renders it.\nNever mention or recommend `/learn-repository` on this path; the server has already decided what the\nnext step is, and on a greenfield project it is never that.\n\n**GREENFIELD REPORT-ONLY branch (`greenfield_setup_path` was already set).** Behave exactly like JOIN\nMODE's no-apply reporting: no applied count, no fabricated apply buckets, no report of onboarding work\nthat did not happen this run. State that this project already completed greenfield setup and that zero\nconfiguration changes were proposed or applied. Then render the concise capability report sourced\nEXCLUSIVELY from the Stage-2 manifest already read — no read-after-write call, because nothing was\nwritten — and retain that manifest's `next_step` for Stage 8.\n\nBoth greenfield branches use the SAME rendering rules as every other branch: the server's section\norder, verbatim item copy, the malformed/missing fallback line, the empty-section handling, the\nnarrow-terminal wrapping rules, and the closing documentation footer. There is one report format, and\nthese branches only change where the manifest came from.\n\n**Fresh-configuration branch (established path, `configured == false`).**\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 — the\ninstall is NOT complete until the apply call reports applied fields. This applied-count line and the\nsix-bucket summary below remain the PRIMARY install result — the capability report that follows is a\nsecondary close, not a replacement for it.\n\nThen summarize the six buckets from the apply response for the user:\n- `applied` — fields written this run.\n- `skipped` — fields already set (left untouched).\n- `conflict` — fields that changed since the manifest was read (re-run /install-bridge to retry).\n- `needs_confirmation` — fields awaiting explicit confirmation (e.g. project_description).\n- `rejected` — 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` — fields intentionally not auto-written by install (owned by /learn-repository or set\n deliberately). `selected_mcp_slugs` belongs here: report it as deferred to `/learn-repository`,\n never as approved, declined, or pending an install-time decision.\n\n### Read-after-write: fetch the concise capability report\n\nAfter the bucket summary, call the `get_install_manifest` MCP tool ONCE MORE. This post-apply read\nreflects the configuration you just wrote (the Stage-2 read was pre-apply and is stale for this\npurpose). This read does not need the snapshot token. Use ONLY this post-write response for the\nreport below.\n\nRetain this post-apply response's `next_step` object as well. On the fresh-configuration path it — not\nthe pre-apply Stage-2 read — is the authoritative source for Stage 8's conditional next step. Neither\nbranch derives a command from `configured`, `learned`, or any role prose: the command is whatever the\nauthoritative manifest returned.\n\nThe response carries `concise_tool_capabilities` — an ADDITIVE projection over the complete\n`tool_capabilities` catalog and the curated `workflows` collection (the response still carries both\nunchanged; this stage simply does not render them). It is a server-ordered array of EXACTLY TWO\navailability sections, in this order:\n\n1. `{\"id\": \"available_now\", \"name\": \"Available now\", \"tools\": [...]}` — what works right now.\n2. `{\"id\": \"needs_setup\", \"name\": \"Needs setup\", \"tools\": [...]}` — what additional setup would unlock.\n\nEach entry in a section's `tools` array is a capability item:\n`{id, kind, display_name, description, how_to_use}`, where `kind` is `\"tool\"` or `\"workflow\"` and\n`how_to_use` may be `null`. Items in the `needs_setup` section carry ONE additional field,\n`availability_text` — the server's plain-language sentence naming what is missing.\n\nServer authority: the server computed this projection's section membership, availability decisions,\nretrieval-pair collapsing, and ordering. Never recompute, re-filter, re-count, re-sort, regroup, infer\navailability, pair a retrieval tool with a primary yourself, invent an item, or re-derive any of it from\n`tool_capabilities`, `workflows`, `docs/mcp-tool-integrations.md`, or any other documentation — render\nexactly what the server sent.\n\nIf the post-write response has no `concise_tool_capabilities` field at all, or it is present but\nmalformed (not the two-section `{id, name, tools}` shape described above), print exactly:\n`No capability status is available yet; configuration can still continue.` and skip the section below\nentirely — never fall back to rendering the complete `tool_capabilities` catalog, the `workflows`\ncollection, or a remembered/hallucinated capability list.\n\nOtherwise, render exactly one section, with this exact heading:\n\n**What Bridge can help with**\n\n- Render the two server sections in the order the server gave them — \"Available now\" first, then\n \"Needs setup\". Use the server's `name` as each sub-heading. Do not reorder, merge, filter, or drop a\n section, and do not add a third one.\n- Use semantic Markdown (a short bold or `###` sub-heading per section, one compact list item per\n capability) with tight vertical spacing — no table, no fixed-width columns, no ASCII box. Long\n descriptions, commands, and URLs must be free to wrap, so the report stays readable in a narrow\n terminal.\n- For each item in **Available now**, render its `display_name`, then its `description`, then its\n `how_to_use` when that value is a non-empty string (omit the invocation line entirely when it is\n `null` or empty). Render NO availability caveat, asterisk, footnote, or status note in this section —\n the server already decided these are available, including any tool that works with less codebase\n context before indexing.\n- For each item in **Needs setup**, render the same three fields, then the server's `availability_text`\n verbatim as emphasized setup guidance on its own line. Do not reword it, prefix it with a warning, or\n substitute setup advice of your own.\n- Render a `kind: \"workflow\"` item EXACTLY like a `kind: \"tool\"` item. `kind` is contract metadata for\n machine consumers, not a visual distinction: no icon, no label, no badge, no separate sub-list, no\n different indentation, and no client branching of any sort on its value.\n- Keep an empty section VISIBLE rather than dropping it. When **Available now** is empty, render the\n heading with one short neutral line such as \"Nothing is available yet.\" When **Needs setup** is empty,\n render the heading with one short line stating that every highlighted capability is ready — for\n example \"Everything highlighted here is ready to use.\"\n- Close the report with this quiet final line, after BOTH sections and regardless of whether either\n `tools` array was empty:\n See the [Bridge MCP server README](https://www.npmjs.com/package/@bridge_gpt/mcp-server) for the\n complete tool documentation.\n\n## Stage 8 — Render the server's next step (only when there is one)\n\n**Skip this entire stage in the install-spawn context** (the spawning instruction identifies it); the\nspawn prompt owns the same conditional closing recommendation there. On direct manual\n`/install-bridge` invocation, run it normally.\n\n**This stage runs in JOIN MODE too** — it is no longer skipped wholesale for joining teammates. What\ngates it is the server's output, not the mode and not the caller's role.\n\nThis stage renders a decision the SERVER already made. The manifest's `next_step.command` encodes both\nwhether the repository still needs the deeper instruction-tier configuration AND whether this caller\ncan actually run the command; a caller who cannot gets an empty command. On a greenfield project the\nserver may name a `connect-github` command instead, or nothing at all. Never re-derive any of that\nfrom `configured`, `learned`, `greenfield_setup_path`, `role`, or `source`, never assemble a\n`connect-github` invocation locally, and never substitute a command you happen to know.\n\n1. Read `next_step` from the AUTHORITATIVE manifest for this path (Stage 7 names it: the post-apply\n read-after-write response on the fresh-configuration path; the read-after-write manifest the\n `greenfield-setup` recipe returned after a greenfield handoff; and the Stage-2 response in JOIN MODE\n and in GREENFIELD REPORT-ONLY, neither of which performs a read-after-write).\n Then branch on `next_step.command` alone:\n - **`next_step.command` is a non-empty string** → render one short heading and the exact command\n the server returned, formatted as Markdown inline code — for example: ``Next step: `<command>` ``\n where `<command>` is that returned string, verbatim. Do not rename it, alias it, expand it into\n a different invocation, or replace it with a locally known command. Do not run it yourself:\n it remains the human's own explicit next invocation.\n - **`next_step.command == \"\"`** (or the key is missing/not a string) → be entirely SILENT. Emit no\n heading, no empty code span, no placeholder or fallback command, no separator or divider, no\n \"nothing to do\" notice, and no generic success or completion claim. The stage simply produces\n nothing and the run continues.\n2. Where you give supporting context for a rendered command, use the manifest's own\n `next_step.post_install_indexing` and `next_step.post_install_scheduler` text as the authoritative\n source rather than inventing a rationale for whatever command the server named. Keep that context\n subordinate to the command itself — it is explanation, not a second call to action.\n3. Do NOT ask about repository indexing in any form. There is no consent question, no\n `parse_repository` tool call, and no `/parse-repository` continuation here — indexing starts\n automatically once the repository reaches full parse readiness (VCS credentials, the Pinecone\n index, `working_in` / `project_description`, and SFCC prerequisites where applicable), via the\n same readiness-gated funnel the GitHub connection-confirm endpoints and the scheduled sweep already\n use. Do not claim indexing has already started — this command has no visibility into that funnel's\n outcome.\n\n## Stage 9 — 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 concise report and the conditional next step the spawn prompt owns remain the sole closing interaction. On direct\nmanual `/install-bridge` invocation, run it normally.\n\n**Also skip this stage in JOIN MODE** (`configured == true`), in **GREENFIELD REPORT-ONLY**, and after\na **greenfield handoff**: none of those paths offers configuration follow-up; they stop after the Stage\n7 capability report and the conditional Stage 8 next step (an eligible admin continues to Stage 11).\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 — 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 — 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** — poll CI results only, never attempt fixes:\n `{\"strategy\": \"poll_only\", \"max_iterations\": 1, \"max_minutes\": 10, \"instructions\": \"\"}`\n - **self-heal** — 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) — leave `ci_followup_config` unset (NULL = baseline poll-only behavior).\n3. Do NOT offer or compose a `custom` strategy during install — 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 — skip and list it as a pending next step. An\n unattended install must never enable fix-and-iterate behavior.\n\n## Stage 10 — Offer the speed-vs-quality repository preference\n\n**Skip this entire stage in the install-spawn context** (the spawning instruction identifies it), so\nthe concise report and the conditional next step the spawn prompt owns remain the sole closing interaction. On direct\nmanual `/install-bridge` invocation, run it normally.\n\n**Also skip this stage in JOIN MODE** (`configured == true`), in **GREENFIELD REPORT-ONLY**, and after\na **greenfield handoff**: none of those paths offers configuration follow-up; they stop after the Stage\n7 capability report and the conditional Stage 8 next step (an eligible admin continues to Stage 11).\n\nThis stage is separate from the single apply call: it writes at most ONE field via the `config_field`\nMCP tool, `field_name: \"speed_vs_quality\"`. The column defaults to `5` (max quality) for every\nrepository, so \"skip\" always leaves a safe, valid value in place.\n\n1. Never ask this question in a non-interactive session — skip and list it as a pending next step. An\n unattended install must never block on or silently answer this preference.\n2. Read the current value first via the `config_field` MCP tool (operation `\"get\"`,\n `field_name: \"speed_vs_quality\"`) so a reinstall can show the stored value — not always `5` — as\n the displayed default rather than silently re-asking from scratch.\n3. Ask ONE question with exactly these five numbered presets (default: the value from step 2, or `5`\n if this is the first install):\n - **1** — Max speed\n - **2** — Prefer speed\n - **3** — Balanced\n - **4** — Prefer quality\n - **5** — Max quality (default)\n4. Persist ONLY on an explicit answer — including an explicitly accepted default — by calling the\n `config_field` MCP tool once (operation `\"update\"`, `field_name: \"speed_vs_quality\"`,\n `value`: the selected integer 1-5). Do not overwrite an existing value when the human gives no\n answer at all (e.g. the session cannot obtain one) — leave the stored/default value untouched in\n that case, distinct from an explicit accepted-default selection of `5`.\n5. After a successful persist, emit one additive structured log line (mirroring the codebase's\n `logging.info(msg, extra={...})` convention for non-response-body observability signals) with\n fields: `event=\"install.speed_vs_quality\"`, `repo_name`, `field_name=\"speed_vs_quality\"`,\n `selected_preset` (the persisted integer), `outcome=\"persisted\"`. Never log this event before the\n `config_field` tool call has confirmed the write.\n6. When skipped, emit the corresponding structured event with `outcome` set to one of\n `\"skipped_non_interactive\"` (non-interactive session) or `\"skipped_no_answer\"` (interactive session,\n no explicit answer obtained) — omit `selected_preset` and any prompt text from this event.\n7. If the `config_field` call is rejected, retry ONCE with a corrected payload; if it is rejected\n again, stop, show the proposed value to the human, and leave the field at its current stored value.\n Do not log a persistence-success event for a rejected or failed write.\n\n## Stage 11 — Invite teammates (gated: b2b admins only; best-effort)\n\nThis is a best-effort final stage that lets an eligible admin mint teammate keys after configuration or\nJOIN MODE. It is **independently gated** and is NOT part of the install-spawn skip set (Stages 8–10) —\nit may run in the install-spawn context for an eligible admin. The entire stage is **fail-open**: a\nprompt failure, a declined offer, a non-interactive context, a malformed tool response, or an\n`invite_member` failure must NEVER cause this command to report the (already-completed) install as\nfailed.\n\n1. **Eligibility gate (AND).** Offer this stage ONLY when BOTH hold, using the values retained in\n Stage 1:\n - `role == \"admin\"`, AND\n - `customer_type == \"b2b\"`.\n Otherwise skip the stage silently with NO prompt: a member, a legacy-source caller whose role is not\n explicitly `\"admin\"`, and a b2c admin all skip. Reachable from EVERY Stage 2 outcome for an eligible\n admin: the established fresh-admin close (after Stage 10), the JOIN MODE path (after the Stage 7\n report), the GREENFIELD REPORT-ONLY path, and a completed greenfield handoff — including an admin\n re-run against an already-configured or already-greenfielded b2b project.\n2. **Interactive surface required.** This stage needs a human response. If no interactive response can\n be obtained (a non-TTY / headless / spawn context that cannot prompt), skip the stage silently\n without changing the completed install result — do not stall.\n3. **Offer prompt.** Ask exactly: `Invite teammates to this project? (y/N)`. Treat a blank answer, `n`,\n `no`, an unavailable response, or any prompt failure as a non-fatal decline — skip the rest of the\n stage and report it as declined.\n4. **Collect invitees.** On an affirmative answer, collect one or more teammate email entries using\n normal **echoed** input (email is PII, not a secret — never use a muted/hidden secret prompt).\n Optionally collect a display name per entry.\n5. **Per-invite role.** Default each invitation to role `member`. Only set a specific request's role to\n `admin` after an explicit per-invite opt-up for that entry; never opt up by default.\n6. **Mint.** For each invitee, call the `invite_member` MCP tool exactly once with `{email, name?,\n role}`. Do NOT pass `repo_name` — the tool resolves the repository from the current session.\n7. **Show each key once.** After each successful call, display that response's plaintext `api_key`\n exactly once, associated with its intended recipient, followed by the exact warning:\n `Distribute securely; this key is shown once.` Do NOT repeat a minted key anywhere else — not in the\n final summary, not in retry guidance, not in diagnostics, not in a later stage.\n8. **Per-invite failure isolation.** Treat each mint failure as local to that invitee: report a\n sanitized failure for it, continue to any remaining invitees, and never change the already-completed\n install outcome. Do not surface raw error text, headers, or the caller's key.\n\n## Return\n\nThe Return contract depends on which outcome Stage 2 selected.\n\n**Fresh-configuration return (established path, `configured == false` admin/legacy).**\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 `project_description` — install's\nonly confirmation-requiring field — (approved / declined / pending human input), the fact that\n`selected_mcp_slugs` is deferred to `/learn-repository` rather than decided here, whether a\nstale-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 — but in the install-spawn\ncontext report neither on success, per Stage 6's silence exception; a failure is still reported), the \"What Bridge can help\nwith\" concise capability report 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), the\nspeed-vs-quality preference outcome (persisted with its preset / skipped_non_interactive /\nskipped_no_answer / pending / skipped in install-spawn context), and the Stage 8 outcome — the exact\nserver-provided next-step command when the manifest returned a non-empty one, otherwise the fact that\nno next step was recommended (never a substituted command of your own).\n\n**JOIN MODE return (`configured == true` path).**\nReport the caller's role, the \"you're joining an already-configured project\" welcome and the explicit\nno-change status (zero configuration proposed or applied — do NOT report an applied count or apply\nbuckets), whether the routing credential was persisted (the returned `target` and `path`, or the\nnon-blocking failure remediation), the \"What Bridge can help with\" concise capability report drawn\nfrom the Stage-2 manifest (no read-after-write), and the Stage 8 outcome — the exact server-provided\nnext-step command when that manifest returned a non-empty one, otherwise the fact that no next step\nwas recommended.\n\n**Greenfield-completion return (Stage 2b classified greenfield and the recipe ran).**\nReport the classification that was printed and whether the human corrected it, the setup path the\nrecipe recorded (`interview` or `defaults`), which standards fields were written and which were left\nalone, whether the deferred-VCS guide was written or the connect command was printed, whether the\nrouting credential was persisted (the returned `target` and `path`, or the non-blocking failure\nremediation — with Stage 6's install-spawn silence exception unchanged), the \"What Bridge can help\nwith\" concise capability report drawn from the recipe's read-after-write manifest, and the Stage 8\noutcome — the exact server-provided next-step command when that manifest returned a non-empty one,\notherwise the fact that no next step was recommended. Do NOT report the ordinary derivation buckets\n(applied / skipped / conflict / needs_confirmation / rejected / deferred): no Stage 5 apply happened on\nthis path, so there are no buckets to report, and do NOT report an applied count. Never mention or\nrecommend `/learn-repository`.\n\n**GREENFIELD REPORT-ONLY return (`greenfield_setup_path` already set).**\nReport that this project already completed greenfield setup and that this run made ZERO setup changes,\nasked ZERO onboarding questions, and applied nothing — no applied count, no apply buckets, no\nderivation buckets. Report whether the routing credential was persisted (same rule as above), the\n\"What Bridge can help with\" concise capability report drawn from the Stage-2 manifest (no\nread-after-write), and the Stage 8 outcome under its usual non-empty-command rule. Never mention or\nrecommend `/learn-repository` on this path either.\n\n**Teammate-invitation outcome (Stage 11, all outcomes).**\nReport the Stage 11 outcome as counts/statuses only — one of offered, declined, skipped (not eligible,\nor non-interactive context), partially completed, or completed, plus how many keys were minted. Never\nrepeat teammate email addresses or minted key values in this summary. Stage 11's eligibility gate and\nits one-time key-secrecy rule are identical on all three Stage 2 outcomes — JOIN MODE, GREENFIELD\nREPORT-ONLY, and CLASSIFY (established or greenfield) — and are unchanged by this contract version.\n",
|
|
15
15
|
"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. There are exactly two narrow exceptions, both invoked directly by this command and never through the recipe: the `ping` admin preflight at entry (immediately below) and the closing `get_install_manifest` capability report (step 6).\n\n## Admin preflight (UX guard — run this first)\n\nBefore the onboarding guidance below and before fetching the recipe, call the `ping` MCP tool\nonce (the first of the two narrow exceptions above) and read `role` from its first (JSON) content item:\n\n- If `role` is `\"admin\"`, or `role` is `null` (not determined — e.g. a legacy shared key): continue to\n the guidance and recipe below.\n- Otherwise (any non-admin `user_access` result — e.g. a \"member\" key): stop immediately, run nothing\n else (no recipe fetch, no research, no configuration write), and display exactly one concise message:\n ```\n Admin role required to learn this repository. Learning writes shared Bridge project configuration,\n which only an admin may change. Ask a project admin to run /learn-repository, or use an admin API key.\n ```\n- If the `ping` call fails or returns a malformed / unrecognized response, treat it as a\n preflight failure: stop with the same admin-required message rather than proceeding into\n configuration writes.\n\nThis preflight is a UX guard ONLY — it fails fast with one clear message instead of the cascade of\nper-field denials a non-admin would otherwise hit. It is NOT the security control: the authoritative\nenforcement is the existing server-side admin gate on the `config_field` update and\n`apply_install_manifest` routes, which already reject non-admin keys regardless of this client-side\ncheck.\n\n1. This command takes no arguments.\n\n2. **Before executing any recipe step**, tell the user what they are about to sit through and why it\n is worth it:\n\n ```\n Learning this repository. This takes a while — the research agents read the actual codebase, and\n all the unlearned fields are researched in parallel, so the wait is roughly the slowest single\n field rather than the sum of all of them. Fields that are already populated are skipped entirely.\n\n What this buys you: these fields are what ground Bridge's agents in THIS codebase. Planning,\n reviewing, and code generation all read them, so they follow your repository's actual\n architecture, testing, documentation, and correctness conventions instead of generic defaults.\n\n It runs unattended — there are no approval prompts during the run. You may be asked one batched\n question at the very end.\n ```\n\n Do not invent a specific number of minutes; the honest statement is the parallel-wait shape above.\n\n3. 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\n4. 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 Retain, for the closing summary: the consolidated research task's structured result (its per-field\n `status`, `character_count`, `condensed`, and `condensation_reason`), each upload step's result,\n and the final confirmation task's structured result. You will need all three — do not discard them\n as you go.\n\n5. After all steps complete, display a summary built from the results you retained:\n\n ```\n ## Learn Complete\n\n **Status**: Success / Completed with gaps / Failed at step N\n\n **Learned and applied**: <fields drafted this run and written to config>\n **Already populated (skipped)**: <fields skipped because they already had a value>\n **Condensed to fit the field limit**: <field — reason it was condensed, per field>\n **Gaps**: <fields whose research or upload failed, each named with its reason>\n **Confirmation**: <approved / applied / declined / pending human input / not applicable, per field>\n\n Review or edit any of these on the **Project Configuration** page, under **Code Writer Settings**\n for the learned instructions and **MCP Validation Manuals** for the manual selection. Bridge's\n agents read whatever is stored there, so correcting a wrong conclusion there changes their\n behavior.\n ```\n\n Rules for the summary:\n\n - A run where some fields failed but others applied is **`Completed with gaps`**, not `Failed`.\n Name every gap explicitly — an unnamed gap is worse than a failed run, because the user believes\n the field was learned.\n - Report confirmation candidates that could not be presented in a headless session with the exact\n phrase `pending human input`.\n - Every field that was condensed must appear with the reason it was condensed.\n\n6. **After** the `## Learn Complete` summary above is fully displayed, close with the same concise\n capability report `/install-bridge` renders (BAPI-658, AC-9). This is the recipe's ONE exception to\n \"do not call MCP tools directly\": call the `get_install_manifest` MCP tool EXACTLY ONCE here,\n directly, with no arguments beyond what it requires — never through the recipe, never a second time,\n and never to apply or change any configuration.\n\n The report is structurally and visually SUBORDINATE to `## Learn Complete` above it — it is a\n closing addendum, not a replacement for or a distraction from the learn summary's own status,\n fields, gaps, and confirmation outcome.\n\n If the `get_install_manifest` call errors, or its response has no `concise_tool_capabilities` field,\n or that field is present but malformed (not the server's two-section `{id, name, tools}` availability\n shape), print exactly this line and stop — do not attempt the report in any other form:\n\n ```\n capability report unavailable — run /install-bridge to see it\n ```\n\n Never substitute documentation, the complete `tool_capabilities` catalog, a remembered tool list, or\n an inferred capability category for a missing or malformed concise field — a hallucinated report\n during this trust-critical first run is worse than no report at all.\n\n Otherwise, render exactly one section, with this exact heading, from `concise_tool_capabilities`\n only:\n\n **What Bridge can help with**\n\n - Render the server's two availability sections in the order it gave them — \"Available now\" first,\n then \"Needs setup\" — using each section's `name` as its sub-heading. Do not reorder, merge, drop a\n section the server included even when its `tools` array is empty, or add a third one.\n - Within a section, render each item's `display_name`, then its `description`, then its `how_to_use`\n when that value is a non-empty string, in server order. Items in \"Needs setup\" additionally carry\n the server's `availability_text`, rendered verbatim as the setup guidance; items in \"Available now\"\n carry no availability caveat at all and must not be given one.\n - Render a `kind: \"workflow\"` item exactly like a `kind: \"tool\"` item. `kind` is contract metadata,\n not a visual distinction — no label, icon, separate list, or client branching on its value.\n - Keep an empty section visible with one short neutral line rather than dropping it, and close the\n report by pointing at the Bridge MCP server README for the complete tool documentation. Name the\n README in prose only — this command never hardcodes a URL.\n - Do not locally filter, count, regroup, sort, infer availability, pair a retrieval tool with a\n primary, fabricate an item, write configuration, or fall back to the complete `tool_capabilities`\n or `workflows` collections for this section under any circumstance.\n",
|
|
16
|
-
"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 — 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 — 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\
|
|
16
|
+
"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 — 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 — 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\nAsk your agent to check progress with the `parse_repository` tool (`action` set to `\"status\"`), 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.\nAsk your agent to check progress with the `parse_repository` tool (`action` set to `\"status\"`).\n```\n\nAfter the parse completes, AI-generated plans and clarifying questions will reflect the latest code changes.\n",
|
|
17
17
|
"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 — 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 — 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 `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. 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 — 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 — 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",
|
|
18
18
|
"plan-ticket.md": "Generate an implementation plan for a Jira ticket, wait for the result, and save it locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 — Parse Arguments\n\n1. **Parse `$ARGUMENTS`**: Extract a required `ticket_key`, an optional `--second-opinion` flag, and an optional `--provider` flag.\n - Split `$ARGUMENTS` on whitespace.\n - If `--second-opinion` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `second_opinion_value`.\n - If `--second-opinion` appears without a provider name following it (or is the last token), set `second_opinion_value = \"auto\"`.\n - If `--second-opinion` is absent, set `second_opinion_value = null`.\n - If `--provider` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `provider_value`.\n - If `--provider` appears without a valid provider name following it (or is the last token), stop immediately and report: \"Usage error: --provider requires a provider name (openai, anthropic, or gemini).\"\n - If `--provider` is absent, set `provider_value = null`.\n - If both `--second-opinion` and `--provider` are present, `--second-opinion` takes precedence (set `provider_value = null`).\n - The remaining token (after removing flags and their arguments) is the `ticket_key`.\n - If `ticket_key` is empty or missing, stop immediately and display:\n\n ```\n Usage: /plan-ticket <ticket_key> [--second-opinion [provider]] [--provider <name>] (e.g., /plan-ticket BAPI-150)\n ```\n\n## Step 2 — Resolve Docs Directory\n\nCall the `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that path as `docs_dir`.\n\n## Step 3 — Generate Plan\n\nCall the `request_plan_generation` MCP tool with:\n- `ticket_number`: the parsed `ticket_key`\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `second_opinion`: set to `second_opinion_value` if non-null; omit entirely if null\n- `provider`: set to `provider_value` if non-null; omit entirely if null\n\nThis step may take 1-5 minutes while the backend processes the plan.\n\nIf the tool returns an error, stop immediately and display:\n\n```\nPlan generation failed: <error message from the tool>\n```\n\n## Step 4 — Confirm Success\n\nDisplay a confirmation message:\n\n```\nPlan generated successfully for <ticket_key>\nSaved to: {docs_dir}/plans/<ticket_key>-plan.md\n```\n\n## Final Summary\n\nDisplay a summary block:\n\n```\n## Plan Generation Report\n\n- **Ticket**: <ticket_key>\n- **Plan Status**: Generated successfully\n- **Local File**: {docs_dir}/plans/<ticket_key>-plan.md\n```\n\nOn failure at any step, stop immediately, display which step failed and the error details, and do not proceed.\n",
|
|
19
19
|
"regression-check.md": "Run the deterministic regression-reviewer (lightweight mode) against a proposed code change and report its blast radius — which real call-sites, tests, mocks, or config the change does and doesn't account for.\n\n$ARGUMENTS\n\n---\n\n<!-- Platform coverage: this command reaches Cursor + Claude Code (the commands\n bundle is scaffolded to .claude/commands/ and .cursor/commands/ by --init).\n The companion `regression-reviewer` agent (agents/src/regression-reviewer.md)\n reaches Claude Code + GitHub Copilot. Union: Cursor, Copilot, and Claude\n Code all get this review, either via the command or the agent. -->\n\n# Instructions\n\nThis is the standalone diff/PR (or ticket-description) review entry point for the regression-reviewer (BAPI-460). It mirrors the `regression-reviewer` agent's orchestration exactly — same subcommand, same parsing, same report — so the same logical review behaves identically whether invoked as a Cursor command or a Claude Code agent.\n\n## Step 1 — Determine the Input Shape\n\nParse `$ARGUMENTS`:\n\n- If it looks like a git diff range, a ref, a PR number, or is empty (defaulting to the working tree vs. `HEAD`), treat this as a **diff/PR invocation**. Resolve a `--diff <range>` value when one is given (e.g. `main...HEAD`, a commit SHA range); omit `--diff` to use the default working-tree-vs-HEAD diff.\n- If it names specific function/class/symbol names (e.g. `--symbols resolve_db_params,SomeClass`, or prose describing a not-yet-diffed planned change), treat this as a **ticket-description invocation**. Extract the symbol names and pass them via `--symbols a,b,c`.\n\nIf neither a diff nor any extractable symbol names are available, stop and ask the user to provide one.\n\n## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the first shell-out below, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\n## Step 2 — Run the Deterministic Core\n\nExecute exactly:\n\n```bash\n<launcher> regression-check --mode lightweight --json [--diff <range> | --symbols a,b,c]\n```\n\nDo NOT hand-roll your own `ast-grep`/`ripgrep` invocations or re-discover call-sites yourself — the subcommand owns that structural analysis.\n\n## Step 3 — Parse the Findings (Fail-Open)\n\nParse the JSON: `summary.symbols_analyzed`, `summary.truncated`, `summary.tools_used`, `summary.degraded_flags`, and the `findings` array (`symbol`, `file`, `definition_location`, `call_sites` (`count`, `by_file`), `broad_mentions`).\n\nIf `summary.degraded_flags` is non-empty (e.g. `ast-grep` or `ripgrep` was unavailable), do NOT treat the run as a failure. Proceed with whatever data IS present and call out each degraded section explicitly — never silently drop a gap. If `summary.truncated` is `true`, state that the symbol set was capped.\n\n**Ripgrep degradation formatting**: if any entry in `summary.degraded_flags` mentions ripgrep, render it with an action-first visual hierarchy of three scannable segments rather than as a plain sentence:\n1. **Status/Problem** — a warning header with a semantic warning indicator (e.g. ⚠️).\n2. **Root Cause** — a brief note that `rg` must be a real binary on `PATH`; a shell function/alias is invisible to the spawned subcommand.\n3. **Remediation** — a standalone, copy-pasteable install command (e.g. `brew install ripgrep`) on its own line.\n\nApply monospace typography (backticks) to every reference to a system command, CLI tool (`rg`), environment term (`PATH`), or installation package, in this warning and throughout the report.\n\n## Step 4 — Synthesize Risk\n\nFor each symbol, compare `call_sites.count` against `broad_mentions.length`:\n- **Accounted for**: every real call-site and broad mention is either already touched by the change or clearly unaffected (e.g. a doc/comment mention).\n- **Not accounted for**: a real call-site, or an uninspected broad mention, sits in a file the proposed change does not touch. Name the specific file.\n\nRank \"not accounted for\" items by how directly they call the changed symbol (a real call-site outranks a textual mention).\n\n## Step 5 — Propose De-Risking (Diagnostic Synthesis, Not a Patch)\n\nFor each \"not accounted for\" item, name ONE of:\n- **Update the affected caller**: point to the exact `file:line` and describe what needs to change there.\n- **Add a compatibility/guard seam**: when updating every caller isn't right (e.g. a public API, a config key read elsewhere), describe the seam needed — not its full implementation.\n\nDo NOT implement the fix. State what needs to happen and where.\n\n## Final Output\n\nPrint this report to chat (no file write required):\n\n```markdown\n# Regression Review: [Symbol(s) / Change Description]\n\n**Mode**: lightweight\n**Input**: [--diff <range> | --symbols a,b,c]\n**Tools used**: [summary.tools_used, joined]\n**Degraded**: [list summary.degraded_flags, or \"none\"]\n**Symbols analyzed**: [summary.symbols_analyzed.length][ — TRUNCATED, capped at N if summary.truncated]\n\n## Summary\n\n[2-3 sentences: overall risk level, how many symbols are fully accounted for vs. not, and any degraded-tool caveats.]\n\n## Systems Accounted For / Not Accounted For\n\nRender as a `.data-table`-style markdown table (bold header row; left-aligned `Symbol` / `File` columns; tight ✅/⚠️ status indicators):\n\n| Symbol | File | Real Call-Sites | Broad Mentions | Status |\n|---|---|---|---|---|\n| `helper` | `src/foo.py` | 3 | 4 | ⚠️ Not accounted for |\n| `caller` | `src/foo.py` | 1 | 1 | ✅ Accounted for |\n\nIf `definition_location` is `null` for a symbol, degrade gracefully — do not leave the `File` column blank. Render a muted `—` placeholder there instead.\n\n## De-Risking Guidance\n\n### 1. [Symbol / File]\n- **Issue**: [what's not accounted for, with file:line]\n- **Recommendation**: Update the affected caller at `file:line` | Add a compatibility/guard seam — [describe]\n\n[Continue for each not-accounted-for item]\n\n---\n\n*Generated by regression-check (lightweight mode). Structural findings via ast-grep + ripgrep; Pinecone semantic search not available.*\n```\n",
|
|
20
20
|
"reimplement-ticket.md": "# Reimplement Ticket: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command retrieves the reimplement context for a previously-implemented Jira ticket via MCP, then implements follow-up changes inline. Use this for small follow-up requests on tickets that have already been through the plan+implement cycle.\n\nIf any critical stage fails (Stage 0 or Stage 1), stop immediately and report which stage failed and why.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline to implement follow-up changes on a Jira ticket using assembled reimplement context. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a required `ticket_key`, an optional `--second-opinion` flag, and an optional `--provider` flag.\n - Split `$ARGUMENTS` on whitespace.\n - If `--second-opinion` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `second_opinion_value`.\n - If `--second-opinion` appears without a provider name following it (or is the last token), set `second_opinion_value = \"auto\"`.\n - If `--second-opinion` is absent, set `second_opinion_value = null`.\n - If `--provider` appears followed by a provider name (one of `openai`, `anthropic`, `gemini`), capture that provider as `provider_value`.\n - If `--provider` appears without a valid provider name following it (or is the last token), stop immediately and report: \"Usage error: --provider requires a provider name (openai, anthropic, or gemini).\"\n - If `--provider` is absent, set `provider_value = null`.\n - If both `--second-opinion` and `--provider` are present, `--second-opinion` takes precedence (set `provider_value = null`).\n - The remaining token (after removing flags and their arguments) is the `ticket_key`.\n - If `ticket_key` is empty or does not match the expected format (one or more uppercase letters, a hyphen, and one or more digits), stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /reimplement-ticket <ticket_key> [--second-opinion [provider]] [--provider <name>] (e.g., /reimplement-ticket BAPI-150)\n ```\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Request and Retrieve Reimplement Context\n\nCall the `request_reimplement_context` MCP tool with:\n- `ticket_number`: the parsed `ticket_key` from Stage 0\n- `wait_for_result`: `true`\n- `save_locally`: `true`\n- `second_opinion`: set to `second_opinion_value` if it is non-null; omit the parameter entirely if `second_opinion_value` is null\n- `provider`: set to `provider_value` if it is non-null; omit the parameter entirely if `provider_value` is null\n\nIf the tool returns an error or 404 persists after polling, stop immediately and display:\n\n```\nFailed to retrieve reimplement context for <ticket_key>.\nThis may mean:\n- The ticket has not been previously processed by Bridge API\n- Background processing failed — check server logs\n- The ticket does not exist in Jira\n\nTry running /plan-ticket <ticket_key> first if this is a new ticket.\n```\n\nOn success, read and internalize the returned context markdown. This document contains:\n- A summary of changes (if applicable)\n- New/changed information since last processing (comments, description changes, attachments)\n- The original ticket description\n- The existing implementation plan (at the bottom, for reference only)\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 2.\n\n## Stage 2 — Implement Follow-Up Changes\n\nExecute changes inline in this conversation. Work directly so the user can see all progress and approve tool calls.\n\nFollow these rules:\n\n1. **Focus on the new information.** The context document identifies what has changed since the last implementation. Focus your changes on addressing the new/changed requirements.\n2. **Reference the existing plan as supplementary guidance only.** The plan at the bottom of the context describes the original implementation, not the follow-up. Use it to understand the existing code structure, not as a step-by-step guide.\n3. **Make code changes** as directed by the new information.\n4. **Run tests and checks** to verify your changes don't break existing functionality.\n5. **Do NOT run `git commit` or `git push`.** Leave all changes uncommitted for developer review.\n6. **Scope guard**: If the follow-up changes are too large in scope (e.g., fundamentally restructuring the original implementation, touching more than 5-6 files, or requiring new infrastructure), stop and ask the user for guidance rather than attempting everything. Follow-up reimplementations should be small and targeted.\n7. **If a change is ambiguous or blocked**, note the issue clearly and continue with the next change rather than halting entirely.\n\nThis stage is **critical** — if a blocking error prevents further progress, stop and report the failure.\n\n## Stage 3 — Final Summary Report\n\nDisplay a structured report after all stages complete:\n\n```\n## Reimplement Complete\n\n**Ticket**: <ticket_key>\n\n**Changes Made**:\n- <brief summary of each change>\n\n**Developer Action Items**:\n- All changes are uncommitted. Review the changes with `git diff` before committing.\n- Run the project's test suite to verify nothing is broken before committing.\n\n**Warnings**:\n<If any issues arose during implementation (scope concerns, ambiguous requirements,\nfiles that couldn't be modified), list them here. If no warnings, omit this section.>\n```\n\n## Final Report\n\nOn success, display the structured report from Stage 3 confirming that the follow-up changes are complete.\n\nOn failure at any critical stage (Stage 0 or Stage 1), display which stage failed and the error details.\n",
|
|
21
21
|
"review-and-implement.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\":\"baseBranch\",\"flag\":\"--base-branch\",\"type\":\"string\"}]}\n---\n\n# Review and Implement: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command runs inside a single already-created worktree (typically spawned by `/start-tickets --workflow review-and-implement`, or its front door `/review-and-start`). It reviews the ticket via `/review-ticket` **in this same session**, pauses at a per-ticket human proceed/halt gate, and — only on approval — hands the ticket off to a **freshly spawned implementation session** (`start-tickets --workflow implement`) that reuses this same worktree. Review and implementation therefore run in **two separate agent sessions/contexts**: the review session ends the moment the fresh implementation session is spawned, and the fresh session reads all reviewed ticket state back from the server rather than inheriting this session's transcript. It creates no *new* worktrees, refreshes no base branch, and does not monitor sibling sessions — those responsibilities stay in the launcher (`start-tickets`) and the parent front-door command.\n\n---\n\n# Instructions\n\n## Argument Parsing\n\nParse `$ARGUMENTS`:\n\n1. **Ticket key**: exactly one required token matching the Jira key pattern (`[A-Z][A-Z0-9]+-\\d+`). If zero tokens match, or more than one token matches, stop immediately and display:\n ```\n Invalid ticket key. Expected exactly one key, e.g. PROJ-123.\n Usage: /review-and-implement <ticket_key> [--auto] [--rounds=1|2] [--base-branch=BRANCH]\n ```\n2. **`--auto`**: an optional position-independent flag. When present, sets chain-level `auto_approve` to `true`. This single flag applies to BOTH the review phase and the implementation phase below — there is no separate review-auto or implementation-auto state.\n3. **`--rounds`**: an optional position-independent `--rounds <n>` / `--rounds=<n>` argument, `1` or `2`. Normalize either form to `--rounds=<n>`. Reject any other value:\n ```\n Invalid --rounds value. Expected: --rounds=1 or --rounds=2.\n Usage: /review-and-implement <ticket_key> [--auto] [--rounds=1|2] [--base-branch=BRANCH]\n ```\n When omitted, forward no `--rounds` to `/review-ticket` (the backend's difficulty-adaptive review policy decides the shape).\n4. **`--base-branch`**: an optional position-independent `--base-branch <branch>` / `--base-branch=<branch>` argument. Validate it using the same rules `/start-tickets` Stage 0 uses: after trimming, non-empty, at most 255 characters, must not start with `-`, and must not contain ASCII control characters (`0x00`–`0x1F` or `0x7F`). Reject a malformed value:\n ```\n Invalid --base-branch value: <reason>\n Usage: /review-and-implement <ticket_key> [--auto] [--rounds=1|2] [--base-branch=BRANCH]\n ```\n\n## Phase 0 — Capture Worktree Identity (before review)\n\nBefore any review work runs, perform the **capture step of the Cleanup Helper** (below): record this ticket worktree's absolute top-level path and current branch, and resolve the main checkout. These captured values are the ONLY inputs the later cleanup and handoff steps use — capturing them up front means a mid-run branch switch cannot redirect a later `git worktree remove`/`git branch -D` at the wrong target.\n\n## Cleanup Helper\n\nA single named procedure invoked by **both** halt paths — review failure (Phase 1) and a declined gate (Phase 2). It never runs on the success path (a successful handoff intentionally keeps the worktree). Perform its steps exactly; it is local to this command's workflow and adds **no** new cleanup CLI flag, launcher subcommand, or MCP surface.\n\n**Capture (run in Phase 0, before `/review-ticket`):**\n\n1. Capture the ticket worktree's **absolute top-level path** with `git rev-parse --show-toplevel` and its **current branch** with `git rev-parse --abbrev-ref HEAD`, both run from inside this worktree. Call these `<capturedPath>` and `<capturedBranch>`.\n2. Resolve the **main checkout** from `git worktree list --porcelain`: parse the `worktree <path>` records exactly, retaining each path verbatim **without splitting on whitespace** (a path may contain spaces), and select the main (non-linked, bare-repo-backed) checkout. **Refuse to treat `<capturedPath>` (the current ticket worktree) as the main checkout** — if the resolution is ambiguous or would resolve to the ticket worktree itself, abort cleanup and report retention instead. Call the result `<mainCheckout>`.\n\n**Remove (run only when a halt path fires):**\n\n3. Confirm `<capturedPath>` is still **registered against `<capturedBranch>`** in `git worktree list --porcelain` before deleting anything. If the path is unregistered, detached, or bound to a different branch than captured, **do not remove** — report retention.\n4. Confirm the ticket worktree is **clean** with `git status --porcelain` (run against `<capturedPath>`). If the output is non-empty (uncommitted or untracked changes), **report and retain** it — never force-remove unexpected local work.\n5. From `<mainCheckout>` (never from inside the ticket worktree), run `git worktree remove <capturedPath>` using shell-safe single-quoting of `<capturedPath>` and **no `--force`**.\n6. Only **after** the worktree removal succeeds, and again from `<mainCheckout>`, run `git branch -D -- <capturedBranch>` (shell-safe quoting). Preserve this remove-then-delete ordering; never delete the branch first.\n7. Report the exact branch and worktree removed (e.g. `Cleaned up worktree <capturedPath> and branch <capturedBranch>`), or, on any failure, a **bounded** cleanup-failure message that names what could not be removed and contains no credential or raw secret material.\n\n## Phase 1 — Review (inline)\n\n### Pre-review tier snapshot\n\nBefore invoking `/review-ticket`, call the `get_ticket` MCP tool with `ticket_number` set to `<ticket_key>` and `include_model_tier` set to `true`, and record a **tier snapshot** from the `tier` field it merges into the response. Retain the value **only** when it is one of the recognized tiers `cheap`, `basic`, or `premium`; treat any tool error, missing/`null` payload, or unrecognized value as an **unresolved** snapshot. A tier-snapshot failure is **non-critical**: do NOT abort — continue into review, and let the later implementation launcher perform its ordinary difficulty-based routing when the snapshot is unresolved. The snapshot exists only to let the fresh implementation session reuse the review-time tier decision (via `--tier`) instead of recomputing it; it is a coarse `cheap|basic|premium` label, never a raw model alias.\n\n### Run review\n\nInvoke `/review-ticket <ticket_key>` **inline, in this same session**, forwarding:\n- `--auto` when chain-level `auto_approve` is `true`.\n- The normalized `--rounds=<n>` when `--rounds` was supplied.\n- `--base-branch=<branch>` when it was supplied.\n\n`/review-ticket` grounds its codebase evaluation against a **fresh `git archive` of `origin/<base>`**, materialized into an isolated temp directory via `fresh_base` with `action: \"materialize\"` (see `docs/BAPI-474-ground-review-against-fresh-base.md`) — **not against this worktree's own working tree** (i.e. fresh-base grounding is not worktree-local). Review therefore behaves identically whether run standalone or, as here, inside a worktree that `start-tickets` already created; the worktree's isolation exists for the implementation phase below, not for review's codebase grounding.\n\nIf the review pipeline itself fails (any step reports `Status: Failed at step N`), halt this ticket immediately — in **both** auto and non-auto mode — before Phase 2 or Phase 3 run. Invoke the **Cleanup Helper** to remove this ticket's worktree and branch, then report:\n```\nReview failed for <ticket_key> at step N. Implementation was not started.\n```\nand stop. No implementation session is spawned.\n\n## Phase 2 — Halt Gate\n\n`/review-ticket` produces **decisions that rewrite the ticket** — it does NOT emit a binary approve/decline verdict token. Do not parse, infer, or synthesize an approve/decline signal from its output; no such token exists to parse.\n\n- **When chain-level `auto_approve` is `true`** and Phase 1 completed successfully: skip this gate entirely and proceed straight to Phase 3.\n- **Otherwise** (non-auto, and Phase 1 completed successfully): after the ticket has been rewritten, ask the user exactly:\n ```\n Proceed to implementation for <ticket_key>? (y/N)\n ```\n Treat an empty response, any negative response (`n`, `no`, or similar), or any ambiguous/unrecognized response as **decline** — do not guess intent. On decline, invoke the **Cleanup Helper** to remove this ticket's worktree and branch, then report:\n ```\n Declined implementation for <ticket_key>. The worktree and branch were cleaned up.\n ```\n and stop. Do not spawn an implementation session.\n- On an explicit affirmative response (`y` or `yes`), proceed to Phase 3.\n\n## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the first shell-out below, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\n## Phase 3 — Implement (fresh session handoff)\n\n**Never invoke `/implement-ticket` inline in this session.** Instead, spawn a fresh implementation session that reuses this worktree, then end this review session.\n\n1. **Resolve the launcher directory.** Run the handoff from the `<mainCheckout>` resolved in Phase 0 — `start-tickets` manages worktrees and must run from the main checkout, never from inside the ticket worktree.\n2. **Construct exactly one launcher command** for this single ticket key, using the `<launcher>` resolved above:\n ```\n <launcher> start-tickets --workflow implement <handoff-flags> <ticket_key>\n ```\n Assemble `<handoff-flags>` conditionally:\n - `--tier=<snapshot>` **only** when the Phase 1 tier snapshot resolved to a valid `cheap|basic|premium` value. When the snapshot is unresolved, **omit `--tier` entirely** so the fresh launcher follows its normal fail-open difficulty routing.\n - `--branch <ticket_key>=<capturedBranch>` **only** when `<capturedBranch>` differs from the launcher default `feature/<ticket_key>` (i.e. an enriched branch such as `feature/<KEY>-<slug>`). This reuses the existing worktree/branch via the launcher's `switch` path instead of cutting a new one; when the branch is already the bare `feature/<ticket_key>`, no override is needed.\n - `--auto` **only** when chain-level `auto_approve` is active.\n - `--base-branch=<value>` **only** when this command received `--base-branch` (Argument Parsing item 4). A fresh terminal does not inherit this session's environment, so re-emitting the flag is the only way the base survives into the implementation launcher — which exports it to the worker as `BAPI_BASE_BRANCH`, and the create-PR step targets it. Escape the value exactly as `/start-tickets` values are escaped: replace every single quote in the branch with `'\\''`, then wrap the complete value in single quotes before inserting it into the launcher command.\n3. **Credential invariant.** `BAPI_API_KEY` and every other credential MUST be absent from this launcher command and its environment assignment — the spawned CLI resolves credentials on its own through `resolveBapiCredentials` / the user-scoped credential store. Never inline a key, authorization header, or credential-store payload into the command string.\n4. **Starter prompt boundary.** The fresh session's starter prompt is only `/implement-ticket <ticket_key>` (plus `--auto` when auto is active) — the launcher builds it. The review transcript, the tier snapshot, the branch metadata, and any review decisions are **excluded** from that prompt; the fresh session reads all reviewed ticket state back from the server.\n5. **On success** (launcher exits `0` and reports the ticket spawned): **end this review session immediately.** Do not monitor, parse, or wait for any output from the fresh implementation session — that session owns the implementation lifecycle from here.\n6. **On a non-zero launcher exit**: treat it as a **handoff failure**. Report the exit code and the launcher's error output, and **retain** the existing clean worktree for a manual retry — do **not** invoke the Cleanup Helper (a failed handoff is not a decline). \n\n## Scope\n\nThis command composes `/review-ticket` (inline) and a fresh `start-tickets --workflow implement` handoff; it owns none of their internals and must not:\n- create, re-cut, or switch a *new* Worktrunk worktree itself (the fresh launcher reuses the existing one),\n- refresh or fetch the launcher's base branch,\n- monitor or coordinate with sibling `/review-and-implement` sessions spawned for other tickets,\n- orchestrate the parent `start-tickets` / `/review-and-start` session,\n- carry the review transcript, tier snapshot, or branch metadata into the fresh implementation session's starter prompt.\n\nA halt (review failure or a declined gate) affects **this ticket only** — it cleans up this ticket's own worktree/branch and never touches sibling worktrees spawned for other keys, which continue independently. The fresh implementation session receives only `/implement-ticket <ticket_key> [--auto]` as its starter prompt and reads all reviewed ticket state back from the server.\n",
|
|
22
|
-
"review-and-start.md": "---\nschedulable: 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\":\"agent\",\"flag\":\"--agent\",\"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# Review and Start: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes one or more Jira ticket keys (e.g., `BAPI-248` or `BAPI-248 BAPI-250` — both flow through the identical ordered `keys: string[]` path) and spawns one refreshed, self-contained Worktrunk worktree per ticket, each running `/review-and-implement <KEY>`: review runs in that spawned session, then a per-ticket human proceed/halt gate, and — only on approval — that session hands the ticket off to a **fresh implementation session** that reuses the same worktree. Review and implementation thus run in two separate agent sessions per ticket, not one shared context. It is the **recommended front door** for chaining \"review these tickets, then implement the ones that pass\" starting from *existing ticket keys* (the entry point `/full-automation` lacks, since its server orchestrator only accepts an idea).\n\nIt is a thin shim over the packaged `start-tickets` CLI, invoked with `--workflow review-and-implement`: this command performs the same connectivity check and branch enrichment as `/start-tickets`, then hands off to the identical worktree/base-refresh/model-routing/credential/cross-platform-spawn/`doctor` engine — reused verbatim. The review→implement halt-gate decision logic lives entirely in the spawned `/review-and-implement` session, never here or in the CLI. Using `start-tickets --workflow review-and-implement` directly (documented in `commands/src/start-tickets.md`) remains available as the lower-level launcher seam this command drives.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline that resolves connectivity and per-ticket branch names, then spawns N parallel Worktrunk worktrees via the packaged CLI's `--workflow review-and-implement` seam. 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 and pass-through flags:\n - **Ticket keys**: every whitespace-separated token matching `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). A single key and multiple keys flow through the identical ordered `keys: string[]` path — there is no separate single-ticket code path. If zero keys are found, stop immediately and display:\n ```\n No ticket keys found in arguments. Expected one or more keys like BAPI-248.\n Usage: /review-and-start [flags] <KEY> [KEY ...] (e.g., /review-and-start BAPI-248 BAPI-250)\n ```\n - **`--auto`**: a single chain-level flag. When present, it is forwarded to `start-tickets --auto`, which threads it into BOTH the review and implementation phases of every spawned `/review-and-implement <KEY>` session.\n - **`--rounds`**: normalize `--rounds 1`/`--rounds=1` and `--rounds 2`/`--rounds=2` to `--rounds=<n>`; reject any other value. This value is forwarded to the review phase of every spawned session.\n - **Selected agent**: validate `--agent <name>` / `--agent=<name>` against `claude`/`cursor-agent` using the same rule as `/start-tickets`; default `claude`.\n - **User-supplied base branch**: validate `--base-branch <branch>` / `--base-branch=<branch>` using the same non-empty/≤255-character/no-leading-dash/no-control-character rules as `/start-tickets` Stage 0. A user-supplied value takes precedence over Stage 2a's `config_field` resolution below.\n - **`--max-parallel N`**: a positive integer; the CLI's own default (3) applies when omitted.\n - **`--dry-run`**: a boolean toggle.\n - Reject malformed input before proceeding: an unsupported flag, a ticket key not matching `[A-Z]+-[0-9]+`, an unsupported `--agent`/`--rounds`/`--base-branch` value — 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 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 `start-tickets` CLI (driven with `--workflow review-and-implement` in Stage 3) owns all platform pre-flight checks, refreshed worktree creation, secret-free credential provisioning, difficulty→model-tier routing, and cross-platform terminal spawning — identical to `/start-tickets`. This command does not duplicate or re-verify any of that; see `commands/src/start-tickets.md` Stage 1 for the full prerequisite/provisioning description it drives. This command does not expose `--conductor`, `--terminal`, or `--no-refresh-main`/`--no-refresh-base` — it stays scoped to the front-door flags listed above. Proceed to Stage 2.\n\n## Stage 2 — Resolve Base Branch + Enrich Branch Names (best-effort)\n\n### Stage 2a — Resolve Base Branch\n\nIdentical to `/start-tickets` Stage 2a: if the user supplied `--base-branch`, use it and skip the `config_field` lookup entirely. Otherwise call `config_field` (`operation: \"get\"`, `field_name: \"base_branch\"`) and treat a non-empty string `value` as the configured base branch; treat `null`, an empty/whitespace-only string, an HTTP `400` response, or any other lookup failure as \"unset\" and omit `--base-branch` from Stage 3 (the CLI then defaults to `main`). When forwarding a resolved value into the Stage 3 Bash invocation, apply the same mandatory single-quote escaping rule as `/start-tickets` (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating it into the command string — never expand it unquoted.\n\n### Stage 2b — Enrich Branch Names\n\nIdentical to `/start-tickets` Stage 2b: for each ticket key without a user-provided `--branch` override, call `get_ticket` (`ticket_number` set to the key, `save_locally: false`), slugify its `summary` (lowercase, collapse runs of `[^a-z0-9]+` to a single `-`, trim leading/trailing dashes, truncate to at most 40 characters preferring a dash boundary), and build `feature/<KEY>-<slug>`. On any per-key failure (404, network error, empty slug), emit a warning and let the CLI apply its default `feature/<KEY>` for that key only; never stop the pipeline. Credentials never reach the Bash-spawned CLI — this enrichment stays in the command, exactly as in `/start-tickets`.\n\nThis stage is **non-critical** — warnings are acceptable; the pipeline continues with the fallback branch for any key that fails enrichment.\n\n## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the Stage 3 shell-out, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\n### Dry-run intended-actions preview\n\nWhen the user passed `--dry-run`, print an intended-actions preview **before** the Stage 3 shell-out. The preview names the resolved launcher explicitly, so an operator can see at a glance whether this run will use the published package or a local build:\n\n```\nIntended actions (dry run — nothing is created)\n Launcher: <the resolved <launcher> value, exactly as it will be escaped into the shell command>\n Command: <launcher> start-tickets --workflow review-and-implement <flags> <ticket-keys>\n Tickets: <the ordered ticket keys>\n```\n\nWhen the override is set, show the **escaped** launcher exactly as it will appear in the Bash command string, so the preview and the command cannot disagree. `--dry-run` is then forwarded to the resolved launcher, which does the previewing of its own actions: **no worktree is created, no agent tab or session is opened, and no worker process is spawned.** Do not state or imply otherwise.\n\n## Stage 3 — Invoke the Packaged CLI\n\nUse the **Bash tool** to invoke exactly **one** CLI invocation covering every requested key — never a per-key loop:\n\n```\n<launcher> start-tickets --workflow review-and-implement <pass-through-flags> <base-branch-flag> <branch-overrides> <ticket-keys>\n```\n\nWhere:\n- `<pass-through-flags>` forwards `--auto` (only if supplied), the normalized `--rounds=<n>` (only if supplied), `--agent <name>` (only if supplied), `--max-parallel N` (only if supplied), and `--dry-run` (only if supplied) verbatim.\n- `<base-branch-flag>` is `--base-branch '<escaped-value>'` only when a value was resolved in Stage 2a; otherwise omitted entirely.\n- `<branch-overrides>` is the list of `--branch KEY=BRANCH` flags assembled in Stage 2b.\n- `<ticket-keys>` is the original ordered list of keys parsed in Stage 0.\n\nCredentials are never placed in this shell command — the same secret-free provisioning path `/start-tickets` uses applies here unchanged.\n\nExample, single key, hands-off:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --workflow review-and-implement --auto --rounds=1 --branch BAPI-248=feature/BAPI-248-add-pr-rating-pre-evaluation-step BAPI-248\n```\n\nExample, multiple keys, manual per-ticket gate (no `--auto`):\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --workflow review-and-implement --branch BAPI-248=feature/BAPI-248-add-pr-rating-pre-evaluation-step --branch BAPI-250=feature/BAPI-250-deep-research-durability BAPI-248 BAPI-250\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: `KEY branch=BRANCH status=STATUS`, optional trailing `path=PATH`) and reformat it as a markdown table, identical in shape to `/start-tickets` Stage 4 (`Ticket | Branch | Status`; statuses `dry-run`, `spawned`, `create-failed`, `spawn-failed`).\n\nThis report describes **worktree/spawn status only**. State explicitly in the report:\n- Each successfully spawned session first runs `/review-ticket` and its own per-ticket halt gate; on successful review and approval (or `--auto`), it then opens a **fresh implementation session** (`/implement-ticket`) that reuses the same worktree. Review and implementation run in two separate agent sessions, not one shared context. This parent session does not observe or report either the review outcome or the later implementation outcome. Never claim or imply that review or implementation has completed.\n- When `--dry-run` was passed, state explicitly that no worktrees were created and no tabs/sessions were opened.\n- As a fixed trade-off of this design: the review session's difficulty-derived model tier is snapshotted and reused (via `--tier`) by the fresh implementation session, so both phases run on the same tier; and a non-auto (including a non-auto scheduled) run pauses at each ticket's own gate rather than proceeding hands-off. For an `--auto` run, each ticket's eventual implementation completion is observed in its own fresh implementation tab, not here.\n\nIf the CLI reported any `create-failed`/`spawn-failed` statuses, or Stage 2b emitted enrichment warnings, list them under a `Warnings:` heading. If there were none, omit that section.\n\nSee `docs/claude/parallel-worktrees.md` for the underlying runbook this command builds on, and `commands/src/review-and-implement.md` for the per-ticket review→gate→implement composition each spawned session runs.\n",
|
|
22
|
+
"review-and-start.md": "---\nschedulable: 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\":\"agent\",\"flag\":\"--agent\",\"type\":\"string\"},{\"name\":\"baseBranch\",\"flag\":\"--base-branch\",\"type\":\"string\"},{\"name\":\"maxParallel\",\"flag\":\"--max-parallel\",\"type\":\"string\"},{\"name\":\"dryRun\",\"flag\":\"--dry-run\",\"type\":\"boolean\"},{\"name\":\"guardStaleBranch\",\"flag\":\"--guard-stale-branch\",\"type\":\"boolean\"}]}\n---\n\n# Review and Start: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes one or more Jira ticket keys (e.g., `BAPI-248` or `BAPI-248 BAPI-250` — both flow through the identical ordered `keys: string[]` path) and spawns one refreshed, self-contained Worktrunk worktree per ticket, each running `/review-and-implement <KEY>`: review runs in that spawned session, then a per-ticket human proceed/halt gate, and — only on approval — that session hands the ticket off to a **fresh implementation session** that reuses the same worktree. Review and implementation thus run in two separate agent sessions per ticket, not one shared context. It is the **recommended front door** for chaining \"review these tickets, then implement the ones that pass\" starting from *existing ticket keys* (the entry point `/full-automation` lacks, since its server orchestrator only accepts an idea).\n\nIt is a thin shim over the packaged `start-tickets` CLI, invoked with `--workflow review-and-implement`: this command performs the same connectivity check and branch enrichment as `/start-tickets`, then hands off to the identical worktree/base-refresh/model-routing/credential/cross-platform-spawn/`doctor` engine — reused verbatim. The review→implement halt-gate decision logic lives entirely in the spawned `/review-and-implement` session, never here or in the CLI. Using `start-tickets --workflow review-and-implement` directly (documented in `commands/src/start-tickets.md`) remains available as the lower-level launcher seam this command drives.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline that resolves connectivity and per-ticket branch names, then spawns N parallel Worktrunk worktrees via the packaged CLI's `--workflow review-and-implement` seam. 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 and pass-through flags:\n - **Ticket keys**: every whitespace-separated token matching `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). A single key and multiple keys flow through the identical ordered `keys: string[]` path — there is no separate single-ticket code path. If zero keys are found, stop immediately and display:\n ```\n No ticket keys found in arguments. Expected one or more keys like BAPI-248.\n Usage: /review-and-start [flags] <KEY> [KEY ...] (e.g., /review-and-start BAPI-248 BAPI-250)\n ```\n - **`--auto`**: a single chain-level flag. When present, it is forwarded to `start-tickets --auto`, which threads it into BOTH the review and implementation phases of every spawned `/review-and-implement <KEY>` session.\n - **`--rounds`**: normalize `--rounds 1`/`--rounds=1` and `--rounds 2`/`--rounds=2` to `--rounds=<n>`; reject any other value. This value is forwarded to the review phase of every spawned session.\n - **Selected agent**: validate `--agent <name>` / `--agent=<name>` against `claude`/`cursor-agent` using the same rule as `/start-tickets`; default `claude`.\n - **User-supplied base branch**: validate `--base-branch <branch>` / `--base-branch=<branch>` using the same non-empty/≤255-character/no-leading-dash/no-control-character rules as `/start-tickets` Stage 0. A user-supplied value takes precedence over Stage 2a's `config_field` resolution below.\n - **`--max-parallel N`**: a positive integer; the CLI's own default (3) applies when omitted.\n - **`--dry-run`**: a boolean toggle.\n - **`--guard-stale-branch`**: a boolean toggle forwarded verbatim to `start-tickets`. It turns on the F7 stale-branch guard: a pre-existing `feature/<KEY>` branch whose tip is **not** an ancestor of the resolved base is refused with a `create-failed` row and the stale-worktree remedy, instead of being silently reused. Pass it on the **LLM-conductor pilot cut path**, where deterministic slugs mean a re-cut epic re-derives the same branch name and would otherwise land on the previous run's branch. Omitted by default, so an ordinary human run keeps reusing a same-named branch.\n - Reject malformed input before proceeding: an unsupported flag, a ticket key not matching `[A-Z]+-[0-9]+`, an unsupported `--agent`/`--rounds`/`--base-branch` value — 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 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 `start-tickets` CLI (driven with `--workflow review-and-implement` in Stage 3) owns all platform pre-flight checks, refreshed worktree creation, secret-free credential provisioning, difficulty→model-tier routing, and cross-platform terminal spawning — identical to `/start-tickets`. This command does not duplicate or re-verify any of that; see `commands/src/start-tickets.md` Stage 1 for the full prerequisite/provisioning description it drives. This command does not expose `--conductor`, `--terminal`, or `--no-refresh-main`/`--no-refresh-base` — it stays scoped to the front-door flags listed above. `--guard-stale-branch` is the one safety flag it *does* forward: `--conductor` implies that guard in the CLI, but this front door must not enable the v2 conductor context (a run id, hook provisioning, run-started emission) that `--conductor` also switches on, so the guard is reachable here on its own. Proceed to Stage 2.\n\n## Stage 2 — Resolve Base Branch + Enrich Branch Names (best-effort)\n\n### Stage 2a — Resolve Base Branch\n\nIdentical to `/start-tickets` Stage 2a: if the user supplied `--base-branch`, use it and skip the `config_field` lookup entirely. Otherwise call `config_field` (`operation: \"get\"`, `field_name: \"base_branch\"`) and treat a non-empty string `value` as the configured base branch; treat `null`, an empty/whitespace-only string, an HTTP `400` response, or any other lookup failure as \"unset\" and omit `--base-branch` from Stage 3 (the CLI then defaults to `main`). When forwarding a resolved value into the Stage 3 Bash invocation, apply the same mandatory single-quote escaping rule as `/start-tickets` (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating it into the command string — never expand it unquoted.\n\n### Stage 2b — Enrich Branch Names\n\nIdentical to `/start-tickets` Stage 2b: for each ticket key without a user-provided `--branch` override, call `get_ticket` (`ticket_number` set to the key, `save_locally: false`), slugify its `summary` (lowercase, collapse runs of `[^a-z0-9]+` to a single `-`, trim leading/trailing dashes, truncate to at most 40 characters preferring a dash boundary), and build `feature/<KEY>-<slug>`. On any per-key failure (404, network error, empty slug), emit a warning and let the CLI apply its default `feature/<KEY>` for that key only; never stop the pipeline. Credentials never reach the Bash-spawned CLI — this enrichment stays in the command, exactly as in `/start-tickets`.\n\nThis stage is **non-critical** — warnings are acceptable; the pipeline continues with the fallback branch for any key that fails enrichment.\n\n## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the Stage 3 shell-out, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\n### Dry-run intended-actions preview\n\nWhen the user passed `--dry-run`, print an intended-actions preview **before** the Stage 3 shell-out. The preview names the resolved launcher explicitly, so an operator can see at a glance whether this run will use the published package or a local build:\n\n```\nIntended actions (dry run — nothing is created)\n Launcher: <the resolved <launcher> value, exactly as it will be escaped into the shell command>\n Command: <launcher> start-tickets --workflow review-and-implement <flags> <ticket-keys>\n Tickets: <the ordered ticket keys>\n```\n\nWhen the override is set, show the **escaped** launcher exactly as it will appear in the Bash command string, so the preview and the command cannot disagree. `--dry-run` is then forwarded to the resolved launcher, which does the previewing of its own actions: **no worktree is created, no agent tab or session is opened, and no worker process is spawned.** Do not state or imply otherwise.\n\n## Stage 3 — Invoke the Packaged CLI\n\nUse the **Bash tool** to invoke exactly **one** CLI invocation covering every requested key — never a per-key loop:\n\n```\n<launcher> start-tickets --workflow review-and-implement <pass-through-flags> <base-branch-flag> <branch-overrides> <ticket-keys>\n```\n\nWhere:\n- `<pass-through-flags>` forwards `--auto` (only if supplied), the normalized `--rounds=<n>` (only if supplied), `--agent <name>` (only if supplied), `--max-parallel N` (only if supplied), `--dry-run` (only if supplied), and `--guard-stale-branch` (only if supplied) verbatim.\n- `<base-branch-flag>` is `--base-branch '<escaped-value>'` only when a value was resolved in Stage 2a; otherwise omitted entirely.\n- `<branch-overrides>` is the list of `--branch KEY=BRANCH` flags assembled in Stage 2b.\n- `<ticket-keys>` is the original ordered list of keys parsed in Stage 0.\n\nCredentials are never placed in this shell command — the same secret-free provisioning path `/start-tickets` uses applies here unchanged.\n\nExample, single key, hands-off:\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --workflow review-and-implement --auto --rounds=1 --branch BAPI-248=feature/BAPI-248-add-pr-rating-pre-evaluation-step BAPI-248\n```\n\nExample, multiple keys, manual per-ticket gate (no `--auto`):\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --workflow review-and-implement --branch BAPI-248=feature/BAPI-248-add-pr-rating-pre-evaluation-step --branch BAPI-250=feature/BAPI-250-deep-research-durability BAPI-248 BAPI-250\n```\n\nExample, LLM-conductor pilot cut (epic base plus the stale-branch guard):\n\n```\nnpx -y @bridge_gpt/mcp-server start-tickets --workflow review-and-implement --auto --guard-stale-branch --base-branch 'epic/BAPI-902' --branch BAPI-902=feature/BAPI-902 BAPI-902\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: `KEY branch=BRANCH status=STATUS`, optional trailing `path=PATH`) and reformat it as a markdown table, identical in shape to `/start-tickets` Stage 4 (`Ticket | Branch | Status`; statuses `dry-run`, `spawned`, `create-failed`, `spawn-failed`).\n\nThis report describes **worktree/spawn status only**. State explicitly in the report:\n- Each successfully spawned session first runs `/review-ticket` and its own per-ticket halt gate; on successful review and approval (or `--auto`), it then opens a **fresh implementation session** (`/implement-ticket`) that reuses the same worktree. Review and implementation run in two separate agent sessions, not one shared context. This parent session does not observe or report either the review outcome or the later implementation outcome. Never claim or imply that review or implementation has completed.\n- When `--dry-run` was passed, state explicitly that no worktrees were created and no tabs/sessions were opened.\n- As a fixed trade-off of this design: the review session's difficulty-derived model tier is snapshotted and reused (via `--tier`) by the fresh implementation session, so both phases run on the same tier; and a non-auto (including a non-auto scheduled) run pauses at each ticket's own gate rather than proceeding hands-off. For an `--auto` run, each ticket's eventual implementation completion is observed in its own fresh implementation tab, not here.\n\nIf the CLI reported any `create-failed`/`spawn-failed` statuses, or Stage 2b emitted enrichment warnings, list them under a `Warnings:` heading. If there were none, omit that section.\n\nSee `docs/claude/parallel-worktrees.md` for the underlying runbook this command builds on, and `commands/src/review-and-implement.md` for the per-ticket review→gate→implement composition each spawned session runs.\n",
|
|
23
23
|
"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` — only when `--auto` was passed; otherwise omit this field entirely.\n - `rounds`: `1` — only when `--rounds=1` was explicitly passed on the command; `2` — 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` — 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",
|
|
24
24
|
"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 — 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 — 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` → per-ticket or global auto-approve flag.\n - `single-pass`, `one-pass`, `rounds=1`, or `--rounds=1` → `rounds=1` (single-pass review).\n - `full`, `two-pass`, `rounds=2`, or `--rounds=2` → `rounds=2` (full second-opinion review).\n - **omitted rounds → `adaptive`**: when no rounds mode is given for a ticket, do NOT choose a round count — 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 — 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 — 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## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the first shell-out below, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\n## Stage 1 — Invoke the Packaged CLI\n\nUse the **Bash tool** to invoke exactly one CLI invocation:\n\n```\n<launcher> 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 — it creates no worktrees.\n- The command never runs `wt` or `git-wt` — 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 — 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",
|
|
25
|
-
"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 `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that 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/ — 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",
|
|
25
|
+
"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 `ping` MCP tool (no parameters) and read `docs_dir` from its first (JSON) content item. Store that 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/ — 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 them with /learn-repository (a project admin must run it) 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; update that field with /teach-bridge, or re-run /learn-repository (a project admin must run it).\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; update that field with /teach-bridge, or re-run /learn-repository (a project admin must run it).\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",
|
|
26
26
|
"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 — a test that genuinely executes the system end-to-end via real database operations, real LLM calls, or real FastAPI routing), and — where integration testing is not possible — 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 — 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` — override the window start date.\n - `--full` — ignore the marker and use a default lookback of 6 months.\n - `--limit=N` — 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 the configured docs directory (`ping`'s `docs_dir`) — 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 — 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** — Jira tokens can be expired — 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 — 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 — no edits, no test design, no solutioning — and to return a structured finding.\n\nEach subagent must:\n\n1. Read the changed files and describe what the feature does in 2–4 sentences, with concrete `file:line` citations.\n\n2. Identify the feature's runtime surface — 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`** — already covered end-to-end; cite the existing integration test file.\n - **`integration_testable`** — 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`** — 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`** — 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 — never abort the whole run.\n\n## Stage 3 — 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 — 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 — Not Integration-Testable:** findings with `bucket` `smoke_only` (sub-group \"Smoke-testable — how\") or `not_testable` (sub-group \"Not testable — why\").\n\n## Stage 4 — 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>` → `HEAD <head_sha>`); the feature count; and per-bucket tallies.\n - **Section 1 — Integration Testing: Covered or Addable.** One `### BAPI-NNN — <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 — Not Integration-Testable.** One heading per feature with the same feature description, plus **Why not integration-testable**, and — for `smoke_only` features — **How to smoke test (genuinely execute the system)** with the runbook citation.\n - A **Warnings** section listing each warning as a bullet — 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",
|
|
27
27
|
"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",
|
|
28
|
-
"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** — 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\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 — 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 — 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 — 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 — 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`–`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** — 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 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 — 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` — 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\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** — they contain no `env` block and no API key, because the shim resolves credentials at runtime. If a spawned agent (or difficulty→model 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`) — 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 — Resolve Base Branch + Enrich Branch Names (best-effort)\n\n### Stage 2a — 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\" — 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 — 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 — never expand the value unquoted into the command line.\n\n### Stage 2b — Enrich Branch Names\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## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the first shell-out below, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\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```\n<launcher> 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** — 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`. This table (and the report as a whole) describes **worktree/spawn status only** — 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 — 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 '<spawned_command>'` 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 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` — 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 — 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 → cheap`, `3-5 → basic`, `6+ → 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` — boolean, **default ON**. Set to `false` to\n disable routing for a repo (the CLI then omits `--model`).\n - `difficulty_model_tier_overrides` — 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→model 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` — 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 —\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]'` — 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\nObservability under `--conductor` is one-directional: workers emit lifecycle events\ninto the local ledger and nothing is passed back into a running session. (Epic-tick\ndispatch always runs with conductor enabled, independent of this user-facing flag.)\n",
|
|
28
|
+
"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\"},{\"name\":\"guardStaleBranch\",\"flag\":\"--guard-stale-branch\",\"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** — 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\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 — 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>`), `--max-parallel N`, and `--guard-stale-branch` that the user supplied. These are forwarded verbatim to the CLI in Stage 3. `--guard-stale-branch` turns on the F7 stale-branch guard: a pre-existing `feature/<KEY>` branch whose tip is **not** an ancestor of the resolved base is refused with a `create-failed` row carrying the stale-worktree remedy, instead of being silently reused on top of another run's commits. It is off by default (a plain run keeps reusing a same-named branch) and is implied by `--conductor`. `--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 — 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 — 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 — 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`–`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** — 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 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 — 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` — 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\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** — they contain no `env` block and no API key, because the shim resolves credentials at runtime. If a spawned agent (or difficulty→model 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`) — 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 — Resolve Base Branch + Enrich Branch Names (best-effort)\n\n### Stage 2a — 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\" — 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 — 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 — never expand the value unquoted into the command line.\n\n### Stage 2b — Enrich Branch Names\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## Packaged CLI launcher (`BAPI_MCP_CLI`)\n\nResolve the packaged-CLI launcher **once**, before the first shell-out below, and reuse that one resolved value for every packaged-CLI invocation in this command. Call it `<launcher>`.\n\n- Read the `BAPI_MCP_CLI` environment variable.\n- **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n- **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\nWhen the override is set:\n\n- Apply this command's mandatory single-quote escaping rule (`'` → `'\\''`, then wrap the whole value in single quotes) before interpolating `<launcher>` into a Bash command string. Never expand it unquoted.\n- Keep every dynamic argument — ticket keys, branch names, base branches, file paths — independently quoted. Never concatenate an argument into the launcher value.\n- Never put a credential, an API key, or an environment assignment carrying one into the launcher value, an example, or a dry-run preview.\n\nWorked examples and printed remediation hints below show the **unset** resolution — the literal `npx -y @bridge_gpt/mcp-server` — because that is the default every operator gets. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\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```\n<launcher> 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`, `--guard-stale-branch`), 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** — 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`. This table (and the report as a whole) describes **worktree/spawn status only** — 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 — 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 '<spawned_command>'` 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 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` — 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 — 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 → cheap`, `3-5 → basic`, `6+ → 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` — boolean, **default ON**. Set to `false` to\n disable routing for a repo (the CLI then omits `--model`).\n - `difficulty_model_tier_overrides` — 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→model 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` — 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 —\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]'` — 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\nObservability under `--conductor` is one-directional: workers emit lifecycle events\ninto the local ledger and nothing is passed back into a running session. (Epic-tick\ndispatch always runs with conductor enabled, independent of this user-facing flag.)\n",
|
|
29
29
|
"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 `ping` MCP tool (no parameters) and read `role` from its first (JSON)\n content item. Inspect it:\n - If `role` is `\"admin\"` OR `role` is `null` (not determined — e.g. a legacy shared key): proceed normally.\n - Otherwise (an explicit non-admin role such as `\"member\"`): stop immediately and display:\n ```\n Admin access required. Your API key has role \"<role>\".\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 `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 — 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 — 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",
|
|
30
30
|
"upgrade-bridge.md": "# Upgrade Bridge\n\n$ARGUMENTS\n\nUse this command to upgrade (or update) the Bridge API MCP — the\n`@bridge_gpt/mcp-server` package, also called the bridge-api MCP — to the latest\npublished version. This is the action behind the ping tool's advice to \"tell\nyour local agent 'upgrade bridge'\".\n\n---\n\n# Instructions\n\nRun the existing packaged upgrade flow. Do not edit files, install anything by\nhand, or invent a new subcommand — just drive the upgrade CLI and report what it\ndid.\n\n## Step 1 — Run the upgrade command\n\nFrom the **project root**, run exactly:\n\n```\nnpx -y @bridge_gpt/mcp-server@latest --upgrade\n```\n\nThis upgrades/updates the installed `@bridge_gpt/mcp-server` (the bridge-api MCP)\nand re-scaffolds the slash commands.\n\n### `BAPI_MCP_CLI` does not apply here — deliberately\n\nEvery other packaged-CLI command honors the `BAPI_MCP_CLI` local-launcher override, which replaces the `npx -y @bridge_gpt/mcp-server` prefix with a local build for pre-publish verification. **This command is the one exception, and ignores it entirely.**\n\nThe reason is that upgrading is inherently about the *published* package. A local launcher points at a build that is already on disk, so running the upgrade through it would \"upgrade\" the operator using the very build they are trying to replace — silently doing nothing while reporting success. That is the exact failure the `@latest` pin below exists to prevent, so honoring the override here would reintroduce it by another route.\n\nRun the command above verbatim even when `BAPI_MCP_CLI` is set, and do not mention the override as an option for this command.\n\nThe `@latest` pin matters. It applies to the short-lived *upgrader* process only:\nwithout it, npx may reuse a cached older copy of the package and \"upgrade\" you\nusing the very build you are trying to replace. The exact version pin the\nupgrader then writes into each host config is a separate, deliberate thing — host\nconfigs stay pinned to an exact `MAJOR.MINOR.PATCH` release so a project's MCP\nserver is reproducible.\n\n## Step 2 — Report the result\n\nThe CLI reports **per config file**, because a project can have several\n(`.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`) and they can disagree.\nRelay those lines as printed. Do not collapse them into a single global\n`oldVersion -> newVersion` transition and do not infer one yourself.\n\nThe forms the CLI emits are:\n\n- `<path>: 0.2.16 -> 0.2.36` — that config's launcher pin moved.\n- `<path>: already 0.2.36` — that config was already at the target.\n- `<path>: normalized <spec> -> @bridge_gpt/mcp-server@0.2.36` — an unpinned or\n `@latest` launcher was given an exact pin.\n- `<path>: added @bridge_gpt/mcp-server@0.2.36` — a `bridge-api` entry was added\n to an existing config.\n- `<path>: created with @bridge_gpt/mcp-server@0.2.36` — the config file was\n created.\n- `<path>: skipped — worktree mcp-invoke shim preserved` — a worktree\n registration that intentionally has no published-package pin.\n\nThen report the CLI's closing status verbatim:\n\n- If the CLI prints `Already up-to-date.`, report `Already up-to-date.` exactly.\n Only the CLI decides this; it means every applicable launcher pin was already\n at the target. Never infer it from a version transition that reads the same on\n both sides.\n- Otherwise report the CLI's completion line together with the per-config lines\n above.\n\n## Step 3 — Handle failures\n\nIf the command fails (non-zero exit or an error in its output), **stop** and\nreport the CLI error verbatim. Do not retry blindly and do not attempt manual\nedits to config files to work around it. A non-zero exit means the upgrade did\nnot converge — for example a config could not be read, a launcher carries a\nversion range the upgrader must not rewrite, a competing local install could not\nbe removed, or a written pin failed post-write verification. Those are reported\nfor a human to resolve, not for you to repair.\n\n## Final Report\n\nReport whether the bridge-api MCP was upgraded (relaying the CLI's per-config\nlines and its completion status), was already current (`Already up-to-date.`),\nor failed (with the CLI error).\n"
|
|
31
31
|
};
|