@bridge_gpt/mcp-server 0.2.41 → 0.2.42

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.
Files changed (73) hide show
  1. package/README.md +10 -10
  2. package/build/agent-capabilities/cli.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +17 -4
  4. package/build/claude-user-config-doctor.js +42 -11
  5. package/build/cli-release.js +2 -1
  6. package/build/commands.generated.js +4 -4
  7. package/build/conduct-epic/bridge-client.js +354 -113
  8. package/build/conduct-epic/checkpoint-store.js +17 -0
  9. package/build/conduct-epic/cli.js +752 -99
  10. package/build/conduct-epic/cut-protocol.js +327 -0
  11. package/build/conduct-epic/spawn.js +14 -2
  12. package/build/conductor/bridge-api-client.js +27 -1
  13. package/build/conductor/cli.js +46 -1
  14. package/build/conductor/doctor.js +101 -16
  15. package/build/conductor/epic-reconcile.js +72 -19
  16. package/build/conductor/epic-runtime.js +15 -3
  17. package/build/conductor/errors.js +47 -0
  18. package/build/conductor/git-hooks.js +205 -11
  19. package/build/conductor/install-doctor.js +230 -1
  20. package/build/conductor/local-merge.js +130 -28
  21. package/build/conductor/tools.js +32 -3
  22. package/build/conductor/worker-ledger-cli.js +27 -1
  23. package/build/conductor-bin.js +15 -15
  24. package/build/credentials-cli.js +3 -2
  25. package/build/doctor.js +107 -41
  26. package/build/executor/cli.js +48 -1
  27. package/build/executor/env.js +21 -0
  28. package/build/executor/index-scope.js +39 -0
  29. package/build/executor/job-log-registry.js +69 -0
  30. package/build/executor/job-runner.js +148 -26
  31. package/build/executor/live-worker-registry.js +83 -0
  32. package/build/executor/observation.js +167 -6
  33. package/build/executor/platform.js +147 -3
  34. package/build/executor/process.js +58 -14
  35. package/build/executor/runner.js +235 -48
  36. package/build/executor/test-clock.js +3 -2
  37. package/build/index-scope-contract.js +96 -0
  38. package/build/index.js +153 -204
  39. package/build/init.js +83 -22
  40. package/build/install-bridge-conductor.js +323 -14
  41. package/build/install-bridge.js +202 -38
  42. package/build/install-doctor.js +23 -9
  43. package/build/install-reexec.js +2 -1
  44. package/build/launcher-config-inspection.js +83 -22
  45. package/build/mcp-host-config.js +331 -67
  46. package/build/mcp-host-targets.js +45 -21
  47. package/build/mcp-identity.js +92 -0
  48. package/build/mcp-install-state.js +94 -1
  49. package/build/mcp-invoke.js +2 -1
  50. package/build/mcp-provisioning.js +45 -12
  51. package/build/mcp-registration-doctor.js +35 -13
  52. package/build/mcp-server-invocation.js +4 -2
  53. package/build/merge-pull-request.js +208 -9
  54. package/build/pipelines.generated.js +3 -3
  55. package/build/plane/defaults.js +4 -1
  56. package/build/plane/preflight.js +81 -10
  57. package/build/plane/test-fakes.js +9 -1
  58. package/build/readme.generated.js +1 -1
  59. package/build/regression-check.js +3 -2
  60. package/build/review-tickets.js +8 -7
  61. package/build/run-unit-tests-launcher.js +74 -1
  62. package/build/schedule-run.js +3 -2
  63. package/build/setup-epic.js +453 -78
  64. package/build/sfcc/tool-wrapper.js +15 -0
  65. package/build/start-tickets-prereqs.js +11 -6
  66. package/build/start-tickets.js +91 -85
  67. package/build/update-check.js +3 -2
  68. package/build/upgrade-advice.js +2 -1
  69. package/build/upgrade-cli.js +50 -18
  70. package/build/version.generated.js +1 -1
  71. package/docs/CONDUCTOR.md +22 -0
  72. package/docs/install/mcp-tool-integrations.md +19 -3
  73. package/package.json +2 -2
@@ -2,17 +2,17 @@
2
2
  // This file is produced by scripts/bundle-commands.js
3
3
  export const COMMANDS = {
4
4
  "bridge-research.md": "Run multi-source, fact-checked web research via Bridge API and save a cited report locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nExecute all steps in this command as a simple linear sequence of MCP tool calls.\n\n## Step 1 — Parse Arguments\n\nParse `$ARGUMENTS`:\n- The required `query` is the full text of `$ARGUMENTS` after removing any recognized flags.\n- An optional `--ticket <KEY>` flag captures a Jira ticket key (e.g., `BAPI-123`) to associate the research with a specific ticket. If `--ticket` appears, treat the immediately following token as the ticket key and remove both from the query.\n- If `$ARGUMENTS` is empty, or the query (after flag removal) is blank, stop immediately and display:\n\n```\nUsage: /bridge-research <question> [--ticket PROJ-123]\nExample: /bridge-research \"Best practices for rate limiting in FastAPI?\"\n```\n\n## Step 2 — Resolve Docs Directory\n\nCall `get_docs_dir` (no parameters). Store the returned path as `docs_dir`.\n\n## Step 3 — Run Research\n\nCall `request_deep_research` with:\n- `query`: the parsed question\n- `wait_for_result`: `true`\n- `ticket_number`: the value from `--ticket` if provided; omit this parameter entirely if not present\n\nThis step polls until the research completes (up to 15 minutes) and returns the full cited report directly. The tool appends a literal `Saved to <path>` line to its result — extract that line and store the path as `saved_path`.\n\nIf the tool returns an error, stop immediately and display:\n\n```\nResearch failed: <error message from the tool>\n```\n\n## Step 4 — Confirm\n\nDisplay a confirmation message:\n\n```\nResearch complete.\nSaved to: {saved_path}\n```\n\n## Final Summary\n\nDisplay a summary block:\n\n```\n## Bridge Research Report\n\n- **Query**: <query>\n- **Status**: Completed\n- **Local File**: {saved_path}\n```\n\nOn failure at any step, stop immediately, display which step failed and the error details, and do not proceed.\n",
5
- "check-ci.md": "# Check CI: $ARGUMENTS\n\n$ARGUMENTS\n\n> **Warning**: Keep this file behaviorally in sync with `mcp_server/instructions/monitor-ci-checks.md` to prevent drift (BAPI-462).\n\nThis command takes a Jira ticket key (e.g., `BAPI-150`), discovers CI checks for the current commit, polls their status, and applies confidence-gated code corrections for failures. It is designed to run after `/create-pr` completes.\n\nIf any critical stage fails (Stage 0), stop immediately and report which stage failed and why. Non-critical stages (Stage 1, Stage 2, and Stage 3) should log a warning but not stop the pipeline.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline to monitor and respond to CI checks for a Jira ticket. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a single required `ticket_key` argument. The expected format is a Jira ticket key such as `BAPI-150` or `PROJ-123` — one or more uppercase letters, a hyphen, and one or more digits (regex: `[A-Z]+-\\d+`). If `$ARGUMENTS` is empty or the value does not match the expected format, stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /check-ci <ticket_key> (e.g., /check-ci BAPI-150)\n ```\n\n2. **Connectivity check**: Call the `ping` MCP tool (no parameters). If the ping fails or does not return `\"status\": \"ok\"`, stop immediately and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n3. **Get current commit SHA**: Run `git rev-parse HEAD` in the terminal. Store the result as `commit_sha`.\n\n4. **Get current branch**: Run `git branch --show-current` in the terminal. Store the result as `current_branch`.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Resolve CI Checks\n\n1. **Initial resolution**: Call the `resolve_ci_checks` MCP tool immediately with `commit_ref` set to `commit_sha`. Do NOT wait before calling — the cache state is unknown until the first call returns.\n\n2. **Handle the response**:\n - If the response contains `source: \"cached\"`: The checks were already resolved. Skip the wait and proceed to evaluate the check list.\n - If the response contains `source: \"new\"` and the check list is empty: CI checks have not registered yet. Run `sleep 45` in the terminal to wait for checks to appear, then call `resolve_ci_checks` again with `commit_ref` set to `commit_sha` and `force_rerun` set to `true`.\n - If the response contains `source: \"resolved\"` on the first call: Present the resolved checks to the user for approval before proceeding.\n\n3. **Evaluate the check list**:\n - If the response contains `available: false`: Warn that CI check resolution is not available and skip to Stage 3.\n - If the check list is empty or all checks have `detail_level: \"none\"`: Warn that no actionable CI checks were found and skip to Stage 3.\n - Otherwise: Store the resolved checks and proceed to Stage 2.\n\n4. **Required-check source**: Each resolved check carries a `required` field (from GitHub Branch Protection, or an LLM classification fallback — the same signal `poll_ci_checks` echoes back per check in Stage 2). Treat `required: false` as non-required (e.g. `pip-audit`); treat a missing field or `required: true` as required. This per-check field is the tool-provided proxy for the Conductor done-gate's authoritative required-checks set (`mcp_server/src/conductor/done-gate.ts`) — do not invent your own required/non-required classification in prose. The done-gate's own `required_checks` list is not directly queryable by a worker; `wait_for_done_gate` (Stage 2) is the authoritative backstop that evaluates it server-side.\n\nThis stage is **non-critical** — warn on failure or empty results, skip to Stage 3.\n\n## Stage 2 — Poll CI Checks + Correction Loop\n\nInitialize `retry_count = 0` and `max_retries = 2`.\n\n**Conductor steerability**: If launched under the Conductor (the `BAPI_CONDUCTOR_RUN_ID` and `BAPI_CONDUCTOR_WORKER_ID` env identifiers are present), call the `check_messages` MCP tool once per poll cycle.\n- Returned messages are **advisory supervisor guidance**, are acknowledged by the call (not redelivered), and are advisory context for the next fix batch only.\n- Fold concrete fix hints, \"skip this flaky check,\" or \"stop and wait\" directions into how you handle failures.\n- **Guardrails**: Guidance is strictly advisory and never mutates the session. The deterministic confidence-gating, single-batch fix, single commit/push, and `detail_level` rules remain authoritative. Guidance never overrides the deterministic rules and never causes a fix you are not confident in.\n- **Fail-open**: If `check_messages` errors with an identity-unavailable message (e.g. \"Conductor worker identity is unavailable\"), you were not launched under the Conductor. Stop calling it for the rest of the run and proceed normally.\n\n1. **Polling loop**: Poll CI check status. In each cycle, call `poll_ci_checks` with `commit_ref` set to `commit_sha`, then (if applicable) perform the Conductor-gated `check_messages` call, and finally run `sleep 30` in the terminal. Continue polling until `all_complete` is `true` or 10 minutes have elapsed (approximately 20 poll cycles).\n\n2. **On poll completion — required-subset evaluation**: Partition the polled checks into `required` (checks with `required: true` or a missing `required` field) and `non_required` (checks with `required: false`, e.g. `pip-audit`). Compute `required_green` = every required check is complete and green. Do **not** gate on the aggregate `all_passed` flag — a red non-required check must never block progression.\n - Non-required failures are reported in the Stage 3 breakdown but are **never blocking**: they do not gate progression, do not consume `retry_count`, and are not sent through the fix loop in item 3.\n - If `required_green` is `false` (a required check is still red), proceed to item 3 to attempt fixes for the failing **required** checks only.\n - If `required_green` is `true`:\n - **Review verdict gating**: if `claude-review` is one of the required checks, its GitHub check reaching a non-pending/\"success\" state means only that the review action *ran* — this is **transport completion, not approval**. Fetch the PR's comments (e.g. `gh pr view --json comments`) and look for the sticky comment's machine-readable verdict line. The review counts as approved only when that comment contains `claude-review-verdict: approved` on its own line **and** the accompanying `Reviewed-SHA:` line matches the current `commit_sha` — a verdict posted against an older head does not count.\n - **Missing or stale verdict** (no `claude-review-verdict:` line at all, or a `Reviewed-SHA:` that does not match the current `commit_sha`): do not treat the review as approved. Report it in Stage 3 and do **not** call `wait_for_done_gate` this cycle — a missing or stale verdict is not approval.\n - **`claude-review-verdict: changes_requested`** (the reviewer rejected the current head): do **not** call `wait_for_done_gate` for the rejected old head. When you are confident you can address the review, remediate it in-session — mirroring the confidence-gated commit+push already specified for CI-failure fixes — instead of merely reporting it:\n - Read the review findings from the sticky comment and any inline review comments.\n - Apply confidence gating: remediate only when you are confident you can address ALL findings; otherwise report in Stage 3 without committing.\n - Address all review findings across all affected files in a single batch. Do not fix one at a time.\n - After applying all fixes, perform a single `git commit` and `git push` so `origin/feature/<KEY>` advances to the new head and a fresh CI/review runs against it.\n - Increment `retry_count`. If `retry_count` exceeds `max_retries`: if launched under the Conductor, call `check_messages` one final time; if the supervisor sent explicit \"continue\" guidance with a concrete hint, apply it for exactly **one additional batch** (the only way the ceiling is raised); otherwise stop the correction loop and proceed to Stage 3 with a warning.\n - Otherwise, update `commit_sha` to the new HEAD (`git rev-parse HEAD`) and restart the polling loop against the new head.\n - **Conductor done-gate**: once the required subset is green and (if `claude-review` is required) the verdict token confirms approval for the current head, and if launched under the Conductor (the `BAPI_CONDUCTOR_RUN_ID`/`BAPI_CONDUCTOR_WORKER_ID` identifiers are present), call the `wait_for_done_gate` MCP tool **once** from inside your worktree before proceeding (no arguments are required — it self-resolves the PR number and head commit SHA from the worktree, fetches the review snapshot, evaluates the composite done-gate against the Conductor's authoritative `required_checks` config, and emits a `gate.met` event correlated to your run/worker so the supervisor can fold and merge). This tool does **not** merge or mutate the repo. It applies its own bounded fast-path poll (a short internal cap, not a multi-minute wait) — if it times out or does not observe `gate_met`, **exit cleanly** to Stage 3 without treating the timeout as a failure: the Conductor's own reconciliation pass is the correctness backstop for reaching `gate.met`, not this call. Fail-open: if the tool errors with an identity-unavailable message, you were not launched under the Conductor — skip it. Then proceed to Stage 3.\n\n3. **On required-subset failures detected**: Examine each failed **required** check's `detail_level` (non-required failures such as `pip-audit` are never processed here — they were already reported and skipped in item 2, and never consume a retry):\n\n - **`detail_level: \"full\"`** — Apply confidence gating:\n - Read the full `failure_details` payload for all failed checks.\n - If you are confident you can fix the errors, address ALL failures across all affected files in a single batch. Do not fix one at a time.\n - After applying all fixes, perform a single `git commit` and `git push`.\n - Increment `retry_count`.\n - If `retry_count` exceeds `max_retries`: if launched under the Conductor, call `check_messages` one final time. If the supervisor sent explicit \"continue\" guidance with a concrete hint, apply it for exactly **one additional batch** (this is the only way the ceiling is raised). Otherwise, stop the correction loop and proceed to Stage 3 with a warning.\n - Otherwise, update `commit_sha` to the new HEAD (`git rev-parse HEAD`) and restart the polling loop.\n - **`detail_level: \"url_only\"`** — Report the check name and URL to the user. Do not attempt fixes. Do not consume a retry.\n - **`detail_level: \"none\"`** — Report the check name only. Do not attempt fixes. Do not consume a retry.\n\n4. **Handle `unknown_checks`**: If the poll response contains `unknown_checks`, call `resolve_ci_checks` with `commit_ref` set to `commit_sha` and `force_rerun` set to `true` at most ONCE. If `unknown_checks` persist on the next poll, warn the user that the CI check configuration is unresolvable and skip to Stage 3.\n\n5. **Timeout**: If 10 minutes elapse without `all_complete` becoming `true`, warn that polling timed out and proceed to Stage 3.\n\nThis stage is **non-critical** — warn on failure or timeout, continue to Stage 3 regardless.\n\n## Stage 3 — Summary Report\n\nDisplay a structured completion report. The report **must** be presented as markdown, using exactly the structure below:\n\n```\n## CI Check Report\n\n**Ticket**: <ticket_key>\n**Branch**: <current_branch>\n**Commit SHA**: <commit_sha>\n**Status**: <Passed / Failed / Timed Out / Not Available>\n\n**Per-check breakdown**:\n| Check Name | Status | Required | Detail Level |\n|------------|--------|----------|--------------|\n| <name> | <pass/fail> | <yes/no> | <full/url_only/none> |\n\n**Review verdict**: <approved / changes_requested / not yet posted / N/A — claude-review not required>\n**Fixes attempted**: <retry_count> of <max_retries>\n**Supervisor guidance applied**: <yes/no>\n\n**Warnings**:\n<If any non-critical stages had warnings (Stage 1: resolution unavailable,\nStage 2: timeout, unfixable required-check failures, unknown_checks,\nnon-required check failures reported for visibility),\nlist them here. If no warnings, omit this section.>\n```\n\nThis stage is **non-critical** — display the report regardless.\n\n## Final Report\n\nOn success, display the structured report from Stage 3 confirming the CI check status, including per-check breakdown, fixes attempted, and any warnings from earlier stages.\n\nOn failure at any critical stage (Stage 0), display which stage failed and the error details.\n",
5
+ "check-ci.md": "# Check CI: $ARGUMENTS\n\n$ARGUMENTS\n\n> **Warning**: Keep this file behaviorally in sync with `mcp_server/instructions/monitor-ci-checks.md` to prevent drift (BAPI-462).\n\nThis command takes a Jira ticket key (e.g., `BAPI-150`), discovers CI checks for the current commit, polls their status, and applies confidence-gated code corrections for failures. It is designed to run after `/create-pr` completes.\n\nIf any critical stage fails (Stage 0), stop immediately and report which stage failed and why. Non-critical stages (Stage 1, Stage 2, and Stage 3) should log a warning but not stop the pipeline.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline to monitor and respond to CI checks for a Jira ticket. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a single required `ticket_key` argument. The expected format is a Jira ticket key such as `BAPI-150` or `PROJ-123` — one or more uppercase letters, a hyphen, and one or more digits (regex: `[A-Z]+-\\d+`). If `$ARGUMENTS` is empty or the value does not match the expected format, stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /check-ci <ticket_key> (e.g., /check-ci BAPI-150)\n ```\n\n2. **Connectivity check**: Call the `ping` MCP tool (no parameters). If the ping fails or does not return `\"status\": \"ok\"`, stop immediately and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n3. **Get current commit SHA**: Run `git rev-parse HEAD` in the terminal. Store the result as `commit_sha`.\n\n4. **Get current branch**: Run `git branch --show-current` in the terminal. Store the result as `current_branch`.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Resolve CI Checks\n\n1. **Initial resolution**: Call the `resolve_ci_checks` MCP tool immediately with `commit_ref` set to `commit_sha`. Do NOT wait before calling — the cache state is unknown until the first call returns.\n\n2. **Handle the response**:\n - If the response contains `source: \"cached\"`: The checks were already resolved. Skip the wait and proceed to evaluate the check list.\n - If the response contains `source: \"new\"` and the check list is empty: CI checks have not registered yet. Run `sleep 45` in the terminal to wait for checks to appear, then call `resolve_ci_checks` again with `commit_ref` set to `commit_sha` and `force_rerun` set to `true`.\n - If the response contains `source: \"resolved\"` on the first call: Present the resolved checks to the user for approval before proceeding.\n\n3. **Evaluate the check list**:\n - If the response contains `available: false`: Warn that CI check resolution is not available and skip to Stage 3.\n - If the check list is empty or all checks have `detail_level: \"none\"`: Warn that no actionable CI checks were found and skip to Stage 3.\n - Otherwise: Store the resolved checks and proceed to Stage 2.\n\n4. **Required-check source**: Each resolved check carries a `required` field (from GitHub Branch Protection, or an LLM classification fallback — the same signal `poll_ci_checks` echoes back per check in Stage 2). Treat `required: false` as non-required (e.g. `pip-audit`); treat a missing field or `required: true` as required. This per-check field is the tool-provided proxy for the Conductor done-gate's authoritative required-checks set (`mcp_server/src/conductor/done-gate.ts`) — do not invent your own required/non-required classification in prose. The done-gate's own `required_checks` list is not directly queryable by a worker; `wait_for_done_gate` (Stage 2) is the authoritative backstop that evaluates it server-side.\n\nThis stage is **non-critical** — warn on failure or empty results, skip to Stage 3.\n\n## Stage 2 — Poll CI Checks + Correction Loop\n\nInitialize `retry_count = 0` and `max_retries = 2`.\n\n**Conductor steerability**: If launched under the Conductor (the `BAPI_CONDUCTOR_RUN_ID` and `BAPI_CONDUCTOR_WORKER_ID` env identifiers are present), call the `check_messages` MCP tool once per poll cycle.\n- Returned messages are **advisory supervisor guidance**, are acknowledged by the call (not redelivered), and are advisory context for the next fix batch only.\n- Fold concrete fix hints, \"skip this flaky check,\" or \"stop and wait\" directions into how you handle failures.\n- **Guardrails**: Guidance is strictly advisory and never mutates the session. The deterministic confidence-gating, single-batch fix, single commit/push, and `detail_level` rules remain authoritative. Guidance never overrides the deterministic rules and never causes a fix you are not confident in.\n- **Fail-open**: If `check_messages` errors with an identity-unavailable message (e.g. \"Conductor worker identity is unavailable\"), you were not launched under the Conductor. Stop calling it for the rest of the run and proceed normally.\n\n1. **Polling loop**: Poll CI check status. In each cycle, call `poll_ci_checks` with `commit_ref` set to `commit_sha`, then (if applicable) perform the Conductor-gated `check_messages` call, and finally run `sleep 30` in the terminal. Continue polling until `all_complete` is `true` or 10 minutes have elapsed (approximately 20 poll cycles).\n\n2. **Conflicting-head escalation**: if the poll shows zero check-runs for `commit_sha` after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for `current_branch`'s pull request. A `mergeable` value of `CONFLICTING` or a `mergeStateStatus` of `DIRTY` means GitHub will not start `pull_request` workflows for that head, so continuing to poll the same SHA is futile — merge the current base branch into your branch, resolve any conflicts, push the result, update `commit_sha` to the new head (`git rev-parse HEAD`), and restart the polling loop from item 1 against that new SHA. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary wait behavior in item 1.\n\n3. **On poll completion — required-subset evaluation**: Partition the polled checks into `required` (checks with `required: true` or a missing `required` field) and `non_required` (checks with `required: false`, e.g. `pip-audit`). Compute `required_green` = every required check is complete and green. Do **not** gate on the aggregate `all_passed` flag — a red non-required check must never block progression.\n - Non-required failures are reported in the Stage 3 breakdown but are **never blocking**: they do not gate progression, do not consume `retry_count`, and are not sent through the fix loop in item 4.\n - If `required_green` is `false` (a required check is still red), proceed to item 4 to attempt fixes for the failing **required** checks only.\n - If `required_green` is `true`:\n - **Review verdict gating**: if `claude-review` is one of the required checks, its GitHub check reaching a non-pending/\"success\" state means only that the review action *ran* — this is **transport completion, not approval**. Fetch the PR's comments (e.g. `gh pr view --json comments`) and look for the sticky comment's machine-readable verdict line. The review counts as approved only when that comment contains `claude-review-verdict: approved` on its own line **and** the accompanying `Reviewed-SHA:` line matches the current `commit_sha` — a verdict posted against an older head does not count.\n - **Missing or stale verdict** (no `claude-review-verdict:` line at all, or a `Reviewed-SHA:` that does not match the current `commit_sha`): do not treat the review as approved. Report it in Stage 3 and do **not** call `wait_for_done_gate` this cycle — a missing or stale verdict is not approval.\n - **`claude-review-verdict: changes_requested`** (the reviewer rejected the current head): do **not** call `wait_for_done_gate` for the rejected old head. When you are confident you can address the review, remediate it in-session — mirroring the confidence-gated commit+push already specified for CI-failure fixes — instead of merely reporting it:\n - Read the review findings from the sticky comment and any inline review comments.\n - Apply confidence gating: remediate only when you are confident you can address ALL findings; otherwise report in Stage 3 without committing.\n - Address all review findings across all affected files in a single batch. Do not fix one at a time.\n - After applying all fixes, perform a single `git commit` and `git push` so `origin/feature/<KEY>` advances to the new head and a fresh CI/review runs against it.\n - Increment `retry_count`. If `retry_count` exceeds `max_retries`: if launched under the Conductor, call `check_messages` one final time; if the supervisor sent explicit \"continue\" guidance with a concrete hint, apply it for exactly **one additional batch** (the only way the ceiling is raised); otherwise stop the correction loop and proceed to Stage 3 with a warning.\n - Otherwise, update `commit_sha` to the new HEAD (`git rev-parse HEAD`) and restart the polling loop against the new head.\n - **Conductor done-gate**: once the required subset is green and (if `claude-review` is required) the verdict token confirms approval for the current head, and if launched under the Conductor (the `BAPI_CONDUCTOR_RUN_ID`/`BAPI_CONDUCTOR_WORKER_ID` identifiers are present), call the `wait_for_done_gate` MCP tool **once** from inside your worktree before proceeding (no arguments are required — it self-resolves the PR number and head commit SHA from the worktree, fetches the review snapshot, evaluates the composite done-gate against the Conductor's authoritative `required_checks` config, and emits a `gate.met` event correlated to your run/worker so the supervisor can fold and merge). This tool does **not** merge or mutate the repo. It applies its own bounded fast-path poll (a short internal cap, not a multi-minute wait) — if it times out or does not observe `gate_met`, **exit cleanly** to Stage 3 without treating the timeout as a failure: the Conductor's own reconciliation pass is the correctness backstop for reaching `gate.met`, not this call. Fail-open: if the tool errors with an identity-unavailable message, you were not launched under the Conductor — skip it. Then proceed to Stage 3.\n\n4. **On required-subset failures detected**: Examine each failed **required** check's `detail_level` (non-required failures such as `pip-audit` are never processed here — they were already reported and skipped in item 3, and never consume a retry):\n\n - **`detail_level: \"full\"`** — Apply confidence gating:\n - Read the full `failure_details` payload for all failed checks.\n - If you are confident you can fix the errors, address ALL failures across all affected files in a single batch. Do not fix one at a time.\n - After applying all fixes, perform a single `git commit` and `git push`.\n - Increment `retry_count`.\n - If `retry_count` exceeds `max_retries`: if launched under the Conductor, call `check_messages` one final time. If the supervisor sent explicit \"continue\" guidance with a concrete hint, apply it for exactly **one additional batch** (this is the only way the ceiling is raised). Otherwise, stop the correction loop and proceed to Stage 3 with a warning.\n - Otherwise, update `commit_sha` to the new HEAD (`git rev-parse HEAD`) and restart the polling loop.\n - **`detail_level: \"url_only\"`** — Report the check name and URL to the user. Do not attempt fixes. Do not consume a retry.\n - **`detail_level: \"none\"`** — Report the check name only. Do not attempt fixes. Do not consume a retry.\n\n5. **Handle `unknown_checks`**: If the poll response contains `unknown_checks`, call `resolve_ci_checks` with `commit_ref` set to `commit_sha` and `force_rerun` set to `true` at most ONCE. If `unknown_checks` persist on the next poll, warn the user that the CI check configuration is unresolvable and skip to Stage 3.\n\n6. **Timeout**: If 10 minutes elapse without `all_complete` becoming `true`, warn that polling timed out and proceed to Stage 3.\n\nThis stage is **non-critical** — warn on failure or timeout, continue to Stage 3 regardless.\n\n## Stage 3 — Summary Report\n\nDisplay a structured completion report. The report **must** be presented as markdown, using exactly the structure below:\n\n```\n## CI Check Report\n\n**Ticket**: <ticket_key>\n**Branch**: <current_branch>\n**Commit SHA**: <commit_sha>\n**Status**: <Passed / Failed / Timed Out / Not Available>\n\n**Per-check breakdown**:\n| Check Name | Status | Required | Detail Level |\n|------------|--------|----------|--------------|\n| <name> | <pass/fail> | <yes/no> | <full/url_only/none> |\n\n**Review verdict**: <approved / changes_requested / not yet posted / N/A — claude-review not required>\n**Fixes attempted**: <retry_count> of <max_retries>\n**Supervisor guidance applied**: <yes/no>\n\n**Warnings**:\n<If any non-critical stages had warnings (Stage 1: resolution unavailable,\nStage 2: timeout, unfixable required-check failures, unknown_checks,\nnon-required check failures reported for visibility),\nlist them here. If no warnings, omit this section.>\n```\n\nThis stage is **non-critical** — display the report regardless.\n\n## Final Report\n\nOn success, display the structured report from Stage 3 confirming the CI check status, including per-check breakdown, fixes attempted, and any warnings from earlier stages.\n\nOn failure at any critical stage (Stage 0), display which stage failed and the error details.\n",
6
6
  "clarify-ticket.md": "Generate clarifying questions for a Jira ticket and save them locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\n`$ARGUMENTS` is a required Jira ticket key (e.g., `PROJ-123`). This command generates clarifying questions for the ticket and saves them locally.\n\nIf any step fails, stop immediately and report which step failed and why.\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\n2. **Validate ticket key format**: Confirm the ticket key matches the Jira key pattern `[A-Z][A-Z0-9]+-\\d+`. If it does not match, stop immediately and report: \"Invalid ticket key format. Expected a Jira key like PROJ-123.\"\n\n## Step 2 — Resolve Docs Directory\n\nCall the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\n## Step 3 — Generate Clarifying Questions\n\nCall the `request_clarifying_questions` MCP tool with:\n- `ticket_number`: the validated `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\nIf the tool returns an error, stop immediately and report: \"Clarifying questions generation failed.\" Include the error details.\n\n## Final Report\n\nOn successful completion, display:\n\n> **Ticket Key**: {ticket_key}\n>\n> **Local File Path**: {docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md\n>\n> **Status**: The clarifying questions document has been saved locally. No changes were pushed to Jira.\n>\n> To incorporate these findings into the Jira ticket description, run: `/update-ticket {ticket_key}`\n",
7
7
  "code-ticket.md": "# Code Ticket: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes a Jira ticket key (e.g., `BAPI-150`), downloads the AI-generated implementation plan and clarifying questions via MCP tools, then executes the plan step by step directly in the main conversation so all progress is visible.\n\nIf any critical stage fails (Stage 0, Stage 1, or Stage 3), stop immediately and report which stage failed and why. Non-critical stages (Stage 2) should log a warning but not stop the pipeline.\n\n---\n\n# Instructions\n\nYou are executing a 4-stage pipeline to implement a Jira ticket using an AI-generated plan. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a single required `ticket_key` argument. The expected format is a Jira ticket key such as `BAPI-150` or `PROJ-123` — one or more uppercase letters, a hyphen, and one or more digits (regex: `[A-Z]+-\\d+`). If `$ARGUMENTS` is empty or the value does not match the expected format, stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /code-ticket <ticket_key> (e.g., /code-ticket BAPI-150)\n ```\n\n2. **Connectivity check**: Call the `ping` MCP tool (no parameters). If the ping fails or does not return `\"status\": \"ok\"`, stop immediately and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Download Implementation Plan\n\nCall the `get_plan` MCP tool with:\n- `ticket_number`: the parsed `ticket_key` from Stage 0\n- `save_locally`: `true`\n\nInspect the response for errors. If the response text contains `NOT_FOUND` or `404` or indicates the plan was not found, stop immediately and display:\n\n```\nNo implementation plan found for <ticket_key>. Run `/plan-ticket <ticket_key>` first to generate one,\nor use the `request_plan_generation` MCP tool with `wait_for_result: true`.\n```\n\nOn success, read and internalize the full plan content. This is the plan you will execute in Stage 3.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 2.\n\n## Stage 2 — Download Clarifying Questions\n\nCall the `get_clarifying_questions` MCP tool with:\n- `ticket_number`: the parsed `ticket_key` from Stage 0\n- `save_locally`: `false`\n\nIf the response contains `NOT_FOUND` or `404` or indicates no clarifying questions were found, log a warning note:\n\n```\nWarning: No clarifying questions found for <ticket_key>. Proceeding without supplementary context.\n```\n\nDo **NOT** stop the pipeline. Clarifying questions are supplementary context, not a hard prerequisite for implementation.\n\nOn success, internalize the clarifying questions content. Reference these for additional context where relevant to implementation steps — the answers provide supplementary guidance on requirements and technical decisions.\n\nThis stage is **non-critical** — warn on failure, continue to Stage 3 regardless.\n\n## Stage 3 — Execute Implementation Plan\n\nExecute the implementation plan step by step, directly in this conversation. Work inline so the user can see all progress and approve tool calls.\n\nFollow these rules:\n\n1. **Execute the plan in order.** Do not skip any steps, especially review steps involving test execution, lint checks, and architectural verification.\n2. **Make code changes** as directed by each step in the plan.\n3. **Run tests and checks** as specified in the plan's review steps.\n4. **Do NOT run `git commit` or `git push`.** Leave all changes uncommitted for developer review.\n5. **If a step is ambiguous or blocked**, note the issue clearly and continue with the next step rather than halting entirely.\n6. **Reference clarifying questions** (if retrieved in Stage 2) when they provide relevant context for a given step.\n7. **If a specific plan step or requirement remains ambiguous** after consulting the plan and any retrieved clarifying questions, call the `get_ticket` MCP tool with `ticket_number` set to the parsed `ticket_key` from Stage 0 to fetch the live Jira ticket details. Use only the fields relevant to resolving that ambiguity, then continue with the affected step. Do not call `get_ticket` unconditionally or as a prerequisite — only when a step's requirement is genuinely unclear.\n\nThis stage is **critical** — if a blocking error prevents further progress, stop and report the failure.\n\n## Stage 4 — Final Summary Report\n\nDisplay a structured report after all stages complete:\n\n```\n## Implementation Complete\n\n**Ticket**: <ticket_key>\n\n**Developer Action Items**:\n- All changes are uncommitted. Review the changes with `git diff` before committing.\n- Run the project's test suite to verify nothing is broken before committing.\n\n**Warnings**:\n<If any non-critical stages had warnings (Stage 2: no clarifying questions),\nlist them here so the developer has full visibility. If no warnings, omit this section.>\n```\n\n## Final Report\n\nOn success, display the structured report from Stage 4 confirming that implementation of the ticket is complete.\n\nOn failure at any critical stage (Stage 0, Stage 1, or Stage 3), display which stage failed and the error details.\n",
8
8
  "commit-ticket.md": "# Commit Ticket: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes a Jira ticket key (e.g., `BAPI-150`), verifies the current git branch matches the ticket, identifies implementation files by cross-referencing git changes against the saved implementation plan, and commits and pushes the work. It is designed to run after `/code-ticket` completes.\n\nIf any critical stage fails (Stage 0, Stage 1, or Stage 3), stop immediately and report which stage failed and why. Non-critical stages (Stage 2, Stage 4, Stage 5, and Stage 6) should log a warning but not stop the pipeline.\n\n---\n\n# Instructions\n\nYou are executing a 7-stage pipeline to commit and push implementation work for a Jira ticket. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a single required `ticket_key` argument. The expected format is a Jira ticket key such as `BAPI-150` or `PROJ-123` — one or more uppercase letters, a hyphen, and one or more digits (regex: `[A-Z]+-\\d+`). If `$ARGUMENTS` is empty or the value does not match the expected format, stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /commit-ticket <ticket_key> (e.g., /commit-ticket BAPI-150)\n ```\n\n2. **Resolve docs directory**: Call the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\n3. **Verify uncommitted changes exist**: Run `git status --porcelain` in the terminal. If the output is empty (no modified, added, or untracked files), stop immediately and display:\n\n ```\n No uncommitted changes found. Nothing to commit for <ticket_key>.\n ```\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Branch Verification and Creation\n\n1. **Get current branch**: Run `git branch --show-current` in the terminal. Store the result as `current_branch`.\n\n2. **Check branch match**: Determine if `current_branch` contains the `ticket_key` (case-insensitive comparison). For example, if the ticket key is `BAPI-150`, branch `feature/BAPI-150-add-caching` matches, as does `feature/BAPI-150` or `bugfix/bapi-150-fix`.\n\n3. **If the branch matches**: Log a confirmation message and proceed:\n\n ```\n Branch '<current_branch>' matches ticket <ticket_key>. Proceeding.\n ```\n\n4. **If the branch does NOT match**: Create a new branch from the current HEAD in the format `feature/<ticket_key>` (e.g., `feature/BAPI-150`). Run `git checkout -b feature/<ticket_key>` in the terminal. If the branch creation fails (e.g., branch already exists), try `git checkout feature/<ticket_key>` instead. If both fail, stop immediately and display:\n\n ```\n Failed to create or switch to branch 'feature/<ticket_key>'.\n Please resolve the branch situation manually and re-run.\n ```\n\n On success, log:\n\n ```\n Created and switched to new branch 'feature/<ticket_key>'.\n ```\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 2.\n\n## Stage 2 — Identify and Stage Implementation Files\n\n1. **Collect git changes**: Run `git status --porcelain` in the terminal. Parse the output to build two lists:\n - `modified_files`: files with status `M`, `MM`, `AM`, or `A` (modified or staged)\n - `untracked_files`: files with status `??` (new untracked files)\n\n Combine into a single list `all_changed_files`.\n\n2. **Load the implementation plan**: Look for the implementation plan file at `{docs_dir}/plans/{ticket_key}-plan.md`. Read the file.\n\n - **If the plan file exists**: Extract file paths mentioned in the plan. Look for patterns like backtick-quoted paths (e.g., `src/python/foo.py`), file references in step descriptions, and any explicit file listings. Build a list `plan_files` of all file paths referenced in the plan.\n\n - **If the plan file does NOT exist**: Log a warning:\n\n ```\n Warning: No implementation plan found at {docs_dir}/plans/{ticket_key}-plan.md.\n Cannot cross-reference changes against plan. Will present all changed files for review.\n ```\n\n Set `plan_files` to an empty list.\n\n3. **Classify changed files**: For each file in `all_changed_files`, classify it into one of three categories:\n\n - **Plan-matched**: The file path appears in `plan_files` (exact match or the plan references a parent directory). These are high-confidence implementation files.\n - **Likely related**: The file is not explicitly in the plan but is a test file for a plan-matched file, a migration file, an `__init__.py` in a directory with plan-matched files, or otherwise clearly related to the implementation (e.g., `requirements.txt` if the plan mentions adding a dependency).\n - **Ambiguous**: The file does not appear related to the plan. These may be pre-existing uncommitted changes.\n\n4. **Present file list for user confirmation**: Display the classified file list to the user:\n\n ```\n ## Files to Commit for <ticket_key>\n\n ### Plan-matched files (high confidence):\n - path/to/file1.py\n - path/to/file2.py\n\n ### Likely related files:\n - tests/pytest/routes/test_file1.py\n - db/alembic/versions/xxxx_migration.py\n\n ### Ambiguous files (not referenced in plan):\n - some/other/file.py\n\n Shall I proceed with committing all listed files?\n If you want to exclude any files, please specify which ones to remove.\n ```\n\n If `plan_files` is empty (plan not found), display all files under a single \"All changed files\" heading instead.\n\n5. **Wait for user confirmation**: The user may:\n - Approve all files (proceed)\n - Specify files to exclude (remove those from the commit list)\n - Cancel entirely (stop the pipeline)\n\n If the user cancels, stop immediately and display:\n\n ```\n Commit cancelled by user. No files were staged or committed.\n ```\n\n6. **Stage the approved files**: Run `git add <file1> <file2> ...` in the terminal, listing only the approved files explicitly. Do NOT use `git add -A` or `git add .`.\n\nThis stage is **non-critical** if the plan file is not found (warn and continue with all files). It is **critical** if the user cancels or if `git add` fails — stop immediately on those failures.\n\n## Stage 3 — Commit and Push\n\n1. **Generate commit message**: Based on the staged files and the implementation plan (if available), generate a concise commit message. The message must:\n - Start with a brief summary line (under 72 characters) that references the ticket key\n - Format: `<ticket_key>: <brief description of changes>`\n - Example: `BAPI-150: Add rate limiting to LLM client`\n - If the plan was available, derive the description from the plan's title or objective\n - If the plan was not available, summarize based on the file names and `git diff --staged` output\n\n2. **Commit**: Run `git commit -m \"<message>\"` in the terminal. If the commit fails due to a pre-commit hook, report the hook output and stop:\n\n ```\n Commit failed due to pre-commit hook. Hook output:\n <hook output>\n\n Please fix the issues and re-run /commit-ticket <ticket_key>.\n ```\n\n3. **Push to remote**: Run `git push -u origin <current_branch>` in the terminal. The `-u` flag sets up upstream tracking. If the push fails, stop immediately and display:\n\n ```\n Push failed. Error:\n <error output>\n\n The commit was created locally. You can push manually with:\n git push -u origin <current_branch>\n ```\n\nThis stage is **critical** — stop immediately on failure.\n\n## Stage 4 — Final Summary Report\n\nDisplay a structured report after all stages complete:\n\n```\n## Commit Complete\n\n**Ticket**: <ticket_key>\n**Branch**: <current_branch>\n**Commit**: <commit_hash> (from `git rev-parse --short HEAD`)\n**Files committed**: <count> files\n**Remote**: Pushed to origin/<current_branch>\n\n**Committed files**:\n- path/to/file1.py\n- path/to/file2.py\n- ...\n\n**Warnings**:\n<If any non-critical warnings occurred (Stage 2: plan not found),\nlist them here. If no warnings, omit this section.>\n```\n\nThis stage is **non-critical** — display the report regardless.\n\n## Stage 5 — Jira Status Transition\n\nThis stage attempts to transition the Jira ticket to the appropriate post-PR status.\n\n1. **Resolve target status**: Call the `resolve_target_status` MCP tool with `ticket_number` set to the `ticket_key`. This returns the cached or LLM-resolved target status for the project.\n\n2. **Attempt transition**: If `resolve_target_status` returned a non-null `target_status`, call the `update_jira_status` MCP tool with `ticket_number` set to the `ticket_key` and `target_status` set to the resolved value. If the ticket is already in the target status, this is a no-op.\n\n3. **On success**: Display `\"Ticket status updated: <from_status> -> <to_status>\"`.\n\n4. **On failure or not applicable**: Display a warning but do not stop the pipeline:\n - If `resolve_target_status` returned null: `\"Ticket status transition skipped: no target status configured for this project\"`\n - If `update_jira_status` failed: `\"Ticket status transition skipped: <error message>\"`\n\nThis stage is **non-critical** — log a warning on failure but do not stop the pipeline.\n\n## Stage 6 — Smoke Test Validation Comment\n\nThis stage reviews the implementation against the ticket requirements and posts a comment if manual validation is needed.\n\n1. **Fetch ticket description**: Call the `get_ticket` MCP tool with `ticket_number` set to the `ticket_key` to retrieve the current ticket requirements.\n\n2. **Review implementation**: Compare the implementation (from the plan loaded in Stage 2 and the files committed in Stage 3) against the ticket requirements. Identify any behavior or requirements that could NOT be validated through the automated tests written during implementation or through code review alone. Consider the limitations of any tests that were written: what functionality or behavior could not be validated by those tests? Examples include: requirements involving visual UI rendering or layout checks, third-party system integrations where mock tests are insufficient, or non-deterministic behaviors.\n\n3. **If untestable requirements exist**: Compose a structured comment describing specific manual validation steps stakeholders should perform. Then call the `add_comment` MCP tool with `ticket_number` set to the `ticket_key` and the comment text. Display: `\"Smoke test validation comment posted to <ticket_key>\"`.\n\n4. **If no untestable requirements exist**: Skip silently. Display: `\"No untestable requirements identified — skipping smoke test comment\"`.\n\nThis stage is **non-critical** — log a warning on failure but do not stop the pipeline.\n\n## Final Report\n\nOn success, display the structured report from Stage 4 confirming that the commit and push are complete, including the branch name, commit hash, file list, and any warnings from earlier stages.\n\nOn failure at any critical stage (Stage 0, Stage 1, or Stage 3), display which stage failed and the error details.\n",
9
- "conduct-epic.md": "---\nschedulable: true\narguments: {\"positionals\":[{\"name\":\"epicKey\",\"type\":\"string\",\"required\":true}],\"flags\":[{\"name\":\"tickets\",\"flag\":\"--tickets\",\"type\":\"string\"},{\"name\":\"baseBranch\",\"flag\":\"--base-branch\",\"type\":\"string\"},{\"name\":\"checkpointPath\",\"flag\":\"--checkpoint-path\",\"type\":\"string\"}]}\n---\n\n# Conduct Epic: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command drives one multi-ticket epic from an approved ticket list to a finished `epic/<EPIC>` branch, one ticket at a time. It is the LLM half of the LLM-as-conductor pivot: there is no server-side reconciler here, no background worker, and no durable run row — the checkpoint file written by the packaged `conduct-epic` CLI plus the live state of GitHub *is* the entire memory of the loop.\n\nCadence is not an argument of this command. `/loop` owns the interval, this command owns exactly one reconcile-then-act step, and the two compose without either one holding state for the other.\n\nIt composes work that already exists rather than reimplementing it: `/review-and-start --auto --base-branch 'epic/<EPIC>' <KEY>` spawns each ticket's worker, the `merge_pull_request` MCP tool merges a green and approved pull request, `parse_repository` / `get_parse_status` re-index the repository after each merge so the next ticket's plan sees its predecessor's code, and the packaged `conduct-epic` CLI (`init`, `status`, `checkpoint set`, `finish`, `spawn`) owns every durable file operation.\n\n---\n\n# Instructions\n\nYou are executing a 5-stage tick. Run the stages in order, take **exactly one** action from the Stage 3 detection table, write **exactly one** checkpoint in Stage 4, then stop. Do not loop internally, do not take a second action because the first one looked cheap, and do not carry assumptions from a previous tick — every tick reconciles from scratch.\n\nThe \"exactly one checkpoint\" rule has **three explicitly documented exemptions** and no others: the two print-only parks, `init_failed` (Stage 1) and `foreign_lock` (Stage 2), which stop before Stage 3; and the `all_done` tick (Row 1), which has no in-flight ticket to name in a `checkpoint set` command. Stage 4 states each one.\n\n## Stage 0 — Arguments and Ping\n\n1. **Parse `$ARGUMENTS`** into exactly one epic positional and the three optional flags. Accept no other input shape.\n\n - **`<EPIC>`**: exactly one positional token, which must match `[A-Z]+-[0-9]+` (e.g. `BAPI-798`). Zero epic positionals, more than one positional, or a positional that does not match the pattern is malformed input. Extra positionals are rejected rather than ignored.\n - **`--tickets <K1,K2,…>`** (and the equals form `--tickets=<K1,K2,…>`): a non-empty, comma-separated, **ordered** list of ticket keys. Preserve the caller's order exactly — it is the execution order of the epic. Every entry must match `[A-Z]+-[0-9]+` after trimming surrounding whitespace; reject a malformed key, an empty entry, and a duplicate key. This flag is required **only on the first tick** (see Stage 1); later ticks read the order from the checkpoint.\n - **`--base-branch <branch>`** (and the equals form `--base-branch=<branch>`): validated with the same rules as `/start-tickets` Stage 0 — after trimming surrounding whitespace it must be non-empty, at most 255 characters, must not start with `-`, and must not contain ASCII control characters (`0x00`–`0x1F` or `0x7F`). It is the branch `epic/<EPIC>` is cut from at `init` time; it is not the pull-request base of a ticket, which is always `epic/<EPIC>`.\n - **`--checkpoint-path <path>`** (and the equals form `--checkpoint-path=<path>`): must be a non-empty string after trimming, checked **before** it is used as a path or interpolated into a CLI invocation. When omitted, the CLI's own default (`~/.config/bridge/conduct/<repo>/<EPIC>.json`) applies and `status` prints the resolved path.\n\n Reject malformed input before any side effect: an unsupported flag, a flag given without its value, a `--tickets` list that fails the rules above, a `--base-branch` value that fails validation, an empty `--checkpoint-path`, a missing epic, or an extra positional. On any of these, stop immediately and display:\n\n ```\n Invalid arguments.\n Usage: /conduct-epic [flags] <EPIC>\n <EPIC> required, matches [A-Z]+-[0-9]+ (e.g. BAPI-798)\n --tickets K1,K2,… ordered ticket keys; required only on the first tick\n --base-branch <branch> branch epic/<EPIC> is cut from (default: the repo base)\n --checkpoint-path <path> override the checkpoint file location\n ```\n\n2. **Connectivity check**: call the `ping` MCP tool with **no parameters**. If the call fails, or does not return `\"status\": \"ok\"`, stop immediately — before Stage 1 initialization, before any CLI invocation, and before any state is written — and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n3. **Execution model.** This command is one tick; run it with `/loop 5m /conduct-epic <EPIC>`; each tick re-reads the checkpoint and GitHub, takes at most one action, and exits. `/loop` is the external driver that re-invokes this command — it is not an internal loop this command runs, and cadence is never an argument of this command.\n\n4. **Bash interpolation rule (global; applies to every Bash invocation in every stage).** Before interpolating any dynamic value — the epic key, a ticket key, a branch name, a checkpoint path, a prompt-file path, a JSON blob, a journal line — replace every `'` in the value with `'\\''`, then wrap the complete value in single quotes. Never expand a dynamic value unquoted, and never build a command by concatenating an unquoted variable. Credentials must never appear in a command argument, in printed output, in a journal line, or in a prompt file: the CLI and the MCP tools resolve their own credentials from the environment and the user-scoped credential store.\n\n5. **Packaged CLI launcher (`BAPI_MCP_CLI`); global, applies to every packaged-CLI invocation in every stage.** Resolve the launcher **once**, here in Stage 0, and reuse that one resolved value for the rest of the tick. Call it `<launcher>`.\n\n - Read the `BAPI_MCP_CLI` environment variable.\n - **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n - **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\n When the override is set, apply item 4's single-quote escaping rule to `<launcher>` before interpolating it into a Bash command string, keep every dynamic argument independently quoted rather than concatenated into the launcher value, and never put a credential or a credential-bearing environment assignment into it. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Initialize If No Checkpoint\n\nRun the first status probe through the **Bash tool**, forwarding `--checkpoint-path '<path>'` only when the user supplied it:\n\n```\n<launcher> conduct-epic status '<EPIC>' --json\n```\n\nA zero-exit response whose `checkpoint_exists` is `false` is the **only** condition under which initialization is allowed.\n\n- **`checkpoint_exists` is `false`** — this is the first tick. `--tickets` is required here, and **only** here: if it was not supplied, halt with the Stage 0 usage message and initialize nothing. On every later tick `--tickets` is optional and ignored, because the ordered list already lives in the checkpoint. Otherwise run, forwarding `--base-branch '<b>'` and `--checkpoint-path '<p>'` only when supplied:\n\n ```\n <launcher> conduct-epic init '<EPIC>' --tickets '<K1,K2,…>'\n ```\n\n Print the initialization preflight output **verbatim** — do not summarize it, do not suppress its announcements, and do not reorder it. `init` runs one preflight that lists every failure at once, and that listing is the operator's only diagnostic when it refuses.\n\n On a **non-zero** exit, `init_failed` is a **print-only park**: emit `NEEDS_HUMAN:init_failed` with the complete secret-free output as evidence, print exactly one bounded, secret-free stdout journal line describing this invocation, and stop the tick. Do **not** call `checkpoint set` and do not otherwise mutate durable state. There is nothing to write to: when initialization failed, no writable checkpoint may exist at all, and any checkpoint that does exist may be the unreadable one that caused the failure. Do not attempt a second initialization in the same tick and do not fall through to Stage 2.\n\n- **`checkpoint_exists` is `true`** — an epic that already has a checkpoint must **never** trigger `init`. The CLI deliberately refuses reinitialization (`already initialized`), so a retry is not a recovery path; it is a bug in the caller. Skip straight to Stage 2.\n\n- **The status command exits non-zero** (a corrupt or wrong-version checkpoint, for example) — treat it exactly like a failed init, including the print-only rule: preserve the secret-free stderr as evidence, emit `NEEDS_HUMAN:init_failed`, print one journal line, call no `checkpoint set`, and stop the tick. `status` never rewrites a checkpoint it could not read, so nothing has been damaged.\n\n## Stage 2 — Reconcile From Status JSON\n\nRun the status probe **again**, with the same conditional `--checkpoint-path '<path>'` forwarding:\n\n```\n<launcher> conduct-epic status '<EPIC>' --json\n```\n\nThis second response is the action snapshot. **This JSON object is the only evidence the tick acts on.** Worker claims are never trusted — a session that says \"CI passed\", \"review approved\", or \"PR merged\" has told you nothing this tick may use. Every one of those facts is re-derived here from GitHub and the server through `status`, and only from there.\n\nThe top-level contract is exactly: `ok`, `epic_key`, `epic_branch`, `checkpoint_path`, `checkpoint_exists`, `all_done`, `ticket`, `worktree_path`, `worktree_exists`, `branch_head`, `worker_commits_since_spawn`, `last_seen_head`, `last_state_change_at`, `stale_for_seconds`, `pr`, `merged_externally`, `ci`, `review`, `parse`, `deadlines`, `lock`, `needs_human`, and `probe_errors`.\n\nThe nested objects the detection table reads are:\n\n- `ticket` — the in-flight ticket (the first entry that is not `done`, or `null` when `all_done`): `key`, `status` (`pending`, `in_progress`, `merged`, `done`, `needs_human`), `branch`, `pr_number`, `spawned_at`, `parse_requested_at`, `parse_requested_for_sha`, `respawns`, `conflict_attempts`, `counters.sessions_spawned`, `counters.plan_generations_observed`, `counters.merge_attempts`, and `journal`.\n - `parse_requested_at` and `parse_requested_for_sha` are **each a string or `null`**, and together they are the durable evidence Row 5 reads: the ISO-8601 time `parse_repository` was called for this ticket's merge, and the `pr.head_sha` it was called for. They are dedicated checkpoint fields, so nothing can evict them.\n - `journal` is the ticket's journal lines, **oldest-first, newest last**, exactly as stored. It is a human-readable audit trail and is **never** the source of a decision: it is capped at 50 lines and evicts oldest-first, so a marker searched for in it would silently vanish after roughly fifty wait ticks and the loop would re-request a parse it had already requested.\n- `pr` — `number`, `state` (`OPEN`, `MERGED`, `CLOSED`), `head_sha`, `base`, `mergeable`, `merge_state`, `updated_at`.\n- `ci` — `required`, `complete`, `stable_across_two_polls`, `head_sha`, and `checks` entries of `name`, `status`, `conclusion`, `required`.\n- `review` — `opted_in`, `source`, `available`, `verdict` (`approved`, `changes_requested`, `unknown`), `head_sha`.\n- `parse` — `status` (`idle`, `queued`, `in_progress`, `succeeded`, `failed`), `terminal`, `started_at`, `finished_at`, and `index_branch_override`. The last three are each **a string or `null`**. `started_at` and `finished_at` are the ISO-8601 times of the current or last parse run; `index_branch_override` names the branch the repository-wide index override currently points indexing at. A `null` on any of them is unavailable evidence and **never** permits advancement — in particular, missing timestamps can never satisfy Row 5's causal check.\n- `deadlines` — `soft_seconds`, `hard_seconds`, `elapsed_since_spawn_seconds` (defaults 3600 and 10800).\n- `lock` — `held_by_me`, `owner_pid`, `host`, `alive`.\n- `needs_human` — `null`, or `reason`, `evidence`, `at`.\n- `probe_errors` — entries of `probe` and `reason`.\n\nA failed probe leaves its sub-object `null` and is listed in `probe_errors`; it never fails the command. **A `null` sub-object is unavailable evidence, not a negative result.** Never infer a merge, an approval, a CI success, or a parse success from a `null` value, from a missing field, or from narrative output of any kind — an unavailable probe means \"wait for the next tick\", never \"proceed\".\n\n**`pr` is the one sub-object whose `null` has two distinct meanings, and `probe_errors` is what tells them apart:**\n\n- **`pr` is `null` and there is no `{probe: \"pr\"}` entry** — confirmed absence. `gh` was asked and answered that this branch has no pull request. This is the **normal** state of every tick between the first spawn and the moment the worker opens its pull request, it is a negative result the rows may act on, and Rows 6 and 7 exist precisely for it.\n- **`pr` is `null` and there IS a `{probe: \"pr\"}` entry** — unavailable evidence. `gh` could not answer: unauthenticated, rate-limited, offline, or output that did not parse. Treat it as \"wait for the next tick\" and never as absence; a pull request that exists but cannot be seen must not be reasoned about as one that does not exist.\n\nDo not collapse these two into \"no PR\". Reading an outage as absence is how the loop would respawn into, or abandon, a pull request that was there all along.\n\nTwo states stop the tick before any action is selected:\n\n- **Already parked.** If `needs_human` is not `null`, print the stable phrase `already parked`, followed by the persisted `reason`, the persisted string `evidence`, and the persisted `at` timestamp — then stop. Take no action this tick and write no checkpoint. A parked epic is a human's to unpark by editing the checkpoint (`needs_human` back to `null`, the ticket `status` back to `pending`/`in_progress`, counters adjusted if a budget is re-granted). Do not select a new recovery action on top of an existing one.\n- **Foreign lock.** If `lock.held_by_me` is `false` and `lock.alive` is `true`, another live process owns this epic. `foreign_lock` is a **print-only park**: emit `NEEDS_HUMAN:foreign_lock` carrying `lock.owner_pid` and `lock.host` as evidence, print one bounded, secret-free stdout journal line for this invocation, and stop. Do **not** call `checkpoint set`, spawn a session, merge a pull request, or start a parse while that lock is alive. The checkpoint belongs to the other live process; writing to it — even to record a park — is the two-authorities corruption the lock exists to prevent, and `checkpoint set` refuses a live foreign lock anyway.\n\n## Stage 3 — Detect and Take Exactly One Action\n\nEvaluate the rows below **strictly in written order, from top to bottom**. Evaluation stops at the first row whose condition matches; that row's action is the only action this tick performs, and control then proceeds directly to Stage 4. A later row is never \"also\" run because it happens to apply.\n\nOne row states a **forward-looking guard** in its own condition: Row 3 (`stalled`) matches only when no later action or fail-closed row would be selectable for this snapshot. That guard is part of Row 3's condition, not a departure from written order — the ordering rule still holds, and Row 3 simply does not match while a real action is available.\n\nEach row is marked **fail-open** (an uncertain or transient condition waits for the next tick) or **fail-closed** (the tick refuses to act and parks rather than guessing).\n\n### Row 1 — `all_done`: finish the epic and open its pull request\n\nWhen `all_done` is `true`, run `<launcher> conduct-epic finish '<EPIC>'` (forwarding `--checkpoint-path '<p>'` when supplied), then call the `create_pull_request` MCP tool with `head_branch` set to `epic/<EPIC>` and `base_branch` set to `main`. Assemble the `body` from the finish summary: the merged ticket pull requests and any skipped tickets. **Open the pull request; never merge it** — a human reviews and merges the epic into `main`. Then stop.\n\n**This tick writes no checkpoint and does not increment `counters.iterations`.** It is the third documented exemption from Stage 4's one-checkpoint-per-tick rule, and unlike the two print-only parks it reaches Stage 3. The reason is mechanical: `all_done` is `true` exactly when `ticket` is `null`, `checkpoint set` requires `--ticket <KEY>`, and there is no in-flight ticket to name. `finish` is this tick's durable act, and it is the last one the epic needs — so do not invent a ticket key to satisfy the rule, and do not write a checkpoint before or after `finish`.\n\n### Row 2 — Wrong base: do not touch a pull request that is not on the epic branch\n\nWhen `pr.base` is present and is not `epic/<EPIC>`, **do not touch the pull request** — no merge, no comment, no respawn. Select `NEEDS_HUMAN:wrong_base`, carrying the observed `pr.base`, `pr.number`, and the expected `epic/<EPIC>`. **Fail-closed**: only pull requests based on `epic/<EPIC>` are ever acted upon, and this row is evaluated before every work and recovery row precisely so a mis-based pull request cannot be merged, respawned into, or advanced by a later row.\n\n### Row 3 — Hard liveness: a stalled epic parks before it waits\n\nWhen `stale_for_seconds >= deadlines.hard_seconds` (default three hours, `10800`) **and no other row below is selectable this tick**, select `NEEDS_HUMAN:stalled`, carrying the observed `stale_for_seconds` and the `deadlines.hard_seconds` it exceeded. **Fail-closed**.\n\n**This row outranks wait rows only.** Before selecting it, check whether any of the following would otherwise be selectable for this snapshot; if any one of them would, take that row instead and do not park:\n\n- pending work (Row 4's first spawn),\n- Row 5's **action** branches only — branch 1's parse request, branch 3's completion, and branch 5's causal `parse_failed` park,\n- a targeted respawn (Rows 7, 9, and 11),\n- CI-red handling (Row 9) and review-remediation handling (Row 11),\n- conflict handling (Row 12),\n- ready-to-merge handling (Row 13),\n- a closed, unmerged pull request (Row 13a).\n\n`stale_for_seconds` counts from the last observed head or status change, not from the last useful event — so an old but green and approved pull request accumulates staleness while being perfectly actionable. Parking that is the exact defect this guard removes. The row remains ahead of every wait row, because without it a wait would match forever and the epic would sit silent instead of asking for a human.\n\n**Row 5's wait branches are deliberately NOT in that list.** Branches 2, 4, and 6 — a parse that is queued or in progress, a non-causal `succeeded` or `failed`, an inconsistent request record — are waits, and exempting them would mean a merged ticket whose parse never starts waits forever with no human ever asked. They accumulate staleness like any other wait and park as `stalled` once `deadlines.hard_seconds` is exceeded.\n\n### Row 4 — Pending ticket: spawn the first worker\n\nWhen `ticket.status` is `pending`, spawn the ticket's session:\n\n```\n/review-and-start --auto --base-branch 'epic/<EPIC>' <KEY>\n```\n\nThen prepare the Stage 4 checkpoint values `spawned_at` (now, ISO-8601), `status=in_progress`, and `counters.sessions_spawned` = the Stage 2 value plus one.\n\n**Fail-closed**: refuse this spawn if the lock is foreign (Stage 2 has already parked in that case). The pull-request base of the spawned worker comes from BAPI-801's `BAPI_BASE_BRANCH` export — `/review-and-start --base-branch` forwards it into the spawned worker shell, and the worker's create-PR step resolves the base from it. That export is what makes the first pull request land on `epic/<EPIC>`; this loop never relies on it alone, because Row 2 independently re-checks the observed `pr.base` on every later tick.\n\n### Row 5 — Merged ticket: parse, then mark done\n\nWhen `pr.state` is `MERGED`, or `merged_externally` is `true`, or `ticket.status` is `merged`, the ticket's code is on the epic branch. An **external merge is successful reconciliation, not an error** — a human who merged the pull request by hand did the loop's work for it, and `merged_externally` records exactly that.\n\n`parse.status` is **repository-level**, not ticket-level: it stays `succeeded` from any earlier parse of any earlier ticket. So \"succeeded\" on its own says nothing about whether *this* merge has been indexed, and treating it as proof would mark a ticket `done` against a parse that finished before its code existed. This row is therefore an **ordered state machine keyed on durable evidence**, evaluated top to bottom, and the first matching branch is the tick's action:\n\n**The request evidence is `ticket.parse_requested_at` and `ticket.parse_requested_for_sha` from this tick's Stage 2 snapshot — never a journal search.** Both are dedicated checkpoint fields. The journal is capped at 50 lines and evicts oldest-first, so a marker searched for there disappears after roughly fifty wait ticks — about four hours at a five-minute cadence — and branch 1 would fire a second time, calling `parse_repository` again against a fresh causal clock. A field cannot be evicted, which is the whole reason these two exist.\n\nThroughout this row, \"the request timestamp\" means `ticket.parse_requested_at`.\n\n1. **No parse has been requested for this merge yet** — `ticket.parse_requested_at` is `null` **and** `ticket.parse_requested_for_sha` is `null`.\n\n First require a usable SHA: `pr.head_sha` must be a non-null value on this tick's snapshot. If it is `null` — an unavailable `pr` probe, or a pull request record without a head — **wait**, journal that the parse request is deferred for want of a head SHA, and take no other action. Requesting a parse you cannot attribute to a commit records evidence that can never be checked.\n\n Otherwise the **only** action is to call the `parse_repository` MCP tool. Capture the ISO-8601 timestamp **immediately before** invoking it, so the recorded time can never be later than a parse the call itself started. Then write one checkpoint setting **both** `parse_requested_at` to that captured timestamp and `parse_requested_for_sha` to the current `pr.head_sha`, in the same single `checkpoint set`, together with the journal line\n\n ```\n parse requested for merge <pr.head_sha>\n ```\n\n carrying that same timestamp. The journal line is **supplementary** — a human-readable record of what the fields already say durably — and its eventual eviction has no effect on any branch of this row.\n\n Record the fields if the request was accepted **or** if `parse_repository` authoritatively reports that a parse is already in progress: the request is what the fields record, and an in-progress parse is a satisfied request, not a failed one. Take no other action and change no other row-specific field this tick. **Fail-open.**\n\n2. **A request is recorded and `parse.status` is `queued` or `in_progress`.** Wait. Journal the observed state. Do not spawn anything and do not advance the next ticket.\n\n3. **A request is recorded, `parse.status` is `succeeded`, `parse.terminal` is `true`, and at least one of `parse.started_at` or `parse.finished_at` is a valid timestamp strictly later than `ticket.parse_requested_at`.** Only then call `update_jira_status` for the ticket with `target_status` set to the Jira `Done` state, and prepare `status=done`. The strictly-later comparison is what makes this causal: it proves the succeeded run began or ended *after* the request, rather than being an older repository parse.\n\n4. **A request is recorded and `parse.status` is `succeeded`, but the timestamps are missing, malformed, equal to, or older than `ticket.parse_requested_at`.** Wait, and journal the causal mismatch naming the request timestamp and the observed `started_at` / `finished_at`. Do **not** mark the ticket done. This is the stale-success case, and it is a wait rather than a park because the correct parse may still be about to start.\n\n5. **A request is recorded, `parse.status` is `failed`, and at least one of `parse.started_at` or `parse.finished_at` is a valid timestamp strictly later than `ticket.parse_requested_at`.** Select `NEEDS_HUMAN:parse_failed`, with the observed parse state as bounded string evidence. **Fail-closed.**\n\n The strictly-later requirement is the **same causal test branch 3 applies, and for the same reason**: `parse.status` is repository-level, so a `failed` that predates this request describes some earlier parse of some earlier ticket. Parking on it would strand a ticket on a failure that has nothing to do with it. A `failed` whose timestamps are missing, malformed, equal to, or older than the request is therefore **not** this branch — it falls to branch 6 and waits.\n\n6. **A request is recorded and none of branches 2–5 matched** — including a stale or non-causal `succeeded`, a stale or non-causal `failed`, and an `idle` repository parse state. Wait, and journal the observed state together with the request timestamp. Neither advance nor park: hard liveness (Row 3) is what eventually escalates a wait that never resolves.\n\n**Inconsistent request evidence never re-requests a parse.** If exactly one of `parse_requested_at` / `parse_requested_for_sha` is populated, or `parse_requested_for_sha` disagrees with the current `pr.head_sha`, do **not** treat that as \"no request yet\" and do **not** call `parse_repository` again. Journal the causal inconsistency, naming both stored values and the observed `pr.head_sha`, and wait. A second parse would mint a new causal clock and invalidate the evidence the earlier request already produced; a mismatch that persists is escalated by Row 3's hard-liveness park, which is the correct place for a state a human must look at.\n\n**No next ticket is spawned until this one reaches `done`.** A merged ticket stays in flight until its parse is terminal, so `ticket` still points at it and Row 4 cannot match for its successor — which is the whole point: the next ticket's review and plan must see this ticket's merged code.\n\n### Row 6 — Worktree working: wait\n\nWhen a worktree exists (`worktree_exists` is `true`), the pull request is **confirmed absent** (`pr` is `null` **and** `probe_errors` carries no `{probe: \"pr\"}` entry), and `worker_commits_since_spawn > 0`, the worker is making observable progress. Wait, and journal the observed `branch_head` and commit count. **Fail-open.**\n\nA `pr: null` accompanied by a PR probe error is unavailable evidence, not absence, and does not match this row — it falls through to Row 15 and waits.\n\n### Row 7 — Soft deadline with no progress: one targeted continuation\n\nWhen the pull request is **confirmed absent** (`pr` is `null` **and** no `{probe: \"pr\"}` entry), `worker_commits_since_spawn` is `0`, and `deadlines.elapsed_since_spawn_seconds >= deadlines.soft_seconds` (default one hour, `3600`), spend the single targeted respawn on kind `continue`, with the prompt:\n\n```\nBranch <b> for <KEY>: continue the existing plan; do not regenerate it; push when done\n```\n\nPrepare `respawns` = the Stage 2 value plus one. `respawns` is **one shared per-ticket budget**, not one allowance per row: Rows 7, 9, and 11 all spend the same single counter, so spending it here leaves nothing for a later CI fix or review fix on this ticket. The attempt **counts only if it pushed** — a later tick observing a non-null `branch_head` is the proof. A respawn that produces no push is a no-op, and a no-op respawn stops the loop rather than spinning: once the one targeted respawn is spent and the ticket still shows no pushed head, select `NEEDS_HUMAN:stalled`. **Fail-closed after one attempt**, which is what keeps a dead worker from being respawned without bound.\n\n### Row 8 — Pull request open, CI not settled: wait\n\nWhen a pull request is open and `ci.complete` is `false` **and no required check in `ci.checks` has already reached a terminal unsuccessful conclusion**, wait; or when `ci.complete` is `true` and green but `ci.stable_across_two_polls` is `false`, wait. **Fail-open.**\n\nThe boolean alone is not the condition. `ci.complete` is `false` both while checks are still running and once a required check has definitively failed, and those are opposite situations: the first is worth waiting on and the second never becomes green on its own. This row therefore covers pending and not-yet-stable checks **only** — a required check with a terminal unsuccessful conclusion is **not** consumed here and falls through to Row 9.\n\n### Row 9 — Pull request open, CI red: one targeted fix\n\nWhen a pull request is open, one or more required checks in `ci.checks` have a terminal unsuccessful conclusion, and there has been no new commit for over 60 minutes (`stale_for_seconds > 3600` is the authoritative no-new-commit duration), spend the single targeted respawn on kind `ci_fix`. Take the failing check names from `ci.checks` — the entries whose `required` is `true` — and use the prompt:\n\n```\nPR #N is red on <checks>: read the check annotations, fix, push; do not regenerate the plan\n```\n\nPrepare `respawns` = the Stage 2 value plus one; the attempt counts only if it pushed. A bare `/implement-ticket --auto` is **prohibited** here: it regenerates the plan, costs a full plan generation, and discards the failure detail the annotations already carry.\n\n`respawns` is **one shared per-ticket budget** across Rows 7, 9, and 11. A continuation respawn spent earlier on this ticket therefore leaves **no** CI-fix attempt: with the counter already at its limit, persistent red CI parks immediately as `NEEDS_HUMAN:ci_red` rather than getting a fix session of its own. Once the shared respawn is spent and CI is still red, select `NEEDS_HUMAN:ci_red` with the failing check names as bounded string evidence. **Fail-closed after one attempt.**\n\n### Row 10 — Review opted in but unavailable: wait one tick\n\nWhen `pr.state` is `OPEN`, `review.opted_in` is `true`, and `review.available` is `false`, the review source could not be read. Wait one tick and journal the condition. **Fail-open** for that tick — but the clock keeps running, so continued unavailability is caught by Row 3's hard-liveness park rather than waiting forever.\n\nThe `pr.state` is `OPEN` guard is load-bearing: without it a `CLOSED` pull request whose review source happens to be unreadable matches here, ahead of Row 13a, and the loop waits tick after tick on abandoned work instead of parking it.\n\n### Row 11 — Changes requested for the current head: one targeted review fix\n\nWhen `pr.state` is `OPEN`, `review.verdict` is `changes_requested`, **and** `review.head_sha` equals `pr.head_sha`, spend the single targeted respawn on kind `review_fix`. The `OPEN` guard is what stops a `changes_requested` verdict left on a **closed** pull request's head from spending this ticket's one respawn on work nobody will merge — that snapshot belongs to Row 13a. The prompt carries the authoritative Stage 2 review evidence: the ticket key, the pull-request number, the reviewed head SHA, and the requested changes. A stale `review.head_sha` (one that does not equal `pr.head_sha`) is a verdict about code that no longer exists and never triggers this row. Prepare `respawns` = the Stage 2 value plus one.\n\n`respawns` is **one shared per-ticket budget** across Rows 7, 9, and 11. Any earlier continuation or CI-fix respawn on this ticket therefore leaves **no** review-fix attempt: with the counter already at its limit, requested changes on the current head park immediately. Once the shared respawn is spent and the verdict still stands for the current head, select `NEEDS_HUMAN:review_changes_requested`. **Fail-closed after one attempt.**\n\n### Row 12 — Conflicting pull request: at most two conflict sessions\n\nWhen `pr.state` is `OPEN` **and** either `pr.mergeable` is `CONFLICTING` or `pr.merge_state` is `DIRTY`, spawn a session of kind `conflict` with the prompt:\n\n```\nrebase onto origin/epic/<EPIC>, resolve, run tests, push\n```\n\nPrepare `conflict_attempts` = the Stage 2 value plus one. The conflict budget is **two** sessions and is counted separately from the single targeted respawn of Rows 7, 9, and 11 — a rebase is a different failure mode from a stalled or red worker. After the second conflict session, if the pull request is still `CONFLICTING`/`DIRTY`, select `NEEDS_HUMAN:conflict`. **Fail-closed after two attempts.**\n\nA **closed** pull request is frequently left `CONFLICTING`/`DIRTY` by GitHub, so without the `pr.state` is `OPEN` guard this row would match ahead of Row 13a and spend a rebase session resolving conflicts on a branch nobody will merge.\n\n### Row 13 — Ready to merge\n\nMerge only when **all** of the following hold on the fresh Stage 2 snapshot: the pull request is open (`pr.state` is `OPEN`); `pr.base` is `epic/<EPIC>`; `ci.complete` is `true` and `ci.stable_across_two_polls` is `true` for `ci.head_sha` equal to `pr.head_sha`; the pull request is not conflicting; and review is either opted out (`review.opted_in` is `false`) or approved (`review.verdict` is `approved`) with `review.head_sha` equal to `pr.head_sha`.\n\nThen call the `merge_pull_request` MCP tool with exactly `pr_number` set to `pr.number` and `expected_head_sha` set to `pr.head_sha`. **The expected SHA is derived only from the fresh Stage 2 status** — never from the checkpoint, never from a worker's report, never from an earlier tick. The checkpoint deliberately stores no expected head; merge identity always comes from a freshly observed `pr.head_sha`. Prepare `counters.merge_attempts` = the Stage 2 value plus one for **every** invocation of the tool, successful or not.\n\nMap the returned envelope:\n\n- **`merged` is `true`** — the only success. It covers `outcome: merged` and `outcome: already_merged`, both of which carry that boolean. Prepare `status=merged` and top-level `counters.merges` = the Stage 2 value plus one.\n- **`outcome: refused` with `reason: head_sha_drift`** — the head moved under the merge. Journal the complete envelope (including `actual_head_sha`) and take a fresh status snapshot on the next tick. Never retry with the stale SHA.\n- **Outcome `lease_held`, `review_not_approved`, or `unknown`, or any envelope carrying `retry_hint: retry_later`** — journal it and wait for the next reconciliation tick.\n- **Outcome `dry_run`, `pending_approval`, `gate_unresolved`, `action_key_mismatch`, `review_unavailable`, `review_source_unsupported`, `error`, or any `refused` result carrying `retry_hint: needs_human`** — select `NEEDS_HUMAN:merge_blocked`. Preserve the **complete** envelope as the evidence, including `hint`, `actual_head_sha`, `ci_summary`, `paths`, and `http_status` whenever those are present; `hint` is usually the exact operator fix. **JSON-stringify that envelope into a bounded, secret-free string** — `evidence` is string data, never an object (see Stage 4).\n\n**Fail-closed**: only `merged: true` is success. A missing, `false`, or malformed `merged` value is never treated as a merge, no matter what `outcome` says alongside it.\n\n### Row 13a — Pull request closed without being merged\n\nWhen `pr.state` is `CLOSED` and the pull request was not merged, the ticket's work has been abandoned on GitHub and nothing this loop does can advance it. Select `NEEDS_HUMAN:merge_blocked`, with bounded string evidence that identifies `pr.state: CLOSED` along with `pr.number`. **Fail-closed** — a closed pull request is never respawned into, reopened, or merged by this loop.\n\n### Row 14 — Local-mode ticket operation refused\n\nWhen a ticket operation returns `409 UNSUPPORTED_IN_LOCAL_MODE`, tolerate it and journal it. The repository is running the local ticket backend, where that response is the documented terminal answer rather than a failure. It introduces **no** new parking reason. **Fail-open.**\n\n### Row 15 — No row matched: journal the snapshot and do nothing else\n\nWhen no row above matches, that is the tick's outcome, not a licence to improvise. Journal a concise summary of the Stage 2 snapshot, take **no** external action — no MCP tool call, no spawn, no merge, no parse — and change **no** row-specific checkpoint field. The single `checkpoint set` this tick writes therefore carries only the universal `counters.iterations` update and its one journal line.\n\nThis row exists because unmatched snapshots are real and reachable: a pull request that is open, complete, stable, and awaiting a review whose `verdict` is still `unknown`; a `stale_for_seconds` or `elapsed_since_spawn_seconds` that is `null` because nothing has been observed yet. Each of those is a legitimate \"wait for reality to move\" state, and a tick that improvised an action for it would be acting on evidence it does not have. **Fail-open.**\n\n### Shared mechanics for every targeted session\n\nRows 7, 9, 11, and 12 spawn a session the same way. The four kinds are exactly `continue`, `ci_fix`, `review_fix`, and `conflict`.\n\n**First, write the prompt file** with the Write tool, at:\n\n```\n~/.config/bridge/conduct/<repo>/<EPIC>/prompts/<KEY>-<kind>-<n>.md\n```\n\nwhere `<EPIC>` and `<KEY>` are the validated keys, `<kind>` is one of the four kinds above, and `<n>` is the applicable absolute attempt number. **`<repo>` is the repository component of the resolved `checkpoint_path` that Stage 2's `status` returned** — read it from there rather than re-deriving it from credentials, from `BAPI_REPO_NAME`, or from anything remembered in conversation. `status` resolves that path itself, including any `--checkpoint-path` override and any `XDG_CONFIG_HOME` redirection, so it is the only value guaranteed to match where the CLI actually keeps this epic's state.\n\n**End every prompt with this exact wording**, so the spawned worker releases its worktree cleanly instead of lingering:\n\n```\nAfter the final pipeline step completes, cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers — but **only when no follow-up remains that you still own**. Do **not** exit while any of the following is true:\n\n- there are unresolved CI failures you are still correcting (the post-PR CI-correction loop in the CI-monitoring step still owns work),\n- review changes were requested and you have not yet addressed them,\n- there is a merge conflict on your PR that you still own,\n- you have unpushed local commits.\n\nExit only after your final branch state is pushed, the done-gate / CI-monitoring workflow required by the recipe has completed, and no CI/review follow-up remains. A clean `SessionEnd` is both the correct terminal lifecycle signal and the point at which the worker should exit.\n```\n\n**Then spawn**, forwarding `--checkpoint-path '<p>'` only when supplied:\n\n```\n<launcher> conduct-epic spawn '<EPIC>' --ticket '<KEY>' --prompt-file '<path>'\n```\n\n`spawn` opens exactly **one** agent tab in the ticket's `worktree_path` running the prompt file's contents. It refuses when the worktree is missing, the prompt file is unreadable, or the lock is held by another live process.\n\n**The budgets are this command's job, not the CLI's.** `spawn` never checks them: it will happily open a fifth tab if asked. One targeted respawn **shared** across Rows 7, 9, and 11 — a single per-ticket `respawns` counter, not one allowance per row — and two conflict sessions in Row 12, are enforced here, by reading the Stage 2 `respawns` and `conflict_attempts` before choosing the row.\n\nAfter a **successful** spawn, prepare `counters.sessions_spawned` = the Stage 2 value plus one. If the spawn command itself fails, do **not** advance `respawns`, `conflict_attempts`, or `counters.sessions_spawned` — a session that never opened has consumed no budget.\n\nKeep credentials, raw environment values, and unrelated command output out of prompt files and out of the spawn command's arguments. The spawned agent resolves its own credentials.\n\n## Stage 4 — Checkpoint and Stop\n\nEvery tick that reaches this stage ends with **exactly one** checkpoint command and **exactly one** journal line. There are **three exemptions**, and they divide into two kinds:\n\n- **Two print-only parks, before Stage 3.** `init_failed` (Stage 1) and `foreign_lock` (Stage 2) stop the tick *before* Stage 3 and write nothing durable at all — they print their `NEEDS_HUMAN:` line and one stdout journal line and stop. Because they never reach Stage 3 they also never increment `counters.iterations`.\n- **The `all_done` tick, inside Stage 3.** Row 1 reaches Stage 3 but has **no in-flight ticket**: `all_done` is `true` exactly when `ticket` is `null`, and `checkpoint set` requires `--ticket <KEY>`. That tick runs `finish`, opens the epic pull request, writes **no** checkpoint, and — as the single stated exception to the rule below — does **not** increment `counters.iterations`.\n\nEvery other tick, including a Row 15 fallthrough, writes here. Run, forwarding `--checkpoint-path '<p>'` whenever the user supplied it:\n\n```\n<launcher> conduct-epic checkpoint set '<EPIC>' --ticket '<KEY>' --field <name> <absolute-value> … --journal '<line>'\n```\n\nRepeat `--field <name> <absolute-value>` once per changed field, and pass `--journal '<line>'` exactly once. Do not issue a second `checkpoint set` in the same tick, and do not split the fields across two invocations — one tick, one auditable write.\n\n**Every value is absolute, computed from the Stage 2 snapshot.** Relative or guessed increments are prohibited: the CLI stores what it is given, so a \"+1\" that was never resolved against a fresh read silently corrupts the count. Compute `n + 1` from the Stage 2 value for `counters.sessions_spawned`, `respawns`, `conflict_attempts`, `counters.merge_attempts`, `counters.iterations`, and `counters.merges`.\n\nInclude only the fields the selected row actually affected — typically some of `status`, `spawned_at`, `parse_requested_at`, `parse_requested_for_sha`, `respawns`, `conflict_attempts`, `counters.sessions_spawned`, `counters.merge_attempts`, `counters.iterations`, and `counters.merges`.\n\n**`parse_requested_at` and `parse_requested_for_sha` are always written together**, in the one `checkpoint set` that also carries Row 5 branch 1's journal line:\n\n```\n--field parse_requested_at '<ISO-8601 time captured immediately before parse_repository>' --field parse_requested_for_sha '<pr.head_sha>'\n```\n\nNever write one without the other. A half-populated pair is the inconsistent-evidence state Row 5 refuses to act on, and splitting them across two invocations would also break the one-tick-one-write rule.\n\n**`counters.iterations` increments exactly once for every tick that reaches Stage 3**, and it is written in that tick's single `checkpoint set` as the Stage 2 absolute value plus one. It is the one field every such tick updates, including a Row 15 fallthrough — which is why a fallthrough tick's checkpoint contains only `counters.iterations` and its journal line, with no status, retry, merge, or parking mutation. The two print-only parks never reach Stage 3 and so never increment it, and the `all_done` tick reaches Stage 3 but writes no checkpoint, so it does not increment it either.\n\n**Parking** adds two fields to the same single command:\n\n```\n--field status needs_human --field needs_human '{\"reason\":\"<reason>\",\"evidence\":\"<bounded secret-free JSON-stringified envelope or output>\",\"at\":\"<ISO-8601 timestamp>\"}'\n```\n\n**`evidence` is a JSON string, never an object.** The CLI's checkpoint schema accepts only `{reason: string, evidence: string, at: string}` and rejects anything else outright, so an object-valued `evidence` makes `checkpoint set` exit non-zero: the `NEEDS_HUMAN:` line prints, the park never persists, and the next tick repeats the failing action. When the evidence is structured — a merge envelope, a command's output — JSON-stringify it and escape every embedded quote and control character so the result is a single valid JSON string value. Keep it bounded and secret-free.\n\nThe `reason` is one of the closed list below and `at` is an ISO-8601 timestamp. Every `NEEDS_HUMAN:<reason>` line printed by a stage carries the **same** evidence that is persisted here — the printed line and the checkpoint never disagree.\n\nThe parking vocabulary is closed, and it has two partitions:\n\n- **Seven persisted reasons**, each written durably by the single `checkpoint set` above: `stalled`, `ci_red`, `review_changes_requested`, `merge_blocked`, `conflict`, `parse_failed`, and `wrong_base`. A persisted park is what makes the *next* tick report `already parked` and stop.\n- **Two print-only reasons**, which are printed and journaled to stdout for the current invocation only and write nothing durable: `init_failed` and `foreign_lock`. Neither may call `checkpoint set`. A print-only park leaves no durable record, so it does not produce an `already parked` tick — the next tick reconciles from scratch and reports the condition again if it persists.\n\nDo not invent a new reason; a genuinely new failure mode is a change to this command and to the BAPI-805 runbook together.\n\nThe journal line is one line containing the ISO-8601 time, the selected action, and concise evidence. Print it **last**, after the checkpoint command has succeeded, so the operator's final line of output is the tick's durable record.\n\nEvery dynamic value in this stage follows the Stage 0 single-quote rule — the epic key, the ticket key, the checkpoint path, the `needs_human` JSON, and the journal line are each escaped (`'` → `'\\''`) and wrapped in single quotes. Credentials never appear in a checkpoint argument or in journal evidence.\n\n## Operational Guarantees\n\n- **Spec freshness is `/review-and-start`'s job, not a separate check.** Each ticket's review phase runs in a worktree cut from the current `epic/<EPIC>` tip, so its review and its plan already see every predecessor's merged code. This command runs no separate spec-freshness check and needs none.\n- **The checkpoint plus GitHub are the resume point.** Nothing relies on conversation memory. A sleeping laptop merely misses ticks; the next invocation reconciles from scratch and continues where reality actually is.\n- **This command never creates an `epic_run`.** It must never be combined with `setup-epic` on the same epic — the v2 conductor stays active there, and two authorities transitioning one epic is exactly the failure this pivot removes.\n- **`/loop 5m /conduct-epic <EPIC>` is the driver.** The operator runbook is BAPI-805's, not this file's.\n- **Recovery is bounded**: one targeted respawn *shared* across Rows 7, 9, and 11, and two conflict sessions, then park. There is no third chance and no escalating retry.\n- **The first spawn relies on BAPI-801's `BAPI_BASE_BRANCH` contract**, while every tick still independently verifies the observed `pr.base` (Row 2). The export makes the right thing happen; the check catches it when it does not.\n",
9
+ "conduct-epic.md": "---\nschedulable: true\narguments: {\"positionals\":[{\"name\":\"epicKey\",\"type\":\"string\",\"required\":true}],\"flags\":[{\"name\":\"tickets\",\"flag\":\"--tickets\",\"type\":\"string\"},{\"name\":\"baseBranch\",\"flag\":\"--base-branch\",\"type\":\"string\"},{\"name\":\"checkpointPath\",\"flag\":\"--checkpoint-path\",\"type\":\"string\"}]}\n---\n\n# Conduct Epic: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command drives one multi-ticket epic from an approved ticket list to a finished `epic/<EPIC>` branch, one ticket at a time. It is the LLM half of the LLM-as-conductor pivot: there is no server-side reconciler here, no background worker, and no durable run row — the checkpoint file written by the packaged `conduct-epic` CLI plus the live state of GitHub *is* the entire memory of the loop.\n\nCadence is not an argument of this command. `/loop` owns the interval, this command owns exactly one reconcile-then-act step, and the two compose without either one holding state for the other.\n\nIt composes work that already exists rather than reimplementing it: `/review-and-start --auto --base-branch 'epic/<EPIC>' <KEY>` spawns each ticket's worker, the `merge_pull_request` MCP tool merges a green and approved pull request, `parse_repository` / `get_parse_status` re-index the repository after each merge so the next ticket's plan sees its predecessor's code, and the packaged `conduct-epic` CLI (`init`, `status`, `checkpoint set`, `finish`, `spawn`) owns every durable file operation.\n\n---\n\n# Instructions\n\nYou are executing a 5-stage tick. Run the stages in order, take **exactly one** action from the Stage 3 detection table, write **exactly one** checkpoint in Stage 4, then stop. Do not loop internally, do not take a second action because the first one looked cheap, and do not carry assumptions from a previous tick — every tick reconciles from scratch.\n\nThe \"exactly one checkpoint\" rule has **three explicitly documented exemptions** and no others: the two print-only parks, `init_failed` (Stage 1) and `foreign_lock` (Stage 2), which stop before Stage 3; and the `all_done` tick (Row 1), which has no in-flight ticket to name in a `checkpoint set` command. Stage 4 states each one.\n\n## Stage 0 — Arguments and Ping\n\n1. **Parse `$ARGUMENTS`** into exactly one epic positional and the three optional flags. Accept no other input shape.\n\n - **`<EPIC>`**: exactly one positional token, which must match `[A-Z]+-[0-9]+` (e.g. `BAPI-798`). Zero epic positionals, more than one positional, or a positional that does not match the pattern is malformed input. Extra positionals are rejected rather than ignored.\n - **`--tickets <K1,K2,…>`** (and the equals form `--tickets=<K1,K2,…>`): a non-empty, comma-separated, **ordered** list of ticket keys. Preserve the caller's order exactly — it is the execution order of the epic. Every entry must match `[A-Z]+-[0-9]+` after trimming surrounding whitespace; reject a malformed key, an empty entry, and a duplicate key. This flag is required **only on the first tick** (see Stage 1); later ticks read the order from the checkpoint.\n - **`--base-branch <branch>`** (and the equals form `--base-branch=<branch>`): validated with the same rules as `/start-tickets` Stage 0 — after trimming surrounding whitespace it must be non-empty, at most 255 characters, must not start with `-`, and must not contain ASCII control characters (`0x00`–`0x1F` or `0x7F`). It is the branch `epic/<EPIC>` is cut from at `init` time; it is not the pull-request base of a ticket, which is always `epic/<EPIC>`.\n - **`--checkpoint-path <path>`** (and the equals form `--checkpoint-path=<path>`): must be a non-empty string after trimming, checked **before** it is used as a path or interpolated into a CLI invocation. When omitted, the CLI's own default (`~/.config/bridge/conduct/<repo>/<EPIC>.json`) applies and `status` prints the resolved path.\n\n Reject malformed input before any side effect: an unsupported flag, a flag given without its value, a `--tickets` list that fails the rules above, a `--base-branch` value that fails validation, an empty `--checkpoint-path`, a missing epic, or an extra positional. On any of these, stop immediately and display:\n\n ```\n Invalid arguments.\n Usage: /conduct-epic [flags] <EPIC>\n <EPIC> required, matches [A-Z]+-[0-9]+ (e.g. BAPI-798)\n --tickets K1,K2,… ordered ticket keys; required only on the first tick\n --base-branch <branch> branch epic/<EPIC> is cut from (default: the repo base)\n --checkpoint-path <path> override the checkpoint file location\n ```\n\n2. **Connectivity check**: call the `ping` MCP tool with **no parameters**. If the call fails, or does not return `\"status\": \"ok\"`, stop immediately — before Stage 1 initialization, before any CLI invocation, and before any state is written — and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n3. **Execution model.** This command is one tick; run it with `/loop 5m /conduct-epic <EPIC>`; each tick re-reads the checkpoint and GitHub, takes at most one action, and exits. `/loop` is the external driver that re-invokes this command — it is not an internal loop this command runs, and cadence is never an argument of this command.\n\n4. **Bash interpolation rule (global; applies to every Bash invocation in every stage).** Before interpolating any dynamic value — the epic key, a ticket key, a branch name, a checkpoint path, a prompt-file path, a JSON blob, a journal line — replace every `'` in the value with `'\\''`, then wrap the complete value in single quotes. Never expand a dynamic value unquoted, and never build a command by concatenating an unquoted variable. Credentials must never appear in a command argument, in printed output, in a journal line, or in a prompt file: the CLI and the MCP tools resolve their own credentials from the environment and the user-scoped credential store.\n\n5. **Packaged CLI launcher (`BAPI_MCP_CLI`); global, applies to every packaged-CLI invocation in every stage.** Resolve the launcher **once**, here in Stage 0, and reuse that one resolved value for the rest of the tick. Call it `<launcher>`.\n\n - Read the `BAPI_MCP_CLI` environment variable.\n - **Unset, empty, or whitespace-only** — `<launcher>` is exactly `npx -y @bridge_gpt/mcp-server`. This is the default, and the resulting shell command is byte-identical to what it was before this override existed.\n - **Otherwise** — `<launcher>` is that value, used verbatim as the command prefix. It names a local launcher, such as `node /absolute/path/to/mcp_server/build/index.js`. Use it for local pilots and pre-publish verification.\n\n When the override is set, apply item 4's single-quote escaping rule to `<launcher>` before interpolating it into a Bash command string, keep every dynamic argument independently quoted rather than concatenated into the launcher value, and never put a credential or a credential-bearing environment assignment into it. A stale local build is exactly as misleading as a stale npm publish: rebuild with `cd mcp_server && npm run build` before relying on the override.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Initialize If No Checkpoint\n\nRun the first status probe through the **Bash tool**, forwarding `--checkpoint-path '<path>'` only when the user supplied it:\n\n```\n<launcher> conduct-epic status '<EPIC>' --json\n```\n\nA zero-exit response whose `checkpoint_exists` is `false` is the **only** condition under which initialization is allowed.\n\n- **`checkpoint_exists` is `false`** — this is the first tick. `--tickets` is required here, and **only** here: if it was not supplied, halt with the Stage 0 usage message and initialize nothing. On every later tick `--tickets` is optional and ignored, because the ordered list already lives in the checkpoint. Otherwise run, forwarding `--base-branch '<b>'` and `--checkpoint-path '<p>'` only when supplied:\n\n ```\n <launcher> conduct-epic init '<EPIC>' --tickets '<K1,K2,…>'\n ```\n\n Print the initialization preflight output **verbatim** — do not summarize it, do not suppress its announcements, and do not reorder it. `init` runs one preflight that lists every failure at once, and that listing is the operator's only diagnostic when it refuses.\n\n On a **non-zero** exit, `init_failed` is a **print-only park**: emit `NEEDS_HUMAN:init_failed` with the complete secret-free output as evidence, print exactly one bounded, secret-free stdout journal line describing this invocation, and stop the tick. Do **not** call `checkpoint set` and do not otherwise mutate durable state. There is nothing to write to: when initialization failed, no writable checkpoint may exist at all, and any checkpoint that does exist may be the unreadable one that caused the failure. Do not attempt a second initialization in the same tick and do not fall through to Stage 2.\n\n- **`checkpoint_exists` is `true`** — an epic that already has a checkpoint must **never** trigger `init`. The CLI deliberately refuses reinitialization (`already initialized`), so a retry is not a recovery path; it is a bug in the caller. Skip straight to Stage 2.\n\n- **The status command exits non-zero** (a corrupt or wrong-version checkpoint, for example) — treat it exactly like a failed init, including the print-only rule: preserve the secret-free stderr as evidence, emit `NEEDS_HUMAN:init_failed`, print one journal line, call no `checkpoint set`, and stop the tick. `status` never rewrites a checkpoint it could not read, so nothing has been damaged.\n\n## Stage 2 — Reconcile From Status JSON\n\nRun the status probe **again**, with the same conditional `--checkpoint-path '<path>'` forwarding:\n\n```\n<launcher> conduct-epic status '<EPIC>' --json\n```\n\nThis second response is the action snapshot. **This JSON object is the only evidence the tick acts on.** Worker claims are never trusted — a session that says \"CI passed\", \"review approved\", or \"PR merged\" has told you nothing this tick may use. Every one of those facts is re-derived here from GitHub and the server through `status`, and only from there.\n\nThe top-level contract is exactly: `ok`, `epic_key`, `epic_branch`, `checkpoint_path`, `checkpoint_exists`, `all_done`, `ticket`, `worktree_path`, `worktree_exists`, `branch_head`, `worker_commits_since_spawn`, `last_seen_head`, `last_state_change_at`, `stale_for_seconds`, `pr`, `merged_externally`, `ci`, `review`, `parse`, `deadlines`, `scope`, `lock`, `needs_human`, and `probe_errors`.\n\nThe nested objects the detection table reads are:\n\n- `ticket` — the in-flight ticket (the first entry that is not `done`, or `null` when `all_done`): `key`, `status` (`pending`, `in_progress`, `merged`, `done`, `needs_human`), `branch`, `pr_number`, `spawned_at`, `parse_requested_at`, `parse_requested_for_sha`, `respawns`, `conflict_attempts`, `counters.sessions_spawned`, `counters.plan_generations_observed`, `counters.merge_attempts`, and `journal`.\n - `parse_requested_at` and `parse_requested_for_sha` are **each a string or `null`**. They are RETAINED for the audit trail of epics driven before the scope-status contract existed, and **no row reads them any more**: Row 5 asks the `scope` sub-object directly instead of reconstructing causality from a request timestamp. Do not write them and do not decide on them.\n - `journal` is the ticket's journal lines, **oldest-first, newest last**, exactly as stored. It is a human-readable audit trail and is **never** the source of a decision: it is capped at 50 lines and evicts oldest-first, so a marker searched for in it would silently vanish after roughly fifty wait ticks and the loop would re-request a parse it had already requested.\n- `pr` — `number`, `state` (`OPEN`, `MERGED`, `CLOSED`), `head_sha`, `base`, `mergeable`, `merge_state`, `updated_at`.\n- `ci` — `required`, `complete`, `stable_across_two_polls`, `head_sha`, and `checks` entries of `name`, `status`, `conclusion`, `required`.\n- `review` — `opted_in`, `source`, `available`, `verdict` (`approved`, `changes_requested`, `unknown`), `head_sha`.\n- `parse` — `status` (`idle`, `queued`, `in_progress`, `succeeded`, `failed`), `terminal`, `started_at`, and `finished_at`. The last two are each **a string or `null`** and are the ISO-8601 times of the current or last parse run. A `null` on either is unavailable evidence and **never** permits advancement — in particular, missing timestamps can never satisfy Row 5's causal check. There is no repository-wide index-branch override field: BAPI-847 retired that control plane, and an epic now gets its own index scope instead of taking the repository's index away.\n- `deadlines` — `soft_seconds`, `hard_seconds`, `elapsed_since_spawn_seconds` (defaults 3600 and 10800).\n- `scope` — the epic's index scope, read directly from the server: `scope_id`, `lifecycle_state`, `freshness_status`, `blocked_reason`, `required_commit_sha`, `indexed_commit_sha`, and `last_error`. It is `null` **only** when this epic declares no scope at all; that is not a probe failure and carries no `probe_errors` entry.\n - `freshness_status` is one of `fresh`, `pending`, `blocked`, `failed`, `unavailable`. **`fresh` is the only value that means the index covers this epic's merged code.** `pending` is a refresh still running. `blocked` is an epic advance the server REFUSED to index and will never resolve by waiting — `blocked_reason` names which refusal. `failed` is the scope's own generation failing. `unavailable` means the scope could not be read this tick, and is reported alongside a `{probe: \"scope\"}` entry in `probe_errors`.\n - `required_commit_sha` is the commit the scope must cover; `indexed_commit_sha` is the commit it actually has. **They are separate fields because they mean different things** — the required SHA moves the moment a merge is accepted, long before anything is indexed, so a required SHA equal to your merge commit is not evidence that your merge was indexed.\n- `lock` — `held_by_me`, `owner_pid`, `host`, `alive`.\n- `needs_human` — `null`, or `reason`, `evidence`, `at`.\n- `probe_errors` — entries of `probe` and `reason`.\n\nA failed probe leaves its sub-object `null` and is listed in `probe_errors`; it never fails the command. **A `null` sub-object is unavailable evidence, not a negative result.** Never infer a merge, an approval, a CI success, or a parse success from a `null` value, from a missing field, or from narrative output of any kind — an unavailable probe means \"wait for the next tick\", never \"proceed\".\n\n**`pr` is the one sub-object whose `null` has two distinct meanings, and `probe_errors` is what tells them apart:**\n\n- **`pr` is `null` and there is no `{probe: \"pr\"}` entry** — confirmed absence. `gh` was asked and answered that this branch has no pull request. This is the **normal** state of every tick between the first spawn and the moment the worker opens its pull request, it is a negative result the rows may act on, and Rows 6 and 7 exist precisely for it.\n- **`pr` is `null` and there IS a `{probe: \"pr\"}` entry** — unavailable evidence. `gh` could not answer: unauthenticated, rate-limited, offline, or output that did not parse. Treat it as \"wait for the next tick\" and never as absence; a pull request that exists but cannot be seen must not be reasoned about as one that does not exist.\n\nDo not collapse these two into \"no PR\". Reading an outage as absence is how the loop would respawn into, or abandon, a pull request that was there all along.\n\nTwo states stop the tick before any action is selected:\n\n- **Already parked.** If `needs_human` is not `null`, print the stable phrase `already parked`, followed by the persisted `reason`, the persisted string `evidence`, and the persisted `at` timestamp — then stop. Take no action this tick and write no checkpoint. A parked epic is a human's to unpark by editing the checkpoint (`needs_human` back to `null`, the ticket `status` back to `pending`/`in_progress`, counters adjusted if a budget is re-granted). Do not select a new recovery action on top of an existing one.\n- **Foreign lock.** If `lock.held_by_me` is `false` and `lock.alive` is `true`, another live process owns this epic. `foreign_lock` is a **print-only park**: emit `NEEDS_HUMAN:foreign_lock` carrying `lock.owner_pid` and `lock.host` as evidence, print one bounded, secret-free stdout journal line for this invocation, and stop. Do **not** call `checkpoint set`, spawn a session, merge a pull request, or start a parse while that lock is alive. The checkpoint belongs to the other live process; writing to it — even to record a park — is the two-authorities corruption the lock exists to prevent, and `checkpoint set` refuses a live foreign lock anyway.\n\n## Stage 3 — Detect and Take Exactly One Action\n\nEvaluate the rows below **strictly in written order, from top to bottom**. Evaluation stops at the first row whose condition matches; that row's action is the only action this tick performs, and control then proceeds directly to Stage 4. A later row is never \"also\" run because it happens to apply.\n\nOne row states a **forward-looking guard** in its own condition: Row 3 (`stalled`) matches only when no later action or fail-closed row would be selectable for this snapshot. That guard is part of Row 3's condition, not a departure from written order — the ordering rule still holds, and Row 3 simply does not match while a real action is available.\n\nEach row is marked **fail-open** (an uncertain or transient condition waits for the next tick) or **fail-closed** (the tick refuses to act and parks rather than guessing).\n\n### Row 1 — `all_done`: finish the epic and open its pull request\n\nWhen `all_done` is `true`, run `<launcher> conduct-epic finish '<EPIC>'` (forwarding `--checkpoint-path '<p>'` when supplied), then call the `create_pull_request` MCP tool with `head_branch` set to `epic/<EPIC>` and `base_branch` set to `main`. Assemble the `body` from the finish summary: the merged ticket pull requests and any skipped tickets. **Open the pull request; never merge it** — a human reviews and merges the epic into `main`. Then stop.\n\n**This tick writes no checkpoint and does not increment `counters.iterations`.** It is the third documented exemption from Stage 4's one-checkpoint-per-tick rule, and unlike the two print-only parks it reaches Stage 3. The reason is mechanical: `all_done` is `true` exactly when `ticket` is `null`, `checkpoint set` requires `--ticket <KEY>`, and there is no in-flight ticket to name. `finish` is this tick's durable act, and it is the last one the epic needs — so do not invent a ticket key to satisfy the rule, and do not write a checkpoint before or after `finish`.\n\n### Row 2 — Wrong base: do not touch a pull request that is not on the epic branch\n\nWhen `pr.base` is present and is not `epic/<EPIC>`, **do not touch the pull request** — no merge, no comment, no respawn. Select `NEEDS_HUMAN:wrong_base`, carrying the observed `pr.base`, `pr.number`, and the expected `epic/<EPIC>`. **Fail-closed**: only pull requests based on `epic/<EPIC>` are ever acted upon, and this row is evaluated before every work and recovery row precisely so a mis-based pull request cannot be merged, respawned into, or advanced by a later row.\n\n### Row 3 — Hard liveness: a stalled epic parks before it waits\n\nWhen `stale_for_seconds >= deadlines.hard_seconds` (default three hours, `10800`) **and no other row below is selectable this tick**, select `NEEDS_HUMAN:stalled`, carrying the observed `stale_for_seconds` and the `deadlines.hard_seconds` it exceeded. **Fail-closed**.\n\n**This row outranks wait rows only.** Before selecting it, check whether any of the following would otherwise be selectable for this snapshot; if any one of them would, take that row instead and do not park:\n\n- pending work (Row 4's first spawn),\n- Row 5's **action** branches only — branch 1's parse request, branch 3's completion, and branch 5's causal `parse_failed` park,\n- a targeted respawn (Rows 7, 9, and 11),\n- CI-red handling (Row 9) and review-remediation handling (Row 11),\n- conflict handling (Row 12),\n- ready-to-merge handling (Row 13),\n- a closed, unmerged pull request (Row 13a).\n\n`stale_for_seconds` counts from the last observed head or status change, not from the last useful event — so an old but green and approved pull request accumulates staleness while being perfectly actionable. Parking that is the exact defect this guard removes. The row remains ahead of every wait row, because without it a wait would match forever and the epic would sit silent instead of asking for a human.\n\n**Row 5's wait branches are deliberately NOT in that list.** Branches 2, 4, and 6 — a parse that is queued or in progress, a non-causal `succeeded` or `failed`, an inconsistent request record — are waits, and exempting them would mean a merged ticket whose parse never starts waits forever with no human ever asked. They accumulate staleness like any other wait and park as `stalled` once `deadlines.hard_seconds` is exceeded.\n\n### Row 4 — Pending ticket: spawn the first worker\n\nWhen `ticket.status` is `pending`, spawn the ticket's session:\n\n```\n/review-and-start --auto --base-branch 'epic/<EPIC>' <KEY>\n```\n\nThen prepare the Stage 4 checkpoint values `spawned_at` (now, ISO-8601), `status=in_progress`, and `counters.sessions_spawned` = the Stage 2 value plus one.\n\n**Fail-closed**: refuse this spawn if the lock is foreign (Stage 2 has already parked in that case). The pull-request base of the spawned worker comes from BAPI-801's `BAPI_BASE_BRANCH` export — `/review-and-start --base-branch` forwards it into the spawned worker shell, and the worker's create-PR step resolves the base from it. That export is what makes the first pull request land on `epic/<EPIC>`; this loop never relies on it alone, because Row 2 independently re-checks the observed `pr.base` on every later tick.\n\n### Row 5 — Merged ticket: refresh the scope index, then mark done\n\nWhen `pr.state` is `MERGED`, or `merged_externally` is `true`, or `ticket.status` is `merged`, the ticket's code is on the epic branch. An **external merge is successful reconciliation, not an error** — a human who merged the pull request by hand did the loop's work for it, and `merged_externally` records exactly that.\n\n**The evidence this row acts on is `scope`, and only `scope`.** The epic's index scope is refreshed by the server the moment it observes the merge: it advances its own `required_commit_sha` to the merge commit and re-parses incrementally. So the question \"has this merge been indexed?\" is a question the scope can answer directly, and this row asks it instead of reconstructing an answer.\n\nThat is a deliberate replacement of the older mechanism. This row used to record the time it called `parse_repository` and the head SHA it called it for, then compare that timestamp against a repository-wide parse run's `started_at` / `finished_at` — because `parse.status` is repository-level and stays `succeeded` from any earlier parse of any earlier ticket, so \"succeeded\" alone proved nothing. Timestamp ordering was the only causality available. It is no longer needed, and inference is strictly worse than an answer: **do not call `parse_repository` from this row, and do not read `parse`, `ticket.parse_requested_at`, or `ticket.parse_requested_for_sha` as freshness evidence.** The server owns the refresh; this loop observes it.\n\nThis row is an **ordered state machine**, evaluated top to bottom, and the first matching branch is the tick's action:\n\n1. **`scope` is `null`** — this epic declares no index scope, so there is nothing to refresh and no freshness to establish. Call `update_jira_status` for the ticket with `target_status` set to the Jira `Done` state, and prepare `status=done`. Journal that the ticket completed with no declared scope. **Fail-open.** An epic that never had a scope must not be blocked by one.\n\n2. **`scope.freshness_status` is `fresh`, and `scope.indexed_commit_sha` equals `scope.required_commit_sha`, both non-null** — the scope's index provably covers the commit the server is holding it to. Only then call `update_jira_status` for the ticket with `target_status` set to the Jira `Done` state, and prepare `status=done`. Journal both observed watermarks.\n\n **Compare the scope's two watermarks against each other — never against `pr.head_sha` or `branch_head`.** Both of those are the *worker's* pre-merge branch tip: `pr.head_sha` is `headRefOid`, and `branch_head` is `git ls-remote` of the ticket's own branch. What lands on `epic/<EPIC>` is the merge commit GitHub creates, and that differs from the worker's tip under every merge strategy — merge, squash, and rebase alike. Comparing an indexed watermark against either one is therefore false essentially always, and a branch that waits on an always-false condition never marks anything done. For the same reason, do not invent a merge-commit field: the `scope` object carries exactly the seven fields named above, and none of them is one.\n\n The identity that IS causal runs between the scope's own two watermarks, and it is what replaces the old timestamp ordering. The server advances `required_commit_sha` the moment it observes this merge, and **only the parse** writes `indexed_commit_sha`; the two fields are owned by different writers precisely so their agreement means something. So `indexed == required` is the server's own statement that it has finished indexing everything it was asked to cover. A scope that finished refreshing for a **previous** ticket reads `fresh` too — but it reads it at that previous required commit, and the moment this merge is observed `required` moves ahead of `indexed` and `freshness_status` drops to `pending` until the re-parse lands. If either watermark is `null` the comparison cannot be made, so this branch does not match and the tick falls to branch 6 and waits.\n\n **The one gap this cannot see through** is the interval between the merge and the server observing it: in that window the scope still reads `fresh` at the previous ticket's watermark, and no field in the contract tells it apart from this ticket's. It is narrow in practice — the same merge event that makes `pr.state` read `MERGED` is the one that notifies the server, so a tick that reaches this row has almost always been preceded by that notification — and it closes on its own. It is not zero: a merge the server never observed at all would leave the watermarks agreeing at the previous commit, and this branch would mark the ticket done against an index that does not contain it. Treat a `done` whose journaled watermarks match the *previous* ticket's as that failure, not as a fresh index.\n\n3. **`scope.freshness_status` is `pending`, `unavailable`, or missing** — the refresh is still in flight, or the scope could not be read. Wait. Journal the observed `scope.lifecycle_state`, `scope.required_commit_sha`, and `scope.indexed_commit_sha`. Do not spawn anything and do not advance the next ticket. **An unread scope is never a fresh one.**\n\n4. **`scope.freshness_status` is `blocked`** — the server REFUSED to index this advance, and waiting will never change that. Select `NEEDS_HUMAN:shadow_stale_deadline`, with `scope.blocked_reason` as bounded string evidence, and state plainly in the evidence that **no epic advance was indexed**. **Fail-closed.**\n\n The controlled reasons and what each one means to a human:\n\n - `advance_blocked_base_merge` — the base branch was merged forward into the epic branch. The epic branch is pinned at its cut point; a base merge would move that pin.\n - `advance_blocked_unexpected_parent` — the merge commit does not descend directly from the branch head the scope pinned. Something other than a worker pull request landed on the branch.\n - `advance_blocked_history_changed` — the pinned head is gone from the branch's history. A force-push or rewrite.\n - `advance_blocked_unverifiable` — the advance could not be verified at all. Doubt blocks; it never indexes.\n\n **This park is immediate, and that is deliberate** — it is the one place the pilot escalates faster than v2. The v2 reconciler routes a blocked advance through the same `shadow.stale_deadline_seconds` clock it uses for an ordinary refresh hold, because its hold is anchored on a single durable episode timestamp that every hold reason shares. The pilot has no such episode and no typed `RunPolicy` deadline, and none of the four reasons above resolves by waiting, so waiting out a deadline would only delay a human by up to that deadline and change nothing else. Both conductors emit the **same** `shadow_stale_deadline` reason so one grep finds a refused advance either way; only the latency to the park differs. An operator comparing the two should expect the pilot to ask sooner, not to have asked for a different thing.\n\n5. **`scope.freshness_status` is `failed`** — the scope's own generation failed, which is a different problem from a refused advance. Select `NEEDS_HUMAN:parse_failed`, with `scope.lifecycle_state` and `scope.last_error` as bounded string evidence. **Fail-closed.**\n\n6. **None of branches 1–5 matched** — including a `fresh` scope whose indexed commit still trails its required commit, and a tick where either watermark is missing so no comparison can be made. Wait, and journal the observed scope fields. Neither advance nor park: hard liveness (Row 3) is what eventually escalates a wait that never resolves.\n\n**No next ticket is spawned until this one reaches `done`.** A merged ticket stays in flight until its scope is fresh for its own merge commit, so `ticket` still points at it and Row 4 cannot match for its successor — which is the whole point: the next ticket's review and plan must see this ticket's merged code.\n\n### Row 6 — Worktree working: wait\n\nWhen a worktree exists (`worktree_exists` is `true`), the pull request is **confirmed absent** (`pr` is `null` **and** `probe_errors` carries no `{probe: \"pr\"}` entry), and `worker_commits_since_spawn > 0`, the worker is making observable progress. Wait, and journal the observed `branch_head` and commit count. **Fail-open.**\n\nA `pr: null` accompanied by a PR probe error is unavailable evidence, not absence, and does not match this row — it falls through to Row 15 and waits.\n\n### Row 7 — Soft deadline with no progress: one targeted continuation\n\nWhen the pull request is **confirmed absent** (`pr` is `null` **and** no `{probe: \"pr\"}` entry), `worker_commits_since_spawn` is `0`, and `deadlines.elapsed_since_spawn_seconds >= deadlines.soft_seconds` (default one hour, `3600`), spend the single targeted respawn on kind `continue`, with the prompt:\n\n```\nBranch <b> for <KEY>: continue the existing plan; do not regenerate it; push when done\n```\n\nPrepare `respawns` = the Stage 2 value plus one. `respawns` is **one shared per-ticket budget**, not one allowance per row: Rows 7, 9, and 11 all spend the same single counter, so spending it here leaves nothing for a later CI fix or review fix on this ticket. The attempt **counts only if it pushed** — a later tick observing a non-null `branch_head` is the proof. A respawn that produces no push is a no-op, and a no-op respawn stops the loop rather than spinning: once the one targeted respawn is spent and the ticket still shows no pushed head, select `NEEDS_HUMAN:stalled`. **Fail-closed after one attempt**, which is what keeps a dead worker from being respawned without bound.\n\n### Row 8 — Pull request open, CI not settled: wait\n\nWhen a pull request is open and `ci.complete` is `false` **and no required check in `ci.checks` has already reached a terminal unsuccessful conclusion**, wait; or when `ci.complete` is `true` and green but `ci.stable_across_two_polls` is `false`, wait. **Fail-open.**\n\nThe boolean alone is not the condition. `ci.complete` is `false` both while checks are still running and once a required check has definitively failed, and those are opposite situations: the first is worth waiting on and the second never becomes green on its own. This row therefore covers pending and not-yet-stable checks **only** — a required check with a terminal unsuccessful conclusion is **not** consumed here and falls through to Row 9.\n\n### Row 9 — Pull request open, CI red: one targeted fix\n\nWhen a pull request is open, one or more required checks in `ci.checks` have a terminal unsuccessful conclusion, and there has been no new commit for over 60 minutes (`stale_for_seconds > 3600` is the authoritative no-new-commit duration), spend the single targeted respawn on kind `ci_fix`. Take the failing check names from `ci.checks` — the entries whose `required` is `true` — and use the prompt:\n\n```\nPR #N is red on <checks>: read the check annotations, fix, push; do not regenerate the plan\n```\n\nPrepare `respawns` = the Stage 2 value plus one; the attempt counts only if it pushed. A bare `/implement-ticket --auto` is **prohibited** here: it regenerates the plan, costs a full plan generation, and discards the failure detail the annotations already carry.\n\n`respawns` is **one shared per-ticket budget** across Rows 7, 9, and 11. A continuation respawn spent earlier on this ticket therefore leaves **no** CI-fix attempt: with the counter already at its limit, persistent red CI parks immediately as `NEEDS_HUMAN:ci_red` rather than getting a fix session of its own. Once the shared respawn is spent and CI is still red, select `NEEDS_HUMAN:ci_red` with the failing check names as bounded string evidence. **Fail-closed after one attempt.**\n\n### Row 10 — Review opted in but unavailable: wait one tick\n\nWhen `pr.state` is `OPEN`, `review.opted_in` is `true`, and `review.available` is `false`, the review source could not be read. Wait one tick and journal the condition. **Fail-open** for that tick — but the clock keeps running, so continued unavailability is caught by Row 3's hard-liveness park rather than waiting forever.\n\nThe `pr.state` is `OPEN` guard is load-bearing: without it a `CLOSED` pull request whose review source happens to be unreadable matches here, ahead of Row 13a, and the loop waits tick after tick on abandoned work instead of parking it.\n\n### Row 11 — Changes requested for the current head: one targeted review fix\n\nWhen `pr.state` is `OPEN`, `review.verdict` is `changes_requested`, **and** `review.head_sha` equals `pr.head_sha`, spend the single targeted respawn on kind `review_fix`. The `OPEN` guard is what stops a `changes_requested` verdict left on a **closed** pull request's head from spending this ticket's one respawn on work nobody will merge — that snapshot belongs to Row 13a. The prompt carries the authoritative Stage 2 review evidence: the ticket key, the pull-request number, the reviewed head SHA, and the requested changes. A stale `review.head_sha` (one that does not equal `pr.head_sha`) is a verdict about code that no longer exists and never triggers this row. Prepare `respawns` = the Stage 2 value plus one.\n\n`respawns` is **one shared per-ticket budget** across Rows 7, 9, and 11. Any earlier continuation or CI-fix respawn on this ticket therefore leaves **no** review-fix attempt: with the counter already at its limit, requested changes on the current head park immediately. Once the shared respawn is spent and the verdict still stands for the current head, select `NEEDS_HUMAN:review_changes_requested`. **Fail-closed after one attempt.**\n\n### Row 12 — Conflicting pull request: at most two conflict sessions\n\nWhen `pr.state` is `OPEN` **and** either `pr.mergeable` is `CONFLICTING` or `pr.merge_state` is `DIRTY`, spawn a session of kind `conflict` with the prompt:\n\n```\nrebase onto origin/epic/<EPIC>, resolve, run tests, push\n```\n\nPrepare `conflict_attempts` = the Stage 2 value plus one. The conflict budget is **two** sessions and is counted separately from the single targeted respawn of Rows 7, 9, and 11 — a rebase is a different failure mode from a stalled or red worker. After the second conflict session, if the pull request is still `CONFLICTING`/`DIRTY`, select `NEEDS_HUMAN:conflict`. **Fail-closed after two attempts.**\n\nA **closed** pull request is frequently left `CONFLICTING`/`DIRTY` by GitHub, so without the `pr.state` is `OPEN` guard this row would match ahead of Row 13a and spend a rebase session resolving conflicts on a branch nobody will merge.\n\n### Row 13 — Ready to merge\n\nMerge only when **all** of the following hold on the fresh Stage 2 snapshot: the pull request is open (`pr.state` is `OPEN`); `pr.base` is `epic/<EPIC>`; `ci.complete` is `true` and `ci.stable_across_two_polls` is `true` for `ci.head_sha` equal to `pr.head_sha`; the pull request is not conflicting; and review is either opted out (`review.opted_in` is `false`) or approved (`review.verdict` is `approved`) with `review.head_sha` equal to `pr.head_sha`.\n\nThen call the `merge_pull_request` MCP tool with exactly `pr_number` set to `pr.number` and `expected_head_sha` set to `pr.head_sha`. **The expected SHA is derived only from the fresh Stage 2 status** — never from the checkpoint, never from a worker's report, never from an earlier tick. The checkpoint deliberately stores no expected head; merge identity always comes from a freshly observed `pr.head_sha`. Prepare `counters.merge_attempts` = the Stage 2 value plus one for **every** invocation of the tool, successful or not.\n\nMap the returned envelope:\n\n- **`merged` is `true`** — the only success. It covers `outcome: merged` and `outcome: already_merged`, both of which carry that boolean. Prepare `status=merged` and top-level `counters.merges` = the Stage 2 value plus one.\n- **`outcome: refused` with `reason: head_sha_drift`** — the head moved under the merge. Journal the complete envelope (including `actual_head_sha`) and take a fresh status snapshot on the next tick. Never retry with the stale SHA.\n- **Outcome `lease_held`, `review_not_approved`, or `unknown`, or any envelope carrying `retry_hint: retry_later`** — journal it and wait for the next reconciliation tick.\n- **Outcome `dry_run`, `pending_approval`, `gate_unresolved`, `action_key_mismatch`, `review_unavailable`, `review_source_unsupported`, `error`, or any `refused` result carrying `retry_hint: needs_human`** — select `NEEDS_HUMAN:merge_blocked`. Preserve the **complete** envelope as the evidence, including `hint`, `actual_head_sha`, `ci_summary`, `paths`, and `http_status` whenever those are present; `hint` is usually the exact operator fix. **JSON-stringify that envelope into a bounded, secret-free string** — `evidence` is string data, never an object (see Stage 4).\n\n**Fail-closed**: only `merged: true` is success. A missing, `false`, or malformed `merged` value is never treated as a merge, no matter what `outcome` says alongside it.\n\n### Row 13a — Pull request closed without being merged\n\nWhen `pr.state` is `CLOSED` and the pull request was not merged, the ticket's work has been abandoned on GitHub and nothing this loop does can advance it. Select `NEEDS_HUMAN:merge_blocked`, with bounded string evidence that identifies `pr.state: CLOSED` along with `pr.number`. **Fail-closed** — a closed pull request is never respawned into, reopened, or merged by this loop.\n\n### Row 14 — Local-mode ticket operation refused\n\nWhen a ticket operation returns `409 UNSUPPORTED_IN_LOCAL_MODE`, tolerate it and journal it. The repository is running the local ticket backend, where that response is the documented terminal answer rather than a failure. It introduces **no** new parking reason. **Fail-open.**\n\n### Row 15 — No row matched: journal the snapshot and do nothing else\n\nWhen no row above matches, that is the tick's outcome, not a licence to improvise. Journal a concise summary of the Stage 2 snapshot, take **no** external action — no MCP tool call, no spawn, no merge, no parse — and change **no** row-specific checkpoint field. The single `checkpoint set` this tick writes therefore carries only the universal `counters.iterations` update and its one journal line.\n\nThis row exists because unmatched snapshots are real and reachable: a pull request that is open, complete, stable, and awaiting a review whose `verdict` is still `unknown`; a `stale_for_seconds` or `elapsed_since_spawn_seconds` that is `null` because nothing has been observed yet. Each of those is a legitimate \"wait for reality to move\" state, and a tick that improvised an action for it would be acting on evidence it does not have. **Fail-open.**\n\n### Shared mechanics for every targeted session\n\nRows 7, 9, 11, and 12 spawn a session the same way. The four kinds are exactly `continue`, `ci_fix`, `review_fix`, and `conflict`.\n\n**First, write the prompt file** with the Write tool, at:\n\n```\n~/.config/bridge/conduct/<repo>/<EPIC>/prompts/<KEY>-<kind>-<n>.md\n```\n\nwhere `<EPIC>` and `<KEY>` are the validated keys, `<kind>` is one of the four kinds above, and `<n>` is the applicable absolute attempt number. **`<repo>` is the repository component of the resolved `checkpoint_path` that Stage 2's `status` returned** — read it from there rather than re-deriving it from credentials, from `BAPI_REPO_NAME`, or from anything remembered in conversation. `status` resolves that path itself, including any `--checkpoint-path` override and any `XDG_CONFIG_HOME` redirection, so it is the only value guaranteed to match where the CLI actually keeps this epic's state.\n\n**End every prompt with this exact wording**, so the spawned worker releases its worktree cleanly instead of lingering:\n\n```\nAfter the final pipeline step completes, cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers — but **only when no follow-up remains that you still own**. Do **not** exit while any of the following is true:\n\n- there are unresolved CI failures you are still correcting (the post-PR CI-correction loop in the CI-monitoring step still owns work),\n- review changes were requested and you have not yet addressed them,\n- there is a merge conflict on your PR that you still own,\n- you have unpushed local commits.\n\nExit only after your final branch state is pushed, the done-gate / CI-monitoring workflow required by the recipe has completed, and no CI/review follow-up remains. A clean `SessionEnd` is both the correct terminal lifecycle signal and the point at which the worker should exit.\n```\n\n**Then spawn**, forwarding `--checkpoint-path '<p>'` only when supplied:\n\n```\n<launcher> conduct-epic spawn '<EPIC>' --ticket '<KEY>' --prompt-file '<path>'\n```\n\n`spawn` opens exactly **one** agent tab in the ticket's `worktree_path` running the prompt file's contents. It refuses when the worktree is missing, the prompt file is unreadable, or the lock is held by another live process.\n\n**The budgets are this command's job, not the CLI's.** `spawn` never checks them: it will happily open a fifth tab if asked. One targeted respawn **shared** across Rows 7, 9, and 11 — a single per-ticket `respawns` counter, not one allowance per row — and two conflict sessions in Row 12, are enforced here, by reading the Stage 2 `respawns` and `conflict_attempts` before choosing the row.\n\nAfter a **successful** spawn, prepare `counters.sessions_spawned` = the Stage 2 value plus one. If the spawn command itself fails, do **not** advance `respawns`, `conflict_attempts`, or `counters.sessions_spawned` — a session that never opened has consumed no budget.\n\nKeep credentials, raw environment values, and unrelated command output out of prompt files and out of the spawn command's arguments. The spawned agent resolves its own credentials.\n\n## Stage 4 — Checkpoint and Stop\n\nEvery tick that reaches this stage ends with **exactly one** checkpoint command and **exactly one** journal line. There are **three exemptions**, and they divide into two kinds:\n\n- **Two print-only parks, before Stage 3.** `init_failed` (Stage 1) and `foreign_lock` (Stage 2) stop the tick *before* Stage 3 and write nothing durable at all — they print their `NEEDS_HUMAN:` line and one stdout journal line and stop. Because they never reach Stage 3 they also never increment `counters.iterations`.\n- **The `all_done` tick, inside Stage 3.** Row 1 reaches Stage 3 but has **no in-flight ticket**: `all_done` is `true` exactly when `ticket` is `null`, and `checkpoint set` requires `--ticket <KEY>`. That tick runs `finish`, opens the epic pull request, writes **no** checkpoint, and — as the single stated exception to the rule below — does **not** increment `counters.iterations`.\n\nEvery other tick, including a Row 15 fallthrough, writes here. Run, forwarding `--checkpoint-path '<p>'` whenever the user supplied it:\n\n```\n<launcher> conduct-epic checkpoint set '<EPIC>' --ticket '<KEY>' --field <name> <absolute-value> … --journal '<line>'\n```\n\nRepeat `--field <name> <absolute-value>` once per changed field, and pass `--journal '<line>'` exactly once. Do not issue a second `checkpoint set` in the same tick, and do not split the fields across two invocations — one tick, one auditable write.\n\n**Every value is absolute, computed from the Stage 2 snapshot.** Relative or guessed increments are prohibited: the CLI stores what it is given, so a \"+1\" that was never resolved against a fresh read silently corrupts the count. Compute `n + 1` from the Stage 2 value for `counters.sessions_spawned`, `respawns`, `conflict_attempts`, `counters.merge_attempts`, `counters.iterations`, and `counters.merges`.\n\nInclude only the fields the selected row actually affected — typically some of `status`, `spawned_at`, `respawns`, `conflict_attempts`, `counters.sessions_spawned`, `counters.merge_attempts`, `counters.iterations`, and `counters.merges`.\n\n**`parse_requested_at` and `parse_requested_for_sha` are no longer written by any row.** The CLI still accepts them so an older checkpoint stays readable, but Row 5 now reads the `scope` sub-object — the server's own answer about whether this merge was indexed — rather than recording a request and timing it. Writing them would record evidence nothing reads.\n\n**`counters.iterations` increments exactly once for every tick that reaches Stage 3**, and it is written in that tick's single `checkpoint set` as the Stage 2 absolute value plus one. It is the one field every such tick updates, including a Row 15 fallthrough — which is why a fallthrough tick's checkpoint contains only `counters.iterations` and its journal line, with no status, retry, merge, or parking mutation. The two print-only parks never reach Stage 3 and so never increment it, and the `all_done` tick reaches Stage 3 but writes no checkpoint, so it does not increment it either.\n\n**Parking** adds two fields to the same single command:\n\n```\n--field status needs_human --field needs_human '{\"reason\":\"<reason>\",\"evidence\":\"<bounded secret-free JSON-stringified envelope or output>\",\"at\":\"<ISO-8601 timestamp>\"}'\n```\n\n**`evidence` is a JSON string, never an object.** The CLI's checkpoint schema accepts only `{reason: string, evidence: string, at: string}` and rejects anything else outright, so an object-valued `evidence` makes `checkpoint set` exit non-zero: the `NEEDS_HUMAN:` line prints, the park never persists, and the next tick repeats the failing action. When the evidence is structured — a merge envelope, a command's output — JSON-stringify it and escape every embedded quote and control character so the result is a single valid JSON string value. Keep it bounded and secret-free.\n\nThe `reason` is one of the closed list below and `at` is an ISO-8601 timestamp. Every `NEEDS_HUMAN:<reason>` line printed by a stage carries the **same** evidence that is persisted here — the printed line and the checkpoint never disagree.\n\nThe parking vocabulary is closed, and it has two partitions:\n\n- **Eight persisted reasons**, each written durably by the single `checkpoint set` above: `stalled`, `ci_red`, `review_changes_requested`, `merge_blocked`, `conflict`, `parse_failed`, `shadow_stale_deadline`, and `wrong_base`. A persisted park is what makes the *next* tick report `already parked` and stop.\n - `shadow_stale_deadline` is Row 5 branch 4's reason, and it is deliberately **the same token the v2 conductor parks under** for the same condition. Both conductors reaching for one string is what lets an operator grep for a refused epic advance without first working out which conductor drove the epic. It is distinct from `parse_failed`: `parse_failed` means the index generation broke, while `shadow_stale_deadline` means the index refused to accept the branch advance at all.\n- **Two print-only reasons**, which are printed and journaled to stdout for the current invocation only and write nothing durable: `init_failed` and `foreign_lock`. Neither may call `checkpoint set`. A print-only park leaves no durable record, so it does not produce an `already parked` tick — the next tick reconciles from scratch and reports the condition again if it persists.\n\nDo not invent a new reason; a genuinely new failure mode is a change to this command and to the BAPI-805 runbook together.\n\nThe journal line is one line containing the ISO-8601 time, the selected action, and concise evidence. Print it **last**, after the checkpoint command has succeeded, so the operator's final line of output is the tick's durable record.\n\nEvery dynamic value in this stage follows the Stage 0 single-quote rule — the epic key, the ticket key, the checkpoint path, the `needs_human` JSON, and the journal line are each escaped (`'` → `'\\''`) and wrapped in single quotes. Credentials never appear in a checkpoint argument or in journal evidence.\n\n## Operational Guarantees\n\n- **Spec freshness is `/review-and-start`'s job, not a separate check.** Each ticket's review phase runs in a worktree cut from the current `epic/<EPIC>` tip, so its review and its plan already see every predecessor's merged code. This command runs no separate spec-freshness check and needs none.\n- **The checkpoint plus GitHub are the resume point.** Nothing relies on conversation memory. A sleeping laptop merely misses ticks; the next invocation reconciles from scratch and continues where reality actually is.\n- **This command never creates an `epic_run`.** It must never be combined with `setup-epic` on the same epic — the v2 conductor stays active there, and two authorities transitioning one epic is exactly the failure this pivot removes.\n- **`/loop 5m /conduct-epic <EPIC>` is the driver.** The operator runbook is BAPI-805's, not this file's.\n- **Recovery is bounded**: one targeted respawn *shared* across Rows 7, 9, and 11, and two conflict sessions, then park. There is no third chance and no escalating retry.\n- **The first spawn relies on BAPI-801's `BAPI_BASE_BRANCH` contract**, while every tick still independently verifies the observed `pr.base` (Row 2). The export makes the right thing happen; the check catches it when it does not.\n",
10
10
  "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 `get_docs_dir` MCP tool (no parameters). Store the returned 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",
11
- "create-pr.md": "# Create PR: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes a Jira ticket key (e.g., `BAPI-150`), fetches the ticket summary, determines the base branch, and creates a pull request on the configured VCS provider. It is designed to run after `/commit-ticket` completes.\n\nIf any critical stage fails (Stage 0), stop immediately and report which stage failed and why. Non-critical stages (Stage 1 and Stage 2) should log a warning but not stop the pipeline.\n\n---\n\n# Instructions\n\nYou are executing a 3-stage pipeline to create a pull request for a Jira ticket. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a single required `ticket_key` argument. The expected format is a Jira ticket key such as `BAPI-150` or `PROJ-123` — one or more uppercase letters, a hyphen, and one or more digits (regex: `[A-Z]+-\\d+`). If `$ARGUMENTS` is empty or the value does not match the expected format, stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /create-pr <ticket_key> (e.g., /create-pr BAPI-150)\n ```\n\n2. **Connectivity check**: Call the `ping` MCP tool (no parameters). If the ping fails or does not return `\"status\": \"ok\"`, stop immediately and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n3. **Get current branch**: Run `git branch --show-current` in the terminal. Store the result as `head_branch`. Verify that `head_branch` contains the `ticket_key` (case-insensitive comparison). If the branch does not contain the ticket key, stop immediately and display:\n\n ```\n Current branch '<head_branch>' does not contain ticket key <ticket_key>.\n Please switch to the correct feature branch before running /create-pr.\n ```\n\n4. **Resolve base branch**: Resolve the base through this ordered precedence and take the first tier that yields a usable value.\n\n 1. **`BAPI_BASE_BRANCH` from the environment, when set and non-empty.** Read it first, explicitly, with Bash — never infer the base from branch ancestry or the repository default branch:\n\n ```bash\n echo \"${BAPI_BASE_BRANCH:-}\"\n ```\n\n The `:-` form returns an empty line when the variable is unset, so the read never fails the stage. `start-tickets` exports this variable into a worker's shell whenever the run has a non-default base (an epic branch, for example), so it must win over the repository-wide configured value.\n 2. **The repository's configured base branch** — only when the environment value is unset. Call the `config_field` MCP tool with `operation` set to `\"get\"` and `field_name` set to `base_branch`.\n 3. **`main`** — the expected fallback default.\n\n Treat a null, empty, or whitespace-only value, an HTTP 400 Validation Error / Invalid field name, or any lookup error as not set, and fall back to `main` rather than failing the stage. Store the resolved value as `base_branch`.\n\n5. **Fetch ticket summary**: Call the `get_ticket` MCP tool with `ticket_number` set to the parsed `ticket_key`. Extract the ticket summary from the response. If the tool returns an error, log a warning and use a generic summary based on the ticket key.\n\n6. **Resolve docs directory**: Call the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Create Pull Request\n\n1. **Compose PR title**: Format the title as `<ticket_key>: <ticket_summary>`. Truncate to 72 characters if needed.\n\n2. **Compose PR body**: Build a PR body that includes, in this order:\n - A brief description derived from the ticket summary\n - A plain text reference to the local implementation plan: `Implementation Plan available locally at {docs_dir}/plans/{ticket_key}-plan.md` (do not use markdown hyperlink syntax — the local path is sufficient for team members pulling the branch)\n - The checklist text of `.github/PULL_REQUEST_TEMPLATE.md`, read from the current worktree when that file exists and appended after the plan reference without rewriting its markdown structure. Omit this part when the file is absent. GitHub's REST API does not automatically apply the repository pull request template — it is a web-UI affordance — so the checklist must be inlined into the body here or the created PR has none.\n\n3. **Create the pull request**: Call the `create_pull_request` MCP tool with:\n - `head_branch`: the current branch from Stage 0\n - `base_branch`: the resolved base branch from Stage 0\n - `title`: the composed PR title\n - `body`: the composed PR body\n\n4. **Handle the response with graceful degradation**:\n - If the response contains `available: false`: Report the reason to the user and skip to Stage 2. Do not halt the pipeline.\n - If the response contains `created: false`: Log \"PR already exists\" and store the returned PR URL. Continue to Stage 2.\n - If the response contains `created: true`: Store the PR URL. Continue to Stage 2.\n - If an HTTP error occurs: Warn the user with the error details and continue to Stage 2. Do not halt the pipeline.\n\nThis stage is **non-critical** — warn on failure, continue to Stage 2 regardless.\n\n## Stage 2 — Summary Report\n\nDisplay a structured report after all stages complete:\n\n```\n## Pull Request Report\n\n**Ticket**: <ticket_key>\n**Branch**: <head_branch>\n**Base Branch**: <base_branch>\n**PR URL**: <pr_url or \"N/A — see warnings\">\n\n**Warnings**:\n<If any non-critical stages had warnings (Stage 1: PR creation failed or unavailable),\nlist them here. If no warnings, omit this section.>\n```\n\nThis stage is **non-critical** — display the report regardless.\n\n## Final Report\n\nOn success, display the structured report from Stage 2 confirming that the pull request was created (or already existed), including the branch name, base branch, PR URL, and any warnings from earlier stages.\n\nOn failure at any critical stage (Stage 0), display which stage failed and the error details.\n",
11
+ "create-pr.md": "# Create PR: $ARGUMENTS\n\n$ARGUMENTS\n\nThis command takes a Jira ticket key (e.g., `BAPI-150`), fetches the ticket summary, determines the base branch, and creates a pull request on the configured VCS provider. It is designed to run after `/commit-ticket` completes.\n\nIf any critical stage fails (Stage 0), stop immediately and report which stage failed and why. Non-critical stages (Stage 1 and Stage 2) should log a warning but not stop the pipeline.\n\n---\n\n# Instructions\n\nYou are executing a 3-stage pipeline to create a pull request for a Jira ticket. Execute all stages in sequence.\n\n## Stage 0 — Setup and Argument Parsing\n\n1. **Parse `$ARGUMENTS`**: Extract a single required `ticket_key` argument. The expected format is a Jira ticket key such as `BAPI-150` or `PROJ-123` — one or more uppercase letters, a hyphen, and one or more digits (regex: `[A-Z]+-\\d+`). If `$ARGUMENTS` is empty or the value does not match the expected format, stop immediately and display:\n\n ```\n Invalid ticket key format: '<value>'. Expected format: PROJ-123 (uppercase letters, hyphen, digits).\n Usage: /create-pr <ticket_key> (e.g., /create-pr BAPI-150)\n ```\n\n2. **Connectivity check**: Call the `ping` MCP tool (no parameters). If the ping fails or does not return `\"status\": \"ok\"`, stop immediately and display:\n\n ```\n Connectivity check failed. Please verify:\n - Check that the Bridge API MCP server is configured in your editor's MCP settings\n - Check that BAPI_BASE_URL is set and the server is reachable\n - Check that BAPI_API_KEY is valid\n - Check that BAPI_REPO_NAME matches a configured repository\n ```\n\n3. **Get current branch**: Run `git branch --show-current` in the terminal. Store the result as `head_branch`. Verify that `head_branch` contains the `ticket_key` (case-insensitive comparison). If the branch does not contain the ticket key, stop immediately and display:\n\n ```\n Current branch '<head_branch>' does not contain ticket key <ticket_key>.\n Please switch to the correct feature branch before running /create-pr.\n ```\n\n4. **Resolve base branch**: Resolve the base through this ordered precedence and take the first tier that yields a usable value.\n\n 1. **`BAPI_BASE_BRANCH` from the environment, when set and non-empty.** Read it first, explicitly, with Bash — never infer the base from branch ancestry or the repository default branch:\n\n ```bash\n echo \"${BAPI_BASE_BRANCH:-}\"\n ```\n\n The `:-` form returns an empty line when the variable is unset, so the read never fails the stage. The packaged `start-tickets` exports this variable into a worker's shell for **every** resolved run base the ordinary `main` case included, not only an epic branch so under a packaged spawn this tier always wins over the repository-wide configured value.\n 2. **The repository's configured base branch** — only when the environment value is unset. Call the `config_field` MCP tool with `operation` set to `\"get\"` and `field_name` set to `base_branch`.\n 3. **`main`** — the expected fallback default.\n\n Tiers 2 and 3 exist for a workflow where the environment contract is genuinely absent: `/create-pr` invoked by hand, or a legacy worker started outside packaged `start-tickets`. They are not the normal packaged-worker path — a packaged worker always arrives with `BAPI_BASE_BRANCH` set.\n\n Treat a null, empty, or whitespace-only value, an HTTP 400 Validation Error / Invalid field name, or any lookup error as not set, and fall back to `main` rather than failing the stage. Store the resolved value as `base_branch`.\n\n5. **Fetch ticket summary**: Call the `get_ticket` MCP tool with `ticket_number` set to the parsed `ticket_key`. Extract the ticket summary from the response. If the tool returns an error, log a warning and use a generic summary based on the ticket key.\n\n6. **Resolve docs directory**: Call the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\nThis stage is **critical** — stop immediately on failure. Do not proceed to Stage 1.\n\n## Stage 1 — Create Pull Request\n\n1. **Compose PR title**: Format the title as `<ticket_key>: <ticket_summary>`. Truncate to 72 characters if needed.\n\n2. **Compose PR body**: Build a PR body that includes, in this order:\n - A brief description derived from the ticket summary\n - A plain text reference to the local implementation plan: `Implementation Plan available locally at {docs_dir}/plans/{ticket_key}-plan.md` (do not use markdown hyperlink syntax — the local path is sufficient for team members pulling the branch)\n - The checklist text of `.github/PULL_REQUEST_TEMPLATE.md`, read from the current worktree when that file exists and appended after the plan reference without rewriting its markdown structure. Omit this part when the file is absent. GitHub's REST API does not automatically apply the repository pull request template — it is a web-UI affordance — so the checklist must be inlined into the body here or the created PR has none.\n\n3. **Create the pull request**: Call the `create_pull_request` MCP tool with:\n - `head_branch`: the current branch from Stage 0\n - `base_branch`: the resolved base branch from Stage 0\n - `title`: the composed PR title\n - `body`: the composed PR body\n\n4. **Handle the response with graceful degradation**:\n - If the response contains `available: false`: Report the reason to the user and skip to Stage 2. Do not halt the pipeline.\n - If the response contains `created: false`: Log \"PR already exists\" and store the returned PR URL. Continue to Stage 2.\n - If the response contains `created: true`: Store the PR URL. Continue to Stage 2.\n - If an HTTP error occurs: Warn the user with the error details and continue to Stage 2. Do not halt the pipeline.\n\nThis stage is **non-critical** — warn on failure, continue to Stage 2 regardless.\n\n## Stage 2 — Summary Report\n\nDisplay a structured report after all stages complete:\n\n```\n## Pull Request Report\n\n**Ticket**: <ticket_key>\n**Branch**: <head_branch>\n**Base Branch**: <base_branch>\n**PR URL**: <pr_url or \"N/A — see warnings\">\n\n**Warnings**:\n<If any non-critical stages had warnings (Stage 1: PR creation failed or unavailable),\nlist them here. If no warnings, omit this section.>\n```\n\nThis stage is **non-critical** — display the report regardless.\n\n## Final Report\n\nOn success, display the structured report from Stage 2 confirming that the pull request was created (or already existed), including the branch name, base branch, PR URL, and any warnings from earlier stages.\n\nOn failure at any critical stage (Stage 0), display which stage failed and the error details.\n",
12
12
  "critique-ticket.md": "Generate a ticket quality critique and save it locally.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command triggers an AI-powered critique of a Jira ticket and saves the result locally. **No human confirmation gates** — the command runs end-to-end without pausing. `$ARGUMENTS` should contain a single Jira ticket key in `PROJECT-NUMBER` format (e.g., `BAPI-123`).\n\nIf any step fails, stop immediately and report which step failed and why.\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\n2. **Validate the ticket key format**: Validate that `ticket_key` matches the regex pattern `^[A-Za-z][A-Za-z0-9]+-\\d+$`. If validation fails, stop immediately and report: \"The argument does not match the expected `PROJECT-NUMBER` format. Example: `BAPI-123`.\"\n\n## Step 2 — Resolve Docs Directory\n\nCall the `get_docs_dir` MCP tool (no parameters). Store the returned path as `docs_dir`.\n\n## Step 3 — Generate Critique\n\nCall the `request_ticket_critique` MCP tool with:\n- `ticket_number`: the validated `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\nIf the tool returns an error, stop immediately and report: \"Critique generation failed.\" Include the error details.\n\n## Final Report\n\n**On success**, display a summary including:\n\n- Path to the saved critique document: `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n\nNote: The critique was NOT pushed to Jira. To incorporate the critique findings into the Jira ticket description, run: `/update-ticket {ticket_key}`\n\n**On failure at any step**, stop immediately and display the step that failed and the error details.\n",
13
13
  "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",
14
14
  "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",
15
- "explore-ticket.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: /explore-ticket <prompt describing your task and goals>`\n\n2. **Resolve docs directory**: Call the `get_docs_dir` MCP tool (no parameters). Store the returned 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\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 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, even if the user has not responded to it.\n\n## Stage 9 — Ticket Handoff\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. **Offer to create the ticket(s).** When the work is ready, 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 point at `/write-ticket` for later. Never create a ticket without an explicit affirmative (`y` or `yes`) — creation is irreversible.\n\n3. **Create on approval.** Use `create_ticket` (or `/write-ticket` for a larger draft), building the ticket from the doc: the ratified acceptance criteria become the ticket's acceptance criteria verbatim, and the proposed design becomes its implementation notes. Do not restate or reinterpret the criteria — they were ratified in that wording. Split into multiple tickets when the design has independently shippable slices; say why you split before you do.\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\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",
15
+ "explore-ticket.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: /explore-ticket <prompt describing your task and goals>`\n\n2. **Resolve docs directory**: Call the `get_docs_dir` MCP tool (no parameters). Store the returned 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\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\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. **Offer to create the ticket(s).** When the work is ready, first output the plan outline so the user can see what they are approving. Never present the gate bare — the user cannot consent to a plan they have not been shown. Use this shape, kept to roughly one screen:\n\n - **Plan** — the approach in one sentence.\n - **Tickets ({n})** — one line per ticket: title, the slice of scope it covers, and the ratified criterion ids it satisfies. 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 point at `/write-ticket` for later. Never create a ticket without an explicit affirmative (`y` or `yes`) — creation is irreversible.\n\n3. **Create on approval.** Use `create_ticket` (or `/write-ticket` for a larger draft), building the ticket from the doc: the ratified acceptance criteria become the ticket's acceptance criteria verbatim, and the proposed design becomes its implementation notes. Do not restate or reinterpret the criteria — they were ratified in that wording. 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\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\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",
16
16
  "full-automation.md": "---\nschedulable: true\narguments: {\"positionals\":[],\"flags\":[{\"name\":\"ideaFile\",\"flag\":\"--idea-file\",\"type\":\"string\",\"required\":true},{\"name\":\"auto\",\"flag\":\"--auto\",\"type\":\"boolean\"}]}\n---\n\nRun the end-to-end full-automation chain (idea-to-ticket → review-ticket → start-tickets) via the server-side chain orchestrator.\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command drives Phase A's server-side full-automation chain. The only orchestration tools you may drive are `run_full_automation` and `resume_full_automation`; any other Bridge API MCP call you make must be one a server `agent_task` instruction explicitly directs. The server owns all orchestration — ticket creation, review fan-out, and the start-tickets handoff. Do NOT enrich, re-implement, or second-guess any of that work on the client side.\n\n## Stage 0 — Parse arguments\n\n1. Tokenize `$ARGUMENTS` on whitespace. Recognize the following position-independent flags. Each flag supports both the space form (`--flag value`) and the equals form (`--flag=value`) where a value is taken:\n - `--idea <text>` / `--idea=<text>`\n - `--idea-file <path>` / `--idea-file=<path>`\n - `--auto`\n - `--require-approval`\n - `--scheduled-at <ISO-8601>` / `--scheduled-at=<ISO-8601>`\n - `--chain-run-id <UUID>` / `--chain-run-id=<UUID>`\n - `--max-children N` / `--max-children=N`\n - `--allow-duplicate`\n\n2. Value-consumption rules:\n - `--idea` (space form) consumes every subsequent token until the next recognized flag — the idea may contain spaces.\n - `--idea-file`, `--scheduled-at`, `--chain-run-id`, and `--max-children` each consume exactly one value token (the immediately following token, or the text after `=`).\n - `--auto`, `--require-approval`, and `--allow-duplicate` are boolean toggles and consume no value.\n\n3. Free-form idea: all non-flag tokens become the free-form `idea` text **only when both `--idea` and `--idea-file` are absent**. Join those tokens back together preserving order and trim surrounding whitespace. When `--idea` or `--idea-file` is present, there must be no leftover non-flag tokens: reject any stray non-flag token (for example, text following `--idea=<text>` or following the `--idea-file <path>` value) before any MCP tool call rather than silently dropping it.\n\n4. Reject **unknown flags** (any token beginning with `--` that is not one of the recognized flags above) before making any MCP tool call. Stop and report the offending flag.\n\n5. Reject **combined `--idea` and `--idea-file`** before making any MCP tool call:\n ```text\n Provide exactly one of --idea or --idea-file; do not pass both.\n ```\n\n6. Missing-input rule: unless `--chain-run-id` is present, an idea is required. If `--chain-run-id` is absent **and** no idea was supplied (no `--idea`, no `--idea-file`, and no free-form idea tokens), stop immediately and display exactly:\n ```text\n Usage: /full-automation (--idea \"<text>\" | --idea-file <path> | <free-form idea>) [--require-approval] [--scheduled-at <ISO-8601>] [--chain-run-id <UUID>] [--max-children N] [--allow-duplicate]\n ```\n\n7. `--chain-run-id` is the resume path and does **not** require any idea content — when it is present, skip the missing-input check above and proceed to resume.\n\n8. `--idea-file` is forwarded as a path. The skill must **not** read the file contents locally; the server resolves the file.\n\n9. Resolve the derived values:\n - `auto_approve` defaults to `true` (full automation is hands-off by default). It is `false` **only** when `--require-approval` is present. `--auto` is accepted but redundant (a no-op that restates the default), and `--scheduled-at` likewise runs hands-off. When `--require-approval` is present, the chain pauses at external-mutation and review-decision gates for confirmation.\n - `max_children` is the parsed positive integer when `--max-children` is present; otherwise omit it entirely so the server default applies.\n - `allow_duplicate` is `true` only when `--allow-duplicate` is present; otherwise omit it.\n\n## Stage 1 — Drift-check gate\n\nThis gate runs immediately after parsing and **before any MCP tool call**.\n\n1. If `--scheduled-at` is absent, skip this entire stage.\n2. Compute `delta_seconds = now_utc - scheduled_at` (both in UTC).\n3. If `delta_seconds <= 60`, proceed silently to Stage 2.\n4. If `delta_seconds > 60`, present this prompt verbatim (substituting the bracketed values):\n ```text\n Scheduled at <T-iso> UTC; running now at <now-iso> UTC (<Δ human-readable> late). The laptop was likely asleep or unavailable at the scheduled time. Confirm to proceed with the chain, or cancel.\n ```\n Offer the user the choices: `[Confirm] / [Cancel]`.\n5. On `Confirm`, proceed to Stage 2.\n6. On `Cancel`, print this message verbatim and stop:\n ```text\n Chain cancelled by user (drift confirmation declined). No Jira tickets created.\n ```\n When the user cancels, `run_full_automation` must **not** be called.\n7. The 60-second threshold is fixed and must not be made configurable.\n\n## Stage 2 — Run or resume the chain\n\nThe chain is driven entirely by the server-side orchestrator. Announce progress using each envelope's `preamble`, preserving its `Stage N of M — <title>` shape.\n\n### Stage 2a — Start (when `--chain-run-id` is absent)\n\nCall **only** `run_full_automation`. Build the payload, **omitting** any optional value that was not provided (never send `null` or empty strings):\n```json\n{\n \"idea\": \"<resolved inline/free-form idea, when provided>\",\n \"idea_file\": \"<idea-file path, when provided>\",\n \"auto_approve\": \"<resolved boolean>\",\n \"scheduled_at\": \"<scheduled-at value, when provided>\",\n \"max_children\": \"<parsed integer, when provided>\",\n \"allow_duplicate\": \"<true, when provided>\"\n}\n```\n\n### Stage 2b — Resume (when `--chain-run-id` is present)\n\nCall **only** `resume_full_automation` first, with:\n```json\n{\n \"chain_run_id\": \"<UUID>\",\n \"agent_result\": \"Manual resume requested from /full-automation --chain-run-id.\"\n}\n```\n\n### Stage 2c — Envelope loop\n\nFor each envelope returned by `run_full_automation` / `resume_full_automation`, dispatch on `status` / `next_action.kind`:\n\n- `status: \"failed\"` → stop chain progression and render the final report (Stage 3) with the failure status. Do **not** advance to any later stage.\n- `status: \"completed\"` or `next_action.kind: \"complete\"` → render the final report (Stage 3).\n- `status: \"needs_agent_task\"` with `next_action.kind: \"agent_task\"` → display the envelope `preamble`, perform the agent task exactly as the `next_action.instruction` directs, then call `resume_full_automation` with `chain_run_id` set to the envelope's `chain_run_id` and `agent_result` set to the resulting text. Loop back and process the next envelope.\n\nSpecial case — the stage-3 handoff: when the agent-task instruction names a `/start-tickets ...` command, invoke that slash command in **this same session**, summarize the outcome in one line, and pass that one-line summary as `agent_result` to `resume_full_automation`.\n\nConstraints:\n- On your own initiative, the skill must **not** call any Bridge API MCP tool other than `run_full_automation` / `resume_full_automation` — in particular, never independently drive orchestration (`run_pipeline`, `resume_pipeline`, `get_pipeline_recipe`) or enrich tickets (`get_ticket`, `update_ticket_description`, etc.). **However, when a `needs_agent_task` instruction returned by the server explicitly directs you to call a specific Bridge API MCP tool** (for example an orchestrator-directed `get_tickets`, `create_ticket`, `attachment`, or `track_ticket`), **you must invoke that tool exactly as instructed** — performing an orchestrator-directed agent task is not re-orchestrating.\n- If a v1 envelope unexpectedly returns `next_action.kind: \"mcp_call\"`, stop with a clear protocol error instead of bypassing the server-side orchestrator:\n ```text\n Protocol error: chain returned next_action.kind \"mcp_call\", which is out of scope for /full-automation v1. Stopping.\n ```\n\n## Stage 3 — Final report\n\nWhen the chain completes or fails, render this skeleton verbatim:\n\n```markdown\n## Full Automation Complete\n\nChain run: <chain_run_id>\nIdea: <first 80 chars of idea>...\nStages:\n 1. idea-to-ticket: <stages[0].summary>\n 2. review-ticket: <stages[1].summary>\n 3. start-tickets: <stages[2].summary>\n\nTotal Jira tickets created: N\nTotal worktrees spawned: M\nStatus: Success / Failed at stage N — <reason>\n```\n\n- Stage summaries come from the chain envelope or manifest when present.\n- When the completed envelope does not include full stage objects, use the summaries already surfaced in the prior `preamble` text rather than calling additional tools.\n- A stage-1 `too_vague_to_ticket` failure must render the upstream halt reason and set `Status: Failed at stage 1 — <reason>`.\n- Failed chains must not advance to later stages after a failed envelope is received.\n",
17
17
  "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",
18
18
  "implement-ticket.md": "# Implement Ticket\n\n$ARGUMENTS\n\n---\n\n# Instructions\n\nThis command is recipe-driven. Do not call MCP tools directly -- the recipe determines which tools to call and with what parameters.\n\n## CONDUCTOR_MESSAGE_RELAY ##\n\nIf you were launched under the Conductor (your environment carries a conductor run/worker identity), cooperatively poll for supervisor guidance while you work: at natural checkpoints — after reading context, before major code changes, after major implementation chunks, while polling CI checks during the post-PR correction loop, and before your final response — call the `check_messages` MCP tool. The tool reads and acknowledges any messages addressed to you, and acknowledged messages are not redelivered, so a later call returns only new guidance. This is cooperative polling only — it is not prompt injection and never mutates a live session. If the tool or conductor identity is unavailable, continue the task without derailing.\n\n1. Parse `$ARGUMENTS` to extract:\n - A required `ticket_key` matching the Jira key pattern (`[A-Z][A-Z0-9]+-\\d+`).\n - An optional position-independent `--auto` flag.\n\n Tokenize `$ARGUMENTS` on whitespace. The first token matching the Jira key pattern is the `ticket_key`; ignore any additional ticket-key tokens. The presence of a `--auto` token (anywhere in `$ARGUMENTS`) sets `auto_approve` to `true`.\n\n If `$ARGUMENTS` is empty or contains no token matching the Jira key pattern, stop immediately and display:\n ```\n Invalid ticket key format. Expected: PROJ-123 [--auto]\n Usage: /implement-ticket <ticket_key> [--auto]\n ```\n\n2. Call the `get_pipeline_recipe` MCP tool with:\n - `pipeline`: `\"implement-ticket\"`\n - `variables`: `{ \"ticket_key\": \"<ticket_key>\" }`\n - `auto_approve`: `true` — only when `--auto` was passed; otherwise omit this field entirely.\n\n If the tool returns an error, stop and report the failure.\n\n Retain the **complete** JSON response — every step, plus `total_steps`, `auto_approve`, and `execution_mode`. `execution_mode` is `\"inline\"` here: you are the executor, there is no server-side orchestrator, and each step's instruction branches on that value.\n\n3. Read and strictly obey the `agent_instructions` field in the response. Execute **every** step the response returned, in its resolved order, announcing each as **Step N of M: <description>** immediately before executing it. Traverse the whole array — never a fixed or remembered subset, which would silently omit the later steps.\n\n **A phase's durable-recording tool call is not the end of the command.** The `record_phase_result` and `record_checkpoint` tools return a success envelope; that envelope means the *current* `agent_task` step finished, nothing more. Continue immediately with the next step in the same turn. This applies in particular at the 3 → 4, 5 → 6, and 8 → 9 boundaries, which are where the recipe previously stopped: those instruction files used to end in a text envelope addressed to an orchestrator that does not exist inline, so the turn ended there and the run stalled with no error. None of these boundaries introduces an approval pause.\n\n4. After all steps complete, display a summary:\n ```\n ## Pipeline Complete\n\n **Ticket**: <ticket_key>\n **Steps executed**: N of M\n **Status**: Success / Failed at step N\n\n **Branch**: <selected branch>\n **PR**: <pull request URL>\n **last_commit_sha**: <latest pushed head SHA>\n **Verification**: <each bounded command run, with its observed outcome>\n **Correction commit**: <subject of the correction commit pushed after verification, or \"None\">\n **Unresolved findings**: <findings reported but not fixed, or \"None\">\n ```\n\n Rules for this summary:\n\n - **`last_commit_sha` is the latest *pushed* head**, not the commit that opened\n the PR. The pipeline opens the pull request before running its bounded\n verification, so a correction pushed afterwards moves the head — report the\n head as it stands after the final push.\n - **`Status` describes pipeline execution, not a merge verdict.** \"Success\" means\n the recipe's steps ran to completion; it does not mean CI passed, that the code\n review approved the change, or that the ticket is mergeable.\n - **Never self-declare a gate outcome.** Do not label the summary with claims such\n as \"CI passed\", \"checks green\", \"review approved\", or \"gate met\". Report the\n exact check and review states you observed instead — the `ci` and `code_review`\n gates are authoritative and the reconciler observes them independently.\n\n---\n\n# Worker scope discipline and clean exit (Conductor auto mode)\n\nThese rules apply only when you were launched under the Conductor in auto mode (`--auto`); a normal interactive `/implement-ticket` run is unaffected.\n\n## Declared file-scope discipline (N-2)\n\nIf your environment carries `BAPI_CONDUCTOR_DECLARED_TOUCHED_FILES_JSON` (a JSON array of the ticket's declared touched files), stay within that declared file boundary: do **not** create, modify, or delete files outside the declared set. Those out-of-scope files typically belong to a sibling ticket, and editing them risks a merge conflict or a silent clobber of the sibling's merged work. The pre-PR file-scope guard (run at the PR-creation step) will warn about any out-of-scope diff — treat that warning as a signal to re-check your scope, not as a blocker. When the variable is absent, empty, or invalid there is no declared boundary and this rule does not apply.\n\n## Clean session exit (D2)\n\nAfter the final pipeline step completes, cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers — but **only when no follow-up remains that you still own**. Do **not** exit while any of the following is true:\n\n- there are unresolved CI failures you are still correcting (the post-PR CI-correction loop in the CI-monitoring step still owns work),\n- review changes were requested and you have not yet addressed them,\n- there is a merge conflict on your PR that you still own,\n- you have unpushed local commits.\n\nExit only after your final branch state is pushed, the done-gate / CI-monitoring workflow required by the recipe has completed, and no CI/review follow-up remains. A clean `SessionEnd` is both the correct terminal lifecycle signal and the point at which the worker should exit.\n",