@bridge_gpt/mcp-server 0.2.10 → 0.2.12

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 (37) hide show
  1. package/README.md +3 -3
  2. package/build/commands.generated.js +6 -6
  3. package/build/conductor/bridge-api-client.js +2 -1
  4. package/build/conductor/cli.js +16 -16
  5. package/build/conductor/doctor.js +1 -1
  6. package/build/conductor/epic-reconcile.js +213 -16
  7. package/build/conductor/epic-runtime.js +89 -6
  8. package/build/conductor/epic-state.js +85 -11
  9. package/build/conductor/errors.js +12 -0
  10. package/build/conductor/git-ci-types.js +10 -0
  11. package/build/conductor/git-producer.js +4 -4
  12. package/build/conductor/merge-ledger.js +7 -7
  13. package/build/conductor/pr-ci-producer.js +6 -6
  14. package/build/conductor/pr-review-producer.js +2 -2
  15. package/build/conductor/producer-ledger.js +5 -5
  16. package/build/conductor/spec-review-producer.js +88 -0
  17. package/build/conductor/store.js +97 -25
  18. package/build/conductor/supervisor-ledger.js +2 -2
  19. package/build/conductor/supervisor-merge.js +5 -5
  20. package/build/conductor/supervisor-message-relay.js +1 -1
  21. package/build/conductor/supervisor-runtime.js +10 -10
  22. package/build/conductor/taxonomy.js +5 -0
  23. package/build/conductor/tools.js +5 -5
  24. package/build/conductor-bin.js +12350 -19
  25. package/build/conductor-claude-hook-bin.js +167 -17
  26. package/build/decision-page-schema.js +26 -0
  27. package/build/doctor.js +200 -0
  28. package/build/index.js +23705 -3630
  29. package/build/install-bridge.js +80 -0
  30. package/build/pipelines.generated.js +70 -48
  31. package/build/readme.generated.js +1 -1
  32. package/build/version.generated.js +1 -1
  33. package/package.json +7 -4
  34. package/pipelines/check-ci-ticket.json +2 -2
  35. package/pipelines/implement-ticket.json +2 -2
  36. package/pipelines/learn-repository.json +84 -42
  37. package/smoke-test/SMOKE-TEST.md +11 -17
@@ -27,8 +27,9 @@ export const PIPELINES = {
27
27
  },
28
28
  {
29
29
  "type": "mcp_call",
30
- "tool": "get_config_field",
30
+ "tool": "config_field",
31
31
  "params": {
32
+ "operation": "get",
32
33
  "field_name": "ci_followup_config"
33
34
  },
34
35
  "description": "Fetch repo-specific CI follow-up configuration",
@@ -170,8 +171,9 @@ export const PIPELINES = {
170
171
  },
171
172
  {
172
173
  "type": "mcp_call",
173
- "tool": "get_config_field",
174
+ "tool": "config_field",
174
175
  "params": {
176
+ "operation": "get",
175
177
  "field_name": "ci_followup_config"
176
178
  },
177
179
  "description": "Fetch repo-specific CI follow-up configuration",
@@ -204,9 +206,10 @@ export const PIPELINES = {
204
206
  },
205
207
  {
206
208
  "type": "mcp_call",
207
- "tool": "get_config_field",
209
+ "tool": "config_field",
208
210
  "params": {
209
- "field_name": "architecture_instructions"
211
+ "field_name": "architecture_instructions",
212
+ "operation": "get"
210
213
  },
211
214
  "description": "Fetch current architecture_instructions value",
212
215
  "on_error": "warn_and_continue"
@@ -218,19 +221,21 @@ export const PIPELINES = {
218
221
  },
219
222
  {
220
223
  "type": "mcp_call",
221
- "tool": "update_config_field",
224
+ "tool": "config_field",
222
225
  "params": {
223
226
  "field_name": "architecture_instructions",
224
- "file_path": "{docs_dir}/standards/architecture_instructions.md"
227
+ "file_path": "{docs_dir}/standards/architecture_instructions.md",
228
+ "operation": "update"
225
229
  },
226
230
  "description": "Upload architecture_instructions to config",
227
231
  "requires_approval": true
228
232
  },
229
233
  {
230
234
  "type": "mcp_call",
231
- "tool": "get_config_field",
235
+ "tool": "config_field",
232
236
  "params": {
233
- "field_name": "review_instructions"
237
+ "field_name": "review_instructions",
238
+ "operation": "get"
234
239
  },
235
240
  "description": "Fetch current review_instructions value",
236
241
  "on_error": "warn_and_continue"
@@ -242,19 +247,21 @@ export const PIPELINES = {
242
247
  },
243
248
  {
244
249
  "type": "mcp_call",
245
- "tool": "update_config_field",
250
+ "tool": "config_field",
246
251
  "params": {
247
252
  "field_name": "review_instructions",
248
- "file_path": "{docs_dir}/standards/review_instructions.md"
253
+ "file_path": "{docs_dir}/standards/review_instructions.md",
254
+ "operation": "update"
249
255
  },
250
256
  "description": "Upload review_instructions to config",
251
257
  "requires_approval": true
252
258
  },
253
259
  {
254
260
  "type": "mcp_call",
255
- "tool": "get_config_field",
261
+ "tool": "config_field",
256
262
  "params": {
257
- "field_name": "documentation_instructions"
263
+ "field_name": "documentation_instructions",
264
+ "operation": "get"
258
265
  },
259
266
  "description": "Fetch current documentation_instructions value",
260
267
  "on_error": "warn_and_continue"
@@ -266,19 +273,21 @@ export const PIPELINES = {
266
273
  },
267
274
  {
268
275
  "type": "mcp_call",
269
- "tool": "update_config_field",
276
+ "tool": "config_field",
270
277
  "params": {
271
278
  "field_name": "documentation_instructions",
272
- "file_path": "{docs_dir}/standards/documentation_instructions.md"
279
+ "file_path": "{docs_dir}/standards/documentation_instructions.md",
280
+ "operation": "update"
273
281
  },
274
282
  "description": "Upload documentation_instructions to config",
275
283
  "requires_approval": true
276
284
  },
277
285
  {
278
286
  "type": "mcp_call",
279
- "tool": "get_config_field",
287
+ "tool": "config_field",
280
288
  "params": {
281
- "field_name": "unit_testing_instructions"
289
+ "field_name": "unit_testing_instructions",
290
+ "operation": "get"
282
291
  },
283
292
  "description": "Fetch current unit_testing_instructions value",
284
293
  "on_error": "warn_and_continue"
@@ -290,19 +299,21 @@ export const PIPELINES = {
290
299
  },
291
300
  {
292
301
  "type": "mcp_call",
293
- "tool": "update_config_field",
302
+ "tool": "config_field",
294
303
  "params": {
295
304
  "field_name": "unit_testing_instructions",
296
- "file_path": "{docs_dir}/standards/unit_testing_instructions.md"
305
+ "file_path": "{docs_dir}/standards/unit_testing_instructions.md",
306
+ "operation": "update"
297
307
  },
298
308
  "description": "Upload unit_testing_instructions to config",
299
309
  "requires_approval": true
300
310
  },
301
311
  {
302
312
  "type": "mcp_call",
303
- "tool": "get_config_field",
313
+ "tool": "config_field",
304
314
  "params": {
305
- "field_name": "e2e_testing_instructions"
315
+ "field_name": "e2e_testing_instructions",
316
+ "operation": "get"
306
317
  },
307
318
  "description": "Fetch current e2e_testing_instructions value",
308
319
  "on_error": "warn_and_continue"
@@ -314,19 +325,21 @@ export const PIPELINES = {
314
325
  },
315
326
  {
316
327
  "type": "mcp_call",
317
- "tool": "update_config_field",
328
+ "tool": "config_field",
318
329
  "params": {
319
330
  "field_name": "e2e_testing_instructions",
320
- "file_path": "{docs_dir}/standards/e2e_testing_instructions.md"
331
+ "file_path": "{docs_dir}/standards/e2e_testing_instructions.md",
332
+ "operation": "update"
321
333
  },
322
334
  "description": "Upload e2e_testing_instructions to config",
323
335
  "requires_approval": true
324
336
  },
325
337
  {
326
338
  "type": "mcp_call",
327
- "tool": "get_config_field",
339
+ "tool": "config_field",
328
340
  "params": {
329
- "field_name": "frontend_correctness_standards"
341
+ "field_name": "frontend_correctness_standards",
342
+ "operation": "get"
330
343
  },
331
344
  "description": "Fetch current frontend_correctness_standards value",
332
345
  "on_error": "warn_and_continue"
@@ -338,19 +351,21 @@ export const PIPELINES = {
338
351
  },
339
352
  {
340
353
  "type": "mcp_call",
341
- "tool": "update_config_field",
354
+ "tool": "config_field",
342
355
  "params": {
343
356
  "field_name": "frontend_correctness_standards",
344
- "file_path": "{docs_dir}/standards/frontend_correctness_standards.md"
357
+ "file_path": "{docs_dir}/standards/frontend_correctness_standards.md",
358
+ "operation": "update"
345
359
  },
346
360
  "description": "Upload frontend_correctness_standards to config",
347
361
  "requires_approval": true
348
362
  },
349
363
  {
350
364
  "type": "mcp_call",
351
- "tool": "get_config_field",
365
+ "tool": "config_field",
352
366
  "params": {
353
- "field_name": "backend_correctness_standards"
367
+ "field_name": "backend_correctness_standards",
368
+ "operation": "get"
354
369
  },
355
370
  "description": "Fetch current backend_correctness_standards value",
356
371
  "on_error": "warn_and_continue"
@@ -362,19 +377,21 @@ export const PIPELINES = {
362
377
  },
363
378
  {
364
379
  "type": "mcp_call",
365
- "tool": "update_config_field",
380
+ "tool": "config_field",
366
381
  "params": {
367
382
  "field_name": "backend_correctness_standards",
368
- "file_path": "{docs_dir}/standards/backend_correctness_standards.md"
383
+ "file_path": "{docs_dir}/standards/backend_correctness_standards.md",
384
+ "operation": "update"
369
385
  },
370
386
  "description": "Upload backend_correctness_standards to config",
371
387
  "requires_approval": true
372
388
  },
373
389
  {
374
390
  "type": "mcp_call",
375
- "tool": "get_config_field",
391
+ "tool": "config_field",
376
392
  "params": {
377
- "field_name": "template_correctness_standards"
393
+ "field_name": "template_correctness_standards",
394
+ "operation": "get"
378
395
  },
379
396
  "description": "Fetch current template_correctness_standards value",
380
397
  "on_error": "warn_and_continue"
@@ -386,19 +403,21 @@ export const PIPELINES = {
386
403
  },
387
404
  {
388
405
  "type": "mcp_call",
389
- "tool": "update_config_field",
406
+ "tool": "config_field",
390
407
  "params": {
391
408
  "field_name": "template_correctness_standards",
392
- "file_path": "{docs_dir}/standards/template_correctness_standards.md"
409
+ "file_path": "{docs_dir}/standards/template_correctness_standards.md",
410
+ "operation": "update"
393
411
  },
394
412
  "description": "Upload template_correctness_standards to config",
395
413
  "requires_approval": true
396
414
  },
397
415
  {
398
416
  "type": "mcp_call",
399
- "tool": "get_config_field",
417
+ "tool": "config_field",
400
418
  "params": {
401
- "field_name": "style_correctness_standards"
419
+ "field_name": "style_correctness_standards",
420
+ "operation": "get"
402
421
  },
403
422
  "description": "Fetch current style_correctness_standards value",
404
423
  "on_error": "warn_and_continue"
@@ -410,19 +429,21 @@ export const PIPELINES = {
410
429
  },
411
430
  {
412
431
  "type": "mcp_call",
413
- "tool": "update_config_field",
432
+ "tool": "config_field",
414
433
  "params": {
415
434
  "field_name": "style_correctness_standards",
416
- "file_path": "{docs_dir}/standards/style_correctness_standards.md"
435
+ "file_path": "{docs_dir}/standards/style_correctness_standards.md",
436
+ "operation": "update"
417
437
  },
418
438
  "description": "Upload style_correctness_standards to config",
419
439
  "requires_approval": true
420
440
  },
421
441
  {
422
442
  "type": "mcp_call",
423
- "tool": "get_config_field",
443
+ "tool": "config_field",
424
444
  "params": {
425
- "field_name": "design_principles"
445
+ "field_name": "design_principles",
446
+ "operation": "get"
426
447
  },
427
448
  "description": "Fetch current design_principles value",
428
449
  "on_error": "warn_and_continue"
@@ -434,10 +455,11 @@ export const PIPELINES = {
434
455
  },
435
456
  {
436
457
  "type": "mcp_call",
437
- "tool": "update_config_field",
458
+ "tool": "config_field",
438
459
  "params": {
439
460
  "field_name": "design_principles",
440
- "file_path": "{docs_dir}/standards/design_principles.md"
461
+ "file_path": "{docs_dir}/standards/design_principles.md",
462
+ "operation": "update"
441
463
  },
442
464
  "description": "Upload design_principles to config",
443
465
  "requires_approval": true
@@ -640,8 +662,8 @@ export const CHAIN_RECIPES = {
640
662
  };
641
663
  export const INSTRUCTIONS = {
642
664
  "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",
643
- "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\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:\n- `ticket_key`: `{ticket_key}`\n- `actionable_items`: combined mapped array from Step 2 (each item has `id`, `question`, `original_question`, `why_it_matters`, `options`, `option_consequences`, `recommendation_explanation`, `codebase_evidence`, `source`, and `recommendation_index`)\n- `clear_improvements`: mapped array from Step 2 (each item has `id`, `title`, `action`, `confidence`, `source`)\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## 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 `upload_attachment`:\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",
644
- "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## Instructions\n\n1. Read `{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\n2. **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\n3. **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\n4. **Call `generate_decision_page`** with:\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 - `system_goals` and `actionable_items` from step 3. (Omit `implementation_order` — the order is produced after decomposition.)\n\n5. **Capture the user's choices (stop and wait).** Direct 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). For any item where `choice === \"ask\"`, discuss until the user gives an explicit decision before finalizing. 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\n6. **Fold the answers back into the framing.** Rewrite `{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. Leave settled sections unchanged.\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",
665
+ "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\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```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## 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",
666
+ "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## Instructions\n\n1. Read `{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\n2. **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\n3. **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\n4. **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\n5. **Capture the user's choices (stop and wait).** Direct 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). For any item where `choice === \"ask\"`, discuss until the user gives an explicit decision before finalizing. 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\n6. **Fold the answers back into the framing.** Rewrite `{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. Leave settled sections unchanged.\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",
645
667
  "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## 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\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>`.\n\n## Return\n\nConfirm the commit was made and pushed by reporting the branch name, the commit subject line, and the pushed remote (e.g. `origin/feature/{ticket_key}`). Note any files that were intentionally excluded from the commit.\n",
646
668
  "create-pr.md": "# Create a pull request for the just-pushed branch\n\nThe implementation has been committed and pushed. Open a PR against `main` for the current branch, with a descriptive title derived from the commit you just made.\n\n## Step 1 — Read the commit subject line\n\nRun `git log -1 --pretty=%s` to get the most recent commit subject. The implement-ticket pipeline asks the commit step to use the form `{ticket_key}: <description>`, so this line is normally already a good PR title.\n\n## Step 2 — Determine the head branch\n\nUse `git branch --show-current`. This is the head branch.\n\n## Step 3 — Call create_pull_request and report the PR URL\n\nCall the `create_pull_request` MCP tool directly with:\n\n- `head_branch`: value from `git branch --show-current`\n- `base_branch`: `\"main\"` — unless the user supplied a different PR base at the commit step, in which case use that value instead.\n- `title`: the commit subject from Step 1 (the derived PR title) — unless the user supplied a different PR title at the commit step, in which case use that value instead.\n\nHonor any PR title / PR base overrides the user gave at the commit step's plan; the commit step advertises those fields as adjustable, so any override the user gave there must carry forward into this tool call rather than being silently replaced by the defaults above.\n\nDo not pass a `body` parameter so the project's `.github/PULL_REQUEST_TEMPLATE.md` populates the description.\n\nReport the returned `pr_url` to the user.\n\n## Return\n\nReturn the URL of the created pull request.\n",
647
669
  "decompose-epic-candidate.md": "Decompose an Epic parent draft into ordered child tickets with idempotency and per-child duplicate checks.\n\n## Inputs\n\n- Epic parent draft: `{docs_dir}/tickets/EPIC-{slug}.md`.\n- Research pack: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/research-pack.md` / `.json`.\n- Standards checklist: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/standards-checklist.json`.\n- Resolved uncertainties: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/resolved-uncertainties.md`.\n- Goals & NFR framing: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/goals-and-nfrs.md` (its System Goals/NFRs and any provisional Recommended Implementation Order should inform the child breakdown and ordering).\n- Hard cap variable `{max_children}` (string integer; default `\"10\"` when not set by the caller). The default `\"10\"` is a **hard ceiling / upper bound, not a target** child count — it caps how many children are allowed, and is **not a goal to fill**. The normal target child count is smaller (fewer, larger M/L slices); see the sizing heuristics in step 2.\n\n## Instructions\n\n1. Read the Epic parent draft, research pack, standards checklist, and resolved uncertainties. Use only this context plus optional narrow web search; do not call deep research from this step.\n\n2. Propose ordered child tickets that, together, fully implement the Epic.\n\n **Sizing heuristics (maintainer-owned defaults).** Size each proposed child by its expected **file-touch breadth and depth plus rough lines of code (LOC) changed**, using these exact thresholds:\n - `S = 1–2 files / <~80 LOC`\n - `M = ~3–8 files / ~80–400 LOC (ideal target)`\n - `L = ~8–15 files / ~400–900 LOC (acceptable)`\n - `XL = >15 files / >~900 LOC → split further; never emit an XL child`\n\n Target size priority: `M (ideal) → L (acceptable) → S (only if unavoidable); never XL`.\n\n Bias the decomposition toward **fewer, larger, independently implementable vertical slices** rather than many tiny one-feature children. The Bridge implementation tooling works better on M–L vertical slices, and a swarm of tiny S children magnifies sibling merge risk under parallel execution. Each child should be an independently implementable vertical slice; if a proposed child would be XL, split it further until each piece is M or L.\n\n Each proposed child must include:\n - `summary` — Jira title.\n - `issue_type` — typically `Task`; use `Spike` only for primarily discovery children.\n - `rationale` — short explanation of why this child exists and what it produces. Include a brief size estimate inside this existing field (do **not** add a new `size` field), e.g. `Estimated size: M (~4 files / ~150 LOC)`.\n - `labels` — must include `ai-generated`, `idea-to-ticket`, `idea-to-ticket-child`, and the unique child idempotency label `bapi-idea-to-ticket-{run_id}-child-<N>` where `<N>` is the 1-based child index in the final ordered list.\n - `idempotency_label` — the same `bapi-idea-to-ticket-{run_id}-child-<N>` string.\n - `draft_path` — `{docs_dir}/tickets/TICKET-{slug}-child-<N>.md` (drafts written by `jira-ticket-writer` later).\n - `depends_on` — array of the 1-based child indexes that are **hard prerequisites** (must land first), or empty. Keep this list minimal and real.\n - `recommended_after` — array of child indexes that are **soft sequencing** preferences (nicer to do after, but not blockers), or empty.\n - `order_rationale` — one line explaining why this child sits at this point in the order.\n\n Keep hard prerequisites (`depends_on`) strictly separate from soft sequencing (`recommended_after`). These fields drive the recommended implementation order posted to the epic later; they do **not** create Jira dependency links.\n\n3. Hard cap enforcement. First attempt a normal, smaller M/L-biased decomposition per the step 2 sizing heuristics. Then count proposed children: `{max_children}` is a hard ceiling that **halts on exceed**, not a target to fill. If the count exceeds `{max_children}` (parsed as an integer), halt locally with a clear \"split first\" message: ask the user to split the idea into multiple smaller Epics or to raise `--max-children` deliberately. Do not silently truncate.\n\n4. Per-child duplicate lookup. For each proposed child (in order), call `get_tickets` once with a title/keyword search built from the child's summary. If a clear duplicate exists, drop that child from the plan and record the drop reason; never halt the whole run because a child has a duplicate. Re-number `<N>` only after all drops are finalized so child indexes are contiguous.\n\n5. Per-child research is restricted to the parent research pack plus optional narrow web search. Do not call deep research per child.\n\n6. Write `{docs_dir}/idea-to-ticket/{slug}-{run_id}/decomposition-plan.json` with at minimum:\n - `parent_summary` — copy from the parent draft.\n - `max_children` — the resolved integer value used for the cap.\n - `children` — ordered array of surviving children with all fields from step 2 (including `depends_on`, `recommended_after`, and `order_rationale`). After re-numbering in step 4, fix up the `depends_on`/`recommended_after` indexes so they still point at the correct surviving children.\n - `dropped_children` — array of `{proposed_summary, reason}` for children removed by duplicate lookup.\n\n## Return\n\nConfirm `decomposition-plan.json` was written, report the final child count and the number of children dropped for duplicate reasons.\n",
@@ -655,7 +677,7 @@ export const INSTRUCTIONS = {
655
677
  "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",
656
678
  "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",
657
679
  "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",
658
- "gather-and-attach-materials.md": "Post-create materials-completeness step. Gather the reachable local text materials a freshly-created ticket references and attach them via `upload_attachment`, while recording everything that is record-only. This is the POST-CREATE half of the upload-time materials-completeness pass (BAPI-423); 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 `list_attachments`, `upload_attachment`, 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 *Reachable Local Files*, *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. **Deduplicate first.** Call the `list_attachments` MCP tool for `{ticket_number}` BEFORE uploading anything, so a resumed or re-run invocation does not re-attach a material that is already present. Compare against the deterministic filenames computed in step 4 and skip any that already exist.\n\n3. **Source classification (scheme-based, no network probe).** Honor the classification already recorded in the draft:\n - **Reachable Local Files** (local filesystem paths) are the only **low-risk** materials eligible for auto-attach — proceed to step 4.\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** are **record-only** in this step: the `upload_attachment` tool is UTF-8 text only, so never attempt a binary upload. (Binary-upload capability is the fast-follow sibling ticket BAPI-424.)\n\n4. **Gather and size-tier each reachable local text material.** For each low-risk 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 `upload_attachment`. 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 - Use a deterministic, sanitized filename of the form `{ticket_number}-material-{hash}.md` (using the `{ticket_number}` input from the Inputs section), where `{hash}` is the first 8 hex characters of the SHA-256 digest of the sanitized absolute source path. Pin this algorithm exactly (SHA-256, first 8 hex chars, of the sanitized absolute path) — do NOT substitute another hash — so the same source always maps to the same filename and the dedup in step 2 works across separate sessions and re-runs. Keep the sanitized source provenance inside the attachment body, not only in the filename.\n\n5. **`upload_attachment` parameter discipline (Zod).** When calling `upload_attachment`, pass `ticket_number`, the attachment filename, and the text content. 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\n6. **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\n7. **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 or attached. Follow the warn-not-halt convention:\n - If an `upload_attachment` call fails (or a file disappeared between inventory and upload), warn gracefully and continue with the next material.\n - On such a post-create attach failure, call `update_ticket_description` to record the failure in the issue's `## Materials & Access` record (the material became unavailable 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 failures. This complements the existing `partial_success` recording convention in `upload-and-track.md`.\n - Apply the step 6 redaction to every warning and recorded note.\n\n## Return\n\nConfirm the outcome: which local materials were attached (with their deterministic filenames), which were skipped/recorded (over-size, external/auth-gated, or binary/image), any attach failures recorded via `update_ticket_description`, and that no failure halted the run.\n",
680
+ "gather-and-attach-materials.md": "Post-create materials-completeness step. Gather the reachable local text materials a freshly-created ticket references and attach them via `attachment` (operation: `\"upload\"`), while recording everything that is record-only. This is the POST-CREATE half of the upload-time materials-completeness pass (BAPI-423); 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`) 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 *Reachable Local Files*, *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. **Deduplicate first.** 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. Compare against the deterministic filenames computed in step 4 and skip any that already exist.\n\n3. **Source classification (scheme-based, no network probe).** Honor the classification already recorded in the draft:\n - **Reachable Local Files** (local filesystem paths) are the only **low-risk** materials eligible for auto-attach — proceed to step 4.\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** are **record-only** in this step: the `attachment` tool's `upload` operation is UTF-8 text only, so never attempt a binary upload. (Binary-upload capability is the fast-follow sibling ticket BAPI-424.)\n\n4. **Gather and size-tier each reachable local text material.** For each low-risk 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 - Use a deterministic, sanitized filename of the form `{ticket_number}-material-{hash}.md` (using the `{ticket_number}` input from the Inputs section), where `{hash}` is the first 8 hex characters of the SHA-256 digest of the sanitized absolute source path. Pin this algorithm exactly (SHA-256, first 8 hex chars, of the sanitized absolute path) — do NOT substitute another hash — so the same source always maps to the same filename and the dedup in step 2 works across separate sessions and re-runs. Keep the sanitized source provenance inside the attachment body, not only in the filename.\n\n5. **`attachment` upload parameter discipline (Zod).** When calling `attachment` with `operation: \"upload\"`, pass `ticket_number`, the attachment filename, and the text content. 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\n6. **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\n7. **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 or attached. 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 - On such a post-create attach failure, call `update_ticket_description` to record the failure in the issue's `## Materials & Access` record (the material became unavailable 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 failures. This complements the existing `partial_success` recording convention in `upload-and-track.md`.\n - Apply the step 6 redaction to every warning and recorded note.\n\n## Return\n\nConfirm the outcome: which local materials were attached (with their deterministic filenames), which were skipped/recorded (over-size, external/auth-gated, or binary/image), any attach failures recorded via `update_ticket_description`, and that no failure halted the run.\n",
659
681
  "get-prd.md": "# get_prd\n\nRetrieve an already-generated **Product Requirements Document (PRD)** for a Jira\nticket.\n\nThis tool only **fetches** an existing PRD — it does **not** start or trigger\ngeneration. If no PRD exists yet (or you need a fresh one), call `request_prd`\nfirst; it starts the async generation and `get_prd` retrieves the result once\nprocessing completes.\n\nThe PRD is product/stakeholder-facing: problem framing, goals, non-goals, target\nusers, success metrics, product requirements, scope, and risks. Present the\nreturned markdown verbatim without summarizing.\n\n## Parameters\n\n| Parameter | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ticket_number` | string | — | Jira ticket key in `PROJECT-NUMBER` format (e.g. `BAPI-123`). |\n| `save_locally` | boolean | `true` | Save the retrieved PRD to a local file. Set to `false` to skip saving. |\n\nLocal saves go to `BAPI_DOCS_DIR/prd/{ticket}-prd-plan.md`.\n\n## Return\n\n- The full PRD as markdown text when one exists.\n- A `404` / not-found response when no PRD is ready yet — that means generation\n has not run, not that the tool failed. Call `request_prd` to generate one.\n",
660
682
  "learn-architecture.md": "## Objective\n\nExplore the codebase to identify architectural principles, directory conventions, design patterns, and data flow, then draft `architecture_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Principles Research\n\nResearch the codebase to identify architectural principles and conventions. For each area below, examine at least 5 representative files. Cite file paths for every pattern. Include code examples (5-15 lines) showing correct usage. Where relevant, include a WRONG example showing the common mistake.\n\nFor each pattern, classify its evidence level:\n- `ENFORCED` — consistently followed across the codebase, violations would be bugs\n- `CONVENTION` — commonly observed, occasional deviations exist\n- `ASPIRATIONAL` — intended direction, not yet consistently applied\n\nResearch areas:\n1. **Architectural coding patterns**: Search `api/routes/` and `api/library/` for separation of concerns, layer boundaries, function-vs-class decisions. Read files matching `*_lib.py`, `*_utils.py`, `*_helpers.py` to document module naming suffix conventions.\n2. **Design patterns**: Search for factory functions, strategy patterns, middleware chains, registry patterns, and dependency injection in `api/` and `src/python/`. Cite concrete usage with file path and function name.\n3. **Dependency management**: Read `requirements.in`, `requirements-dev.in`, and `package.json` files to document how dependencies are declared and organized.\n4. **Error handling architecture**: Search for `log_exception_to_sentry` and `HTTPException` usage patterns across `api/routes/` to document the system-wide error propagation strategy.\n5. **Configuration management**: Search for `os.environ` and `get_config_field` usage to document the two-tier system (env vars vs. database config).\n6. **Tech stack detection**: Read `requirements.in`, `package.json`, and `main.py` to identify primary languages, frameworks, and key libraries.\n7. **Security architecture**: Read `api/routes/setup/auth.py` and search for `require_api_key`, `require_api_session`, and `verify_repo_access` to document authentication and authorization design.\n8. **Agent prompting conventions**: Read files in `src/python/llms/agents/` to document prompt construction, section headers, dynamic content delimiters, and role-based personas.\n\nScope exclusion: Do NOT document testing patterns. Skip the `tests/` directory entirely.\n\nWrite findings to `{docs_dir}/tmp/architecture-principles.md`.\n\n### Phase 2 — Structure & Data Flow Research\n\n1. Call the `regenerate_directory_map` MCP tool to get a fresh directory map.\n2. Read the principles document from Phase 1.\n3. Research and document:\n - **Directory conventions**: For each major directory, document purpose, file naming, internal structure, and an example file.\n - **Module boundaries and import patterns**: Which directories are distinct modules and how they interact. Document import restrictions.\n - **Data flow patterns**: Trace 2-3 complete request paths (synchronous, async background task, agent orchestration).\n - **Integration patterns**: How external services (Jira, GitHub/Bitbucket, LLMs, Pinecone, PostgreSQL) are integrated.\n - **Background task patterns**: The async task lifecycle with `asyncio.create_task`, semaphores, and error reporting.\n\nWrite findings to `{docs_dir}/tmp/architecture-structure.md`.\n\n### Phase 3 — Draft\n\n1. Read both research documents.\n2. Combine into a single `architecture_instructions` draft with these required sections:\n - **1. Core Principles** — Each principle with evidence level and explanation.\n - **2. Layered Architecture** — Layer separation, dependency rule, agent vs orchestration logic.\n - **3. Directory Conventions** — Purpose, naming, structure for each major directory.\n - **4. Data Flow Patterns** — Complete request path traces with file paths.\n - **5. Technical Standards** — Coding style, async patterns, database, schema, LLM integration, config, dependencies.\n - **6. Error Handling & Monitoring** — Error propagation strategy, Sentry integration, Langfuse tracing.\n - **7. Security & Authentication** — Auth architecture, session model, permission model.\n - **8. Agent Prompting Conventions** — Prompt construction, section headers, content delimiters.\n - **9. Integration Points** — External service clients and their calling patterns.\n - **10. AI Code Generation Guidelines** — Anti-patterns, duplication avoidance, pattern compliance checklist.\n\n3. Write the draft to `{docs_dir}/standards/architecture_instructions.md`.\n\n## Return\n\nReturn a brief summary of what was learned about the project's architecture (core principles, directory conventions, data flow), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/architecture_instructions.md`.\n",
661
683
  "learn-backend-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for backend code, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `backend_correctness`\n- **Field name**: `backend_correctness_standards`\n- **Scope**: Server-side code: Python, Ruby, Go, Java, C#, Node.js server code, API routes, business logic.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.py` in `api/` and `src/python/`. If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative files in `api/routes/` and `api/library/` to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n Also read files to document:\n - Error handling implementation (try/except ordering, Sentry calls) with CORRECT/WRONG examples\n - Authentication implementation (auth check sequence) with code examples\n - Database call patterns (`postgres_helpers` (bool, result) tuple handling) with CORRECT/WRONG examples\n - Input validation patterns (Pydantic models, naming conventions)\n - HTTP client patterns (error handling, JiraError sanitization)\n - Async implementation patterns (`asyncio.to_thread()` for blocking code)\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nAlso include:\n- Route handler boilerplate (auth -> validation -> business logic -> error handling)\n- Database interaction patterns with CORRECT/WRONG examples\n- Exception handling pattern (specific first, HTTPException re-raise, generic with Sentry)\n- Sentry reporting patterns and common mistakes\n- Input sanitization rules (JiraError headers, raw exception messages)\n\nWrite the draft to `{docs_dir}/standards/backend_correctness_standards.md`.\n\n## Return\n\nReturn a brief summary of what was learned about backend correctness conventions (structure, naming, error handling, auth, DB patterns), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/backend_correctness_standards.md`.\n",
@@ -667,14 +689,14 @@ export const INSTRUCTIONS = {
667
689
  "learn-style-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for style files, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `style_correctness`\n- **Field name**: `style_correctness_standards`\n- **Scope**: Style files: CSS, SCSS, SASS, LESS, Styled Components, Tailwind configs.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.css`, `**/*.scss`, `**/*.sass`, `**/*.less` (excluding `node_modules/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative style files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/style_correctness_standards.md`.\n\n## Return\n\nReturn a brief summary of what was learned about style-file correctness conventions (structure, naming, methodology), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/style_correctness_standards.md`.\n",
668
690
  "learn-template-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for template files, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `template_correctness`\n- **Field name**: `template_correctness_standards`\n- **Scope**: Template files: HTML, Jinja2, Handlebars, EJS, ERB, Blade, Pug, Twig.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.html`, `**/*.jinja2`, `**/*.j2` in `templates/` and similar directories (excluding `node_modules/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative template files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/template_correctness_standards.md`.\n\n## Return\n\nReturn a brief summary of what was learned about template-file correctness conventions (structure, naming, framework idioms), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/template_correctness_standards.md`.\n",
669
691
  "learn-unit-testing.md": "## Objective\n\nExplore the codebase to identify the test runner, assertion library, mocking framework, and testing patterns, then draft `unit_testing_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Explore Testing Infrastructure\n\n1. **Test Runner and Framework Detection**: Search for test runner configs (`pytest.ini`, `pyproject.toml` `[tool.pytest]` section, `jest.config.*`) and read `package.json` test scripts. Read the `tests/` directory structure.\n\n2. **Testing Patterns**: Read 3-5 representative test files in `tests/pytest/` to identify:\n - Assertion library and style (`assert`, `expect`, custom matchers)\n - Mocking framework (`unittest.mock`, `jest.mock`, `sinon`, etc.)\n - Fixture patterns (setup/teardown)\n - Test organization (by module, feature, layer)\n - Exemplary tests vs. weak tests\n\n3. **How to Run Tests**: Read `pyproject.toml`, `package.json`, and `Makefile` (if present) to determine exact commands for: full suite, single file, by name pattern, with verbose output.\n\n4. **Mocking vs. Fidelity**: Read test helper files in `tests/pytest/helpers/` to document how external APIs are mocked, whether integration tests exist alongside unit tests, and patterns for avoiding third-party calls in tests.\n\n### Phase 2 — Draft\n\nDraft `unit_testing_instructions` as clear, actionable instructions for an AI agent writing unit tests. Cover:\n- How to run tests (exact commands)\n- Which test framework and assertion library to use\n- How to mock external dependencies without calling third parties\n- How to structure test files and test functions\n- What constitutes a thorough test (not just happy path)\n- How to avoid shallow tests that pass but don't verify meaningful behavior\n- Guards against common AI weaknesses: tests that mock the thing being tested, trivially passing assertions, overly complex setup\n\nWrite the draft to `{docs_dir}/standards/unit_testing_instructions.md`.\n\n## Return\n\nReturn a brief summary of what was learned about the project's unit testing setup (test runner, assertion library, mocking framework, run commands), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/unit_testing_instructions.md`.\n",
670
- "monitor-ci-checks.md": "Monitor CI checks for the most recent commit. The behavior is dispatched on the repo-specific `ci_followup_config` JSON value: `poll_only`, `fix_and_iterate`, or `custom`. Read this entire file once before doing anything, then follow only the matching branch.\n\n## Step 3 — Parse `ci_followup_config`\n\nLook at the response from the immediately preceding `get_config_field` call (the pipeline step that ran right before this one). The response envelope's `value` field is itself a JSON string and must be parsed again with `JSON.parse` (i.e., the `value` is double-encoded — the outer envelope is JSON, and the inner `value` is a JSON-encoded string of the actual config object).\n\nIf ANY of the following hold, log a warning and use the defaults `{\"strategy\":\"poll_only\",\"max_iterations\":1,\"max_minutes\":10}`:\n\n- The `get_config_field` response is missing or unavailable (e.g., the step warned-and-continued).\n- The response `value` is `null`.\n- Parsing `value` with `JSON.parse` fails (the persisted text is not valid JSON).\n- The parsed result is not a JSON object.\n- One or more of the required keys (`strategy`, `max_iterations`, `max_minutes`, `instructions`) is missing.\n- `strategy` is not one of `poll_only`, `fix_and_iterate`, or `custom`.\n\n## Step 4 — Dispatch on `strategy`\n\nRead this whole file once and then follow only the matching branch:\n\n- `poll_only` → follow Step 5.\n- `fix_and_iterate` → follow Step 6.\n- `custom` → follow Step 7.\n\nIf `strategy` is unrecognized, log a warning and fall through to Step 5 (`poll_only`).\n\n## Step 5 — `poll_only`\n\nPreserve the baseline polling behavior. The configured `max_minutes` is IGNORED in this branch — `poll_only` always uses the existing 10-minute baseline.\n\n1. Run `git rev-parse HEAD` to get the current commit SHA.\n2. Call the `resolve_ci_checks` tool with `commit_ref` set to that SHA. This discovers and classifies the CI checks for the repository.\n3. Poll CI status by calling `poll_ci_checks` with `commit_ref` set to the same SHA. Check the response for `all_complete` and `all_passed`.\n4. If checks are not yet complete, wait 30 seconds and poll again. Repeat until all checks are complete or 10 minutes have elapsed.\n5. If all checks pass, report success.\n6. If any checks fail, report which checks failed and include any available annotations or log details from the poll response. Do NOT attempt to fix failures — just report them clearly.\n7. If CI status is unavailable (resolver/poll returns `available: false`), report unavailable status and exit; do not attempt fixes.\n8. If the 10-minute timeout is reached, report timeout and exit.\n\n### Polling Directive\n\nDuring the polling loop, execute `sleep 30` silently. Do NOT output any inline commentary, reasoning, or partial status updates between polls. Only output a status message when:\n- All checks are complete (pass or fail), OR\n- The 10-minute timeout is reached.\n\nThis minimizes context window consumption during long-running CI waits.\n\n## Step 6 — `fix_and_iterate`\n\nThis is a self-contained loop where `iteration` is the number of correction rounds already pushed and `start_time` is captured before the first iteration. `max_minutes` is the TOTAL wall-clock cap across all iterations, not an additional per-iteration budget. The 10-minute per-iteration `poll_ci_checks` cap is INSIDE that total budget.\n\nInitialize:\n\n- `iteration = 0`\n- `start_time = now()`\n\nBefore starting each iteration AND before applying corrections, check the total wall-clock budget. If `now() - start_time >= max_minutes`, warn and exit.\n\nPer iteration:\n\n1. Run `git rev-parse HEAD` to get the current commit SHA. The previous push may have changed it; always read fresh.\n2. Run `git branch --show-current` to get the current branch. Always read fresh.\n3. Call `resolve_ci_checks` with `commit_ref` set to the current SHA (once per new SHA — the server caches per project but the agent should still call it for each new SHA).\n4. Poll `poll_ci_checks` with `commit_ref` set to the current SHA. Stop when `all_complete` is true, OR the per-iteration 10-minute timeout is reached, OR the remaining total wall-clock budget is exhausted.\n5. If CI status is unavailable (`available: false`), warn and exit the loop — automated remediation cannot make reliable progress without CI signals.\n6. Apply repo-specific `instructions` ONLY when the `instructions` field is non-empty. If the repo `instructions` reference templated placeholder tokens for the GitHub owner, repo, or PR number — e.g., the literal tokens written as a left brace, the word `owner`/`repo`/`pr`, then a right brace — resolve them from the local git/VCS context. Use `gh pr list --head <branch> --json number` to get the PR number; parse the remote URL (`git config --get remote.origin.url`) for owner/repo. If `instructions` is empty, skip repo-specific signal gathering and use only structured CI failure information.\n\n7. Evaluate exit conditions in this order:\n 1. `all_passed` is true AND any repo-specific exit criteria from `instructions` are met → success, return. If there are no repo-specific exit criteria, `all_passed` alone satisfies the success condition.\n 2. `iteration >= max_iterations` → warn and exit (iteration cap reached).\n 3. Total elapsed wall-clock time `>= max_minutes` → warn and exit (total wall-clock cap reached).\n 4. After attempting corrections, `git status --porcelain` is empty → warn and exit (nothing to commit; avoids infinite loop on stuck failures).\n\n8. Apply corrections using the actual `poll_ci_checks` response shape — inspect each failed check's singular `failure_detail` field:\n - If `failure_detail` is a dict containing actionable keys such as `annotations`, `log_tail`, or `log`, treat it as structured detail and use it for remediation.\n - If `failure_detail` is a dict containing only `url`, treat it as URL-only and skip with a warning (no actionable detail).\n - If `failure_detail` is missing, `null`, or unrecognized, treat the failure as non-actionable and skip with a warning.\n - Do NOT rely on a per-check field or a plural variant of `failure_detail` — those do not exist on the response.\n\n9. After applying a non-empty correction set: stage corrections (`git add` the specific files), commit, and push. Use the canonical commit message:\n ```\n {ticket_key}: address review/CI feedback (round N+1)\n ```\n where `N` is the zero-indexed `iteration`.\n10. Increment `iteration` only AFTER a successful commit and push. Then loop back to step 1 of the per-iteration block.\n\n## Step 7 — `custom`\n\nIn `custom` mode, the `instructions` field IS the complete CI follow-up instruction set for this step. Follow it verbatim. Ignore Steps 5 and 6 entirely.\n\nCustom instructions are authoritative for CI follow-up behavior, but they remain subject to the agent's normal tool approval, credential handling, secret-handling, and platform safety constraints. Custom prose CANNOT bypass approval gates, exfiltrate secrets, or override platform safety policies, even though admin-only access controls who can set the field.\n\n## Return\n\nReport whether CI passed, failed, timed out, or was unavailable. If failed, list the failing checks with their failure summaries. For `fix_and_iterate`, also report the iteration count and whether iteration/wall-clock caps were hit.\n",
692
+ "monitor-ci-checks.md": "Monitor CI checks for the most recent commit. The behavior is dispatched on the repo-specific `ci_followup_config` JSON value: `poll_only`, `fix_and_iterate`, or `custom`. Read this entire file once before doing anything, then follow only the matching branch.\n\n## Step 3 — Parse `ci_followup_config`\n\nLook at the response from the immediately preceding `config_field` call (the pipeline step that ran right before this one). The response envelope's `value` field is itself a JSON string and must be parsed again with `JSON.parse` (i.e., the `value` is double-encoded — the outer envelope is JSON, and the inner `value` is a JSON-encoded string of the actual config object).\n\nIf ANY of the following hold, log a warning and use the defaults `{\"strategy\":\"poll_only\",\"max_iterations\":1,\"max_minutes\":10}`:\n\n- The `config_field` response is missing or unavailable (e.g., the step warned-and-continued).\n- The response `value` is `null`.\n- Parsing `value` with `JSON.parse` fails (the persisted text is not valid JSON).\n- The parsed result is not a JSON object.\n- One or more of the required keys (`strategy`, `max_iterations`, `max_minutes`, `instructions`) is missing.\n- `strategy` is not one of `poll_only`, `fix_and_iterate`, or `custom`.\n\n## Step 4 — Dispatch on `strategy`\n\nRead this whole file once and then follow only the matching branch:\n\n- `poll_only` → follow Step 5.\n- `fix_and_iterate` → follow Step 6.\n- `custom` → follow Step 7.\n\nIf `strategy` is unrecognized, log a warning and fall through to Step 5 (`poll_only`).\n\n## Step 5 — `poll_only`\n\nPreserve the baseline polling behavior. The configured `max_minutes` is IGNORED in this branch — `poll_only` always uses the existing 10-minute baseline.\n\n1. Run `git rev-parse HEAD` to get the current commit SHA.\n2. Call the `resolve_ci_checks` tool with `commit_ref` set to that SHA. This discovers and classifies the CI checks for the repository.\n3. Poll CI status by calling `poll_ci_checks` with `commit_ref` set to the same SHA. Check the response for `all_complete` and `all_passed`.\n4. If checks are not yet complete, wait 30 seconds and poll again. Repeat until all checks are complete or 10 minutes have elapsed.\n5. If all checks pass, report success.\n6. If any checks fail, report which checks failed and include any available annotations or log details from the poll response. Do NOT attempt to fix failures — just report them clearly.\n7. If CI status is unavailable (resolver/poll returns `available: false`), report unavailable status and exit; do not attempt fixes.\n8. If the 10-minute timeout is reached, report timeout and exit.\n\n### Polling Directive\n\nDuring the polling loop, execute `sleep 30` silently. Do NOT output any inline commentary, reasoning, or partial status updates between polls. Only output a status message when:\n- All checks are complete (pass or fail), OR\n- The 10-minute timeout is reached.\n\nThis minimizes context window consumption during long-running CI waits.\n\n## Step 6 — `fix_and_iterate`\n\nThis is a self-contained loop where `iteration` is the number of correction rounds already pushed and `start_time` is captured before the first iteration. `max_minutes` is the TOTAL wall-clock cap across all iterations, not an additional per-iteration budget. The 10-minute per-iteration `poll_ci_checks` cap is INSIDE that total budget.\n\nInitialize:\n\n- `iteration = 0`\n- `start_time = now()`\n\nBefore starting each iteration AND before applying corrections, check the total wall-clock budget. If `now() - start_time >= max_minutes`, warn and exit.\n\nPer iteration:\n\n1. Run `git rev-parse HEAD` to get the current commit SHA. The previous push may have changed it; always read fresh.\n2. Run `git branch --show-current` to get the current branch. Always read fresh.\n3. Call `resolve_ci_checks` with `commit_ref` set to the current SHA (once per new SHA — the server caches per project but the agent should still call it for each new SHA).\n4. Poll `poll_ci_checks` with `commit_ref` set to the current SHA. Stop when `all_complete` is true, OR the per-iteration 10-minute timeout is reached, OR the remaining total wall-clock budget is exhausted.\n5. If CI status is unavailable (`available: false`), warn and exit the loop — automated remediation cannot make reliable progress without CI signals.\n6. Apply repo-specific `instructions` ONLY when the `instructions` field is non-empty. If the repo `instructions` reference templated placeholder tokens for the GitHub owner, repo, or PR number — e.g., the literal tokens written as a left brace, the word `owner`/`repo`/`pr`, then a right brace — resolve them from the local git/VCS context. Use `gh pr list --head <branch> --json number` to get the PR number; parse the remote URL (`git config --get remote.origin.url`) for owner/repo. If `instructions` is empty, skip repo-specific signal gathering and use only structured CI failure information.\n\n7. Evaluate exit conditions in this order:\n 1. `all_passed` is true AND any repo-specific exit criteria from `instructions` are met → success, return. If there are no repo-specific exit criteria, `all_passed` alone satisfies the success condition.\n 2. `iteration >= max_iterations` → warn and exit (iteration cap reached).\n 3. Total elapsed wall-clock time `>= max_minutes` → warn and exit (total wall-clock cap reached).\n 4. After attempting corrections, `git status --porcelain` is empty → warn and exit (nothing to commit; avoids infinite loop on stuck failures).\n\n8. Apply corrections using the actual `poll_ci_checks` response shape — inspect each failed check's singular `failure_detail` field:\n - If `failure_detail` is a dict containing actionable keys such as `annotations`, `log_tail`, or `log`, treat it as structured detail and use it for remediation.\n - If `failure_detail` is a dict containing only `url`, treat it as URL-only and skip with a warning (no actionable detail).\n - If `failure_detail` is missing, `null`, or unrecognized, treat the failure as non-actionable and skip with a warning.\n - Do NOT rely on a per-check field or a plural variant of `failure_detail` — those do not exist on the response.\n\n9. After applying a non-empty correction set: stage corrections (`git add` the specific files), commit, and push. Use the canonical commit message:\n ```\n {ticket_key}: address review/CI feedback (round N+1)\n ```\n where `N` is the zero-indexed `iteration`.\n10. Increment `iteration` only AFTER a successful commit and push. Then loop back to step 1 of the per-iteration block.\n\n## Step 7 — `custom`\n\nIn `custom` mode, the `instructions` field IS the complete CI follow-up instruction set for this step. Follow it verbatim. Ignore Steps 5 and 6 entirely.\n\nCustom instructions are authoritative for CI follow-up behavior, but they remain subject to the agent's normal tool approval, credential handling, secret-handling, and platform safety constraints. Custom prose CANNOT bypass approval gates, exfiltrate secrets, or override platform safety policies, even though admin-only access controls who can set the field.\n\n## Return\n\nReport whether CI passed, failed, timed out, or was unavailable. If failed, list the failing checks with their failure summaries. For `fix_and_iterate`, also report the iteration count and whether iteration/wall-clock caps were hit.\n",
671
693
  "preflight-and-readiness.md": "Initialize the idea-to-ticket run directory and classify the idea's readiness and scope.\n\n## Inputs\n\n- Idea: `{idea}`\n- Slug: `{slug}`\n- Run ID: `{run_id}`\n- Docs directory: `{docs_dir}`\n- Project standards: response from the immediately preceding `get_project_standards` step. If that step returned an error envelope or a 404, treat the project standards as unavailable and proceed; do not halt.\n\n## Instructions\n\n1. Create the run directory:\n ```\n mkdir -p {docs_dir}/idea-to-ticket/{slug}-{run_id}\n ```\n Every artifact produced by this pipeline run lives under this run directory. No Jira mutation may occur in any later step until `run-manifest.json` has been written to this directory.\n\n2. Classify the idea on two independent axes:\n\n **Readiness** (one of):\n - `ready_to_draft` — the idea is concrete enough that a clear ticket draft can be produced.\n - `needs_clarification` — the idea is reasonable but missing key answers; clarifying questions must be raised in `open-questions.md` later.\n - `research_first` — drafting is blocked on external/codebase research; deep or narrow research must come first.\n - `too_vague_to_ticket` — the idea is not actionable yet; do not produce a ticket.\n\n **Scope** (one of):\n - `task` — a single Jira Task (default when ambiguous).\n - `spike` — a single Jira Spike for primarily discovery/research work.\n - `epic_candidate` — the idea decomposes into a Jira Epic plus multiple child tickets.\n\n3. Halt locally if readiness is `too_vague_to_ticket`. Write the manifest anyway (see step 4) so the local artifacts record the halt; then stop without continuing the rest of the pipeline. Do not attempt any Jira mutation.\n\n4. Write `run-manifest.json` to `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`. Required fields:\n - `idea` — the original `{idea}` text.\n - `slug` — `{slug}`.\n - `run_id` — `{run_id}`.\n - `run_dir` — `{docs_dir}/idea-to-ticket/{slug}-{run_id}/`.\n - `readiness` — one of the four readiness values above.\n - `scope` — one of the three scope values above.\n - `project_standards_available` — `true` if `get_project_standards` returned a usable result, `false` otherwise.\n - `idempotency_label` — `bapi-idea-to-ticket-{run_id}` (per-run label; lets downstream steps resume THIS run by label).\n - `stable_label` — `bapi-idea-hash-{idea_hash}` (stable across runs of the same idea; lets the duplicate-detection step catch a PRIOR run of the same idea by label, not just fuzzy text).\n - `created_at` — ISO 8601 timestamp.\n\n5. The manifest is the resumability artifact for the whole run. Do not include secrets or raw credentials. Keep the file under a few KB.\n\n## Return\n\nConfirm the run directory and `run-manifest.json` were created, and report the classified `readiness` and `scope`. If readiness is `too_vague_to_ticket`, also report that the pipeline must stop without Jira mutation.\n",
672
694
  "request-prd.md": "# request_prd\n\nStart (or refresh) asynchronous generation of a **Product Requirements Document\n(PRD)** for a Jira ticket.\n\nA PRD is the most product/stakeholder-facing document in the design-document\nfamily. It frames product intent — the problem, goals, non-goals, target users,\nsuccess metrics, product requirements, scope, and risks — rather than the\ndetailed functional flows and acceptance behavior an FSD covers, or the\narchitecture/implementation guidance a TDD covers.\n\n## Async request/retrieve pattern\n\n`request_prd` only **starts** generation; it does not return the PRD directly\nunless you set `wait_for_result`. PRD generation typically takes **2–4 minutes**.\n\n1. Call `request_prd` with the `ticket_number`.\n2. Wait for processing to complete (2–4 minutes).\n3. Call `get_prd` with the same `ticket_number` to retrieve the result.\n\nSet `wait_for_result: true` to block and return the PRD content directly instead\nof polling separately.\n\n## Parameters\n\n| Parameter | Type | Default | Description |\n| --- | --- | --- | --- |\n| `ticket_number` | string | — | Jira ticket key in `PROJECT-NUMBER` format (e.g. `BAPI-123`). |\n| `wait_for_result` | boolean | `false` | When `true`, block and poll until the PRD is ready, then return it directly. |\n| `save_locally` | boolean | `true` | When `wait_for_result` is `true`, save the PRD to `BAPI_DOCS_DIR/prd/{ticket}-prd-plan.md`. |\n| `second_opinion` | string | — | Provider routing override for **this** generation request (e.g. `anthropic`, `openai`, `gemini`). This is **not** the standalone `second_opinion` tool — it only changes which provider produces this request's artifact, and takes precedence over `provider`. |\n| `provider` | string | — | Pure provider switch without second-opinion semantics. If both `provider` and `second_opinion` are set, `second_opinion` wins. |\n\n## Return\n\n- `202` when the request is accepted (async dispatch).\n- `404` if the ticket does not exist in Jira.\n- `403` if the API key is unauthorized.\n",
673
695
  "research-decision.md": "Decide which research tools to run for this idea, biased toward cheap local research first.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json` (must already exist from the preflight step).\n\n## Instructions\n\n1. Read `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`. This is the source of truth for `idea`, `readiness`, `scope`, and `run_id`. If the file does not exist, halt locally — the preflight step did not complete.\n\n2. Decide which research tools should run for this idea, in roughly this priority order:\n - **Local codebase research first.** Inspect the working tree (search, grep, file reads) for prior art, related modules, and existing tests. Prefer this for anything that touches code you already own.\n - **Narrow web search second.** Use targeted web search for short factual lookups: a specific library API, a known external standard, a public spec.\n - **Deep research only when justified.** Deep research is expensive and slow; it must be earned by one of the rubric items below.\n\n3. Deep-research allowance rubric. Deep research is only allowed when at least one of these is true:\n - **blast radius**: the change spans many systems or has high reversibility cost (e.g., schema migrations, auth, billing, public APIs).\n - **unfamiliar external domain**: the idea depends on a third-party domain or specification the repository has no prior coverage of.\n - **compliance/security uncertainty**: there is real compliance or security uncertainty (SOC2, PII, secret handling, access control).\n - **cheaper research failed**: a cheaper round (local + narrow web search) already happened in this run and left blocking unknowns.\n - **explicit user request**: the user explicitly asked for deep research.\n\n4. Write `{docs_dir}/idea-to-ticket/{slug}-{run_id}/research-plan.json`. Required fields:\n - `selected_tools` — array of tool identifiers to run, drawn from at least `[\"codebase_search\", \"web_search\", \"deep_research\"]`. Empty array is allowed when no research is needed.\n - `rationale` — short string explaining the choice in terms of the rubric above.\n - `deep_research_query` — string. Required when `deep_research` is in `selected_tools`, otherwise empty string.\n - `web_search_topics` — array of strings; may be empty.\n - `codebase_search_topics` — array of strings; may be empty.\n - `expected_unknowns` — array of strings describing what the research is expected to resolve.\n\n5. Do not invoke any research tool from this step — that happens in `execute-research.md`. This step only writes the plan.\n\n## Return\n\nConfirm `research-plan.json` was written, list `selected_tools`, and quote the rationale.\n",
674
696
  "screen-and-resolve.md": "Apply project standards and the minimum-evidence gate before drafting.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`.\n- Research pack: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/research-pack.md` / `.json` (may be partial or absent).\n- Duplicate assessment: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/duplicate-assessment.json`.\n- Project standards: the response from the earlier `get_project_standards` step in the same pipeline run. Treat the standards as unavailable when that response was an error envelope, a 404, or missing.\n\n## Instructions\n\n1. Produce three artifacts in the run directory:\n - `{docs_dir}/idea-to-ticket/{slug}-{run_id}/standards-checklist.json`\n - `{docs_dir}/idea-to-ticket/{slug}-{run_id}/open-questions.md`\n - `{docs_dir}/idea-to-ticket/{slug}-{run_id}/resolved-uncertainties.md`\n\n2. Standards checklist:\n - When `get_project_standards` returned a usable result, derive the checklist items from those standards. Each item is a `{requirement, satisfied, evidence}` triple where `evidence` either cites the research pack or notes \"deferred to draft\".\n - When standards are unavailable (404, error envelope, missing), generate a fallback baseline checklist covering at least: requirements clarity, acceptance criteria presence, testability, security/PII consideration, and rollback/observability when scope warrants it. Mark each as `satisfied: false` with `evidence: \"fallback baseline — no project standards available\"`.\n\n3. Open questions:\n - List every unresolved unknown that blocks drafting. Pull from the research pack's `unresolved_unknowns` and from your own reading of `idea`.\n - Each question gets its own bullet (no markdown tables, no `- [ ]` checkboxes — BAPI-320 hygiene).\n - If the question has a defensible best-guess answer, write it in `resolved-uncertainties.md` instead, with explicit assumption language (\"Assuming X because Y...\").\n\n4. Resolved uncertainties:\n - Mirror open questions that have defensible best-guess answers. Each entry must include `assumption`, `basis` (research-pack reference or codebase reference), and `confidence` (\"low\", \"medium\", \"high\").\n\n5. Minimum-evidence gate. Halt locally if ALL of the following are true:\n - The research pack contains no codebase references for the idea.\n - The standards checklist has zero items (even the fallback baseline is missing).\n - `resolved-uncertainties.md` records no explicit assumptions.\n When the gate fires, do not continue to drafting. Report the halt and direct the user to either run a smaller idea, run `--allow-duplicate` semantics for replays, or supply more context manually.\n\n## Return\n\nConfirm the three artifacts were written and whether the minimum-evidence gate fired.\n",
675
697
  "store-and-approve-epic-plan.md": "Store the approved epic plan DAG in the backend and approve it.\n\nThis step runs after the user has approved the decomposition in the\n`decompose-epic` step. It reads the machine-readable sidecar written by that\nstep and wires it into the backend durable store.\n\n## Variables\n\n- `{epic_key}` — Jira epic key (e.g. BAPI-405)\n- `{epic_slug}` — lowercase-hyphen slug derived from the epic key\n- `{docs_dir}` — base docs directory (e.g. `docs/tmp`)\n\n## Step 1 — Read the DAG sidecar\n\nRead the structured JSON sidecar from\n`{docs_dir}/epic-plans/{epic_slug}/epic-plan.dag.json`.\n\nThe DAG must be serialized from this sidecar — **never by re-parsing the\nmarkdown** (`epic-plan.md`). If the sidecar is missing or unparseable, warn\nthe user with:\n\n> \"The structured DAG sidecar (`epic-plan.dag.json`) is missing or invalid.\n> The plan cannot be stored automatically. To recover, you can reconstruct the\n> DAG manually by parsing the Jira dependency links for each sub-task\n> (deterministic Jira-link DAG builder — documented fallback, not built here).\"\n\nThen stop this step with a warning (do not raise an error that aborts the\nentire pipeline).\n\n## Step 2 — Create the epic run\n\nCall `mcp__bridge-api__create_epic_run` (or equivalent) to create the epic run\nrecord for `{epic_key}`. If the run already exists (HTTP 409), read the\nexisting run ID and proceed with that run — do not fail.\n\nAlternatively, if a `create_epic_run` MCP tool is not available, make a direct\nAPI call to `POST /jira/epic-runs/runs` with `{ repo_name, epic_key, status: \"planning\" }`.\n\n## Step 3 — Compute the plan hash and store the plan\n\nUse the `storeEpicPlan` conductor client method (or equivalent) to POST the\nplan blob:\n\n- `plan_version`: the integer `plan_version` field from the DAG sidecar (must be ≥ 1).\n- `plan_blob`: the full parsed DAG object from the sidecar.\n- `plan_hash`: computed by `hashPlan(dag)` from `mcp_server/src/conductor/plan.ts`.\n If the hash cannot be computed locally, pass the SHA-256 hex of the\n canonical JSON string (keys sorted, no extra whitespace) as a fallback.\n\nIf the store call returns HTTP 409 with a hash mismatch, warn the user (substituting the actual version number from the sidecar):\n\n> \"Plan version N is already stored with a different hash.\n> Increment `plan_version` in the sidecar and retry.\"\n\nThen stop this step.\n\n## Step 4 — Approve the plan\n\nCall `approveEpicPlan` for `plan_version` from the sidecar. On success, the\nbackend transitions the run to `active` status.\n\nIf HTTP 409 is returned (superseded), warn the user:\n\n> \"A later plan version is already approved — approval skipped.\"\n\n## Return\n\nReport:\n- The epic run ID.\n- The stored `plan_version`.\n- The `plan_hash` returned by the approve call.\n- Whether the run transitioned to `active`.\n\nExample: \"Plan v1 stored and approved for epic run `<epic_run_id>`. Run is now active.\"\n",
676
698
  "update-ticket-rewrite.md": "Rewrite the Jira ticket description for {ticket_key} using the generated clarifying questions and critique documents.\n\n1. Fetch the current ticket description using the `get_ticket` tool with ticket_number `{ticket_key}`.\n2. Read the clarifying questions from the local file saved by the previous step (check `{docs_dir}/clarifying-questions/` for `{ticket_key}-clarifying-questions.md`). For each best-guess answer, verify it against the codebase using file search and code grep. Accept verified answers, correct inaccurate ones with evidence, and let ambiguous ones stand.\n3. Read the critique from the local file saved by the previous step (check `{docs_dir}/ticket-critiques/` for `{ticket_key}-ticket-quality-critique.md`). Address all Requested Changes. Apply Points to Consider selectively — accept genuine improvements, skip stylistic preferences.\n4. Write the rewritten ticket in standard markdown format (not Jira wiki markup). Preserve the Summary, Requirements, and Acceptance Criteria structure.\n5. Save the output to `{docs_dir}/tickets/{ticket_key}.md`. Output only the clean rewritten ticket — no meta-commentary.\n\n## Return\n\nConfirm the rewritten ticket was saved to `{docs_dir}/tickets/{ticket_key}.md` and briefly note which clarifying-question answers were corrected against the codebase and which critique Requested Changes were addressed.\n",
677
- "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`, `upload_attachment`, `list_attachments`, `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`, `upload_attachment`, 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 `upload_attachment` 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 reachable local text materials 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}`).\n 2. Draft any surviving children that lack a draft on disk by calling `jira-ticket-writer` per child with the `draft_path` from the decomposition plan. After drafting, extend `draft-metadata.json` so `children[]` mirrors the final list from the decomposition plan.\n 3. 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 `upload_attachment` 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 4. 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 `upload_attachment` 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 5. 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 6. **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 `upload_attachment` 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",
678
- "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 `upload_attachment` 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 `upload_attachment` 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",
699
+ "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 reachable local text materials 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}`).\n 2. Draft any surviving children that lack a draft on disk by calling `jira-ticket-writer` per child with the `draft_path` from the decomposition plan. After drafting, extend `draft-metadata.json` so `children[]` mirrors the final list from the decomposition plan.\n 3. 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 4. 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 5. 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 6. **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",
700
+ "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",
679
701
  "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\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## Next Steps\n{One-line summaries for each sub-task, specifically formatted so they can be copy-pasted directly into the `/write-ticket` command. 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, and the Recommended Implementation Order as a **comment** on that epic by calling the `add_comment` MCP tool with `ticket_number` set to `{epic_key}` and a concise comment containing those three parts. Do not create Jira dependency links and do not attach a separate markdown doc — the comment is the delivery. Display: `\"Posted epic goals/NFRs and recommended implementation order 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"
680
702
  };