@bridge_gpt/mcp-server 0.2.59 → 0.2.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +181 -51
  2. package/build/agent-guidance.generated.js +1 -1
  3. package/build/agent-notes.js +7 -1
  4. package/build/commands.generated.js +6 -6
  5. package/build/conductor/bridge-api-client.js +406 -4
  6. package/build/conductor/doctor.js +255 -53
  7. package/build/conductor/epic-runtime.js +11 -2
  8. package/build/conductor/install-doctor.js +24 -2
  9. package/build/conductor/local-merge.js +319 -26
  10. package/build/conductor/merge-execution-contract.js +86 -0
  11. package/build/conductor/merge-execution-eligibility.js +130 -0
  12. package/build/conductor/readiness-sections.js +256 -16
  13. package/build/conductor/readiness.js +203 -29
  14. package/build/conductor/recovery-operations.js +8 -2
  15. package/build/connect-bitbucket-api.js +6 -1
  16. package/build/connect-bitbucket.js +398 -88
  17. package/build/credential-store.js +172 -0
  18. package/build/docs.generated.js +2 -2
  19. package/build/doctor.js +277 -15
  20. package/build/drive-epic.js +147 -12
  21. package/build/epic-implementer-bin.js +15 -15
  22. package/build/epic-implementer-claude-hook-bin.js +1 -1
  23. package/build/epic-implementer-env.js +43 -14
  24. package/build/epic-integration-pr.js +61 -15
  25. package/build/epic-launch-profile.js +455 -0
  26. package/build/executor/ci-rerun-job.js +269 -0
  27. package/build/executor/cli.js +40 -0
  28. package/build/executor/credentials.js +28 -1
  29. package/build/executor/env.js +60 -0
  30. package/build/executor/http-client.js +45 -1
  31. package/build/executor/index.js +21 -4
  32. package/build/executor/integration-job.js +759 -0
  33. package/build/executor/job-runner.js +218 -9
  34. package/build/executor/job-types.js +12 -2
  35. package/build/executor/merge-job.js +11 -2
  36. package/build/executor/observation.js +114 -7
  37. package/build/executor/runner.js +101 -1
  38. package/build/executor/service-lifecycle.js +136 -0
  39. package/build/executor/service-unit.js +427 -9
  40. package/build/executor/types.js +48 -0
  41. package/build/index.js +479 -240
  42. package/build/install-bridge-conductor.js +599 -14
  43. package/build/install-bridge.js +448 -46
  44. package/build/mcp-install-state.js +42 -4
  45. package/build/merge-pull-request.js +103 -10
  46. package/build/merge-workflow-codeowners.js +224 -0
  47. package/build/merge-workflow-config.js +187 -0
  48. package/build/merge-workflow-drift.js +97 -0
  49. package/build/merge-workflow-install.js +638 -0
  50. package/build/merge-workflow.js +956 -0
  51. package/build/pipelines.generated.js +3 -3
  52. package/build/plane/cli.js +24 -2
  53. package/build/plane/preflight.js +68 -0
  54. package/build/plane/supervisor.js +6 -0
  55. package/build/readme.generated.js +1 -1
  56. package/build/run-unit-tests-launcher.js +5 -0
  57. package/build/setup-epic.js +263 -38
  58. package/build/version.generated.js +3 -3
  59. package/package.json +3 -3
  60. package/build/conduct-epic/bridge-client.js +0 -773
  61. package/build/conduct-epic/checkpoint-store.js +0 -542
  62. package/build/conduct-epic/cli.js +0 -3145
  63. package/build/conduct-epic/cut-protocol.js +0 -392
  64. package/build/conduct-epic/lock.js +0 -302
  65. package/build/conduct-epic/pr-state.js +0 -286
  66. package/build/conduct-epic/spawn.js +0 -113
  67. package/build/conductor/cli.js +0 -1278
  68. package/build/conductor-bundle-cli.js +0 -256
  69. package/build/direct-ticket-tools.js +0 -70
  70. package/build/executor/reconcile-overlap-governance.js +0 -129
  71. package/build/implement-epic/bridge-client.js +0 -773
  72. package/build/implement-epic/checkpoint-store.js +0 -542
  73. package/build/implement-epic/cli.js +0 -3158
  74. package/build/implement-epic/cut-protocol.js +0 -392
  75. package/build/implement-epic/lock.js +0 -302
  76. package/build/implement-epic/pr-state.js +0 -286
  77. package/build/implement-epic/spawn.js +0 -113
  78. package/build/plan-epic-conductor-eligibility.js +0 -213
@@ -900,15 +900,15 @@ export const INSTRUCTIONS = {
900
900
  "learn-documentation-instructions.md": "## Objective\n\nExplore the codebase to identify implementation documentation patterns — the markdown records that document what was built, why, and when — then draft `documentation_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Explore Implementation Record Patterns\n\nFocus on how the project records what was built, why, and when. These records serve as persistent project memory. Code-level documentation (docstrings, inline comments) is handled by correctness standards, not here.\n\n1. **Implementation Record Discovery**: Search for:\n - Ticket-numbered documents matching `BAPI-*.md` or `PROJ-*.md` in `docs/` and subdirectories\n - Feature/migration documents in `docs/`, `documentation/`, or similar directories\n - Architecture Decision Records (ADRs) in `adr/`, `decisions/`, or similar\n - Changelogs (`CHANGELOG.md`, release notes)\n\n Count how many records exist and identify the naming convention.\n\n2. **Record Structure Analysis**: Read 3-5 representative implementation records (mix of early and recent). Document:\n - Sections present (Summary, Architecture, Database Changes, API Reference, etc.)\n - Level of detail provided\n - Types of information captured (motivation, design decisions, schema changes, file paths, API contracts)\n - How code examples and diagrams are used\n\n3. **Documentation Location and Organization**: Read the directory structure of `docs/` to identify where records are stored, the file naming convention, whether there is a table of contents or index, and whether subdirectories serve different purposes.\n\n### Phase 2 — Draft\n\nDraft `documentation_instructions` as **exactly one concise prose paragraph** that an AI agent will follow when writing implementation documentation after completing a feature. The drafted value is inlined verbatim into a generated plan step, so it has hard formatting constraints:\n\n- The output MUST be **one prose paragraph under 1,500 characters**.\n- The output MUST avoid **markdown headings, bullets, numbered lists, and intentional blank lines**. Write flowing prose (semicolon-separated clauses are fine), not a document outline or multi-section manual.\n- The paragraph MUST cover, in prose: the discovered **file naming convention** (or a sensible default), the **file location** where implementation records live, and the **key content to include** (what changed and why, important files and design decisions, any API/configuration/database impacts, and brief usage or validation examples).\n- The paragraph SHOULD include **skip guidance**: skip implementation documentation for trivial, test-only, or docs-only changes where appropriate.\n\nKeep the scope to implementation records only; code-level documentation (docstrings, inline comments) belongs in correctness standards.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and write a short conservative standard; do not invent files, commands, or conventions.\n\nWrite the draft to `{docs_dir}/standards/documentation_instructions.md`.\n\n## Length Budget\n\nThe general platform ceiling for a configuration field is **40,000 characters**, but\n`documentation_instructions` has a stricter **effective limit of 1,500 characters** enforced by the\nserver for this field specifically, because the value is inlined verbatim into a generated plan step.\nThe uploaded draft must satisfy the 1,500-character limit — the 40,000-character ceiling is not the\nconstraint that applies here.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 1,500 characters, condense it: remove redundancy, tighten the prose, and drop\n examples before anything else.\n3. Keep all four required topics (naming convention, location, key content, skip guidance) and the\n single-paragraph form. Never meet the budget by dropping a required topic, and never truncate the\n paragraph mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 1,500 characters.\n\n## Return\n\nReturn a brief summary of what was learned about the project's implementation-record conventions (naming, location, required sections), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/documentation_instructions.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (1,500 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
901
901
  "learn-e2e-testing.md": "## Objective\n\nDetect whether an E2E testing framework exists in the codebase, document how to run and write E2E tests, then draft `e2e_testing_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Detect E2E Framework\n\nSearch for config files and indicators:\n- **Playwright**: Search for `playwright.config.ts`, `playwright.config.js`, `@playwright/test` in `package.json`\n- **Cypress**: Search for `cypress.json`, `cypress.config.*`, `cypress/` directory\n- **Selenium/WebDriver**: Search for `selenium` in `requirements.in` or `package.json`\n- **Puppeteer**: Search for `puppeteer` in `package.json`\n- **TestCafe**: Search for `.testcaferc.json`\n\nAlso read `package.json` for E2E-related scripts and search for test directories containing E2E tests.\n\nIf NO E2E testing framework is detected, write \"No E2E testing framework detected in this repository.\" to `{docs_dir}/standards/e2e_testing_instructions.md` and stop.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and write a short conservative standard; do not invent files, commands, or conventions.\n\n### Phase 2 — Explore E2E Testing Conventions\n\n1. **Test Execution**: Read the E2E config file and `package.json` scripts to determine exact commands (all tests, single file, headed/headless), prerequisites (server running, database seeded), and environment requirements.\n\n2. **Test Patterns**: Read 2-3 representative E2E test files in `tests/playwright/` to identify structure (page objects, fixtures, helpers), login/auth flows, test data setup/teardown, async waiting strategies, and selector patterns.\n\n3. **Common Pitfalls**: Search for hard-coded waits (`setTimeout`, `page.waitForTimeout`), test isolation issues, and browser state management patterns across E2E test files.\n\n### Phase 3 — Draft\n\nDraft `e2e_testing_instructions` as clear, actionable instructions for an AI agent writing E2E tests. Cover:\n- How to run tests (exact commands, prerequisites)\n- Test structure and organization\n- Authentication and setup patterns\n- How to wait for async operations (never hard-coded sleeps)\n- Common pitfalls with browser automation\n- Guards against common AI weaknesses: flaky tests, brittle selectors, hard-coded waits\n\nWrite the draft to `{docs_dir}/standards/e2e_testing_instructions.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``e2e_testing_instructions`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about the project's E2E testing setup (framework detected, run commands, test patterns) — or state that no framework was detected — citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/e2e_testing_instructions.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
902
902
  "learn-frontend-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for frontend code, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `frontend_correctness`\n- **Field name**: `frontend_correctness_standards`\n- **Scope**: JS, TS, JSX, TSX files: React/Vue/Angular/Svelte components, client-side logic, state management.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.js`, `**/*.ts`, `**/*.jsx`, `**/*.tsx` (excluding `node_modules/` and `build/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative frontend files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/frontend_correctness_standards.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``frontend_correctness_standards`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about frontend correctness conventions (structure, naming, framework idioms), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/frontend_correctness_standards.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
903
- "learn-repository-confirmations.md": "## Objective\n\nApply the configuration fields that the server requires a human to confirm — in **one** batched round\nat the very end of the run, after everything that could be applied unattended already has been.\n\nFields carrying `requires_confirmation: true` can never be written on derivation alone: the\nrequirement is a server-side registry property, not a client-side courtesy. This task is where the\nhuman is asked, exactly once, with the full run behind them.\n\n## Operating rules\n\n1. **One question, not one per field.** Every candidate is presented together in a single round.\n2. **Only explicitly approved values are applied.** Silence is not approval.\n3. **Never stall.** If no human response can be obtained, omit the unconfirmed fields, report them,\n and finish successfully.\n4. **This step never invalidates the run.** The learned fields were already uploaded by earlier\n steps. A decline, a rejection, or a conflict here is a reportable outcome for that one field — it\n never undoes or discards anything already applied.\n\n## Step 1 — Read the manifest once\n\n1. Call the `get_install_manifest` MCP tool **exactly once**, with `save_locally: false` — this is a\n confirmation round, not an install artifact worth writing to disk.\n2. Keep the returned `snapshot_token` **verbatim**. The apply call in Step 4 must present that same\n token. Do not re-read the manifest before applying; a fresh read invalidates the token you are\n about to use.\n3. Select candidates from the manifest's field groups using the manifest's own metadata:\n - the field's `requires_confirmation` is `true`, **and**\n - the field's `is_set` is `false`.\n\n A confirmation-required field that is already set is **not** a candidate: record it as\n `skipped_existing` and leave it alone. Do not maintain your own list of which fields require\n confirmation — the manifest is the authority, so a field the server adds later is picked up here\n with no change to this instruction.\n\n## Step 2 — Derive a proposed value for each candidate\n\n### `selected_mcp_slugs`\n\nMCP validation manuals are supplied to the final plan reviewer. Propose them **only** from clear\nrepository markers, and record the evidence you used for each:\n\n| Evidence in the repository | Proposed slug |\n| --- | --- |\n| SFCC cartridges, or Salesforce Commerce Cloud markers | `b2c-commerce-developer` |\n| A Playwright configuration file | `playwright-mcp` |\n| PWA Kit markers | `pwa-kit-mcp` |\n\nRules:\n\n- Every proposed slug must come from the manifest's supported values for the field. Never propose a\n slug that is not in the catalog — the server rejects unknown slugs outright.\n- Every proposed slug needs concrete evidence, named in the question. Cite the file or marker.\n- **No clear marker means the field is `not_applicable`** — report it as such and omit it from the\n question. Do not guess, and do not propose a manual on weak evidence: a wrong manual degrades the\n reviewer's behavior.\n\n### `project_description`\n\nIf `project_description` is still unset after install, derive a concise candidate description from the\nlocal codebase, following the field's manifest `guidance`. If it is already set, it is\n`skipped_existing`.\n\n### Any other confirmation-required candidate\n\nFor a candidate this instruction does not name explicitly, follow the field's manifest `guidance` to\nderive a value, and apply the same rule: no clear evidence means omit it rather than guess.\n\n## Step 3 — Ask once\n\nPresent **all** candidates in a **single** batched question. Separate per-field confirmation rounds\nare prohibited — the entire point of this step is that the human is interrupted exactly once.\n\nFor each candidate include:\n\n- the **field name**,\n- the **proposed value**,\n- the **evidence** behind the proposal, and\n- the **impact** of setting it — what changes about Bridge's behavior once the value is applied.\n\nLet the human approve or decline each field individually within that one round.\n\n## Step 4 — Apply the approved fields (one call)\n\n1. Make **at most one** `apply_install_manifest` call, passing the exact `snapshot_token` from Step 1.\n2. Submit every approved field in the `fields` map as `{ \"value\": <approved value>, \"confirmed\": true }`.\n The `confirmed: true` metadata is what satisfies the server's confirmation requirement.\n3. **Omit** declined fields and unanswered fields entirely — do not send them with `confirmed: false`,\n and do not send a placeholder value.\n4. If no field was approved, make no apply call at all.\n5. The apply is partial-tolerant. A field returned in the `rejected` bucket, or in the `conflict`\n bucket because it changed since the manifest was read, is reported as that field's outcome — the\n other approved fields still commit, and the learned fields uploaded earlier are untouched. Do not\n retry the whole apply because one field failed.\n\n## Step 5 — Headless safety\n\nIf interaction is unavailable, or no response can be obtained (a non-interactive or headless\nsession):\n\n- Do **not** wait indefinitely and do **not** abandon the run.\n- Omit **every** unconfirmed field from the apply.\n- Report each candidate as `pending human input`, using that exact phrase.\n- Finish successfully. Everything else the run applied stays applied — an unconfirmed field never\n blocks a confirmed or already-uploaded one.\n\n## Return\n\nReturn a single JSON object listing each field name in its outcome bucket:\n\n```json\n{\n \"approved\": [],\n \"applied\": [],\n \"declined\": [],\n \"pending_human_input\": [],\n \"not_applicable\": [],\n \"skipped_existing\": [],\n \"conflict\": [],\n \"rejected\": []\n}\n```\n\n- `approved` — the human approved the proposed value this round.\n- `applied` — the server committed the value.\n- `declined` — the human explicitly rejected the proposal.\n- `pending_human_input` — presented but unanswered, or never presentable (headless).\n- `not_applicable` — no clear evidence supported a proposal, so none was made.\n- `skipped_existing` — already set; never re-proposed.\n- `conflict` — changed since the manifest snapshot was read.\n- `rejected` — failed server-side validation.\n",
903
+ "learn-repository-confirmations.md": "## Objective\n\nApply the configuration fields that the server requires a human to confirm — in **one** batched round\nat the very end of the run, after everything that could be applied unattended already has been.\n\nFields carrying `requires_confirmation: true` can never be written on derivation alone: the\nrequirement is a server-side registry property, not a client-side courtesy. This task is where the\nhuman is asked, exactly once, with the full run behind them.\n\nTwo of those fields describe the repository's MCP servers. `selected_mcp_slugs` picks Bridge's\ncurated validation manuals; `learned_mcp_guidance` holds repository-local generic guidance for the\nservers Bridge has no manual for. Both are fed by one local discovery pass, described in Step 2, that\nyou — the agent running this task, acting as the repository's own MCP client — perform entirely on\nthe local machine.\n\n## Operating rules\n\n1. **One question, not one per field.** Every candidate is presented together in a single round.\n2. **Only explicitly approved values are applied.** Silence is not approval.\n3. **Never stall.** If no human response can be obtained, omit the unconfirmed fields, report them,\n and finish successfully.\n4. **This step never invalidates the run.** The learned fields were already uploaded by earlier\n steps. A decline, a rejection, or a conflict here is a reportable outcome for that one field — it\n never undoes or discards anything already applied.\n5. **Discovery is local and read-only.** Every MCP server is enumerated, introspected, and described\n by the local agent. Bridge's backend must never call a third-party MCP server, and nothing that\n leaves this step may carry a credential.\n\n## Step 1 — Read the manifest once\n\n1. Call the `get_install_manifest` MCP tool **exactly once**, with `save_locally: false` — this is a\n confirmation round, not an install artifact worth writing to disk.\n2. Keep the returned `snapshot_token` **verbatim**. The apply call in Step 4 must present that same\n token. Do not re-read the manifest before applying; a fresh read invalidates the token you are\n about to use.\n3. Select candidates from the manifest's field groups using the manifest's own metadata:\n - the field's `requires_confirmation` is `true`, **and**\n - the field's `is_set` is `false`.\n\n A confirmation-required field that is already set is **not** a candidate: record it as\n `skipped_existing` and leave it alone. Do not maintain your own list of which fields require\n confirmation — the manifest is the authority, so a field the server adds later is picked up here\n with no change to this instruction.\n\n This rule is applied to `selected_mcp_slugs` and to `learned_mcp_guidance` **independently**. One\n of them being already set says nothing about the other: an already-set field is `skipped_existing`\n while its sibling can still be a candidate. Discovery (Step 2) still runs and its outcomes are still\n reported for a `skipped_existing` field, but that field is never merged into, appended to, cleared,\n or written by this task.\n\n## Step 2 — Derive a proposed value for each candidate\n\n### Discover the repository's configured MCP servers\n\nRun this discovery pass once, before deriving either MCP field. Its output — one de-duplicated,\nscrubbed entry per configured server — feeds both `selected_mcp_slugs` and `learned_mcp_guidance`\nand is reported under `mcp_discovery` in the Return.\n\n#### Sources: repository-local only\n\nInspect **only** these repository sources:\n\n- the repository-root `.mcp.json`,\n- the repository-root `.cursor/mcp.json`, and\n- repository dependency and configuration files — `package.json` dependencies and similar\n dependency declarations, plus other safe configuration markers checked into the repository.\n\nUser-level and global MCP configurations (a home-directory or editor-wide MCP registration, a\nper-user settings file) are **explicitly excluded**: they describe the person's machine, not the\nrepository, and must never be inspected or cited.\n\n`.mcp.json` and `.cursor/mcp.json` are **independent evidence sources**:\n\n- A **missing** file contributes no evidence. It is not an error and is not reported as one.\n- An **unreadable or malformed** file produces one **non-blocking sanitized warning** naming only its\n safe repository-relative path and its condition (for example `.cursor/mcp.json: malformed JSON`).\n Never quote the file's content in the warning.\n- When any one source is unavailable, discovery **continues** through the other registration file,\n the dependency declarations, and the safe configuration markers. Never infer that the repository\n has no MCP servers merely because one source could not be used.\n\n#### Server identity, de-duplication, and conflicts\n\n- The **configured server key** — the key under which the server is registered — is the server's\n identity.\n- Equivalent registrations of the same server in both project files are **de-duplicated into one\n entry** that retains both safe source paths as evidence.\n- Two registrations under the same key whose **non-secret metadata conflicts** (a different package,\n command, or transport kind) become **one conflict-marked entry**. Do not call `tools/list` for a\n conflict-marked entry, and never persist anything for it unless the human explicitly approves the\n conflict-marked proposal in the batched round of Step 3.\n\n#### Introspection is the local agent's job\n\n- For each de-duplicated entry that is **reachable**, you — acting as the MCP client, through the\n repository's own configuration — call that server's `tools/list` **at most once**, and record only a\n **bounded summary**: the server key or a safe label, the tool names, and short non-sensitive\n descriptions.\n- **Bridge's backend must never call a third-party MCP server.** Introspection happens on the local\n machine only. There is no retry loop and no backend fallback: if the one `tools/list` attempt does\n not answer, the server is unreachable for this run.\n- An **unreachable** server is still a **successful discovery**. Record it with the exact outcome\n `unreachable; configuration-only evidence`, mark its tool metadata unavailable, and cite only its\n scrubbed configuration, dependency, or marker evidence. Unreachability never fails the learn run.\n- Never retain tool schemas, command arguments, transport settings, arbitrary server responses, or\n raw `tools/list` payloads — not in evidence, not in confirmation text, not in drafted guidance, not\n in diagnostics, and not in any persistence payload.\n\n#### Scrub boundary: nothing leaves this step with a secret in it\n\nBefore any discovered evidence is **presented**, **drafted** into guidance, or **submitted**, scrub\nit. Remove, without exception:\n\n- every environment value and every environment-variable reference (the `env` block of a\n registration, dollar-brace and bare-dollar variable references, and any value read from the process environment);\n- every token, key, and password field, whatever it is named;\n- `Authorization` headers and basic-auth data;\n- private keys;\n- connection strings;\n- credential-bearing URLs (a URL carrying a token, key, or user:password segment); and\n- any value that cannot be rendered safely.\n\nReplace anything removed with a high-visibility placeholder such as `[REDACTED_TOKEN]`, following the\n`gather-and-attach-materials` redaction precedent, and never echo the original. Retained evidence is\nlimited to: the server identity, safe repository-relative configuration paths, safe dependency and\nconfiguration markers, and bounded non-sensitive tool names with short descriptions. Nothing else\nsurvives the boundary.\n\n#### Dispatch: curated slug or generic guidance, decided deterministically\n\nRoute every discovered server to exactly one destination:\n\n1. **Curated** — the server's stable server key, package name, or command **exactly matches** a\n supported catalog slug listed in the manifest's supported values for `selected_mcp_slugs`. Propose\n that slug on the `selected_mcp_slugs` path below.\n2. **Generic** — every other server. Draft repository-local generic guidance on the\n `learned_mcp_guidance` path below.\n\nCurated matching is exact-identity only. **Heuristic name similarity, approximate slug matching, and\nLLM inference are prohibited** when choosing a curated slug: a near miss is a generic server, never a\nguessed slug. A curated match proposes **only the slug**; it never regenerates, copies, replaces, or\noverwrites the curated manual body, which stays Bridge's.\n\n### `selected_mcp_slugs`\n\nMCP validation manuals are supplied to the final plan reviewer. Propose them **only** from clear\nrepository markers, and record the evidence you used for each:\n\n| Evidence in the repository | Proposed slug |\n| --- | --- |\n| SFCC cartridges, or Salesforce Commerce Cloud markers | `b2c-commerce-developer` |\n| A Playwright configuration file | `playwright-mcp` |\n| PWA Kit markers | `pwa-kit-mcp` |\n\nThe table above is the explicit marker mapping. Beyond it, the general rule from the discovery pass\napplies to the **whole** curated catalog: a configured MCP server whose stable server key, package\nname, or command exactly matches a supported catalog slug is proposed as that slug, with the\nscrubbed discovery evidence cited. Any other configured server is **not** a curated candidate; it\nroutes to `learned_mcp_guidance`.\n\nRules:\n\n- Every proposed slug must come from the manifest's supported values for the field. Never propose a\n slug that is not in the catalog — the server rejects unknown slugs outright.\n- Every proposed slug needs concrete evidence, named in the question. Cite the file or marker, or the\n scrubbed discovery entry.\n- Curated mappings propose only the slug through `selected_mcp_slugs`. Never regenerate, copy, replace,\n or overwrite the curated manual body.\n- **No clear marker means the field is `not_applicable`** — report it as such and omit it from the\n question. Do not guess, and do not propose a manual on weak evidence: a wrong manual degrades the\n reviewer's behavior.\n- If `selected_mcp_slugs` is already set it is `skipped_existing`: discovery outcomes may still be\n reported, but the stored value is never merged, appended, cleared, or written by this task.\n\n### `learned_mcp_guidance`\n\nEach discovered server that the dispatch rule routed to **generic** gets one drafted entry. The entry's\nexact shape, field limits, and closed vocabularies are governed by the manifest's metadata for\n`learned_mcp_guidance` — read the field's `guidance` and `validation_summary` from the manifest you\nalready hold, and draft to them. Do not hard-code the field's object keys or its scope vocabulary\nfrom memory; the manifest is the authority, exactly as it is for candidate selection.\n\nEvery generic draft must, in prose:\n\n- explain **what the MCP is**;\n- summarize its **relevant capabilities**, from the bounded tool summary when the server was reachable\n and from the configuration and dependency markers otherwise;\n- identify its **concern** — the kind of work it bears on;\n- state **how and where it is useful** in this repository, so its usefulness is self-justifying rather\n than an opaque tag;\n- give a **ticket-scope classification** drawn from the manifest's vocabulary for the field, justified\n by the usefulness write-up (a Playwright-alternative browser-testing MCP is frontend-facing); and\n- cite **one concise scrubbed evidence summary**.\n\nConstraints on the draft:\n\n- Keep every value within the manifest's stated limits. Raw configuration, raw server responses,\n credential material, and environment references never appear in guidance or evidence.\n- Operational discovery metadata — reachability flags, tool collections, configuration paths — must\n **not** be added as ad hoc persisted object keys. Safe source-path information belongs only inside\n the concise evidence content the manifest permits.\n- A conflict-marked server's draft is labeled as such and is never persisted without explicit approval.\n- If `learned_mcp_guidance` is already set it is `skipped_existing`: discovery outcomes may still be\n reported, but the stored value is never merged, appended, cleared, or written by this task.\n- If the manifest does not carry `learned_mcp_guidance` at all, Step 1's rule holds: the field is not\n a candidate, nothing is submitted for it, and each generic draft is reported in `mcp_discovery` with\n outcome `field_unavailable` so the discovery is not lost.\n\n### `project_description`\n\nIf `project_description` is still unset after install, derive a concise candidate description from the\nlocal codebase, following the field's manifest `guidance`. If it is already set, it is\n`skipped_existing`.\n\n### Any other confirmation-required candidate\n\nFor a candidate this instruction does not name explicitly, follow the field's manifest `guidance` to\nderive a value, and apply the same rule: no clear evidence means omit it rather than guess.\n\n## Step 3 — Ask once\n\nPresent **all** candidates in a **single** batched question. Separate per-field confirmation rounds\nare prohibited — the entire point of this step is that the human is interrupted exactly once.\n\nCurated-slug proposals, generic-guidance drafts, and every other confirmation-required candidate\nappear together in that one question. There is no second round: an entry that goes unanswered stays\nunapproved and is never inferred as accepted.\n\nFor each candidate include:\n\n- the **field name**,\n- the **proposed value**,\n- the **evidence** behind the proposal, and\n- the **impact** of setting it — what changes about Bridge's behavior once the value is applied.\n\nFor each discovered MCP proposal — a curated slug or a generic draft — also include:\n\n- its **destination field** (`selected_mcp_slugs` or `learned_mcp_guidance`),\n- the **proposed value or draft** in full,\n- the **scrubbed cited evidence**,\n- the **dispatch reason** — the exact curated identity match, or \"no exact curated match\", and\n- an explicit label when the entry is **conflict-marked** or **unreachable**, using the exact wording\n `unreachable; configuration-only evidence` for the latter.\n\nLet the human approve or decline each field individually within that one round — and, within the two\nMCP fields, approve or decline **each discovered server's entry individually**. Approval is per entry,\nnever per field-level array.\n\nSanitized warnings about an unreadable or malformed source are shown alongside, clearly separate from\nthe approvable entries: a warning never fabricates a discovered server and never suppresses evidence\ngathered from the healthy sources.\n\n## Step 4 — Apply the approved fields (one call)\n\n1. Make **at most one** `apply_install_manifest` call, passing the exact `snapshot_token` from Step 1.\n2. Submit every approved field in the `fields` map as `{ \"value\": <approved value>, \"confirmed\": true }`.\n The `confirmed: true` metadata is what satisfies the server's confirmation requirement.\n3. **Omit** declined fields and unanswered fields entirely — do not send them with `confirmed: false`,\n and do not send a placeholder value.\n4. If no field was approved, make no apply call at all.\n5. The apply is partial-tolerant. A field returned in the `rejected` bucket, or in the `conflict`\n bucket because it changed since the manifest was read, is reported as that field's outcome — the\n other approved fields still commit, and the learned fields uploaded earlier are untouched. Do not\n retry the whole apply because one field failed.\n\nFor the two MCP fields, build each array from the per-entry answers:\n\n- `selected_mcp_slugs` is built **solely** from the explicitly approved curated entries.\n- `learned_mcp_guidance` is built **solely** from the explicitly approved generic entries, each in the\n manifest-defined entry shape and within its validation limits.\n- Every declined or unanswered entry is **excluded** from its array — no placeholder, no\n `confirmed: false`.\n- Each non-empty approved subset is submitted as `{ \"value\": <approved subset>, \"confirmed\": true }`\n in the same `fields` map, alongside any other approved confirmation-required field, in the single\n call above with the unchanged snapshot token.\n- When a field's approved subset is empty — including when every proposal for it was declined or\n unanswered — **omit that field** from the call.\n- Server-side validation remains the final boundary: it enforces the supported curated slugs, the\n generic-guidance shape, the confirmation requirement, and snapshot conflicts. This task adds no\n manifest, registry, backend, or catalog-exposure change to get around it.\n\n## Step 5 — Headless safety\n\nIf interaction is unavailable, or no response can be obtained (a non-interactive or headless\nsession):\n\n- Do **not** wait indefinitely and do **not** abandon the run.\n- Omit **every** unconfirmed field from the apply.\n- Report each candidate as `pending human input`, using that exact phrase.\n- Finish successfully. Everything else the run applied stays applied — an unconfirmed field never\n blocks a confirmed or already-uploaded one.\n\nEvery unconfirmed MCP proposal — each curated slug and each generic draft — is reported as\n`pending human input` too. An unset `selected_mcp_slugs` or `learned_mcp_guidance` whose entries could\nnot be confirmed lands in `pending_human_input`; an already-set one stays `skipped_existing`. A headless\nrun issues **no** apply call for those unconfirmed fields, performs no retry and no backend fallback,\nand still finishes successfully.\n\n## Return\n\nReturn a single JSON object listing each field name in its outcome bucket, plus one `mcp_discovery`\narray describing the discovery pass:\n\n```json\n{\n \"approved\": [],\n \"applied\": [],\n \"declined\": [],\n \"pending_human_input\": [],\n \"not_applicable\": [],\n \"skipped_existing\": [],\n \"conflict\": [],\n \"rejected\": [],\n \"mcp_discovery\": []\n}\n```\n\n- `approved` — the human approved the proposed value this round.\n- `applied` — the server committed the value.\n- `declined` — the human explicitly rejected the proposal.\n- `pending_human_input` — presented but unanswered, or never presentable (headless).\n- `not_applicable` — no clear evidence supported a proposal, so none was made.\n- `skipped_existing` — already set; never re-proposed.\n- `conflict` — changed since the manifest snapshot was read.\n- `rejected` — failed server-side validation.\n\n`mcp_discovery` carries **one entry per de-duplicated discovered server**. Each entry names the server\nkey, its **routing** (`curated` with the matched slug, or `generic`), its **outcome** — `approved`,\n`declined`, `pending human input`, `skipped_existing`, `conflict-marked`,\n`unreachable; configuration-only evidence`, or `field_unavailable` — and its bounded, scrubbed\nevidence: safe source paths, safe markers, and non-sensitive tool names only. The eight field buckets\nabove are unchanged; `mcp_discovery` is additive and is the only place discovery detail is reported.\n",
904
904
  "learn-repository-fan-out.md": "## Objective\n\nResearch all ten learned configuration fields for this repository **in parallel**, one isolated\nsubagent per field, and leave a bounded, upload-ready draft on disk for each field that needs one.\n\nThis step performs research only. It never uploads: the ten declarative `config_field` update steps\nthat follow this task own every write.\n\n## Operating rules (apply to the whole task)\n\n1. **Local research only.** Every field is learned by reading *this checkout* on local disk. Do not\n delegate research to the Bridge API backend or any server-side index — the backend has a vector\n index of the code, not the working tree in front of you.\n2. **One isolated subagent per missing field.** Use the host coding agent's own subagent/task\n mechanism. Each subagent gets exactly one field and does not see the others' work.\n3. **Launch before you await.** Start every missing-field subagent first, then collect results. Never\n spawn a subagent, wait for it to finish, and only then spawn the next one — a sequential\n spawn-and-wait loop defeats the entire purpose of this step.\n4. **Subagents never upload and never talk to the human.** A subagent's only outputs are its draft\n file and its structured result. All human interaction is deferred to the final confirmation step.\n5. **A failed field is not a failed run.** Isolate each failure to its own field, let the other\n subagents finish, and return a successful aggregate result naming the gap.\n\n## Step 1 — Classify each field from the ten preceding fetch results\n\nThe ten `config_field` steps immediately before this task already read the current value of every\nlearned field. Use **those results** — do not re-read the fields and do not infer state from whether\na draft file happens to exist on disk from an earlier run.\n\nFor each field, read the fetch response's `value` and `is_catalog_default` properties. Those are the\nauthoritative signals:\n\n- `value` is `null`, absent, or contains only whitespace → the field is **missing** (needs research).\n- `is_catalog_default` is `true` → the field is **missing** (needs research). The stored content is an\n unmodified Bridge-seeded catalog default, not learned knowledge; the server's guarded write permits\n replacing it while still protecting user-edited or previously learned content.\n- `value` holds any other content → the field is **populated** (already learned).\n\nIf a fetch step warned and returned no usable response at all, treat that field as **missing**.\n\nRecord every populated field as `skipped_existing` and do not spawn a subagent for it. This is what\nmakes a rerun after a partial failure cheap: only the fields that are still empty are researched\nagain.\n\n## Step 2 — Prepare the artifact targets (before any subagent starts)\n\nThe upload steps that follow this task read a fixed file path per field. Stale content at one of\nthose paths would be uploaded as if it were this run's work, so:\n\n1. For every **missing** field, delete any existing file at its draft path. A field whose subagent\n later fails must leave *no* file behind, so the declarative upload has nothing to send.\n2. For every **skipped_existing** field, write the value you just fetched to that field's draft path,\n so the unchanged upload step has a readable file. The upload carries `only_if_null: true`, so the\n server skips the write for learned or user-edited content — and if the value is a still-default\n catalog seed, re-sending the same bytes is a harmless self-copy under the widened\n null-or-default guard.\n\n## Step 3 — Fan out\n\nLaunch one subagent for every missing field, all of them, before awaiting any result. Then await all\nof them and collect each result.\n\nGive each subagent the prompt block for its field from the matrix below. Every prompt block already\ncarries the field name, the draft path, the research responsibilities, the evidence expectations, and\nthe character budget. Pass each block through verbatim, substituting `{docs_dir}` with the resolved\ndocs directory.\n\n### Shared subagent contract\n\nInclude this contract in every subagent prompt, in addition to the field-specific block:\n\n\"\"\"\nYou are researching exactly one configuration field for this repository. Read the local checkout on\ndisk — the actual files, not a summary of them. Cite concrete file paths as evidence for every claim\nyou make; a pattern you cannot point at a file for does not belong in the draft.\n\nWrite your finished draft to the draft path given below, and nothing else. Do not call any\nconfiguration-update tool, do not upload your draft anywhere, and do not ask the human any question:\nanother step owns writing and another step owns asking.\n\n**Length budget — this is a hard limit, measured in characters.**\n\n- Your draft must be at most the maximum character count stated in your field block below, measured\n in **characters** (not tokens, not words, not bytes).\n- When your draft is complete, measure its length in characters.\n- If it exceeds the budget, condense it: remove redundancy, collapse repetitive passages, and shorten\n or drop verbose code examples. Keep every required section — never meet the budget by deleting a\n required section, and never truncate mid-sentence.\n- Re-measure after condensing, and repeat until the draft is within budget.\n- The budget is enforced by the server at upload time, so a draft over the limit is rejected outright\n and the field is simply not learned. Condensing is how the field gets learned at all.\n\nReturn a JSON object:\n`{\"field_name\": \"...\", \"draft_path\": \"...\", \"character_count\": <int>, \"max_character_count\": <int>,\n\"condensed\": <bool>, \"condensation_reason\": \"<why you condensed, or empty>\"}`\n\"\"\"\n\n### Field matrix\n\nTen fields. Each row names the field, its draft path, its character budget, and the research\nresponsibilities to expand into that subagent's prompt block. The responsibilities mirror the\nmatching standalone `learn-*.md` instruction, which remains the fuller reference for that field.\n\n| Field | Draft path | Max characters | Mirrors |\n| --- | --- | --- | --- |\n| `architecture_instructions` | `{docs_dir}/standards/architecture_instructions.md` | 40000 | `learn-architecture.md` |\n| `review_instructions` | `{docs_dir}/standards/review_instructions.md` | 40000 | `learn-review-instructions.md` |\n| `documentation_instructions` | `{docs_dir}/standards/documentation_instructions.md` | 1500 | `learn-documentation-instructions.md` |\n| `unit_testing_instructions` | `{docs_dir}/standards/unit_testing_instructions.md` | 40000 | `learn-unit-testing.md` |\n| `e2e_testing_instructions` | `{docs_dir}/standards/e2e_testing_instructions.md` | 40000 | `learn-e2e-testing.md` |\n| `frontend_correctness_standards` | `{docs_dir}/standards/frontend_correctness_standards.md` | 40000 | `learn-frontend-correctness.md` |\n| `backend_correctness_standards` | `{docs_dir}/standards/backend_correctness_standards.md` | 40000 | `learn-backend-correctness.md` |\n| `template_correctness_standards` | `{docs_dir}/standards/template_correctness_standards.md` | 40000 | `learn-template-correctness.md` |\n| `style_correctness_standards` | `{docs_dir}/standards/style_correctness_standards.md` | 40000 | `learn-style-correctness.md` |\n| `design_principles` | `{docs_dir}/standards/design_principles.md` | 40000 | `learn-design-principles.md` |\n\n#### `architecture_instructions` — max 40000 characters\n\n\"\"\"\nDraft `architecture_instructions` to `{docs_dir}/standards/architecture_instructions.md`, at most\n40000 characters.\n\nExplore the codebase for architectural principles, directory conventions, design patterns, and data\nflow. Examine at least 5 representative files per area and cite file paths for every pattern.\nClassify each pattern's evidence level as `ENFORCED` (violations would be bugs), `CONVENTION`\n(commonly observed, deviations exist), or `ASPIRATIONAL` (intended, not yet consistent).\n\nCover: architectural coding patterns and layer boundaries; design patterns (factories, strategies,\nmiddleware, registries, dependency injection); dependency management; error-handling architecture;\nconfiguration management; tech-stack detection; security/auth architecture; and agent prompting\nconventions. Trace 2-3 complete request paths end to end. Do NOT document testing patterns — skip the\ntests directory entirely.\n\nRequired sections: 1. Core Principles; 2. Layered Architecture; 3. Directory Conventions; 4. Data\nFlow Patterns; 5. Technical Standards; 6. Error Handling & Monitoring; 7. Security & Authentication;\n8. Agent Prompting Conventions; 9. Integration Points; 10. AI Code Generation Guidelines.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and\nwrite a short conservative standard; do not invent files, commands, or conventions.\n\"\"\"\n\n#### `review_instructions` — max 40000 characters\n\n\"\"\"\nDraft `review_instructions` to `{docs_dir}/standards/review_instructions.md`, at most 40000\ncharacters.\n\nFollow the responsibilities in `learn-review-instructions.md`: research what this repository's code\nreviewers actually enforce, and turn it into instructions the AI code reviewer can apply. Cite file\npaths as evidence for every rule, and preserve that instruction's required sections.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and\nwrite a short conservative standard; do not invent files, commands, or conventions.\n\"\"\"\n\n#### `documentation_instructions` — max 1500 characters\n\n\"\"\"\nDraft `documentation_instructions` to `{docs_dir}/standards/documentation_instructions.md`, at most\n**1500 characters**.\n\nThis field's limit is far stricter than the platform's general 40000-character ceiling, because the\nserver enforces 1500 characters for this field specifically and the value is inlined verbatim into a\ngenerated plan step.\n\nProduce **a single concise prose paragraph**: no markdown headings, no bullets, no numbered lists, no\nintentional blank lines. The paragraph must cover the implementation-document file naming convention,\nwhere documentation lives, the key content to capture, and when to skip documentation.\n\nBecause 1500 characters is tight, expect to condense. Cut redundancy and examples first; keep all\nfour required topics.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and\nwrite a short conservative standard; do not invent files, commands, or conventions.\n\"\"\"\n\n#### `unit_testing_instructions` — max 40000 characters\n\n\"\"\"\nDraft `unit_testing_instructions` to `{docs_dir}/standards/unit_testing_instructions.md`, at most\n40000 characters.\n\nFollow the responsibilities in `learn-unit-testing.md`: research this repository's unit-testing\nconventions — framework, layout, fixtures, mocking boundaries, and assertion style — and cite file\npaths for every convention. Preserve that instruction's required sections.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and\nwrite a short conservative standard; do not invent files, commands, or conventions.\n\"\"\"\n\n#### `e2e_testing_instructions` — max 40000 characters\n\n\"\"\"\nDraft `e2e_testing_instructions` to `{docs_dir}/standards/e2e_testing_instructions.md`, at most 40000\ncharacters.\n\nFollow the responsibilities in `learn-e2e-testing.md`: research this repository's end-to-end testing\nconventions — runner, prerequisites, page/selector patterns, and how tests are executed — and cite\nfile paths for every convention. Preserve that instruction's required sections.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and\nwrite a short conservative standard; do not invent files, commands, or conventions.\n\"\"\"\n\n#### `frontend_correctness_standards` — max 40000 characters\n\n\"\"\"\nDraft `frontend_correctness_standards` to\n`{docs_dir}/standards/frontend_correctness_standards.md`, at most 40000 characters.\n\nFollow the responsibilities in `learn-frontend-correctness.md`: research the correctness rules that\napply to this repository's frontend code and cite file paths as evidence. Preserve that\ninstruction's required sections. If the repository has no frontend code, say so explicitly rather\nthan inventing standards.\n\"\"\"\n\n#### `backend_correctness_standards` — max 40000 characters\n\n\"\"\"\nDraft `backend_correctness_standards` to `{docs_dir}/standards/backend_correctness_standards.md`, at\nmost 40000 characters.\n\nFollow the responsibilities in `learn-backend-correctness.md`: research the correctness rules that\napply to this repository's backend code and cite file paths as evidence. Preserve that instruction's\nrequired sections.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and\nwrite a short conservative standard; do not invent files, commands, or conventions.\n\"\"\"\n\n#### `template_correctness_standards` — max 40000 characters\n\n\"\"\"\nDraft `template_correctness_standards` to `{docs_dir}/standards/template_correctness_standards.md`,\nat most 40000 characters.\n\nFollow the responsibilities in `learn-template-correctness.md`: research the correctness rules that\napply to this repository's template files and cite file paths as evidence. Preserve that\ninstruction's required sections. If the repository has no templates, say so explicitly rather than\ninventing standards.\n\"\"\"\n\n#### `style_correctness_standards` — max 40000 characters\n\n\"\"\"\nDraft `style_correctness_standards` to `{docs_dir}/standards/style_correctness_standards.md`, at most\n40000 characters.\n\nFollow the responsibilities in `learn-style-correctness.md`: research the style and formatting rules\nthat apply to this repository's styling files and cite file paths as evidence. Preserve that\ninstruction's required sections. If the repository has no styling files, say so explicitly rather\nthan inventing standards.\n\"\"\"\n\n#### `design_principles` — max 40000 characters\n\n\"\"\"\nDraft `design_principles` to `{docs_dir}/standards/design_principles.md`, at most 40000 characters.\n\nFollow the responsibilities in `learn-design-principles.md`: research this repository's design\ntokens, component inventory, layout patterns, and composition rules, and cite file paths as evidence.\nPreserve that instruction's required sections. If the repository has no user interface, say so\nexplicitly rather than inventing principles.\n\"\"\"\n\n## Step 4 — Verify each result before marking it upload-ready\n\nFor every subagent that returned successfully:\n\n1. Confirm the draft file exists at the field's draft path.\n2. Measure the file's length in characters **yourself**. Do not trust the subagent's reported count —\n an oversized draft handed to the declarative upload is rejected by the server, and the field is\n silently not learned.\n3. If the file is within the field's budget, mark the field `drafted`.\n4. If the file is still over budget, condense it yourself to fit — preserving its required sections —\n then re-measure. If you cannot bring it within budget, delete the draft file and record the field\n as `failed` with the reason, so the upload sends nothing rather than something the server rejects.\n\nFor every subagent that failed, errored, or returned nothing usable: leave the draft path absent,\nrecord the field as `failed` with a short sanitized reason (no credentials, tokens, URLs, or raw\nstack traces), and continue. Do not abandon the other fields and do not fail this task.\n\n## Return\n\nReturn a single JSON object. This task succeeds even when some fields failed — the summary is how a\ngap gets reported, not an exception.\n\n```json\n{\n \"fields\": [\n {\n \"field_name\": \"architecture_instructions\",\n \"status\": \"drafted\",\n \"draft_path\": \"{docs_dir}/standards/architecture_instructions.md\",\n \"character_count\": 18240,\n \"max_character_count\": 40000,\n \"condensed\": false,\n \"condensation_reason\": \"\",\n \"failure_reason\": \"\"\n }\n ],\n \"drafted\": [\"architecture_instructions\"],\n \"skipped_existing\": [],\n \"condensed\": [],\n \"failed\": []\n}\n```\n\nRules for the return value:\n\n- `status` is exactly one of `drafted`, `skipped_existing`, or `failed`.\n- Every one of the ten fields appears exactly once in `fields`.\n- `condensed` is `true` only when the draft was shortened to meet the budget; `condensation_reason`\n explains why whenever `condensed` is `true`.\n- `failure_reason` is populated only for `failed` fields and is sanitized.\n- The four aggregate arrays list the field names in each outcome, so the command's closing summary\n can report them without re-deriving them from prose.\n",
905
905
  "learn-review-instructions.md": "## Objective\n\nExplore the codebase to identify self-verification patterns, downstream impact analysis techniques, and local validation tooling, then draft `review_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Explore Self-Verification Patterns\n\nFocus on how an AI agent working in a code editor (with capabilities to search file contents by text pattern, search by filename pattern, read files, and call MCP tools) can verify its own code changes before requesting human review. Do NOT document test runners or CI/CD — focus on static analysis by reading code and searching for patterns.\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and write a short conservative standard; do not invent files, commands, or conventions.\n\n1. **Code Correctness Patterns**: Read 3-5 representative modules in `api/routes/` and `api/library/` to identify:\n - Function signature conventions (return types, parameter patterns)\n - Import conventions and layer boundaries (deprecated modules, import restrictions)\n - Return value handling (structured results, tuple unpacking)\n - Auth pattern compliance (required decorators, dependency injections, call order)\n - Naming conventions (files, functions, classes, variables)\n - Error handling patterns (try/except structure, ordering, logging)\n\n2. **Downstream Impact Analysis**: For each technique, demonstrate with a concrete codebase example:\n - Caller discovery (text-pattern search for finding all callers of utility functions)\n - Import graph analysis (finding all files importing from a module)\n - Route registration verification (checking new routes are properly included)\n - Database schema impact (finding queries referencing a given table/column)\n - Model/schema usage (verifying model changes don't break dependents)\n\n3. **Local Validation Tooling**: Discover available MCP tools and validation capabilities:\n - Database MCP tools (schema verification, query validation)\n - Project API MCP tools (config verification, health checks)\n - Hooks and guards (pre-commit hooks, pre-tool hooks)\n - Safety model (read-only vs. mutating operations)\n - Runtime smoke verification capability: Document which tools the executor can use to _run_ code safely (test runners, dbhub MCP, local dev servers, fixture loaders) and whether mutations are permitted against local/ephemeral state. The per-repo `allow_mutating_smoke_ops` flag (on `config_code_repositories`) controls whether the final reviewer is allowed to plan mutating verification steps.\n\n4. **Correctness Standards Integration**: Read files in `{docs_dir}/standards/` matching `*_correctness_standards.md`. Extract key verification checkpoints that can be statically verified.\n\n### Phase 2 — Draft\n\nDraft `review_instructions` with these required sections:\n1. **Self-Verification Checklist** — Concise, scannable checklist with concrete actions and tools.\n2. **Local Code Verification** — Detailed static analysis instructions (function calls, imports, auth, error handling, naming).\n3. **Downstream Effect Analysis** — Finding callers, checking signature compatibility, import tracking, schema impact, route registration.\n4. **Validation Using Local Tooling** — Database validation, project API validation, hooks and guards.\n5. **Correctness Standards Reference** — Distilled checkpoints from loaded standards, or placeholder paths.\n6. **Common AI Agent Mistakes** — Verification-framed guards against duplication, unnecessary abstraction, data leaks, edge cases.\n\nWrite the draft to `{docs_dir}/standards/review_instructions.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``review_instructions`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about the project's self-review and downstream-impact analysis patterns (verification checkpoints, local validation tooling), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/review_instructions.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
906
906
  "learn-style-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for style files, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `style_correctness`\n- **Field name**: `style_correctness_standards`\n- **Scope**: Style files: CSS, SCSS, SASS, LESS, Styled Components, Tailwind configs.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.css`, `**/*.scss`, `**/*.sass`, `**/*.less` (excluding `node_modules/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative style files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/style_correctness_standards.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``style_correctness_standards`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about style-file correctness conventions (structure, naming, methodology), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/style_correctness_standards.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
907
907
  "learn-template-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for template files, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `template_correctness`\n- **Field name**: `template_correctness_standards`\n- **Scope**: Template files: HTML, Jinja2, Handlebars, EJS, ERB, Blade, Pug, Twig.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.html`, `**/*.jinja2`, `**/*.j2` in `templates/` and similar directories (excluding `node_modules/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative template files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/template_correctness_standards.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``template_correctness_standards`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about template-file correctness conventions (structure, naming, framework idioms), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/template_correctness_standards.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
908
908
  "learn-unit-testing.md": "## Objective\n\nExplore the codebase to identify the test runner, assertion library, mocking framework, and testing patterns, then draft `unit_testing_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Explore Testing Infrastructure\n\n1. **Test Runner and Framework Detection**: Search for test runner configs (`pytest.ini`, `pyproject.toml` `[tool.pytest]` section, `jest.config.*`) and read `package.json` test scripts. Read the `tests/` directory structure.\n\n2. **Testing Patterns**: Read 3-5 representative test files in `tests/pytest/` to identify:\n - Assertion library and style (`assert`, `expect`, custom matchers)\n - Mocking framework (`unittest.mock`, `jest.mock`, `sinon`, etc.)\n - Fixture patterns (setup/teardown)\n - Test organization (by module, feature, layer)\n - Exemplary tests vs. weak tests\n\n3. **How to Run Tests**: Read `pyproject.toml`, `package.json`, and `Makefile` (if present) to determine exact commands for: full suite, single file, by name pattern, with verbose output.\n\n4. **Mocking vs. Fidelity**: Read test helper files in `tests/pytest/helpers/` to document how external APIs are mocked, whether integration tests exist alongside unit tests, and patterns for avoiding third-party calls in tests.\n\n### Phase 2 — Draft\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and write a short conservative standard; do not invent files, commands, or conventions.\n\nDraft `unit_testing_instructions` as clear, actionable instructions for an AI agent writing unit tests. Cover:\n- How to run tests (exact commands)\n- Which test framework and assertion library to use\n- How to mock external dependencies without calling third parties\n- How to structure test files and test functions\n- What constitutes a thorough test (not just happy path)\n- How to avoid shallow tests that pass but don't verify meaningful behavior\n- Guards against common AI weaknesses: tests that mock the thing being tested, trivially passing assertions, overly complex setup\n\nWrite the draft to `{docs_dir}/standards/unit_testing_instructions.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``unit_testing_instructions`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about the project's unit testing setup (test runner, assertion library, mocking framework, run commands), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/unit_testing_instructions.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
909
- "monitor-ci-checks.md": "Monitor CI checks for the most recent commit. The behavior is dispatched on the repo-specific `ci_followup_config` JSON value: `poll_only`, `fix_and_iterate`, or `custom`. Read this entire file once before doing anything, then follow only the matching branch.\n\n\n**Required-check source**: Both the `poll_only` (Step 5) and `fix_and_iterate` (Step 6) branches gate progression on the *required* check subset, not the aggregate `all_passed` flag. Each check returned by `resolve_ci_checks`/`poll_ci_checks` carries a `required` field (from GitHub Branch Protection, or an LLM classification fallback) — treat `required: false` as non-required (e.g. `pip-audit`) and a missing field or `required: true` as required. This is the tool-provided proxy for the Conductor done-gate's authoritative required-checks set (`mcp_server/src/conductor/done-gate.ts`); do not re-derive required/non-required status in prose.\n\n## How to wait (BAPI-1104)\n\n**Never wait with a shell `sleep`, and never poll in an unbounded foreground loop.** The\nClaude Code harness refuses a foreground `sleep` outright, so an instruction to sleep does\nnot slow a worker down — it kills the step. Wait with `wait_for_ci_checks` instead.\n\n`wait_for_ci_checks` takes `commit_ref` (and an optional `budget_ms`, default and maximum\n240000). One call occupies at most 240 seconds — comfortably inside the MCP client\ndeadline — and returns a JSON body with a `state` field:\n\n- `state: \"pending\"` — NORMAL. Some check is still running and the window closed. Call the\n tool again. This is the replacement for every \"wait N seconds and poll again\" instruction:\n each repeated call is one bounded window, and repeating is how you wait for longer.\n- `state: \"ready\"` — every reported check is **terminal**. **Terminal is not passing.** This\n tool never decides pass or fail; it reports each check's `name`, `required`, `status`,\n `conclusion` and `complete`, and the branches below decide `required_green` from them.\n- `state: \"unavailable\"` — CI check configuration is not resolved for this repository, so\n there is nothing to wait on. Do not keep calling. Call `resolve_ci_checks` for the current\n SHA and retry once; if it is still unavailable, stop and report unavailable status through\n the branch's existing unavailable-CI path.\n- `state: \"error\"` — terminal. Report the `reason` and stop waiting; further windows\n reproduce it.\n\nEach returned window counts toward the branch's own wall-clock budget (`max_minutes` where\nthe branch has one). Track elapsed time across repeated windows and escalate through the\nbranch's existing exit when the budget is spent — the budget is enforced here, in the\nrecipe, not by the tool.\n\n## Entry state and ownership boundary\n\n**Re-resolve the git context before anything else.** Run `git branch --show-current`\nand `git rev-parse HEAD` when this step begins. In the implement pipeline the pull\nrequest is opened *before* the bounded post-finalization verification phase, and\nthat phase may have pushed a correction on top of the commit the PR was originally\nopened at. The head you monitor must be the branch's current pushed head, not a SHA\ncarried over from PR creation.\n\n**Ownership is split, and the split matters.** The bounded verification phase\n(`verify-plan.md`) owns findings produced by its own local, touched-area commands.\nThis step owns everything subsequently reported by the authoritative `ci` and\n`code_review` gates — failing required checks and requested review changes. Do not\nre-adjudicate the other phase's findings, and do not assume a finding it reported\nhas been fixed unless a pushed commit shows it.\n\n**Do not substitute a broad local run for the authoritative checks.** Running the\nfull local suite here does not establish that CI passed; it duplicates the work the\n`conductor-ci` gate already performs on the pull request, and it is exactly the\nbudget sink this protocol was reordered to avoid. Use the structured CI failure\ndetail from `poll_ci_checks` to target a fix, and scope any local reproduction to\nthe failing area.\n\n**Keep every correction on the existing pull request branch**, and commit *and push*\neach correction before polling resumes. Polling always restarts against the new\npushed head — an unpushed correction is invisible to CI, to review, and to the\nreconciler.\n\nThroughout, report the check and review states you **observed**. Observing that a\ncheck is green is not the same as issuing a verdict: the done-gate evaluation is\nserver-side and the reconciler decides. This step never emits a control signal of\nits own.\n\n## Step 3 — Parse `ci_followup_config`\n\nLook at the response from the immediately preceding `config_field` call (the pipeline step that ran right before this one). The response envelope's `value` field is itself a JSON string and must be parsed again with `JSON.parse` (i.e., the `value` is double-encoded — the outer envelope is JSON, and the inner `value` is a JSON-encoded string of the actual config object).\n\nIf ANY of the following hold, log a warning and use the defaults `{\"strategy\":\"poll_only\",\"max_iterations\":1,\"max_minutes\":10}`:\n\n- The `config_field` response is missing or unavailable (e.g., the step warned-and-continued).\n- The response `value` is `null`.\n- Parsing `value` with `JSON.parse` fails (the persisted text is not valid JSON).\n- The parsed result is not a JSON object.\n- One or more of the required keys (`strategy`, `max_iterations`, `max_minutes`, `instructions`) is missing.\n- `strategy` is not one of `poll_only`, `fix_and_iterate`, or `custom`.\n\n## Step 4 — Dispatch on `strategy`\n\nRead this whole file once and then follow only the matching branch:\n\n- `poll_only` → follow Step 5.\n- `fix_and_iterate` → follow Step 6.\n- `custom` → follow Step 7.\n\nIf `strategy` is unrecognized, log a warning and fall through to Step 5 (`poll_only`).\n\n## Step 5 — `poll_only`\n\nPreserve the baseline polling behavior. The configured `max_minutes` is IGNORED in this branch — `poll_only` always uses the existing 10-minute baseline.\n\n1. Run `git rev-parse HEAD` to get the current commit SHA.\n2. Call the `resolve_ci_checks` tool with `commit_ref` set to that SHA. This discovers and classifies the CI checks for the repository, including each check's `required` field.\n3. Poll CI status by calling `poll_ci_checks` with `commit_ref` set to the same SHA. Check the response for `all_complete`, and note each check's own `required`/green status — do not use the aggregate `all_passed` flag to decide pass/fail (see step 6 below).\n4. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the 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, obtain the new head SHA (`git rev-parse HEAD`), and restart polling from step 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 below.\n5. If checks are not yet complete, call `wait_for_ci_checks` with `commit_ref` set to the same SHA and repeat that call while it returns `state: \"pending\"`. Each call is one bounded 240-second window; repeat until it returns `state: \"ready\"` (every check terminal) or 10 minutes of accumulated windows have elapsed. On `state: \"unavailable\"` follow step 9 rather than continuing to wait.\n6. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green. If `required_green` is `true`, report success — non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility only and never flip the Passed/Failed classification.\n7. **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 and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` all mean the review is not yet approved and success is not yet reached. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n7a. **A `claude-review` failure blocked by the preflight is TERMINAL — never retry it.** When `claude-review` fails within about twelve seconds and no verdict comment was ever posted, read the run's `Preflight the review action` step. The review action refuses to run whenever this workflow differs from the repository's default-branch copy, and the preflight names which of the two causes it found:\n - `status=workflow_modified` — the pull request's own commits changed `.github/workflows/claude-review.yml`. Such a pull request must be reviewed and merged by hand; it can never be conductor-merged.\n - `status=base_workflow_stale` — the pull request changed no workflow file and **inherited** a stale copy from its base branch. The stale ref is the **base branch**, not the pull request.\n\n Both aggregate to `reason=review_action_blocked_by_workflow_modification`. In both cases the preflight is a deterministic blob-SHA comparison over unchanged inputs, so **retrying, re-running, or continuing to poll reproduces byte-identical evidence and can never succeed** — stop the retry loop and report it rather than spending the remaining budget. For the stale-base case the only remedy is to reconcile the base branch with the default branch; see `docs/claude/runbooks/claude-review-base-branch-drift.md` for detection, diagnosis, and both remediation paths.\n\n8. If any required checks fail, report which required checks failed (and any non-required failures for visibility) and include any available annotations or log details from the poll response. Do NOT attempt to fix failures — just report them clearly.\n9. If CI status is unavailable (`wait_for_ci_checks` returns `state: \"unavailable\"`, or the resolver/poll returns `available: false`), call `resolve_ci_checks` once for the current SHA and retry the wait; if it is still unavailable, report unavailable status and exit. Do not attempt fixes and do not keep waiting.\n10. If the 10-minute timeout is reached, report timeout and exit.\n\n### Polling Directive\n\nWait only through repeated `wait_for_ci_checks` calls — never a shell `sleep`, and never an\nunbounded foreground poll. Do NOT output any inline commentary, reasoning, or partial status\nupdates between windows. Only output a status message when:\n- All checks are terminal (`state: \"ready\"`), OR\n- CI is unavailable or the wait returns a terminal error, OR\n- The 10-minute accumulated budget is reached.\n\nThis minimizes context window consumption during long-running CI waits.\n\n## Step 6 — `fix_and_iterate`\n\nThis is a self-contained loop where `iteration` is the number of correction rounds already pushed and `start_time` is captured before the first iteration. `max_minutes` is the TOTAL wall-clock cap across all iterations, not an additional per-iteration budget. The 10-minute per-iteration CI-wait cap is INSIDE that total budget, and it is counted from the accumulated `wait_for_ci_checks` windows this iteration has already spent.\n\nInitialize:\n\n- `iteration = 0`\n- `start_time = now()`\n\nBefore starting each iteration AND before applying corrections, check the total wall-clock budget. If `now() - start_time >= max_minutes`, warn and exit.\n\nPer iteration:\n\n1. Run `git rev-parse HEAD` to get the current commit SHA. The previous push may have changed it; always read fresh.\n2. Run `git branch --show-current` to get the current branch. Always read fresh.\n3. Call `resolve_ci_checks` with `commit_ref` set to the current SHA (once per new SHA — the server caches per project but the agent should still call it for each new SHA). Each returned check carries a `required` field — this is the tool-provided proxy for the done-gate's authoritative required-checks set.\n4. Wait for terminality by calling `wait_for_ci_checks` with `commit_ref` set to the current SHA, repeating while it returns `state: \"pending\"`. Each call is one bounded 240-second window and every window counts against both budgets below. Stop when it returns `state: \"ready\"` (every check terminal), OR `state: \"unavailable\"` (go to step 6), OR `state: \"error\"`, OR the per-iteration 10-minute accumulated cap is reached, OR the remaining total wall-clock budget is exhausted. Use `poll_ci_checks` for the failure detail of a terminal check; it is the detail source, not the waiting mechanism.\n5. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the 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, obtain the new head SHA (`git rev-parse HEAD`), and restart polling from step 1 of this per-iteration block against that new SHA. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary per-iteration behavior below.\n6. If CI status is unavailable (`wait_for_ci_checks` returns `state: \"unavailable\"`, or `poll_ci_checks` returns `available: false`), call `resolve_ci_checks` once for the current SHA and retry the wait. If it is still unavailable, warn and exit the loop — automated remediation cannot make reliable progress without CI signals, and further waiting cannot produce one.\n7. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green; non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility but never gate exit condition 1 below. If `claude-review` is a required check, its GitHub check reaching a non-pending/\"success\" state is transport completion only, not approval — fetch the PR's comments and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` mean the review is not yet approved. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n7a. **A `claude-review` failure blocked by the preflight is TERMINAL — never spend a correction or a retry on it.** When `claude-review` fails within about twelve seconds and no verdict comment was ever posted, read the run's `Preflight the review action` step. The review action refuses to run whenever this workflow differs from the repository's default-branch copy, and the preflight names which of the two causes it found:\n - `status=workflow_modified` — the pull request's own commits changed `.github/workflows/claude-review.yml`. It must be reviewed and merged by hand; it can never be conductor-merged.\n - `status=base_workflow_stale` — the pull request changed no workflow file and **inherited** a stale copy from its base branch. The stale ref is the **base branch**, not the pull request, so no correction you can make on this branch will clear it.\n\n Both aggregate to `reason=review_action_blocked_by_workflow_modification`, and the preflight is a deterministic blob-SHA comparison over unchanged inputs — every iteration reproduces byte-identical evidence. Treat it as non-actionable, exit the loop with that reason reported, and do not consume the remaining iteration or wall-clock budget. Remedy for the stale-base case: reconcile the base branch with the default branch. See `docs/claude/runbooks/claude-review-base-branch-drift.md`.\n\n8. Apply repo-specific `instructions` ONLY when the `instructions` field is non-empty. If the repo `instructions` reference templated placeholder tokens for the GitHub owner, repo, or PR number — e.g., the literal tokens written as a left brace, the word `owner`/`repo`/`pr`, then a right brace — resolve them from the local git/VCS context. Use `gh pr list --head <branch> --json number` to get the PR number; parse the remote URL (`git config --get remote.origin.url`) for owner/repo. If `instructions` is empty, skip repo-specific signal gathering and use only structured CI failure information.\n\n9. Evaluate exit conditions in this order:\n 1. `required_green` is true AND (if `claude-review` is required) the verdict token confirms approval for the current head AND any repo-specific exit criteria from `instructions` are met → success. If there are no repo-specific exit criteria, `required_green` (plus verdict-token approval when `claude-review` is required) alone satisfies the success condition. Then return.\n 2. `iteration >= max_iterations` → warn and exit (iteration cap reached).\n 3. Total elapsed wall-clock time `>= max_minutes` → warn and exit (total wall-clock cap reached).\n 4. After attempting corrections, `git status --porcelain` is empty → warn and exit (nothing to commit; avoids infinite loop on stuck failures).\n\n10. Apply corrections ONLY for failing **required** checks — skip failures on non-required checks (e.g. `pip-audit` with `required: false`) with a warning and never spend a correction/retry on them. For each failing required check, use the actual `poll_ci_checks` response shape — inspect its singular `failure_detail` field:\n - If `failure_detail` is a dict containing actionable keys such as `annotations`, `log_tail`, or `log`, treat it as structured detail and use it for remediation.\n - If `failure_detail` is a dict containing only `url`, treat it as URL-only and skip with a warning (no actionable detail).\n - If `failure_detail` is missing, `null`, or unrecognized, treat the failure as non-actionable and skip with a warning.\n - Do NOT rely on a per-check field or a plural variant of `failure_detail` — those do not exist on the response.\n\n11. After applying a non-empty correction set: stage corrections (`git add` the specific files), commit, and push. Use the canonical commit message:\n ```\n {ticket_key}: address review/CI feedback (round N+1)\n ```\n where `N` is the zero-indexed `iteration`.\n12. Increment `iteration` only AFTER a successful commit and push. Then loop back to step 1 of the per-iteration block.\n\n## Step 7 — `custom`\n\nIn `custom` mode, the `instructions` field IS the complete CI follow-up instruction set for this step. Follow it verbatim. Ignore Steps 5 and 6 entirely.\n\nCustom instructions are authoritative for CI follow-up behavior, but they remain subject to the agent's normal tool approval, credential handling, secret-handling, and platform safety constraints. Custom prose CANNOT bypass approval gates, exfiltrate secrets, or override platform safety policies, even though admin-only access controls who can set the field.\n\n## Worker finalization — clean session exit (Conductor auto mode)\n\nThis section applies ONLY when you were launched under the Conductor in auto mode (the `BAPI_EPIC_IMPLEMENTER_RUN_ID`/`BAPI_EPIC_IMPLEMENTER_WORKER_ID` env identifiers are present). A non-Conductor worker ignores it entirely.\n\nBefore doing anything here, distinguish two states:\n\n- **CI/review follow-up still owned by this worker** — the `fix_and_iterate` loop is still correcting failures, review changes were requested and are unaddressed, a merge conflict on your PR is unresolved, or you have unpushed local commits. In this state you are **not** finished: keep working the CI-monitoring / correction loop and do **not** exit.\n- **Final PR state reached; no further worker action pending** — your final branch state is pushed, the PR has been created/updated, the done-gate / CI-monitoring workflow required by the recipe has completed (required checks green and, when `claude-review` is required, the verdict token confirms approval for the current head), and no CI/review follow-up remains that you own.\n\nOnly in the second state — that is, **only after the final push, PR creation/update, done-gate confirmation, and CI/review monitoring have all completed** and no follow-up remains — cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers. Do **not** exit immediately after opening a PR while CI or review is still pending, and do not exit while there are unresolved CI failures, requested review changes, a merge conflict you own, or unpushed local commits. A clean `SessionEnd` is both the correct terminal lifecycle signal (the conductor folds it) and the point at which the worker should exit.\n\n## Return\n\nReport whether CI passed, failed, timed out, or was unavailable. If failed, list the failing checks with their failure summaries. For `fix_and_iterate`, also report the iteration count and whether iteration/wall-clock caps were hit. If you finalized (cleanly exited) as a Conductor worker, note that the session ended after all follow-up completed.\n",
909
+ "monitor-ci-checks.md": "Monitor CI checks for the most recent commit. The behavior is dispatched on the repo-specific `ci_followup_config` JSON value: `poll_only`, `fix_and_iterate`, or `custom`. Read this entire file once before doing anything, then follow only the matching branch.\n\n\n**Required-check source**: Both the `poll_only` (Step 5) and `fix_and_iterate` (Step 6) branches gate progression on the *required* check subset, not the aggregate `all_passed` flag. Each check returned by `resolve_ci_checks`/`poll_ci_checks` carries a `required` field (from GitHub Branch Protection, or an LLM classification fallback) — treat `required: false` as non-required (e.g. `pip-audit`) and a missing field or `required: true` as required. This is the tool-provided proxy for the Conductor done-gate's authoritative required-checks set (`mcp_server/src/conductor/done-gate.ts`); do not re-derive required/non-required status in prose.\n\n## How to wait (BAPI-1104)\n\n**Never wait with a shell `sleep`, and never poll in an unbounded foreground loop.** The\nClaude Code harness refuses a foreground `sleep` outright, so an instruction to sleep does\nnot slow a worker down — it kills the step. Wait with `wait_for_ci_checks` instead.\n\n`wait_for_ci_checks` takes `commit_ref` (and an optional `budget_ms`, default and maximum\n240000). One call occupies at most 240 seconds — comfortably inside the MCP client\ndeadline — and returns a JSON body with a `state` field:\n\n- `state: \"pending\"` — NORMAL. Some check is still running and the window closed. Call the\n tool again. This is the replacement for every \"wait N seconds and poll again\" instruction:\n each repeated call is one bounded window, and repeating is how you wait for longer.\n- `state: \"ready\"` — every reported check is **terminal**. **Terminal is not passing.** This\n tool never decides pass or fail; it reports each check's `name`, `required`, `status`,\n `conclusion` and `complete`, and the branches below decide `required_green` from them.\n- `state: \"unavailable\"` — CI check configuration is not resolved for this repository, so\n there is nothing to wait on. Do not keep calling. Call `resolve_ci_checks` for the current\n SHA and retry once; if it is still unavailable, stop and report unavailable status through\n the branch's existing unavailable-CI path.\n- `state: \"error\"` — terminal. Report the `reason` and stop waiting; further windows\n reproduce it.\n\nEach returned window counts toward the branch's own wall-clock budget (`max_minutes` where\nthe branch has one). Track elapsed time across repeated windows and escalate through the\nbranch's existing exit when the budget is spent — the budget is enforced here, in the\nrecipe, not by the tool.\n\n## Entry state and ownership boundary\n\n**Re-resolve the git context before anything else.** Run `git branch --show-current`\nand `git rev-parse HEAD` when this step begins. In the implement pipeline the pull\nrequest is opened *before* the bounded post-finalization verification phase, and\nthat phase may have pushed a correction on top of the commit the PR was originally\nopened at. The head you monitor must be the branch's current pushed head, not a SHA\ncarried over from PR creation.\n\n**Ownership is split, and the split matters.** The bounded verification phase\n(`verify-plan.md`) owns findings produced by its own local, touched-area commands.\nThis step owns everything subsequently reported by the authoritative `ci` and\n`code_review` gates — failing required checks and requested review changes. Do not\nre-adjudicate the other phase's findings, and do not assume a finding it reported\nhas been fixed unless a pushed commit shows it.\n\n**Do not substitute a broad local run for the authoritative checks.** Running the\nfull local suite here does not establish that CI passed; it duplicates the work the\n`conductor-ci` gate already performs on the pull request, and it is exactly the\nbudget sink this protocol was reordered to avoid. Use the structured CI failure\ndetail from `poll_ci_checks` to target a fix, and scope any local reproduction to\nthe failing area.\n\n**Keep every correction on the existing pull request branch**, and commit *and push*\neach correction before polling resumes. Polling always restarts against the new\npushed head — an unpushed correction is invisible to CI, to review, and to the\nreconciler.\n\nThroughout, report the check and review states you **observed**. Observing that a\ncheck is green is not the same as issuing a verdict: the done-gate evaluation is\nserver-side and the reconciler decides. This step never emits a control signal of\nits own.\n\n## Step 3 — Parse `ci_followup_config`\n\nLook at the response from the immediately preceding `config_field` call (the pipeline step that ran right before this one). The response envelope's `value` field is itself a JSON string and must be parsed again with `JSON.parse` (i.e., the `value` is double-encoded — the outer envelope is JSON, and the inner `value` is a JSON-encoded string of the actual config object).\n\nIf ANY of the following hold, log a warning and use the defaults `{\"strategy\":\"poll_only\",\"max_iterations\":1,\"max_minutes\":10}`:\n\n- The `config_field` response is missing or unavailable (e.g., the step warned-and-continued).\n- The response `value` is `null`.\n- Parsing `value` with `JSON.parse` fails (the persisted text is not valid JSON).\n- The parsed result is not a JSON object.\n- One or more of the required keys (`strategy`, `max_iterations`, `max_minutes`, `instructions`) is missing.\n- `strategy` is not one of `poll_only`, `fix_and_iterate`, or `custom`.\n\n## Step 4 — Dispatch on `strategy`\n\nRead this whole file once and then follow only the matching branch:\n\n- `poll_only` → follow Step 5.\n- `fix_and_iterate` → follow Step 6.\n- `custom` → follow Step 7.\n\nIf `strategy` is unrecognized, log a warning and fall through to Step 5 (`poll_only`).\n\n## Step 5 — `poll_only`\n\nPreserve the baseline polling behavior. The configured `max_minutes` is IGNORED in this branch — `poll_only` always uses the existing 10-minute baseline.\n\n1. Run `git rev-parse HEAD` to get the current commit SHA.\n2. Call the `resolve_ci_checks` tool with `commit_ref` set to that SHA. This discovers and classifies the CI checks for the repository, including each check's `required` field. It resolves the required-check **configuration** only and **never re-runs a workflow** — including with `force_rerun: true`, which bypasses the cache and nothing more.\n3. Poll CI status by calling `poll_ci_checks` with `commit_ref` set to the same SHA. Check the response for `all_complete`, and note each check's own `required`/green status — do not use the aggregate `all_passed` flag to decide pass/fail (see step 6 below).\n4. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the 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 — dispatch to the Posture-Specific Conflict Handler (below) rather than merging, resolving, or pushing directly here; `poll_only` never merges, edits, commits, or pushes on its own authority. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary wait behavior below.\n5. If checks are not yet complete, call `wait_for_ci_checks` with `commit_ref` set to the same SHA and repeat that call while it returns `state: \"pending\"`. Each call is one bounded 240-second window; repeat until it returns `state: \"ready\"` (every check terminal) or 10 minutes of accumulated windows have elapsed. On `state: \"unavailable\"` follow step 9 rather than continuing to wait.\n6. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green. If `required_green` is `true`, continue to review verdict gating (below) and then the Mergeability Gate (item 11) — `required_green` alone is not sufficient to report success. Non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility only and never flip the Passed/Failed classification.\n7. **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 and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` all mean the review is not yet approved and success is not yet reached. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed. Once the verdict is confirmed approved (or `claude-review` is not required), continue to the Mergeability Gate (item 11) before reporting success.\n7a. **A `claude-review` failure blocked by the preflight is TERMINAL — never retry it.** When `claude-review` fails within about twelve seconds and no verdict comment was ever posted, read the run's `Preflight the review action` step. The review action refuses to run whenever this workflow differs from the repository's default-branch copy, and the preflight names which of the two causes it found:\n - `status=workflow_modified` — the pull request's own commits changed `.github/workflows/claude-review.yml`. Such a pull request must be reviewed and merged by hand; it can never be conductor-merged.\n - `status=base_workflow_stale` — the pull request changed no workflow file and **inherited** a stale copy from its base branch. The stale ref is the **base branch**, not the pull request.\n\n Both aggregate to `reason=review_action_blocked_by_workflow_modification`. In both cases the preflight is a deterministic blob-SHA comparison over unchanged inputs, so **retrying, re-running, or continuing to poll reproduces byte-identical evidence and can never succeed** — stop the retry loop and report it rather than spending the remaining budget. For the stale-base case the only remedy is to reconcile the base branch with the default branch; see `docs/claude/runbooks/claude-review-base-branch-drift.md` for detection, diagnosis, and both remediation paths.\n\n8. If any required checks fail, report which required checks failed (and any non-required failures for visibility) and include any available annotations or log details from the poll response. Do NOT attempt to fix failures — just report them clearly.\n9. If CI status is unavailable (`wait_for_ci_checks` returns `state: \"unavailable\"`, or the resolver/poll returns `available: false`), call `resolve_ci_checks` once for the current SHA and retry the wait; if it is still unavailable, report unavailable status and exit. Do not attempt fixes and do not keep waiting. Note that this call re-resolves the required-check **configuration** and actuates no CI run, so it cannot make a missing run appear.\n10. If the 10-minute timeout is reached, report timeout and exit.\n11. **Mergeability Gate — final ready-decision.** Reached only after `required_green` is true and, when `claude-review` is required, its verdict is confirmed approved for the current head. Run the shared Mergeability Gate described below under \"Mergeability Gate (Shared, Final Ready-Decision)\".\n - If the gate reports the current head is not a confirmed conflict (including a recognized non-conflicting state such as `BLOCKED` or `BEHIND`), report success — a green PR with a recognized non-conflicting result continues without a prompt or a conflict report.\n - If the gate reports a confirmed conflict (`mergeable == \"CONFLICTING\"` or `mergeStateStatus == \"DIRTY\"`), dispatch to the Posture-Specific Conflict Handler (below), which routes `poll_only` to Attended Investigation and Approval; `poll_only` never merges, edits, commits, or pushes on its own authority. Report `awaiting conflict-resolution approval` while a proposal is pending, or `blocked: unresolved conflict` / `blocked: full-suite failure after resolution` if the attended flow reaches one of those blocked outcomes.\n - If the gate reports mergeability could not be confirmed after its own additional read, report `mergeability could not be confirmed` and exit — this is not an ordinary CI failure.\n - This gate runs once, at this final ready-decision point — never on every poll.\n\n### Polling Directive\n\nWait only through repeated `wait_for_ci_checks` calls — never a shell `sleep`, and never an\nunbounded foreground poll. Do NOT output any inline commentary, reasoning, or partial status\nupdates between windows. Only output a status message when:\n- All checks are terminal (`state: \"ready\"`), OR\n- CI is unavailable or the wait returns a terminal error, OR\n- The 10-minute accumulated budget is reached.\n\nThis minimizes context window consumption during long-running CI waits.\n\n## Step 6 — `fix_and_iterate`\n\nThis is a self-contained loop where `iteration` is the number of correction rounds already pushed and `start_time` is captured before the first iteration. `max_minutes` is the TOTAL wall-clock cap across all iterations, not an additional per-iteration budget. The 10-minute per-iteration CI-wait cap is INSIDE that total budget, and it is counted from the accumulated `wait_for_ci_checks` windows this iteration has already spent.\n\nInitialize:\n\n- `iteration = 0`\n- `start_time = now()`\n\nBefore starting each iteration AND before applying corrections, check the total wall-clock budget. If `now() - start_time >= max_minutes`, warn and exit.\n\nPer iteration:\n\n1. Run `git rev-parse HEAD` to get the current commit SHA. The previous push may have changed it; always read fresh.\n2. Run `git branch --show-current` to get the current branch. Always read fresh.\n3. Call `resolve_ci_checks` with `commit_ref` set to the current SHA (once per new SHA — the server caches per project but the agent should still call it for each new SHA). Each returned check carries a `required` field — this is the tool-provided proxy for the done-gate's authoritative required-checks set. The call resolves **configuration** only and re-runs nothing; to re-run a failed lane by hand use `gh run rerun --failed <run_id> --repo <owner/repo>`.\n4. Wait for terminality by calling `wait_for_ci_checks` with `commit_ref` set to the current SHA, repeating while it returns `state: \"pending\"`. Each call is one bounded 240-second window and every window counts against both budgets below. Stop when it returns `state: \"ready\"` (every check terminal), OR `state: \"unavailable\"` (go to step 6), OR `state: \"error\"`, OR the per-iteration 10-minute accumulated cap is reached, OR the remaining total wall-clock budget is exhausted. Use `poll_ci_checks` for the failure detail of a terminal check; it is the detail source, not the waiting mechanism.\n5. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the 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 — dispatch to the Posture-Specific Conflict Handler (below), which resolves the conflict autonomously through the Autonomous Verified Merge Resolution path and restarts polling from step 1 of this per-iteration block against the new head SHA once it pushes. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary per-iteration behavior below.\n6. If CI status is unavailable (`wait_for_ci_checks` returns `state: \"unavailable\"`, or `poll_ci_checks` returns `available: false`), call `resolve_ci_checks` once for the current SHA and retry the wait. If it is still unavailable, warn and exit the loop — automated remediation cannot make reliable progress without CI signals, and further waiting cannot produce one. `resolve_ci_checks` re-resolves the required-check **configuration** and never actuates a run, so repeating it cannot create the missing signal.\n7. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green; non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility but never gate exit condition 1 below. If `claude-review` is a required check, its GitHub check reaching a non-pending/\"success\" state is transport completion only, not approval — fetch the PR's comments and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` mean the review is not yet approved. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n7a. **A `claude-review` failure blocked by the preflight is TERMINAL — never spend a correction or a retry on it.** When `claude-review` fails within about twelve seconds and no verdict comment was ever posted, read the run's `Preflight the review action` step. The review action refuses to run whenever this workflow differs from the repository's default-branch copy, and the preflight names which of the two causes it found:\n - `status=workflow_modified` — the pull request's own commits changed `.github/workflows/claude-review.yml`. It must be reviewed and merged by hand; it can never be conductor-merged.\n - `status=base_workflow_stale` — the pull request changed no workflow file and **inherited** a stale copy from its base branch. The stale ref is the **base branch**, not the pull request, so no correction you can make on this branch will clear it.\n\n Both aggregate to `reason=review_action_blocked_by_workflow_modification`, and the preflight is a deterministic blob-SHA comparison over unchanged inputs — every iteration reproduces byte-identical evidence. Treat it as non-actionable, exit the loop with that reason reported, and do not consume the remaining iteration or wall-clock budget. Remedy for the stale-base case: reconcile the base branch with the default branch. See `docs/claude/runbooks/claude-review-base-branch-drift.md`.\n\n Once `required_green` is true and (if required) the review verdict is confirmed approved, continue to the Mergeability Gate inside exit condition 1 below before returning success.\n\n8. Apply repo-specific `instructions` ONLY when the `instructions` field is non-empty. If the repo `instructions` reference templated placeholder tokens for the GitHub owner, repo, or PR number — e.g., the literal tokens written as a left brace, the word `owner`/`repo`/`pr`, then a right brace — resolve them from the local git/VCS context. Use `gh pr list --head <branch> --json number` to get the PR number; parse the remote URL (`git config --get remote.origin.url`) for owner/repo. If `instructions` is empty, skip repo-specific signal gathering and use only structured CI failure information.\n\n9. Evaluate exit conditions in this order:\n 1. `required_green` is true AND (if `claude-review` is required) the verdict token confirms approval for the current head AND any repo-specific exit criteria from `instructions` are met AND the Mergeability Gate (described below under \"Mergeability Gate (Shared, Final Ready-Decision)\") confirms the current head is not a confirmed conflict → success. If there are no repo-specific exit criteria, `required_green` (plus verdict-token approval when `claude-review` is required, plus the Mergeability Gate) alone satisfies the success condition. A confirmed conflict from the gate does not exit the loop — it routes to the Autonomous Verified Merge Resolution path (below), which may push a new head. Any newly pushed head, whether from an ordinary correction or from conflict resolution, must re-enter CI/review monitoring at step 1 of this per-iteration block and receive its own final Mergeability Gate evaluation before success. If the gate instead reports mergeability could not be confirmed, that outcome is terminal for this gate invocation, exactly as the gate itself states — do **not** loop back to per-iteration step 1 hoping a repeat read resolves it: every check is already terminal, so `wait_for_ci_checks` would return instantly and `iteration` cannot advance without a commit to push, meaning nothing about looping changes an already-exhausted read. Report `mergeability could not be confirmed` and stop this loop now.\n 2. `iteration >= max_iterations` → warn and exit (iteration cap reached).\n 3. Total elapsed wall-clock time `>= max_minutes` → warn and exit (total wall-clock cap reached).\n 4. After attempting corrections, `git status --porcelain` is empty → warn and exit (nothing to commit; avoids infinite loop on stuck failures).\n\n10. Apply corrections ONLY for failing **required** checks — skip failures on non-required checks (e.g. `pip-audit` with `required: false`) with a warning and never spend a correction/retry on them. For each failing required check, use the actual `poll_ci_checks` response shape — inspect its singular `failure_detail` field:\n - If `failure_detail` is a dict containing actionable keys such as `annotations`, `log_tail`, or `log`, treat it as structured detail and use it for remediation.\n - If `failure_detail` is a dict containing only `url`, treat it as URL-only and skip with a warning (no actionable detail).\n - If `failure_detail` is missing, `null`, or unrecognized, treat the failure as non-actionable and skip with a warning.\n - Do NOT rely on a per-check field or a plural variant of `failure_detail` — those do not exist on the response.\n\n11. After applying a non-empty correction set: stage corrections (`git add` the specific files), commit, and push. Use the canonical commit message:\n ```\n {ticket_key}: address review/CI feedback (round N+1)\n ```\n where `N` is the zero-indexed `iteration`.\n12. Increment `iteration` only AFTER a successful commit and push. Then loop back to step 1 of the per-iteration block.\n\n## Step 7 — `custom`\n\nIn `custom` mode, the `instructions` field IS the complete CI follow-up instruction set for this step. Follow it verbatim. Ignore Steps 5 and 6 entirely.\n\nCustom instructions are authoritative for CI follow-up behavior, but they remain subject to the agent's normal tool approval, credential handling, secret-handling, and platform safety constraints. Custom prose CANNOT bypass approval gates, exfiltrate secrets, or override platform safety policies, even though admin-only access controls who can set the field.\n\n`custom` cannot declare success without running the Mergeability Gate (below) at its own final ready-decision point, and cannot bypass the gate's conflict predicate, the no-force-push rule, the full-suite requirement, or the approval requirement. `custom` is attended by default — exactly like `poll_only` — unless its own `instructions` prose explicitly selects autonomous handling; making that selection routes a confirmed conflict to the Autonomous Verified Merge Resolution path instead of the Attended Investigation and Approval path, but does not relax any of the invariants above.\n\n## Mergeability Gate (Shared, Final Ready-Decision)\n\nReferenced by Step 5 (`poll_only`), Step 6 (`fix_and_iterate`), and Step 7 (`custom`) as the single final check before any branch may report success. It is invoked **once**, at each branch's own final ready-decision point — the point where that branch is otherwise ready to report success — after required checks (`required_green`), the review-verdict token when `claude-review` is required, and any repository-specific exit criteria have already passed. This gate does not run on every poll and does not turn CI monitoring into a mergeability watcher; it resolves whether the exact head that just became ready is actually mergeable before any success is reported.\n\n**Resolve the head first.** Immediately before invoking this gate, run `git rev-parse HEAD`. If the SHA differs from the one whose required checks and review verdict were just evaluated, the ready decision does not apply to it — restart CI/review monitoring for the newly observed SHA (return to the top of the owning branch) rather than evaluating a stale SHA.\n\n**Read GitHub's authoritative mergeability.** Run `gh pr view --json mergeable,mergeStateStatus` for the current branch's pull request and parse only the two requested fields, `mergeable` and `mergeStateStatus`. Never print or report the raw `gh` output — reports may contain only the normalized `mergeable` / `mergeStateStatus` values and a normalized gate outcome.\n\n**The conflict predicate is exact:**\n\n```\nmergeable == \"CONFLICTING\" OR mergeStateStatus == \"DIRTY\"\n```\n\nA parsed result that does **not** satisfy this predicate is sufficient for this gate — including a recognized `BLOCKED` or `BEHIND` value. Do not require an exact `MERGEABLE` plus `CLEAN` pair; GitHub reports several non-conflicting states, and this gate only screens for the two conflicting ones above.\n\n**Fail closed on ambiguity.** `null`, `UNKNOWN`, a malformed response, an unrecognized value, or an unreadable/failed `gh pr view` call are all **\"mergeability not confirmed\"** — never treated as ready. On an ambiguous or failed read, re-read `gh pr view --json mergeable,mergeStateStatus` exactly one more time before giving up. `wait_for_ci_checks` is **not** a pause here and must not be used as one: it blocks only while some CI check is still non-terminal, and by the time this gate runs every check is already terminal by construction, so it would return on its very first round-trip and add no actual delay. No mechanism available to this instruction produces a genuine timed pause at this point, and a shell `sleep` is refused outright by the harness — never reach for one, and never spin in a tight foreground retry loop hoping the value changes on its own. Treat the second read as an independent observation, not a timed retry: `mergeable: null` is GitHub's own transient background-computation state, and a fresh API call sometimes catches it resolved even without an artificial wait. If the second read is still ambiguous, report **`mergeability could not be confirmed`** and stop — this is a terminal outcome for this gate invocation, not a condition to keep looping on. Never let an exception from a failed `gh` call escape this gate uncaught.\n\n**Outcome:**\n\n- **Not a confirmed conflict** (including recognized `BLOCKED` / `BEHIND` states) → the gate is satisfied. A green PR with a recognized non-conflicting result continues without a prompt or a conflict report.\n- **Confirmed conflict** (`mergeable == \"CONFLICTING\"` or `mergeStateStatus == \"DIRTY\"`) → dispatch to the Posture-Specific Conflict Handler below.\n- **Mergeability not confirmed** after the one bounded re-read → report `mergeability could not be confirmed` and stop.\n\n### Posture-Specific Conflict Handler\n\nA confirmed conflict is dispatched by the current `ci_followup_config.strategy` — never by any additional flag, environment variable, or configuration field:\n\n- **`fix_and_iterate`** is the autonomous posture: it resolves a confirmed conflict itself, through the Autonomous Verified Merge Resolution path below, without asking.\n- **`poll_only`** is the attended posture: it cannot mutate any conflict-resolution state before explicit human approval, through the Attended Investigation and Approval path below.\n- **`custom`** is attended by default — the same as `poll_only` — unless the repository's own `instructions` prose explicitly selects autonomous handling. Custom prose can change *how* a conflict is investigated or resolved, but it cannot bypass this gate, the no-force-push rule, the full-suite requirement, the approval requirement (when attended), credential handling, or platform-safety constraints. `custom` cannot report success without this gate.\n\n**Base resolution.** Both paths resolve `<base>` through this precedence, taking the first tier with a usable value:\n\n1. Non-empty `BAPI_BASE_BRANCH` from the environment.\n2. A PR base the user supplied at the commit step, when that value is still available in this session.\n3. `gh pr view --json baseRefName -q .baseRefName` — the durable fallback for the user-supplied-base tier when same-session memory of it is unavailable.\n4. `config_field` with `operation: \"get\"` and `field_name: \"base_branch\"`.\n5. `main`.\n\nRun `git fetch` before inspecting or integrating the selected base. Record the current head SHA and the fetched `origin/<base>` SHA as the specific pair being investigated or resolved — every report below is scoped to that exact pair.\n\n#### Attended Investigation and Approval (`poll_only`, attended `custom`)\n\nRuns after `git fetch` and base resolution above. From this point the investigation is strictly read-only — nothing here may touch the worktree, the index, `HEAD`, a ref, or the remote.\n\n1. Compute the merge base between `HEAD` and `origin/<base>`.\n2. Run `git merge-tree --write-tree -z --name-only --messages HEAD origin/<base>` (current head first, fetched base second). `git merge-tree` performs the investigation entirely in the object database — it does not authorize any worktree, index, `HEAD`, ref, commit-history, or remote mutation.\n3. Classify the result as clean, conflicted, or operationally unavailable, and list the conflicting files.\n4. Run `git log --oneline <merge-base>..origin/<base>` to summarize the commits that advanced the base.\n5. Write a concise prose proposal describing how each conflict or conflict group should be resolved.\n6. Report the investigated head SHA, the fetched base SHA, the conflicting-file classification, the base delta, and the proposed resolution — **before** asking for approval.\n\nA locally clean `git merge-tree` result does **not** override GitHub's authoritative `CONFLICTING` or `DIRTY` result. Report that discrepancy as-is, without mutating anything.\n\nIf `git fetch`, the merge-base calculation, or `git merge-tree` fails, report which component was unavailable and whatever context is safely known, and stop — do not fall through into any mutation.\n\n**Approval.** Require explicit human approval for that exact head SHA, base SHA, and proposal before any merge, conflict edit, commit, or push. A pending approval request is a **resumable state**, exempt from the branch's existing ten-minute CI-poll baseline — report it as `awaiting conflict-resolution approval` and stop, rather than counting the wait as CI-poll time.\n\nOn resumption, re-read `HEAD`, re-fetch the base, and re-run this gate's authoritative mergeability read — never assume the previously approved state is still current. If either the head or the base SHA changed before the approved resolution was pushed, the approval is **invalidated**: run a fresh investigation-and-approval cycle before proceeding.\n\nAn approved, still-current proposal is routed into the same Autonomous Verified Merge Resolution path used by `fix_and_iterate`, below.\n\n#### Autonomous Verified Merge Resolution (`fix_and_iterate`, approved attended proposals)\n\n1. Run `git status --porcelain` immediately before merging. Require a clean worktree before `git merge origin/<base>`; if the worktree is not clean, preserve the owned changes through the repository's established safe stash workflow, or escalate without discarding them.\n2. Merge in the worker's own worktree with `git merge origin/<base>`. **Never** `git rebase`, a force-push option, or any equivalent history rewrite.\n3. Resolve the merge semantically — read the conflicting hunks and confirm the resolved code is behaviorally correct, not just that the textual markers are gone.\n4. After resolving conflicts, check for an unresolved index and run `git diff --check`. Residual conflict markers or unresolved index entries are an **incomplete resolution** — escalate without pushing.\n5. Run the full local test suite: the existing advisory pre-push-hook gate documented in `CLAUDE.md` under \"The test gate\" (`scripts/hooks/pre-push`) — the same gate the resume-mode remediation precedent (`buildResumeModeRemediationFinalizeInstruction` in `mcp_server/src/start-tickets.ts`) already uses as its definition of \"the full suite.\" Run it after the merge resolves and **before** any resolution push.\n6. If the merge cannot be resolved, or the full-suite gate is not green, report a **blocked** outcome (`blocked: unresolved conflict` or `blocked: full-suite failure after resolution`) and do **not** push.\n7. Only after the full-suite gate succeeds: commit and push the resolved merge normally. Increment the owning branch's `iteration` counter (Step 6) only after both the commit and the push succeed.\n8. Run `git rev-parse HEAD` after the push and restart CI resolution and waiting against that new SHA. The new head must become required-green, satisfy any review/repository criteria, and pass this Mergeability Gate again before success.\n9. Repeated conflicts and resolution attempts are bound by the owning branch's existing `max_iterations` and `max_minutes` limits. Escalate — do not spin — when a conflict repeats, a budget expires, or no safe resolution can be produced.\n\n## Worker finalization — clean session exit (Conductor auto mode)\n\nThis section applies ONLY when you were launched under the Conductor in auto mode (the `BAPI_EPIC_IMPLEMENTER_RUN_ID`/`BAPI_EPIC_IMPLEMENTER_WORKER_ID` env identifiers are present). A non-Conductor worker ignores it entirely.\n\nBefore doing anything here, distinguish two states:\n\n- **CI/review follow-up still owned by this worker** — the `fix_and_iterate` loop is still correcting failures, review changes were requested and are unaddressed, a merge conflict on your PR is unresolved, the Mergeability Gate's Autonomous Verified Merge Resolution reported `blocked: unresolved conflict` or `blocked: full-suite failure after resolution`, an attended conflict-resolution proposal is `awaiting conflict-resolution approval`, or you have unpushed local commits. In this state you are **not** finished: keep working the CI-monitoring / correction loop and do **not** exit.\n- **Final PR state reached; no further worker action pending** — your final branch state is pushed, the PR has been created/updated, the done-gate / CI-monitoring workflow required by the recipe has completed (required checks green and, when `claude-review` is required, the verdict token confirms approval for the current head), the Mergeability Gate confirms the current head is not a confirmed conflict, and no CI/review follow-up remains that you own.\n\nOnly in the second state — that is, **only after the final push, PR creation/update, done-gate confirmation, CI/review monitoring, and the Mergeability Gate have all completed** and no follow-up remains — cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers. Do **not** exit immediately after opening a PR while CI or review is still pending, and do not exit while there are unresolved CI failures, requested review changes, a merge conflict you own, a blocked or pending mergeability-gate conflict resolution, or unpushed local commits. A clean `SessionEnd` is both the correct terminal lifecycle signal (the conductor folds it) and the point at which the worker should exit.\n\n## Return\n\nReport one of: **passed**, **failed** (list the failing checks with their failure summaries), **timed out**, **unavailable**, **`mergeability could not be confirmed`** (the Mergeability Gate's read stayed ambiguous or unavailable after its one additional read — terminal for this gate invocation, not a condition the loop kept retrying), **`blocked: unresolved conflict`** (the Autonomous Verified Merge Resolution path could not produce a safe, complete merge), **`blocked: full-suite failure after resolution`** (the merge resolved but the advisory pre-push-hook full-suite gate did not pass), or the resumable **`awaiting conflict-resolution approval`** (an attended investigation produced a proposal for a specific head/base pair that is still pending explicit approval — report the approved-scope candidate; this wait does not count as CI-poll time). Blocked and uncertain outcomes report only normalized context — never raw `gh` output. For `fix_and_iterate`, also report the iteration count and whether iteration/wall-clock caps were hit. If you finalized (cleanly exited) as a Conductor worker, note that the session ended after all follow-up completed.\n\nNo post-green watcher is introduced by this gate: it runs once, at each branch's own final ready-decision point, for the head that was actually evaluated. A conflict that first appears after this monitoring loop has already exited is outside this mechanism entirely — it is owned by the epic-implementer reconciler's own authoritative, uncached mergeability check performed at merge/completion admission.\n",
910
910
  "preflight-and-readiness.md": "Initialize the idea-to-ticket run directory and classify the idea's readiness and scope.\n\n## Inputs\n\n- Idea: `{idea}`\n- Slug: `{slug}`\n- Run ID: `{run_id}`\n- Docs directory: `{docs_dir}`\n- Project standards: response from the immediately preceding `get_project_standards` step. If that step returned an error envelope or a 404, treat the project standards as unavailable and proceed; do not halt.\n\n## Instructions\n\n1. Create the run directory:\n ```\n mkdir -p {docs_dir}/idea-to-ticket/{slug}-{run_id}\n ```\n Every artifact produced by this pipeline run lives under this run directory. No Jira mutation may occur in any later step until `run-manifest.json` has been written to this directory.\n\n2. Classify the idea on two independent axes:\n\n **Readiness** (one of):\n - `ready_to_draft` — the idea is concrete enough that a clear ticket draft can be produced.\n - `needs_clarification` — the idea is reasonable but missing key answers; clarifying questions must be raised in `open-questions.md` later.\n - `research_first` — drafting is blocked on external/codebase research; deep or narrow research must come first.\n - `too_vague_to_ticket` — the idea is not actionable yet; do not produce a ticket.\n\n **Scope** (one of):\n - `task` — a single Jira Task (default when ambiguous).\n - `spike` — a single Jira Spike. Resolve `spike` only on the objective\n condition that the idea has **no committed production-code deliverable** —\n the same trigger as exception E2 in `docs/bridge-ticket-authoring.md`. This\n is a deliverable that is absent, not a judgement about whether the work\n feels investigative. **Any uncertainty resolves to `task`.** This decision\n is made HERE and only here: the `scope` you write into the manifest is what\n selects the spike ticket format downstream, and `jira-ticket-writer` is\n explicitly forbidden from deciding it for itself.\n - `epic_candidate` — the idea decomposes into a Jira Epic plus multiple child tickets.\n\n3. Halt locally if readiness is `too_vague_to_ticket`. Write the manifest anyway (see step 4) so the local artifacts record the halt; then stop without continuing the rest of the pipeline. Do not attempt any Jira mutation.\n\n4. Write `run-manifest.json` to `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`. Required fields:\n - `idea` — the original `{idea}` text.\n - `slug` — `{slug}`.\n - `run_id` — `{run_id}`.\n - `run_dir` — `{docs_dir}/idea-to-ticket/{slug}-{run_id}/`.\n - `readiness` — one of the four readiness values above.\n - `scope` — one of the three scope values above.\n - `project_standards_available` — `true` if `get_project_standards` returned a usable result, `false` otherwise.\n - `idempotency_label` — `bapi-idea-to-ticket-{run_id}` (per-run label; lets downstream steps resume THIS run by label).\n - `stable_label` — `bapi-idea-hash-{idea_hash}` (stable across runs of the same idea; lets the duplicate-detection step catch a PRIOR run of the same idea by label, not just fuzzy text).\n - `created_at` — ISO 8601 timestamp.\n\n5. The manifest is the resumability artifact for the whole run. Do not include secrets or raw credentials. Keep the file under a few KB.\n\n## Return\n\nConfirm the run directory and `run-manifest.json` were created, and report the classified `readiness` and `scope`. If readiness is `too_vague_to_ticket`, also report that the pipeline must stop without Jira mutation.\n",
911
- "render-ticket-manifest.md": "Render every entry of an approved ticket manifest into a drafted body, one `jira-ticket-writer` invocation per entry, without re-deciding the split.\n\nThis stage sits between **decomposition** (which decided the split once and froze\nit) and **creation** (`upload-epic-hierarchy.md` for an epic, ordinary\n`create_ticket` calls for siblings). It owns exactly one job: turning frozen\nmanifest entries into drafted bodies. It decides nothing.\n\n## Inputs\n\n- **The approved manifest** — the ordered plan the decomposition pass froze.\n On the `idea-to-ticket` path this is `decomposition-plan.json` in the run\n directory, produced by `decompose-epic-candidate.md`. On the `plan-epic` path\n it is the sub-task list in `epic-plan.md`, produced by `decompose-epic.md`. On\n a client-driven path (`/explore-ticket` Stage 9) it is the approved outline\n shown at the gate.\n- **The approval state** for that manifest. Whether approval is an explicit\n affirmative or a pipeline auto-approval variable is the calling surface's own\n rule; this stage only needs to know it resolved.\n- **The research and framing artifacts** each entry's writer invocation needs —\n the same set the standalone drafting path passes.\n\n## Preflight — fail closed, before any body is rendered\n\nCheck all of the following first. Each one is a **hard failure** that stops the\nflow before a single writer invocation and long before any Jira mutation. None\nof them degrades to a partial run:\n\n1. **Approval unresolved.** The manifest's approval gate has not resolved, or\n resolved as declined. Nothing renders and nothing is created.\n2. **Manifest identity changed.** The manifest presented here is not the one that\n was approved — a different entry count, different ordering, renumbered\n entries, or an altered scope boundary. A substituted split never silently\n replaces the approved one.\n3. **An entry past the size ceiling.** An entry whose stated scope runs beyond\n roughly 40 files or ~3000 LOC is over the bound wherever it sits — a\n standalone ticket and an epic child alike. Fail and name the entry; do not\n draft it, do not split it here, and do not shrink its stated scope to fit. An\n `XL` entry below that ceiling is not a violation and needs no supporting\n evidence: `XL` is the preferred shape for work that does not fit in `L`.\n4. **An entry with no writer draft after rendering.** Every entry must have\n produced a body. A missing draft is a failure, not an entry to skip.\n\nFail-open applies **only** to enrichment and context gathering — an optional\nresearch artifact that is missing may degrade the context a body is written\nfrom. Approval, manifest identity, sizing validity, and writer-output\ncompleteness all fail closed.\n\n## Instructions\n\n1. Read the approved manifest in full. Record its entry count, its order, and\n each entry's identity, so the identity check above has something to compare\n against.\n\n2. **Fan out one `jira-ticket-writer` invocation per entry**, in manifest order —\n the epic parent, then each child; or each ordinary sibling. Each invocation\n is bound to exactly one entry and receives, verbatim:\n - the entry's identity, position in the order, scope boundary, **size band**,\n parent relationship, `depends_on`, and `recommended_after`;\n - the acceptance criteria and design material for that entry's slice;\n - the shared research, materials, and framing artifacts;\n - an explicit output path for the drafted body.\n\n State in every prompt that the decomposition is **frozen**: the invocation\n renders its entry and may not add, remove, merge, reorder, renumber, or\n rescope anything, and may not write about sibling entries as though it were\n deciding them.\n\n3. **Do not batch.** One invocation per entry, never one invocation asked to emit\n every body. Two failure modes are both real and this shape avoids both: N\n fully independent decisions overlap, omit dependencies, and contradict the\n parent — which is why the decomposition is frozen upstream rather than\n re-derived per child; and one invocation emitting the whole set produces\n unreliable output as the manifest grows. Separating the decision from the\n rendering is what lets the rendering fan out safely, so a large manifest is\n handled by *more* invocations, never by collapsing back to one.\n\n4. Every entry type gets the same treatment — the same research protocol,\n materials inventory, secret-redaction rules, required sections, and Jira\n description bounds. An epic parent is not a thinner document than a\n standalone ticket, and an epic child is not a thinner document than either.\n\n5. When every entry has a draft, re-run the preflight checks against the\n rendered set and hand off to creation:\n - **Epic** — `upload-epic-hierarchy.md`, unchanged. It creates the parent with\n `issue_type = \"Epic\"`, captures the resolved epic key, then calls\n `create_ticket(parent_key=<epic_key>)` per child in manifest order with\n `track_ticket` after each, and carries idempotency labels throughout so a\n partial failure resumes rather than duplicating. Nothing new is required to\n *create* an epic — only to propose one.\n - **Siblings** — ordinary `create_ticket` calls in manifest order, unparented,\n with no epic parent synthesized.\n\n6. This stage emits **no conductor invocation**. The handoff belongs to the\n calling surface and names exactly one entry point, `drive-epic`.\n\n## Return\n\nReport the manifest's shape (`epic` or `siblings`), the entry count, each entry's\ndrafted body path in manifest order, and the preflight verdict. On a preflight\nfailure, name the violated prerequisite and the offending entry, and confirm that\nno ticket was created.\n",
911
+ "render-ticket-manifest.md": "Render every entry of an approved ticket manifest into a drafted body, one `jira-ticket-writer` invocation per entry, without re-deciding the split.\n\nThis stage sits between **decomposition** (which decided the split once and froze\nit) and **creation** (`upload-epic-hierarchy.md` for an epic, ordinary\n`create_ticket` calls for siblings). It owns exactly one job: turning frozen\nmanifest entries into drafted bodies. It decides nothing.\n\n## Inputs\n\n- **The approved manifest** — the ordered plan the decomposition pass froze.\n On the `idea-to-ticket` path this is `decomposition-plan.json` in the run\n directory, produced by `decompose-epic-candidate.md`. On the `plan-epic` path\n it is the sub-task list in `epic-plan.md`, produced by `decompose-epic.md`. On\n a client-driven path (`/define-tickets` Stage 9) it is the approved outline\n shown at the gate.\n- **The approval state** for that manifest. Whether approval is an explicit\n affirmative or a pipeline auto-approval variable is the calling surface's own\n rule; this stage only needs to know it resolved.\n- **The research and framing artifacts** each entry's writer invocation needs —\n the same set the standalone drafting path passes.\n\n## Preflight — fail closed, before any body is rendered\n\nCheck all of the following first. Each one is a **hard failure** that stops the\nflow before a single writer invocation and long before any Jira mutation. None\nof them degrades to a partial run:\n\n1. **Approval unresolved.** The manifest's approval gate has not resolved, or\n resolved as declined. Nothing renders and nothing is created.\n2. **Manifest identity changed.** The manifest presented here is not the one that\n was approved — a different entry count, different ordering, renumbered\n entries, or an altered scope boundary. A substituted split never silently\n replaces the approved one.\n3. **An entry past the size ceiling.** An entry whose stated scope runs beyond\n roughly 40 files or ~3000 LOC is over the bound wherever it sits — a\n standalone ticket and an epic child alike. Fail and name the entry; do not\n draft it, do not split it here, and do not shrink its stated scope to fit. An\n `XL` entry below that ceiling is not a violation and needs no supporting\n evidence: `XL` is the preferred shape for work that does not fit in `L`.\n4. **An entry with no writer draft after rendering.** Every entry must have\n produced a body. A missing draft is a failure, not an entry to skip.\n\nFail-open applies **only** to enrichment and context gathering — an optional\nresearch artifact that is missing may degrade the context a body is written\nfrom. Approval, manifest identity, sizing validity, and writer-output\ncompleteness all fail closed.\n\n## Instructions\n\n1. Read the approved manifest in full. Record its entry count, its order, and\n each entry's identity, so the identity check above has something to compare\n against.\n\n2. **Fan out one `jira-ticket-writer` invocation per entry**, in manifest order —\n the epic parent, then each child; or each ordinary sibling. Each invocation\n is bound to exactly one entry and receives, verbatim:\n - the entry's identity, position in the order, scope boundary, **size band**,\n parent relationship, `depends_on`, and `recommended_after`;\n - the acceptance criteria and design material for that entry's slice;\n - the shared research, materials, and framing artifacts;\n - an explicit output path for the drafted body.\n\n State in every prompt that the decomposition is **frozen**: the invocation\n renders its entry and may not add, remove, merge, reorder, renumber, or\n rescope anything, and may not write about sibling entries as though it were\n deciding them.\n\n3. **Do not batch.** One invocation per entry, never one invocation asked to emit\n every body. Two failure modes are both real and this shape avoids both: N\n fully independent decisions overlap, omit dependencies, and contradict the\n parent — which is why the decomposition is frozen upstream rather than\n re-derived per child; and one invocation emitting the whole set produces\n unreliable output as the manifest grows. Separating the decision from the\n rendering is what lets the rendering fan out safely, so a large manifest is\n handled by *more* invocations, never by collapsing back to one.\n\n4. Every entry type gets the same treatment — the same research protocol,\n materials inventory, secret-redaction rules, required sections, and Jira\n description bounds. An epic parent is not a thinner document than a\n standalone ticket, and an epic child is not a thinner document than either.\n\n5. When every entry has a draft, re-run the preflight checks against the\n rendered set and hand off to creation:\n - **Epic** — `upload-epic-hierarchy.md`, unchanged. It creates the parent with\n `issue_type = \"Epic\"`, captures the resolved epic key, then calls\n `create_ticket(parent_key=<epic_key>)` per child in manifest order with\n `track_ticket` after each, and carries idempotency labels throughout so a\n partial failure resumes rather than duplicating. Nothing new is required to\n *create* an epic — only to propose one.\n - **Siblings** — ordinary `create_ticket` calls in manifest order, unparented,\n with no epic parent synthesized.\n\n6. This stage emits **no conductor invocation**. The handoff belongs to the\n calling surface and names exactly one entry point, `drive-epic`.\n\n## Return\n\nReport the manifest's shape (`epic` or `siblings`), the entry count, each entry's\ndrafted body path in manifest order, and the preflight verdict. On a preflight\nfailure, name the violated prerequisite and the offending entry, and confirm that\nno ticket was created.\n",
912
912
  "research-decision.md": "Decide which research tools to run for this idea, biased toward cheap local research first.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json` (must already exist from the preflight step).\n\n## Instructions\n\n1. Read `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`. This is the source of truth for `idea`, `readiness`, `scope`, and `run_id`. If the file does not exist, halt locally — the preflight step did not complete.\n\n2. Decide which research tools should run for this idea, in roughly this priority order:\n - **Local codebase research first.** Inspect the working tree (search, grep, file reads) for prior art, related modules, and existing tests. Prefer this for anything that touches code you already own.\n - **Narrow web search second.** Use targeted web search for short factual lookups: a specific library API, a known external standard, a public spec.\n - **Deep research only when justified.** Deep research is expensive and slow; it must be earned by one of the rubric items below.\n\n3. Deep-research allowance rubric. Deep research is only allowed when at least one of these is true:\n - **blast radius**: the change spans many systems or has high reversibility cost (e.g., schema migrations, auth, billing, public APIs).\n - **unfamiliar external domain**: the idea depends on a third-party domain or specification the repository has no prior coverage of.\n - **compliance/security uncertainty**: there is real compliance or security uncertainty (SOC2, PII, secret handling, access control).\n - **cheaper research failed**: a cheaper round (local + narrow web search) already happened in this run and left blocking unknowns.\n - **explicit user request**: the user explicitly asked for deep research.\n\n4. Write `{docs_dir}/idea-to-ticket/{slug}-{run_id}/research-plan.json`. Required fields:\n - `selected_tools` — array of tool identifiers to run, drawn from at least `[\"codebase_search\", \"web_search\", \"deep_research\"]`. Empty array is allowed when no research is needed.\n - `rationale` — short string explaining the choice in terms of the rubric above.\n - `deep_research_query` — string. Required when `deep_research` is in `selected_tools`, otherwise empty string.\n - `web_search_topics` — array of strings; may be empty.\n - `codebase_search_topics` — array of strings; may be empty.\n - `expected_unknowns` — array of strings describing what the research is expected to resolve.\n\n5. Do not invoke any research tool from this step — that happens in `execute-research.md`. This step only writes the plan.\n\n## Return\n\nConfirm `research-plan.json` was written, list `selected_tools`, and quote the rationale.\n",
913
913
  "screen-and-resolve.md": "Apply project standards and the minimum-evidence gate before drafting.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`.\n- Research pack: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/research-pack.md` / `.json` (may be partial or absent).\n- Duplicate assessment: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/duplicate-assessment.json`.\n- Project standards: the response from the earlier `get_project_standards` step in the same pipeline run. Treat the standards as unavailable when that response was an error envelope, a 404, missing, or contains the onboarding placeholder sentence \"No project standards are configured yet for\" (a 200 response body that is telling the reader how to get standards, not standards content — it can appear alone or appended after a populated `## Project Context` section).\n\n## Instructions\n\n1. Produce three artifacts in the run directory:\n - `{docs_dir}/idea-to-ticket/{slug}-{run_id}/standards-checklist.json`\n - `{docs_dir}/idea-to-ticket/{slug}-{run_id}/open-questions.md`\n - `{docs_dir}/idea-to-ticket/{slug}-{run_id}/resolved-uncertainties.md`\n\n2. Standards checklist:\n - When `get_project_standards` returned a usable result — real standards content, not the onboarding placeholder — derive the checklist items from those standards. Each item is a `{requirement, satisfied, evidence}` triple where `evidence` either cites the research pack or notes \"deferred to draft\".\n - When standards are unavailable (404, error envelope, missing, or the onboarding placeholder), generate a fallback baseline checklist covering at least: requirements clarity, acceptance criteria presence, testability, security/PII consideration, and rollback/observability when scope warrants it. Mark each as `satisfied: false` with `evidence: \"fallback baseline — no project standards available\"`.\n\n3. Open questions:\n - List every unresolved unknown that blocks drafting. Pull from the research pack's `unresolved_unknowns` and from your own reading of `idea`.\n - Each question gets its own bullet (no markdown tables, no `- [ ]` checkboxes — BAPI-320 hygiene).\n - If the question has a defensible best-guess answer, write it in `resolved-uncertainties.md` instead, with explicit assumption language (\"Assuming X because Y...\").\n\n4. Resolved uncertainties:\n - Mirror open questions that have defensible best-guess answers. Each entry must include `assumption`, `basis` (research-pack reference or codebase reference), and `confidence` (\"low\", \"medium\", \"high\").\n\n5. Minimum-evidence gate. Halt locally if ALL of the following are true:\n - The research pack contains no codebase references for the idea.\n - The standards checklist has zero items (even the fallback baseline is missing).\n - `resolved-uncertainties.md` records no explicit assumptions.\n When the gate fires, do not continue to drafting. Report the halt and direct the user to either run a smaller idea, run `--allow-duplicate` semantics for replays, or supply more context manually.\n\n## Return\n\nConfirm the three artifacts were written and whether the minimum-evidence gate fired.\n",
914
914
  "store-and-approve-epic-plan.md": "Store the approved epic plan DAG in the backend and approve it.\n\nThis step runs after the user has approved the decomposition in the\n`decompose-epic` step. It reads the machine-readable sidecar written by that\nstep and wires it into the backend durable store.\n\nThe whole step is a single deterministic command — `setup-epic`. Do **not**\nhand-roll the HTTP calls, and do **not** compute the plan hash yourself.\n\n## Variables\n\n- `{epic_key}` — Jira epic key (e.g. BAPI-405)\n- `{epic_slug}` — lowercase-hyphen slug derived from the epic key\n- `{docs_dir}` — base docs directory (e.g. `docs/tmp`)\n\n## Step 1 — Confirm the DAG sidecar exists\n\nThe sidecar written by `decompose-epic` lives at\n`{docs_dir}/epic-plans/{epic_slug}/epic-plan.dag.json`.\n\nIf it is missing, warn the user with:\n\n> \"The structured DAG sidecar (`epic-plan.dag.json`) is missing or invalid.\n> The plan cannot be stored automatically. To recover, you can reconstruct the\n> DAG manually by parsing the Jira dependency links for each sub-task\n> (deterministic Jira-link DAG builder — documented fallback, not built here).\"\n\nThen stop this step with a warning (do not raise an error that aborts the\nentire pipeline).\n\nThe DAG must always come from this sidecar — **never from re-parsing the\nmarkdown** (`epic-plan.md`).\n\n## Step 2 — Run `setup-epic`\n\n```bash\nnpx -y @bridge_gpt/mcp-server setup-epic \\\n --epic-key {epic_key} \\\n --plan-file {docs_dir}/epic-plans/{epic_slug}/epic-plan.dag.json \\\n --json\n```\n\nThis creates the epic run, stores the plan blob, and approves it — in the one\norder that is safe. It is idempotent: re-running it on an epic that already has\na live run reuses that run rather than creating a second one.\n\n**Never work around a failure by POSTing to `/jira/epic-runs/runs` directly.**\nCreating a second active run for an epic wedges it permanently — every later\nplan call fails with \"Multiple active runs\" — and double-charges billing.\n\nPass `--dry-run` first if you want to validate the plan and preview the calls\nwithout changing anything.\n\n## Step 3 — Interpret the result\n\n`setup-epic` exits `0` on success and prints a JSON object with `epic_run_id`,\n`plan_version`, `plan_hash`, `status`, and any `warnings`.\n\nOn a non-zero exit, relay its error message verbatim — it is already actionable.\nThe three you are most likely to see:\n\n- **\"already stored with a DIFFERENT hash\"** — the plan blob changed after being\n stored. Increment `plan_version` in the sidecar and re-run.\n- **\"MULTIPLE active runs\"** — the epic is wedged. It must be repaired by\n abandoning the duplicate run before anything else can proceed.\n- **\"no touched_files\"** — a **warning**, not a rejection. The three cases are\n distinct and only one of them fails:\n - **Omitted entirely** — the node **opts out** of preemptive file-overlap\n serialization. The plan is stored and approved normally. That node is simply\n never pre-serialized against an overlapping sibling; if a sibling merge does\n conflict with it, the conflict is caught reactively at the merge gate and\n rebased. Adding the files it is predicted to touch buys back the scheduling\n protection, and is worth doing, but is not required.\n - **`touched_files: []`** — an explicit \"no predicted files\" **declaration**.\n Different from omission: the node is treated as declared, and its pairs are\n evaluated normally (and found non-overlapping).\n - **Malformed** — a non-list, or a list containing a blank, absolute, or\n non-string path — is still a hard `VALIDATION` **rejection**. Data you did\n supply must be well-formed. Fix the offending node and re-run.\n\n## Updating one approved node's `ticket_spec` mid-run\n\nA specification larger than Jira's 32,767-character description cap does **not**\nhave to be cut down. Store it on the approved plan node instead:\n\n```\nPATCH /epic-runs/runs/<epic_run_id>/plan/nodes/<ticket_key>/ticket-spec\nbody: repo_name, expected_plan_hash, ticket_spec\n```\n\n- Bounded at **64,000 characters**, stored verbatim — never truncated. The\n reconciler consults the plan node **first** when building a spec-review prompt,\n so the reviewer sees the complete text on the next tick.\n- **Safe mid-run.** It changes only that node's `ticket_spec` (plus the plan hash),\n and never the plan version, the approval pointers, or any `epic_ticket_status`\n row. No ticket is re-seeded and no in-flight gate state is disturbed.\n- **`expected_plan_hash` is required** and must match the currently approved hash.\n A stale hash on a real content change returns **409**; re-read the approved plan\n and retry. Re-sending content that already matches is an idempotent no-op\n (`updated: false`) and succeeds even with a stale hash, because nothing would be\n overwritten.\n- This is **not** the same as re-storing the full plan at a new version and\n re-approving it. That path re-seeds ticket rows and moves approval pointers, and\n remains unsafe for casual mid-run spec edits.\n\nA `plan_hash` that differs from the locally computed hash is **not** an error:\nthe server re-hashes after applying file-overlap serialization, and the server's\nhash is authoritative.\n\n## Return\n\nReport:\n- The epic run ID.\n- The stored `plan_version`.\n- The `plan_hash` returned by `setup-epic`.\n- Whether the run reached `active`.\n- Any warnings.\n\nExample: \"Plan v1 stored and approved for epic run `<epic_run_id>`. Run is now\nactive — the server-side reconciler will pick it up within ~30s.\"\n",
@@ -469,6 +469,27 @@ async function runUpAction(options, sinks, overrides) {
469
469
  }
470
470
  return launch.exitCode;
471
471
  }
472
+ /**
473
+ * Format one diagnostic as one or more sink lines (BAPI-1237).
474
+ *
475
+ * `formatDiagnostic` returns a single string, but a diagnostic's `message` can
476
+ * itself be multiline — the shared legacy-environment guard's message is its
477
+ * own fixed header plus one `key -> successor` line per offending key. Writing
478
+ * that whole string through a single sink call would bury every line after the
479
+ * first inside one write, so this splits on newline boundaries and lets each
480
+ * substantive line become its own element — preserving the deterministic order
481
+ * the guard already produced.
482
+ *
483
+ * The final EMPTY element is dropped, never any other: it exists only because
484
+ * `runLegacyEpicImplementerEnvPreflight`'s formatter ends its block with a
485
+ * trailing newline, which is block punctuation, not a line to render.
486
+ */
487
+ function formatDiagnosticLines(diagnostic) {
488
+ const lines = formatDiagnostic(diagnostic).split("\n");
489
+ if (lines.length > 0 && lines[lines.length - 1].length === 0)
490
+ lines.pop();
491
+ return lines;
492
+ }
472
493
  /**
473
494
  * The aggregate preflight refusal, as lines.
474
495
  *
@@ -482,7 +503,7 @@ export function formatPlanePreflightRefusal(preflight) {
482
503
  "plane up REFUSED — preflight found blocking problems:",
483
504
  ...preflight.diagnostics
484
505
  .filter((d) => d.severity === "blocking")
485
- .map((d) => formatDiagnostic(d)),
506
+ .flatMap((d) => formatDiagnosticLines(d)),
486
507
  "",
487
508
  `Fix every item above and retry. ${PLANE_NOTHING_STARTED}.`,
488
509
  ];
@@ -792,7 +813,8 @@ async function runRuntimeAction(options, overrides) {
792
813
  sinks.stderr("");
793
814
  sinks.stderr("plane runtime REFUSED — preflight found blocking problems:");
794
815
  for (const diagnostic of preflight.diagnostics.filter((d) => d.severity === "blocking")) {
795
- sinks.stderr(formatDiagnostic(diagnostic));
816
+ for (const line of formatDiagnosticLines(diagnostic))
817
+ sinks.stderr(line);
796
818
  }
797
819
  sinks.stderr("");
798
820
  sinks.stderr(`${PLANE_NOTHING_STARTED}.`);
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import path from "path";
15
15
  import { PLANE_SERVER_PORT_ENV_VAR, planeCheckFinding, planeCheckPassed, } from "./types.js";
16
+ import { runLegacyEpicImplementerEnvPreflight } from "../epic-implementer-env.js";
16
17
  import { checkPlaneBuildFreshness, checkPlaneRuntimeEntrypoint } from "./build-freshness.js";
17
18
  import { checkAlembicHead } from "./alembic-head.js";
18
19
  import { manifestHasLiveProcess, planeManifestLifecycle, probeManifestMembers, readPlaneManifest, } from "./manifest.js";
@@ -163,6 +164,50 @@ const ROOT_DEPENDENT_CHECKS = [
163
164
  ["alembic-head", "alembic-head", "Database migration head"],
164
165
  ["existing-plane", "existing-plane", "Existing plane"],
165
166
  ];
167
+ /** Fixed detail for a prerequisite skipped because the legacy-environment guard refused first. */
168
+ const PLANE_LEGACY_ENVIRONMENT_SKIP_DETAIL = "not run — the legacy-environment guard refused first";
169
+ /**
170
+ * Every prerequisite `runPlanePreflight` short-circuits past when the shared
171
+ * legacy-environment guard finds an old name (BAPI-1237).
172
+ *
173
+ * `repository-root` is included here, unlike {@link ROOT_DEPENDENT_CHECKS}: the
174
+ * legacy-environment guard is the FIRST check, running even before repository
175
+ * root is confirmed, so a hit skips everything else the same way a bad root
176
+ * skips everything after it.
177
+ */
178
+ const LEGACY_ENVIRONMENT_DEPENDENT_CHECKS = [
179
+ ["repository-root", "repository-root", "Repository root"],
180
+ ...ROOT_DEPENDENT_CHECKS,
181
+ ];
182
+ /** Fixed remediation for a legacy Conductor / dead-man environment variable still set. */
183
+ export const PLANE_LEGACY_ENVIRONMENT_REMEDIATION = "rename or remove each legacy variable named above to its EPIC_IMPLEMENTER_* successor, then retry.";
184
+ /**
185
+ * Run the shared legacy Epic Implementer / dead-man environment guard as a
186
+ * plane preflight check (BAPI-1237).
187
+ *
188
+ * Key-only and non-mutating: this adapter never reads a legacy value,
189
+ * translates a key, deletes a key, or constructs a successor value from a
190
+ * supplied value. It only asks the shared guard — the same one `epic-implementer-env.ts`
191
+ * exports for every other guarded entry point — whether any old name is
192
+ * present in the INJECTED preflight environment, and renders its one fixed
193
+ * multiline diagnostic as a blocking finding when it is.
194
+ */
195
+ function checkLegacyEnvironment(env) {
196
+ const label = "Legacy environment";
197
+ let captured = "";
198
+ const hit = runLegacyEpicImplementerEnvPreflight(env, (chunk) => {
199
+ captured += chunk;
200
+ });
201
+ if (hit === 0) {
202
+ return planeCheckPassed("legacy-environment", label, "no legacy Conductor or dead-man environment variable is set");
203
+ }
204
+ return planeCheckFinding({
205
+ check: "legacy-environment",
206
+ severity: "blocking",
207
+ message: captured,
208
+ remediation: PLANE_LEGACY_ENVIRONMENT_REMEDIATION,
209
+ }, label, "a legacy Conductor or dead-man environment variable is still set");
210
+ }
166
211
  /** Bound on the port probe so a black-holed port cannot stall bring-up. */
167
212
  export const PLANE_PORT_PROBE_TIMEOUT_MS = 1_500;
168
213
  /**
@@ -188,6 +233,23 @@ export async function runPlanePreflight(repoRoot, deps) {
188
233
  outcomes.push(...report.outcomes);
189
234
  return report;
190
235
  };
236
+ // BAPI-1237: the shared legacy-environment guard runs FIRST — before
237
+ // repository inspection, filesystem setup, subprocess execution, port
238
+ // probing, or credential resolution — so a legacy Conductor/dead-man name
239
+ // blocks before any of those collaborators run, not merely before launch.
240
+ const legacyEnvironment = record(checkLegacyEnvironment(deps.env));
241
+ if (legacyEnvironment.diagnostic) {
242
+ for (const [check, id, label] of LEGACY_ENVIRONMENT_DEPENDENT_CHECKS) {
243
+ outcomes.push({
244
+ check,
245
+ id,
246
+ label,
247
+ status: "skip",
248
+ detail: PLANE_LEGACY_ENVIRONMENT_SKIP_DETAIL,
249
+ });
250
+ }
251
+ return { ok: false, diagnostics, outcomes };
252
+ }
191
253
  const rootCheck = record(await checkRepositoryRoot(repoRoot, deps));
192
254
  // Every remaining check is relative to the repository root. Without a valid
193
255
  // one there is nothing coherent left to check, so report and stop here rather
@@ -803,6 +865,12 @@ async function fileExists(filePath, fs) {
803
865
  * yield every prerequisite as an explicit unknown.
804
866
  */
805
867
  export const PLANE_READINESS_DESCRIPTORS = [
868
+ // BAPI-1237 — the shared legacy-environment guard is the first check
869
+ // `runPlanePreflight` runs, so it leads this descriptor table too. Omitting
870
+ // it here would make the check invisible to `epic-implementer readiness` /
871
+ // `drive-epic` even though it can block `plane up` — exactly the "silence
872
+ // reads as pass" failure this table exists to rule out.
873
+ { id: "legacy-environment", label: "Legacy environment" },
806
874
  { id: "repository-root", label: "Repository root" },
807
875
  { id: "worktree-presence", label: "Git work tree" },
808
876
  { id: "claude-login", label: "Claude login" },
@@ -1172,6 +1172,12 @@ function describeBaselineFailure(reason) {
1172
1172
  * sitting out the remaining budget. A worker that crashed on its first tick is
1173
1173
  * the case this readiness gate exists for, and reporting it two minutes late as
1174
1174
  * a timeout would describe the symptom rather than the cause.
1175
+ *
1176
+ * BAPI-1260 — what this does NOT prove. A member that is ready here is a
1177
+ * PROCESS fact about this plane's own identified member. It is not an enrolled
1178
+ * actor handshake: whether an actor can consume a privileged operation is the
1179
+ * server's `durable_actor` admission evidence, and `drive-epic` keeps the two
1180
+ * separate — process readiness admits scoped lanes, never dependent dispatch.
1175
1181
  */
1176
1182
  export async function waitForMemberReady(spec, deps, pollIntervalMs = 250, isAlive = () => true, heartbeat = {}) {
1177
1183
  if (!spec.readiness)