@bridge_gpt/mcp-server 0.2.59 → 0.2.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -51
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +53 -2
- package/build/conductor/doctor.js +255 -53
- package/build/conductor/epic-runtime.js +11 -2
- package/build/conductor/install-doctor.js +24 -2
- package/build/conductor/local-merge.js +319 -26
- package/build/conductor/merge-execution-contract.js +61 -0
- package/build/conductor/merge-execution-eligibility.js +129 -0
- package/build/conductor/readiness-sections.js +159 -2
- package/build/conductor/readiness.js +95 -14
- package/build/conductor/recovery-operations.js +8 -2
- package/build/connect-bitbucket-api.js +6 -1
- package/build/connect-bitbucket.js +398 -88
- package/build/credential-store.js +16 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +183 -9
- package/build/drive-epic.js +23 -0
- package/build/epic-implementer-bin.js +10 -10
- package/build/epic-implementer-claude-hook-bin.js +1 -1
- package/build/epic-implementer-env.js +43 -14
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +31 -0
- package/build/executor/credentials.js +11 -0
- package/build/executor/env.js +60 -0
- package/build/executor/index.js +21 -4
- package/build/executor/job-runner.js +126 -0
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +11 -2
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +5 -0
- package/build/index.js +194 -179
- package/build/install-bridge-conductor.js +201 -8
- package/build/install-bridge.js +356 -30
- package/build/mcp-install-state.js +42 -4
- package/build/merge-pull-request.js +103 -10
- package/build/merge-workflow-codeowners.js +224 -0
- package/build/merge-workflow-config.js +133 -0
- package/build/merge-workflow-drift.js +66 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +572 -0
- package/build/pipelines.generated.js +2 -2
- package/build/plane/cli.js +24 -2
- package/build/plane/preflight.js +68 -0
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +163 -9
- package/build/version.generated.js +3 -3
- package/package.json +3 -3
- package/build/conduct-epic/bridge-client.js +0 -773
- package/build/conduct-epic/checkpoint-store.js +0 -542
- package/build/conduct-epic/cli.js +0 -3145
- package/build/conduct-epic/cut-protocol.js +0 -392
- package/build/conduct-epic/lock.js +0 -302
- package/build/conduct-epic/pr-state.js +0 -286
- package/build/conduct-epic/spawn.js +0 -113
- package/build/conductor/cli.js +0 -1278
- package/build/conductor-bundle-cli.js +0 -256
- package/build/direct-ticket-tools.js +0 -70
- package/build/executor/reconcile-overlap-governance.js +0 -129
- package/build/implement-epic/bridge-client.js +0 -773
- package/build/implement-epic/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- package/build/implement-epic/cut-protocol.js +0 -392
- package/build/implement-epic/lock.js +0 -302
- package/build/implement-epic/pr-state.js +0 -286
- package/build/implement-epic/spawn.js +0 -113
- package/build/plan-epic-conductor-eligibility.js +0 -213
|
@@ -906,9 +906,9 @@ export const INSTRUCTIONS = {
|
|
|
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 (`/
|
|
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",
|
package/build/plane/cli.js
CHANGED
|
@@ -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
|
-
.
|
|
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
|
-
|
|
816
|
+
for (const line of formatDiagnosticLines(diagnostic))
|
|
817
|
+
sinks.stderr(line);
|
|
796
818
|
}
|
|
797
819
|
sinks.stderr("");
|
|
798
820
|
sinks.stderr(`${PLANE_NOTHING_STARTED}.`);
|
package/build/plane/preflight.js
CHANGED
|
@@ -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" },
|