@bridge_gpt/mcp-server 0.2.51 → 0.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +59 -13
  2. package/build/agent-capabilities/probe-context.js +15 -7
  3. package/build/agent-capabilities/probes.js +42 -6
  4. package/build/agent-launchers/claude-executor-adapter.js +98 -14
  5. package/build/commands.generated.js +7 -5
  6. package/build/conduct-epic/cut-protocol.js +17 -3
  7. package/build/conductor/bridge-api-client.js +232 -5
  8. package/build/conductor/cli.js +23 -0
  9. package/build/conductor/deny-enforcement-preflight.js +107 -10
  10. package/build/conductor/doctor.js +428 -5
  11. package/build/conductor/install-doctor.js +65 -656
  12. package/build/conductor/local-merge.js +170 -11
  13. package/build/conductor/readiness-cli.js +152 -0
  14. package/build/conductor/readiness-sections.js +666 -0
  15. package/build/conductor/readiness.js +710 -0
  16. package/build/conductor/tools.js +56 -3
  17. package/build/conductor-bin.js +21 -17
  18. package/build/connect-bitbucket-api.js +370 -0
  19. package/build/connect-bitbucket.js +437 -0
  20. package/build/docs.generated.js +1 -1
  21. package/build/doctor.js +40 -1
  22. package/build/drive-epic.js +423 -11
  23. package/build/env-file-link.js +164 -0
  24. package/build/epic-integration-pr.js +10 -0
  25. package/build/executor/cli.js +41 -6
  26. package/build/executor/deps.js +5 -1
  27. package/build/executor/env-file-guard.js +113 -0
  28. package/build/executor/env.js +78 -1
  29. package/build/executor/heartbeat.js +9 -0
  30. package/build/executor/http-client.js +90 -22
  31. package/build/executor/job-errors.js +43 -2
  32. package/build/executor/job-runner.js +130 -28
  33. package/build/executor/merge-job.js +67 -16
  34. package/build/executor/permissions.js +106 -0
  35. package/build/executor/preflight.js +38 -13
  36. package/build/executor/resume-pre-spawn.js +2 -1
  37. package/build/executor/runner.js +175 -4
  38. package/build/executor/service-unit.js +15 -0
  39. package/build/executor/terminal-mutation.js +22 -1
  40. package/build/executor/types.js +86 -0
  41. package/build/executor/worker-command.js +21 -5
  42. package/build/executor/worker-guard-hook.js +939 -0
  43. package/build/executor/worker-log.js +56 -0
  44. package/build/executor/worktree.js +11 -0
  45. package/build/git-reachability.js +147 -0
  46. package/build/index.js +4734 -4270
  47. package/build/install-bridge.js +95 -0
  48. package/build/install-doctor.js +154 -2
  49. package/build/pipelines.generated.js +6 -4
  50. package/build/plan-epic-conductor-eligibility.js +37 -7
  51. package/build/plane/alembic-head.js +40 -11
  52. package/build/plane/build-freshness.js +22 -11
  53. package/build/plane/cli.js +78 -15
  54. package/build/plane/defaults.js +165 -0
  55. package/build/plane/manifest.js +63 -8
  56. package/build/plane/member-logs.js +6 -0
  57. package/build/plane/member-roster.js +195 -11
  58. package/build/plane/preflight.js +402 -44
  59. package/build/plane/shutdown.js +25 -3
  60. package/build/plane/status.js +11 -0
  61. package/build/plane/supervisor.js +343 -14
  62. package/build/plane/test-fakes.js +43 -0
  63. package/build/plane/types.js +118 -11
  64. package/build/pr-base-contract.js +20 -0
  65. package/build/readiness-check.js +412 -0
  66. package/build/readme.generated.js +1 -1
  67. package/build/review-synthesis-config.js +60 -0
  68. package/build/scripts/executor-protocol-contract-driver.js +311 -0
  69. package/build/setup-epic.js +560 -139
  70. package/build/sfcc/log-query.js +2 -1
  71. package/build/start-tickets-conductor.js +11 -2
  72. package/build/start-tickets.js +69 -2
  73. package/build/version.generated.js +3 -3
  74. package/build/worker-containment-diagnostic.js +97 -0
  75. package/build/worker-guard-hook-bin.js +6 -0
  76. package/docs/CONDUCTOR.md +27 -0
  77. package/docs/install/mcp-tool-integrations.md +3 -2
  78. package/package.json +4 -3
  79. package/pipelines/{full-automation.json → idea-to-pr.json} +1 -1
@@ -862,9 +862,9 @@ export const PIPELINES = {
862
862
  }
863
863
  };
864
864
  export const INSTRUCTIONS = {
865
- "assess-conductor-eligibility.md": "Assess conductor merge/review eligibility for the frozen epic child set.\n\n## Instructions\n\n1. Read the approved decomposition from `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md` to\n obtain each sub-task's frozen title and scope. Do NOT re-split or rescope children here —\n the decomposition is frozen; this stage only classifies it.\n\n2. For each sub-task, also read its exploration document\n `{docs_dir}/epic-plans/{epic_slug}/explorations/NN-{subtask-slug}.md` (written by\n `explore-epic-subtasks`) if it exists. Its Context, Relevant Code, and Recommendation\n sections are the sub-task's most complete available text — the closest thing to a\n description/requirements body that exists at this stage, since no Jira ticket has been\n created yet.\n\n3. Build one ordered child list, preserving decomposition order, where each child carries:\n - `id`: the sub-task's placeholder identifier (its position, e.g. `\"1\"`, or a slug — the\n real Jira key does not exist yet).\n - `title`: the sub-task title from `epic-plan.md`.\n - `scope`: the sub-task's Scope field from `epic-plan.md`.\n - `description`/`requirements`: the sub-task's exploration document content, when one\n exists; omit when it does not (missing text is treated as empty, never invented).\n\n4. Call the `assess_epic_conductor_eligibility` MCP tool exactly once with the complete\n ordered child list from step 3. Never call it per-child and never call it with a partial\n subset — a partial call cannot produce a meaningful epic-wide count.\n\n5. Write the tool's result verbatim as a structured JSON artifact to\n `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json`. The artifact's shape mirrors\n the tool's own result type:\n - On `\"status\": \"assessed\"`: `totalChildren`, `predictedWorkflowChildren`, `reason`,\n `reviewSubsetChildren`, and `affectedChildren` (each with `id`, `title`, `matchedPaths`,\n `requiresHandReview`).\n - On `\"status\": \"unavailable\"`: exactly that status, and nothing else. **Never** substitute\n zero counts or an empty `affectedChildren` list for an unavailable assessment — an\n unavailable result and a genuine zero-workflow-children result are different facts, and\n collapsing them into the same shape is exactly the failure this stage exists to prevent.\n\n6. This stage performs no other action. It does not start, dispatch, or spawn any worker, and\n it makes no Jira call — it is read frozen inputs, classify, write one artifact.\n\n## Return\n\nConfirm the artifact was written to `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json`\nand report its status: either the assessed counts (`N of M` children predicted to modify\nworkflow files, `K of those N` predicted to require hand review) or that the assessment was\nunavailable.\n",
865
+ "assess-conductor-eligibility.md": "Assess conductor merge/review eligibility for the frozen epic child set.\n\n## Instructions\n\n1. Read the approved decomposition from `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md` to\n obtain each sub-task's frozen title and scope. Do NOT re-split or rescope children here —\n the decomposition is frozen; this stage only classifies it.\n\n2. For each sub-task, also read its exploration document\n `{docs_dir}/epic-plans/{epic_slug}/explorations/NN-{subtask-slug}.md` (written by\n `explore-epic-subtasks`) if it exists. Its Context, Relevant Code, and Recommendation\n sections are the sub-task's most complete available text — the closest thing to a\n description/requirements body that exists at this stage, since no Jira ticket has been\n created yet.\n\n3. Build one ordered child list, preserving decomposition order, where each child carries:\n - `id`: the sub-task's placeholder identifier (its position, e.g. `\"1\"`, or a slug — the\n real Jira key does not exist yet).\n - `title`: the sub-task title from `epic-plan.md`.\n - `scope`: the sub-task's Scope field from `epic-plan.md`.\n - `description`/`requirements`: the sub-task's exploration document content, when one\n exists; omit when it does not (missing text is treated as empty, never invented).\n\n4. Call the `assess_epic_conductor_eligibility` MCP tool exactly once with the complete\n ordered child list from step 3. Never call it per-child and never call it with a partial\n subset — a partial call cannot produce a meaningful epic-wide count.\n\n5. Write the tool's result verbatim as a structured JSON artifact to\n `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json`. The artifact's shape mirrors\n the tool's own result type:\n - On `\"status\": \"assessed\"`: `totalChildren`, `predictedWorkflowChildren`, `reason`,\n `reviewSubsetChildren`, `containmentChildren`, and `affectedChildren` (each with `id`,\n `title`, `matchedPaths`, `requiresHandReview`, `requiresContainmentReview`,\n `matchedHazards`). `containmentChildren` is an independent ticket-text lower bound — a\n child whose text describes a destructive primitive or a change to conductor enforcement\n code — and is counted separately from `predictedWorkflowChildren`/`reviewSubsetChildren`.\n `affectedChildren` is the union of workflow-affected and containment-affected children, not\n a human-merge-only list: a containment-only child (no matched workflow paths) still appears\n here, with `requiresContainmentReview: true` and its bounded `matchedHazards`.\n - On `\"status\": \"unavailable\"`: exactly that status, and nothing else. **Never** substitute\n zero counts or an empty `affectedChildren` list for an unavailable assessment — an\n unavailable result and a genuine zero-workflow-children/zero-containment-children result are\n different facts, and collapsing them into the same shape is exactly the failure this stage\n exists to prevent.\n\n6. This stage performs no other action. It does not start, dispatch, or spawn any worker, and\n it makes no Jira call — it is read frozen inputs, classify, write one artifact.\n\n## Return\n\nConfirm the artifact was written to `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json`\nand report its status: either the assessed counts (`N of M` children predicted to modify\nworkflow files, `K of those N` predicted to require hand review, and `P of M` predicted to\ndescribe a containment hazard — reported independently, since a child can be containment-flagged\nwithout being workflow-modifying) or that the assessment was unavailable.\n",
866
866
  "assess-epic-research-needs.md": "Analyze the epic description and build a structured research plan.\n\n## Epic Description\n\n{epic_description}\n\n## Instructions\n\n1. Create the directory structure for this epic's artifacts:\n ```\n mkdir -p {docs_dir}/epic-plans/{epic_slug}\n ```\n\n2. Analyze the epic description above. Determine what external knowledge is required to plan this epic effectively. Consider:\n - Unfamiliar technologies, libraries, or frameworks mentioned\n - API documentation or integration specs that need to be consulted\n - Best practices or architectural patterns that require research\n - Domain-specific knowledge gaps\n\n3. Decide on a **Research Mode**:\n - **deep**: Use when the epic involves large, multi-faceted unknowns requiring synthesis from multiple sources (e.g., \"best practices for implementing WebSocket connection pooling in Python asyncio\").\n - **web**: Use for quick factual lookups — library API signatures, configuration syntax, small \"how to\" questions.\n - **none**: Use when the codebase exploration alone will provide sufficient context and no external knowledge is needed.\n\n4. Write a structured research plan to `{docs_dir}/epic-plans/{epic_slug}/research-plan.md` with these sections:\n\n```markdown\n# Research Plan\n\n## Research Mode\n{deep | web | none}\n\n## Deep Research Query\n{If mode is \"deep\": a single, well-crafted query for the deep research tool. Otherwise: \"N/A\"}\n\n## Web Search Topics\n{If mode is \"web\" or as fallback topics for \"deep\": a numbered list of specific search topics. Otherwise: \"N/A\"}\n\n## Rationale\n{Brief explanation of why this research mode was chosen and what knowledge gaps it addresses.}\n```\n\n## Return\n\nConfirm the research plan was written to `{docs_dir}/epic-plans/{epic_slug}/research-plan.md` and report the chosen Research Mode (`deep`, `web`, or `none`) along with a one-line rationale.\n",
867
- "capture-review-decisions.md": "Capture user decisions on review findings for {ticket_key} using the HTML decision page, then interpretively rewrite the clarifying questions and critique docs and upload both to Jira.\n\n## Step 1: Read source documents\n\nRead the combined review-and-resolution file:\n- `{docs_dir}/review/{ticket_key}-review-and-resolution.md`\n\nIf the file does not exist or is unreadable, stop and report: \"Combined review-and-resolution file not found or unreadable. Run the earlier pipeline steps first.\"\n\nThe combined file existing but containing no actionable items (empty `Needs Scrutiny` and `Open Questions` sections) is **not** a failure condition — Step 4 handles the no-decisions-needed flow gracefully when `generate_decision_page` is called with empty `actionable_items`.\n\n## Step 2: Map evaluation items to decision page input\n\nTransform the combined review-and-resolution document into `generate_decision_page` JSON input using these mapping rules:\n\n| Evaluation Section | JSON Field | Mapping Rule |\n|---|---|---|\n| Open Questions | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, combined `**Assessment**` paragraph and `**Codebase Evidence**` bullet list → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Needs Scrutiny | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, combined `**Assessment**` paragraph and `**Codebase Evidence**` bullet list → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Confirmed Improvements | `clear_improvements` | E-item title → `title`, confidence tag → `confidence`, recommended action → `action`, `**Source**` from the combined file → `source` |\n\n**Important**: The `original_question`, `why_it_matters`, `option_consequences`, `recommendation_explanation`, and the collapsed `codebase_evidence` block together replace the old single `context` blob. Each clarity field guides a different facet of the user's decision: `original_question` reminds the reviewer what was asked, `why_it_matters` frames the impact, `option_consequences` describe the behavioral outcome of each branch, `recommendation_explanation` motivates the recommended branch, and the closed-by-default `codebase_evidence` block surfaces the Assessment + file:line citations on demand without overwhelming the card.\n\nFor each actionable item, the `options` array is a list of plain label strings extracted from the combined file's decision tree branches. The tool auto-generates value keys (`opt-0`, `opt-1`, etc.) and auto-appends a \"None of these\" option. Do not generate value keys yourself.\n\n## Step 2.5: Auto-approve fast path\n\nFor this run, `auto_approve` = `{auto_approve}`.\n\nIf `auto_approve` is `true` and Step 2 produced at least one actionable item, skip Steps 3–6 entirely and synthesize the commit JSON directly:\n\n- `ticket_key`: `{ticket_key}`\n- `general_comment`: `\"\"`\n- `decisions`: an object keyed by each `actionable_items[*].id` from Step 2's mapped input. For each item:\n - If `recommendation_index` is a non-negative integer within range of `options`: `choice = \"opt-\" + recommendation_index`, `chosen_label = options[recommendation_index]`, `comment = \"\"`, `source` copied from the item.\n - Otherwise (missing, null, or out of range): `choice = \"opt-0\"`, `chosen_label = options[0]`, `comment = \"\"`, `source` copied. Never emit `\"none\"` and never emit `\"ask\"`.\n\nPost a single chat acknowledgement listing each auto-approved item ID and chosen label, then proceed directly to Step 7 with the synthesized JSON. Step 7's \"Hard rule\" about resolving `ask` items does not apply because no item carries `choice === \"ask\"`.\n\nThe synthesized settled decisions still receive the implications review described under Step 6's \"Implications review and proceed gate\" before Step 7 runs — literal `auto_approve = true` only skips the human proceed gate, not the review itself. \"Skip Steps 3–6\" above means skipping their interactive portions (rendering the page, waiting on chat, the Q&A loop); it does not exempt this fast path from the review obligation.\n\nIf Step 2 produced zero actionable items, fall through to Step 3 — Step 4's existing `no_decisions_needed` branch handles the empty case correctly.\n\nOtherwise (any value of `auto_approve` other than the literal `true` — including empty, `false`, or missing), proceed to Step 3.\n\n## Step 3: Call the MCP tool\n\nCall `generate_decision_page` with `ticket_key` at the root and the review arrays nested under `content`:\n\n**Always pass `content`, even when both arrays are empty.** Send `\"content\": { \"actionable_items\": [], \"clear_improvements\": [] }` rather than omitting the key — that is what reaches the `no_decisions_needed` branch Step 2 relies on. Omitting `content` entirely is rejected with a `VALIDATION_ERROR`, because root-level arrays are silently dropped by the tool's lean input schema and a missing wrapper is far more often a mistake than a deliberate empty call.\n\n```typescript\ninterface ReviewDecisionsContent {\n actionable_items?: Array<{\n id: string;\n question: string;\n why_it_matters: string; // required — concrete one-sentence impact\n recommendation_explanation: string; // required — why the recommended branch is best\n options: string[]; // 2-4 option labels\n option_consequences: string[]; // same length as options\n recommendation_index: number; // 0-based index into options\n original_question?: string; // optional display field\n codebase_evidence?: string; // optional display field — assessment + file:line\n source?: string; // optional source reference\n }>;\n clear_improvements?: Array<{\n id: string;\n title: string;\n action: string;\n confidence: string;\n source: string; // required for clear_improvements\n }>;\n}\n```\n\nExample call:\n```json\n{\n \"ticket_key\": \"{ticket_key}\",\n \"content\": {\n \"actionable_items\": [\n {\n \"id\": \"E-1\",\n \"question\": \"Should we add a configurable timeout?\",\n \"why_it_matters\": \"Timeout behavior affects retry paths and user-visible latency.\",\n \"recommendation_explanation\": \"Configurable matches existing latency-branching code.\",\n \"options\": [\"Keep existing\", \"Add configurable timeout\"],\n \"option_consequences\": [\"No new work.\", \"Implementers add config + tests.\"],\n \"recommendation_index\": 1,\n \"original_question\": \"Does the ticket specify timeout behavior?\",\n \"source\": \"Clarifying Q1\"\n }\n ],\n \"clear_improvements\": [\n { \"id\": \"ci-1\", \"title\": \"Tidy logging\", \"action\": \"Use the logger.\", \"confidence\": \"high\", \"source\": \"Eval 1\" }\n ]\n }\n}\n```\n\n## Step 4: Check tool response\n\nThe tool returns a JSON response with a `status` field:\n- If `status` is `\"no_decisions_needed\"`: skip Steps 5, 6, 7, and 8 entirely. Output a success message: \"No actionable review decisions needed — skipping doc rewrite and upload.\" This covers both the case where every item was confirmed as a Confirmed Improvement and the case where no items were emitted (e.g., both upstream source documents were absent).\n- If `status` is `\"decision_page_generated\"`: continue to Step 5. The response includes `file_path`.\n\n## Step 5: Direct user to the decision page\n\nTell the user to open the generated HTML file in their browser. Provide the `file_path` from the tool response. Then say to the user, verbatim: `Open the page. For any item you're unsure about, choose \"Ask about this\" — when you submit, I'll talk through those before we proceed. You can also ask me questions in chat before submitting if you prefer.`\n\nThis step only directs the user to the page and explains the two allowed next actions (submit selections, or ask questions first). Do not describe Step 7's rewrite semantics here; that belongs to the rewrite step.\n\n## Step 6: Q&A loop and commit signal\n\nEnter an open-ended Q&A loop. There is no turn cap — the user may ask any number of questions in any number of turns. Do not stop and wait silently; engage with each user message as either a commit signal or a discussion turn.\n\n### Proceed signal (commit)\n\nTrim the full user message and attempt to parse the entire trimmed message as JSON. The message is a commit only when the parsed value is an object with all three of these top-level fields:\n\n- `ticket_key` — must be a string\n- `decisions` — must be an object\n- `general_comment` — must be a string\n\nThe first valid commit-shaped JSON paste commits immediately. Proceed to Step 7 without prompting for additional confirmation. Any combination of `decisions` keys is accepted (the page may submit a partial set if the user only resolved some items conversationally). Do not over-validate the per-card fields beyond the top-level commit-shape check — the page guarantees the per-card schema, and over-validating risks rejecting valid pastes if the page schema evolves.\n\n### Discussion signal (Q&A turn)\n\nAnything that is not commit-shaped JSON is a discussion turn. This includes:\n\n- Freeform questions (with or without other text).\n- Questions pasted alongside other text or alongside JSON.\n- Malformed JSON (parse failure).\n- Well-formed JSON missing one or more of the required top-level keys (`ticket_key`, `decisions`, `general_comment`).\n\nFor JSON-shaped input that is missing required top-level fields, call this out in the reply — explain which fields are missing and ask whether the user intended to submit or share partial state — rather than silently treating it as a freeform question.\n\nAnswer discussion turns using these sources, in priority order:\n\n1. The combined `{ticket_key}-review-and-resolution.md` file already read in Step 1.\n2. The original `{ticket_key}-clarifying-questions.md` and `{ticket_key}-ticket-quality-critique.md` documents.\n3. Codebase lookups when the question requires verifying current code state.\n\nFallback: if running on a pre-PR1 branch where the combined review-and-resolution document does not exist, use the pre-PR1 `{ticket_key}-review-evaluation.md` and `{ticket_key}-resolution-guide.md` pair in its place.\n\nFor plain freeform questions, infer the item from chat context when possible.\n\n### In-flight decision state\n\nDuring the Q&A loop, maintain in-flight JSON state — agent-owned working memory representing the user's current intent for `decisions` and `general_comment`. This in-flight JSON state lives only in the agent's working memory for the duration of the loop; do not persist it server-side.\n\n- When the user clearly changes their mind about an item, chooses an option conversationally with reasonably explicit decision language (\"choose option B for E-3\", \"go with the configurable timeout\", \"change E-7 to None of these\"), or gives new overarching guidance, record that as an in-flight override.\n- Ambiguous preference language (\"I'm leaning toward...\", \"maybe option B is fine\") should be discussed but not recorded as an override unless the user gives reasonably explicit decision language.\n- `general_comment` may be updated in the in-flight state when the user gives overarching guidance during Q&A.\n- The page's general-comment textarea is preserved unchanged. Do not modify the page DOM during Q&A; the user can still fill the textarea before submitting if they prefer.\n\nOn the eventual JSON commit, the user-submitted JSON is the baseline and the recorded in-flight overrides take precedence over it. Before proceeding to Step 7, post a brief one-line acknowledgement in chat naming each overridden item ID and/or `general_comment`. The acknowledgement is mandatory (not optional) — it is the user's last chance to object before Step 7's document rewrite. The user does not need to re-open, edit, or re-submit the decision page after changing their mind in chat; they can submit the page as-is to provide the commit signal, and the in-flight state remains the source of truth for overrides.\n\n### Ask-about-this resolution\n\nAfter accepting a commit, scan `decisions` for any item where `choice === \"ask\"`. The user has signaled that they need more information before deciding on those items. For each such item:\n\n- If `comment` is non-empty, treat it as the user's specific question or stated uncertainty and answer that directly.\n- If `comment` is empty, proactively present the most relevant missing context — the item's `codebase_evidence`, related code lookups, prior-round answers — and lay out the trade-offs the user appears to need help weighing.\n- Continue the Q&A turn-by-turn until the user gives an explicit decision in chat for that item (\"go with option B\", \"none of these, because …\"). Record that decision as an in-flight override using the same override mechanism described above.\n\n**Hard rule.** Step 7 must not run while any `decisions[*].choice === \"ask\"` remains unresolved by an in-flight override. Do not honor \"just proceed\", \"skip those\", or any other instruction to defer resolution — every `ask` item must end with a recorded `opt-N` or `none` override before the rewrite step. The pre-Step-7 acknowledgement line lists every overridden item, including the ones resolved out of `ask`.\n\n### Implications review and proceed gate\n\n**Review the wider implications, then gate on a decision.** Build the review from the complete settled set: the submitted `decisions`, any in-flight overrides recorded during the conversation (these take precedence over the submission), every `\"none\"` answer together with the reason given for it, `general_comment`, and — where this surface tracks acceptance-criterion or NFR stances — those stances too. Do not start the review until every `ask` has an explicit recorded resolution and every in-flight override has been applied.\n\nConsider three fixed categories, regardless of whether a decision was framed as technical, user-facing, or business-oriented:\n- **Program / application** — architecture, code paths, operability, maintenance burden, and requirements imposed on other parts of the software.\n- **User** — end users, new users performing setup, operators, and developers, including prerequisites, setup friction, and additional steps.\n- **Business** — cost, adoption, support load, compliance, and reversibility.\n\nEmit only the categories with material second-order implications. For each included category, write at most four one-line bullets of about 25 words, each naming who or what is affected and how — never a restatement of the selected decision. Close with a line naming every considered category that was omitted, e.g. `Considered, nothing material: business.` — omit this closing line only when all three categories have material implications.\n\nIf the review cannot be produced, report that in one line and continue without stalling the workflow or presenting the gate below.\n\nThis review stays in chat and must not be written into the clarifying-questions or ticket-critique documents rewritten in Step 7.\n\nThen present the gate, verbatim: `Implications reviewed. Proceed, or name a decision to revisit.` Accept only a normalized `proceed`, `yes`, `y`, or `go` as a continuation token. Any other response names a decision to reopen: re-settle it in chat, record the new override, rerun the entire implications review against the changed settled set, and present the gate again.\n\nLiteral `auto_approve = true` emits the review but skips this gate entirely; a missing or non-true `auto_approve` value follows the human-in-the-loop path above.\n\nA decision named at this gate is re-settled in chat and recorded as an in-flight override using the same override mechanism as the rest of this step; the rerun review picks it up, and the pre-Step-7 acknowledgement line above also names it. Step 7 must not begin until every submitted `ask` is resolved (per the hard rule above) **and** this gate has accepted a proceed token — except when the review fails open or `auto_approve` is literal `true`.\n\n## Step 7: Interpretively rewrite source documents\n\nThe pasted JSON contains a `decisions` object keyed by item ID. Each decision includes `source`, `choice`, `chosen_label`, and `comment`. Use these fields to locate and rewrite the corresponding sections in:\n- `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n- `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n\nAfter a second-opinion run, each document has this shape:\n\n- A top-level H1 (`# Ticket Analysis` or `# Ticket Quality Critique`) followed by an italic provider-attribution line `_This analysis was generated by GPT|Claude|Gemini._` naming the first-round LLM family. **Preserve this attribution line verbatim** — do not move, edit, or remove it during the rewrite step.\n- The first-round questions / critique items, exactly as written by the first-round model.\n- **Inline second-opinion blockquotes** (`> **Second opinion (<provider>) - concurrence|refinement|disagreement.** ... > *Citations: ...*`) nested directly under each prior item the second round addressed. The `(<provider>)` parenthetical is the second-round LLM family (`GPT|Claude|Gemini`). Items the second round did not comment on have no blockquote — that is the \"weak concurrence\" signal.\n- A **`## New in Second Opinion`** tail block listing items the second round added on top of the first round. Immediately under the H2 there is a second italic attribution line `_These additional points were raised by GPT|Claude|Gemini._` naming the second-round family — **also preserve this verbatim**. Then agent-specific sub-headings:\n - Clarifier docs: `### New Requirements Questions` / `### New Technical Questions` (numbering continues from the prior section).\n - Critique docs: `### New Requested Changes` / `### New Points to Consider` (numbering continues from the prior section).\n- A final **`## Second Opinion Summary`** footer (1-3 sentences). **This footer must be preserved verbatim** — it is the canonical record of the second round's overall position and should not be edited.\n\nThe `source` field on each decision tells you where the item lives:\n\n- `Clarifying Q3 (prior round, weak concurrence)` → the prior section, no inline blockquote. Rewrite the prior item's answer.\n- `Clarifying Q9 (prior round, concurrence inline)` → the prior section, prior item carries an explicit `concurrence` blockquote. Rewrite the prior answer; the blockquote can be removed once the answer absorbs the resolution.\n- `Clarifying Q3 (prior round, refinement inline)` / `(prior round, disagreement inline)` → the prior section, prior item carries an explicit `refinement` or `disagreement` blockquote. Rewrite the prior answer to reconcile the dispute, then handle the blockquote per the rule below.\n- `Clarifying Q11 (new in second opinion → New Requirements Questions)` → the `## New in Second Opinion > ### New Requirements Questions` sub-section. Rewrite the item in place inside that sub-section, not at the top of the prior analysis.\n- Equivalent forms for critique items: `Critique: Requested Change 2 (prior round, refinement inline)`, `Critique: Points to Consider N+1 (new in second opinion → New Points to Consider)`, etc.\n\n**Legacy fallback shape**: if the document instead ends with `\\n\\n---\\n\\n` followed by a `## Second Opinion` section (because the JSON pipeline fell back), apply decisions to the equivalent location: `### Response to Prior Items` for inline-style responses, `### Additional Points > New X` for tail-style new items. Preserve the `\\n\\n---\\n\\n` separator and the `## Second Opinion` heading verbatim.\n\nApply the decision to the item in its home location. Then apply the decision:\n\n### Actionable item decisions\n\n- **Selected option** (`choice` is `opt-N`): Add `**Review Decision**: Accepted. <chosen_label>.` to the corresponding section. Integrate the selected direction into the section text so it reads as a final recommendation or resolved answer.\n- **None of these** (`choice` is `none`): Add `**Review Decision**: Rejected — none of the proposed options accepted.` Include the user's `comment` explaining why. Rewrite the section to reflect this decision.\n\nFor actionable items sourced from clarifying questions, rewrite the question's best-guess answer so it reads as the final resolved direction chosen by the reviewer. Do not leave the item framed as an unresolved accept/reject/modify prompt.\n\nFor items sourced from `(prior round, refinement inline)` or `(prior round, disagreement inline)` — disputes of a prior-round item carried in an inline blockquote — the prior-round item is the canonical home: rewrite its answer to absorb the resolution. Then handle the blockquote in one of two ways: (a) remove the blockquote outright if the rewritten answer fully absorbs the second-opinion content, or (b) shorten the blockquote to a single sentence noting the resolution while preserving the `(<provider>)` attribution (e.g. `> **Second opinion (Claude) - refinement.** Resolved by reviewer decision E-N.`). Citations from the original blockquote may be promoted into the rewritten prior-item answer if useful — keep the strongest 1-2 grounding refs.\n\nFor items sourced from `(new in second opinion → ...)` — gap-captured items that received a decision — rewrite the item in place inside its tail-block sub-section (`## New in Second Opinion > ### New X`), not at the top of the prior analysis. Preserve the sub-section heading and continued numbering.\n\n### General comment handling\n\nTreat `general_comment` as overarching guidance that informs the tone and direction of both document rewrites. If it contains specific actionable feedback, weave it into the relevant sections. If it is broad or general, use it as context for how the rewrites should read. Do not create a separate \"General Comment\" or \"Reviewer Notes\" section — the goal is \"final draft\" form.\n\n### Rewrite principles\n\nThe goal is a **final draft** — the documents should read as if they were written with the decisions already made. Do not mechanically append decisions. Instead, lightly rewrite affected sections so they reflect the decisions naturally. Preserve all non-affected sections unchanged. The prior-round content should still read as coherent standalone analysis after integration. Preserve the `## New in Second Opinion` tail block intact for any items that weren't decided. **Always preserve the `## Second Opinion Summary` footer verbatim** — it is the canonical record of the second round's overall position and should not be edited even when individual items it references have been resolved.\n\n## Step 8: Upload to Jira\n\nUpload both updated documents to Jira using `attachment` (operation: `\"upload\"`):\n\n1. Upload clarifying questions:\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n - `link_type`: `clarifying-questions.md`\n\n2. Upload ticket quality critique:\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n - `link_type`: `ticket-quality-critique.md`\n\n## Step 9: Complete\n\nConfirm: \"Review decisions captured and uploaded to {ticket_key}.\"\n\n## Return\n\nConfirm \"Review decisions captured and uploaded to {ticket_key}.\" and list the two attachments uploaded (`{ticket_key}-clarifying-questions.md` and `{ticket_key}-ticket-quality-critique.md`). Note any decisions that could not be applied.\n",
867
+ "capture-review-decisions.md": "Capture user decisions on review findings for {ticket_key} using the HTML decision page, then interpretively rewrite the clarifying questions and critique docs and upload both to Jira.\n\n## Step 1: Read source documents\n\nRead the combined review-and-resolution file:\n- `{docs_dir}/review/{ticket_key}-review-and-resolution.md`\n\nIf the file does not exist or is unreadable, stop and report: \"Combined review-and-resolution file not found or unreadable. Run the earlier pipeline steps first.\"\n\nThe combined file existing but containing no actionable items (empty `Needs Scrutiny` and `Open Questions` sections) is **not** a failure condition — Step 4 handles the no-decisions-needed flow gracefully when `generate_decision_page` is called with empty `actionable_items`.\n\n## Step 2: Map evaluation items to decision page input\n\nTransform the combined review-and-resolution document into `generate_decision_page` JSON input using these mapping rules:\n\n| Evaluation Section | JSON Field | Mapping Rule |\n|---|---|---|\n| Open Questions | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, combined `**Assessment**` paragraph and `**Codebase Evidence**` bullet list → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Needs Scrutiny | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, combined `**Assessment**` paragraph and `**Codebase Evidence**` bullet list → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Confirmed Improvements | `clear_improvements` | E-item title → `title`, confidence tag → `confidence`, recommended action → `action`, `**Source**` from the combined file → `source` |\n\n**Important**: The `original_question`, `why_it_matters`, `option_consequences`, `recommendation_explanation`, and the collapsed `codebase_evidence` block together replace the old single `context` blob. Each clarity field guides a different facet of the user's decision: `original_question` reminds the reviewer what was asked, `why_it_matters` frames the impact, `option_consequences` describe the behavioral outcome of each branch, `recommendation_explanation` motivates the recommended branch, and the closed-by-default `codebase_evidence` block surfaces the Assessment + file:line citations on demand without overwhelming the card.\n\nFor each actionable item, the `options` array is a list of plain label strings extracted from the combined file's decision tree branches. The tool auto-generates value keys (`opt-0`, `opt-1`, etc.) and auto-appends a \"None of these\" option. Do not generate value keys yourself.\n\n## Step 2.5: Auto-approve fast path\n\nFor this run, `auto_approve` = `{auto_approve}`.\n\nIf `auto_approve` is `true` and Step 2 produced at least one actionable item, skip Steps 3–6 entirely and synthesize the commit JSON directly:\n\n- `ticket_key`: `{ticket_key}`\n- `general_comment`: `\"\"`\n- `decisions`: an object keyed by each `actionable_items[*].id` from Step 2's mapped input. For each item:\n - If `recommendation_index` is a non-negative integer within range of `options`: `choice = \"opt-\" + recommendation_index`, `chosen_label = options[recommendation_index]`, `comment = \"\"`, `source` copied from the item.\n - Otherwise (missing, null, or out of range): `choice = \"opt-0\"`, `chosen_label = options[0]`, `comment = \"\"`, `source` copied. Never emit `\"none\"` and never emit `\"ask\"`.\n\nPost a single chat acknowledgement listing each auto-approved item ID and chosen label, then proceed directly to Step 7 with the synthesized JSON. Step 7's \"Hard rule\" about resolving `ask` items does not apply because no item carries `choice === \"ask\"`.\n\nThe synthesized settled decisions still receive the implications review described under Step 6's \"Implications review and proceed gate\" before Step 7 runs — literal `auto_approve = true` only skips the human proceed gate, not the review itself. \"Skip Steps 3–6\" above means skipping their interactive portions (rendering the page, waiting on chat, the Q&A loop); it does not exempt this fast path from the review obligation.\n\nIf Step 2 produced zero actionable items, fall through to Step 3 — Step 4's existing `no_decisions_needed` branch handles the empty case correctly.\n\nOtherwise (any value of `auto_approve` other than the literal `true` — including empty, `false`, or missing), proceed to Step 3.\n\n## Step 3: Call the MCP tool\n\nCall `generate_decision_page` with `ticket_key` at the root and the review arrays nested under `content`:\n\n**Always pass `content`, even when both arrays are empty.** Send `\"content\": { \"actionable_items\": [], \"clear_improvements\": [] }` rather than omitting the key — that is what reaches the `no_decisions_needed` branch Step 2 relies on. Omitting `content` entirely is rejected with a `VALIDATION_ERROR`, because root-level arrays are silently dropped by the tool's lean input schema and a missing wrapper is far more often a mistake than a deliberate empty call.\n\n```typescript\ninterface ReviewDecisionsContent {\n actionable_items?: Array<{\n id: string;\n question: string;\n why_it_matters: string; // required — concrete one-sentence impact\n recommendation_explanation: string; // required — why the recommended branch is best\n options: string[]; // 2-4 option labels\n option_consequences: string[]; // same length as options\n recommendation_index: number; // 0-based index into options\n original_question?: string; // optional display field\n codebase_evidence?: string; // optional display field — assessment + file:line\n source?: string; // optional source reference\n }>;\n clear_improvements?: Array<{\n id: string;\n title: string;\n action: string;\n confidence: string;\n source: string; // required for clear_improvements\n }>;\n}\n```\n\nExample call:\n```json\n{\n \"ticket_key\": \"{ticket_key}\",\n \"content\": {\n \"actionable_items\": [\n {\n \"id\": \"E-1\",\n \"question\": \"Should we add a configurable timeout?\",\n \"why_it_matters\": \"Timeout behavior affects retry paths and user-visible latency.\",\n \"recommendation_explanation\": \"Configurable matches existing latency-branching code.\",\n \"options\": [\"Keep existing\", \"Add configurable timeout\"],\n \"option_consequences\": [\"No new work.\", \"Implementers add config + tests.\"],\n \"recommendation_index\": 1,\n \"original_question\": \"Does the ticket specify timeout behavior?\",\n \"source\": \"Clarifying Q1\"\n }\n ],\n \"clear_improvements\": [\n { \"id\": \"ci-1\", \"title\": \"Tidy logging\", \"action\": \"Use the logger.\", \"confidence\": \"high\", \"source\": \"Eval 1\" }\n ]\n }\n}\n```\n\n## Step 4: Check tool response\n\nThe tool returns a JSON response with a `status` field:\n- If `status` is `\"no_decisions_needed\"`: skip Steps 5, 6, 7, and 8 entirely. Output a success message: \"No actionable review decisions needed — skipping doc rewrite and upload.\" This covers both the case where every item was confirmed as a Confirmed Improvement and the case where no items were emitted (e.g., both upstream source documents were absent).\n- If `status` is `\"decision_page_generated\"`: continue to Step 5. The response includes `file_path`.\n\n## Step 5: Direct user to the decision page\n\nTell the user to open the generated HTML file in their browser. Provide the `file_path` from the tool response. Then say to the user, verbatim: `Open the page. For any item you're unsure about, choose \"Ask about this\" — when you submit, I'll talk through those before we proceed. You can also ask me questions in chat before submitting if you prefer.`\n\nThis step only directs the user to the page and explains the two allowed next actions (submit selections, or ask questions first). Do not describe Step 7's rewrite semantics here; that belongs to the rewrite step.\n\n## Step 6: Q&A loop and commit signal\n\nEnter an open-ended Q&A loop. There is no turn cap — the user may ask any number of questions in any number of turns. Do not stop and wait silently; engage with each user message as either a commit signal or a discussion turn.\n\n### Proceed signal (commit)\n\nTrim the full user message and attempt to parse the entire trimmed message as JSON. The message is a commit only when the parsed value is an object with all three of these top-level fields:\n\n- `ticket_key` — must be a string\n- `decisions` — must be an object\n- `general_comment` — must be a string\n\nThe first valid commit-shaped JSON paste commits immediately. Proceed to Step 7 without prompting for additional confirmation. Any combination of `decisions` keys is accepted (the page may submit a partial set if the user only resolved some items conversationally). Do not over-validate the per-card fields beyond the top-level commit-shape check — the page guarantees the per-card schema, and over-validating risks rejecting valid pastes if the page schema evolves.\n\n### Discussion signal (Q&A turn)\n\nAnything that is not commit-shaped JSON is a discussion turn. This includes:\n\n- Freeform questions (with or without other text).\n- Questions pasted alongside other text or alongside JSON.\n- Malformed JSON (parse failure).\n- Well-formed JSON missing one or more of the required top-level keys (`ticket_key`, `decisions`, `general_comment`).\n\nFor JSON-shaped input that is missing required top-level fields, call this out in the reply — explain which fields are missing and ask whether the user intended to submit or share partial state — rather than silently treating it as a freeform question.\n\nAnswer discussion turns using these sources, in priority order:\n\n1. The combined `{ticket_key}-review-and-resolution.md` file already read in Step 1.\n2. The original `{ticket_key}-clarifying-questions.md` and `{ticket_key}-ticket-quality-critique.md` documents.\n3. Codebase lookups when the question requires verifying current code state.\n\nFallback: if running on a pre-PR1 branch where the combined review-and-resolution document does not exist, use the pre-PR1 `{ticket_key}-review-evaluation.md` and `{ticket_key}-resolution-guide.md` pair in its place.\n\nFor plain freeform questions, infer the item from chat context when possible.\n\n### In-flight decision state\n\nDuring the Q&A loop, maintain in-flight JSON state — agent-owned working memory representing the user's current intent for `decisions` and `general_comment`. This in-flight JSON state lives only in the agent's working memory for the duration of the loop; do not persist it server-side.\n\n- When the user clearly changes their mind about an item, chooses an option conversationally with reasonably explicit decision language (\"choose option B for E-3\", \"go with the configurable timeout\", \"change E-7 to None of these\"), or gives new overarching guidance, record that as an in-flight override.\n- Ambiguous preference language (\"I'm leaning toward...\", \"maybe option B is fine\") should be discussed but not recorded as an override unless the user gives reasonably explicit decision language.\n- `general_comment` may be updated in the in-flight state when the user gives overarching guidance during Q&A.\n- The page's general-comment textarea is preserved unchanged. Do not modify the page DOM during Q&A; the user can still fill the textarea before submitting if they prefer.\n\nOn the eventual JSON commit, the user-submitted JSON is the baseline and the recorded in-flight overrides take precedence over it. Before proceeding to Step 7, post a brief one-line acknowledgement in chat naming each overridden item ID and/or `general_comment`. The acknowledgement is mandatory (not optional) — it is the user's last chance to object before Step 7's document rewrite. The user does not need to re-open, edit, or re-submit the decision page after changing their mind in chat; they can submit the page as-is to provide the commit signal, and the in-flight state remains the source of truth for overrides.\n\n### Ask-about-this resolution\n\nAfter accepting a commit, scan `decisions` for any item where `choice === \"ask\"`. The user has signaled that they need more information before deciding on those items. For each such item:\n\n- If `comment` is non-empty, treat it as the user's specific question or stated uncertainty and answer that directly.\n- If `comment` is empty, proactively present the most relevant missing context — the item's `codebase_evidence`, related code lookups, prior-round answers — and lay out the trade-offs the user appears to need help weighing.\n- Continue the Q&A turn-by-turn until the user gives an explicit decision in chat for that item (\"go with option B\", \"none of these, because …\"). Record that decision as an in-flight override using the same override mechanism described above.\n\n**Hard rule.** Step 7 must not run while any `decisions[*].choice === \"ask\"` remains unresolved by an in-flight override. Do not honor \"just proceed\", \"skip those\", or any other instruction to defer resolution — every `ask` item must end with a recorded `opt-N` or `none` override before the rewrite step. The pre-Step-7 acknowledgement line lists every overridden item, including the ones resolved out of `ask`.\n\n### Implications review and proceed gate\n\n**Review the wider implications, then gate on a decision.** Build the review from the complete settled set: the submitted `decisions`, any in-flight overrides recorded during the conversation (these take precedence over the submission), every `\"none\"` answer together with the reason given for it, `general_comment`, and — where this surface tracks acceptance-criterion or NFR stances — those stances too. Do not start the review until every `ask` has an explicit recorded resolution and every in-flight override has been applied.\n\nConsider three fixed categories, regardless of whether a decision was framed as technical, user-facing, or business-oriented:\n- **Program / application** — architecture, code paths, operability, maintenance burden, and requirements imposed on other parts of the software.\n- **User** — end users, new users performing setup, operators, and developers, including prerequisites, setup friction, and additional steps.\n- **Business** — cost, adoption, support load, compliance, and reversibility.\n\nEmit only the categories with material second-order implications. For each included category, write at most four one-line bullets of about 25 words, each naming who or what is affected and how — never a restatement of the selected decision. Close with a line naming every considered category that was omitted, e.g. `Considered, nothing material: business.` — omit this closing line only when all three categories have material implications.\n\nIf the review cannot be produced, report that in one line and continue without stalling the workflow or presenting the gate below.\n\nThis review stays in chat and must not be written into the clarifying-questions or ticket-critique documents rewritten in Step 7.\n\nThen present the gate, verbatim: `Implications reviewed. Proceed, or name a decision to revisit.` Accept only a normalized `proceed`, `yes`, `y`, or `go` as a continuation token. Any other response names a decision to reopen: re-settle it in chat, record the new override, rerun the entire implications review against the changed settled set, and present the gate again.\n\nLiteral `auto_approve = true` emits the review but skips this gate entirely; a missing or non-true `auto_approve` value follows the human-in-the-loop path above.\n\nA decision named at this gate is re-settled in chat and recorded as an in-flight override using the same override mechanism as the rest of this step; the rerun review picks it up, and the pre-Step-7 acknowledgement line above also names it. Step 7 must not begin until every submitted `ask` is resolved (per the hard rule above) **and** this gate has accepted a proceed token — except when the review fails open or `auto_approve` is literal `true`.\n\n## Step 7: Interpretively rewrite source documents\n\nThe pasted JSON contains a `decisions` object keyed by item ID. Each decision includes `source`, `choice`, `chosen_label`, and `comment`. Use these fields to locate and rewrite the corresponding sections in:\n- `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n- `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n\nOnly the documents this review delivered exist. A cost-saved review resolves zero critique rounds for an Easy ticket and ships clarifying questions alone, so the critique document is absent — rewrite each document that WAS delivered and skip the leg the review did not run. An absent document is not an error.\n\nAfter a second-opinion run, each document has this shape:\n\n- A top-level H1 (`# Ticket Analysis` or `# Ticket Quality Critique`) followed by an italic provider-attribution line `_This analysis was generated by GPT|Claude|Gemini._` naming the first-round LLM family. **Preserve this attribution line verbatim** — do not move, edit, or remove it during the rewrite step.\n- The first-round questions / critique items, exactly as written by the first-round model.\n- **Inline second-opinion blockquotes** (`> **Second opinion (<provider>) - concurrence|refinement|disagreement.** ... > *Citations: ...*`) nested directly under each prior item the second round addressed. The `(<provider>)` parenthetical is the second-round LLM family (`GPT|Claude|Gemini`). Items the second round did not comment on have no blockquote — that is the \"weak concurrence\" signal.\n- A **`## New in Second Opinion`** tail block listing items the second round added on top of the first round. Immediately under the H2 there is a second italic attribution line `_These additional points were raised by GPT|Claude|Gemini._` naming the second-round family — **also preserve this verbatim**. Then agent-specific sub-headings:\n - Clarifier docs: `### New Requirements Questions` / `### New Technical Questions` (numbering continues from the prior section).\n - Critique docs: `### New Requested Changes` / `### New Points to Consider` (numbering continues from the prior section).\n- A final **`## Second Opinion Summary`** footer (1-3 sentences). **This footer must be preserved verbatim** — it is the canonical record of the second round's overall position and should not be edited.\n\nThe `source` field on each decision tells you where the item lives:\n\n- `Clarifying Q3 (prior round, weak concurrence)` → the prior section, no inline blockquote. Rewrite the prior item's answer.\n- `Clarifying Q9 (prior round, concurrence inline)` → the prior section, prior item carries an explicit `concurrence` blockquote. Rewrite the prior answer; the blockquote can be removed once the answer absorbs the resolution.\n- `Clarifying Q3 (prior round, refinement inline)` / `(prior round, disagreement inline)` → the prior section, prior item carries an explicit `refinement` or `disagreement` blockquote. Rewrite the prior answer to reconcile the dispute, then handle the blockquote per the rule below.\n- `Clarifying Q11 (new in second opinion → New Requirements Questions)` → the `## New in Second Opinion > ### New Requirements Questions` sub-section. Rewrite the item in place inside that sub-section, not at the top of the prior analysis.\n- Equivalent forms for critique items: `Critique: Requested Change 2 (prior round, refinement inline)`, `Critique: Points to Consider N+1 (new in second opinion → New Points to Consider)`, etc.\n\n**Legacy fallback shape**: if the document instead ends with `\\n\\n---\\n\\n` followed by a `## Second Opinion` section (because the JSON pipeline fell back), apply decisions to the equivalent location: `### Response to Prior Items` for inline-style responses, `### Additional Points > New X` for tail-style new items. Preserve the `\\n\\n---\\n\\n` separator and the `## Second Opinion` heading verbatim.\n\nApply the decision to the item in its home location. Then apply the decision:\n\n### Actionable item decisions\n\n- **Selected option** (`choice` is `opt-N`): Add `**Review Decision**: Accepted. <chosen_label>.` to the corresponding section. Integrate the selected direction into the section text so it reads as a final recommendation or resolved answer.\n- **None of these** (`choice` is `none`): Add `**Review Decision**: Rejected — none of the proposed options accepted.` Include the user's `comment` explaining why. Rewrite the section to reflect this decision.\n\nFor actionable items sourced from clarifying questions, rewrite the question's best-guess answer so it reads as the final resolved direction chosen by the reviewer. Do not leave the item framed as an unresolved accept/reject/modify prompt.\n\nFor items sourced from `(prior round, refinement inline)` or `(prior round, disagreement inline)` — disputes of a prior-round item carried in an inline blockquote — the prior-round item is the canonical home: rewrite its answer to absorb the resolution. Then handle the blockquote in one of two ways: (a) remove the blockquote outright if the rewritten answer fully absorbs the second-opinion content, or (b) shorten the blockquote to a single sentence noting the resolution while preserving the `(<provider>)` attribution (e.g. `> **Second opinion (Claude) - refinement.** Resolved by reviewer decision E-N.`). Citations from the original blockquote may be promoted into the rewritten prior-item answer if useful — keep the strongest 1-2 grounding refs.\n\nFor items sourced from `(new in second opinion → ...)` — gap-captured items that received a decision — rewrite the item in place inside its tail-block sub-section (`## New in Second Opinion > ### New X`), not at the top of the prior analysis. Preserve the sub-section heading and continued numbering.\n\n### General comment handling\n\nTreat `general_comment` as overarching guidance that informs the tone and direction of both document rewrites. If it contains specific actionable feedback, weave it into the relevant sections. If it is broad or general, use it as context for how the rewrites should read. Do not create a separate \"General Comment\" or \"Reviewer Notes\" section — the goal is \"final draft\" form.\n\n### Rewrite principles\n\nThe goal is a **final draft** — the documents should read as if they were written with the decisions already made. Do not mechanically append decisions. Instead, lightly rewrite affected sections so they reflect the decisions naturally. Preserve all non-affected sections unchanged. The prior-round content should still read as coherent standalone analysis after integration. Preserve the `## New in Second Opinion` tail block intact for any items that weren't decided. **Always preserve the `## Second Opinion Summary` footer verbatim** — it is the canonical record of the second round's overall position and should not be edited even when individual items it references have been resolved. **Preserve the `bridge-review-decision-identity` footer on each document verbatim** — it is the only channel carrying the generation identity Step 7.5 must send, and a rewrite that drops it makes the capture unsendable.\n\n## Step 7.5: Record the decisions durably (AUTHORITATIVE)\n\n**This step, not the document rewrite, is what makes the decisions durable.** The\n`**Review Decision**:` markers Step 7 stamps into the two documents are a\nhuman-readable record only. Those documents are stored with `url='ai-generated'`,\nand BAPI-738 deliberately excludes such rows from the description plan generation\nreads — so a decision that exists only as prose reaches nothing downstream. The\nstructured write below is the authoritative one; the prose is secondary.\n\n### Build one resolution per manifest id\n\nEvery critique finding and every clarifying question carries a stable id as an\nHTML comment on its item line: `<!-- bapi-item:crit-001 -->`,\n`<!-- bapi-item:clar-004 -->`. Preserve these markers through the Step 7 rewrite —\nnever delete, renumber, or invent one.\n\nProduce **exactly one** resolution for **every** id present in the delivered\ndocuments, not just the ones the reviewer chose an option for. A cost-saved review\ndelivers only the legs it ran — an Easy ticket resolves zero critique rounds and\nships clarifying questions alone — so build resolutions for the ids in whichever\ndocuments this review actually delivered. An id you omit is indistinguishable from\na decision that was lost, which is the exact failure this record exists to prevent.\nThe server refuses a payload with a missing, duplicate, or unknown id.\n\nMap each settled decision onto the id of the item it resolves:\n\n| Settled decision | `adjudication` | `effect` | `binding_instruction` |\n| --- | --- | --- | --- |\n| An option was chosen (`choice` is `opt-N`) and it obliges a change | `accepted` | `apply` | the ratified direction, non-empty |\n| Resolved, but nothing is to be done about it | `accepted` | `dismiss` | `null` |\n| None of these (`choice` is `none`) | `rejected` | `dismiss` | `null` |\n\nAn accepted dismissal is exactly `{adjudication: \"accepted\", effect: \"dismiss\",\nbinding_instruction: null}`. It means \"this item is settled and requires no\nwork\" — which is a different fact from \"this item was declined\", and the two must\nnot be collapsed onto one another.\n\n**Binding corrections go in `binding_instruction`, never in prose alone.** When\nthe reviewer's answer obliges an implementation to do something specific, that\nobligation belongs in this field on that item's id, even when the conversation\nproduced no `**Review Decision**:` marker for it. An unmarked prose correction is\nnot an independent requirement source and will not survive.\n\n### Read the generation identity from the delivered documents\n\nEvery delivered review document carries one metadata footer naming the decision\ngeneration it belongs to:\n\n```\n<!-- bridge-review-decision-identity: {\"generation\":7,\"source_bundle_sha256\":\"<64 lowercase hex>\"} -->\n```\n\nRead it from whichever of these documents this review actually delivered:\n\n- `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n- `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n\nA cost-saved review legitimately delivers only one leg — an Easy ticket resolves\nzero critique rounds and ships clarifying questions alone, so the critique document\ndoes not exist. Read the footer from each document that WAS delivered, and do not\ntreat an absent document as a failure.\n\nEvery delivered document's footer must be present and must agree — the same\npositive `generation` and the same 64-character lowercase-hex\n`source_bundle_sha256`. If a footer is missing or malformed on a document that WAS\ndelivered, or two delivered documents disagree, **stop and report it**. Do not\nguess a generation, do not send one document's identity on behalf of a document\nthat carries none, and do not ask the server which generation is current. The\nvalue's entire worth is that it came from the documents you actually adjudicated\nrather than from whatever happens to be pending by the time you call — a review\nre-run between your read and this call is exactly what it exists to catch.\n\nPreserve every delivered document's footer verbatim through the Step 7 rewrite.\n\n### Call the tool\n\nCall `capture_review_decisions` with:\n\n- `ticket_number`: `{ticket_key}`\n- `generation`: **required** — the integer read from the identity footer above.\n- `source_bundle_sha256`: **required** — the 64-character lowercase-hex digest\n read from that same footer. It is what proves these resolutions were authored\n against the exact bytes the generation was minted from.\n- `schema_version`: optional; omit it unless you actually know it.\n- `resolutions`: the array built above\n- `critique_document` / `clarify_document`: the **rewritten** markdown from\n Step 7, in memory, for each leg this review delivered — send only the documents\n that exist, omitting the leg a cost-saved review skipped. These are transient —\n the server counts `Review Decision` markers in them as a floor check and then\n discards them.\n- `request_id`: reuse the same value across retries of one logical capture\n\n### Do not continue past a refusal\n\nA refusal names a closed `reason`. Stop and report it; do not proceed to Step 8\nand do not describe the capture as successful.\n\n| `reason` | What it means |\n| --- | --- |\n| `manifest_coverage_mismatch` | An id is missing, duplicated, or unknown. Rebuild the resolution set against the ids actually in the documents. |\n| `marker_count_gap` | The prose records more decisions than the payload carries. Some decision was not captured — find it. |\n| `decision_set_too_large` | The set exceeds the record's bounds; nothing was truncated. Read `terminalized`: `true` means the generation is now `invalid` and closed, `false` means the invalidating write did not commit and the generation is still open — report it either way, and never assume the generation closed. |\n| `missing_identity` | The call carried no `generation` or no `source_bundle_sha256`. Stop and re-read the identity footer from each delivered document; never substitute a value you did not read. |\n| `stale_generation` | The review was re-run, so the documents you adjudicated are no longer the current generation. Re-read the latest delivered documents and re-establish the adjudication against them. Do not resubmit the same resolutions under a newly discovered generation number. |\n| `source_bundle_mismatch` | The submitted digest does not identify the current pending bundle. It is never adopted and never overwrites what is stored. Re-read the delivered documents and adjudicate the bundle that is actually pending. |\n| `invalid_decision_entry` | An `apply` has an empty instruction, or a `dismiss` has a non-null one. |\n| `conflicting_decision_set` | This generation was already captured with different decisions. Do not overwrite it. |\n| `generation_already_terminal` | The generation is already `complete` or `invalid`. A new review bundle is what produces a new generation; this one cannot be reopened. |\n| `no_pending_generation` | No decision generation exists for this ticket. The review was never delivered, or it was delivered before this record existed. |\n| `unsupported_schema` / `record_unreadable` | A record exists that this deployment cannot interpret. Report it; do not attempt to replace it. |\n\nA `409` or `422` here is a real failure of adjudication, not a formality to route\naround.\n\n## Step 8: Upload to Jira\n\nOnly after Step 7.5 succeeded. This upload and the prose markers it carries are\nthe human-readable record; the durable one is already written. A failure here is\nreported on its own and never invalidates the completed capture.\n\nUpload each document this review delivered to Jira using `attachment` (operation: `\"upload\"`) — a cost-saved Easy ticket delivered clarifying questions alone, so upload only the documents that exist:\n\n1. Upload clarifying questions:\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n - `link_type`: `clarifying-questions.md`\n\n2. Upload the ticket quality critique **only when the review delivered a critique leg** (skip this when the critique document does not exist):\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n - `link_type`: `ticket-quality-critique.md`\n\n## Step 9: Complete\n\nConfirm: \"Review decisions captured and uploaded to {ticket_key}.\" State the\ngeneration that was recorded. Say this only when Step 7.5 returned a success\noutcome — an upload without a durable capture is not a completed adjudication.\n\n## Return\n\nConfirm \"Review decisions captured and uploaded to {ticket_key}.\" and list the attachment(s) uploaded (`{ticket_key}-clarifying-questions.md`, and `{ticket_key}-ticket-quality-critique.md` when the review delivered a critique leg). Note any decisions that could not be applied.\n",
868
868
  "checkpoint-work.md": "Checkpoint the work produced for ticket {ticket_key}.\n\nThis is the **durability boundary**. The production phase has just authored its\nartifacts and they exist only in the worktree. The pre-PR verification phase that\nruns next executes the plan's review steps, its test commands, and — for a\nfrontend ticket — a remediation loop of up to three cycles. That is the long part of\nthe run, and it is exactly where a session runs out of budget.\n\nSo the work is pushed to origin *first*. After this step, a worker that dies mid\nverification has still left its implementation recoverable on a remote ref.\n\nThis step is deliberately narrow. It makes **no branch decision**, opens **no pull\nrequest**, and asks for **no approval**. Branch selection and the pull request belong\nto `commit-and-push.md` and `create-pr.md`, which run later on the same branch. Doing\nany of that here would put a decision — and a possible pause — in front of the very\ndurability guarantee this step exists to provide.\n\n**Execution mode for this run: `{execution_mode}`.** Under `orchestrated`\n(a server-side orchestrator) the checkpoint is returned as a fenced text block\nthat orchestration parses. Under `inline` (`get_pipeline_recipe`) there is no\norchestrator, so the checkpoint is recorded with a tool call instead. Follow the\nbranch that matches wherever the two are named.\n\n---\n\n## Step 1 — Assess the Worktree\n\nRun and note the results:\n\n- `git rev-parse --abbrev-ref HEAD` — the current branch.\n- `git status --porcelain` — everything modified, added, or untracked.\n\n**Use the branch you are on.** Do not create, rename, switch, or select a branch.\n\nStop immediately, reporting the reason, if any of these hold:\n\n- HEAD is detached (`git rev-parse --abbrev-ref HEAD` reports `HEAD`).\n- A merge, rebase, or cherry-pick is in progress.\n\nNeither is a state to commit into, and both need a human.\n\n## Step 2 — Clean Tree\n\nAn empty `git status --porcelain` means there is nothing new to checkpoint. It does\n**not** automatically mean everything is safe: the point of this step is that the work\nis on origin, so prove it rather than assume it.\n\n1. Run `git rev-parse HEAD`.\n2. Run `git ls-remote --heads origin <branch>` and compare the remote tip to HEAD.\n3. If the remote already contains HEAD, the checkpoint is satisfied. Report it and\n return.\n4. If HEAD is not on origin, there are local commits that were never pushed. Push them\n now with `git push origin <branch>` and re-verify.\n\nDo **not** create an empty commit to represent a checkpoint. An empty commit records\nnothing and proves nothing.\n\n## Step 3 — Commit and Push\n\nWhen there are changes to checkpoint:\n\n1. Stage the produced ticket work explicitly with `git add <file1> <file2> ...`. Do not use `git add -A` or `git add .` — a blanket stage sweeps in unrelated\n local files, and this step runs without an approval gate to catch that.\n2. Commit with:\n\n ```\n {ticket_key}: checkpoint produced work before verification\n ```\n\n3. Push the current branch immediately: `git push origin <branch>`. Add `-u` only if\n the branch has no upstream yet.\n\nUse the plain push command — do **not** add `--no-verify`. A Conductor worker already\nreceives `BRIDGE_SKIP_PREPUSH=1` from the executor, so bypassing hooks here is never\nnecessary.\n\n## Step 4 — Prove Durability\n\n1. Run `git rev-parse HEAD` and record the SHA.\n2. Run `git ls-remote --heads origin <branch>` and confirm the remote tip equals that\n SHA.\n\n**Stop the pipeline** and report the failure if the commit fails, the push fails or is\nrejected, or the remote tip does not match HEAD. The phase that follows is the long\none; entering it without durable work is precisely the failure this step prevents.\n\n## Return\n\nRecord the checkpoint the way this run's executor can actually read.\n\n### orchestrated\n\nReturn a machine-readable result as a fenced block tagged `bapi-checkpoint`, followed\nby a one-line human summary:\n\n```bapi-checkpoint\n{\"version\":1,\"branch\":\"<current branch>\",\"sha\":\"<checkpoint HEAD sha>\",\"pushed\":true,\"remoteMatchesHead\":true}\n```\n\nIf nothing needed committing because HEAD was already on origin, report the same shape\nwith the existing SHA and note that no new commit was required.\n\n### inline\n\nCall the `record_checkpoint` tool with `ticket_key` `{ticket_key}`, the `branch` and\n`sha` you verified in Step 4, and `pushed` / `remote_matches_head` set from what you\nactually observed. The tool refuses anything that does not report the work durable on\norigin, which is the point: a checkpoint that is not on the remote is not a checkpoint.\nReport the one-line human summary as well, but **do not emit a fenced `bapi-checkpoint`\nblock** — nothing parses one on this path.\n\nThe same applies when HEAD was already on origin and no new commit was needed: record\nthat existing SHA. The checkpoint is a claim about durability, not about having made a\ncommit.\n\nThe tool call is this step's final action, **not the end of your turn.** When it\nreturns successfully, continue immediately to the next recipe step —\n`execute-plan-verification.md`, the long pre-PR verification phase this checkpoint\nexists to protect.\n",
869
869
  "clarify-open-nfrs.md": "Proactively clarify any open non-functional requirements with the user via an interactive decision page before decomposing the epic. Clear goals and a clear desired end-state make the functional decomposition far more accurate, so resolve the unclear NFRs first.\n\n## Inputs\n\n- The framing written by the previous step: `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md`.\n\n## Step 1: Read the framing\n\nRead `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md`. Collect the NFRs marked `open` and note the business goal, desired end-state, and system behavior.\n\n## Step 2: Skip if nothing is open\n\n**If there are no `open` NFRs**, skip the decision page entirely. Note that no clarification was needed and proceed (return). Do not generate a page just to fill it.\n\n## Step 3: Build the decision-page inputs\n\n**If there is at least one `open` NFR**, build the inputs for an interactive planning decision page:\n - `system_goals` (read-only): `business_goal`, `desired_end_state`, `system_behavior`, and `nfrs` — the full classified NFR list, each with `category`, `requirement`, `implication`, and `status`.\n - `actionable_items`: one card per `open` NFR. Each card has:\n - `id`: a short stable id, e.g. `NFR-1`, `NFR-2`.\n - `question`: the decision the open NFR poses (e.g. \"What latency budget must the harvester meet?\").\n - `options`: 2–4 concrete option labels. Do **not** include \"None of these\" or \"Ask about this\" — the renderer auto-appends both. If there is one obvious answer, still provide the strongest alternative as a second option.\n - `option_consequences`: one consequence line per option, parallel to and the same length as `options`.\n - `why_it_matters`: the concrete impact of the decision.\n - `recommendation_explanation`: why the recommended option is best.\n - `recommendation_index`: the 0-based index of the recommended option.\n\n## Step 4: Call generate_decision_page\n\n**Call `generate_decision_page`** with `ticket_key`, `artifact_type`, routing fields, and `labels` at the root, and `system_goals` + `actionable_items` nested under `content`:\n - `artifact_type`: `pre_ticket_planning`.\n - `ticket_key`: `{epic_slug}`.\n - `output_subdir`: `epic-plans/{epic_slug}`.\n - `output_filename`: `{epic_slug}-nfr-decisions.html`.\n - `labels`: planning-flavored overrides, e.g. `title` = \"Epic Planning Decisions\", `section_heading` = \"Open Non-Functional Requirements\", and an `intro` that frames the page as settling the goals and NFRs before decomposition.\n - `content`: an object containing `system_goals` and `actionable_items` from step 3. (Omit `implementation_order` — the order is produced after decomposition.)\n\n ```typescript\n interface NfrPlanningContent {\n system_goals?: {\n business_goal: string;\n desired_end_state: string;\n system_behavior: string;\n nfrs?: Array<{\n category: string; // e.g. \"security/privacy\", \"performance/latency\"\n requirement: string;\n implication: string; // required — what this changes about the implementation\n status: \"confirmed\" | \"assumed\" | \"open\";\n }>;\n };\n actionable_items?: Array<{\n id: string; // e.g. \"NFR-1\", \"NFR-2\"\n question: string;\n why_it_matters: string;\n recommendation_explanation: string;\n options: string[]; // 2-4 option labels\n option_consequences: string[]; // same length as options\n recommendation_index: number;\n }>;\n }\n ```\n\n Example call:\n ```json\n {\n \"ticket_key\": \"{epic_slug}\",\n \"artifact_type\": \"pre_ticket_planning\",\n \"output_subdir\": \"epic-plans/{epic_slug}\",\n \"output_filename\": \"{epic_slug}-nfr-decisions.html\",\n \"labels\": { \"title\": \"Epic Planning Decisions\", \"section_heading\": \"Open Non-Functional Requirements\" },\n \"content\": {\n \"system_goals\": {\n \"business_goal\": \"Reduce MCP token tax to improve agent context efficiency.\",\n \"desired_end_state\": \"Core profile uses fewer than 15k tokens per session.\",\n \"system_behavior\": \"On-demand contract delivery with no schema round-trips.\",\n \"nfrs\": [\n { \"category\": \"performance/latency\", \"requirement\": \"No latency regression\", \"implication\": \"Validate in handler, not at boundary\", \"status\": \"confirmed\" },\n { \"category\": \"security/privacy\", \"requirement\": \"Errors never leak into HTML\", \"implication\": \"Use JSON envelope only\", \"status\": \"confirmed\" }\n ]\n },\n \"actionable_items\": [\n {\n \"id\": \"NFR-1\",\n \"question\": \"What latency budget must the harvester meet?\",\n \"why_it_matters\": \"Sets the retry window for downstream consumers.\",\n \"recommendation_explanation\": \"Under 30s matches existing SLA.\",\n \"options\": [\"Under 30s\", \"Under 60s\"],\n \"option_consequences\": [\"Tight but achievable.\", \"Relaxed, may delay alerts.\"],\n \"recommendation_index\": 0\n }\n ]\n }\n }\n ```\n\n## Step 5: Capture the user's choices (stop and wait)\n\nDirect the user to the returned `file_path`, tell them to open it and submit. Treat a paste as a commit only when it is a JSON object with all three top-level fields: `ticket_key` (string), `decisions` (object), and `general_comment` (string). The submitted JSON is the baseline; record any explicit conversational change to a decision as an in-flight override, which takes precedence over the submission when constructing the settled set used in Step 6. A `\"none\"` selection carries any reason supplied in the card comment or chat — retain it. For any item where `choice === \"ask\"`, discuss until the user gives an explicit decision — record it the same way as any other override — before continuing; every `\"ask\"` must be replaced by an explicit recorded decision before Step 6 begins. You MUST stop and wait for the user to respond — do NOT assume answers and do NOT proceed until the open NFRs are resolved or the user explicitly declines.\n\n## Step 6: Review implications and gate\n\nThis surface's tracked stances are the NFR stances captured in Step 5. **Review the wider implications, then gate on a decision.** Build the review from the complete settled set: the submitted `decisions`, any in-flight overrides recorded during the conversation (these take precedence over the submission), every `\"none\"` answer together with the reason given for it, `general_comment`, and — where this surface tracks acceptance-criterion or NFR stances — those stances too. Do not start the review until every `ask` has an explicit recorded resolution and every in-flight override has been applied.\n\nConsider three fixed categories, regardless of whether a decision was framed as technical, user-facing, or business-oriented:\n- **Program / application** — architecture, code paths, operability, maintenance burden, and requirements imposed on other parts of the software.\n- **User** — end users, new users performing setup, operators, and developers, including prerequisites, setup friction, and additional steps.\n- **Business** — cost, adoption, support load, compliance, and reversibility.\n\nEmit only the categories with material second-order implications. For each included category, write at most four one-line bullets of about 25 words, each naming who or what is affected and how — never a restatement of the selected decision. Close with a line naming every considered category that was omitted, e.g. `Considered, nothing material: business.` — omit this closing line only when all three categories have material implications.\n\nIf the review cannot be produced, report that in one line and continue without stalling the workflow or presenting the gate below.\n\nThe latest review will be written under `## Implications` in `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md` once the gate below allows continuation.\n\nThen present the gate, verbatim: `Implications reviewed. Proceed, or name a decision to revisit.` Accept only a normalized `proceed`, `yes`, `y`, or `go` as a continuation token. Any other response names a decision to reopen: re-settle it in chat, record the new override, rerun the entire implications review against the changed settled set, and present the gate again.\n\nLiteral `auto_approve = true` emits the review but skips this gate entirely; a missing or non-true `auto_approve` value follows the human-in-the-loop path above.\n\n## Step 7: Fold the answers back into the framing\n\nRewrite `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md` so each resolved NFR's `status` moves from `open` to `confirmed` (or `assumed` when the user chose a provisional default), recording the chosen resolution in the `requirement`/`implication`. Weave `general_comment` in as overarching guidance. Add or update the `## Implications` section with the latest review from Step 6 — the final one after any reopen/review loop — or its one-line fail-open notice if generation was unavailable. Leave settled sections unchanged. Run this rewrite only after Step 6's implications-review loop has completed or followed its non-blocking fail-open path.\n\nThis step is non-blocking only insofar as the user may explicitly decline; if `generate_decision_page` fails, log a warning, direct the user to the markdown framing instead, and continue.\n\n## Return\n\nReport whether a planning decision page was generated (and its path) or skipped because there were no open NFRs, and how many open NFRs were resolved into `confirmed`/`assumed`.\n",
870
870
  "commit-and-push.md": "Stage, commit, and push implementation changes for ticket {ticket_key}.\n\nBefore executing, assess the git state and present a clear plan for user approval.\n\n## Step 1 — Assess Git State\n\nRun these commands and note the results:\n- `git branch --show-current` — record the current branch name\n- `git status --porcelain` — identify all modified, added, and untracked files\n\n### If the working tree is clean (nothing to commit)\n\nAn empty `git status --porcelain` is not one situation — it is two, and they must\nbe told apart before anything else happens. Check the published state of the\nbranch this ticket's work would live on (the current branch if it references\n`{ticket_key}`, otherwise `feature/{ticket_key}`):\n\n- `git ls-remote --heads origin <branch>` — non-empty output means the branch is\n already pushed.\n- `gh pr list --head <branch> --state all` — reports any pull request already\n opened from that branch.\n\nThen apply the matching rule:\n\n1. **Clean tree, and no pushed branch or pull request exists for this ticket** —\n the implementation phase produced nothing. **Stop the pipeline here** and\n report the failure. Proceeding would open an empty pull request; an empty\n implementation must halt.\n2. **Clean tree, but the branch is already pushed and/or a pull request already\n exists** — this is a legitimate **re-entry**, not an empty implementation.\n Recovery runs (`ci_fix`, `remediate`, `rebase`) re-enter this pipeline on a\n branch whose commits were pushed by an earlier session, so there is often\n nothing new to commit. **Do not halt.** There is nothing to stage, so skip\n Steps 2–5 entirely: run `git rev-parse HEAD`, record the result as\n `last_commit_sha`, and return successfully so the pipeline continues to the\n downstream steps (pull request handling, verification, CI monitoring) the\n re-entry was dispatched to run. Before returning, compare `git rev-parse HEAD`\n against the remote tip from `git ls-remote` — if local commits exist that were\n never pushed, push them (`git push origin <branch>`) first, preserving the\n finalization boundary.\n\nEither signal — a pushed branch or an existing pull request — selects the\nre-entry case. Only when both are absent does the clean tree mean an empty\nimplementation.\n\n## Step 2 — Determine Branch\n\nDecide the branching strategy and be prepared to state it explicitly. Cover:\n\n- Whether you will commit on the current branch, or create a new branch.\n- If creating a new branch: the exact new branch name, and which branch it will be created from (current branch vs. `main`).\n- If branching from `main`: whether `main` needs to be pulled/updated first, and the command you will run.\n- Whether the target branch already exists remotely (and if so, whether you will push to the existing remote branch).\n\nDefault rules:\n\n- If the current branch already contains `{ticket_key}` (case-insensitive), plan to commit on the current branch.\n- Otherwise, plan to create a new branch named `feature/{ticket_key}` from the current branch.\n\n## Step 3 — Prepare Commit Details\n\n- Separate implementation files from unrelated changes. Only stage files related to the ticket.\n- Compose a commit message: `{ticket_key}: <brief description of what was implemented>`\n\n## Step 4 — Present Plan for Approval (commit, push, and PR)\n\nFor this run, `auto_approve` = `{auto_approve}`.\n\n**Auto-approve mode.** If `auto_approve` is `true`, do NOT present the approval plan and do NOT wait for user input. Apply the default branching rule from Step 2 (commit on the current branch if it contains `{ticket_key}` case-insensitively; otherwise create `feature/{ticket_key}` from the current branch). Stage all files reported by `git status --porcelain` that you assess as related to the ticket per Step 3's \"Only stage files related to the ticket\" rule (when uncertain, prefer including over excluding — auto-approve trades caution for momentum, and the user has explicitly opted in). Use the commit-message format from Step 3. Skip directly to Step 5 and execute.\n\nOtherwise (any value of `auto_approve` other than the literal `true` — including empty, `false`, or missing), proceed with the existing approval flow below.\n\nPresent a single approval plan covering the commit, push, and pull request creation before proceeding:\n\n```\nCommit Plan for {ticket_key}\n─────────────────────────────\nCurrent branch: <current branch name>\nBranching: - <\"Commit on current branch\" | \"Create new branch `<name>` from `<source branch>`\">\n - <if branching from main: \"Pull latest main first via `git checkout main && git pull`\" | omit if N/A>\n - <\"Remote branch already exists — will push to existing\" | \"New remote branch — will push with -u\" | omit if N/A>\nFiles to stage: <count> files\n - path/to/file1.py\n - path/to/file2.py\nExcluded: <any unrelated changed files, or \"None\">\nCommit message: {ticket_key}: <description>\nPush to: origin/<target branch>\nPR title: <commit subject — derived automatically after commit>\nPR base: main\n```\n\nWait for the user to approve, request changes, or reject. The user may adjust the branch name, file inclusion, commit message, PR title, PR base, or give other instructions. The PR title defaults to the commit subject after the commit is made, and the PR base defaults to `main`.\n\nDo not proceed until the user explicitly approves.\n\n## Step 5 — Execute\n\nThis step is the **finalization boundary**: the commit and the push must both\ncomplete, synchronously and successfully, before this instruction returns. Nothing\ndownstream — pull request creation, verification, CI monitoring — may begin while\nthe implementation exists only in the local worktree.\n\n1. If creating a new branch, run `git checkout -b <branch name>`.\n2. Stage approved files with `git add <file1> <file2> ...` — do not use `git add -A` or `git add .`.\n3. Commit with the approved message.\n4. Push with `git push -u origin <branch>`. Use the plain push command — do **not**\n add `--no-verify`. Normal git hook behavior is preserved here; a Conductor\n worker already receives `BRIDGE_SKIP_PREPUSH=1` from the executor, so the\n instruction never needs to bypass hooks itself.\n5. If the commit fails, or the push fails or is rejected, **stop the pipeline\n here** and report the failure. Do not continue toward pull request creation or\n verification with work that exists only locally. One exception: a commit that\n fails only because there is genuinely nothing to commit means Step 1's\n clean-tree rule applies — resolve it there (halt when nothing was ever\n published; proceed when the branch/PR already exists) instead of treating the\n empty commit itself as the terminal failure.\n6. After the push succeeds, run `git rev-parse HEAD` and record the resulting SHA\n as `last_commit_sha` — the durable head that was actually published to the\n remote.\n\n## Return\n\nReport that the commit was made and pushed: the selected branch name, the pushed\nremote (e.g. `origin/feature/{ticket_key}`), the commit subject line, and the\npost-push `git rev-parse HEAD` value labeled `last_commit_sha`. Note any files that\nwere intentionally excluded from the commit. If commit or push failed, report the\nfailure instead — the pipeline stops rather than proceeding.\n\nIn the clean-tree re-entry case (Step 1, rule 2), report that no new commit was\nneeded: the branch name, the existing pushed remote, and the current\n`git rev-parse HEAD` value labeled `last_commit_sha` — and state that the pipeline\nis continuing to the downstream steps.\n",
@@ -883,7 +883,7 @@ export const INSTRUCTIONS = {
883
883
  "explore-epic-codebase.md": "Perform a holistic, epic-level codebase exploration.\n\n## Epic Description\n\n{epic_description}\n\n## Instructions\n\n1. Read the research findings from `{docs_dir}/epic-plans/{epic_slug}/research-findings.md` to establish context. If the file does not exist or is empty, proceed without it.\n\n2. Explore the codebase with a focus on breadth rather than depth. The goal is to build a \"lay of the land\" understanding for the entire epic, not to deeply analyze any single sub-task. Search by filename pattern, search file contents by text pattern, and read relevant files to find:\n - Files, modules, and directories relevant to the epic\n - Architectural patterns used in similar features\n - Integration points and dependencies between modules\n - Existing conventions for the type of work this epic involves\n - Database models, API routes, agent flows, and utilities that may be affected\n\n3. Build a mental model of:\n - What exists today that relates to the epic\n - What patterns and conventions are used in similar features\n - What dependencies, data flows, and integration points are involved\n - What areas of the codebase will likely need changes\n\n4. Write the exploration findings to `{docs_dir}/epic-plans/{epic_slug}/codebase-exploration.md` with this structure:\n\n```markdown\n# Codebase Exploration\n\n## Architecture Overview\n{High-level description of how the relevant parts of the codebase are structured.}\n\n## Relevant Code Areas\n{List of key files, modules, and directories with brief descriptions of their relevance to the epic.}\n\n## Existing Patterns\n{Patterns and conventions discovered that should be followed when implementing the epic.}\n\n## Integration Points\n{Dependencies, data flows, and integration points that the epic will need to account for.}\n\n## Potential Challenges\n{Any architectural constraints, technical debt, or complexity that could affect implementation.}\n```\n\n## Return\n\nConfirm the codebase exploration was written to `{docs_dir}/epic-plans/{epic_slug}/codebase-exploration.md` and return a concise summary of the discovered codebase areas, naming the key files and patterns relevant to the epic.\n",
884
884
  "explore-epic-subtasks.md": "Perform focused code explorations for each approved sub-task.\n\n## Instructions\n\n1. Read the approved decomposition from `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md`.\n\n2. Create the explorations directory:\n ```\n mkdir -p {docs_dir}/epic-plans/{epic_slug}/explorations/\n ```\n\n3. For each sub-task in the decomposition, perform a focused exploration:\n - Search for specific files and patterns relevant to the sub-task\n - Identify implementation options and tradeoffs\n - Reference the holistic codebase exploration (`{docs_dir}/epic-plans/{epic_slug}/codebase-exploration.md`) and research findings (`{docs_dir}/epic-plans/{epic_slug}/research-findings.md`) for context\n - Default to lightweight exploration — only go deeper when the holistic exploration left significant gaps for a specific sub-task\n\n4. Write an exploration document for each sub-task to `{docs_dir}/epic-plans/{epic_slug}/explorations/NN-{subtask-slug}.md` (using zero-padded numbering, e.g., `01-add-pipeline-json.md`, `02-create-instruction-files.md`).\n\n5. Each exploration document MUST include these exactly named sections:\n\n```markdown\n# {Sub-task title}\n\n## Context\n{Brief description of the sub-task scope and its role within the epic.}\n\n## Relevant Code\n{Specific files, functions, and patterns relevant to this sub-task. Reference with file_path:line_number format.}\n\n## Implementation Options\n{Viable approaches for implementing the sub-task. For each option: description, pros, cons.}\n\n## Recommendation\n{Which option to pursue and why. Include any caveats or risks.}\n```\n\n6. **Word count guidance**: Target 300-500 words per document. Keep the exploration lightweight. Only exceed this limit if the holistic codebase exploration left significant gaps for a specific sub-task.\n\n## Return\n\nConfirm one exploration document was written per sub-task under `{docs_dir}/epic-plans/{epic_slug}/explorations/` and return a concise summary of the discovered code areas and recommended approaches across the sub-tasks.\n",
885
885
  "frame-goals-and-nfrs.md": "Frame the business goals, desired end-state, and non-functional requirements (NFRs) for this work before any functional decomposition or drafting. When the goals and the desired end-state of the system are clear, the functional requirements become much easier to design accurately. This step is documentary: it records the framing and classifies what is unclear. It does NOT pause and does NOT generate a decision page (interactive surfaces handle that separately).\n\n## Inputs\n\n- The idea or epic description for this run, plus any prior planning artifacts the earlier steps wrote into this run's working directory under `{docs_dir}` (for example: research findings, codebase exploration, resolved uncertainties, duplicate assessment). Read whichever of these exist; proceed without the ones that do not.\n\n## Instructions\n\n1. From the inputs, derive and state plainly:\n - **Business goal** — the business value this work delivers and why it matters.\n - **Desired end-state** — the concrete state the system should reach once this work is done.\n - **System behavior** — how the system must behave to complete its task (the quality attributes in prose, not a feature list).\n\n2. Identify the non-functional requirements. Consider every one of these canonical NFR categories and include the ones that genuinely apply (omit categories that do not):\n - security/privacy\n - performance/latency\n - reliability/failure-modes\n - observability/auditability\n - accessibility/UX\n - data-integrity/migration\n - compatibility\n - operability/config\n - compliance/SOC2\n - rollout/reversibility\n\n For each NFR you include, write three things: the `requirement`, its `implication` (what this requirement changes about the implementation), and a `status`. **An NFR with no concrete implication is boilerplate — drop it rather than record it.**\n\n3. Classify each NFR's `status` with this rubric:\n - `confirmed` — only if it is explicitly stated in the idea/description/standards or is directly observable in the codebase.\n - `assumed` — only if it is a low-risk, conventional, and reversible default.\n - `open` — if it touches architecture, the data model, security, user-visible behavior, migration, or irreversible Jira creation and is not settled. Be willing to mark things `open`: surfacing an unclear NFR is the point of this step.\n\n4. If this work is an epic (it will be decomposed into multiple sub-tasks or child tickets), draft a provisional **recommended implementation order**. For each slice, record a short title, its hard prerequisites (`depends_on` — what must land first), any soft sequencing preferences (`recommended_after` — not hard blockers), and a one-line rationale. Keep hard prerequisites separate from soft sequencing. Do not create Jira dependency links — the order is delivered into the epic downstream.\n\n5. Write the framing to a file named `goals-and-nfrs.md` in this run's working directory — the **same directory the earlier exploration/research steps in this pipeline wrote to** under `{docs_dir}`. Getting this path right matters: downstream steps read `goals-and-nfrs.md` from that exact directory and silently degrade (they see no framing) if it lands elsewhere. The directory differs by pipeline:\n - **plan-epic**: the epic plan directory, `docs/epic-plans/<epic-slug>/` (alongside `codebase-exploration.md` and `epic-plan.md`).\n - **idea-to-ticket**: the run directory, `docs/idea-to-ticket/<slug>-<run-id>/` (alongside `research-pack.md` and `resolved-uncertainties.md`).\n\n Use this structure (no markdown tables, no `- [ ]` checkboxes — BAPI-320 hygiene):\n\n```markdown\n# Goals & Non-Functional Requirements\n\n## Business Goal\n{business goal}\n\n## Desired End-State\n{desired end-state}\n\n## System Behavior\n{how the system must behave to complete its task}\n\n## Non-Functional Requirements\n- **{nfr category}** ({confirmed, assumed, or open}): {the requirement}. Implication: {what it changes about the implementation}.\n- ...\n\n## Recommended Implementation Order\n(Epics only; omit this section for a single task or spike.)\n1. {slice title} — depends on: {hard prerequisites or \"none\"}; recommended after: {soft preferences or \"none\"}. Rationale: {one line}.\n2. ...\n```\n\n## Return\n\nConfirm `goals-and-nfrs.md` was written, report the counts of `confirmed` / `assumed` / `open` NFRs, and state whether a recommended implementation order was produced (epics) or skipped (single task/spike).\n",
886
- "gather-and-attach-materials.md": "Post-create materials-completeness step. Gather the phase-eligible local materials a freshly-created ticket references and attach them via `attachment` (operations: `\"list\"`, `\"upload\"`, `\"delete\"`), while recording everything that is record-only. This is the POST-CREATE half of the upload-time materials-completeness pass (BAPI-423, phase-tagged since BAPI-643); the PRE-CREATE half — inventorying and writing the `## Materials & Access` section into the draft — already ran in the `jira-ticket-writer` agent.\n\n## Inputs\n\n- `{ticket_number}` — the real Jira key of the already-created ticket (e.g. `BAPI-423`). Attachment is a POST-CREATE step; never attempt to attach before the key exists.\n- `{draft_file_path}` — path to the draft markdown that carries the trailing `## Materials & Access` section.\n- `{auto_approve_external}` — the unattended-vs-interactive signal (named for consistency with `upload-and-track.md`). **Polarity is counter-intuitive: `\"true\"` means UNATTENDED, which is the MORE restrictive mode here** — skip all prompts AND keep external/auth-gated materials record-only (never auto-attach them). It does NOT grant permission to attach external materials. Any other value (including `\"false\"`, missing, or empty) means an interactive invocation that MAY prompt for external/auth-gated materials. Invocations from `write-ticket` and `full-automation` are always unattended (`\"true\"`) for this step.\n\n## Instructions\n\n> **Orchestrator-directed step.** This agent task is authorized to call `attachment` (operations: `list`, `upload`, `delete`) and `update_ticket_description` as directed below.\n\n1. **Read the record.** Read `{draft_file_path}` and parse its trailing `## Materials & Access` section. Collect the inventoried items grouped under *Planning Assets*, *Downloadable Assets*, *Planning & Downloadable Assets* (each phase-eligible; present only when a material of that phase exists), *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*. If there is no `## Materials & Access` section, there is nothing to gather — return a no-op success.\n\n2. **Canonical phase-token mapping.** Every phase-eligible bullet maps to exactly one filename token:\n - *Planning Assets* → `planasset`\n - *Downloadable Assets* → `implasset`\n - *Planning & Downloadable Assets* → `bothasset`\n\n *External/Auth-Gated Links* and *Binary/Image Materials (Record-Only)* are **never** phase-tagged and are behaviorally unchanged by this step — they stay record-only, exactly as before.\n\n3. **Source classification (scheme-based, no network probe).** Honor the classification already recorded in the draft:\n - **Planning Assets / Downloadable Assets / Planning & Downloadable Assets** — a local filesystem path that is **NOT tracked in version control** is a **low-risk** material eligible for auto-attach — proceed to step 5. The pre-create inventory already excluded version-controlled files (source code and in-repo docs are already available in the repository and are never attached — they are cited inline as *Relevant code*). As a safety net, this step must **never attach a file that is available in version control**: if any phase-tagged item is a code file or otherwise clearly version-controlled, skip it and treat it as record-only. A bullet under *Planning & Downloadable Assets* recorded via an existing `attachment_id` (not a local file — e.g. a design comp already attached on this or another ticket) is **not** re-uploaded by this step; leave it recorded with its `attachment_id`/path so a later implementation agent can download it into its worktree via the Jira attachment download capability.\n - **External/Auth-Gated Links** (every `http(s)` URI, even if explicitly linked) are **record-only** on unattended paths. If `{auto_approve_external}` is `\"true\"` (or the invocation is from `write-ticket` / `full-automation`), leave them record-only and never auto-attach. (Mind the polarity: `auto_approve_external = \"true\"` means we are in unattended mode, so external materials must stay record-only — `\"true\"` is NOT permission to attach them.) Only an explicitly interactive invocation (`auto_approve_external` is any non-`\"true\"` value) may prompt the user to confirm before attaching.\n - **Binary/Image Materials (Record-Only)** — ordinary/unrelated binaries (arbitrary screenshots, PDFs, ZIPs, and other binaries not design-relevant) stay **record-only** in this step; never attempt to upload them.\n\n4. **Deduplicate and detect stale phase reclassification.** Call the `attachment` MCP tool with `operation` set to `\"list\"` and `ticket_number` set to `{ticket_number}` BEFORE uploading anything, so a resumed or re-run invocation does not re-attach a material that is already present.\n - Compute each candidate's deterministic filename first (step 6), then compare the listed attachments by **`{ticket_number}/{hash}`** (the ticket prefix plus the 8-hex-char hash segment) against the phase-tagged filenames already on the ticket, using the exact grammar `{ticket}-{planasset|implasset|bothasset}-{hash}{ext}` — this comparison spans all three phase tokens for the same source.\n - If a listed attachment shares the same `{ticket_number}/{hash}` as a candidate but carries a **different phase token**, treat it as a **stale classification for the same source** — the material was reclassified to a different phase since it was last gathered.\n - If a listed attachment's filename **exactly matches** the candidate's newly computed filename (same ticket, same phase token, same hash, same extension), it is already attached — skip re-uploading it.\n - Legacy `{ticket_number}-material-{hash}{ext}` attachments (pre-BAPI-643) are a distinct, valid, untouched naming form — never treat them as stale phase-tagged attachments, and never delete or supersede them here.\n\n5. **Supersede a stale phase-tagged attachment before uploading the reclassified filename.** When step 4 identifies a stale same-source attachment under a different phase token:\n - Call the `attachment` MCP tool with `operation` set to `\"delete\"`, `ticket_number` set to `{ticket_number}`, and `file_name` set to the stale attachment's exact filename. This is a genuine removal (not `replace_existing`, which only matches an upload's *own* filename) — the stale filename differs from the new filename whenever the phase token changed, so `replace_existing` alone cannot supersede it.\n - **If the delete call fails** (or reports `deleted: false` for a reason other than \"already absent\" — e.g. a transient Jira error), **skip the replacement upload for that material entirely** this run, preserving the one-active-attachment invariant rather than leaving two phase-tagged copies of the same source live at once. Record the failure (see step 9) and continue with the next material; never halt the run.\n - **If the delete succeeds** (or the stale attachment was already absent), proceed to upload the newly classified filename in step 7.\n\n6. **Gather and size-tier each phase-eligible local material; compute deterministic filenames.**\n - For each phase-eligible local **text** material:\n - Read the local file from disk.\n - If the content exceeds **200,000 characters**, SKIP the upload and RECORD it (note the path and that it was skipped for size) — do not attach it.\n - If the content is **<= 200,000 characters**, upload it RAW via `attachment` (operation: `\"upload\"`). Do NOT summarize locally: the backend already summarizes attached text at plan time, so the size tiers are backend behavior this step defers to. The agent performs NO local summarization.\n - **Extension rule by phase**: a **planning-only** (`planasset`) text material normalizes to `.md` regardless of its original extension — the planner only needs prose content, not the original format. A **downloadable or both-phase** (`implasset`/`bothasset`) text material instead **preserves its lowercased original source extension** (e.g. `.json`, `.csv`, `.py`, `.yml`/`.yaml`) — the implementing agent needs the file back in its original, directly usable format. There is no blanket \"all text becomes `.md`\" rule; the extension is phase-dependent.\n - Use a deterministic, sanitized filename of the form `{ticket_number}-{phase-token}-{hash}{ext}` (using the `{ticket_number}` input from the Inputs section and the phase token from step 2), where `{hash}` is the first 8 lowercase hex characters of the SHA-256 digest of the sanitized absolute source path, and `{ext}` follows the extension rule above.\n - For each eligible local design/UI comp image (or other phase-tagged image, extension mapping to an allowlisted MIME type — `image/png`, `image/jpeg`, `image/webp`, `image/gif`), eligible for the allowlist-guarded binary upload path (distinct from the text upload path above):\n - Use a deterministic filename of the form `{ticket_number}-{phase-token}-{hash}{ext}`, where `{hash}` is computed the same way (first 8 lowercase hex characters of the SHA-256 digest of the sanitized absolute source path) and `{ext}` is the lowercased allowlisted source extension (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`) — the existing image-extension behavior is unchanged by phase.\n - Pin the hash algorithm exactly (SHA-256, first 8 lowercase hex chars, of the sanitized absolute path) — do NOT substitute another hash — so the same source always maps to the same `{ticket}/{hash}` identity and the dedup/stale-detection in step 4 works across separate sessions, re-runs, and phase reclassification. Compute filenames before the step 4 dedup/stale comparison is applied. Keep the sanitized source provenance inside the text attachment body (not applicable to binary comp uploads), not only in the filename.\n\n7. **`attachment` upload parameter discipline (Zod).**\n - For text materials, call `attachment` with `operation: \"upload\"`, `ticket_number`, the deterministic phase-tagged attachment filename, and the text `content`.\n - For design comp image uploads, call `attachment` with `operation: \"upload\"`, `ticket_number`, `file_path` (the local source path), and `file_name` set to the deterministic phase-tagged comp filename from step 6 — pass `file_path` rather than reading and UTF-8-encoding the bytes yourself, so `resolveUploadAttachment()` performs binary detection, the MIME allowlist check, and base64 encoding. Never UTF-8-encode image bytes locally.\n - In both cases, OMIT the optional parameters `link_type` and `replace_existing` entirely when they are unused — do NOT pass `null` or empty strings for them. The Zod schemas reject `null`/empty values, so an unused optional parameter must be omitted rather than nulled.\n\n8. **Persist destination provenance after successful uploads (one batched update).** For every material successfully uploaded in this run, record a stable, sanitized mapping from its generated Jira filename to the bullet's original repository-relative destination path (as recorded in the draft's `## Materials & Access` section). Write these mappings as bulleted lines under a dedicated trailing `### Asset Destinations` subsection of `## Materials & Access`, one line per mapping, in the exact form:\n ```markdown\n ### Asset Destinations\n\n - `{jira-filename}` → `{original/repo/relative/destination.ext}`\n ```\n This is the canonical, machine-parsed contract consumed by `extract_asset_destination_paths()` (`api/agent_flows/code_writer/library/ticket_asset_lib.py`) — do not vary the arrow character (`→`), the backtick wrapping, or the bullet form. Append new mappings to this subsection (creating it if absent) rather than replacing prior mappings from earlier gather runs. Apply **all** of this run's successful-upload provenance updates in **one single batched call** to `update_ticket_description` — never one Jira description update per asset. If the provenance update call fails, **retain the original bullet unchanged** in the ticket description and record a non-blocking warning that the destination mapping could not be persisted; this does not fail the run, and the filename token itself is still enough for plan generation to classify the asset even without a fully actionable destination.\n\n9. **Redact secrets everywhere.** Before writing any URL or access note ANYWHERE — the Jira `## Materials & Access` record, any warning or final-report output, and any local intermediate file — sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. Mirror the backend `_redact_forge_fields()` / `_sanitize_jira_error_message()` patterns. A location/access note must never expose a plaintext secret.\n\n10. **Warn, never halt (error handling).** This step must NEVER halt, prompt-to-fail, or fail the overarching command because a material could not be gathered, attached, or superseded. Follow the warn-not-halt convention:\n - If an `attachment` upload call fails (or a file disappeared between inventory and upload), warn gracefully and continue with the next material.\n - If a stale-attachment `delete` call fails, skip only that material's replacement upload (step 5) and continue with the rest — never halt the run.\n - This includes design comp image uploads: an unsupported/disallowed MIME type, an oversize image (`> 10 MB`), a missing local file, a malformed upload payload, or a Jira upload failure must all be warned and skipped, never halting the run.\n - On such a post-create attach/delete failure, call `update_ticket_description` to record the failure in the issue's `## Materials & Access` record (the material became unavailable, or its stale copy could not be superseded, only after the issue existed). `update_ticket_description` is an existing MCP tool, not a backend change.\n - Everything knowable PRE-CREATE was already written into the description at create time, so `update_ticket_description` is reserved for these rarer post-create attach/delete/provenance failures. This complements the existing `partial_success` recording convention in `upload-and-track.md`.\n - Apply the step 9 redaction to every warning and recorded note.\n\n## Return\n\nConfirm the outcome, reporting each category **by its user-facing phase name and original path** (not by the internal filename token, which is implementation metadata): which Planning Assets, Downloadable Assets, and Planning & Downloadable Assets were attached (noting their deterministic Jira filenames for cross-reference only), which stale phase-tagged attachments were superseded (and which supersession attempts failed and were skipped), which materials were skipped/recorded as record-only (over-size text, external/auth-gated links, ordinary/unrelated binaries, or non-local design references), whether the destination-provenance batched update succeeded, any attach/delete/provenance failures recorded via `update_ticket_description`, and that no failure halted the run.\n",
886
+ "gather-and-attach-materials.md": "Post-create materials-completeness step. Gather the phase-eligible local materials a freshly-created ticket references and attach them via `attachment` (operations: `\"list\"`, `\"upload\"`, `\"delete\"`), while recording everything that is record-only. This is the POST-CREATE half of the upload-time materials-completeness pass (BAPI-423, phase-tagged since BAPI-643); the PRE-CREATE half — inventorying and writing the `## Materials & Access` section into the draft — already ran in the `jira-ticket-writer` agent.\n\n## Inputs\n\n- `{ticket_number}` — the real Jira key of the already-created ticket (e.g. `BAPI-423`). Attachment is a POST-CREATE step; never attempt to attach before the key exists.\n- `{draft_file_path}` — path to the draft markdown that carries the trailing `## Materials & Access` section.\n- `{auto_approve_external}` — the unattended-vs-interactive signal (named for consistency with `upload-and-track.md`). **Polarity is counter-intuitive: `\"true\"` means UNATTENDED, which is the MORE restrictive mode here** — skip all prompts AND keep external/auth-gated materials record-only (never auto-attach them). It does NOT grant permission to attach external materials. Any other value (including `\"false\"`, missing, or empty) means an interactive invocation that MAY prompt for external/auth-gated materials. Invocations from `write-ticket` and `idea-to-pr` are always unattended (`\"true\"`) for this step.\n\n## Instructions\n\n> **Orchestrator-directed step.** This agent task is authorized to call `attachment` (operations: `list`, `upload`, `delete`) and `update_ticket_description` as directed below.\n\n1. **Read the record.** Read `{draft_file_path}` and parse its trailing `## Materials & Access` section. Collect the inventoried items grouped under *Planning Assets*, *Downloadable Assets*, *Planning & Downloadable Assets* (each phase-eligible; present only when a material of that phase exists), *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*. If there is no `## Materials & Access` section, there is nothing to gather — return a no-op success.\n\n2. **Canonical phase-token mapping.** Every phase-eligible bullet maps to exactly one filename token:\n - *Planning Assets* → `planasset`\n - *Downloadable Assets* → `implasset`\n - *Planning & Downloadable Assets* → `bothasset`\n\n *External/Auth-Gated Links* and *Binary/Image Materials (Record-Only)* are **never** phase-tagged and are behaviorally unchanged by this step — they stay record-only, exactly as before.\n\n3. **Source classification (scheme-based, no network probe).** Honor the classification already recorded in the draft:\n - **Planning Assets / Downloadable Assets / Planning & Downloadable Assets** — a local filesystem path that is **NOT tracked in version control** is a **low-risk** material eligible for auto-attach — proceed to step 5. The pre-create inventory already excluded version-controlled files (source code and in-repo docs are already available in the repository and are never attached — they are cited inline as *Relevant code*). As a safety net, this step must **never attach a file that is available in version control**: if any phase-tagged item is a code file or otherwise clearly version-controlled, skip it and treat it as record-only. A bullet under *Planning & Downloadable Assets* recorded via an existing `attachment_id` (not a local file — e.g. a design comp already attached on this or another ticket) is **not** re-uploaded by this step; leave it recorded with its `attachment_id`/path so a later implementation agent can download it into its worktree via the Jira attachment download capability.\n - **External/Auth-Gated Links** (every `http(s)` URI, even if explicitly linked) are **record-only** on unattended paths. If `{auto_approve_external}` is `\"true\"` (or the invocation is from `write-ticket` / `idea-to-pr`), leave them record-only and never auto-attach. (Mind the polarity: `auto_approve_external = \"true\"` means we are in unattended mode, so external materials must stay record-only — `\"true\"` is NOT permission to attach them.) Only an explicitly interactive invocation (`auto_approve_external` is any non-`\"true\"` value) may prompt the user to confirm before attaching.\n - **Binary/Image Materials (Record-Only)** — ordinary/unrelated binaries (arbitrary screenshots, PDFs, ZIPs, and other binaries not design-relevant) stay **record-only** in this step; never attempt to upload them.\n\n4. **Deduplicate and detect stale phase reclassification.** Call the `attachment` MCP tool with `operation` set to `\"list\"` and `ticket_number` set to `{ticket_number}` BEFORE uploading anything, so a resumed or re-run invocation does not re-attach a material that is already present.\n - Compute each candidate's deterministic filename first (step 6), then compare the listed attachments by **`{ticket_number}/{hash}`** (the ticket prefix plus the 8-hex-char hash segment) against the phase-tagged filenames already on the ticket, using the exact grammar `{ticket}-{planasset|implasset|bothasset}-{hash}{ext}` — this comparison spans all three phase tokens for the same source.\n - If a listed attachment shares the same `{ticket_number}/{hash}` as a candidate but carries a **different phase token**, treat it as a **stale classification for the same source** — the material was reclassified to a different phase since it was last gathered.\n - If a listed attachment's filename **exactly matches** the candidate's newly computed filename (same ticket, same phase token, same hash, same extension), it is already attached — skip re-uploading it.\n - Legacy `{ticket_number}-material-{hash}{ext}` attachments (pre-BAPI-643) are a distinct, valid, untouched naming form — never treat them as stale phase-tagged attachments, and never delete or supersede them here.\n\n5. **Supersede a stale phase-tagged attachment before uploading the reclassified filename.** When step 4 identifies a stale same-source attachment under a different phase token:\n - Call the `attachment` MCP tool with `operation` set to `\"delete\"`, `ticket_number` set to `{ticket_number}`, and `file_name` set to the stale attachment's exact filename. This is a genuine removal (not `replace_existing`, which only matches an upload's *own* filename) — the stale filename differs from the new filename whenever the phase token changed, so `replace_existing` alone cannot supersede it.\n - **If the delete call fails** (or reports `deleted: false` for a reason other than \"already absent\" — e.g. a transient Jira error), **skip the replacement upload for that material entirely** this run, preserving the one-active-attachment invariant rather than leaving two phase-tagged copies of the same source live at once. Record the failure (see step 9) and continue with the next material; never halt the run.\n - **If the delete succeeds** (or the stale attachment was already absent), proceed to upload the newly classified filename in step 7.\n\n6. **Gather and size-tier each phase-eligible local material; compute deterministic filenames.**\n - For each phase-eligible local **text** material:\n - Read the local file from disk.\n - If the content exceeds **200,000 characters**, SKIP the upload and RECORD it (note the path and that it was skipped for size) — do not attach it.\n - If the content is **<= 200,000 characters**, upload it RAW via `attachment` (operation: `\"upload\"`). Do NOT summarize locally: the backend already summarizes attached text at plan time, so the size tiers are backend behavior this step defers to. The agent performs NO local summarization.\n - **Extension rule by phase**: a **planning-only** (`planasset`) text material normalizes to `.md` regardless of its original extension — the planner only needs prose content, not the original format. A **downloadable or both-phase** (`implasset`/`bothasset`) text material instead **preserves its lowercased original source extension** (e.g. `.json`, `.csv`, `.py`, `.yml`/`.yaml`) — the implementing agent needs the file back in its original, directly usable format. There is no blanket \"all text becomes `.md`\" rule; the extension is phase-dependent.\n - Use a deterministic, sanitized filename of the form `{ticket_number}-{phase-token}-{hash}{ext}` (using the `{ticket_number}` input from the Inputs section and the phase token from step 2), where `{hash}` is the first 8 lowercase hex characters of the SHA-256 digest of the sanitized absolute source path, and `{ext}` follows the extension rule above.\n - For each eligible local design/UI comp image (or other phase-tagged image, extension mapping to an allowlisted MIME type — `image/png`, `image/jpeg`, `image/webp`, `image/gif`), eligible for the allowlist-guarded binary upload path (distinct from the text upload path above):\n - Use a deterministic filename of the form `{ticket_number}-{phase-token}-{hash}{ext}`, where `{hash}` is computed the same way (first 8 lowercase hex characters of the SHA-256 digest of the sanitized absolute source path) and `{ext}` is the lowercased allowlisted source extension (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`) — the existing image-extension behavior is unchanged by phase.\n - Pin the hash algorithm exactly (SHA-256, first 8 lowercase hex chars, of the sanitized absolute path) — do NOT substitute another hash — so the same source always maps to the same `{ticket}/{hash}` identity and the dedup/stale-detection in step 4 works across separate sessions, re-runs, and phase reclassification. Compute filenames before the step 4 dedup/stale comparison is applied. Keep the sanitized source provenance inside the text attachment body (not applicable to binary comp uploads), not only in the filename.\n\n7. **`attachment` upload parameter discipline (Zod).**\n - For text materials, call `attachment` with `operation: \"upload\"`, `ticket_number`, the deterministic phase-tagged attachment filename, and the text `content`.\n - For design comp image uploads, call `attachment` with `operation: \"upload\"`, `ticket_number`, `file_path` (the local source path), and `file_name` set to the deterministic phase-tagged comp filename from step 6 — pass `file_path` rather than reading and UTF-8-encoding the bytes yourself, so `resolveUploadAttachment()` performs binary detection, the MIME allowlist check, and base64 encoding. Never UTF-8-encode image bytes locally.\n - In both cases, OMIT the optional parameters `link_type` and `replace_existing` entirely when they are unused — do NOT pass `null` or empty strings for them. The Zod schemas reject `null`/empty values, so an unused optional parameter must be omitted rather than nulled.\n\n8. **Persist destination provenance after successful uploads (one batched update).** For every material successfully uploaded in this run, record a stable, sanitized mapping from its generated Jira filename to the bullet's original repository-relative destination path (as recorded in the draft's `## Materials & Access` section). Write these mappings as bulleted lines under a dedicated trailing `### Asset Destinations` subsection of `## Materials & Access`, one line per mapping, in the exact form:\n ```markdown\n ### Asset Destinations\n\n - `{jira-filename}` → `{original/repo/relative/destination.ext}`\n ```\n This is the canonical, machine-parsed contract consumed by `extract_asset_destination_paths()` (`api/agent_flows/code_writer/library/ticket_asset_lib.py`) — do not vary the arrow character (`→`), the backtick wrapping, or the bullet form. Append new mappings to this subsection (creating it if absent) rather than replacing prior mappings from earlier gather runs. Apply **all** of this run's successful-upload provenance updates in **one single batched call** to `update_ticket_description` — never one Jira description update per asset. If the provenance update call fails, **retain the original bullet unchanged** in the ticket description and record a non-blocking warning that the destination mapping could not be persisted; this does not fail the run, and the filename token itself is still enough for plan generation to classify the asset even without a fully actionable destination.\n\n9. **Redact secrets everywhere.** Before writing any URL or access note ANYWHERE — the Jira `## Materials & Access` record, any warning or final-report output, and any local intermediate file — sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. Mirror the backend `_redact_forge_fields()` / `_sanitize_jira_error_message()` patterns. A location/access note must never expose a plaintext secret.\n\n10. **Warn, never halt (error handling).** This step must NEVER halt, prompt-to-fail, or fail the overarching command because a material could not be gathered, attached, or superseded. Follow the warn-not-halt convention:\n - If an `attachment` upload call fails (or a file disappeared between inventory and upload), warn gracefully and continue with the next material.\n - If a stale-attachment `delete` call fails, skip only that material's replacement upload (step 5) and continue with the rest — never halt the run.\n - This includes design comp image uploads: an unsupported/disallowed MIME type, an oversize image (`> 10 MB`), a missing local file, a malformed upload payload, or a Jira upload failure must all be warned and skipped, never halting the run.\n - On such a post-create attach/delete failure, call `update_ticket_description` to record the failure in the issue's `## Materials & Access` record (the material became unavailable, or its stale copy could not be superseded, only after the issue existed). `update_ticket_description` is an existing MCP tool, not a backend change.\n - Everything knowable PRE-CREATE was already written into the description at create time, so `update_ticket_description` is reserved for these rarer post-create attach/delete/provenance failures. This complements the existing `partial_success` recording convention in `upload-and-track.md`.\n - Apply the step 9 redaction to every warning and recorded note.\n\n## Return\n\nConfirm the outcome, reporting each category **by its user-facing phase name and original path** (not by the internal filename token, which is implementation metadata): which Planning Assets, Downloadable Assets, and Planning & Downloadable Assets were attached (noting their deterministic Jira filenames for cross-reference only), which stale phase-tagged attachments were superseded (and which supersession attempts failed and were skipped), which materials were skipped/recorded as record-only (over-size text, external/auth-gated links, ordinary/unrelated binaries, or non-local design references), whether the destination-provenance batched update succeeded, any attach/delete/provenance failures recorded via `update_ticket_description`, and that no failure halted the run.\n",
887
887
  "greenfield-apply-and-report.md": "Save the project setup in one authoritative call, then hand `/install-bridge` the fresh manifest it\nresumes its reporting from. This is the only step in this pipeline that writes the install manifest.\n\n## Step 1 — Gather what the earlier steps settled\n\nCollect, from the results already produced in this pipeline:\n\n- the **snapshot token** from this pipeline's single `get_install_manifest` step;\n- the interview task's `setup_path`, candidate manifest values, VCS decision, and repository facts;\n- the synthesis task's `final_path`, approved `project_description`, and drafted fields;\n- the outcome of the ten `config_field` upload steps.\n\nUse the snapshot token **exactly as that step returned it**. Do not re-read the manifest to obtain a\nfresher one before applying: a new read invalidates the token this apply is about to use.\n\n## Step 2 — Build the apply payload\n\nInclude a field only when it was genuinely resolved. An omitted field stays unset, which is always a\nsafe outcome; a guessed field is a wrong value someone has to find and correct later.\n\n- **`working_in`** — include only when it was resolved through the authoritative configuration, clear\n local evidence, or the interview. Never infer it from an ambiguous template.\n- **`project_description`** — include **only** in the form `{ \"value\": \"...\", \"confirmed\": true }`, and\n only when the decision and standards-approval path explicitly confirmed that text. If it was never\n confirmed, omit it entirely.\n- **Unit and end-to-end test stacks** — include each only when the interview gave a definite answer.\n Never invent a framework, and never fill one in from the other.\n- **`version_control_system` and `base_branch`** — include only when the collected VCS decision and the\n local repository facts make the values valid. A project that deferred version control, or that has no\n repository yet, gets neither field.\n- **`ticket_backend_mode`** — include only when the interview legitimately asked for it because Jira\n was not configured. When Jira is configured, this field is not this pipeline's business.\n- **`greenfield_setup_path`** — always include it, set to exactly `interview` or `defaults` according\n to the **final approved outcome**: `interview` when the standards were approved, `defaults` when the\n human chose defaults, skipped the interview, or the revision loops ran out. This is the field that\n marks the project greenfield-complete, so it must describe what actually happened.\n\n## Step 3 — Apply exactly once\n\nCall `apply_install_manifest` **exactly once**, with the snapshot token from Step 1 and the fields from\nStep 2.\n\nOn the `defaults` path, the server seeds this project's catalog defaults as part of this call and\nreturns the per-field outcomes in `greenfield_defaults_seeding`. Report those outcomes as the server\nreturned them. `noop_already_current`, `swapped`, and `skipped_user_content` are all successful results\n— an idempotent re-apply and a project whose content someone already edited are both correct.\n\nThe apply is partial-tolerant: a field that fails validation lands in the `rejected` bucket while the\nvalid fields still commit. Report a rejected field; do not retry the whole call because of one.\n\n## Step 4 — Read the manifest back\n\nAfter the apply succeeds, call `get_install_manifest` **once**. This read-after-write response reflects\nwhat was just written, and it is what `/install-bridge` needs: its Stage 7 capability report and its\nStage 8 next step both come from this response and from nothing else. This read does not need a\nsnapshot token.\n\n## Step 5 — Failures\n\nIf the apply fails, or the read-after-write fails, report it through this pipeline's ordinary failure\nshape, naming which of the two failed and what the server said.\n\n**Do not retry the apply with a new snapshot token.** A retry that re-reads the manifest for a fresh\ntoken would apply a second time against state the first attempt may already have changed. Reporting a\nclean failure lets the human re-run `/install-bridge`, which is safe.\n\n## Constraints\n\n- **Never call `parse_repository`**, and never mention or invoke `/learn-repository` in any output. A\n brand-new project has nothing to index and nothing to learn from; the server decides the next step and\n returns it in `next_step`.\n- **Never request, echo, log, document, or return a credential** or any secret-like value.\n\n## Return\n\nReturn the complete read-after-write manifest from Step 4 to the calling `/install-bridge` flow — it is\nthe authoritative source for that command's Stage 7 report and Stage 8 next step, and the command\nrequires it before it resumes.\n\nAlongside it, report:\n\n- `greenfield_setup_path` as applied (`interview` or `defaults`);\n- the apply buckets the server returned (applied, skipped, conflict, needs_confirmation, rejected,\n deferred);\n- `greenfield_defaults_seeding` verbatim when the defaults path ran, omitted otherwise;\n- which standards fields were written and which uploads were skipped;\n- whether the deferred version-control guide was written, and its path.\n",
888
888
  "greenfield-interview.md": "Settle what this new project is, what it must not do, and how its code will be reviewed — before any\ncode exists. This step gathers a bounded framing from the human, ratifies whatever it could not settle\non its own, and decides how version control will work. It is the only step in this pipeline that\ninterviews the human about the project itself.\n\nNothing downstream may run until this step ends in an explicit decision commit.\n\n## Operating rules (apply to the whole task)\n\n1. **Never ask for a secret, and never echo one.** No API key, token, password, connection string, or\n `.env` value is an input here. If the human volunteers one, do not repeat it back, do not write it\n to any file, and do not carry it into the decision page, the framing document, the council brief, or\n this task's return value. Redact it to `[redacted]` at the moment you notice it.\n2. **Ask about the project, not about the repository's code.** This project is new; there is nothing to\n read. Local evidence is worth a glance (a `package.json`, a framework template) but it never\n overrides an answer the human gives.\n3. **Keep the surface calm.** One question at a time, a compact progress marker, and no diagnostic\n transcript. The human is being onboarded, not debugged.\n4. **A headless session never waits.** Wherever this file says to ask, a session that cannot obtain an\n interactive response takes the stated non-interactive branch immediately and continues.\n\n## Step 1 — Offer the interview\n\nLead with this exact sentence, verbatim:\n\n`Skipping uses generic defaults; the agents will write noticeably weaker code until standards exist.`\n\nThen offer exactly two choices:\n\n- **Interview** — answer up to eight short questions so the standards match this project. This is the\n higher-quality path, and say so.\n- **Use defaults** — skip the questions and start from generic defaults.\n\nPresent defaults as a legitimate choice, not a failure or a warning. Someone who wants to write code\nright now is making a reasonable trade, and the sentence above already tells them what it costs.\n\nRecord the answer as the **setup path**: `interview` or `defaults`.\n\n**In a headless session, select `defaults` immediately** and continue — do not wait, do not prompt, and\ndo not treat the absent human as an error.\n\nWhen the setup path is `defaults`, skip Steps 2 through 6 entirely: no questions, no framing document,\nno decision page. Go straight to Step 7 (version control), which runs on both paths, and then return.\n\n## Step 2 — Interview (at most eight questions)\n\nAsk **at most eight** questions, one at a time, covering these areas in this order. Merge or drop any\nthat a previous answer already settled — eight is a ceiling, not a quota.\n\n1. **Purpose and users** — what this project is for and who uses it.\n2. **Non-goals** — what it deliberately will not do.\n3. **Language and framework commitments** — what is already decided about the stack.\n4. **Architecture shape** — the rough structure (service, CLI, library, app, monolith, split).\n5. **Testing expectations** — what \"tested enough\" means here.\n6. **Quality bars** — what \"good\" means: performance, accessibility, security posture, review rigor.\n7. **Delivery constraints** — deadlines, platforms, environments, or team constraints that shape the\n code.\n8. **Must-never-happen** — the failures that would be unacceptable in this project.\n\nShow progress compactly — for example `Question 3 of 8` — and nothing else. Do not print the running\ntranscript, an evidence dump, or a per-answer confirmation block.\n\n## Step 3 — Collect the configuration facts the setup needs\n\nThese are configuration values, not framing. Fold them into the same eight-question budget wherever\nthey fit naturally (the stack question and the language/framework question are usually the same\nquestion), and never ask for one whose answer you already have.\n\n- **`working_in`** — ask **only** when local evidence and existing configuration do not already resolve\n it. Offer exactly these values, and never invent, narrow, or reword the list:\n `Salesforce Commerce Cloud`, `Salesforce Core`, `Scayle`, `Shopify`, `Python`,\n `JavaScript/TypeScript`, `Go`, `Java/Kotlin`, `Ruby`, `.NET`, `Other`.\n- **Unit test stack and end-to-end test stack** — collect each only when it applies. Do not guess an\n ambiguous framework and do not offer to pick one later: an unset stack is a fine outcome, a wrong one\n is not.\n- **`ticket_backend_mode`** — ask **only** when the authoritative configuration shows Jira is not\n configured for this project. When Jira is configured, do not raise the subject at all.\n\nCarry every collected value forward as a **candidate manifest value**. This task writes none of them —\nthe final apply task owns every write.\n\n## Step 4 — Frame the answers into goals and NFRs\n\nWrite the framing to `{docs_dir}/greenfield/{repo_name}/goals-and-nfrs.md`.\n\nUse the vocabulary and structure defined in `frame-goals-and-nfrs.md`: a business goal, a desired\nend-state, the system behavior, and a classified NFR list where each NFR carries a `requirement`, its\n`implication`, and a `status` of `confirmed`, `assumed`, or `open`. Apply that file's rubric for the\nstatus classification, and its rule that an NFR with no concrete implication is boilerplate and should\nbe dropped rather than recorded. Omit the recommended-implementation-order section — this is a project\nframing, not an epic decomposition.\n\nThe interview answers are the input in place of an epic description. Where an answer settles something\noutright, mark it `confirmed`. Where you filled a conventional, low-risk, reversible gap yourself, mark\nit `assumed`. Where the answer touches architecture, the data model, security, user-visible behavior,\nor something otherwise expensive to reverse and is not settled, mark it `open` — that is what the next\nstep exists to resolve.\n\n## Step 5 — Put the open NFRs on a decision page\n\nCall `generate_decision_page` with the routing fields at the root and the content nested under\n`content`:\n\n- `artifact_type`: `pre_ticket_planning`\n- `ticket_key`: `greenfield-{repo_name}`\n- `output_subdir`: `greenfield/{repo_name}`\n- `labels`: onboarding-flavored overrides — for example a `title` naming the project setup and a\n `section_heading` such as \"Open questions about this project\".\n- `content.system_goals` (read-only): `business_goal`, `desired_end_state`, `system_behavior`, and the\n full classified `nfrs` list from Step 4.\n- `content.actionable_items`: **one card per `open` NFR**, built exactly as `clarify-open-nfrs.md`\n specifies — a short stable `id`, the `question` the open NFR poses, 2–4 concrete `options`, one\n `option_consequences` line per option, `why_it_matters`, `recommendation_explanation`, and a 0-based\n `recommendation_index`. Do not add \"None of these\" or \"Ask about this\" options yourself; the renderer\n appends both, and they are how the human disagrees or asks rather than commits.\n\nOrder the surface so the **project framing and the unresolved questions come first** and the supporting\nevidence sits behind them. Expose exactly **one** clear commit action — the page's own submission. Do\nnot offer a second competing way to finish.\n\nIf there are no `open` NFRs at all, still require an explicit commit: present the framing in chat and\nask for it there, exactly as Step 6's fallback does. Ratification is the gate, not the page.\n\n## Step 6 — Wait for the commit, and resolve everything it left open\n\nDirect the human to the returned `file_path`, and **stop and wait**.\n\nTreat a paste as a commit only when it is a JSON object carrying all three top-level fields:\n`ticket_key` (string), `decisions` (object), and `general_comment` (string).\n\nThen settle every card:\n\n- **Committed** — an explicit option choice. Record it.\n- **Ask** — the human wants to discuss it. Discuss until they give an explicit decision, then record\n that decision. Every `ask` must become an explicit recorded decision.\n- **Disagree** — the \"none of these\" answer, meaning no offered option is right. Capture the reason\n they gave, in the card comment or in chat, and settle the question with them explicitly. A disagree\n that is never resolved is not a commit.\n\nAn explicit conversational change to a decision after the paste is an in-flight override and takes\nprecedence over the submitted JSON.\n\nThen fold the results back into `{docs_dir}/greenfield/{repo_name}/goals-and-nfrs.md`: move each\nresolved NFR's `status` from `open` to `confirmed` (or `assumed` when the human chose a provisional\ndefault), record the chosen resolution in its `requirement` and `implication`, and weave\n`general_comment` in as overarching guidance.\n\n**If `generate_decision_page` fails**, do not stall and do not skip the gate: present the same\ncontent — the framing, then each open question with its options and consequences — directly in chat,\nand require the same explicit commit there. The surface may degrade; the ratification may not.\n\n**Nothing downstream may run before this commit.** Do not call `request_council`, do not draft any\nstandards field, and do not begin synthesis until a valid explicit decision commit exists. The visible\norder is `Decision committed` → `Standards review` → `One approval`, and it is not merely cosmetic:\nstandards drafted before the commit would encode assumptions the human never agreed to.\n\n## Step 7 — Settle version control (both paths)\n\nAsk whether this project will use GitHub.\n\nTreat absent Git metadata — no repository, no commits, no remote — as neutral context, not as an error,\na warning, or an answer. A brand-new project legitimately has none of it.\n\n**If yes:** retain `version_control_system` = `github` as a candidate manifest value, and print the\ncommand the human runs to connect it, as wrapped monospace technical text on its own line:\n\n`npx -y @bridge_gpt/mcp-server@latest connect-github --repo {repo_name}`\n\nDo not run it yourself — connecting the repository is the human's own explicit next action.\n\n**If no (or deferred):** write `{docs_dir}/greenfield/{repo_name}/connect-version-control.md`\nidempotently — write the guide when it is absent, refresh it in place when it already exists, and never\nappend a second copy. The guide explains, in order:\n\n1. Creating the repository when they are ready.\n2. Installing the Bridge GitHub App on it.\n3. Running the `connect-github` command above.\n4. What connecting unlocks: repository indexing, pull-request creation, CI monitoring and follow-up,\n and the council modes that need an indexed repository (the `general` council works without it).\n\nThen say the guide was written and where, in one line. Do not treat a deferred VCS as a problem — it is\na normal state for a project that has not started yet.\n\nIn a headless session, do not wait for this answer either: skip the question, write the deferred-VCS\nguide, and leave `version_control_system` unset.\n\n## Return\n\nReturn a secret-free structured payload for the rest of the pipeline. It must carry:\n\n- `setup_path` — exactly `interview` or `defaults`.\n- `decision_committed` — whether an explicit commit was received (always `false` on the `defaults`\n path, which asks no questions to commit to).\n- `framing` — the committed business goal, desired end-state, system behavior, and the settled NFR list\n with each one's final `status`, plus the path to `goals-and-nfrs.md`. Omit on the `defaults` path.\n- `candidate_manifest_values` — the configuration values collected here for the final apply task:\n `working_in`, the unit and end-to-end test stacks, and `ticket_backend_mode` when it was legitimately\n asked for. Include only values that were actually resolved.\n- `vcs` — the decision (`github`, or deferred), whether the connect command was printed, and whether\n `connect-version-control.md` was written and at what path.\n- `repository_facts` — whether a local repository, any commits, and a remote exist. Report absence as\n absence, never as an error.\n\nNever include a credential, a token, or any secret-like value in this payload, and never include the\nraw interview transcript.\n",
889
889
  "greenfield-synthesize-standards.md": "Turn the ratified project framing into one coherent set of project standards, and take exactly one\napproval on the whole set. These standards are what the agents will follow until this project has real\ncode to learn from.\n\n## Step 0 — Decide whether this task runs at all\n\nRead the previous task's result.\n\n- **`setup_path` is `defaults`** — return immediately. Do not call `request_council`, do not draft a\n single standards field, and do not ask for approval. The defaults path exists precisely to skip this\n work, and the server seeds catalog defaults when the final apply records it.\n- **`setup_path` is `interview` but `decision_committed` is not true** — refuse to execute. Report that\n the interview did not end in a ratified decision commit and return without drafting anything.\n Standards synthesized from an uncommitted framing would encode assumptions the human never agreed\n to, which is the one failure this pipeline's ordering exists to prevent.\n- **`setup_path` is `interview` and the decision was committed** — continue.\n\n## Step 1 — Determine which fields are unset\n\nRead the ten `config_field` get results from earlier in this pipeline. A field is **unset** — and\ntherefore in scope — when its `value` is null, absent, or whitespace only, **or** when\n`is_catalog_default` is true (Bridge-seeded catalog content is not this project's standard).\n\nEvery other field is already populated. Exclude it: do not draft it, do not summarize it, and do not\ninclude it in the approval screen. Decide this from the fetch results, not from whether a draft file\nhappens to exist on disk from an earlier run, and not from the upload steps' `only_if_null` guard —\nthat guard is the server's last line of defense, not this task's decision procedure.\n\n## Step 2 — Ask the council once\n\nCall `request_council` exactly once, with:\n\n- `mode`: `\"general\"` — hardcoded. **Never** `technical` and never `discovery`: both expect an indexed\n repository, and this project has no code to index. Never pass a different mode, and never retry with\n one.\n- `wait_for_result`: `true`.\n- `task_description`: the brief described below.\n\nBuild the brief from the ratified framing (the business goal, desired end-state, system behavior, and\nthe settled NFR list), the definitions of the ten standards fields listed in Step 3, and each field's\ncharacter cap. Ask the council for a **translation brief** — how this project's goals and constraints\nshould shape each standard, what to emphasize, what to leave out. Do **not** ask it to write the final\nstandards text; that is Step 3's job and it must read as one coherent voice.\n\nRedact any secret-like value out of the brief before sending it.\n\n**A council failure or timeout is not a failure of this task.** Continue the synthesis without it and\nmention the missing advisory input once, as a plain informational line — not a warning, not an error,\nand not a reason to pause. The standards still get written.\n\n## Step 3 — Synthesize one coherent set of standards\n\nDraft **one** set of standards covering every unset field from Step 1 plus a concise\n`project_description`. Write them as a single coherent whole — the review standard should reflect the\nsame architecture the architecture standard describes, and the testing standards should agree with each\nother. Do not generate ten unrelated field-by-field documents.\n\nThe ten fields, and where each draft goes:\n\n| Field | Draft path | Max characters |\n| --- | --- | --- |\n| `architecture_instructions` | `{docs_dir}/greenfield/{repo_name}/standards/architecture_instructions.md` | 6000 |\n| `review_instructions` | `{docs_dir}/greenfield/{repo_name}/standards/review_instructions.md` | 6000 |\n| `documentation_instructions` | `{docs_dir}/greenfield/{repo_name}/standards/documentation_instructions.md` | 1500 |\n| `unit_testing_instructions` | `{docs_dir}/greenfield/{repo_name}/standards/unit_testing_instructions.md` | 6000 |\n| `e2e_testing_instructions` | `{docs_dir}/greenfield/{repo_name}/standards/e2e_testing_instructions.md` | 6000 |\n| `frontend_correctness_standards` | `{docs_dir}/greenfield/{repo_name}/standards/frontend_correctness_standards.md` | 6000 |\n| `backend_correctness_standards` | `{docs_dir}/greenfield/{repo_name}/standards/backend_correctness_standards.md` | 6000 |\n| `template_correctness_standards` | `{docs_dir}/greenfield/{repo_name}/standards/template_correctness_standards.md` | 6000 |\n| `style_correctness_standards` | `{docs_dir}/greenfield/{repo_name}/standards/style_correctness_standards.md` | 6000 |\n| `design_principles` | `{docs_dir}/greenfield/{repo_name}/standards/design_principles.md` | 6000 |\n\nRules for every draft:\n\n1. **Write only the unset fields.** A populated field keeps its existing content untouched.\n2. **Begin every draft with this exact marker line, as its first line:**\n `> Greenfield standard: chosen before implementation code existed; revisit once the repository has real code.`\n3. **Respect the character cap** in the table — 6,000 characters per field, and 1,500 for\n `documentation_instructions`. Measure in characters. Condense rather than truncate: drop\n repetition and long examples, never a required section and never mid-sentence.\n4. **Derive each field's section skeleton from `learn-repository-fan-out.md`**, whose field matrix names\n what each standard must cover. Adapt the depth to a project with no code: state the intended\n convention rather than citing files that do not exist, and never invent a file path, command, or\n pattern. Never mention the `/learn-repository` command to the human — this project has nothing to\n learn from yet, and pointing them at it is a dead end.\n5. **Redact secrets** from every draft, every summary, and everything returned.\n\nWrite each draft to its path in the table. A field you decide not to draft must leave **no file** at\nits path, so the declarative upload step that follows simply skips it.\n\n## Step 4 — One review screen, one approval\n\nPresent **one** screen containing:\n\n- the proposed `project_description`, in full — it is short, and it is what the human is really\n confirming; and\n- **one short summary per drafted field** — a line or two naming what that standard commits the project\n to.\n\nKeep the full drafts secondary: expandable, or offered on request, wherever the interaction surface\nsupports it. The screen the human reads is the summary, not ten documents.\n\nThen ask **exactly one** approval question, with two outcomes: `yes` or `no`.\n\n- **`yes`** — the drafts stand as written. Continue.\n- **`no`** — ask exactly one follow-up: `targeted revisions (which?) or use defaults?`\n - **Targeted revisions** — revise only the named fields, then present the same one-screen review and\n the same single approval question again. Allow **at most two** revision loops.\n - **Use defaults**, or a second unapproved round after those two loops — the final outcome becomes\n `defaults`. Discard the drafts: delete every draft file this task wrote so nothing unapproved can\n be uploaded, and record the final path as `defaults`. Unapproved and abandoned drafts are not\n standards, and must never reach a `config_field` write.\n\nMaintain the visible sequence `Decision committed` → `Standards review` → `One approval`. Never imply\nthat synthesis began before the commit, because it did not.\n\n## Return\n\nReturn a secret-free structured payload carrying:\n\n- `final_path` — `interview` when the standards were approved, `defaults` when the human chose defaults\n or the revision loops ran out.\n- `project_description` — the approved description, or absent when the final path is `defaults`.\n- `drafted_fields` — the fields with an approved draft on disk, each with its path; empty when the\n final path is `defaults`.\n- `field_summaries` — the one-line summary shown for each drafted field.\n- `skipped_fields` — the fields excluded because they were already populated.\n- `council_available` — whether the council result was used, or the synthesis proceeded without it.\n- `revision_loops` — how many targeted revision rounds ran (0, 1, or 2).\n\nNever include a credential, a token, or any secret-like value in this payload.\n",
@@ -909,5 +909,7 @@ export const INSTRUCTIONS = {
909
909
  "upload-and-track.md": "Step-10 umbrella upload instruction. Idempotently create the Jira ticket(s) for this run, attach the full draft(s), and call `track_ticket`.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`.\n- Draft metadata: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/draft-metadata.json`.\n- For epic runs, this instruction is also responsible for producing or refreshing `{docs_dir}/idea-to-ticket/{slug}-{run_id}/decomposition-plan.json` before any Jira mutation, by following `decompose-epic-candidate.md` (hard cap `{max_children}`).\n- Pipeline variable `auto_approve_external` controls whether the external-mutation pause is skipped (for this run, `auto_approve_external` = `{auto_approve_external}`). Treat the literal string `\"true\"` as skip; any other value (including `\"false\"`, missing, or empty) means pause and ask.\n\n## Instructions\n\n> **Orchestrator-directed step.** This agent task is part of the full-automation chain and is authorized to call `get_tickets`, `create_ticket`, `attachment` (operations: `upload`, `list`), `update_ticket_description`, `track_ticket`, and `add_comment`, and to execute the shared `gather-and-attach-materials.md` instruction, as directed below — performing orchestrator-directed tool calls is not \"re-orchestrating\".\n\n1. Read `run-manifest.json` and `draft-metadata.json`. Branch internally based on the manifest's `scope`:\n - `task` or `spike` → follow the **Single-ticket path** below.\n - `epic_candidate` → follow the **Epic path** below.\n The orchestrator does not support conditional steps; this branching lives in agent logic.\n\n2. External approval gate, applied before any mutating MCP tool call:\n - If `auto_approve_external` is `\"false\"` (or any non-`\"true\"` value), summarize the exact planned Jira mutations — list every `create_ticket`, `attachment` (operation: `\"upload\"`), and `track_ticket` call with its key arguments — and ask the user for explicit confirmation in this agent task before proceeding.\n - If `auto_approve_external` is `\"true\"`, proceed without the confirmation pause.\n\n3. **Single-ticket path** (`scope` is `task` or `spike`):\n 1. Idempotency lookup. Call `get_tickets` with its `labels` parameter set to both the per-run label `<idempotency_label>` and the stable `bapi-idea-hash-{idea_hash}` label from `draft-metadata.json` (comma-separated). If a match is found by either label, reuse that ticket key and skip `create_ticket`.\n 2. If no match was found, call `create_ticket` with `summary`, `slim_description` as the description, `issue_type`, and `labels` exactly as written in the metadata. Capture the returned `ticket_key`.\n 3. Upload the full markdown draft via `attachment` (operation: `\"upload\"`) using `attachment_path`.\n 4. **Gather and attach referenced materials.** Execute the shared `gather-and-attach-materials.md` instruction as an `agent_task`, passing `ticket_number` = the resolved ticket key, `draft_file_path` = `attachment_path`, and `auto_approve_external` = the inherited `{auto_approve_external}` value. It attaches phase-eligible local materials (Planning Assets, Downloadable Assets, and Planning & Downloadable Assets) and records external/auth-gated and binary/image materials per its own warn-not-halt rules. Any attach failure it reports is recorded (via `update_ticket_description`) as `partial_success` and never halts this step.\n 5. Call `track_ticket` with the resolved ticket key so Bridge API picks the new ticket up.\n 6. Write `{docs_dir}/idea-to-ticket/{slug}-{run_id}/upload-state.json` describing the final state.\n\n4. **Epic path** (`scope` is `epic_candidate`):\n 1. If `decomposition-plan.json` does not yet exist for this run, follow `decompose-epic-candidate.md` first to produce it (hard cap `{max_children}`). If it **does** already exist, it is the approved manifest and this step performs **no fresh decomposition** — read it and use it as written. Decomposition happens once; re-deriving the split immediately before upload is how children end up overlapping or contradicting the parent they are about to be created under.\n 2. Render bodies against the frozen manifest by following `render-ticket-manifest.md`: one `jira-ticket-writer` invocation per entry that lacks a draft on disk, each bound to its own entry's fixed boundary and size band, using the `draft_path` from the decomposition plan. A rendering invocation may not re-split, merge, reorder, renumber, or rescope. Its preflight is fail-closed — an unapproved manifest, a changed manifest identity, an XL epic child, or a missing writer draft stops the flow **before** any Jira mutation. After drafting, extend `draft-metadata.json` so `children[]` mirrors the final list from the decomposition plan.\n 3. Create only from writer-produced drafts. Every ticket body uploaded below came from `jira-ticket-writer`; nothing here composes a description inline.\n 4. Parent first. Look up the Epic parent by `bapi-idea-to-ticket-{run_id}-parent` via `get_tickets`. If found, reuse that key; otherwise call `create_ticket` with the parent's summary, slim description, issue type `Epic`, and parent labels. Attach the Epic draft via `attachment` (operation: `\"upload\"`) using `parent.attachment_path`. Then **gather and attach the Epic parent's referenced materials** by executing the shared `gather-and-attach-materials.md` instruction as an `agent_task`, passing `ticket_number` = the Epic key, `draft_file_path` = `parent.attachment_path`, and `auto_approve_external` = the inherited `{auto_approve_external}` value. Then call `track_ticket` for the Epic key.\n 5. Children next. For each child in order:\n - Look up by the child's `idempotency_label`. If found, reuse that key.\n - Otherwise call `create_ticket(parent_key=<epic_key>)` with the child's `summary`, `slim_description`, `issue_type`, and `labels`. The `parent_key` is required so Jira's modern parent linkage is set.\n - Upload the child draft via `attachment` (operation: `\"upload\"`) using `draft_path`.\n - **Gather and attach this child's referenced materials** by executing the shared `gather-and-attach-materials.md` instruction as an `agent_task`, passing `ticket_number` = the child key, `draft_file_path` = `draft_path`, and `auto_approve_external` = the inherited `{auto_approve_external}` value.\n - Call `track_ticket` for the child key.\n 6. After every parent or child mutation, write partial progress to `{docs_dir}/idea-to-ticket/{slug}-{run_id}/upload-state.json` so a later resume can pick up exactly where the run stopped.\n 7. **Recommended implementation order comment.** Once the Epic parent and all surviving children exist (real keys known), post a single comment on the Epic via `add_comment` with `ticket_number` set to the Epic key. The comment carries (a) a short System Goals / Non-Functional Requirements summary from `goals-and-nfrs.md`, and (b) the **Recommended Implementation Order** — the children in order, each referenced by its real Jira key, derived from the `depends_on` / `recommended_after` / `order_rationale` fields in `decomposition-plan.json`. State that this is recommended sequencing only — do **not** create Jira dependency links and do **not** attach a separate markdown doc. Skip this only if the run reused a pre-existing comment for the same run (idempotency); do not post duplicate order comments on resume.\n\n5. Required child label set whenever any child is created: `ai-generated`, `idea-to-ticket`, `idea-to-ticket-child`, and `bapi-idea-to-ticket-{run_id}-child-<N>` (1-based index from the decomposition plan).\n\n6. Partial-failure recovery rules:\n - If `create_ticket` succeeds but `attachment` (operation: `\"upload\"`) fails, record the outcome as `partial_success` in `upload-state.json` and continue with the next planned mutation; do not retry inside this step.\n - If the Epic parent is created successfully but one or more children fail, preserve the parent key and any completed child keys in `upload-state.json` before raising the failure.\n - On resume of any prior run, search by every relevant idempotency label first (`bapi-idea-to-ticket-{run_id}` for single tickets, `bapi-idea-to-ticket-{run_id}-parent`, and each `bapi-idea-to-ticket-{run_id}-child-<N>`) before considering any `create_ticket` call. Idempotency labels are how this pipeline avoids creating duplicate tickets across retries.\n\n## Return\n\nConfirm the run's final upload outcome: attachment results, `track_ticket` outcome, and any `partial_success` rows recorded in `upload-state.json`.\n\nThen, as the FINAL content of your reply, emit a fenced ```json block holding the authoritative payload for this run — and nothing else. The chain reads ONLY this final fenced JSON block to pick its review / start-tickets targets, so it must contain exactly the keys from `upload-state.json` and never any key you merely looked up during duplicate detection. Duplicate-detection / looked-up keys must not appear in this authoritative payload unless they are the final created/reused ticket for this run.\n\nThere are exactly two authoritative final payload shapes:\n\n- **Single-ticket path** (`scope` is `task` or `spike`): emit strictly `created_ticket_keys` containing **exactly one** implementable ticket key. `created_ticket_keys` is only for the single-ticket `task`/`spike` path and must contain exactly one implementable ticket key:\n\n ```json\n {\"created_ticket_keys\": [\"BAPI-331\"]}\n ```\n\n- **Epic path** (`scope` is `epic_candidate`): emit the Epic parent key separately as `epic_parent_key`, and the implementable children as `child_ticket_keys`:\n\n ```json\n {\"epic_parent_key\": \"BAPI-400\", \"child_ticket_keys\": [\"BAPI-401\", \"BAPI-402\"]}\n ```\n\n `child_ticket_keys` contains **only** implementable child Task/Spike ticket keys, listed in final decomposition order. `child_ticket_keys` must **never** include the Epic parent key.\n",
910
910
  "upload-epic-hierarchy.md": "Standalone Epic upload protocol. Use as the detailed reference for the Epic path triggered from `upload-and-track.md`.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json` with `scope == \"epic_candidate\"`.\n- Draft metadata: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/draft-metadata.json` with a populated `parent` and `children`.\n- Decomposition plan: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/decomposition-plan.json`.\n- Pipeline variable `auto_approve_external` governs the external-mutation pause as in `upload-and-track.md` (for this run, `auto_approve_external` = `{auto_approve_external}`).\n\n## Instructions\n\n1. Parent idempotency lookup. Search Jira via `get_tickets` for issues carrying the label `bapi-idea-to-ticket-{run_id}-parent`. If a match exists, reuse that ticket key as the Epic parent and skip `create_ticket` for the parent. Otherwise call `create_ticket` with the parent's summary, slim description, `issue_type = \"Epic\"`, and labels including `ai-generated`, `idea-to-ticket`, and `bapi-idea-to-ticket-{run_id}-parent`. After creation or reuse, upload the Epic draft via `attachment` (operation: `\"upload\"`) and call `track_ticket`.\n\n2. Capture the resolved Epic key into a local variable `epic_key`. Every subsequent child mutation must reference this exact key.\n\n3. Per-child idempotency lookup. For each child in `decomposition-plan.json` (in order), search Jira by the child's `idempotency_label` (`bapi-idea-to-ticket-{run_id}-child-<N>`). If a match exists, reuse that key and skip `create_ticket` for that child. Otherwise call `create_ticket(parent_key=<epic_key>)` with:\n - `summary` — child summary.\n - `slim_description` — child slim description.\n - `issue_type` — typically `Task` (or `Spike` when the child is primarily discovery).\n - `labels` — `ai-generated`, `idea-to-ticket`, `idea-to-ticket-child`, and the child's own `bapi-idea-to-ticket-{run_id}-child-<N>` label.\n The `parent_key` argument is REQUIRED for every child `create_ticket` call so Jira sets the modern parent relationship; never omit it.\n\n4. After each child is created or reused, upload its draft via `attachment` (operation: `\"upload\"`) using the child's `draft_path`, then call `track_ticket` for that child key, then append the child outcome to `upload-state.json` in the run directory.\n\n5. On partial failure (e.g., parent succeeded, third child failed), preserve `epic_key` plus every completed child key in `upload-state.json`. The next run of this protocol must rediscover those keys via the idempotency-label lookups in steps 1 and 3 before considering any new `create_ticket` call.\n\n## Return\n\nConfirm the Epic key, the number of children created vs reused vs failed, and the path of the updated `upload-state.json`.\n",
911
911
  "verify-plan.md": "Close the remaining plan gaps for ticket {ticket_key}, now that the pull request is open.\n\nThe plan's work has already been executed. The production phase authored the\nartifacts, the checkpoint pushed them, and the pre-PR verification phase ran the\nplan's review steps, test commands, and rendered-UI remediation — publishing each\nmaterial correction as it went. The pull request was then opened on top of all of it.\n\nThis phase exists for the narrow remainder: the plan obligations that genuinely could\n**not** be reached before a pull request existed, plus corrections attributable to this\nticket.\n\nTwo consequences follow, and both are deliberate:\n\n- **This phase does not re-run completed work.** A step the durable ledger records as\n `executed` or `adapted` stays settled unless a later correction invalidated its\n evidence. Re-running it duplicates work the pre-PR phase already did and burns the\n budget this protocol was reordered to protect.\n- **This phase never issues a verdict.** You report what you observed. The\n authoritative pass/fail belongs to the pipeline's `ci` and `code_review` gates,\n which the reconciler observes independently. Worker self-verification has\n demonstrably reported green while the full suite was red; that is exactly why the\n gates, not this phase, decide.\n\n**Tool and scope boundary.** Use only the tools this recipe names, the repo's own tooling, and MCP\ncapabilities provisioned for this repository. Keep all work confined to this worktree unless\nexplicitly told to do otherwise.\n\n**Execution mode for this run: `{execution_mode}`.** Under `orchestrated`\n(a server-side orchestrator) orchestration appends the routed phase context and\nparses the fenced result envelope you return. Under `inline` (`get_pipeline_recipe`)\nthere is no orchestrator: the ledger is read with a tool call and written with one.\nFollow the branch that matches wherever the two are named.\n\n---\n\n## Step 1 — Establish that the durable artifact exists\n\nBefore running any check:\n\n1. Run `git branch --show-current` and `git rev-parse HEAD`, then verify the branch\n has been pushed and the local head is present on the remote (for example via\n `git status -sb` showing no unpushed ahead-count, or `git ls-remote origin <branch>`).\n2. Verify that a usable pull request URL was obtained by the preceding PR step —\n either a newly opened pull request or an already-open one on this head branch.\n\nIf the branch is not pushed, or no usable pull request URL exists, then\n**stop this phase** and report the missing prerequisite. This phase exists only to\nadd work on top of an open pull request.\n\n## Step 2 — Recover what remains from durable state\n\n1. Call the `get_plan` tool for `{ticket_key}`. The local copy at\n `{docs_dir}/plans/{ticket_key}-plan.md` may be used as a reference.\n2. Recover the durable ledger, by mode:\n - **orchestrated** — read the **Routed phase context** block appended to this\n instruction: `ledger` carries every disposition earlier phases recorded, and\n `ownedSteps` carries anything routed directly to this phase.\n - **inline** — call `get_phase_context` with `ticket_key` `{ticket_key}` and\n `phase` `post_pr_gap_close`. It returns the same `ownedSteps` plus the `ledger`\n merged from every earlier phase's artifact, `terminalStepIds` for what is\n already settled, and `unresolved` for the escalations that are this phase's\n actual subject.\n\nRecover the remaining work from that durable record, not from conversation. A\ncompaction or a resumed session loses the conversation; the ledger survives both,\nwhich is why it replaced the conversational hand-off.\n\n## Step 3 — Select only genuine gaps\n\nRun only:\n\n- Steps the ledger records as `escalated` **because a capability was unavailable\n before the pull request existed**, or **because the check genuinely requires an open pull request** — and\n which are now satisfiable.\n- Corrections clearly attributable to this ticket's change.\n\nReport — do not attempt to fix — failures that are unrelated to this ticket, flaky,\nenvironmental, pre-existing on the base branch, or outside the declared file scope.\nSpeculative edits made under budget pressure are how a correction round turns into a\nregression.\n\nApply the same adaptation boundary the earlier phases use: **locator-correction**,\n**repository-command-correction**, and **equivalent-implementation-recognized** are\nmechanical and may be applied; anything touching design, schema, public API,\ndependencies, or security escalates instead.\n\nBefore beginning any correction, apply the **low-budget guard**: only start if enough\nsession budget clearly remains to make the edit, commit it, *and* push it. Starting a\nfix you cannot finish and publish is strictly worse than reporting the finding and\nletting the CI and review gates handle it — an unpushed correction is invisible to\nthose gates.\n\n## Step 4 — Report what you observed, honestly\n\nFor every check you run, record the exact command, its observed result, and — on\nfailure — the relevant failure detail (the failing test names, the error output, the\ndiagnostic lines).\n\nReport all of it, including failures you did not fix. Never soften or omit a failing\nresult.\n\nDescribe only what you observed. Do not write that CI passed, that the gate is met,\nthat the review is approved, or any equivalent claim about the pipeline's verdict —\nthose states are decided by the `ci` and `code_review` gates and observed by the\nreconciler, never asserted by this phase.\n\n## Step 5 — Correct only what is clearly yours, and push it immediately\n\nFor each accepted correction:\n\n1. Make the edit.\n2. Stage the specific files, commit, and **push immediately** — the commit and its\n push are one consecutive sequence, never separated by another check. A local\n commit that is never pushed is not visible to the pull request, to CI, or to the\n reconciler.\n3. Run `git rev-parse HEAD` again and record the new pushed head as\n `last_commit_sha`.\n\n## Step 6 — Final git-state audit\n\nBefore returning, run `git status --porcelain` and resolve the working tree:\n\n- Legitimate corrections still uncommitted → commit and push them (Step 5's\n commit-then-push-immediately rule applies).\n- Accidental diagnostic edits — debug prints, scratch files, temporary config\n tweaks made while investigating a failure → revert them when it is safe to do so.\n- Anything you cannot safely resolve → leave it and **report it explicitly**,\n naming each remaining dirty path.\n\nNever return leaving unpushed commits unreported.\n\n## Step 7 — Hand unresolved findings forward\n\nAn unresolved local finding is normally **not** a reason to stop the pipeline. The\npull request is open and the authoritative gates will evaluate it. Report the\nfinding and let CI monitoring and code review take it from there.\n\nStop only when continuing would be unsafe or impossible — for example the durable\nartifact from Step 1 turned out to be missing, or the working tree is in a state you\ncannot resolve without risking the pushed branch.\n\n## Return\n\nReturn a summary containing:\n\n- the branch and the pull request URL,\n- the latest pushed `last_commit_sha`,\n- every gap-closing command run, with its observed outcome,\n- the correction commit, if one was made and pushed,\n- every unresolved finding and every unresolved dirty path.\n\nState these as worker observations. Do not include a pass/fail verdict for the `ci`\nor `code_review` gates.\n\nThen record the machine-readable phase result, by mode, so the durable ledger records\nhow the remaining gaps closed.\n\n### orchestrated\n\nEnd your result with the envelope in a fenced block tagged `bapi-phase-result`, which\norchestration parses, validates, and persists:\n\n```bapi-phase-result\n{\"version\":1,\"phase\":\"post_pr_gap_close\",\"lastCommitSha\":\"<sha>\",\"records\":[]}\n```\n\n### inline\n\nCall the `record_phase_result` tool with `ticket_key` `{ticket_key}` and\n`phase_result` set to that same envelope object; the tool validates and persists it.\n**Do not also emit a fenced `bapi-phase-result` block** — nothing parses one on this\npath.\n\nThe tool call is this phase's final action, **not the end of your turn.** When it\nreturns successfully, continue immediately with the next recipe step — the ticket\nstatus transition, the CI follow-up config, and CI monitoring. The pull request is\nopen and its gates are still pending; stopping here abandons the run before anything\nobserves them.\n\nIf the call fails, fix what it reports and call it again.\n",
912
- "write-epic-summary.md": "Synthesize all sub-task explorations into a final overview document.\n\n## Instructions\n\n1. First, use a terminal command or glob pattern to list all files in `{docs_dir}/epic-plans/{epic_slug}/explorations/`. Then read each file. Do not guess filenames — discover them dynamically.\n\n2. Also read:\n - `{docs_dir}/epic-plans/{epic_slug}/research-findings.md`\n - `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md`\n - `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md` (the goals/NFR framing; carry its System Goals, NFRs, and any Recommended Implementation Order through to the overview).\n - `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json` (written by `assess-conductor-eligibility`; required for the Conductor Eligibility section below).\n\n3. Synthesize the information into an overview and write it to `{docs_dir}/epic-plans/{epic_slug}/overview.md` with the following required sections:\n\n```markdown\n# Epic Overview: {epic title derived from description}\n\n## Epic Description and Goals\n{Summary of the epic's purpose, scope, and desired outcomes. Lead with the business goal and desired end-state from goals-and-nfrs.md.}\n\n## Non-Functional Requirements\n{The classified NFRs from goals-and-nfrs.md — each with its category, requirement, implication, and final status (confirmed/assumed). Any NFRs the user clarified should now read as confirmed/assumed, not open.}\n\n## Research Summary\n{Key external findings that informed the decomposition. If no research was performed, state \"No external research was needed.\"}\n\n## Sub-task List\n{Numbered list of all sub-tasks with relative markdown links to their exploration docs.}\n1. [Sub-task title](explorations/01-subtask-slug.md) — one-line summary\n2. [Sub-task title](explorations/02-subtask-slug.md) — one-line summary\n...\n\n## Dependency Graph\n{Textual list showing execution ordering and dependencies between sub-tasks.}\n- Sub-task 1: No dependencies (start here)\n- Sub-task 2: Depends on Sub-task 1\n- Sub-task 3: Depends on Sub-task 1\n- Sub-task 4: Depends on Sub-tasks 2, 3\n...\n\n## Recommended Implementation Order\n{The recommended order in which to implement the sub-tasks, reconciling the provisional order from goals-and-nfrs.md with the approved decomposition. For each sub-task give the position, its hard prerequisites (depends on), any soft sequencing preferences (recommended after), and a one-line rationale. This is recommended sequencing only — no Jira dependency links are created.}\n\n## Conductor Eligibility\n{Render from `conductor-eligibility.json`, written by `assess-conductor-eligibility`. This section\nis about predicted HUMAN WORKLOAD before any execution starts — it never blocks planning\ncompletion, on any status.\n\n**When status is `\"assessed\"` and `predictedWorkflowChildren` > 0:**\n\n- A merge row stating: \"At least N of M children are predicted to modify workflow files and\n require human merge.\" where N is `predictedWorkflowChildren` and M is `totalChildren`. Include\n the canonical `reason` identifier (e.g. `workflow_files_modified`) inline in monospace when it\n is present, e.g. \"(reason: `workflow_files_modified`)\". This is a lower bound, not a promise —\n see the caveat below.\n- If `reviewSubsetChildren` > 0, a second, more severe row stating: \"K of those N are predicted to\n modify `.github/workflows/claude-review.yml`; automated review will not run and hand review is\n required.\" where K is `reviewSubsetChildren`. Use the literal path from the artifact's affected-child\n matched paths, never a hard-coded literal in this instruction. State plainly that this narrower set\n is also part of the human-merge set above — it can obtain neither an automated merge nor an\n automated review verdict.\n- The caveat, always present when the count is non-zero: \"Predictions are based on ticket text;\n implementation choices, including defense-in-depth workflow changes, can increase the final\n count.\"\n- A note on why this matters more than it might look: human merge and hand-review requirements are\n especially consequential for unattended v2 execution, where no operator is watching for a stalled\n child.\n- A collapsed \"Show affected children\" section (a Markdown `<details>` block) listing, for each\n entry in `affectedChildren`: its compact `id`, `title`, `matchedPaths`, and whether it requires\n human merge only or both human merge and hand review (`requiresHandReview`).\n\n**When status is `\"assessed\"` and `predictedWorkflowChildren` == 0:** render \"No workflow-file\nchanges predicted from current ticket text.\" Still include the ticket-text-prediction caveat above\n a zero count is not a guarantee that automated merge/review will succeed once implemented.\n\n**When status is `\"unavailable\"`:** render a non-blocking warning: eligibility could not be\npredicted from the current planning data, and workflow-related merge/review eligibility must be\nverified manually before execution. Do not report `0 of M` or any other count when the status is\nunavailable — an unresolved assessment and a genuine zero are different facts.\n\n## Next Steps\n{One-line summaries for each sub-task, specifically formatted so they can be handed directly to the Jira Ticket Writer / ticket-authoring workflow as input. Each line should be a self-contained ticket description.}\n```\n\n4. After writing the overview, display the file path to the user and summarize the epic plan.\n\n5. **Push the goals/NFRs + recommended order into the Jira epic (only when `{epic_key}` is non-empty).** The `epic_key` is empty when this run was started from free-form text rather than an existing Epic; in that case skip this step. When `{epic_key}` is a real Jira key, post the System Goals, the final NFRs, the Recommended Implementation Order, and a concise Conductor Eligibility summary as a **comment** on that epic by calling the `add_comment` MCP tool with `ticket_number` set to `{epic_key}` and a comment containing those four parts. The eligibility summary is one or two lines derived the same way as the overview's Conductor Eligibility section (the \"At least N of M\" / \"K of those N\" wording, or the zero/unavailable wording) — never the full affected-children detail. Do not create Jira dependency links and do not attach a separate markdown doc — the comment is the delivery. Display: `\"Posted epic goals/NFRs, recommended implementation order, and conductor eligibility to {epic_key}\"`.\n\n## Return\n\nConfirm the overview was written to `{docs_dir}/epic-plans/{epic_slug}/overview.md` and report the total sub-task count along with a one-line summary of the epic plan. State whether the goals/NFRs + recommended order were posted as a comment on `{epic_key}` or skipped because no epic key was provided.\n"
912
+ "write-epic-summary.md": "Synthesize all sub-task explorations into a final overview document.\n\n## Instructions\n\n1. First, use a terminal command or glob pattern to list all files in `{docs_dir}/epic-plans/{epic_slug}/explorations/`. Then read each file. Do not guess filenames — discover them dynamically.\n\n2. Also read:\n - `{docs_dir}/epic-plans/{epic_slug}/research-findings.md`\n - `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md`\n - `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md` (the goals/NFR framing; carry its System Goals, NFRs, and any Recommended Implementation Order through to the overview).\n - `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json` (written by `assess-conductor-eligibility`; required for the Conductor Eligibility section below).\n\n3. Synthesize the information into an overview and write it to `{docs_dir}/epic-plans/{epic_slug}/overview.md` with the following required sections:\n\n```markdown\n# Epic Overview: {epic title derived from description}\n\n## Epic Description and Goals\n{Summary of the epic's purpose, scope, and desired outcomes. Lead with the business goal and desired end-state from goals-and-nfrs.md.}\n\n## Non-Functional Requirements\n{The classified NFRs from goals-and-nfrs.md — each with its category, requirement, implication, and final status (confirmed/assumed). Any NFRs the user clarified should now read as confirmed/assumed, not open.}\n\n## Research Summary\n{Key external findings that informed the decomposition. If no research was performed, state \"No external research was needed.\"}\n\n## Sub-task List\n{Numbered list of all sub-tasks with relative markdown links to their exploration docs.}\n1. [Sub-task title](explorations/01-subtask-slug.md) — one-line summary\n2. [Sub-task title](explorations/02-subtask-slug.md) — one-line summary\n...\n\n## Dependency Graph\n{Textual list showing execution ordering and dependencies between sub-tasks.}\n- Sub-task 1: No dependencies (start here)\n- Sub-task 2: Depends on Sub-task 1\n- Sub-task 3: Depends on Sub-task 1\n- Sub-task 4: Depends on Sub-tasks 2, 3\n...\n\n## Recommended Implementation Order\n{The recommended order in which to implement the sub-tasks, reconciling the provisional order from goals-and-nfrs.md with the approved decomposition. For each sub-task give the position, its hard prerequisites (depends on), any soft sequencing preferences (recommended after), and a one-line rationale. This is recommended sequencing only — no Jira dependency links are created.}\n\n## Conductor Eligibility\n{Render from `conductor-eligibility.json`, written by `assess-conductor-eligibility`. This section\nis about predicted HUMAN WORKLOAD before any execution starts — it never blocks planning\ncompletion, on any status. Render this section whenever the status is `\"assessed\"` and either\n`predictedWorkflowChildren` or `containmentChildren` is non-zero — a containment-only epic\n(`predictedWorkflowChildren` == 0, `containmentChildren` > 0) still renders the section, it just\nskips the workflow-merge rows.\n\nOrder: assessment availability/status first, then the workflow, hand-review-subset, and\ncontainment counts — reported independently of one another — then affected-child evidence.\n\n**When status is `\"assessed\"`:**\n\n- If `predictedWorkflowChildren` > 0:\n - A merge row stating: \"At least N of M children are predicted to modify workflow files and\n require human merge.\" where N is `predictedWorkflowChildren` and M is `totalChildren`. Include\n the canonical `reason` identifier (e.g. `workflow_files_modified`) inline in monospace when it\n is present, e.g. \"(reason: `workflow_files_modified`)\". This is a lower bound, not a promise —\n see the caveat below.\n - If `reviewSubsetChildren` > 0, a second, more severe row stating: \"K of those N are predicted to\n modify `.github/workflows/claude-review.yml`; automated review will not run and hand review is\n required.\" where K is `reviewSubsetChildren`. Use the literal path from the artifact's affected-child\n matched paths, never a hard-coded literal in this instruction. State plainly that this narrower set\n is also part of the human-merge set above — it can obtain neither an automated merge nor an\n automated review verdict.\n - If `predictedWorkflowChildren` == 0: render \"No workflow-file changes predicted from current\n ticket text.\"\n- A containment row, independent of the workflow rows above:\n - If `containmentChildren` > 0: \"P of M children are predicted to describe a containment hazard —\n a destructive primitive (truncate, drop, reset of shared state) or a change to conductor\n enforcement code.\" where P is `containmentChildren` and M is `totalChildren`. State plainly that\n containment is advisory and orthogonal to workflow merge eligibility: a containment-flagged\n child is not thereby a human-merge requirement, and a workflow-flagged child is not thereby a\n containment hazard.\n - If `containmentChildren` == 0: render \"No containment hazards predicted from ticket text.\"\n- The caveat, always present when either count is non-zero: \"Predictions are based on ticket text;\n implementation choices, including defense-in-depth workflow changes, can increase the final\n count.\" This lower-bound caveat covers both the workflow and containment predictions — neither\n count is a guarantee that the corresponding merge/review/operator-attention need will or will not\n materialize once implemented.\n- A note on why this matters more than it might look: human merge, hand-review, and containment\n requirements are especially consequential for unattended v2 execution, where no operator is\n watching for a stalled or destructive child.\n- A collapsed \"Show affected children\" section (a Markdown `<details>` block) listing, for each\n entry in `affectedChildren` (the union of workflow-affected and containment-affected children):\n its compact `id`, `title`, its workflow-merge status (`matchedPaths` and whether it requires\n human merge, from `requiresHandReview`/workflow membership), and its containment-review status\n independently, from `requiresContainmentReview` and the bounded `matchedHazards` evidence in the\n order the artifact supplies. Label each status independently a child can require human merge,\n containment review, both, or (if only fetched via containment) neither workflow label. **Never**\n label a containment-only child (hazard evidence present, `matchedPaths` empty) as \"Requires human\n merge\" — use a distinct \"Containment review\" label for it instead.\n\n**When status is `\"unavailable\"`:** render a non-blocking warning: eligibility could not be\npredicted from the current planning data, and workflow-related merge/review/containment eligibility\nmust be verified manually before execution. Do not report `0 of M` or any other count when the\nstatus is unavailable — an unresolved assessment and a genuine zero are different facts.\n\n## Next Steps\n{One-line summaries for each sub-task, specifically formatted so they can be handed directly to the Jira Ticket Writer / ticket-authoring workflow as input. Each line should be a self-contained ticket description.}\n```\n\n4. After writing the overview, display the file path to the user and summarize the epic plan.\n\n5. **Push the goals/NFRs + recommended order into the Jira epic (only when `{epic_key}` is non-empty).** The `epic_key` is empty when this run was started from free-form text rather than an existing Epic; in that case skip this step. When `{epic_key}` is a real Jira key, post the System Goals, the final NFRs, the Recommended Implementation Order, and a concise Conductor Eligibility summary as a **comment** on that epic by calling the `add_comment` MCP tool with `ticket_number` set to `{epic_key}` and a comment containing those four parts. The eligibility summary is one or two lines derived the same way as the overview's Conductor Eligibility section (the \"At least N of M\" / \"K of those N\" wording, or the zero/unavailable wording) — never the full affected-children detail. Do not create Jira dependency links and do not attach a separate markdown doc — the comment is the delivery. Display: `\"Posted epic goals/NFRs, recommended implementation order, and conductor eligibility to {epic_key}\"`.\n\n## Return\n\nConfirm the overview was written to `{docs_dir}/epic-plans/{epic_slug}/overview.md` and report the total sub-task count along with a one-line summary of the epic plan. State whether the goals/NFRs + recommended order were posted as a comment on `{epic_key}` or skipped because no epic key was provided.\n",
913
+ "phase1/capture-review-decisions.md": "Capture user decisions on review findings for {ticket_key} using the HTML decision page, then interpretively rewrite the clarifying questions and critique docs and upload both to Jira.\n\n<!-- BAPI-977 Phase-1 variant of capture-review-decisions.md. Selected only when\n REVIEW_SYNTHESIS_PHASE1_ENABLED is on. The legacy instruction at\n instructions/capture-review-decisions.md is the disabled path and is unchanged. -->\n\n## Step 1: Read source documents\n\nRead the combined review-and-resolution file:\n- `{docs_dir}/review/{ticket_key}-review-and-resolution.md`\n\nIf the file does not exist or is unreadable, stop and report: \"Combined review-and-resolution file not found or unreadable. Run the earlier pipeline steps first.\"\n\nThe combined file existing but containing no actionable items (empty `Needs Scrutiny` and `Open Questions` sections) is **not** a failure condition — Step 4 handles the no-decisions-needed flow gracefully when `generate_decision_page` is called with empty `actionable_items`.\n\n## Step 2: Map evaluation items to decision page input\n\nTransform the combined review-and-resolution document into `generate_decision_page` JSON input using these mapping rules:\n\n| Evaluation Section | JSON Field | Mapping Rule |\n|---|---|---|\n| Open Questions (full actionable cards) | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, `**Assessment**` paragraph plus the item's single `**Canonical citation**` → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Needs Scrutiny (full actionable cards) | `actionable_items` | Same mapping as Open Questions above |\n| Confirmed Improvements (terse rows) | `clear_improvements` | E-item id → `id`, E-item title → `title`, recommended action → `action`, `Confidence` → `confidence`, `Source` → `source` |\n\n### `clear_improvements` carries exactly five fields\n\nA terse Confirmed Improvement row maps to a `clear_improvements` entry containing **only** these five fields, every one of them a non-empty string:\n\n- `id`\n- `title`\n- `action`\n- `confidence`\n- `source`\n\n**Never add anything else to a `clear_improvements` entry.** In particular, do not add `options`, `option_consequences`, `recommendation_index`, `recommendation_explanation`, `question`, `original_question`, `why_it_matters`, or `codebase_evidence` — not as real values, not as empty strings, not as empty arrays, and not as placeholders. Those fields belong to the actionable-card contract, and a confirmed improvement is not a decision the reviewer is being asked to make. A terse row that seems to need one of them was misclassified upstream; map it as it stands rather than inventing the missing decision data.\n\n### Only full decision cards map to `actionable_items`\n\nAn entry in `actionable_items` must carry the complete contract: `id`, `question`, `why_it_matters`, `recommendation_explanation`, `options` (2–4 labels), `option_consequences` (same length as `options`, in the same order), and a `recommendation_index` that is a 0-based integer within range of `options`. `original_question`, `codebase_evidence`, and `source` are optional display fields.\n\nDo not promote a terse Confirmed Improvement row into `actionable_items` by synthesizing branches, consequences, or a recommendation index for it. If the evaluation did not emit a decision tree for an item, that item is not an actionable decision.\n\n### `codebase_evidence` uses the single canonical citation\n\nMap the item's `**Assessment**` paragraph together with its one `**Canonical citation**` into `codebase_evidence`. There is exactly one citation per item; do not reconstruct a citation list, do not re-derive citations from the source documents, and do not repeat the canonical citation elsewhere in the payload.\n\n### Ignore the machine-only silent-validation records\n\nThe combined document carries a `**Silent-agreement accounting**` block (`silent_validated`, `silent_accepted`, `silent_demoted`) and a `<!-- silent-validation-ledger ... -->` HTML comment. Both are machine-only records of work already completed. **Ignore them entirely when building both decision-page arrays.** A silently accepted item is not a user decision and never becomes an `actionable_items` entry or a `clear_improvements` entry. A demoted item is already present as its own visible E-item; map that visible item, not the ledger line that references it.\n\n### Item ids\n\nPreserve the E-item id when mapping an actionable card (`E-5` → `id: \"E-5\"`). For terse Confirmed Improvement rows, use the E-item id the evaluation assigned; visible E-numbering is sequential and unique across the whole document, so those ids are already stable and distinct.\n\nFor each actionable item, the `options` array is a list of plain label strings extracted from the combined file's decision tree branches. The tool auto-generates value keys (`opt-0`, `opt-1`, etc.) and auto-appends a \"None of these\" option. Do not generate value keys yourself.\n\n## Step 2.5: Auto-approve fast path\n\nFor this run, `auto_approve` = `{auto_approve}`.\n\nIf `auto_approve` is `true` and Step 2 produced at least one actionable item, skip Steps 3–6 entirely and synthesize the commit JSON directly:\n\n- `ticket_key`: `{ticket_key}`\n- `general_comment`: `\"\"`\n- `decisions`: an object keyed by each `actionable_items[*].id` from Step 2's mapped input. For each item:\n - If `recommendation_index` is a non-negative integer within range of `options`: `choice = \"opt-\" + recommendation_index`, `chosen_label = options[recommendation_index]`, `comment = \"\"`, `source` copied from the item.\n - Otherwise (missing, null, or out of range): `choice = \"opt-0\"`, `chosen_label = options[0]`, `comment = \"\"`, `source` copied. Never emit `\"none\"` and never emit `\"ask\"`.\n\nPost a single chat acknowledgement listing each auto-approved item ID and chosen label, then proceed directly to Step 7 with the synthesized JSON. Step 7's \"Hard rule\" about resolving `ask` items does not apply because no item carries `choice === \"ask\"`.\n\nThe synthesized settled decisions still receive the implications review described under Step 6's \"Implications review and proceed gate\" before Step 7 runs — literal `auto_approve = true` only skips the human proceed gate, not the review itself. \"Skip Steps 3–6\" above means skipping their interactive portions (rendering the page, waiting on chat, the Q&A loop); it does not exempt this fast path from the review obligation.\n\nIf Step 2 produced zero actionable items, fall through to Step 3 — Step 4's existing `no_decisions_needed` branch handles the empty case correctly.\n\nOtherwise (any value of `auto_approve` other than the literal `true` — including empty, `false`, or missing), proceed to Step 3.\n\n## Step 3: Call the MCP tool\n\nCall `generate_decision_page` with `ticket_key` at the root and the review arrays nested under `content`:\n\n**Always pass `content`, even when both arrays are empty.** Send `\"content\": { \"actionable_items\": [], \"clear_improvements\": [] }` rather than omitting the key — that is what reaches the `no_decisions_needed` branch Step 2 relies on. Omitting `content` entirely is rejected with a `VALIDATION_ERROR`, because root-level arrays are silently dropped by the tool's lean input schema and a missing wrapper is far more often a mistake than a deliberate empty call.\n\n```typescript\ninterface ReviewDecisionsContent {\n actionable_items?: Array<{\n id: string;\n question: string;\n why_it_matters: string; // required — concrete one-sentence impact\n recommendation_explanation: string; // required — why the recommended branch is best\n options: string[]; // 2-4 option labels\n option_consequences: string[]; // same length as options\n recommendation_index: number; // 0-based index into options\n original_question?: string; // optional display field\n codebase_evidence?: string; // optional display field — assessment + the one canonical citation\n source?: string; // optional source reference\n }>;\n clear_improvements?: Array<{\n id: string;\n title: string;\n action: string;\n confidence: string;\n source: string; // required for clear_improvements\n }>; // exactly these five fields — nothing else\n}\n```\n\nExample call:\n```json\n{\n \"ticket_key\": \"{ticket_key}\",\n \"content\": {\n \"actionable_items\": [\n {\n \"id\": \"E-1\",\n \"question\": \"Should we add a configurable timeout?\",\n \"why_it_matters\": \"Timeout behavior affects retry paths and user-visible latency.\",\n \"recommendation_explanation\": \"Configurable matches existing latency-branching code.\",\n \"options\": [\"Keep existing\", \"Add configurable timeout\"],\n \"option_consequences\": [\"No new work.\", \"Implementers add config + tests.\"],\n \"recommendation_index\": 1,\n \"original_question\": \"Does the ticket specify timeout behavior?\",\n \"codebase_evidence\": \"The retry path already branches on latency. src/python/llms/ai_client.py:212\",\n \"source\": \"Clarifying Q1\"\n }\n ],\n \"clear_improvements\": [\n { \"id\": \"E-2\", \"title\": \"Tidy logging\", \"action\": \"Use the module logger.\", \"confidence\": \"High\", \"source\": \"Clarifying Q4 (prior round, concurrence inline)\" }\n ]\n }\n}\n```\n\n## Step 4: Check tool response\n\nThe tool returns a JSON response with a `status` field:\n- If `status` is `\"no_decisions_needed\"`: skip Steps 5, 6, 7, and 8 entirely. Output a success message: \"No actionable review decisions needed — skipping doc rewrite and upload.\" This covers both the case where every item was confirmed as a Confirmed Improvement and the case where no items were emitted (e.g., both upstream source documents were absent).\n- If `status` is `\"decision_page_generated\"`: continue to Step 5. The response includes `file_path`.\n\n## Step 5: Direct user to the decision page\n\nTell the user to open the generated HTML file in their browser. Provide the `file_path` from the tool response. Then say to the user, verbatim: `Open the page. For any item you're unsure about, choose \"Ask about this\" — when you submit, I'll talk through those before we proceed. You can also ask me questions in chat before submitting if you prefer.`\n\nThis step only directs the user to the page and explains the two allowed next actions (submit selections, or ask questions first). Do not describe Step 7's rewrite semantics here; that belongs to the rewrite step.\n\n## Step 6: Q&A loop and commit signal\n\nEnter an open-ended Q&A loop. There is no turn cap — the user may ask any number of questions in any number of turns. Do not stop and wait silently; engage with each user message as either a commit signal or a discussion turn.\n\n### Proceed signal (commit)\n\nTrim the full user message and attempt to parse the entire trimmed message as JSON. The message is a commit only when the parsed value is an object with all three of these top-level fields:\n\n- `ticket_key` — must be a string\n- `decisions` — must be an object\n- `general_comment` — must be a string\n\nThe first valid commit-shaped JSON paste commits immediately. Proceed to Step 7 without prompting for additional confirmation. Any combination of `decisions` keys is accepted (the page may submit a partial set if the user only resolved some items conversationally). Do not over-validate the per-card fields beyond the top-level commit-shape check — the page guarantees the per-card schema, and over-validating risks rejecting valid pastes if the page schema evolves.\n\n### Discussion signal (Q&A turn)\n\nAnything that is not commit-shaped JSON is a discussion turn. This includes:\n\n- Freeform questions (with or without other text).\n- Questions pasted alongside other text or alongside JSON.\n- Malformed JSON (parse failure).\n- Well-formed JSON missing one or more of the required top-level keys (`ticket_key`, `decisions`, `general_comment`).\n\nFor JSON-shaped input that is missing required top-level fields, call this out in the reply — explain which fields are missing and ask whether the user intended to submit or share partial state — rather than silently treating it as a freeform question.\n\nAnswer discussion turns using these sources, in priority order:\n\n1. The combined `{ticket_key}-review-and-resolution.md` file already read in Step 1.\n2. The original `{ticket_key}-clarifying-questions.md` and `{ticket_key}-ticket-quality-critique.md` documents.\n3. Codebase lookups when the question requires verifying current code state.\n\nIf a discussion turn asks about an item the reviewer cannot find in the visible sections, the silent-validation ledger is a legitimate place to *look it up* — it records which prior items were silently accepted and what grounded each one. Answer from it in chat when asked. It still never becomes a decision-page entry or a rewrite target.\n\nFallback: if running on a pre-PR1 branch where the combined review-and-resolution document does not exist, use the pre-PR1 `{ticket_key}-review-evaluation.md` and `{ticket_key}-resolution-guide.md` pair in its place.\n\nFor plain freeform questions, infer the item from chat context when possible.\n\n### In-flight decision state\n\nDuring the Q&A loop, maintain in-flight JSON state — agent-owned working memory representing the user's current intent for `decisions` and `general_comment`. This in-flight JSON state lives only in the agent's working memory for the duration of the loop; do not persist it server-side.\n\n- When the user clearly changes their mind about an item, chooses an option conversationally with reasonably explicit decision language (\"choose option B for E-3\", \"go with the configurable timeout\", \"change E-7 to None of these\"), or gives new overarching guidance, record that as an in-flight override.\n- Ambiguous preference language (\"I'm leaning toward...\", \"maybe option B is fine\") should be discussed but not recorded as an override unless the user gives reasonably explicit decision language.\n- `general_comment` may be updated in the in-flight state when the user gives overarching guidance during Q&A.\n- The page's general-comment textarea is preserved unchanged. Do not modify the page DOM during Q&A; the user can still fill the textarea before submitting if they prefer.\n\nOn the eventual JSON commit, the user-submitted JSON is the baseline and the recorded in-flight overrides take precedence over it. Before proceeding to Step 7, post a brief one-line acknowledgement in chat naming each overridden item ID and/or `general_comment`. The acknowledgement is mandatory (not optional) — it is the user's last chance to object before Step 7's document rewrite. The user does not need to re-open, edit, or re-submit the decision page after changing their mind in chat; they can submit the page as-is to provide the commit signal, and the in-flight state remains the source of truth for overrides.\n\n### Ask-about-this resolution\n\nAfter accepting a commit, scan `decisions` for any item where `choice === \"ask\"`. The user has signaled that they need more information before deciding on those items. For each such item:\n\n- If `comment` is non-empty, treat it as the user's specific question or stated uncertainty and answer that directly.\n- If `comment` is empty, proactively present the most relevant missing context — the item's `codebase_evidence`, related code lookups, prior-round answers — and lay out the trade-offs the user appears to need help weighing.\n- Continue the Q&A turn-by-turn until the user gives an explicit decision in chat for that item (\"go with option B\", \"none of these, because …\"). Record that decision as an in-flight override using the same override mechanism described above.\n\n**Hard rule.** Step 7 must not run while any `decisions[*].choice === \"ask\"` remains unresolved by an in-flight override. Do not honor \"just proceed\", \"skip those\", or any other instruction to defer resolution — every `ask` item must end with a recorded `opt-N` or `none` override before the rewrite step. The pre-Step-7 acknowledgement line lists every overridden item, including the ones resolved out of `ask`.\n\n### Implications review and proceed gate\n\n**Review the wider implications, then gate on a decision.** Build the review from the complete settled set: the submitted `decisions`, any in-flight overrides recorded during the conversation (these take precedence over the submission), every `\"none\"` answer together with the reason given for it, `general_comment`, and — where this surface tracks acceptance-criterion or NFR stances — those stances too. Do not start the review until every `ask` has an explicit recorded resolution and every in-flight override has been applied.\n\nConsider three fixed categories, regardless of whether a decision was framed as technical, user-facing, or business-oriented:\n- **Program / application** — architecture, code paths, operability, maintenance burden, and requirements imposed on other parts of the software.\n- **User** — end users, new users performing setup, operators, and developers, including prerequisites, setup friction, and additional steps.\n- **Business** — cost, adoption, support load, compliance, and reversibility.\n\nEmit only the categories with material second-order implications. For each included category, write at most four one-line bullets of about 25 words, each naming who or what is affected and how — never a restatement of the selected decision. Close with a line naming every considered category that was omitted, e.g. `Considered, nothing material: business.` — omit this closing line only when all three categories have material implications.\n\nIf the review cannot be produced, report that in one line and continue without stalling the workflow or presenting the gate below.\n\nThis review stays in chat and must not be written into the clarifying-questions or ticket-critique documents rewritten in Step 7.\n\nThen present the gate, verbatim: `Implications reviewed. Proceed, or name a decision to revisit.` Accept only a normalized `proceed`, `yes`, `y`, or `go` as a continuation token. Any other response names a decision to reopen: re-settle it in chat, record the new override, rerun the entire implications review against the changed settled set, and present the gate again.\n\nLiteral `auto_approve = true` emits the review but skips this gate entirely; a missing or non-true `auto_approve` value follows the human-in-the-loop path above.\n\nA decision named at this gate is re-settled in chat and recorded as an in-flight override using the same override mechanism as the rest of this step; the rerun review picks it up, and the pre-Step-7 acknowledgement line above also names it. Step 7 must not begin until every submitted `ask` is resolved (per the hard rule above) **and** this gate has accepted a proceed token — except when the review fails open or `auto_approve` is literal `true`.\n\n## Step 7: Interpretively rewrite source documents\n\nThe pasted JSON contains a `decisions` object keyed by item ID. Each decision includes `source`, `choice`, `chosen_label`, and `comment`. Use these fields to locate and rewrite the corresponding sections in:\n- `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n- `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n\nOnly the documents this review delivered exist. A cost-saved review resolves zero critique rounds for an Easy ticket and ships clarifying questions alone, so the critique document is absent — rewrite each document that WAS delivered and skip the leg the review did not run. An absent document is not an error.\n\n**Only submitted actionable decisions produce rewrites.** A silently accepted item generates no rewrite operation. Its presence in the machine-only ledger records that it was validated, not that a reviewer decided anything about it, and rewriting a document because of a ledger line would put a change into the source docs that no human ever approved.\n\nAfter a second-opinion run, each document has this shape:\n\n- A top-level H1 (`# Ticket Analysis` or `# Ticket Quality Critique`) followed by an italic provider-attribution line `_This analysis was generated by GPT|Claude|Gemini._` naming the first-round LLM family. **Preserve this attribution line verbatim** — do not move, edit, or remove it during the rewrite step.\n- The first-round questions / critique items, exactly as written by the first-round model.\n- **Inline second-opinion blockquotes** (`> **Second opinion (<provider>) - concurrence|refinement|disagreement.** ...`) nested directly under each prior item the second round addressed. The `(<provider>)` parenthetical is the second-round LLM family (`GPT|Claude|Gemini`). A `concurrence` blockquote may legitimately carry no body and no citations — that is a valid pure concurrence, not a malformed blockquote. Items the second round did not comment on have no blockquote at all — that is the silent-agreement signal.\n- A **`## New in Second Opinion`** tail block listing items the second round added on top of the first round. Immediately under the H2 there is a second italic attribution line `_These additional points were raised by GPT|Claude|Gemini._` naming the second-round family — **also preserve this verbatim**. Then agent-specific sub-headings:\n - Clarifier docs: `### New Requirements Questions` / `### New Technical Questions` (numbering continues from the prior section).\n - Critique docs: `### New Requested Changes` / `### New Points to Consider` (numbering continues from the prior section).\n- A final **`## Second Opinion Summary`** footer. **This footer must be preserved verbatim** — under Phase 1 it is generated by the server from execution metadata and is the canonical record that the second round ran. Do not edit it, do not regenerate it, and do not remove it, even when individual items it accounts for have been resolved.\n\nThe `source` field on each decision tells you where the item lives:\n\n- `Clarifying Q3 (prior round, silent agreement — demoted)` → the prior section, no inline blockquote. Rewrite the prior item's answer.\n- `Clarifying Q9 (prior round, concurrence inline)` → the prior section, prior item carries an explicit `concurrence` blockquote (possibly an empty one). Rewrite the prior answer; the blockquote can be removed once the answer absorbs the resolution.\n- `Clarifying Q3 (prior round, refinement inline)` / `(prior round, disagreement inline)` → the prior section, prior item carries an explicit `refinement` or `disagreement` blockquote. Rewrite the prior answer to reconcile the dispute, then handle the blockquote per the rule below.\n- `Clarifying Q11 (new in second opinion → New Requirements Questions)` → the `## New in Second Opinion > ### New Requirements Questions` sub-section. Rewrite the item in place inside that sub-section, not at the top of the prior analysis.\n- Equivalent forms for critique items: `Critique: Requested Change 2 (prior round, refinement inline)`, `Critique: Points to Consider N+1 (new in second opinion → New Points to Consider)`, etc.\n\n**Legacy fallback shape**: if the document instead ends with `\\n\\n---\\n\\n` followed by a `## Second Opinion` section (because the JSON pipeline fell back), apply decisions to the equivalent location: `### Response to Prior Items` for inline-style responses, `### Additional Points > New X` for tail-style new items. Preserve the `\\n\\n---\\n\\n` separator and the `## Second Opinion` heading verbatim.\n\nApply the decision to the item in its home location. Then apply the decision:\n\n### Actionable item decisions\n\n- **Selected option** (`choice` is `opt-N`): Add `**Review Decision**: Accepted. <chosen_label>.` to the corresponding section. Integrate the selected direction into the section text so it reads as a final recommendation or resolved answer.\n- **None of these** (`choice` is `none`): Add `**Review Decision**: Rejected — none of the proposed options accepted.` Include the user's `comment` explaining why. Rewrite the section to reflect this decision.\n\nFor actionable items sourced from clarifying questions, rewrite the question's best-guess answer so it reads as the final resolved direction chosen by the reviewer. Do not leave the item framed as an unresolved accept/reject/modify prompt.\n\nFor items sourced from `(prior round, refinement inline)` or `(prior round, disagreement inline)` — disputes of a prior-round item carried in an inline blockquote — the prior-round item is the canonical home: rewrite its answer to absorb the resolution. Then handle the blockquote in one of two ways: (a) remove the blockquote outright if the rewritten answer fully absorbs the second-opinion content, or (b) shorten the blockquote to a single sentence noting the resolution while preserving the `(<provider>)` attribution (e.g. `> **Second opinion (Claude) - refinement.** Resolved by reviewer decision E-N.`). The strongest grounding reference from the original blockquote may be promoted into the rewritten prior-item answer if useful — keep one.\n\nFor items sourced from `(new in second opinion → ...)` — gap-captured items that received a decision — rewrite the item in place inside its tail-block sub-section (`## New in Second Opinion > ### New X`), not at the top of the prior analysis. Preserve the sub-section heading and continued numbering.\n\n### General comment handling\n\nTreat `general_comment` as overarching guidance that informs the tone and direction of both document rewrites. If it contains specific actionable feedback, weave it into the relevant sections. If it is broad or general, use it as context for how the rewrites should read. Do not create a separate \"General Comment\" or \"Reviewer Notes\" section — the goal is \"final draft\" form.\n\n### Rewrite principles\n\nThe goal is a **final draft** — the documents should read as if they were written with the decisions already made. Do not mechanically append decisions. Instead, lightly rewrite affected sections so they reflect the decisions naturally. Preserve all non-affected sections unchanged. The prior-round content should still read as coherent standalone analysis after integration. Preserve the `## New in Second Opinion` tail block intact for any items that weren't decided. **Always preserve the `## Second Opinion Summary` footer verbatim** — it is the server-generated record of the second round's execution and should not be edited even when individual items are resolved. **Preserve the `bridge-review-decision-identity` footer on each document verbatim** — it is the only channel carrying the generation identity Step 7.5 must send, and a rewrite that drops it makes the capture unsendable.\n\n## Step 7.5: Record the decisions durably (AUTHORITATIVE)\n\n**This step, not the document rewrite, is what makes the decisions durable.** The\n`**Review Decision**:` markers Step 7 stamps into the two documents are a\nhuman-readable record only. Those documents are stored with `url='ai-generated'`,\nand BAPI-738 deliberately excludes such rows from the description plan generation\nreads — so a decision that exists only as prose reaches nothing downstream. The\nstructured write below is the authoritative one; the prose is secondary.\n\n### Build one resolution per manifest id\n\nEvery critique finding and every clarifying question carries a stable id as an\nHTML comment on its item line: `<!-- bapi-item:crit-001 -->`,\n`<!-- bapi-item:clar-004 -->`. Preserve these markers through the Step 7 rewrite —\nnever delete, renumber, or invent one.\n\nProduce **exactly one** resolution for **every** id present in the delivered\ndocuments, not just the ones the reviewer chose an option for. A cost-saved review\ndelivers only the legs it ran — an Easy ticket resolves zero critique rounds and\nships clarifying questions alone — so build resolutions for the ids in whichever\ndocuments this review actually delivered. An id you omit is indistinguishable from\na decision that was lost, which is the exact failure this record exists to prevent.\nThe server refuses a payload with a missing, duplicate, or unknown id.\n\nMap each settled decision onto the id of the item it resolves:\n\n| Settled decision | `adjudication` | `effect` | `binding_instruction` |\n| --- | --- | --- | --- |\n| An option was chosen (`choice` is `opt-N`) and it obliges a change | `accepted` | `apply` | the ratified direction, non-empty |\n| Resolved, but nothing is to be done about it | `accepted` | `dismiss` | `null` |\n| None of these (`choice` is `none`) | `rejected` | `dismiss` | `null` |\n\nAn accepted dismissal is exactly `{adjudication: \"accepted\", effect: \"dismiss\",\nbinding_instruction: null}`. It means \"this item is settled and requires no\nwork\" — which is a different fact from \"this item was declined\", and the two must\nnot be collapsed onto one another.\n\n**Binding corrections go in `binding_instruction`, never in prose alone.** When\nthe reviewer's answer obliges an implementation to do something specific, that\nobligation belongs in this field on that item's id, even when the conversation\nproduced no `**Review Decision**:` marker for it. An unmarked prose correction is\nnot an independent requirement source and will not survive.\n\n### Read the generation identity from the delivered documents\n\nEvery delivered review document carries one metadata footer naming the decision\ngeneration it belongs to:\n\n```\n<!-- bridge-review-decision-identity: {\"generation\":7,\"source_bundle_sha256\":\"<64 lowercase hex>\"} -->\n```\n\nRead it from whichever of these documents this review actually delivered:\n\n- `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n- `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n\nA cost-saved review legitimately delivers only one leg — an Easy ticket resolves\nzero critique rounds and ships clarifying questions alone, so the critique document\ndoes not exist. Read the footer from each document that WAS delivered, and do not\ntreat an absent document as a failure.\n\nEvery delivered document's footer must be present and must agree — the same\npositive `generation` and the same 64-character lowercase-hex\n`source_bundle_sha256`. If a footer is missing or malformed on a document that WAS\ndelivered, or two delivered documents disagree, **stop and report it**. Do not\nguess a generation, do not send one document's identity on behalf of a document\nthat carries none, and do not ask the server which generation is current. The\nvalue's entire worth is that it came from the documents you actually adjudicated\nrather than from whatever happens to be pending by the time you call — a review\nre-run between your read and this call is exactly what it exists to catch.\n\nPreserve every delivered document's footer verbatim through the Step 7 rewrite.\n\n### Call the tool\n\nCall `capture_review_decisions` with:\n\n- `ticket_number`: `{ticket_key}`\n- `generation`: **required** — the integer read from the identity footer above.\n- `source_bundle_sha256`: **required** — the 64-character lowercase-hex digest\n read from that same footer. It is what proves these resolutions were authored\n against the exact bytes the generation was minted from.\n- `schema_version`: optional; omit it unless you actually know it.\n- `resolutions`: the array built above\n- `critique_document` / `clarify_document`: the **rewritten** markdown from\n Step 7, in memory, for each leg this review delivered — send only the documents\n that exist, omitting the leg a cost-saved review skipped. These are transient —\n the server counts `Review Decision` markers in them as a floor check and then\n discards them.\n- `request_id`: reuse the same value across retries of one logical capture\n\n### Do not continue past a refusal\n\nA refusal names a closed `reason`. Stop and report it; do not proceed to Step 8\nand do not describe the capture as successful.\n\n| `reason` | What it means |\n| --- | --- |\n| `manifest_coverage_mismatch` | An id is missing, duplicated, or unknown. Rebuild the resolution set against the ids actually in the documents. |\n| `marker_count_gap` | The prose records more decisions than the payload carries. Some decision was not captured — find it. |\n| `decision_set_too_large` | The set exceeds the record's bounds; nothing was truncated. Read `terminalized`: `true` means the generation is now `invalid` and closed, `false` means the invalidating write did not commit and the generation is still open — report it either way, and never assume the generation closed. |\n| `missing_identity` | The call carried no `generation` or no `source_bundle_sha256`. Stop and re-read the identity footer from each delivered document; never substitute a value you did not read. |\n| `stale_generation` | The review was re-run, so the documents you adjudicated are no longer the current generation. Re-read the latest delivered documents and re-establish the adjudication against them. Do not resubmit the same resolutions under a newly discovered generation number. |\n| `source_bundle_mismatch` | The submitted digest does not identify the current pending bundle. It is never adopted and never overwrites what is stored. Re-read the delivered documents and adjudicate the bundle that is actually pending. |\n| `invalid_decision_entry` | An `apply` has an empty instruction, or a `dismiss` has a non-null one. |\n| `conflicting_decision_set` | This generation was already captured with different decisions. Do not overwrite it. |\n| `generation_already_terminal` | The generation is already `complete` or `invalid`. A new review bundle is what produces a new generation; this one cannot be reopened. |\n| `no_pending_generation` | No decision generation exists for this ticket. The review was never delivered, or it was delivered before this record existed. |\n| `unsupported_schema` / `record_unreadable` | A record exists that this deployment cannot interpret. Report it; do not attempt to replace it. |\n\nA `409` or `422` here is a real failure of adjudication, not a formality to route\naround.\n\n## Step 8: Upload to Jira\n\nOnly after Step 7.5 succeeded. This upload and the prose markers it carries are\nthe human-readable record; the durable one is already written. A failure here is\nreported on its own and never invalidates the completed capture.\n\nUpload each document this review delivered to Jira using `attachment` (operation: `\"upload\"`) — a cost-saved Easy ticket delivered clarifying questions alone, so upload only the documents that exist:\n\n1. Upload clarifying questions:\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n - `link_type`: `clarifying-questions.md`\n\n2. Upload the ticket quality critique **only when the review delivered a critique leg** (skip this when the critique document does not exist):\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n - `link_type`: `ticket-quality-critique.md`\n\n## Step 9: Complete\n\nConfirm: \"Review decisions captured and uploaded to {ticket_key}.\" State the\ngeneration that was recorded. Say this only when Step 7.5 returned a success\noutcome — an upload without a durable capture is not a completed adjudication.\n\n## Return\n\nConfirm \"Review decisions captured and uploaded to {ticket_key}.\" and list the attachment(s) uploaded (`{ticket_key}-clarifying-questions.md`, and `{ticket_key}-ticket-quality-critique.md` when the review delivered a critique leg). Note any decisions that could not be applied.\n",
914
+ "phase1/evaluate-and-recommend.md": "Evaluate the clarifying questions and ticket critiques generated for {ticket_key} against the actual codebase, then decorate every genuinely actionable item with the resolution guidance the reviewer will need on the decision page. The result is a single combined review-and-resolution document.\n\n<!-- BAPI-977 Phase-1 variant of evaluate-and-recommend.md. Selected only when\n REVIEW_SYNTHESIS_PHASE1_ENABLED is on. The legacy instruction at\n instructions/evaluate-and-recommend.md is the disabled path and is unchanged. -->\n\n## Phase 0 — Grounding & Audit Setup\n\nBefore gathering any source documents, extract the codebase-grounding context produced by the preceding `fresh_base` (`action: \"materialize\"`) pipeline step:\n\n- Read the `fresh_base` materialize tool result from earlier in this session. It returns JSON `{ base_sha, base_branch, fresh_base_root }` — or, when `no_refresh_base` was set, `{ base_sha: \"local-stale\", fresh_base_root: <original repo root> }`.\n- Retain `fresh_base_root` and `base_sha` for the rest of this procedure. Every codebase read in Phase 1 / Phase 2 below is grounded against `fresh_base_root`. `fresh_base_root` is also the exact value you must pass to the pipeline's later `fresh_base` (`action: \"cleanup\"`) step — it is a *runtime* value returned by the tool call, not a static recipe variable, so pass the real path string you captured here, not any placeholder text shown in the step's params.\n- If the materialize step's result contains an `error` field and `no_refresh_base` was NOT set, this is the fail-loud condition the recipe's `on_error: \"halt\"` exists for: stop here, do not fall back to grounding against your own working directory, and report the failure (name the attempted base branch and the remediation — retry, or rerun with `--no-refresh-base`).\n\n**Metadata Audit Header** — the very first content of the generated review-and-resolution document, before any other section, must be:\n\n```\n**Base SHA**: <base_sha>\n**Base Branch**: <base_branch, or \"(local, in-place)\" when base_sha is \"local-stale\">\n**Grounding Status**: <Freshly Materialized | Stale/In-Place Fallback>\n```\n\n- `Grounding Status` is **Freshly Materialized** whenever `base_sha` is a real commit SHA (the normal path).\n- `Grounding Status` is **Stale/In-Place Fallback** whenever `base_sha` is exactly `local-stale` (the `--no-refresh-base` opt-out path). In this case, immediately follow the header with a prominent, bold, high-contrast warning block, for example:\n\n > **⚠ STALE GROUNDING — `--no-refresh-base` was used.** This review evaluated the codebase as checked out locally, NOT a freshly-fetched `origin/<base>`. `file:line` citations may reflect uncommitted or unmerged local state.\n\n**Codebase grounding rule**: Ground ALL file reads and codebase searches exclusively against the `fresh_base_root` directory extracted above. Do NOT read codebase files from your default working directory or session cwd — `fresh_base_root` is the only trustworthy source of truth for `file:line` citations in this procedure.\n\n**Original-repo rule**: Ticket docs, `{docs_dir}` inputs, and ALL output paths stay in the ORIGINAL repository, never the `fresh_base_root` temp dir. This includes the ticket-fetch call below, the clarifying-questions / critique source documents, and the saved review-and-resolution output file (see the Save rule at the bottom). Do NOT redirect any of these into `fresh_base_root`.\n\n**Path hygiene rule**: Every `file:line` citation and every `Canonical citation` in the output document MUST be repo-relative — strip the `fresh_base_root` absolute-path prefix before writing it down. A citation must never contain a temp-dir / `/tmp/...`-style absolute path (write `src/foo.ts:10`, never `/tmp/bridge-review-.../src/foo.ts:10`).\n\n1. Fetch the current ticket description using the `get_ticket` tool with ticket_number `{ticket_key}` exactly once at the top of this procedure.\n\n2. Gather the clarifying questions and critique documents from the preceding pipeline steps. The local files at `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md` and `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md` are the canonical source. After a second-opinion run, each document has this shape:\n\n - A top-level H1 (`# Ticket Analysis` for clarifier docs, `# Ticket Quality Critique` for critique docs) followed by an italic provider-attribution line of the form `_This analysis was generated by GPT|Claude|Gemini._` The attribution names the LLM family that produced the **first round**.\n - The first-round questions / critique items, exactly as written by the first-round model.\n - **Inline second-opinion blockquotes** nested directly under each prior item the second round addressed. Each blockquote starts with `> **Second opinion (<provider>) - <stance>.**` where `<provider>` is `GPT|Claude|Gemini` and `<stance>` is `concurrence|refinement|disagreement`. The blockquote may be followed by `> *Citations: <comma-separated grounding refs>*`. Items the second round did **not** comment on have no blockquote — that is the \"silent agreement\" signal.\n\n **A `concurrence` blockquote may legitimately carry no body text and no citations.** Under Phase 1 the second round is instructed not to pad a pure agreement, so an empty concurrence blockquote is a deliberate, valid signal — it is an *explicit* concurrence, not a malformed one, and not the same thing as silence. Treat it as such; do not discard it and do not demand a citation from it.\n - A **`## New in Second Opinion`** tail block listing items the second round added on top of the first round. Immediately under the H2 you will find a second italic attribution line of the form `_These additional points were raised by GPT|Claude|Gemini._` — this names the second-round LLM family. Sub-headings are agent-specific:\n - Clarifier docs: `### New Requirements Questions` and `### New Technical Questions` — numbering continues from the prior section.\n - Critique docs: `### New Requested Changes` and `### New Points to Consider` — numbering continues from the prior section.\n Each new item has its own `*Citations: ...*` line.\n - A final **`## Second Opinion Summary`** footer capturing the second round's execution outcome. Under Phase 1 this footer is generated by the server from execution metadata rather than written by the model, so it is a run record and NOT a source of analytical content. Do not mine it for findings and do not cite it.\n\n **Legacy fallback shape**: in rare cases (model lacks JSON-schema support, the JSON call failed, or the response could not be parsed), the document may instead end with `\\n\\n---\\n\\n` followed by a `## Second Opinion` section containing `### Response to Prior Items` and `### Additional Points` subsections. If you detect this fallback shape, treat it equivalently: subsection responses tagged `concurrence` map to explicit concurrence, `refinement`/`disagreement` map to the disagree buckets, and items under `### Additional Points` map to the gap-captured bucket below.\n\n **Partial-source-doc tolerance**: if the clarifying-questions doc OR the ticket-critique doc is missing or unreadable, skip that document silently and produce items only for the surviving doc. Do not fail. If **both** documents are absent, still write the combined output file at `{docs_dir}/review/{ticket_key}-review-and-resolution.md` with the standard top-level sections (`Confirmed Improvements`, `Needs Scrutiny`, `Open Questions`, `Round Agreement Summary`) present, no emitted E-items in any section, and the Silent-Agreement Accounting block present with all totals at zero. This preserves downstream file-existence expectations for the capture-review-decisions step.\n\n3. Determine **Round Agreement** for every clarifying question and critique point using these rules:\n\n - **Silent agreement** — the prior item has NO inline blockquote AND is not in `## New in Second Opinion`. The second round did not object to the point and did not consider it important enough to comment on. Handle it under **Phase 1S — Silent-agreement validation and accounting** below; it does not become a visible E-item unless it is demoted.\n - **Explicit concurrence** — the prior item carries an inline `> **Second opinion (<provider>) - concurrence.** ...` blockquote, with or without body text and citations. The second round took the trouble to mark this item specifically. It stays a **retained visible item**. Establish its canonical citation yourself from the codebase — do not rely on the blockquote having supplied one.\n - **Rounds disagree (refinement)** — the prior item carries an inline `> **Second opinion (<provider>) - refinement.** ...` blockquote. The second round modified or added detail. Apply full disagreement-depth analysis; the blockquote's citations are a starting point you must verify.\n - **Rounds disagree (disagreement)** — the prior item carries an inline `> **Second opinion (<provider>) - disagreement.** ...` blockquote. The second round contradicts the prior. Apply full disagreement-depth analysis; categorize the outcome based on which position the codebase supports.\n - **Gap captured** — the item lives under `## New in Second Opinion > ### New <category>` (one of: New Requirements Questions, New Technical Questions, New Requested Changes, New Points to Consider). Apply the two-axis check below. The new item's citations are a starting point you must verify.\n - **Single round only** — the document has none of the above markers (no inline blockquotes, no `## New in Second Opinion` block, no `## Second Opinion Summary` footer). The pipeline ran only one round. Treat every item as a disagreement: cite the strongest codebase location and give full analytical depth.\n\n Apply these depth and categorization rules:\n\n - **Explicit concurrence**: 1-2 sentence assessment. Categorize as Confirmed Improvement unless your own validation contradicts it, in which case it becomes Needs Scrutiny at disagreement depth.\n - **Rounds disagree (refinement or disagreement)**: 3-4 sentence assessment that explicitly weighs the prior-round position against the second-opinion position. Categorize based on which position the evidence supports. Always include both positions in the Assessment.\n - **Gap captured — two-axis check** (for items in `## New in Second Opinion`):\n - If both the question is grounded in the codebase/standards AND the best-guess answer is sensible → **Confirmed Improvement** with a 1-2 sentence assessment.\n - If the question is genuine but the best-guess answer is flawed → **Needs Scrutiny**. Disagreement depth.\n - If the question itself does not hold up → **Needs Scrutiny** with evidence of what the code actually does. Disagreement depth.\n - If neither codebase nor standards can settle the question → **Open Questions**. Disagreement depth.\n - **Single round only**: Treat as a disagreement and give full analytical depth.\n\n For critique points (Requested Changes and Points to Consider), apply the same Round Agreement rules. The signal locations are inline `> **Second opinion (<provider>) - ...**` blockquotes nested under items in `### Requested Changes` / `### Points to Consider`, and gap-captured items under `## New in Second Opinion > ### New Requested Changes` / `### New Points to Consider`.\n\n **Source field conventions** — the `**Source**` string disambiguates where in the source doc the item lives so the downstream `capture-review-decisions` step can route the rewrite correctly. Use these forms:\n\n - **Silent agreement, demoted to visible**: `Clarifying Q3 (prior round, silent agreement — demoted)` or `Critique: Requested Change 2 (prior round, silent agreement — demoted)`.\n - **Explicit concurrence**: `Clarifying Q9 (prior round, concurrence inline)` or `Critique: Points to Consider 1 (prior round, concurrence inline)`.\n - **Refinement (inline blockquote)**: `Clarifying Q3 (prior round, refinement inline)`.\n - **Disagreement (inline blockquote)**: `Clarifying Q5 (prior round, disagreement inline)`.\n - **Gap captured (tail-block item)**: `Clarifying Q11 (new in second opinion → New Requirements Questions)` or `Critique: Requested Change N+1 (new in second opinion → New Requested Changes)`. Always spell out the sub-section name after the arrow — capture-review-decisions uses it to find the rewrite target.\n - **Single round only**: `Clarifying Q3 (single round)`.\n\n## Phase 1S — Silent-agreement validation and accounting\n\nThis phase covers every prior-round item classified as **silent agreement** above. The second round's silence is not itself evidence, so each such item is still checked — but a check that passes produces a *count and a ledger entry*, not a reviewer-facing paragraph. Re-printing an item that two rounds and a codebase check all agreed on is the padding this phase exists to remove.\n\n**Validate every silent item individually.** Do not batch, sample, or spot-check. For each silent prior item, look at the actual code the item claims something about, in `fresh_base_root`, and decide whether the item's claim and its best-guess answer hold up. An item you did not individually validate may not be counted as accepted.\n\nThree counts govern this phase. Define them exactly as follows and emit all three:\n\n- `silent_validated` — the number of silent prior items you individually validated. This is every silent item, with no exceptions.\n- `silent_accepted` — the number of those whose claim and answer the codebase supports. These are **not** emitted as visible items.\n- `silent_demoted` — the number of those the codebase does **not** support. These **are** emitted as visible items.\n\nThe accounting must satisfy this invariant exactly, with no rounding, no \"approximately\", and no unaccounted remainder:\n\n```\nsilent_validated = silent_accepted + silent_demoted\n```\n\n### Accepted silent items — counted, not printed\n\nA silently accepted item is **omitted entirely from the reviewer-facing output**. It gets no E-item row, no bullet in any agreement list, and no entry in any visible section. It is recorded only in the machine-only ledger below.\n\n### Demoted silent items — the safety catch\n\nA silent item whose validation finds the claim or the answer to be wrong is **demoted**: emit it as a full **Needs Scrutiny** E-item at disagreement depth, with `**Source**` in the `(prior round, silent agreement — demoted)` form, and count it in `silent_demoted`. Silence must never be able to bury an incorrect item — the whole point of validating each one is that this catch fires.\n\n### The machine-only silent-validation ledger\n\nEmit exactly one ledger, as a single structured HTML comment, immediately after the Silent-Agreement Accounting block. Markdown renderers do not display HTML comments, so this record is available to tooling without adding a word to what the reviewer reads. Use this exact shape:\n\n```\n<!-- silent-validation-ledger\naccepted:\n- anchor: <source anchor, e.g. Clarifying Q4> | citation: <one repo-relative file:line>\n- anchor: <source anchor> | citation: <one repo-relative file:line>\ndemoted:\n- anchor: <source anchor> | item: <emitted E-item id, e.g. E-7>\n-->\n```\n\nRules for the ledger:\n\n- Every silently **accepted** item appears in `accepted:` with its source anchor and **one** canonical repo-relative `file:line` reference — the single grounding that made you accept it.\n- Every **demoted** item appears in `demoted:` with its source anchor and the id of the visible E-item it became. Its canonical citation lives on that visible item and is **not** repeated here; a demoted item's grounding is something the reviewer reads, so duplicating it into the ledger would be the same content twice.\n- Source anchors are unique across the whole ledger. The same anchor must never appear twice, and never in both lists.\n- The ledger is emitted even when both lists are empty (emit the two keys with no entries beneath them).\n- Explicit concurrences never appear here. They are retained visible items, not silent agreements.\n\n## Phase 1 — Evaluate and classify every visible item\n\nNumber every **visible** item sequentially across all sections (E-1, E-2, E-3, …) with no gaps. Silently accepted items consume no number — they are not visible items. When the same underlying issue is raised in BOTH the clarifying-questions doc and the critique doc, consolidate it into a SINGLE E-item rather than emitting one per source, and cite both origins in its `**Source**` field (e.g. `Clarifying Q3 + Critique: Requested Change 2`); keep the numbering sequential with no gaps.\n\nClassify every visible item into exactly one of three buckets using the Round Agreement rules, codebase groundedness checks, and the `gap captured` two-axis check before producing any recommendation decoration:\n\n- **Confirmed Improvements**: Grounded suggestions that would genuinely improve the ticket. Includes explicit concurrences and `gap captured` items that passed both axes.\n- **Needs Scrutiny**: Suggestions based on inaccurate codebase assumptions, with evidence of the actual code behavior. Includes `gap captured` items that failed either axis, demoted silent items, and the loser of any rounds-disagree pair.\n- **Open Questions**: Legitimate ambiguities that require human input to resolve.\n\nPhase 1 must complete before Phase 2 begins — do not start decorating an item with a decision tree, recommendation index, or clarity fields until classification is final.\n\n## Canonical citation — exactly one grounding reference per retained item\n\nEvery retained visible item carries **one** `Canonical citation` field: a single repo-relative `file:line` reference that is the strongest grounding for the item. This replaces the previous practice of repeating grounding in three places — the decision-tree branches, the Assessment prose, and a Codebase Evidence list.\n\n- **One field, one reference.** Do not emit a list. If two locations genuinely matter, name the stronger one in `Canonical citation` and describe the second in the Assessment *without* a `file:line` reference.\n- **Do not repeat the canonical citation anywhere else in the item.** Not in the Assessment, not in a branch, not in an evidence block. It appears exactly once.\n- **Decision-tree branches carry no citations.** Do not append `See file:line` to a branch. A branch states a condition and an action; its grounding is the item's canonical citation.\n- If the item genuinely has no codebase grounding, write `Canonical citation: none — <one clause saying why>` and set `Confidence` to `Low`.\n\n## Phase 2 — Decorate genuinely actionable items\n\nPhase 2 applies **only** to items in the `Needs Scrutiny` and `Open Questions` buckets that represent a **genuine unresolved choice for the reviewer** — a decision a human still has to make. Confirmed Improvements stay terse (see \"Confirmed Improvements output\" below), and so does any Needs Scrutiny item whose resolution is already settled by the evidence.\n\n**A full decision card is only for a real choice.** If the evidence settles the matter and there is nothing for the reviewer to decide, the item does not get a card: render it as a terse Confirmed Improvement instead. Do not manufacture a second branch to justify a card.\n\nFor every genuinely actionable item, produce the following template using these stable labels:\n\n```\n### E-<sequential number>: <concise title>\n\n**Source**: <where this item lives in the source doc — see Source field conventions above>\n\n**Round Agreement**: <one of the values above> — <1 sentence on what the second round contributed>\n\n**Confidence**: <High|Medium|Low>\n\n**Canonical citation**: `path/to/file.ts:42`\n\n**Resolution path**: <\"resolve at your desk\" or \"needs a conversation\">\n\n**Decision tree**:\n- If <condition 1>, then <action 1>.\n- If <condition 2>, then <action 2>.\n\n**Recommendation Index**: <0-based index of the recommended branch in the decision tree above>\n\n**Recommendation**: <which branch the evidence best supports and why, 1-2 sentences>\n\n**Original question**: <the clarifying-question or critique point as it was originally raised, sourced verbatim or near-verbatim from the original clarifying-questions / critique docs. Light rephrasing is allowed; do NOT introduce new technical content. Soft cap ~30 words.>\n\n**Option consequences**:\n- <consequence for branch 1 — the behavioral consequence of choosing this option, not its rationale. ~25 words.>\n- <consequence for branch 2 — same shape. ~25 words.>\n\n**Why it matters**: <one concrete sentence on the impact this decision has on the ticket, the users, or the affected code paths. Soft cap ~40 words.>\n\n**Recommendation explanation**: <explain why the recommended branch is the best choice, tied to the evidence and the consequences of each option. Soft cap ~60 words.>\n\n**Assessment**: <what the code shows and what it implies, 3-4 sentences for a disagreement, 1-2 for a concurrence. State the evidence and the implication. Do NOT restate the original question — it is already in the Original question field. Do NOT restate the branch rationale — it is already in Recommendation explanation. Do NOT repeat the canonical citation.>\n```\n\n**Decision tree rules**:\n- Each decision tree must have **2–4 branches**, each a genuine, non-contrived alternative. Do not exceed 4 and do not produce only 1.\n- **Strict lower bound — reclassify on single-branch items**: If you can think of only one branch — that is, the resolution is effectively forced — reclassify the item as a **Confirmed Improvement** instead of emitting a single-branch decision tree. Do not stretch to a contrived second branch. If a single answer is genuinely the only path, the item belongs in Confirmed Improvements.\n- Each branch must end with a concrete, actionable step (not \"investigate further\").\n- Branches carry no `file:line` references. The item's `Canonical citation` is its grounding.\n- Cap each branch at one sentence.\n- `**Recommendation Index**` must be the 0-based index of the recommended branch in the decision tree above. The first branch is index 0, the second is index 1, etc. It must be within range of the branches actually emitted.\n- **Option consequences** must be a list parallel to the decision-tree branches: one entry per branch, in the same order, same length. Describe the behavioral consequence of choosing that option, not its rationale.\n- **\"resolve at your desk\"**: The item can be resolved through technical investigation — reading code, running tests, or checking configuration. No stakeholder input needed.\n- **\"needs a conversation\"**: The item involves a product decision, scope question, or cross-team dependency that cannot be resolved from the codebase alone.\n\n**Confidence Tags** — assign confidence based on codebase evidence strength:\n- **High**: The canonical citation directly supports the assessment.\n- **Medium**: The canonical citation is a related pattern or convention, not the exact code in question.\n- **Low**: No direct codebase grounding. Assessment rests on general reasoning or domain knowledge.\n\n### Confirmed Improvements output\n\nRender each Confirmed Improvement as a single terse row. No per-item headings, no decision trees, no clarity-field decoration:\n\n- **E-<number>: <title>** — Source: <source string>; Round Agreement: <value>; Confidence: <High|Medium|Low>; Canonical citation: `path/to/file.ts:42`. <recommended action, 1 sentence.>\n\nA Confirmed Improvement row never carries `options`, `option_consequences`, a decision tree, a recommendation index, or an empty placeholder for any of them. If an item seems to need one of those fields, it is not a Confirmed Improvement — reclassify it.\n\n## Silent-Agreement Accounting\n\nEmit this block in every run, including runs where one or both source documents were missing (in which case every total is `0`):\n\n```\n**Silent-agreement accounting**\n- silent_validated: <N>\n- silent_accepted: <N>\n- silent_demoted: <N>\n```\n\nThe three numbers must satisfy `silent_validated = silent_accepted + silent_demoted` exactly. Immediately after this block, emit the machine-only silent-validation ledger described in Phase 1S.\n\n## Round Agreement Summary\n\nAfter all items are processed, produce a summary section that groups items by round agreement status:\n\n### Points of Disagreement\nFor items where the evaluation marked `rounds disagree (refinement)`, `rounds disagree (disagreement)`, or `single round only` — including `gap captured` items that failed the two-axis check and landed in Needs Scrutiny — list as bullets with the E-number, the nature of the disagreement, and a 1-sentence explanation of why this disagreement matters for the ticket (e.g. it indicates an architectural ambiguity, a scope question, or a standards gap).\n\nIf no items were marked as disagreements, write: \"All reviewed points had round consensus. No disagreement-driven risks identified.\"\n\n### Points of Agreement\n\n**Strong agreement** — items where the evaluation marked `explicit concurrence`. The second round took the trouble to mark this item specifically; that is a soft signal it is important enough to underline. List as bullets with the E-number and a half-sentence noting the shared conclusion.\n\n**Weak agreement** — represented by the accounting totals, not by an item list. Write a single line of the form: `<silent_accepted> prior item(s) were silently accepted after individual validation; <silent_demoted> were demoted to Needs Scrutiny (<E-numbers, or \"none\">).` Do not list the accepted items individually — they are recorded in the machine-only ledger, and re-printing them here is exactly the padding this variant removes.\n\nIf a sub-bullet has no items, omit it (rather than writing a \"no items\" note for each — keep the section tidy). The weak-agreement accounting line is always emitted, even when every total is zero.\n\n### Gaps Captured by Second Round\nFor items where the evaluation marked `gap captured` (sound second-opinion additions confirmed as Confirmed Improvements): list as bullets with the E-number and a half-sentence noting the gap the second round surfaced. These items did not require a decision — they are already in Confirmed Improvements — but are surfaced here so the reviewer sees what the second-round analysis added on top of the first round.\n\nIf no gaps were captured, write: \"The second round did not surface any net-new confirmed improvements.\"\n\n## Edge Cases\n\n- If the evaluation contains zero items in Needs Scrutiny, write: \"No items flagged for scrutiny. All reviewed suggestions were either confirmed or remain open questions.\"\n- If the evaluation contains zero items in Open Questions, write: \"No open questions identified. All ambiguities were resolved through codebase analysis.\"\n- If both Needs Scrutiny and Open Questions are empty, include only the Confirmed Improvements section and add a summary: \"All suggestions from the review were confirmed as grounded improvements. No decision trees are needed.\"\n- If both source documents are absent, still write the combined file with the standard top-level sections present, no emitted E-items, the Silent-Agreement Accounting block with all totals at zero, and the ledger with both lists empty — rather than failing.\n\n## Example of a Well-Written Terse Row (Explicit Concurrence — Confirmed Improvement)\n\n- **E-4: Sequential per-type review_repository fan-out** — Source: Clarifying Technical Q2 (prior round, concurrence inline); Round Agreement: explicit concurrence; Confidence: High; Canonical citation: `src/python/learn_repository/review_repository.py:369`. Keep the per-type fan-out sequential; the internal chunk-level gather already provides the concurrency.\n\n## Example of a Well-Written Actionable Card (Rounds Disagree — Needs Scrutiny)\n\n### E-5: Authentication middleware placement for new endpoint\n\n**Source**: Clarifying Q2 (prior round, disagreement inline)\n\n**Round Agreement**: rounds disagree (disagreement) — the prior round recommended adding auth at the router level; the second-opinion blockquote argued the existing middleware stack already covers it.\n\n**Confidence**: High\n\n**Canonical citation**: `api/routes/__init__.py:18`\n\n**Resolution path**: resolve at your desk\n\n**Decision tree**:\n- If routers each opt in to auth via dependencies, then add `Depends(require_api_key)` to the new endpoint.\n- If the global middleware stack already enforces auth on `/api/*`, then drop the explicit dependency.\n\n**Recommendation Index**: 0\n\n**Recommendation**: Routers opt in individually here, so the new endpoint must declare the dependency itself.\n\n**Original question**: Should the new `/api/exports` endpoint declare an explicit auth dependency, or is it covered by the global middleware?\n\n**Option consequences**:\n- Endpoint requires a valid API key, matching every other `/api/*` route.\n- Endpoint becomes publicly reachable; protected data leaks via the new path.\n\n**Why it matters**: Authentication on `/api/exports` decides whether protected data leaks; the wrong default is a security regression, not a stylistic choice.\n\n**Recommendation explanation**: Each router declares its own dependency and there is no catch-all auth middleware, so following the established pattern adds two lines and keeps auth uniform across endpoints.\n\n**Assessment**: The global middleware applies rate limiting and CORS but not authentication, and every existing router opts in explicitly. That contradicts the second opinion's claim of blanket coverage and supports the prior round's position. Omitting the dependency would leave the endpoint unauthenticated.\n\n## Example of the Accounting Block and Ledger\n\n```\n**Silent-agreement accounting**\n- silent_validated: 5\n- silent_accepted: 4\n- silent_demoted: 1\n```\n\n<!-- silent-validation-ledger\naccepted:\n- anchor: Clarifying Q4 | citation: src/python/learn_repository/__init__.py:14\n- anchor: Clarifying Q6 | citation: api/library/db/postgres_helpers.py:88\n- anchor: Critique: Requested Change 1 | citation: db/alembic/versions/0165_add_review_claims_and_result_cache.py:12\n- anchor: Critique: Points to Consider 3 | citation: main.py:45\ndemoted:\n- anchor: Clarifying Q8 | item: E-7\n-->\n\n## Save rule\n\nSave the combined review-and-resolution document to `{docs_dir}/review/{ticket_key}-review-and-resolution.md`. Output only the combined review-and-resolution document — no meta-commentary.\n\n## Return\n\nConfirm \"Review-and-resolution document written to `{docs_dir}/review/{ticket_key}-review-and-resolution.md`.\" and report the total count of visible E-items captured together with the three accounting totals (`silent_validated`, `silent_accepted`, `silent_demoted`).\n"
913
915
  };