@bridge_gpt/mcp-server 0.2.60 → 0.2.62
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 +16 -8
- package/build/commands.generated.js +3 -3
- package/build/conductor/bridge-api-client.js +353 -2
- package/build/conductor/merge-execution-contract.js +30 -5
- package/build/conductor/merge-execution-eligibility.js +10 -9
- package/build/conductor/readiness-sections.js +99 -16
- package/build/conductor/readiness.js +108 -15
- package/build/credential-store.js +156 -0
- package/build/doctor.js +94 -6
- package/build/drive-epic.js +124 -12
- package/build/epic-implementer-bin.js +13 -13
- package/build/epic-integration-pr.js +61 -15
- package/build/epic-launch-profile.js +455 -0
- package/build/executor/cli.js +9 -0
- package/build/executor/credentials.js +17 -1
- package/build/executor/http-client.js +45 -1
- package/build/executor/integration-job.js +759 -0
- package/build/executor/job-runner.js +92 -9
- package/build/executor/observation.js +114 -7
- package/build/executor/runner.js +101 -1
- package/build/executor/types.js +43 -0
- package/build/index.js +338 -114
- package/build/install-bridge-conductor.js +398 -6
- package/build/install-bridge.js +92 -16
- package/build/merge-workflow-config.js +54 -0
- package/build/merge-workflow-drift.js +35 -4
- package/build/merge-workflow.js +386 -2
- package/build/pipelines.generated.js +1 -1
- package/build/plane/supervisor.js +6 -0
- package/build/readme.generated.js +1 -1
- package/build/setup-epic.js +103 -32
- package/build/version.generated.js +3 -3
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ export const COMMANDS = {
|
|
|
5
5
|
"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",
|
|
6
6
|
"create-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",
|
|
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
|
-
"define-tickets.md": "Explore the codebase for a task, settle its acceptance criteria with the user, then propose a design that meets them.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\n`$ARGUMENTS` is a free-form prompt describing a task you want to accomplish and your goals for it. This is **not** a Jira ticket key — it is plain text describing the work.\n\nExecute all exploration and analysis directly in the main conversation. The user should see exploration progress as it happens.\n\nThis command runs strictly outside-in, and the order is the point:\n\n1. **Requirements first.** Establish what the system must do, how it must behave, and what standards it must meet — then get the user to ratify that on an interactive decision page. The page settles **requirements only**. It never asks the user to pick an implementation.\n2. **Then how.** Only once the criteria are ratified do you consider how to meet them, optionally with a council.\n3. **Then the design.** You describe the final proposed design yourself, in the exploration doc. There is no second decision page.\n4. **Then ticket(s).**\n\nNever invert this. A design proposed against unratified criteria is a guess, and an implementation choice presented before the criteria are settled asks the user to commit to a solution for a problem they have not yet agreed on.\n\nIf any critical stage fails, stop immediately and report which stage failed and why.\n\n## Stage 0 — Setup\n\n1. **Parse prompt**: Extract the prompt text from `$ARGUMENTS`. Trim any surrounding whitespace. If the prompt is empty or whitespace-only, stop immediately and display: `Usage: /define-tickets <prompt describing your task and goals>`\n\n2. **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\n3. **Generate output slug**: Create a kebab-case slug from the prompt — take the first 6-8 meaningful words, strip non-alphanumeric characters, lowercase, and truncate to 60 characters. The slug **must start with a letter** so it is a valid decision-page `ticket_key` in Stage 5 (`/^[A-Za-z][A-Za-z0-9_-]*$/`); if it would start with a digit or hyphen, prefix it with `exploration-`. If `{docs_dir}/explorations/{slug}.md` already exists, append a short timestamp suffix (e.g., `-1710000000`) — and fold that suffix **into the `slug` variable itself**, not just the filename, so that Stage 4 (the doc), Stage 5 (`ticket_key`, `output_filename`), and Stage 8 (the doc rewrite) all reference the same slug. The output file path is `{docs_dir}/explorations/{slug}.md`.\n\n4. **Initialize tracking**: Prepare to track `key_files_examined` (list of files read during exploration), `web_searches` (list of topics searched), and `research_queries` (list of deep research queries).\n\nIf this stage fails, stop immediately and report the error. Do not proceed to Stage 1.\n\n## Stage 1 — Codebase Exploration\n\nThis is the core discovery stage. Take your time — thorough exploration is more valuable than speed.\n\n1. **Analyze the prompt** to identify which areas of the codebase are relevant: route files, agent flows, database models, library utilities, LLM integration, MCP server, unit and E2E suites, etc.\n\n2. **Search for files** matching patterns related to the task (e.g., `api/routes/**/*.py`, `src/python/llms/agents/**/*.py`, `db/models/*.py`).\n\n3. **Search for content** — relevant function names, class names, patterns, and keywords across the codebase.\n\n4. **Read the most relevant files** in detail — understand existing implementations, conventions, and patterns that relate to the task.\n\n5. **Build a mental model** of:\n - What exists today that relates to the task\n - What patterns and conventions are used in similar features\n - What dependencies, data flows, and integration points are involved\n - What gaps or unknowns remain that need external research\n - Whether there is an established precedent for this kind of work, or none at all — Stage 7 depends on this judgement\n\nExplore to understand the problem and its constraints. Resist designing a solution while you read — you do not yet know what the system is required to do, and Stage 8 is where the design gets written.\n\n6. **Track all significant files** examined in `key_files_examined`.\n\nDo not rush this stage. When in doubt, read more code rather than less. Continue exploring until you have a solid understanding of the relevant code.\n\nThis stage is non-blocking — always proceed to Stage 2 regardless of what you find, since the exploration informs what research is needed.\n\n## Stage 2 — Research Unknowns\n\nBased on gaps identified in Stage 1, decide what research is needed. Apply these decision rules:\n\n- **No research needed**: The codebase exploration answered all questions. Skip directly to Stage 3.\n- **Web search**: For quick factual lookups — library API signatures, configuration syntax, small \"how to\" questions. Examples: \"FastAPI dependency injection with custom headers\", \"Alembic batch migration syntax\". Do web searches inline and capture relevant findings.\n- **Deep research** (via `request_deep_research` MCP tool): For large, multi-faceted unknowns that require synthesizing information from multiple sources. Examples: \"Best practices for implementing WebSocket connection pooling in Python asyncio\", \"Tradeoffs between different approaches to real-time notification delivery in FastAPI applications\". Only use deep research when the question genuinely needs a multi-source investigation.\n\n**If deep research is needed:**\n\n1. Call `request_deep_research` with `wait_for_result` set to `true`, `save_locally` set to `true`, a descriptive `query`, and `context` describing the Bridge API tech stack and the specific task.\n2. If deep research fails, note the failure and fall back to web searches for the same topic. Do NOT halt the pipeline.\n\nTrack all research performed in `research_queries` and `web_searches`.\n\nThis stage is non-blocking — failures degrade the quality of analysis but do not stop the command. Log a warning for any failed research and continue.\n\n## Stage 3 — Frame Acceptance Criteria\n\nEstablish what \"done and correct\" means. Everything in this stage is about the system's obligations, not its implementation. Do not name a technical approach here — that is Stage 8's job, and it does not happen until the user has ratified this framing.\n\n1. **State the frame plainly (required).**\n - **Business goal** — the value this work delivers and why it matters.\n - **Desired end-state** — the concrete state the system should reach once this work is done.\n - **System behavior** — how the system must behave to complete its task (the quality attributes in prose, not a feature list).\n\n2. **Derive the acceptance criteria — what the system must do (required).** Write 3-8 criteria. Each one gets:\n - An `id` (`AC-1`, `AC-2`, …).\n - A `criterion` — a single obligation stated concretely enough to be checked. Write it as observable behavior (\"an operator who revokes a key sees the next request rejected\"), not as a task (\"add a revocation endpoint\").\n - A `verification` — how we would confirm it holds. Name the observable signal: a response code on a specific route, a row state, a log line, a rendered element, a user-visible outcome. **A criterion nobody can check is not yet a criterion** — sharpen it or drop it.\n - A `status`, using the rubric in step 4.\n\n Cover the failure and edge behavior, not just the happy path. If the work changes something that already exists, at least one criterion should pin down what must **not** regress.\n\n3. **Identify the non-functional requirements — the standards the system must meet (required).** Consider every one of these canonical NFR categories and include the ones that genuinely apply (omit the rest): security/privacy, performance/latency, reliability/failure-modes, observability/auditability, accessibility/UX, data-integrity/migration, compatibility, operability/config, compliance/SOC2, rollout/reversibility. For each NFR you include, write its `requirement` and its `implication` (what it changes about the implementation) — an NFR with no concrete implication is boilerplate; drop it.\n\n4. **Classify every acceptance criterion and every NFR** with this rubric: `confirmed` only if explicitly stated or observable in code; `assumed` only if a low-risk, reversible default; `open` if it touches architecture, the data model, security, user-visible behavior, migration, or irreversible creation and is not settled. When a criterion or an NFR is genuinely unclear, prefer marking it `open` and asking. Clear criteria make everything downstream more accurate, so surfacing an unclear one is a success, not a delay.\n\n5. **Frame the open requirement questions.** Where a requirement is unsettled **and** has discrete candidate answers, express it as a question the user can answer by clicking (e.g. \"Must revocation take effect immediately, or is eventual acceptable?\"). These become cards in Stage 5. They are questions about *what the system must do* — never about how to build it. If a question has no discrete answers, leave it as prose in the doc instead.\n\n6. **Sanity-check the frame against itself.** Do any two criteria conflict? Does a criterion conflict with an NFR (e.g. an auditability requirement against a latency budget)? Note every tension you find — Stage 7 treats these as a council trigger, and Stage 8 must resolve them explicitly rather than quietly favouring one side.\n\nThis stage is inline analysis — no tool calls required. This stage is non-blocking — always proceed to Stage 4.\n\n## Stage 4 — Write Requirements Draft\n\nWrite what you know so far to disk, so the user has something to read alongside the decision page. The design is deliberately absent — it does not exist yet.\n\n1. Create the `explorations/` directory under `docs_dir` if it does not exist.\n\n2. Write the exploration document to the slug-based path determined in Stage 0 (`{docs_dir}/explorations/{slug}.md`) with this structure:\n\n```markdown\n# Exploration: {concise summary of the prompt}\n\n**Date**: {current date}\n**Prompt**: {original prompt text}\n**Status**: Requirements drafted — awaiting ratification\n\n## Context\n\n{Brief description of the task and what areas of the codebase are relevant.}\n\n## Acceptance Criteria\n\n{The criteria from Stage 3 — what the system must do. One entry per criterion: its id, the criterion itself, how it is verified, and its status (confirmed / assumed / open).}\n\n## Goals & NFRs\n\n{The business goal, desired end-state, and required system behavior from Stage 3. Then the non-functional requirements — the standards the system must meet: each with its category, requirement, implication, and status (confirmed / assumed / open). Note any tension between criteria or between a criterion and an NFR.}\n\n## Open Questions\n\n{Requirement questions that are still unsettled. Mark which ones are going onto the decision page as cards and which are open-ended prose.}\n\n## Codebase Findings\n\n{Key discoveries from Stage 1. What exists today, what patterns are used, what the relevant code paths look like. Reference specific files and functions with file_path:line_number format.}\n\n## Research Findings\n\n{Findings from web searches and deep research, if any. If no research was performed, state \"No external research was needed.\"}\n\n## Key Files\n\n{Bulleted list of the most important files examined, with one-line descriptions of their relevance.}\n```\n\nDo not add a design, an implementation plan, or a recommendation to this draft. Stage 8 adds those once the criteria are settled.\n\nIf the file cannot be written, stop immediately and report the failure.\n\n## Stage 5 — Generate Requirements Decision Page\n\nTurn the Stage 3 framing into an interactive HTML decision page so the user can ratify the requirements by clicking. **This page settles requirements only.** It must not contain a single implementation option — the user is agreeing on what the system must do, not choosing how to build it.\n\n1. **Map the acceptance criteria to `acceptance_criteria`.** Each entry has `id`, `criterion`, `verification`, and `status`. Ids must be unique — a duplicate id is rejected, because the id is the key the page reports the user's stance under. Every criterion renders with an Agreed / Ask about this / Disagree control, so pass all of them, not only the open ones. Pass the NFRs the same way under `nfrs`.\n\n2. **Map each open requirement question from Stage 3 step 5 to an actionable item.** Each entry has:\n - `id`: a short stable id, e.g. `R-1`, `R-2`.\n - `question`: the requirement question.\n - `options`: the 2-4 candidate answers (string array). Do **not** include \"None of these\" or \"Ask about this\" — the renderer auto-appends both.\n - `option_consequences`: what each answer would mean for the criteria, **parallel to and the same length as** `options`.\n - `why_it_matters`: the concrete impact line.\n - `recommendation_explanation`: why the recommended answer is best.\n - `recommendation_index`: the 0-based index of the recommended answer (must be within `options`).\n - `codebase_evidence` (optional): the Assessment paragraph plus `file:line` citations, shown collapsed.\n\n **These cards are requirement questions, never implementation choices.** \"Must revocation be immediate or is eventual acceptable?\" is a valid card. \"Should we use a short-TTL cache or pub/sub invalidation?\" is not — it is a solution, it belongs to Stage 8, and putting it here defeats the purpose of the page. If you cannot phrase a card without naming a mechanism, it is not a requirement question. When there are no such questions, pass an empty array — a criteria-only page is expected and renders correctly.\n\n3. **Call `generate_decision_page`** with routing fields at the root and all heavy arrays nested under `content`:\n - `artifact_type`: `pre_ticket_planning` (renders the acceptance-criteria and goals panel above any cards).\n - `ticket_key`: the Stage 0 `slug` (a non-Jira slug is fine — it must start with a letter and contain only letters, digits, hyphens, or underscores).\n - `output_subdir`: `explorations` (so the page lands beside the markdown doc).\n - `output_filename`: `{slug}-requirements.html`.\n - `labels`: requirements-flavored overrides, e.g. `title` = \"Requirements\", `section_heading` = \"Open Requirement Questions\", and an `intro` that frames the page as agreeing on what the system must do before any design work begins.\n - `content`: an object containing `system_goals` and `actionable_items`. **`system_goals` MUST ALWAYS be passed** inside `content` so the backend always writes a page. Never omit it, even if every criterion and NFR is confirmed. `acceptance_criteria` and `nfrs` both live inside `system_goals`. (Do not pass `implementation_order` inside `content` — that is for epic surfaces, not a single explored task.)\n\n ```typescript\n interface ExploreTicketContent {\n system_goals?: {\n business_goal: string;\n desired_end_state: string;\n system_behavior: string;\n acceptance_criteria?: Array<{\n id: string; // e.g. \"AC-1\"; must be unique\n criterion: string; // what the system must do\n verification: string; // how we would confirm it holds\n status: \"confirmed\" | \"assumed\" | \"open\";\n }>;\n nfrs?: Array<{\n category: string;\n requirement: string;\n implication: string;\n status: \"confirmed\" | \"assumed\" | \"open\";\n }>;\n };\n actionable_items?: Array<{\n id: string; // e.g. \"R-1\"; a REQUIREMENT question, not a design choice\n question: string;\n why_it_matters: string;\n recommendation_explanation: string;\n options: string[]; // 2-4 candidate answers (no \"None of these\" or \"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 original_question?: string; // optional: only when item maps to a verbatim question\n }>;\n // clear_improvements: not used by this command — it captures requirements, not findings\n // implementation_order: for epic surfaces only — do NOT include for single task explorations\n // depends_on: hard prerequisites (titles/keys that must land first)\n // recommended_after: soft sequencing preferences, not hard blockers\n }\n ```\n\n Example call:\n ```json\n {\n \"ticket_key\": \"revoke-api-keys\",\n \"artifact_type\": \"pre_ticket_planning\",\n \"output_subdir\": \"explorations\",\n \"output_filename\": \"revoke-api-keys-requirements.html\",\n \"labels\": { \"title\": \"Requirements\", \"section_heading\": \"Open Requirement Questions\" },\n \"content\": {\n \"system_goals\": {\n \"business_goal\": \"Operators can cut off a leaked key immediately.\",\n \"desired_end_state\": \"Revocation is self-serve and takes effect at once.\",\n \"system_behavior\": \"Rejects revoked credentials without a restart.\",\n \"acceptance_criteria\": [\n { \"id\": \"AC-1\", \"criterion\": \"An operator who revokes a key sees the next request with it rejected.\", \"verification\": \"The following call to the protected route returns 401.\", \"status\": \"confirmed\" },\n { \"id\": \"AC-2\", \"criterion\": \"Revocation is recorded with actor and timestamp.\", \"verification\": \"An audit row names the operator and the revoked key id.\", \"status\": \"open\" }\n ],\n \"nfrs\": [\n { \"category\": \"security/privacy\", \"requirement\": \"The raw key is never logged on the revoke path.\", \"implication\": \"Log the key id, never the secret.\", \"status\": \"open\" }\n ]\n },\n \"actionable_items\": [\n {\n \"id\": \"R-1\",\n \"question\": \"Must revocation take effect immediately, or is eventual acceptable?\",\n \"why_it_matters\": \"Sets the hard bound AC-1 has to meet.\",\n \"recommendation_explanation\": \"A leaked key is an active incident; eventual leaves a usable window.\",\n \"options\": [\"Immediately (under 5s)\", \"Eventually (under 60s is acceptable)\"],\n \"option_consequences\": [\"AC-1 gains a 5s bound.\", \"AC-1 gains a 60s bound.\"],\n \"recommendation_index\": 0\n }\n ]\n }\n }\n ```\n\n4. **Handle the response `status`:**\n - `no_decisions_needed`: no page was written. This should not occur when `system_goals` is always passed. Skip Stage 6 entirely, tell the user there were no open requirements, and proceed to Stage 7 treating the Stage 3 framing as the settled criteria.\n - `decision_page_generated`: surface the returned `file_path` and proceed to Stage 6. **Always proceed to Stage 6 when `decision_page_generated` is returned**, regardless of `actionable_items_count`. A criteria-only page with zero cards still has stance controls that must be submitted.\n\nThis stage is non-blocking: if `generate_decision_page` fails, do not halt. **You MUST output a highly visible warning** (e.g. **⚠ WARNING: The requirements page could not be generated** in bold) explaining that generation failed and that the user should review the criteria in the markdown doc written in Stage 4 instead. Do not silently continue — the failure must be diagnosable from your output. Then ask the user to confirm the criteria in chat before proceeding to Stage 7.\n\n## Stage 6 — Ratify Requirements\n\nCapture the user's stances, settle the criteria, gate on the implications review, and fold the result into the doc. Nothing downstream — including a council in Stage 7 — may start until the criteria are agreed and the implications gate has accepted a proceed token.\n\n1. **Direct the user to the page.** Provide the `file_path` from Stage 5 and tell them to open it in their browser. Explain that they are agreeing on what the system must do — not how it will be built — that they can accept, question, or reject each criterion, and that they can ask questions in chat before submitting.\n\n2. **Q&A loop and commit signal.** Engage with each user message as either a commit or a discussion turn:\n - **Commit:** trim the full message and attempt to parse the entire trimmed message as JSON. Treat it as a commit only when the parsed value 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 per-card fields. The page also submits `acceptance_criteria_feedback` and `nfr_feedback` objects, each keyed by criterion id or NFR category with a `stance` of `agreed`, `ask`, or `disagree` plus a `comment`.\n - **Discussion:** anything that is not commit-shaped JSON. Answer from the doc written in Stage 4 and from codebase lookups. If a JSON-shaped paste is missing one of the three required fields, say which field is missing rather than treating it as a freeform question.\n - **In-flight overrides:** when the user clearly changes an answer in chat (\"AC-2 is wrong\", \"go with eventual for R-1\") or gives new overarching guidance, record it as a working-memory override. On commit, the submitted JSON is the baseline and recorded overrides take precedence; post a one-line acknowledgement naming each overridden item before you rewrite the doc.\n\n3. **Resolve every \"ask\" (hard rule).** After accepting a commit, scan all three: any item in `decisions` where `choice === \"ask\"`, any entry in `acceptance_criteria_feedback` where `stance === \"ask\"`, and any entry in `nfr_feedback` where `stance === \"ask\"`. For each, present the relevant evidence and continue the discussion until the user gives an explicit answer, which you record as an override. Do not proceed while any `ask` remains unresolved — do not honor \"just skip those\".\n\n4. **Resolve every \"disagree\".** A disagree means the criterion is wrong as written. Work out with the user what it should say, restate it back, and get explicit agreement on the corrected wording. A rejected criterion is either rewritten or dropped — never carried forward as-is.\n\n5. This surface's tracked stances are the `acceptance_criteria_feedback` and `nfr_feedback` objects captured above. **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 Retain the latest emitted review; once the gate below allows continuation, write it under `## Implications` during the Stage 6 document rewrite.\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\n6. **Settle the criteria and update the doc.** Rewrite the Acceptance Criteria, Goals & NFRs, and Open Questions sections of `{docs_dir}/explorations/{slug}.md` to the agreed set: fold in every correction, resolve each answered requirement question into the criterion it affects, promote settled criteria out of `open`, and weave `general_comment` in as overarching guidance. Add or update the `## Implications` section with the latest review from item 5 — the final one after any reopen/review loop — or its one-line fail-open notice if generation was unavailable. Set the doc's Status line to \"Requirements ratified\". Preserve all unaffected sections unchanged. **The settled criteria are now the contract** — every later stage is judged against them.\n\nThis stage is non-blocking: if the user never commits, leave the doc as written in Stage 4, tell them the requirements are unratified, and stop without forcing a decision. Do not proceed to a council or a design on unratified criteria.\n\n## Stage 7 — Council Gate\n\nThe criteria are ratified. Now assess honestly whether you know **how** to meet them — and offer to convene a council when you do not.\n\n**Lean toward offering.** A council is cheap relative to committing the user to the wrong design, and this command prioritizes discovery over premature commitment. Do not wait for the user to ask for one.\n\n1. **Check the triggers.** Offer a council when **any** of these hold:\n - More than one materially different approach could satisfy a criterion, and the codebase evidence you gathered cannot separate them.\n - A criterion has no obvious implementation path in the existing code.\n - Meeting one criterion appears to trade off against another criterion or against an NFR (any tension noted in Stage 3 step 6, or created by a correction in Stage 6).\n - The work touches an area with no established pattern — Stage 1 found no precedent to follow.\n - Ratification materially changed the problem — the user tightened a bound, rejected a criterion, or added an obligation you had not framed.\n - Stage 2 research surfaced competing approaches with no clear winner.\n\n Do **not** offer when every ratified criterion maps cleanly onto a well-trodden pattern already used in this codebase and you can point to the precedent.\n\n2. **Ask for approval.** When a trigger fires, first summarize the uncertainty in 1-3 bullets — name the specific criteria at issue and what you cannot currently decide. Then ask exactly:\n\n ```\n Significant uncertainty about how to meet {AC ids}. Run a council before I draft the design? (y/N)\n ```\n\n Mention that a council polls for up to ~15 minutes before you ask, so the user is choosing with the cost in view.\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, note in one line that the council was offered and declined, and proceed to Stage 8 on your own analysis. Never run a council without an explicit affirmative (`y` or `yes`).\n\n3. **Run it on approval.** Call `request_council` with:\n - `task_description`: the task, the **ratified** acceptance criteria and NFRs from Stage 6, and the specific uncertainty you summarized. Sent verbatim — this tool does not read from a file. State plainly that the criteria are settled and the council's job is to find how to meet them, not to revisit what they are.\n - `mode`: `technical`.\n - `wait_for_result`: `true`. `save_locally`: `true`.\n\n While it runs, tell the user it is polling and roughly how long it may take.\n\n4. **Fold the result into your analysis.** Carry the council's approaches, objections, and any option you had not considered into Stage 8. If the council argues a ratified criterion is unmeetable, do not silently drop it — raise it with the user in Stage 8 as an explicit conflict.\n\nIf the council fails or times out, note the failure visibly and proceed with your own analysis — a missing council degrades the design but does not invalidate it. This stage is non-blocking — always proceed to Stage 8.\n\n## Stage 8 — Propose Final Design\n\nNow describe how you would build it. **Do not generate a decision page for this stage.** The requirements page was the user's decision surface; the design is your proposal, written into the doc and discussed in chat. Generating a second page here would ask the user to ratify a solution, which is not what this command does.\n\n1. **Work out the design against the ratified criteria.** Consider the approaches you know plus anything the council surfaced. For each candidate, establish which criteria it satisfies and at what cost. An approach that cannot meet a ratified criterion is not a candidate — discard it and say why.\n\n2. **Resolve any tension explicitly.** Where meeting one criterion costs another, or costs an NFR, state which obligation your design privileges and what that costs the other. Do not let a tension pass silently.\n\n3. **Commit to a single proposed design.** You are recommending, not offering a menu. Name the approach, describe how it works, list the files to create or modify, and map each ratified criterion to the part of the design that satisfies it. Where you seriously considered an alternative, record it and why you rejected it — as history, not as an open choice.\n\n **Record the implementation slices with a size band.** The design's sequence of work is also the proposed ticket split, so name each slice and size it with the ladder in the posture block below — `S` / `M` / `L` / `XL`, by file-touch breadth and rough LOC. Size the slices here, in the design, rather than at the handoff gate: the split is a design decision, and deciding it under the pressure of an approval prompt is how a coherent design turns into a swarm of tiny tickets. A slice that will not fit in `L` becomes one `XL` slice — do not split it to fit a band. Split only when it is genuinely two independent pieces of work, or when it runs past roughly 40 files / ~3000 LOC.\n\n4. **Rewrite `{docs_dir}/explorations/{slug}.md`** so it reads as a finished proposal, not a mechanical append. Set the Status line to \"Design proposed\". Keep the ratified Acceptance Criteria, Goals & NFRs, and `## Implications` sections intact — they are the contract and must not drift — and add:\n\n```markdown\n## Approaches Considered\n\n{Each candidate, what it would mean, and why it was or was not chosen. Note which came from the council, if one ran. If no alternatives were seriously considered, state that and why the path was obvious.}\n\n## Proposed Design\n\n{The recommended approach in enough detail to implement: how it works, the files to create or modify, the sequence of work, and the risks. Reference specific files with file_path:line_number format.}\n\n## Criteria Coverage\n\n{Each ratified criterion mapped to the part of the design that satisfies it, and how it will be verified. Any criterion the design only partially meets must say so plainly.}\n```\n\n5. **Present the design in chat and invite pushback.** Summarize the proposal in at most six bullets — the approach, the files it creates or modifies, the order of the work, and any ratified criterion the design only partially meets — and state clearly that it is a proposal. If the user objects, revise the design — but if their objection actually changes what the system must do rather than how it is built, say so: that is a criteria change, and it means reopening the criteria rather than quietly bending the design around it.\n\nThis stage is non-blocking — always proceed to Stage 9 once the design is written and summarized in chat, even if the user has not responded to it.\n\n## Stage 9 — Ticket Handoff\n\n<!-- BEGIN BRIDGE TICKET-AUTHORING POSTURE -->\n<!-- Canonical source: docs/bridge-ticket-authoring.md.\n This block is duplicated byte-identically onto every carrier. Never edit a\n copy: edit the canonical source and re-copy it verbatim. A cross-surface\n byte-equality test fails the build if any copy drifts by a single byte. -->\n\n## Ticket-authoring posture\n\nDeep reference: `docs/bridge-ticket-authoring.md`.\n\n**Draft through the writer.** Every ticket body — an epic parent, an epic child,\nand an ordinary sibling alike — is drafted by the `jira-ticket-writer` agent\nbefore `create_ticket` is called. Do not compose a ticket description inline.\n\n**Size the work.** Size each ticket by file-touch breadth and depth plus rough\nlines of code (LOC) changed:\n\n- `S = 1-2 files / <~80 LOC`\n- `M = ~3-8 files / ~80-400 LOC`\n- `L = ~8-15 files / ~400-900 LOC`\n- `XL = >15 files / >~900 LOC`\n\nTarget size priority: **L (target) -> XL (when the work does not fit in L) -> M\n(third choice) -> S (only when unavoidable)**. This applies equally to a\nstandalone ticket and to an epic child.\n\nAim each slice at L. When one will not fit, grow it to XL rather than splitting\nit — split only when the slice is genuinely two independent pieces of work,\nnever merely to land inside a band. Bridge's grooming and implementation process\nhandles a large vertical slice well and is overkill on small ones: every extra\nticket is another worktree, another PR, another rebase, and another chance for\ntwo workers to touch the same file. Reach for M because the work genuinely is\nthat size, not to avoid an XL.\n\nBeyond roughly 40 files or ~3000 LOC, split anyway. Past that point review\nturnaround and rebase cost dominate the run's budget, and a review that wedges\nholds the gate to its full retry ceiling before anyone notices.\n\n**Group at three.** Three or more implementable tickets is an epic: propose an\nepic parent plus an ordered child manifest, and resolve this surface's own\napproval gate before anything is created. One or two tickets are ordinary\nsiblings — no epic parent, no manifest. The threshold is exactly three.\n\n**Hand off once.** An epic handoff names exactly one conductor entry point,\n`drive-epic`, which selects the runnable path itself. Never present a choice\nbetween conductors.\n\n**Departure is closed-list only.** These three exceptions, and no others, permit\ndeparting from the rules above. Invoking one requires no announcement.\n\n- **E1 External-tracker mirroring** — a recorded upstream identifier exists and\n its granularity is contractual. Bypasses sizing and the epic threshold.\n- **E2 Discovery-only spike** — no committed production-code deliverable.\n Bypasses sizing only; does not bypass drafting through the writer.\n- **E3 Authorized incident containment** — tied to an active incident record,\n not to schedule pressure. Bypasses sizing and the epic threshold.\n\nThe list is closed. Anything outside it is an escalation to the operator, not a\njudgement call. Explicitly refused as grounds for departure: a single-file\ntrivial fix (that is `S` reached through the normal path, not an exception),\ngeneric time pressure, \"already well specified\", \"faster without the writer\",\ndeveloper discretion, minor refactor, unattended mode, context limits, and \"hard\nto decompose\" (XL is the normal overflow, so that is the ordinary path and not a\ndeparture). Writer unavailability escalates; it never silently authorizes inline\ndrafting.\n\n<!-- END BRIDGE TICKET-AUTHORING POSTURE -->\n\n1. **Assess readiness.** The work is ready to become a ticket when the criteria are ratified, the design is proposed, and no criterion is left unresolved or only partially covered. If something is still open, say what it is and recommend the follow-up that would close it rather than creating a ticket on a soft foundation:\n - A **wider council** (`request_council`) when the design would benefit from a broad review before implementation. If Stage 7 already ran one, only suggest another when something material changed since.\n - A **second opinion** (`second_opinion`) when a few specific contested points need an independent check.\n - **Web or deep research** (`request_deep_research`) when the design still rests on technical unknowns that need grounding.\n\n2. **Decompose once, then offer to create.** When the work is ready, freeze the split before you show it, and show the whole thing. Never present the gate bare — the user cannot consent to a plan they have not been shown.\n\n Count the implementation slices Stage 8 recorded. That count decides the shape, and the threshold is exactly three:\n\n - **Three or more slices → propose an epic.** Freeze an epic parent plus an ordered child manifest in one pass. Per child: title, scope boundary, size band, `depends_on` (hard prerequisites that must land first), `recommended_after` (soft sequencing preferences that are not blockers), and a one-line order rationale. Keep hard prerequisites strictly separate from soft sequencing. A child may be `XL`; grow one rather than splitting it to fit, and split only past roughly 40 files / ~3000 LOC.\n - **One or two slices → ordinary siblings.** No epic parent, no manifest, no decomposition pass. Just the ticket or the pair.\n\n This decomposition is the **single authoritative split** for the rest of the stage. It is decided here, once, with the whole portfolio in view; step 3 renders bodies against it and does not revisit it.\n\n Output the plan outline, kept to roughly one screen:\n\n - **Plan** — the approach in one sentence.\n - **Shape** — `epic` (with the parent's title) or `siblings`. Say which and why the count put it there.\n - **Tickets ({n})** — one line per ticket: title, the slice of scope it covers, its size band, its ratified criterion ids, and — for an epic — its `depends_on` / `recommended_after` and order rationale. Say plainly when it is a single ticket.\n - **Files** — the files to create or modify, grouped by area.\n - **Sequence** — the ordered steps of the work, one line each.\n - **Risks / not covered** — any criterion the design only partially meets, or `none`.\n\n Then ask exactly:\n\n ```\n Requirements ratified and design proposed. Create the ticket(s) now? (y/N)\n ```\n\n Treat an empty response, any negative response, or any ambiguous/unrecognized response as **decline** — do not guess intent. On decline, report that the exploration doc is the artifact, and tell the user they can later ask their agent to use the Jira Ticket Writer to create ticket drafts from it. Never create a ticket without an explicit affirmative (`y` or `yes`) — creation is irreversible.\n\n3. **Render every body through `jira-ticket-writer`, then create.** Nothing calls `create_ticket` before the gate in step 2 resolves with an explicit affirmative.\n\n On approval, fan out **one `jira-ticket-writer` invocation per entry** in the frozen outline — the epic parent, each child, and each ordinary sibling. Each invocation is bound to exactly its own entry and renders a body against that entry's fixed boundary and size band. A child invocation must not re-split, merge, reorder, renumber, or rescope: the decomposition is already frozen, and an invocation that sees one entry is deciding on strictly less information than the pass that saw them all.\n\n Pass into every invocation, verbatim: the ratified acceptance criteria that entry satisfies, the proposed design for its slice, and the entry's own manifest fields. Do not restate or reinterpret the criteria — they were ratified in that wording, and they go to the writer unchanged.\n\n Then create exactly the approved set from the writer's drafts:\n\n - **Siblings** — `create_ticket` per draft, unparented.\n - **Epic** — `create_ticket` with `issue_type` set to `Epic` for the parent first, capture the resolved epic key, then `create_ticket` with `parent_key` set to that key for each child in manifest order. The `parent_key` argument is required on every child call; never omit it.\n\n Create the tickets exactly as the outline named them — the split was decided and shown there, so do not create a different set than the user approved. If you now believe the split is wrong, say so and re-ask rather than changing it silently.\n\n **Track each ticket as you create it.** Immediately after each successful `create_ticket`, call `track_ticket` with that key and the ticket's description. Bridge's workflow state lives in its own database, not in the ticket backend, and an untracked ticket is invisible to it — `update_ticket_state` and `get_ticket_state` both return 404 until something registers the key. Tracking works against either ticket backend and is a safe no-op when the key is already tracked.\n\n Tracking is **warn, not halt**: if `track_ticket` returns an error of any kind, record the reason for the final report and keep going. The ticket exists and is the thing that mattered; tracking can be repeated later.\n\n **On a partial failure, stop and report what exists.** Ticket creation is irreversible and there is no resume protocol here: report the epic key and every child key created so far, name the entry that failed, and tell the user to re-run naming the existing epic rather than repeating this command. Do not retry the failed call in a loop, and do not create the remaining children as though nothing happened — a half-built epic the user cannot see is worse than a stopped one they can.\n\n Idempotency labels are deliberately not used. They ride on `create_ticket`'s `labels` argument and are read back through `get_tickets(labels=...)`, and **both are Jira-only**: against the local ticket backend each returns a terminal `409 UNSUPPORTED_IN_LOCAL_MODE`, and the create rejects without making a ticket. A resume protocol built on them would work on one backend and silently fail on the other.\n\n4. **Write the plan DAG.** After an epic's tickets are created, write the conductor's plan sidecar to `{docs_dir}/epic-plans/{slug}/epic-plan.dag.json`. You already hold everything it needs: the frozen manifest carries each child's `depends_on`, and Stage 8's design named the files each slice touches. Nothing downstream derives this from an epic key, so if you skip it the handoff dead-ends.\n\n ```json\n {\n \"plan_version\": 1,\n \"nodes\": [\n {\n \"ticket_key\": \"KEY-2\",\n \"status\": \"planned\",\n \"depends_on\": [],\n \"touched_files\": [\"src/example/service.py\", \"src/example/handler.py\"]\n }\n ],\n \"edges\": []\n }\n ```\n\n One node per child, in manifest order, using the **real** keys just created — never a `TBD-N` placeholder, since the tickets already exist. `depends_on` carries the manifest's hard prerequisites only; leave `recommended_after` out, because soft sequencing is not a dependency and encoding it there serializes work that could run in parallel. `edges` may be `[]` when `depends_on` already expresses the graph. The graph must be acyclic and every `depends_on` entry must name a node in the file.\n\n Populate `touched_files` on every node from the design's file list, as repository-relative POSIX paths. It is what lets the conductor serialize two children that would otherwise collide, and it matters more the larger the slices are. An omitted list is accepted by the server as an explicit opt-out, so leaving it off does not fail — it silently turns overlap protection off. Use `[]` only when a ticket genuinely touches no repository files.\n\n Include the files that are easy to forget, because those are the ones that actually collided: **anticipated tests, fixtures, golden files, guard allowlists, and policy files**, not only the production modules. Both known misses were exactly this class — two children shared `ticket_type_evaluator.py` *and its test*, and another pair shared golden files and a `save_repo_to_db` test — and in each case nobody predicted the shared file, so no ordering edge was synthesized and both PRs merged over each other. Paths are compared as exact strings after normalization, so name concrete files: a directory or a glob matches nothing.\n\n5. **Hand off once.** Then point at exactly one conductor entry point:\n\n ```\n npx -y @bridge_gpt/mcp-server drive-epic {EPIC-KEY} --plan-file {docs_dir}/epic-plans/{slug}/epic-plan.dag.json\n ```\n\n `drive-epic` reads conductor readiness and selects the runnable path itself. Do not name an underlying conductor, and never present a choice between two of them — two transition authorities on one epic is the failure this single entry point exists to remove. A sibling pair or a lone ticket gets no conductor handoff at all.\n\nThis stage is non-blocking: if the user never answers, leave the doc as written in Stage 8 and stop without forcing a decision.\n\n## Final Report\n\nOn successful completion of all stages, display:\n\n> **Exploration Complete**\n>\n> **Prompt**: {first 80 characters of prompt}...\n> **Output**: {full path to the exploration doc}\n> **Requirements Page**: {full path to the generated requirements.html, or \"not generated\" when generation failed}\n> **Acceptance Criteria**: {count} ratified ({count} corrected by the user, {count} still open)\n> **Files Examined**: {count of key_files_examined}\n> **Research**: {count of web_searches} web searches, {count of research_queries} deep research queries, council {\"run\" | \"offered and declined\" | \"not needed\"}\n>\n> **Requirements**: {\"Ratified\" | \"Unratified — page not submitted\"}\n> **Design**: {\"Proposed\" | \"Not reached\"}\n> **Ticket(s)**: {\"Created: KEY-1, KEY-2\" | \"Declined — doc is the artifact\" | \"Not offered — work not ready\"}\n> **Tracking**: {\"Registered\" | \"Warning: not tracked — {reason}\" | \"n/a — no tickets created\"}\n> **Plan DAG**: {full path to epic-plan.dag.json | \"n/a — not an epic\"}\n\nOn failure at any stage, stop immediately and report:\n- Which stage failed (by number and name)\n- The error details\n- Any partial results that were produced before the failure\n",
|
|
8
|
+
"define-tickets.md": "Explore the codebase for a task, settle its acceptance criteria with the user, then propose a design that meets them.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\n`$ARGUMENTS` is a free-form prompt describing a task you want to accomplish and your goals for it. This is **not** a Jira ticket key — it is plain text describing the work.\n\nExecute all exploration and analysis directly in the main conversation. The user should see exploration progress as it happens.\n\nThis command runs strictly outside-in, and the order is the point:\n\n1. **Requirements first.** Establish what the system must do, how it must behave, and what standards it must meet — then get the user to ratify that on an interactive decision page. The page settles **requirements only**. It never asks the user to pick an implementation.\n2. **Then how.** Only once the criteria are ratified do you consider how to meet them, optionally with a council.\n3. **Then the design.** You describe the final proposed design yourself, in the exploration doc. There is no second decision page.\n4. **Then ticket(s).**\n\nNever invert this. A design proposed against unratified criteria is a guess, and an implementation choice presented before the criteria are settled asks the user to commit to a solution for a problem they have not yet agreed on.\n\nIf any critical stage fails, stop immediately and report which stage failed and why.\n\n## Stage 0 — Setup\n\n1. **Parse prompt**: Extract the prompt text from `$ARGUMENTS`. Trim any surrounding whitespace. If the prompt is empty or whitespace-only, stop immediately and display: `Usage: /define-tickets <prompt describing your task and goals>`\n\n2. **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\n3. **Generate output slug**: Create a kebab-case slug from the prompt — take the first 6-8 meaningful words, strip non-alphanumeric characters, lowercase, and truncate to 60 characters. The slug **must start with a letter** so it is a valid decision-page `ticket_key` in Stage 5 (`/^[A-Za-z][A-Za-z0-9_-]*$/`); if it would start with a digit or hyphen, prefix it with `exploration-`. If `{docs_dir}/explorations/{slug}.md` already exists, append a short timestamp suffix (e.g., `-1710000000`) — and fold that suffix **into the `slug` variable itself**, not just the filename, so that Stage 4 (the doc), Stage 5 (`ticket_key`, `output_filename`), and Stage 8 (the doc rewrite) all reference the same slug. The output file path is `{docs_dir}/explorations/{slug}.md`.\n\n4. **Initialize tracking**: Prepare to track `key_files_examined` (list of files read during exploration), `web_searches` (list of topics searched), and `research_queries` (list of deep research queries).\n\nIf this stage fails, stop immediately and report the error. Do not proceed to Stage 1.\n\n## Stage 1 — Codebase Exploration\n\nThis is the core discovery stage. Take your time — thorough exploration is more valuable than speed.\n\n1. **Analyze the prompt** to identify which areas of the codebase are relevant: route files, agent flows, database models, library utilities, LLM integration, MCP server, unit and E2E suites, etc.\n\n2. **Search for files** matching patterns related to the task (e.g., `api/routes/**/*.py`, `src/python/llms/agents/**/*.py`, `db/models/*.py`).\n\n3. **Search for content** — relevant function names, class names, patterns, and keywords across the codebase.\n\n4. **Read the most relevant files** in detail — understand existing implementations, conventions, and patterns that relate to the task.\n\n5. **Build a mental model** of:\n - What exists today that relates to the task\n - What patterns and conventions are used in similar features\n - What dependencies, data flows, and integration points are involved\n - What gaps or unknowns remain that need external research\n - Whether there is an established precedent for this kind of work, or none at all — Stage 7 depends on this judgement\n\nExplore to understand the problem and its constraints. Resist designing a solution while you read — you do not yet know what the system is required to do, and Stage 8 is where the design gets written.\n\n6. **Track all significant files** examined in `key_files_examined`.\n\nDo not rush this stage. When in doubt, read more code rather than less. Continue exploring until you have a solid understanding of the relevant code.\n\nThis stage is non-blocking — always proceed to Stage 2 regardless of what you find, since the exploration informs what research is needed.\n\n## Stage 2 — Research Unknowns\n\nBased on gaps identified in Stage 1, decide what research is needed. Apply these decision rules:\n\n- **No research needed**: The codebase exploration answered all questions. Skip directly to Stage 3.\n- **Web search**: For quick factual lookups — library API signatures, configuration syntax, small \"how to\" questions. Examples: \"FastAPI dependency injection with custom headers\", \"Alembic batch migration syntax\". Do web searches inline and capture relevant findings.\n- **Deep research** (via `request_deep_research` MCP tool): For large, multi-faceted unknowns that require synthesizing information from multiple sources. Examples: \"Best practices for implementing WebSocket connection pooling in Python asyncio\", \"Tradeoffs between different approaches to real-time notification delivery in FastAPI applications\". Only use deep research when the question genuinely needs a multi-source investigation.\n\n**If deep research is needed:**\n\n1. Call `request_deep_research` with `wait_for_result` set to `true`, `save_locally` set to `true`, a descriptive `query`, and `context` describing the Bridge API tech stack and the specific task.\n2. If deep research fails, note the failure and fall back to web searches for the same topic. Do NOT halt the pipeline.\n\nTrack all research performed in `research_queries` and `web_searches`.\n\nThis stage is non-blocking — failures degrade the quality of analysis but do not stop the command. Log a warning for any failed research and continue.\n\n## Stage 3 — Frame Acceptance Criteria\n\nEstablish what \"done and correct\" means. Everything in this stage is about the system's obligations, not its implementation. Do not name a technical approach here — that is Stage 8's job, and it does not happen until the user has ratified this framing.\n\n1. **State the frame plainly (required).**\n - **Business goal** — the value this work delivers and why it matters.\n - **Desired end-state** — the concrete state the system should reach once this work is done.\n - **System behavior** — how the system must behave to complete its task (the quality attributes in prose, not a feature list).\n\n2. **Derive the acceptance criteria — what the system must do (required).** Write 3-8 criteria. Each one gets:\n - An `id` (`AC-1`, `AC-2`, …).\n - A `criterion` — a single obligation stated concretely enough to be checked. Write it as observable behavior (\"an operator who revokes a key sees the next request rejected\"), not as a task (\"add a revocation endpoint\").\n - A `verification` — how we would confirm it holds. Name the observable signal: a response code on a specific route, a row state, a log line, a rendered element, a user-visible outcome. **A criterion nobody can check is not yet a criterion** — sharpen it or drop it.\n - A `status`, using the rubric in step 4.\n\n Cover the failure and edge behavior, not just the happy path. If the work changes something that already exists, at least one criterion should pin down what must **not** regress.\n\n3. **Identify the non-functional requirements — the standards the system must meet (required).** Consider every one of these canonical NFR categories and include the ones that genuinely apply (omit the rest): security/privacy, performance/latency, reliability/failure-modes, observability/auditability, accessibility/UX, data-integrity/migration, compatibility, operability/config, compliance/SOC2, rollout/reversibility. For each NFR you include, write its `requirement` and its `implication` (what it changes about the implementation) — an NFR with no concrete implication is boilerplate; drop it.\n\n4. **Classify every acceptance criterion and every NFR** with this rubric: `confirmed` only if explicitly stated or observable in code; `assumed` only if a low-risk, reversible default; `open` if it touches architecture, the data model, security, user-visible behavior, migration, or irreversible creation and is not settled. When a criterion or an NFR is genuinely unclear, prefer marking it `open` and asking. Clear criteria make everything downstream more accurate, so surfacing an unclear one is a success, not a delay.\n\n5. **Frame the open requirement questions.** Where a requirement is unsettled **and** has discrete candidate answers, express it as a question the user can answer by clicking (e.g. \"Must revocation take effect immediately, or is eventual acceptable?\"). These become cards in Stage 5. They are questions about *what the system must do* — never about how to build it. If a question has no discrete answers, leave it as prose in the doc instead.\n\n6. **Sanity-check the frame against itself.** Do any two criteria conflict? Does a criterion conflict with an NFR (e.g. an auditability requirement against a latency budget)? Note every tension you find — Stage 7 treats these as a council trigger, and Stage 8 must resolve them explicitly rather than quietly favouring one side.\n\nThis stage is inline analysis — no tool calls required. This stage is non-blocking — always proceed to Stage 4.\n\n## Stage 4 — Write Requirements Draft\n\nWrite what you know so far to disk, so the user has something to read alongside the decision page. The design is deliberately absent — it does not exist yet.\n\n1. Create the `explorations/` directory under `docs_dir` if it does not exist.\n\n2. Write the exploration document to the slug-based path determined in Stage 0 (`{docs_dir}/explorations/{slug}.md`) with this structure:\n\n```markdown\n# Exploration: {concise summary of the prompt}\n\n**Date**: {current date}\n**Prompt**: {original prompt text}\n**Status**: Requirements drafted — awaiting ratification\n\n## Context\n\n{Brief description of the task and what areas of the codebase are relevant.}\n\n## Acceptance Criteria\n\n{The criteria from Stage 3 — what the system must do. One entry per criterion: its id, the criterion itself, how it is verified, and its status (confirmed / assumed / open).}\n\n## Goals & NFRs\n\n{The business goal, desired end-state, and required system behavior from Stage 3. Then the non-functional requirements — the standards the system must meet: each with its category, requirement, implication, and status (confirmed / assumed / open). Note any tension between criteria or between a criterion and an NFR.}\n\n## Open Questions\n\n{Requirement questions that are still unsettled. Mark which ones are going onto the decision page as cards and which are open-ended prose.}\n\n## Codebase Findings\n\n{Key discoveries from Stage 1. What exists today, what patterns are used, what the relevant code paths look like. Reference specific files and functions with file_path:line_number format.}\n\n## Research Findings\n\n{Findings from web searches and deep research, if any. If no research was performed, state \"No external research was needed.\"}\n\n## Key Files\n\n{Bulleted list of the most important files examined, with one-line descriptions of their relevance.}\n```\n\nDo not add a design, an implementation plan, or a recommendation to this draft. Stage 8 adds those once the criteria are settled.\n\nIf the file cannot be written, stop immediately and report the failure.\n\n## Stage 5 — Generate Requirements Decision Page\n\nTurn the Stage 3 framing into an interactive HTML decision page so the user can ratify the requirements by clicking. **This page settles requirements only.** It must not contain a single implementation option — the user is agreeing on what the system must do, not choosing how to build it.\n\n1. **Map the acceptance criteria to `acceptance_criteria`.** Each entry has `id`, `criterion`, `verification`, and `status`. Ids must be unique — a duplicate id is rejected, because the id is the key the page reports the user's stance under. Every criterion renders with an Agreed / Ask about this / Disagree control, so pass all of them, not only the open ones. Pass the NFRs the same way under `nfrs`.\n\n2. **Map each open requirement question from Stage 3 step 5 to an actionable item.** Each entry has:\n - `id`: a short stable id, e.g. `R-1`, `R-2`.\n - `question`: the requirement question.\n - `options`: the 2-4 candidate answers (string array). Do **not** include \"None of these\" or \"Ask about this\" — the renderer auto-appends both.\n - `option_consequences`: what each answer would mean for the criteria, **parallel to and the same length as** `options`.\n - `why_it_matters`: the concrete impact line.\n - `recommendation_explanation`: why the recommended answer is best.\n - `recommendation_index`: the 0-based index of the recommended answer (must be within `options`).\n - `codebase_evidence` (optional): the Assessment paragraph plus `file:line` citations, shown collapsed.\n\n **These cards are requirement questions, never implementation choices.** \"Must revocation be immediate or is eventual acceptable?\" is a valid card. \"Should we use a short-TTL cache or pub/sub invalidation?\" is not — it is a solution, it belongs to Stage 8, and putting it here defeats the purpose of the page. If you cannot phrase a card without naming a mechanism, it is not a requirement question. When there are no such questions, pass an empty array — a criteria-only page is expected and renders correctly.\n\n3. **Call `generate_decision_page`** with routing fields at the root and all heavy arrays nested under `content`:\n - `artifact_type`: `pre_ticket_planning` (renders the acceptance-criteria and goals panel above any cards).\n - `ticket_key`: the Stage 0 `slug` (a non-Jira slug is fine — it must start with a letter and contain only letters, digits, hyphens, or underscores).\n - `output_subdir`: `explorations` (so the page lands beside the markdown doc).\n - `output_filename`: `{slug}-requirements.html`.\n - `labels`: requirements-flavored overrides, e.g. `title` = \"Requirements\", `section_heading` = \"Open Requirement Questions\", and an `intro` that frames the page as agreeing on what the system must do before any design work begins.\n - `content`: an object containing `system_goals` and `actionable_items`. **`system_goals` MUST ALWAYS be passed** inside `content` so the backend always writes a page. Never omit it, even if every criterion and NFR is confirmed. `acceptance_criteria` and `nfrs` both live inside `system_goals`. (Do not pass `implementation_order` inside `content` — that is for epic surfaces, not a single explored task.)\n\n ```typescript\n interface ExploreTicketContent {\n system_goals?: {\n business_goal: string;\n desired_end_state: string;\n system_behavior: string;\n acceptance_criteria?: Array<{\n id: string; // e.g. \"AC-1\"; must be unique\n criterion: string; // what the system must do\n verification: string; // how we would confirm it holds\n status: \"confirmed\" | \"assumed\" | \"open\";\n }>;\n nfrs?: Array<{\n category: string;\n requirement: string;\n implication: string;\n status: \"confirmed\" | \"assumed\" | \"open\";\n }>;\n };\n actionable_items?: Array<{\n id: string; // e.g. \"R-1\"; a REQUIREMENT question, not a design choice\n question: string;\n why_it_matters: string;\n recommendation_explanation: string;\n options: string[]; // 2-4 candidate answers (no \"None of these\" or \"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 original_question?: string; // optional: only when item maps to a verbatim question\n }>;\n // clear_improvements: not used by this command — it captures requirements, not findings\n // implementation_order: for epic surfaces only — do NOT include for single task explorations\n // depends_on: hard prerequisites (titles/keys that must land first)\n // recommended_after: soft sequencing preferences, not hard blockers\n }\n ```\n\n Example call:\n ```json\n {\n \"ticket_key\": \"revoke-api-keys\",\n \"artifact_type\": \"pre_ticket_planning\",\n \"output_subdir\": \"explorations\",\n \"output_filename\": \"revoke-api-keys-requirements.html\",\n \"labels\": { \"title\": \"Requirements\", \"section_heading\": \"Open Requirement Questions\" },\n \"content\": {\n \"system_goals\": {\n \"business_goal\": \"Operators can cut off a leaked key immediately.\",\n \"desired_end_state\": \"Revocation is self-serve and takes effect at once.\",\n \"system_behavior\": \"Rejects revoked credentials without a restart.\",\n \"acceptance_criteria\": [\n { \"id\": \"AC-1\", \"criterion\": \"An operator who revokes a key sees the next request with it rejected.\", \"verification\": \"The following call to the protected route returns 401.\", \"status\": \"confirmed\" },\n { \"id\": \"AC-2\", \"criterion\": \"Revocation is recorded with actor and timestamp.\", \"verification\": \"An audit row names the operator and the revoked key id.\", \"status\": \"open\" }\n ],\n \"nfrs\": [\n { \"category\": \"security/privacy\", \"requirement\": \"The raw key is never logged on the revoke path.\", \"implication\": \"Log the key id, never the secret.\", \"status\": \"open\" }\n ]\n },\n \"actionable_items\": [\n {\n \"id\": \"R-1\",\n \"question\": \"Must revocation take effect immediately, or is eventual acceptable?\",\n \"why_it_matters\": \"Sets the hard bound AC-1 has to meet.\",\n \"recommendation_explanation\": \"A leaked key is an active incident; eventual leaves a usable window.\",\n \"options\": [\"Immediately (under 5s)\", \"Eventually (under 60s is acceptable)\"],\n \"option_consequences\": [\"AC-1 gains a 5s bound.\", \"AC-1 gains a 60s bound.\"],\n \"recommendation_index\": 0\n }\n ]\n }\n }\n ```\n\n4. **Handle the response `status`:**\n - `no_decisions_needed`: no page was written. This should not occur when `system_goals` is always passed. Skip Stage 6 entirely, tell the user there were no open requirements, and proceed to Stage 7 treating the Stage 3 framing as the settled criteria.\n - `decision_page_generated`: surface the returned `file_path` and proceed to Stage 6. **Always proceed to Stage 6 when `decision_page_generated` is returned**, regardless of `actionable_items_count`. A criteria-only page with zero cards still has stance controls that must be submitted.\n\nThis stage is non-blocking: if `generate_decision_page` fails, do not halt. **You MUST output a highly visible warning** (e.g. **⚠ WARNING: The requirements page could not be generated** in bold) explaining that generation failed and that the user should review the criteria in the markdown doc written in Stage 4 instead. Do not silently continue — the failure must be diagnosable from your output. Then ask the user to confirm the criteria in chat before proceeding to Stage 7.\n\n## Stage 6 — Ratify Requirements\n\nCapture the user's stances, settle the criteria, gate on the implications review, and fold the result into the doc. Nothing downstream — including a council in Stage 7 — may start until the criteria are agreed and the implications gate has accepted a proceed token.\n\n1. **Direct the user to the page.** Provide the `file_path` from Stage 5 and tell them to open it in their browser. Explain that they are agreeing on what the system must do — not how it will be built — that they can accept, question, or reject each criterion, and that they can ask questions in chat before submitting.\n\n2. **Q&A loop and commit signal.** Engage with each user message as either a commit or a discussion turn:\n - **Commit:** trim the full message and attempt to parse the entire trimmed message as JSON. Treat it as a commit only when the parsed value 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 per-card fields. The page also submits `acceptance_criteria_feedback` and `nfr_feedback` objects, each keyed by criterion id or NFR category with a `stance` of `agreed`, `ask`, or `disagree` plus a `comment`.\n - **Discussion:** anything that is not commit-shaped JSON. Answer from the doc written in Stage 4 and from codebase lookups. If a JSON-shaped paste is missing one of the three required fields, say which field is missing rather than treating it as a freeform question.\n - **In-flight overrides:** when the user clearly changes an answer in chat (\"AC-2 is wrong\", \"go with eventual for R-1\") or gives new overarching guidance, record it as a working-memory override. On commit, the submitted JSON is the baseline and recorded overrides take precedence; post a one-line acknowledgement naming each overridden item before you rewrite the doc.\n\n3. **Resolve every \"ask\" (hard rule).** After accepting a commit, scan all three: any item in `decisions` where `choice === \"ask\"`, any entry in `acceptance_criteria_feedback` where `stance === \"ask\"`, and any entry in `nfr_feedback` where `stance === \"ask\"`. For each, present the relevant evidence and continue the discussion until the user gives an explicit answer, which you record as an override. Do not proceed while any `ask` remains unresolved — do not honor \"just skip those\".\n\n4. **Resolve every \"disagree\".** A disagree means the criterion is wrong as written. Work out with the user what it should say, restate it back, and get explicit agreement on the corrected wording. A rejected criterion is either rewritten or dropped — never carried forward as-is.\n\n5. This surface's tracked stances are the `acceptance_criteria_feedback` and `nfr_feedback` objects captured above. **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 Retain the latest emitted review; once the gate below allows continuation, write it under `## Implications` during the Stage 6 document rewrite.\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\n6. **Settle the criteria and update the doc.** Rewrite the Acceptance Criteria, Goals & NFRs, and Open Questions sections of `{docs_dir}/explorations/{slug}.md` to the agreed set: fold in every correction, resolve each answered requirement question into the criterion it affects, promote settled criteria out of `open`, and weave `general_comment` in as overarching guidance. Add or update the `## Implications` section with the latest review from item 5 — the final one after any reopen/review loop — or its one-line fail-open notice if generation was unavailable. Set the doc's Status line to \"Requirements ratified\". Preserve all unaffected sections unchanged. **The settled criteria are now the contract** — every later stage is judged against them.\n\nThis stage is non-blocking: if the user never commits, leave the doc as written in Stage 4, tell them the requirements are unratified, and stop without forcing a decision. Do not proceed to a council or a design on unratified criteria.\n\n## Stage 7 — Council Gate\n\nThe criteria are ratified. Now assess honestly whether you know **how** to meet them — and offer to convene a council when you do not.\n\n**Lean toward offering.** A council is cheap relative to committing the user to the wrong design, and this command prioritizes discovery over premature commitment. Do not wait for the user to ask for one.\n\n1. **Check the triggers.** Offer a council when **any** of these hold:\n - More than one materially different approach could satisfy a criterion, and the codebase evidence you gathered cannot separate them.\n - A criterion has no obvious implementation path in the existing code.\n - Meeting one criterion appears to trade off against another criterion or against an NFR (any tension noted in Stage 3 step 6, or created by a correction in Stage 6).\n - The work touches an area with no established pattern — Stage 1 found no precedent to follow.\n - Ratification materially changed the problem — the user tightened a bound, rejected a criterion, or added an obligation you had not framed.\n - Stage 2 research surfaced competing approaches with no clear winner.\n\n Do **not** offer when every ratified criterion maps cleanly onto a well-trodden pattern already used in this codebase and you can point to the precedent.\n\n2. **Ask for approval.** When a trigger fires, first summarize the uncertainty in 1-3 bullets — name the specific criteria at issue and what you cannot currently decide. Then ask exactly:\n\n ```\n Significant uncertainty about how to meet {AC ids}. Run a council before I draft the design? (y/N)\n ```\n\n Mention that a council polls for up to ~15 minutes before you ask, so the user is choosing with the cost in view.\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, note in one line that the council was offered and declined, and proceed to Stage 8 on your own analysis. Never run a council without an explicit affirmative (`y` or `yes`).\n\n3. **Run it on approval.** Call `request_council` with:\n - `task_description`: the task, the **ratified** acceptance criteria and NFRs from Stage 6, and the specific uncertainty you summarized. Sent verbatim — this tool does not read from a file. State plainly that the criteria are settled and the council's job is to find how to meet them, not to revisit what they are.\n - `mode`: `technical`.\n - `wait_for_result`: `true`. `save_locally`: `true`.\n\n While it runs, tell the user it is polling and roughly how long it may take.\n\n4. **Fold the result into your analysis.** Carry the council's approaches, objections, and any option you had not considered into Stage 8. If the council argues a ratified criterion is unmeetable, do not silently drop it — raise it with the user in Stage 8 as an explicit conflict.\n\nIf the council fails or times out, note the failure visibly and proceed with your own analysis — a missing council degrades the design but does not invalidate it. This stage is non-blocking — always proceed to Stage 8.\n\n## Stage 8 — Propose Final Design\n\nNow describe how you would build it. **Do not generate a decision page for this stage.** The requirements page was the user's decision surface; the design is your proposal, written into the doc and discussed in chat. Generating a second page here would ask the user to ratify a solution, which is not what this command does.\n\n1. **Work out the design against the ratified criteria.** Consider the approaches you know plus anything the council surfaced. For each candidate, establish which criteria it satisfies and at what cost. An approach that cannot meet a ratified criterion is not a candidate — discard it and say why.\n\n2. **Resolve any tension explicitly.** Where meeting one criterion costs another, or costs an NFR, state which obligation your design privileges and what that costs the other. Do not let a tension pass silently.\n\n3. **Commit to a single proposed design.** You are recommending, not offering a menu. Name the approach, describe how it works, list the files to create or modify, and map each ratified criterion to the part of the design that satisfies it. Where you seriously considered an alternative, record it and why you rejected it — as history, not as an open choice.\n\n **Record the implementation slices with a size band.** The design's sequence of work is also the proposed ticket split, so name each slice and size it with the ladder in the posture block below — `S` / `M` / `L` / `XL`, by file-touch breadth and rough LOC. Size the slices here, in the design, rather than at the handoff gate: the split is a design decision, and deciding it under the pressure of an approval prompt is how a coherent design turns into a swarm of tiny tickets. A slice that will not fit in `L` becomes one `XL` slice — do not split it to fit a band. Split only when it is genuinely two independent pieces of work, or when it runs past roughly 40 files / ~3000 LOC.\n\n4. **Rewrite `{docs_dir}/explorations/{slug}.md`** so it reads as a finished proposal, not a mechanical append. Set the Status line to \"Design proposed\". Keep the ratified Acceptance Criteria, Goals & NFRs, and `## Implications` sections intact — they are the contract and must not drift — and add:\n\n```markdown\n## Approaches Considered\n\n{Each candidate, what it would mean, and why it was or was not chosen. Note which came from the council, if one ran. If no alternatives were seriously considered, state that and why the path was obvious.}\n\n## Proposed Design\n\n{The recommended approach in enough detail to implement: how it works, the files to create or modify, the sequence of work, and the risks. Reference specific files with file_path:line_number format.}\n\n## Criteria Coverage\n\n{Each ratified criterion mapped to the part of the design that satisfies it, and how it will be verified. Any criterion the design only partially meets must say so plainly.}\n```\n\n5. **Present the design in chat and invite pushback.** Summarize the proposal in at most six bullets — the approach, the files it creates or modifies, the order of the work, and any ratified criterion the design only partially meets — and state clearly that it is a proposal. If the user objects, revise the design — but if their objection actually changes what the system must do rather than how it is built, say so: that is a criteria change, and it means reopening the criteria rather than quietly bending the design around it.\n\nThis stage is non-blocking — always proceed to Stage 9 once the design is written and summarized in chat, even if the user has not responded to it.\n\n## Stage 9 — Ticket Handoff\n\n<!-- BEGIN BRIDGE TICKET-AUTHORING POSTURE -->\n<!-- Canonical source: docs/bridge-ticket-authoring.md.\n This block is duplicated byte-identically onto every carrier. Never edit a\n copy: edit the canonical source and re-copy it verbatim. A cross-surface\n byte-equality test fails the build if any copy drifts by a single byte. -->\n\n## Ticket-authoring posture\n\nDeep reference: `docs/bridge-ticket-authoring.md`.\n\n**Draft through the writer.** Every ticket body — an epic parent, an epic child,\nand an ordinary sibling alike — is drafted by the `jira-ticket-writer` agent\nbefore `create_ticket` is called. Do not compose a ticket description inline.\n\n**Size the work.** Size each ticket by file-touch breadth and depth plus rough\nlines of code (LOC) changed:\n\n- `S = 1-2 files / <~80 LOC`\n- `M = ~3-8 files / ~80-400 LOC`\n- `L = ~8-15 files / ~400-900 LOC`\n- `XL = >15 files / >~900 LOC`\n\nTarget size priority: **L (target) -> XL (when the work does not fit in L) -> M\n(third choice) -> S (only when unavoidable)**. This applies equally to a\nstandalone ticket and to an epic child.\n\nAim each slice at L. When one will not fit, grow it to XL rather than splitting\nit — split only when the slice is genuinely two independent pieces of work,\nnever merely to land inside a band. Bridge's grooming and implementation process\nhandles a large vertical slice well and is overkill on small ones: every extra\nticket is another worktree, another PR, another rebase, and another chance for\ntwo workers to touch the same file. Reach for M because the work genuinely is\nthat size, not to avoid an XL.\n\nBeyond roughly 40 files or ~3000 LOC, split anyway. Past that point review\nturnaround and rebase cost dominate the run's budget, and a review that wedges\nholds the gate to its full retry ceiling before anyone notices.\n\n**Group at three.** Three or more implementable tickets is an epic: propose an\nepic parent plus an ordered child manifest, and resolve this surface's own\napproval gate before anything is created. One or two tickets are ordinary\nsiblings — no epic parent, no manifest. The threshold is exactly three.\n\n**Hand off once.** An epic handoff names exactly one conductor entry point,\n`drive-epic`, which selects the runnable path itself. Never present a choice\nbetween conductors.\n\n**Departure is closed-list only.** These three exceptions, and no others, permit\ndeparting from the rules above. Invoking one requires no announcement.\n\n- **E1 External-tracker mirroring** — a recorded upstream identifier exists and\n its granularity is contractual. Bypasses sizing and the epic threshold.\n- **E2 Discovery-only spike** — no committed production-code deliverable.\n Bypasses sizing only; does not bypass drafting through the writer.\n- **E3 Authorized incident containment** — tied to an active incident record,\n not to schedule pressure. Bypasses sizing and the epic threshold.\n\nThe list is closed. Anything outside it is an escalation to the operator, not a\njudgement call. Explicitly refused as grounds for departure: a single-file\ntrivial fix (that is `S` reached through the normal path, not an exception),\ngeneric time pressure, \"already well specified\", \"faster without the writer\",\ndeveloper discretion, minor refactor, unattended mode, context limits, and \"hard\nto decompose\" (XL is the normal overflow, so that is the ordinary path and not a\ndeparture). Writer unavailability escalates; it never silently authorizes inline\ndrafting.\n\n<!-- END BRIDGE TICKET-AUTHORING POSTURE -->\n\n1. **Assess readiness.** The work is ready to become a ticket when the criteria are ratified, the design is proposed, and no criterion is left unresolved or only partially covered. If something is still open, say what it is and recommend the follow-up that would close it rather than creating a ticket on a soft foundation:\n - A **wider council** (`request_council`) when the design would benefit from a broad review before implementation. If Stage 7 already ran one, only suggest another when something material changed since.\n - A **second opinion** (`second_opinion`) when a few specific contested points need an independent check.\n - **Web or deep research** (`request_deep_research`) when the design still rests on technical unknowns that need grounding.\n\n2. **Decompose once, then offer to create.** When the work is ready, freeze the split before you show it, and show the whole thing. Never present the gate bare — the user cannot consent to a plan they have not been shown.\n\n Count the implementation slices Stage 8 recorded. That count decides the shape, and the threshold is exactly three:\n\n - **Three or more slices → propose an epic.** Freeze an epic parent plus an ordered child manifest in one pass. Per child: title, scope boundary, size band, `depends_on` (hard prerequisites that must land first), `recommended_after` (soft sequencing preferences that are not blockers), and a one-line order rationale. Keep hard prerequisites strictly separate from soft sequencing. A child may be `XL`; grow one rather than splitting it to fit, and split only past roughly 40 files / ~3000 LOC.\n - **One or two slices → ordinary siblings.** No epic parent, no manifest, no decomposition pass. Just the ticket or the pair.\n\n This decomposition is the **single authoritative split** for the rest of the stage. It is decided here, once, with the whole portfolio in view; step 3 renders bodies against it and does not revisit it.\n\n Output the plan outline, kept to roughly one screen:\n\n - **Plan** — the approach in one sentence.\n - **Shape** — `epic` (with the parent's title) or `siblings`. Say which and why the count put it there.\n - **Tickets ({n})** — one line per ticket: title, the slice of scope it covers, its size band, its ratified criterion ids, and — for an epic — its `depends_on` / `recommended_after` and order rationale. Say plainly when it is a single ticket.\n - **Files** — the files to create or modify, grouped by area.\n - **Sequence** — the ordered steps of the work, one line each.\n - **Risks / not covered** — any criterion the design only partially meets, or `none`.\n\n Then ask exactly:\n\n ```\n Requirements ratified and design proposed. Create the ticket(s) now? (y/N)\n ```\n\n Treat an empty response, any negative response, or any ambiguous/unrecognized response as **decline** — do not guess intent. On decline, report that the exploration doc is the artifact, and tell the user they can later ask their agent to use the Jira Ticket Writer to create ticket drafts from it. Never create a ticket without an explicit affirmative (`y` or `yes`) — creation is irreversible.\n\n3. **Render every body through `jira-ticket-writer`, then create.** Nothing calls `create_ticket` before the gate in step 2 resolves with an explicit affirmative.\n\n On approval, fan out **one `jira-ticket-writer` invocation per entry** in the frozen outline — the epic parent, each child, and each ordinary sibling. Each invocation is bound to exactly its own entry and renders a body against that entry's fixed boundary and size band. A child invocation must not re-split, merge, reorder, renumber, or rescope: the decomposition is already frozen, and an invocation that sees one entry is deciding on strictly less information than the pass that saw them all.\n\n Pass into every invocation, verbatim: the ratified acceptance criteria that entry satisfies, the proposed design for its slice, and the entry's own manifest fields. Do not restate or reinterpret the criteria — they were ratified in that wording, and they go to the writer unchanged.\n\n Then create exactly the approved set from the writer's drafts:\n\n - **Siblings** — `create_ticket` per draft, unparented.\n - **Epic** — `create_ticket` with `issue_type` set to `Epic` for the parent first, capture the resolved epic key, then `create_ticket` with `parent_key` set to that key for each child in manifest order. The `parent_key` argument is required on every child call; never omit it.\n\n Create the tickets exactly as the outline named them — the split was decided and shown there, so do not create a different set than the user approved. If you now believe the split is wrong, say so and re-ask rather than changing it silently.\n\n **Track each ticket as you create it.** Immediately after each successful `create_ticket`, call `track_ticket` with that key and the ticket's description. Bridge's workflow state lives in its own database, not in the ticket backend, and an untracked ticket is invisible to it — `update_ticket_state` and `get_ticket_state` both return 404 until something registers the key. Tracking works against either ticket backend and is a safe no-op when the key is already tracked.\n\n Tracking is **warn, not halt**: if `track_ticket` returns an error of any kind, record the reason for the final report and keep going. The ticket exists and is the thing that mattered; tracking can be repeated later.\n\n **On a partial failure, stop and report what exists.** Ticket creation is irreversible and there is no resume protocol here: report the epic key and every child key created so far, name the entry that failed, and tell the user to re-run naming the existing epic rather than repeating this command. Do not retry the failed call in a loop, and do not create the remaining children as though nothing happened — a half-built epic the user cannot see is worse than a stopped one they can.\n\n Idempotency labels are deliberately not used. They ride on `create_ticket`'s `labels` argument and are read back through `get_tickets(labels=...)`, and **both are Jira-only**: against the local ticket backend each returns a terminal `409 UNSUPPORTED_IN_LOCAL_MODE`, and the create rejects without making a ticket. A resume protocol built on them would work on one backend and silently fail on the other.\n\n4. **Write the plan DAG.** After an epic's tickets are created, write the conductor's plan sidecar to `{docs_dir}/epic-plans/{slug}/epic-plan.dag.json`. You already hold everything it needs: the frozen manifest carries each child's `depends_on`, and Stage 8's design named the files each slice touches. Nothing downstream derives this from an epic key, so if you skip it the handoff dead-ends.\n\n ```json\n {\n \"plan_version\": 1,\n \"nodes\": [\n {\n \"ticket_key\": \"KEY-2\",\n \"status\": \"planned\",\n \"depends_on\": [],\n \"touched_files\": [\"src/example/service.py\", \"src/example/handler.py\"]\n }\n ],\n \"edges\": []\n }\n ```\n\n One node per child, in manifest order, using the **real** keys just created — never a `TBD-N` placeholder, since the tickets already exist. `depends_on` carries the manifest's hard prerequisites only; leave `recommended_after` out, because soft sequencing is not a dependency and encoding it there serializes work that could run in parallel. `edges` may be `[]` when `depends_on` already expresses the graph. The graph must be acyclic and every `depends_on` entry must name a node in the file.\n\n Populate `touched_files` on every node from the design's file list, as repository-relative POSIX paths. It is what lets the conductor serialize two children that would otherwise collide, and it matters more the larger the slices are. An omitted list is accepted by the server as an explicit opt-out, so leaving it off does not fail — it silently turns overlap protection off. Use `[]` only when a ticket genuinely touches no repository files.\n\n Include the files that are easy to forget, because those are the ones that actually collided: **anticipated tests, fixtures, golden files, guard allowlists, and policy files**, not only the production modules. Both known misses were exactly this class — two children shared `ticket_type_evaluator.py` *and its test*, and another pair shared golden files and a `save_repo_to_db` test — and in each case nobody predicted the shared file, so no ordering edge was synthesized and both PRs merged over each other. Paths are compared as exact strings after normalization, so name concrete files: a directory or a glob matches nothing.\n\n5. **Hand off once.** Then point at exactly one conductor entry point:\n\n ```\n npx -y @bridge_gpt/mcp-server drive-epic {EPIC-KEY} --plan-file {docs_dir}/epic-plans/{slug}/epic-plan.dag.json --policy-file <path to a JSON policy declaring notify.webhook_url> --launch-profile <name>\n ```\n\n `--launch-profile` selects the host-local launch profile that one-time guided setup approved (`npx -y @bridge_gpt/mcp-server install conductor --launch-profile <name> --base-url <url> --policy-file <path> --required-check <name>`): the Bridge API target, repository, execution mode, runtime, and the policy and credential references. It is what makes the command run unchanged in a fresh shell — no `BAPI_BASE_URL` export, no temporary notification terminal. If this host has no usable profile yet, hand off to that setup command first instead; it ends in `incomplete-setup` with the named gap when a prerequisite is unproven, and prints the one launch command only once everything holds. `drive-epic` never defaults to production: a missing or conflicting target refuses before any authenticated request.\n\n `drive-epic` defaults to the unattended posture, which refuses with `unattended_notify_default` unless `--policy-file` declares `notify.webhook_url`; pass `--attended` to opt out of that default instead. `drive-epic` reads conductor readiness and selects the runnable path itself. Do not name an underlying conductor, and never present a choice between two of them — two transition authorities on one epic is the failure this single entry point exists to remove. A sibling pair or a lone ticket gets no conductor handoff at all.\n\n Recommended practice: **the command creates the run; a session drives it.** Once the command returns \"the plane is READY\", open a session and run `/drive-epic-run {EPIC-KEY}`. Every unattended epic-branch run so far has needed operator recoveries within minutes of a trigger, and the reconciler never applies them — a command-only launch leaves nobody to apply them, so the run parks or blocks. The command creates and starts the run; the session observes and applies the sanctioned recovery verbs.\n\nThis stage is non-blocking: if the user never answers, leave the doc as written in Stage 8 and stop without forcing a decision.\n\n## Final Report\n\nOn successful completion of all stages, display:\n\n> **Exploration Complete**\n>\n> **Prompt**: {first 80 characters of prompt}...\n> **Output**: {full path to the exploration doc}\n> **Requirements Page**: {full path to the generated requirements.html, or \"not generated\" when generation failed}\n> **Acceptance Criteria**: {count} ratified ({count} corrected by the user, {count} still open)\n> **Files Examined**: {count of key_files_examined}\n> **Research**: {count of web_searches} web searches, {count of research_queries} deep research queries, council {\"run\" | \"offered and declined\" | \"not needed\"}\n>\n> **Requirements**: {\"Ratified\" | \"Unratified — page not submitted\"}\n> **Design**: {\"Proposed\" | \"Not reached\"}\n> **Ticket(s)**: {\"Created: KEY-1, KEY-2\" | \"Declined — doc is the artifact\" | \"Not offered — work not ready\"}\n> **Tracking**: {\"Registered\" | \"Warning: not tracked — {reason}\" | \"n/a — no tickets created\"}\n> **Plan DAG**: {full path to epic-plan.dag.json | \"n/a — not an epic\"}\n\nOn failure at any stage, stop immediately and report:\n- Which stage failed (by number and name)\n- The error details\n- Any partial results that were produced before the failure\n",
|
|
9
9
|
"draft-ticket.md": "Draft a local Markdown Jira ticket via the Jira Ticket Writer — no Jira issue is created or updated.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\n`$ARGUMENTS` is the complete description of what the ticket should cover. This command delegates drafting exclusively to the `jira-ticket-writer` agent and produces only a verified local Markdown file — it never creates, updates, or attaches to a Jira issue. To upload a drafted ticket to Jira, use an approved creation workflow separately.\n\nIf any step fails, stop immediately and report which step failed and why, preserving the original description.\n\n## Step 1 — Validate the Description\n\nTreat the complete, original `$ARGUMENTS` value as the description, verbatim — do not derive a title or select a subset of it.\n\nIf `$ARGUMENTS` is empty or whitespace-only, stop immediately and report:\n```\nUsage: /draft-ticket <description of the problem, feature, or improvement>\n```\nDo not attempt to synthesize ticket content from an empty description.\n\n## Step 2 — Resolve the Output Directory\n\nRead the `BAPI_DOCS_DIR` environment variable. If it is not set, default to `docs/tmp`. Store this value as `docs_dir`. The required output directory is `{docs_dir}/tickets/`.\n\n## Step 3 — Delegate to the Jira Ticket Writer\n\nInvoke the `jira-ticket-writer` agent through the Task tool (or a host equivalent you have confirmed can resolve it). This delegation is the only drafting operation this command performs — do not compose a ticket title, summary, requirements, acceptance criteria, or any other ticket body content here, and do not name or invoke a Jira mutation tool (`create_ticket`, `update_ticket_description`, or similar) anywhere in this command.\n\nPass the subagent exactly:\n- The original description, verbatim (from Step 1).\n- The requirement to create the Markdown draft under `{docs_dir}/tickets/`.\n- The requirement to return the exact final file path of the draft it wrote.\n\nDo not copy the writer agent's sizing, research, epic-threshold, approval-gate, or ticket-composition rules into this prompt — those policies belong to the agent, not this command.\n\n**If the writer agent is unavailable in the current host** (for example, no Task-equivalent capability can resolve `jira-ticket-writer` — this is expected in Cursor, which has no `.cursor/agents/` mirror for this agent), this is an unsupported-environment failure. Stop and report:\n```\n/draft-ticket requires an agent-enabled host that can invoke the jira-ticket-writer agent. This\nenvironment cannot resolve it. Switch to a supported agent-enabled environment (e.g. Claude Code),\nor ask the repository tooling owner for guidance.\n```\nPreserve the original description in this failure report. Do not attempt to draft inline as a substitute.\n\n## Step 4 — Verify the Output\n\n1. **Capture exactly one path** from the agent's response. If the response names no path, or more than one plausible output path, this is an output-verification failure — not an agent-invocation failure. Stop and report:\n ```\n The Jira Ticket Writer did not return a single verifiable draft path. No substitute was drafted\n inline. Original description preserved below.\n ```\n2. **Confirm the returned path** identifies a Markdown file under the resolved `{docs_dir}/tickets/` directory, and that the exact file exists on disk.\n3. **If the agent claimed success but the file does not exist**, treat this as false success — an output-verification failure, distinct from an agent-invocation failure. Stop and report the same output-verification message as above. Do not generate replacement content.\n\nDistinguish the two failure kinds explicitly in whichever report you give: an agent-invocation failure (Step 3) means the writer could not be reached or run at all; an output-verification failure (this step) means the writer ran but its output could not be verified.\n\n## Final Report\n\n**On success**, display:\n\n> **Draft ready**: {verified exact path}\n>\n> No Jira ticket was created or updated. Review the draft, then use an approved creation workflow\n> when you're ready to upload it.\n\nStop after this instruction. Do not offer or initiate automatic continuation into a Jira creation step.\n\n**On failure**, display the agent-invocation or output-verification failure message from Step 3 or Step 4, preserving the original description and stating plainly that no substitute was drafted inline.\n",
|
|
10
10
|
"drive-epic-run.md": "---\nschedulable: true\narguments: {\"positionals\":[{\"name\":\"epicKey\",\"type\":\"string\",\"required\":true}]}\n---\n\n# Drive Epic Run: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command is the posture for a session that **drives a live Epic Implementer run** — one that `drive-epic` or `setup-epic` has already created. It does not create a run, cut a branch, or approve a plan. It tells the driving session what it may do without asking, what it must stop and ask about, how much it may intervene, which verbs it may use, and what it must write down.\n\nIt replaces the per-run `docs/tmp/<KEY>-driving-manual.md` files. Those were event allowlists (\"you may, without asking: unpark once per ticket…\"), so every recovery their author did not foresee stopped the driver. This command is a **scope rule** instead: it decides by blast radius, not by event.\n\nThe v2 reconciler stays the single transition authority. The driver observes, applies one sanctioned verb at a time, and records what it did. It never edits gate state, never edits code, and never signals a worker process.\n\n---\n\n# Instructions\n\nRun Stage 0 once, at the start of the driving session. Then run the monitoring loop until the run is terminal (`done`, `stopped`, or `abandoned`) or the session must end. Re-invoking the command later (a new session) runs Stage 0 again before anything else.\n\n## Stage 0 — Pre-flight\n\nRun these six operations **in this order**. Any failure stops the command **before any run state is read or written, before the tally is created, and before any recovery verb is applied.**\n\n1. **Parse `$ARGUMENTS`.** Accept exactly one positional token, the epic key, matching `[A-Z][A-Z0-9]+-[0-9]+` (e.g. `BAPI-1145`). Reject a missing key, a second positional, any flag, and a key that does not match. Reject before any side effect and display:\n\n ```\n Invalid arguments.\n Usage: /drive-epic-run <EPIC>\n <EPIC> required, matches [A-Z][A-Z0-9]+-[0-9]+ (e.g. BAPI-1145)\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 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. **Resolve the packaged CLI launcher (`BAPI_MCP_CLI`) once.** Call it `<launcher>` and reuse the one resolved value for the whole session.\n\n - Read the `BAPI_MCP_CLI` environment variable.\n - **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`, so the resulting shell command is byte-identical to the default packaged invocation.\n - **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix (for example `node /absolute/path/to/mcp_server/build/index.js`). Rebuild with `cd mcp_server && npm run build` before relying on a local launcher; a stale local build misleads exactly as a stale npm publish does.\n\n The packaged CLI resolves its own credentials through the shared resolver (`BAPI_API_KEY` from the environment, then the user-scoped store `~/.config/bridge/credentials.json`). Keep every dynamic argument independently quoted rather than concatenated into the launcher value. Never put a credential, or a credential-bearing environment assignment, into `<launcher>` or into any command argument.\n\n **Bash interpolation rule (global; applies to every Bash invocation in this command).** Before interpolating any dynamic value — the epic key, a ticket key, a run id, a scope id, a PR number, a path, a JSON body — 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 log line, in the tally, in the notes log, or in a context file.\n\n4. **Epic Implementer health check.** From the repository root, run `ENVIRONMENT=TEST python scripts/conductor_health_check.py --json` (the BAPI-1146 script; activate `.venv` first). It proves the deterministic conductor suites pass on this checkout and names every failure class that has no drill. Exit `0` passes — undrilled classes alone do not fail it; record their names in the tally header. Exit `1` (a suite failed), `2` (a prerequisite is missing; nothing was proven), or `3` (the failure-class registry is invalid) stops the command.\n\n5. **Epic Implementer readiness read.** Run `'<launcher>' epic-implementer readiness --json` (read-only; it resolves credentials itself). Its server facts come from `GET /jira/epic-runs/conductor-readiness`, with the meanings in the operator runbook's \"What the readiness endpoint reports\" table: `supervisor.required_checks_empty` (an enabled done gate that gates nothing), `reconciler.stale` (no tick within 900 s), `executor.ready` (`null` = never provisioned, `false` = provisioned but not seen recently). The command always exits `0`, so read the report: any check with status `fail`, or any entry in `unavailableSources`, stops the command. Record `warn` checks in the tally header.\n\n6. **Migration head.** Run `alembic -c alembic.ini heads` and `alembic -c alembic.ini current`. The database must report the **single current migration head**. Two heads, or a current revision behind the head, stops the command — a reconciler whose code is ahead of the database misbehaves in ways no verb repairs.\n\n**Run resolution.** Every run-addressed CLI verb and route in this command takes the epic key directly as its run id (`--epic-run-id <EPIC>`). The server resolves it to the single non-terminal run for the repository. Zero matching runs (`404`) or more than one non-terminal run (`409 CONFLICT`) is a non-mutating stop: record it and ask. Never guess, and never pick a run heuristically. Record the resolved `epic_run_id` from the first state read in the tally header.\n\n## Autonomy\n\n**Scope rule (verbatim):**\n\nThe session driving an epic performs every recovery whose blast radius is inside the run without asking, and stops to ask only when a remediation would touch the base branch or any branch outside `epic/<KEY>` and its children, a `.github/workflows/**` file, the reconciler/executor code path itself, a database migration, a plane or database shared with another run, a credential, or a billable multi-model run beyond the run's budget.\n\n1. **The base branch, or any branch outside `epic/<KEY>` and its children.**\n Recognition: a ref the remediation would push to, merge into, or rewrite is neither `epic/<KEY>` nor a `branch_name` bound to a ticket of this run in the state read. Match: `target_refs outside run_branches`\n2. **A `.github/workflows/**` file.**\n Recognition: the remediation's diff adds, edits, or deletes a path under `.github/workflows/`, or the merge path refused with `workflow_files_modified` (`MERGE_BLOCKED_WORKFLOW_FILES_MODIFIED` in `api/library/epic_implementer/reconciler.py`, the `workflow_files_modified` refusal in `api/library/vcs/epic_implementer_merge_service.py`). Match: `touched_paths under .github/workflows/ or signals has workflow_files_modified`\n3. **The reconciler/executor code path itself.**\n Recognition: the remediation's diff touches a path in the drills lane's transition-authority prefix set (the `run_drills` regex in `.github/workflows/epic-implementer-ci.yml`) or `mcp_server/src/drive-epic.ts`. Match: `touched_paths under api/library/epic_implementer/ api/library/db/epic_* api/library/vcs/epic_observers.py api/library/index_scope/ src/python/llms/executor_jobs.py mcp_server/src/executor/ tests/integration/conductor_harness/ mcp_server/src/drive-epic.ts`\n4. **A database migration.**\n Recognition: the remediation's diff adds, edits, or deletes an Alembic revision (the `script_location` in `alembic.ini` is `db/alembic`). Match: `touched_paths under db/alembic/versions/`\n5. **A plane or database shared with another run.**\n Recognition: the state, plane, or scope read shows the resource the remediation acts on — a plane process, an executor lane, a database, an index scope — is bound to, or also serving, another run. Match: `shared_resources nonempty`\n6. **A credential.**\n Recognition: the operation would read, create, change, disclose, transmit, or record a credential or authorization value — an API key, a token, a git credential helper, a `gh` login, the credential store file. Match: `credentials nonempty`\n7. **A billable multi-model run beyond the run's budget.**\n Recognition: the operation would call `request_council` or `request_deep_research`. Run policy has no multi-model budget field, so the effective budget is `0` and every such call is beyond it. Match: `tools has request_council request_deep_research`\n\n**This is a scope rule, not an event allowlist.** An unforeseen recovery whose blast radius is inside the run is permitted. The absence of a matching row in any table in this command is never grounds to stop. Only a match against the stop list above is.\n\n**How a `Match:` spec reads.** Each spec tests the facts of one proposed remediation. `<fact> outside <set>` matches when some value of the fact is not in the named set. `<fact> under <pattern>…` matches when some path equals a pattern, lies under a pattern ending in `/`, or fits a pattern whose `*` stands for any characters except `/`. `<fact> has <token>…` matches when some value contains one of the tokens. `<fact> nonempty` matches when the fact lists anything. `or` joins alternatives. The facts are: `target_refs` (refs the remediation writes), `run_branches` (`epic/<KEY>` plus every `branch_name` in the state read), `touched_paths` (files its diff changes), `signals` (park reasons, refusal tokens, and job facts it responds to), `tools` (MCP tools it calls), `shared_resources`, and `credentials`.\n\n**Stop-and-ask is non-mutating.** It leaves the run exactly as it is. It does not trigger `stop-run` or `abandon-run`. Record the evidence in the tally and the notes log, apply no further verb to the affected ticket, keep driving every other ticket, and put the question to the operator. An operator's answer authorizes that one remediation only; it does not widen this posture.\n\n**Bookkeeping is not remediation.** Writing the tally and the notes log, and committing the notes log on `docs/<KEY>-conductor-run-notes` at wind-down, are bookkeeping. They are exempt from stop-list entry 1 and authorize no code change on any branch.\n\n**Child merges and the integration PR.** A child PR may merge automatically into `epic/<KEY>` only when all of these hold: the run policy explicitly sets `policy_json.v2_auto_merge_enabled`, the run has the unattended posture and the strict identity the epic implementer already requires, and every remaining merge-admission check passes (head-bound CI, head-bound review, workflow files, overlap, and head drift). The epic implementer never merges the epic-to-base integration PR, never schedules a merge of it, and never treats an approval as authority to merge it. That PR must stay open, non-draft, and ready for a final human review.\n\n- *Detection:* each child PR's diff, the existing CI, review, and overlap gates, and finally the integration PR's diff.\n- *Containment:* automated merges land only on `epic/<KEY>`, and the integration PR is never auto-merged, so nothing reaches the base branch without a human.\n- *Escalation:* the integration PR's human reviewer. Workflow files, migrations, credentials, shared planes or databases, and budget still stop the driver earlier, through the stop list above.\n\nBAPI-1215 retired the marker-based ownership guard. Nothing path-based replaced it: an edit to a formerly marked area gets no special status, label, configuration, report, notification, or end-of-run callout, and removing the guard does not widen which child PRs may auto-merge.\n\n## Budget\n\nTwo bounds, per ticket:\n\n- **3 interventions per ticket.**\n- **2 hours of no forward progress per ticket.**\n\nWhen either bound is reached, the driver **parks the ticket**: it ends intervention on that ticket instead of applying a fourth verb or intervening further. Parking needs no asking unless the remediation it would have applied independently matches the stop list.\n\n- **An intervention** is one committed, state-changing verb from the verb table below — one whose response reports `committed: true` or an equivalent durable change. A `409`/CAS conflict, a refusal, a retry of a refused call, an idempotent replay (`committed: false`), and a no-op are not interventions. Ending intervention is not an intervention.\n- **A run-wide verb** (`stop-run`, `abandon-run`, `scope-reparse`, `index-scope catch-up`, `policy PATCH`, `resume`) is charged to **each** blocked ticket that motivated it. Record that attribution in the tally.\n- **Forward progress** is measured only by the reconciler's own clock: the ticket's `last_progress_at` in the state read. Two hours of no forward progress is **now minus `last_progress_at`** reaching two hours. When `last_progress_at` is null — the ticket has never progressed — measure from the ticket's `created_at` in the same state read. The reconciler writes `last_progress_at` only on genuine progress, so a regression, pending-state churn, a lease-owner change, or a park does not reset it. The driver keeps no timer of its own.\n- **To park** is to end intervention: call `second_opinion` first (see below), record the exhausted bound in the tally and the notes log, and apply no further verb to that ticket. The reconciler remains the only writer of parking state; the run keeps driving its other tickets. There is no park verb. Do not use the generic ticket `PATCH` or the run-wide `pause` to park.\n\n## Verb table\n\nThe closed set of state-changing recovery verbs. **No verb outside this table is sanctioned.** Reads, pre-flight, tally and notes writes, and `second_opinion` are supporting operations outside this table.\n\n| Verb | When to use | Sanctioned CLI or route | Mapped reasons / preconditions |\n|---|---|---|---|\n| `unpark` | A parked ticket whose cause is now fixed, or whose park a `second_opinion` judged spurious. | `'<launcher>' epic-implementer unpark --epic-run-id <EPIC> --ticket-key <TICKET>` → `POST /jira/epic-runs/runs/{epic_run_id}/tickets/{ticket_key}/unpark` | `pr_not_attached` with the PR attached at the expected head; `review_verdictless_ceiling_reached` only after `second_opinion`; `merge_overlap_peer_parked` after the peer is recovered; `merge_overlap_wait_expired`, `merge_overlap_observation_expired` after the overlap is resolved; `required_checks_empty_no_mode` after `policy PATCH` is observed. |\n| `adopt-current-head-and-unpark` | A parked ticket whose PR head moved off its anchored commit and the current head is the one to keep. | `'<launcher>' epic-implementer adopt-current-head-and-unpark --epic-run-id <EPIC> --ticket-key <TICKET>` → `POST /jira/epic-runs/runs/{epic_run_id}/tickets/{ticket_key}/adopt-current-head-and-unpark` | `pr_not_attached` when the head drifted; `head_sha_drift`; `ci_fix_contract_incomplete` on a green PR. |\n| `stop-run` | The run must end now and its queued work must not run. Terminal. | `'<launcher>' epic-implementer stop-run --epic-run-id <EPIC>` → `POST /jira/epic-runs/runs/{epic_run_id}/stop` | Never an automatic response to Stop-and-ask. Ends every ticket, so `second_opinion` first. A hung worker is recovered by the executor's per-job timeout, or by this verb if the run must end. |\n| `abandon-run` | A stopped or stale run that blocks a fresh run, or whose stale epic shadow fails child reviews closed. Terminal and irreversible. | `'<launcher>' epic-implementer abandon-run --epic-run-id <EPIC>` → `PATCH /jira/epic-runs/runs/{epic_run_id}` with `{\"status\": \"abandoned\"}` | Never on a live run whose work you mean to keep. `second_opinion` first. |\n| `scope-reparse` | The run's index scope is wedged at its pinned commit: `stale` or `seeding` with no parse behind it, and no unobserved merge. | `'<launcher>' epic-implementer scope-reparse --epic-run-id <EPIC> --scope-id <SCOPE>` → `POST /jira/epic-runs/runs/{epic_run_id}/scope-reparse` | `shadow_stale_deadline` when scope status is not `unobserved_advance`. Refused lifecycles return `409 SCOPE_REPARSE_REFUSED`; it does not wait for `ready`. |\n| `dispatch-remediation` | Any code change on a child branch. The **only** way the driver changes code. | `'<launcher>' epic-implementer dispatch-remediation --epic-run-id <EPIC> --ticket-key <TICKET> --context-file <PATH>` → `POST /jira/epic-runs/runs/{epic_run_id}/tickets/{ticket_key}/dispatch-remediation` | `pr_conflicting`; a `code_review` remediation loop that needs the driver's context. Ticket at `code_review`, or parked with `code_review` as its gate. The server picks the job type. Never the MCP tool, never a worktree edit. |\n| `merge-child` | A green, approved child PR the epic implementer did not merge on its own. | `'<launcher>' epic-implementer merge-child --epic-run-id <EPIC> --pr <NUMBER>` → `POST /jira/epic-runs/runs/{epic_run_id}/merge-child`; the CLI records a local merge through `POST /jira/epic-runs/runs/{epic_run_id}/merge-child/complete` | Only after the gates permit the merge (CI green at head, review approved). Run from the repository checkout with `gh auth status` passing. Never `gh pr merge`, and never the general `merge_pull_request` MCP tool, which is not scoped to the run. |\n| `index-scope catch-up` | A merge landed on `epic/<KEY>` that the scope did not observe. | `'<launcher>' epic-implementer scope-catch-up --epic-run-id <EPIC> --scope-id <SCOPE>` → `POST /jira/epic-runs/runs/{epic_run_id}/scope-catch-up` | `shadow_stale_deadline` when scope status is `unobserved_advance`; a child in `parsing` with the scope behind the epic head; a stranded review worker, once the scope reads `ready`. |\n| `policy PATCH` | The run's policy is wrong for the run (a lost key, a cap that must rise). | `'<launcher>' epic-implementer patch-policy --epic-run-id <EPIC> --patch-file <PATH>` → `POST /jira/epic-runs/runs/{epic_run_id}/patch-policy` | `required_checks_empty_no_mode`; a cap pause that needs a higher cap. |\n| `resume` | A paused run whose cause is cleared. | `'<launcher>' epic-implementer resume-run --epic-run-id <EPIC> [--clear-quarantine]` → `POST /jira/epic-runs/runs/{epic_run_id}/resume-run` | `run_control.cause` pauses such as `cap_wall_clock`; `integration_pr_missing` / `integration_pr_ready_failed` after the integration PR is corrected (the correction itself touches the base branch: Stop-and-ask). Never `--clear-quarantine` without `second_opinion`. |\n\n`release-job` is **not** a verb. No route exists for it. BAPI-1147 fixed the stranded-worker defect it was specified for, and the executor's per-job timeout bounds any residual case. **The driver never signals, kills, or restarts a worker process directly.**\n\n**Policy patch file.** `patch-policy`'s `--patch-file <PATH>` is a JSON object naming only the policy keys to change — an RFC 7396 merge patch. The server merges it into the policy it reads: `null` deletes a key, and protected paths (`base_branch`, `notify.webhook_url`, and every other server-retained key) are refused. Keep the patch file at the deterministic path `~/.local/state/bapi-conductor-runs/<KEY>/policy-patch.json`, retained alongside `tally.md`; restrict the containing state directory to the user (`chmod 700`) and the file to user-only read/write (`chmod 600`). It contains only the policy keys to change and never a credential. `<PATH>` remains subject to Stage 0's Bash interpolation rule.\n\n**Credential-resolution failures.** If a verb fails to resolve credentials or authenticate, run `'<launcher>' epic-implementer doctor` — a read-only diagnosis — then stop and ask. Do not inspect the credential store directly, and do not automatically retry the mutation.\n\n**Concurrency.** Every CLI verb owns and resolves its own concurrency mechanism, and refuses every version flag — the driver never supplies a status, row-version, version, or generation argument. Ticket-addressed verbs read the ticket's `row_version` internally; `resume-run` retries a concurrent control change itself and is never given a generation; `patch-policy` merges against the policy the server itself reads. On a `409` or any CAS conflict: call `explain_run`, re-read state, and decide again. Retry only if the verb is still needed, and only against the fresh read (its new `row_version`, status, or generation). The refused call is not an intervention.\n\n## Second opinion\n\n**Before any park and before any change you cannot verify with tests, call `second_opinion` and record its answer in the tally.**\n\n- **Why.** The driver assumes the operator is unavailable. A park can stop a ticket, and everything that depends on it, for hours. A second model answers in seconds.\n- **\"Park\"** means ending intervention on a ticket (Budget, above), including `stop-run` and `abandon-run`, which end every ticket.\n- **\"A change you cannot verify with tests\"** is one with no authoritative postcondition. For code, the postcondition is tests. For an operational verb, it is the route's response plus the gate, status, scope, or merge-ledger state it should produce on the next read.\n- **Not a multi-model run.** `second_opinion` is a single-model, synchronous call. It is **not** the billable multi-model run that stop-list entry 7 names; `request_council` and `request_deep_research` are.\n- Record a sanitized summary of the answer, never the raw model reply or provider payload.\n- If the call fails, times out, or returns something unusable, record the failure and retry once. The retry is not an intervention. If it fails again, apply no further verb to that ticket and escalate through the tally and the notes log. Never skip the precondition.\n\n## The driver never edits code (R-6)\n\nThe driver never edits code on `epic/<KEY>` or on any child branch. A code change on a child branch is made by dispatching a worker job that carries the driver's context: `dispatch-remediation --context-file <PATH>`.\n\n- The context file carries only what the worker needs: the reason code, a bounded failing-test excerpt, the conflict or diff summary, and the remediation the driver wants. The CLI rejects an empty, invalid-UTF-8, or oversized file (16384 bytes) but it **does not scrub it**. The driver must remove credentials, environment values, authorization headers, raw provider payloads, and unrelated data before dispatch.\n- **Counter-example.** During the BAPI-979 run the driver hand-merged `ticket_evaluator.py`, `ticket_type_evaluator.py`, and `test_ticket_evaluator.py` on BAPI-981's branch and pushed the result. Under this posture that work is dispatched, not typed: one `dispatch-remediation` whose context names both children's intent.\n- Writing the tally and the notes log is bookkeeping, not a code edit.\n\n## Records\n\n### The tally\n\nWrite `~/.local/state/bapi-conductor-runs/<KEY>/tally.md` **as events happen**, not at wind-down.\n\n- After Stage 0 succeeds, create the directory with user-only permissions (`mkdir -p` then `chmod 700`) and the file with user-only read/write (`chmod 600`). Open it with a header: the epic key, the resolved `epic_run_id`, the scope id, the health-check undrilled classes, and the readiness warnings.\n- The tally is **append-only**. Never rewrite an earlier entry.\n- One entry per issue, with these five fields: `issue`, `evidence` (ids, SHAs, gate, reason code), `verb applied`, `outcome`, and `time` — plus the run id and an event id (`<KEY>-<n>`) so a duplicate is detectable, and the budget charge (`ticket <TICKET>: intervention 2 of 3`).\n- **Write before you act.** Append the entry's pre-action part (issue, evidence, the verb you are about to apply) before applying the verb. Append the outcome under the same event id after the response. A failed pre-action write blocks that recovery. A failed outcome write blocks every later recovery until recording works again.\n\n### The notes log\n\nThe tally sits alongside the durable notes log at `docs/conductor-runs/v2-engine/<date>-<KEY>-run-issues.md` and does not replace it. The tally is the live, write-as-it-happens record; the notes log is the committed artifact. Write the notes log during the run, not only at wind-down. Keep its shape so the two stay compatible:\n\n```markdown\n# <KEY> conductor run — issues log (v2 engine)\n\nDriver: automated LLM session under /drive-epic-run.\nGoal: <one line>.\n\n## Header / baseline (<date>, pre-launch)\n\n| Fact | Value |\n|---|---|\n| `origin/main` | <sha> |\n| DB | <database>, Alembic head <rev> (single head) |\n| epic_run_id | <id> |\n| scope_id | <id> |\n| cut SHA | <sha> |\n| log paths | `~/.local/state/bapi-conductor-runs/<KEY>/` |\n\n## Notes (dated, one per event)\n\n### <date> <time> · <TICKET or run> · <category>\n<what happened, the evidence, the verb applied, the outcome>\n```\n\nEach entry's category is one of `impediment`, `friction`, `defect`, `decision`.\n\nAt wind-down, add the finished run's row to the v2-engine table in `docs/conductor-runs/README.md` — the tally is not a substitute for that row — and commit the notes log and the README row on `docs/<KEY>-conductor-run-notes`. The pull request into the base branch stays human-gated.\n\n### Scrubbing\n\nOne rule for the tally, the notes log, every context file, and every recorded `second_opinion` answer: never record credentials, environment values, authorization headers, raw provider payloads, or raw model answers. Record bounded, secret-free evidence. The notes log gets a sanitized summary, never a raw copy of the tally.\n\n## Monitoring loop\n\nEach pass reads in this order, then applies at most one verb per ticket:\n\n1. **`explain_run`** (`epic_run_id` = the epic key; add `ticket_key` to narrow) — the fastest \"why is this stuck\" read. Inspect the current gate, the latest observation and its age, queued jobs and their queue position, the lease owner, and the expected versus observed head SHA.\n2. **`get_epic_snapshot`** — the run-state read (`GET /jira/epic-runs/runs/{epic_run_id}/state`): every ticket's `status`, `gates`, `needs_human_reason`, `last_progress_at`, `created_at`, `branch_name`, `pr_number`, `row_version`, and the `executor_jobs` block. Read `executor_jobs.state` before `jobs`.\n3. **Scope status** — `GET /jira/index-scope/status` for the run's scope: `lifecycle_state`, `freshness_status`, `required_commit_sha`, `indexed_commit_sha`.\n4. **Plane** — `'<launcher>' plane status`, and `poll_ci_checks` for a PR whose CI you need to see.\n\nAfter every committed verb, run a fresh pass before choosing another action. Between passes, wait with a bounded wait your host provides; never busy-poll.\n\n### Signals → actions\n\nEvery state-changing action below is a verb from the verb table. `Wait` means no action: the condition resolves itself. `End intervention` means park the ticket as the Budget section defines it. `Stop-and-ask` means a stop-list match.\n\n| Signal | Likely cause | Action |\n|---|---|---|\n| `reconciler.stale`, or no tick for more than 3 minutes | Host slept, or the reconciler died | Check `pmset -g log` for sleep. Slept: `Wait`. Process dead: `Stop-and-ask` (restarting a plane process is not a verb, and a plane may be shared). |\n| `needs_human`, `required_checks_empty_no_mode` | The policy lost `required_checks` | `policy PATCH` → `unpark` |\n| `needs_human`, `merge_blocked_workflow_files_modified` | A child diff edits a workflow file | `Stop-and-ask` |\n| `needs_human`, `pr_not_attached` | The worker produced no PR, or the PR head drifted | `explain_run`, then `unpark` (PR attached at the expected head) or `adopt-current-head-and-unpark` (head drifted and the current head is the one to keep) |\n| `needs_human`, `head_sha_drift` or `ci_fix_contract_incomplete` on a green PR | The head moved after the gate anchored it | `adopt-current-head-and-unpark` |\n| `needs_human`, `pr_conflicting` | The child conflicts with `epic/<KEY>` | `dispatch-remediation` → `merge-child` once the gates permit |\n| `needs_human`, `shadow_stale_deadline`, scope `unobserved_advance` | A merge landed that the scope did not observe | `index-scope catch-up` → `unpark` |\n| `needs_human`, `shadow_stale_deadline`, scope wedged at its pinned commit | The scope stopped with no parse behind it | `scope-reparse` → `unpark` once the scope reads ready |\n| Child in `parsing` for more than 45 minutes, scope behind the epic head | A post-merge observation was missed | `index-scope catch-up` |\n| A review worker idle-waiting on a review that expired, scope now `ready` | The review started before the shadow was ready | `index-scope catch-up`, then `Wait` for the executor's per-job timeout; never a process kill |\n| `needs_human`, `review_verdictless_ceiling_reached` | The reviewer returned no verdict until the ceiling | `second_opinion`, then `unpark` |\n| `needs_human` at `code_review` after the remediation budget | The worker cannot fix the finding alone | `dispatch-remediation` with the driver's context, or `End intervention` if the finding repeats |\n| `needs_human` at `spec_review` (spec gap) | The ticket text is ambiguous | `second_opinion`, then `unpark` (the review was wrong) or `End intervention` (the spec must change; no verb edits it) |\n| `merge_held_behind_peer` | A deterministic hold behind an unmerged peer | `Wait` until its hold deadline passes |\n| `needs_human`, `merge_overlap_peer_parked` | The peer it waits behind is parked | Recover the peer with its own verb, then `unpark` |\n| `needs_human`, `merge_overlap_wait_expired` or `merge_overlap_observation_expired` | The overlap hold ran out | `dispatch-remediation` → `merge-child`, or `unpark` once the peer is readable |\n| `needs_human`, `reconcile_overlap_push_unauthorized` | The executor machine's git credential helper refused | `Stop-and-ask` (a credential) |\n| Green, approved child PR not merging (for example `merge_ci_pending_at_head` on a settled head) | The merge path stalled | `merge-child` |\n| Run `paused`, `run_control.cause` a cap | A wall-clock or job cap tripped | `policy PATCH` → `resume` |\n| Run blocked, `integration_pr_missing` or `integration_pr_ready_failed` | The integration PR into base is missing or not ready | `Stop-and-ask` (base branch), then `resume` after the operator corrects it |\n| Scope `lifecycle_state` `failed` | Seed or verify failed | `second_opinion`, then `abandon-run`, or `Stop-and-ask` when the cause lies outside the run |\n\nA signal with no row here is not a reason to stop. Apply the scope rule: find the sanctioned verb whose blast radius stays inside the run, or stop only on a stop-list match.\n\n## Host coverage\n\nThis command ships to the Claude Code and Cursor command mirrors (`.claude/commands` and `.cursor/commands`) — the only slash-command surfaces this repository installs. Codex and Copilot hosts have no slash-command surface here. They rely on the packaged CLI verbs plus the Autonomy section of the operator runbook (`docs/claude/epic-implementer-v2-operator-runbook.md`).\n\nA missing or drifted CLI equivalent on such a host is filed as a conductor defect ticket naming the host, the missing verb, and the command or package version. The agent never improvises an equivalent — no hand-built route call, MCP write, local process action, or worktree edit stands in for a verb this command sanctions.\n",
|
|
11
11
|
"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,8 +13,8 @@ export const COMMANDS = {
|
|
|
13
13
|
"idea-to-pr.md": "Turn an idea into tickets with the `idea-to-ticket` recipe, then hand the created keys to `/review-and-start --auto`.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command is a thin client-side wrapper around two things that already exist: the\n`idea-to-ticket` recipe, and the `/review-and-start` command. It runs them in that order,\nin this session, and does nothing else.\n\nStage 1 is recipe-driven. Do not call MCP tools directly other than `get_pipeline_recipe`\n— the recipe determines which tools to call and with what parameters. Stage 2 is a single\nslash-command invocation. There is no server-side run to start, poll, resume, or schedule:\nthis command holds no state of its own between the two stages beyond the ticket keys stage 1\nreturned.\n\n## Stage 0 — Parse arguments\n\n1. Tokenize `$ARGUMENTS` on whitespace. Recognize these position-independent flags; every\n other token is part of the idea:\n - `--allow-duplicate`\n - `--max-children=N` where `N` is a positive integer\n - `--auto`\n\n Flags are optional. Treat absence of a flag as the default; never pass empty-string or\n null as a placeholder.\n\n2. Reject **unknown flags** — any token beginning with `--` that is not one of the three\n above — before calling `get_pipeline_recipe`. Stop and report the offending flag.\n\n3. Reject a **non-positive or non-integer `--max-children`** value before calling\n `get_pipeline_recipe`. Stop and report the offending value.\n\n4. Everything that is not a recognized flag is the free-form `idea` text. Join those tokens\n back together preserving order. Trim surrounding whitespace.\n\n5. If the resulting `idea` is empty, stop immediately and display:\n ```\n Usage: /idea-to-pr <idea> [--allow-duplicate] [--max-children=N] [--auto]\n ```\n\n## Stage 1 — Run the `idea-to-ticket` recipe\n\n6. Derive `slug` from the first 6-8 meaningful words of the idea: lowercase, kebab-case,\n strip non-alphanumeric characters except hyphens, and truncate to roughly 60 characters.\n Skip stop-words such as \"the\", \"a\", \"an\" when picking the 6-8 meaningful words.\n\n7. Derive `run_id` as `<YYYYMMDD-HHMMSS>-<short-uuid>` using the current UTC time and a\n short UUID suffix (8 hex chars is enough).\n\n8. Derive the boolean-as-string variables:\n - `allow_duplicate` is `\"true\"` if `--allow-duplicate` was present, otherwise `\"false\"`.\n - `auto_approve_external` is `\"true\"` if `--auto` was present, otherwise `\"false\"`.\n - `max_children` is the integer following `--max-children=` as a string, or `\"10\"` when\n the flag is absent.\n\n9. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `\"idea-to-ticket\"`\n - `variables`: `{ \"idea\": \"<idea>\", \"slug\": \"<slug>\", \"run_id\": \"<run_id>\", \"allow_duplicate\": \"<allow_duplicate>\", \"auto_approve_external\": \"<auto_approve_external>\", \"max_children\": \"<max_children>\" }`\n\n Do NOT pass `docs_dir` or `idea_hash` in variables — both are auto-injected by the\n pipeline system (`docs_dir` from `BAPI_DOCS_DIR`; `idea_hash` is a stable hash derived\n from the `idea`).\n\n If the tool returns an error, stop and report the failure.\n\n10. Read and strictly obey the `agent_instructions` field in the response. Execute each\n returned step in the order returned, announcing each as **Step N of M: <description>**.\n The recipe owns its internal stages — do not restate them here and do not invoke them\n directly.\n\n11. **A recipe halt is terminal.** If any step halts — a failed preflight,\n `too_vague_to_ticket`, duplicate detection, or a screen/resolve halt — stop there.\n Report the upstream halt reason with enough of its own wording to stay actionable, and\n state explicitly that no review/start handoff was made. Never fall through a halt to\n key collection or to Stage 2.\n\n## Stage 2 — Hand off to `/review-and-start`\n\n12. Collect the created Jira keys from the terminal `upload-and-track` step's result, in\n the exact order that step returned them. Do not query or reconstruct the keys through\n any other MCP tool.\n\n13. If zero keys were returned, stop with that step's reason. Do not invoke\n `/review-and-start` with an empty key list.\n\n14. Otherwise invoke, **exactly once, in this same session**:\n ```\n /review-and-start --auto <KEY> [KEY ...]\n ```\n When this workflow runs in Codex, invoke `$bridge-review-and-start --auto --agent codex --host codex <KEY> [KEY ...]` instead. In Claude and Cursor retain the slash-command form above. Use the keys in the order stage 1 returned them. `/review-and-start` accepts a variadic\n key list and threads `--auto` into both the review and the implementation phase of every\n session it spawns. The handoff always carries `--auto` — this command is hands-off by\n default, and the command-line `--auto` flag governs stage 1's `auto_approve_external`\n rather than the handoff.\n\n## Stage 3 — Final report\n\n15. Keep the report compact:\n ```markdown\n ## Idea to PR Complete\n\n Idea: <first 80 chars of idea>...\n idea-to-ticket: <completed | halted at \"<step description>\">\n Keys created: <KEY, KEY, ... in returned order, or \"none\">\n Handoff: <\"/review-and-start --auto <keys>\" issued | not issued — <reason>>\n ```\n\n On a halt, `Keys created` is `none` and `Handoff` states `not issued` with the upstream\n halt reason.\n",
|
|
14
14
|
"idea-to-ticket.md": "Convert a short human idea into a Jira ticket (or Epic plus child tickets) via the server-side idea-to-ticket pipeline.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command is recipe-driven. Do not call MCP tools directly other than `get_pipeline_recipe` — the recipe determines which tools to call and with what parameters.\n\n## Stage 0 — Parse arguments\n\n1. Tokenize `$ARGUMENTS` on whitespace. Recognize the following position-independent flags (any other tokens are part of the idea):\n - `--allow-duplicate`\n - `--max-children=N` where `N` is a positive integer\n - `--auto`\n\n Flags are optional. Treat absence of a flag as the default; never pass empty-string or null as a placeholder.\n\n2. Everything that is not a recognized flag is the free-form `idea` text. Join those tokens back together preserving order. Trim surrounding whitespace.\n\n3. If the resulting `idea` is empty, stop immediately and display:\n ```\n Usage: /idea-to-ticket <idea> [--allow-duplicate] [--max-children=N] [--auto]\n ```\n\n## Stage 1 — Derive pipeline variables\n\n4. Derive `slug` from the first 6-8 meaningful words of the idea: lowercase, kebab-case, strip non-alphanumeric characters except hyphens, and truncate to roughly 60 characters. Skip stop-words such as \"the\", \"a\", \"an\" when picking the 6-8 meaningful words.\n\n5. Derive `run_id` as `<YYYYMMDD-HHMMSS>-<short-uuid>` using the current UTC time and a short UUID suffix (8 hex chars is enough). The combination of `slug` and `run_id` uniquely identifies this run's artifact directory.\n\n6. Derive the boolean-as-string variables:\n - `allow_duplicate` is `\"true\"` if `--allow-duplicate` was present, otherwise `\"false\"`.\n - `auto_approve_external` is `\"true\"` if `--auto` was present, otherwise `\"false\"`.\n - `max_children` is the integer following `--max-children=` as a string, or `\"10\"` when the flag is absent.\n\n## Stage 2 — Call the recipe\n\n7. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `\"idea-to-ticket\"`\n - `variables`: `{ \"idea\": \"<idea>\", \"slug\": \"<slug>\", \"run_id\": \"<run_id>\", \"allow_duplicate\": \"<allow_duplicate>\", \"auto_approve_external\": \"<auto_approve_external>\", \"max_children\": \"<max_children>\" }`\n\n Do NOT pass `docs_dir` or `idea_hash` in variables — both are auto-injected by the pipeline system (`docs_dir` from `BAPI_DOCS_DIR`; `idea_hash` is a stable hash derived from the `idea`).\n\n If the tool returns an error, stop and report the failure.\n\n8. Read and strictly obey the `agent_instructions` field in the response. Execute each step in order, announcing each as **Step N of M: <description>**.\n\n The recipe drives the ordered stages for you — do not invoke them directly. In order they are: preflight-and-readiness → research-decision → execute-research → duplicate-and-context-scan → screen-and-resolve → frame-goals-and-nfrs → **comp-analysis** (a gated, backend-safe perception step that maps any attached/referenced design comp to existing components, templates, SCSS/CSS tokens, and routes before drafting; it short-circuits for backend-only or no-comp work) → draft-and-critique → upload-and-track.\n\n## Stage 3 — Final summary\n\n9. After all steps complete, display a summary:\n ```\n ## Pipeline Complete\n\n **Idea**: <first 80 characters of idea>...\n **Slug**: <slug>\n **Run directory**: <docs_dir>/idea-to-ticket/<slug>-<run_id>/\n **Steps executed**: N of M\n **Status**: Success / Failed at step N\n ```\n",
|
|
15
15
|
"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\n **Background work is not completion.** Step 2's `request_plan_generation` submits\n without waiting. Step 3 must retrieve the terminal plan with repeated bounded\n `get_plan` calls (`budget_ms: 20000`); `state: \"pending\"` is normal. In `--auto`\n mode, a pending result is not a reason to ask the operator whether to continue\n or to end the headless turn. Keep opening bounded wait windows until the plan\n is ready or the tool returns a terminal error, even if several minutes have\n passed. Do not invent a poll-count or elapsed-time cutoff. If the host itself\n prevents another tool call, report the pending state and the exact safe re-entry\n command; do not describe it as a plan-generation failure. If the submission\n outcome is ambiguous because of a dropped connection, use `get_plan` to inspect\n the existing run — never reissue the step automatically. The same rule covers any ticket review\n started before the commit handoff: reach a terminal result through a bounded wait tool\n (`wait_for_ticket_review`, called again on each normal `state: \"pending\"` result) or the\n artifact's retrieval tool. A shell `sleep`, an unbounded foreground poll, and re-issuing\n the original generation request are all forbidden — the last abandons the run already in\n flight and starts a second billable one. This applies to pre-handoff prerequisites only;\n post-push CI belongs to the CI-monitoring step.\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_EPIC_IMPLEMENTER_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 — including one the CI-monitoring step's mergeability gate detected but has not yet resolved, or one that gate's resolution attempt left blocked,\n- an explicit conflict-resolution approval from that mergeability gate's attended investigation flow is still pending,\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",
|
|
16
|
-
"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\n**How to run this.** Run `/install-bridge` now, with no arguments — that is the entire invocation.\n\"Install-spawn context\" (described below) is a mode identified by the spawning instruction's own\nprose, not a command-line option — never turn it into `--context install-spawn` or any other\nargument. The Bridge MCP tools this command names (`ping`, `get_install_manifest`,\n`apply_install_manifest`, `invite_member`) exist only inside the currently running agent session —\nnever invoke them through Bash or by constructing a direct HTTP request. This command owns and\nautomatically drives its own internal stages; do not reproduce or hand-execute those stages\nyourself. Never place the API-key value in a Bash command, its arguments, printed output, a log\nline, an error message, or a file you write; verify connectivity with the `ping` MCP tool only —\nnever through an environment export, `echo`, or another shell command.\n\nThis command takes no arguments; any `$ARGUMENTS` text is ignored.\n\nCommand contract version: **10**. 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). 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\n**Plain-language narration (BAPI-1174).** Every major operation this command performs — reading the\nmanifest, deriving field values, applying the configuration, persisting the routing credential,\nrendering the capability report — gets a short, plain-language explanation of WHAT is happening and WHY\nit matters to the person running the command, in ordinary words rather than internal shorthand.\nUser-facing narration you author yourself must never name an internal stage number, this command's\ncontract version, a raw internal MCP tool identifier (`get_install_manifest`, `apply_install_manifest`,\n`invite_member`, …), or a manifest field key (`command_contract_version`, `next_step`, `configured`,\n…) — say what is actually happening instead (\"checking whether this project is already set up,\" not\n\"reading `configured` from the manifest\"). This restriction is scoped to narration YOU compose: it does\nnot touch this file's own internal procedure, which keeps its stage headings, contract-version checks,\nand tool/field names exactly as written — an agent following these instructions still reads them\nverbatim to decide what to do next; it just never repeats that internal vocabulary back to the person\nrunning the command. Two exceptions are not narration at all and stay exactly as specified wherever\nthis file calls for them: a user-invocable command name (`/install-bridge`, `/learn-repository`,\n`connect-github`, `start-tickets`, …) that the person needs to actually run, and any value a stage\nexplicitly marks as server-authored and rendered verbatim (the concise capability report, `human_setup`\ndisplay text, a `next_step.command` string) — those are never reworded, curated, or filtered by you\neither way.\n\nIf any stage fails, stop immediately and explain in plain language what could not be completed and why\n— describe the actual problem (a failed manifest read, a rejected field, a connectivity error) rather\nthan naming the internal stage label.\n\n**Install-spawn context.** When the spawning instruction explicitly says this `/install-bridge` run is\nin the \"install-spawn context\" (it was launched by the `install-bridge` CLI's fresh agent session),\nStage 8 and Stage 9 are SKIPPED and the single closing interaction is the 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 and 9 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`, `human_setup`,\n `locked_tools`, `unlocked_tools`) — but ignore those here; the accurate capability status is the\n post-apply read in Stage 7. `tool_capabilities` is the COMPLETE catalog-backed report field (one\n entry per registered MCP tool, grouped and ordered by the server); `workflows` is the separate\n curated collection of non-MCP workflows (slash commands and packaged CLI subcommands, which are not\n registered MCP tools and never appear in `tool_capabilities`); `concise_tool_capabilities` is the\n ADDITIVE projection over BOTH of those that Stage 7 actually renders — a server-ordered array of\n exactly two availability sections, each `{id, name, tools}` holding mixed capability items (see\n Stage 7); `human_setup` is a SEPARATE additive top-level list (contract v9) naming each remaining\n unconfigured integration and what finishing it unlocks — also rendered by Stage 7, and never a third\n section of `concise_tool_capabilities`; `locked_tools` / `unlocked_tools` are LEGACY compatibility\n data covering only the VCS/index policy cases and are NOT the tool inventory.\n4. Compare the manifest's `command_contract_version` to this command's contract version (10, 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 Stage 9.\n 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 Stage 9. Then continue to the independently gated Stage 11.\n In the install-spawn context the ordinary Stage 8–9 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, 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). 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 Stage 9, 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### What finishing setup unlocks: rendering `human_setup` (contract v9)\n\nThe same manifest response this stage already read for `concise_tool_capabilities` also carries a\nSEPARATE top-level field, `human_setup` — an ordered list naming each remaining unconfigured\nintegration and what finishing it unlocks, computed server-side from the same checklist data\n`integrations` already carries. It is additive: a `human_setup`-unaware reader of this manifest sees no\nchange to `integrations`, `tool_capabilities`, `workflows`, or the two-section\n`concise_tool_capabilities` shape above. It is never a third section of that report — keep the two\nsections above exactly as they are and render this as its own, separate block.\n\nCompatibility and validity, in this order:\n\n- **`human_setup` is absent from the response entirely** (an older, still-compatible v8 manifest) →\n render nothing for this block. Do not fabricate a heading, a completion line, or fallback setup\n advice, and do not treat the absence as an error.\n- **`human_setup` is present but malformed** (not a list, or an entry missing its `display_text`) →\n render nothing for this block, the same as if it were absent. The two-section capability report above\n is unaffected either way.\n- **`human_setup` is a valid, empty list (`[]`)** → render nothing: no heading, no \"everything is set\n up\" line, no completion claim. An empty list means every integration is already configured, and the\n Available now / Needs setup sections above already say what that state unlocks.\n- **`human_setup` is a valid, non-empty list** → render it under this exact heading:\n\n **What finishing setup unlocks**\n\n Render one compact Markdown list item per entry, in the server's given order, using\n `human_setup[].display_text` as that item's ENTIRE substantive content, byte-for-byte. Do not\n summarize, reword, sort, filter, merge, or otherwise compose your own setup advice from\n `configure_in`, `setup_instructions`, `required_for`, `integrations`, or any tool/workflow catalog —\n the same server-authority rule Stage 7 already applies to `concise_tool_capabilities` extends to this\n field. `configure_in` and `setup_instructions` travel on each entry for completeness but are not\n separately rendered here; `display_text` already carries the one line this block shows.\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 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–9) —\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 the remaining applicable Stage 8 and Stage 9\n behavior), 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), and\nthe Stage 8 outcome — the exact server-provided next-step command when the manifest returned a\nnon-empty one, otherwise the fact that no next step was recommended (never a substituted command of\nyour 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",
|
|
17
|
-
"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
|
|
16
|
+
"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\n**How to run this.** Run `/install-bridge` now, with no arguments — that is the entire invocation.\n\"Install-spawn context\" (described below) is a mode identified by the spawning instruction's own\nprose, not a command-line option — never turn it into `--context install-spawn` or any other\nargument. The Bridge MCP tools this command names (`ping`, `get_install_manifest`,\n`apply_install_manifest`, `invite_member`) exist only inside the currently running agent session —\nnever invoke them through Bash or by constructing a direct HTTP request. This command owns and\nautomatically drives its own internal stages; do not reproduce or hand-execute those stages\nyourself. Never place the API-key value in a Bash command, its arguments, printed output, a log\nline, an error message, or a file you write; verify connectivity with the `ping` MCP tool only —\nnever through an environment export, `echo`, or another shell command.\n\nThis command takes no arguments; any `$ARGUMENTS` text is ignored.\n\nCommand contract version: **10**. 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). 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\n**Plain-language narration (BAPI-1174).** Every major operation this command performs — reading the\nmanifest, deriving field values, applying the configuration, persisting the routing credential,\nrendering the capability report — gets a short, plain-language explanation of WHAT is happening and WHY\nit matters to the person running the command, in ordinary words rather than internal shorthand.\nUser-facing narration you author yourself must never name an internal stage number, this command's\ncontract version, a raw internal MCP tool identifier (`get_install_manifest`, `apply_install_manifest`,\n`invite_member`, …), or a manifest field key (`command_contract_version`, `next_step`, `configured`,\n…) — say what is actually happening instead (\"checking whether this project is already set up,\" not\n\"reading `configured` from the manifest\"). This restriction is scoped to narration YOU compose: it does\nnot touch this file's own internal procedure, which keeps its stage headings, contract-version checks,\nand tool/field names exactly as written — an agent following these instructions still reads them\nverbatim to decide what to do next; it just never repeats that internal vocabulary back to the person\nrunning the command. Two exceptions are not narration at all and stay exactly as specified wherever\nthis file calls for them: a user-invocable command name (`/install-bridge`, `/learn-repository`,\n`connect-github`, `start-tickets`, …) that the person needs to actually run, and any value a stage\nexplicitly marks as server-authored and rendered verbatim (the concise capability report, `human_setup`\ndisplay text, a `next_step.command` string) — those are never reworded, curated, or filtered by you\neither way.\n\nIf any stage fails, stop immediately and explain in plain language what could not be completed and why\n— describe the actual problem (a failed manifest read, a rejected field, a connectivity error) rather\nthan naming the internal stage label.\n\n**Install-spawn context.** When the spawning instruction explicitly says this `/install-bridge` run is\nin the \"install-spawn context\" (it was launched by the `install-bridge` CLI's fresh agent session),\nStage 8 and Stage 9 are SKIPPED and the single closing interaction is the 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 and 9 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`, `human_setup`,\n `locked_tools`, `unlocked_tools`) — but ignore those here; the accurate capability status is the\n post-apply read in Stage 7. `tool_capabilities` is the COMPLETE catalog-backed report field (one\n entry per registered MCP tool, grouped and ordered by the server); `workflows` is the separate\n curated collection of non-MCP workflows (slash commands and packaged CLI subcommands, which are not\n registered MCP tools and never appear in `tool_capabilities`); `concise_tool_capabilities` is the\n ADDITIVE projection over BOTH of those that Stage 7 actually renders — a server-ordered array of\n exactly two availability sections, each `{id, name, tools}` holding mixed capability items (see\n Stage 7); `human_setup` is a SEPARATE additive top-level list (contract v9) naming each remaining\n unconfigured integration and what finishing it unlocks — also rendered by Stage 7, and never a third\n section of `concise_tool_capabilities`; `locked_tools` / `unlocked_tools` are LEGACY compatibility\n data covering only the VCS/index policy cases and are NOT the tool inventory.\n4. Compare the manifest's `command_contract_version` to this command's contract version (10, 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 Stage 9.\n 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 Stage 9. Then continue to the independently gated Stage 11.\n In the install-spawn context the ordinary Stage 8–9 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, 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). 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 Stage 9, 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 Also do NOT derive `learned_mcp_guidance`. It appears in the manifest as an unset,\n confirmation-requiring field, but it is not a bootstrap field: its evidence comes only from the\n local MCP discovery that `/learn-repository` performs. A value guessed here and approved would\n become the field's only write, because the server is first-write-only and every later\n `/learn-repository` run would report it as already set. Install neither proposes, drafts, nor\n applies it.\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).\n The same holds for `learned_mcp_guidance`: never include it either.\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### What finishing setup unlocks: rendering `human_setup` (contract v9)\n\nThe same manifest response this stage already read for `concise_tool_capabilities` also carries a\nSEPARATE top-level field, `human_setup` — an ordered list naming each remaining unconfigured\nintegration and what finishing it unlocks, computed server-side from the same checklist data\n`integrations` already carries. It is additive: a `human_setup`-unaware reader of this manifest sees no\nchange to `integrations`, `tool_capabilities`, `workflows`, or the two-section\n`concise_tool_capabilities` shape above. It is never a third section of that report — keep the two\nsections above exactly as they are and render this as its own, separate block.\n\nCompatibility and validity, in this order:\n\n- **`human_setup` is absent from the response entirely** (an older, still-compatible v8 manifest) →\n render nothing for this block. Do not fabricate a heading, a completion line, or fallback setup\n advice, and do not treat the absence as an error.\n- **`human_setup` is present but malformed** (not a list, or an entry missing its `display_text`) →\n render nothing for this block, the same as if it were absent. The two-section capability report above\n is unaffected either way.\n- **`human_setup` is a valid, empty list (`[]`)** → render nothing: no heading, no \"everything is set\n up\" line, no completion claim. An empty list means every integration is already configured, and the\n Available now / Needs setup sections above already say what that state unlocks.\n- **`human_setup` is a valid, non-empty list** → render it under this exact heading:\n\n **What finishing setup unlocks**\n\n Render one compact Markdown list item per entry, in the server's given order, using\n `human_setup[].display_text` as that item's ENTIRE substantive content, byte-for-byte. Do not\n summarize, reword, sort, filter, merge, or otherwise compose your own setup advice from\n `configure_in`, `setup_instructions`, `required_for`, `integrations`, or any tool/workflow catalog —\n the same server-authority rule Stage 7 already applies to `concise_tool_capabilities` extends to this\n field. `configure_in` and `setup_instructions` travel on each entry for completeness but are not\n separately rendered here; `display_text` already carries the one line this block shows.\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. When you render a command and `next_step.command_context` is a non-empty string, print it\n verbatim on its own line directly under the command. It is the server's plain-language\n explanation of what the command gives the human and how long it takes. Do not reword, shorten, or\n extend it. When it is empty, missing, or not a string, render nothing in its place.\n For any other supporting context, 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 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–9) —\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 the remaining applicable Stage 8 and Stage 9\n behavior), 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), and\nthe Stage 8 outcome — the exact server-provided next-step command when the manifest returned a\nnon-empty one, otherwise the fact that no next step was recommended (never a substituted command of\nyour 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",
|
|
17
|
+
"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 usually takes 15–30 minutes — the research agents read the actual\n codebase, and all the unlearned fields are researched in parallel, so the wait is roughly the\n slowest single field rather than the sum of all of them.\n 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 Keep the 15–30 minute range exactly as written; do not invent a different number of minutes.\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",
|
|
18
18
|
"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",
|
|
19
19
|
"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",
|
|
20
20
|
"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",
|