@bridge_gpt/mcp-server 0.2.49 → 0.2.51

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 (62) hide show
  1. package/README.md +25 -8
  2. package/build/base-ref.js +28 -3
  3. package/build/claude-review-workflow-drift-probe.js +130 -0
  4. package/build/claude-review-workflow-drift.js +173 -0
  5. package/build/claude-review-workflow.js +81 -16
  6. package/build/commands.generated.js +5 -5
  7. package/build/conduct-epic/bridge-client.js +115 -1
  8. package/build/conduct-epic/cli.js +351 -33
  9. package/build/conduct-epic/cut-protocol.js +51 -0
  10. package/build/conductor/done-gate.js +25 -3
  11. package/build/conductor/install-doctor.js +65 -5
  12. package/build/conductor/latest-check-selector.js +170 -0
  13. package/build/conductor/local-merge.js +8 -6
  14. package/build/conductor-bin.js +1 -1
  15. package/build/{brainstorm-files.js → council-files.js} +15 -15
  16. package/build/decision-page-schema.js +1 -1
  17. package/build/docs.generated.js +1 -1
  18. package/build/doctor.js +352 -4
  19. package/build/epic-integration-pr.js +280 -0
  20. package/build/executor/job-runner.js +7 -1
  21. package/build/executor/merge-job.js +46 -1
  22. package/build/executor/worktree.js +46 -1
  23. package/build/index.js +153 -65
  24. package/build/init.js +9 -2
  25. package/build/install-bridge.js +60 -2
  26. package/build/install-reexec.js +47 -9
  27. package/build/pipelines.generated.js +8 -2
  28. package/build/plan-epic-conductor-eligibility.js +183 -0
  29. package/build/plane/cli.js +12 -2
  30. package/build/plane/manifest.js +25 -1
  31. package/build/plane/member-roster.js +61 -7
  32. package/build/plane/preflight.js +24 -9
  33. package/build/plane/supervisor.js +77 -5
  34. package/build/plane/types.js +23 -3
  35. package/build/readme.generated.js +1 -1
  36. package/build/run-unit-tests-launcher.js +2 -1
  37. package/build/setup-epic.js +32 -0
  38. package/build/sfcc/reads-custom-object-def.js +10 -13
  39. package/build/sfcc/reads-site-preference.js +5 -5
  40. package/build/sfcc/reads-system-object.js +4 -4
  41. package/build/sfcc/writes-custom-object-def.js +7 -7
  42. package/build/sfcc/writes-site-preference.js +4 -3
  43. package/build/sfcc/writes-system-object.js +7 -6
  44. package/build/stale-worktree-doctor.js +120 -0
  45. package/build/start-tickets-prereqs.js +70 -0
  46. package/build/start-tickets.js +91 -3
  47. package/build/version.generated.js +3 -2
  48. package/package.json +6 -3
  49. package/pipelines/plan-epic.json +5 -0
  50. package/build/chain-orchestrator.js +0 -1457
  51. package/build/chain-utils.js +0 -68
  52. package/build/command-catalog.js +0 -376
  53. package/build/schedule-run.js +0 -1300
  54. package/build/schedule-store.js +0 -172
  55. package/build/scheduled-prompt.js +0 -115
  56. package/build/scheduler-backends/at-fallback.js +0 -139
  57. package/build/scheduler-backends/escaping.js +0 -143
  58. package/build/scheduler-backends/index.js +0 -72
  59. package/build/scheduler-backends/launchd.js +0 -225
  60. package/build/scheduler-backends/systemd-user.js +0 -250
  61. package/build/scheduler-backends/task-scheduler.js +0 -214
  62. package/build/scheduler-backends/types.js +0 -23
package/build/init.js CHANGED
@@ -15,7 +15,7 @@ import os from "os";
15
15
  import { COMMANDS } from "./commands.generated.js";
16
16
  import { AGENTS } from "./agents.generated.js";
17
17
  import { DOCS } from "./docs.generated.js";
18
- import { VERSION } from "./version.generated.js";
18
+ import { VERSION, LAUNCHER_ARGS } from "./version.generated.js";
19
19
  import { reconstructAgentMarkdown, translateAgentToCopilot } from "./agent-utils.js";
20
20
  import { validateRepoName, resolveRepoNameForProjectRoot } from "./bridge-config.js";
21
21
  import { ensureGitignored as ensureGitignoredShared } from "./git-ignore-utils.js";
@@ -58,9 +58,16 @@ export function buildBridgeApiEntry(cwd, metadata) {
58
58
  // BAPI-728: `repoName`/`baseUrl` are non-secret and always supplied by the
59
59
  // caller when it has already resolved them. `BAPI_API_KEY` remains absent in
60
60
  // EVERY branch — the server self-resolves it at runtime.
61
+ //
62
+ // BAPI-930: `LAUNCHER_ARGS` (generated by scripts/launcher-args.js ->
63
+ // scripts/bundle-version.js -> version.generated.ts) is the canonical launcher
64
+ // token sequence; `mcp_server/README.md`'s hand-configuration examples are
65
+ // rendered from the same generated constant by
66
+ // scripts/sync-readme-host-examples.js, so the two cannot drift. Spread into a
67
+ // fresh array so each registration gets its own independently mutable `args`.
61
68
  return {
62
69
  command: "npx",
63
- args: ["-y", "--prefer-offline", `${MCP_PACKAGE_NAME}@${VERSION}`, "serve"],
70
+ args: [...LAUNCHER_ARGS],
64
71
  env: {
65
72
  BAPI_BASE_URL: metadata?.baseUrl ?? DEFAULT_BRIDGE_BASE_URL,
66
73
  BAPI_REPO_NAME: metadata?.repoName ?? PLACEHOLDER_REPO_NAME,
@@ -165,6 +165,9 @@ import { runInstallBridgeConductorCli, } from "./install-bridge-conductor.js";
165
165
  import { runConductorInstallDoctor, CONDUCTOR_PROFILE_TOKEN, } from "./conductor/install-doctor.js";
166
166
  import { resolveConductorBridgeApiAccess, } from "./conductor/bridge-api-client.js";
167
167
  import { claudeReviewWorkflowPath, writeClaudeReviewWorkflow, } from "./claude-review-workflow.js";
168
+ // BAPI-941: read-only workflow-lineage probe, wired into the conductor install
169
+ // doctor's optional drift seam below.
170
+ import { probeClaudeReviewWorkflowDrift, resolveCurrentBranch, resolveRepositoryDefaultBranch, } from "./claude-review-workflow-drift-probe.js";
168
171
  import { runSetupEpicCli } from "./setup-epic.js";
169
172
  import { ensureGitignored as ensureGitignoredShared, } from "./git-ignore-utils.js";
170
173
  import { resolveStartTicketsRepoName } from "./start-tickets-repo.js";
@@ -927,6 +930,10 @@ export function sanitizePrewarmEnv(env) {
927
930
  delete sanitized.BAPI_API_KEY;
928
931
  delete sanitized.BAPI_INVITE;
929
932
  delete sanitized.BAPI_SIGNUP_EMAIL;
933
+ // BAPI-931: the provenance marker is a label, not a secret, but it describes a
934
+ // credential this probe deliberately no longer carries. Stripping it keeps the
935
+ // prewarm environment internally consistent rather than leaving an orphan.
936
+ delete sanitized[INSTALL_REEXEC_KEY_SOURCE_ENV];
930
937
  return sanitized;
931
938
  }
932
939
  function spawnPrewarmDefault(command, args, env) {
@@ -959,6 +966,10 @@ function spawnPrewarmDefault(command, args, env) {
959
966
  }
960
967
  /** Build default deps from the live process. */
961
968
  export function createDefaultInstallBridgeDeps() {
969
+ // BAPI-941: hoisted so the conductor install doctor's read-only
970
+ // workflow-lineage seam below can reuse the SAME command runner the rest of
971
+ // installation uses, rather than constructing a second one.
972
+ const startTicketsDeps = createDefaultStartTicketsDeps();
962
973
  const isTTY = Boolean(process.stdin.isTTY);
963
974
  // One production fetch, reused for both the `fetch` seam and the default
964
975
  // resolver, so a direct caller of this factory gets a resolver bound to the
@@ -1005,7 +1016,7 @@ export function createDefaultInstallBridgeDeps() {
1005
1016
  discardBootstrapPending: discardBootstrapPendingCredential,
1006
1017
  buildShellCommand: buildGenericAgentShellCommand,
1007
1018
  spawnTerminalTab: getDefaultSpawnTerminalTabForPlatform(process.platform),
1008
- startTicketsDeps: createDefaultStartTicketsDeps(),
1019
+ startTicketsDeps,
1009
1020
  log: (m) => console.log(m),
1010
1021
  errorLog: (m) => console.error(m),
1011
1022
  // Debug-only sink (BAPI-666): gated on BAPI_INSTALL_DEBUG so it is silent on a
@@ -1022,6 +1033,24 @@ export function createDefaultInstallBridgeDeps() {
1022
1033
  fetch: params.fetch,
1023
1034
  reviewPolicySource: params.reviewPolicySource,
1024
1035
  readWorkflowFile: () => params.readFile(claudeReviewWorkflowPath(params.cwd)),
1036
+ // BAPI-941: read-only workflow-lineage classification. Runs only after
1037
+ // the file is confirmed present, uses the same command runner the rest
1038
+ // of installation already has, and touches the network never — two
1039
+ // `git show` reads against the local object database. Any failure
1040
+ // resolves to `unverified` inside the probe, which leaves the doctor's
1041
+ // presence state exactly as it was.
1042
+ classifyWorkflowDrift: async () => {
1043
+ const probeDeps = {
1044
+ runCommand: startTicketsDeps.runCommand,
1045
+ cwd: params.cwd,
1046
+ };
1047
+ const baseRef = await resolveCurrentBranch(probeDeps);
1048
+ const defaultRef = await resolveRepositoryDefaultBranch(probeDeps);
1049
+ return probeClaudeReviewWorkflowDrift(probeDeps, {
1050
+ baseRef: baseRef ?? "",
1051
+ defaultRef,
1052
+ });
1053
+ },
1025
1054
  // BAPI-775: the SAME project root the rest of installation resolves
1026
1055
  // against, and the SAME read-only host-config inspector the
1027
1056
  // tool-visibility phase reports from — so the doctor's profile-token
@@ -1242,6 +1271,28 @@ function buildDoctorServiceStateInspector(deps) {
1242
1271
  return inspection.state;
1243
1272
  };
1244
1273
  }
1274
+ /**
1275
+ * Internal marker carrying credential PROVENANCE — never a credential VALUE —
1276
+ * across the `install` self-re-exec (BAPI-931).
1277
+ *
1278
+ * `prepareInstallReexecArguments` lifts `--api-key <value>` out of child argv into
1279
+ * `BAPI_API_KEY` so the secret stays out of `ps`. Without this marker the child
1280
+ * then resolves from its environment and attributes a 401 to "the BAPI_API_KEY
1281
+ * environment variable" — naming a variable the operator never set, at the exact
1282
+ * moment their install is failing. That regressed BAPI-668 R11 for every user
1283
+ * whose installed copy is behind `@latest`, which is the common case for the
1284
+ * documented unpinned `npx … install`.
1285
+ *
1286
+ * NOT a public interface. It is undocumented, set only by this package's own
1287
+ * re-exec alongside `BAPI_API_KEY`, read only by {@link resolveApiKey}, and
1288
+ * stripped by {@link sanitizePrewarmEnv}. Its vocabulary is CLOSED: only the
1289
+ * exact string `"flag"` relabels; anything else — absent, blank, misspelled, or
1290
+ * hand-set — falls through to the ordinary `env` attribution, so a malformed or
1291
+ * forged value can never produce a source that is not one of the three real ones.
1292
+ */
1293
+ export const INSTALL_REEXEC_KEY_SOURCE_ENV = "BAPI_INTERNAL_KEY_SOURCE";
1294
+ /** The only value {@link INSTALL_REEXEC_KEY_SOURCE_ENV} may carry. */
1295
+ export const INSTALL_REEXEC_KEY_SOURCE_FLAG = "flag";
1245
1296
  /**
1246
1297
  * Resolve the ordinary credential entry: `--api-key` → `BAPI_API_KEY` env →
1247
1298
  * interactive no-echo prompt. Fails (secret-free) when none is available and
@@ -1264,7 +1315,14 @@ export async function resolveApiKey(options, deps) {
1264
1315
  }
1265
1316
  const fromEnv = deps.env.BAPI_API_KEY;
1266
1317
  if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
1267
- return { ok: true, value: fromEnv.trim(), source: "env" };
1318
+ // BAPI-931: in a re-exec'd child the flag was lifted into this env var to keep
1319
+ // it out of `ps`, so "env" would name a variable the operator never set. The
1320
+ // marker restores the truth. Strict equality against the closed vocabulary —
1321
+ // any other value keeps today's `env` attribution byte-for-byte.
1322
+ const source = deps.env[INSTALL_REEXEC_KEY_SOURCE_ENV] === INSTALL_REEXEC_KEY_SOURCE_FLAG
1323
+ ? "flag"
1324
+ : "env";
1325
+ return { ok: true, value: fromEnv.trim(), source };
1268
1326
  }
1269
1327
  if (deps.isTTY && deps.promptSecret) {
1270
1328
  // BAPI-708 (A-3): the prompt no longer asks the user to pre-classify their own
@@ -34,12 +34,20 @@
34
34
  * the child down the bare-onboarding branch instead. The failure diagnostic is a
35
35
  * fixed string — it interpolates no argv, no environment value, and no exception
36
36
  * text.
37
+ *
38
+ * PROVENANCE SURVIVES THE HAND-OFF (BAPI-931). Lifting `--api-key` into the
39
+ * environment destroys the evidence of which input the operator actually used, so
40
+ * the child reported a 401 as coming from "the BAPI_API_KEY environment variable"
41
+ * — naming a variable nobody set. A separate `provenanceEnv` overlay carries the
42
+ * LABEL `flag` alongside the secret so the child can attribute the failure
43
+ * truthfully. It is a label, never a value, and it is kept out of `secretEnv`
44
+ * precisely so that overlay's "never logged" contract stays true of every member.
37
45
  */
38
46
  import { spawn } from "child_process";
39
47
  import { VERSION } from "./version.generated.js";
40
48
  import { isNewerVersion } from "./update-check.js";
41
49
  import { fetchLatestVersion } from "./cli-release.js";
42
- import { runInstallBridgeCli } from "./install-bridge.js";
50
+ import { runInstallBridgeCli, INSTALL_REEXEC_KEY_SOURCE_ENV, INSTALL_REEXEC_KEY_SOURCE_FLAG, } from "./install-bridge.js";
43
51
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
44
52
  /** The sentinel that marks an already-re-exec'd child. Matches `upgrade-cli.ts`. */
45
53
  export const INSTALL_REEXEC_SENTINEL = "--internal-reexec";
@@ -91,6 +99,7 @@ export function stripInternalReexecSentinels(argv) {
91
99
  export function prepareInstallReexecArguments(argv) {
92
100
  const forwardedArgs = [];
93
101
  const secretEnv = {};
102
+ const provenanceEnv = {};
94
103
  for (let i = 0; i < argv.length; i++) {
95
104
  const arg = argv[i];
96
105
  if (arg === "--api-key" || arg.startsWith("--api-key=")) {
@@ -109,6 +118,11 @@ export function prepareInstallReexecArguments(argv) {
109
118
  continue;
110
119
  }
111
120
  secretEnv.BAPI_API_KEY = value;
121
+ // BAPI-931: record that the FLAG was the real source. Only set here,
122
+ // beside the lift that destroys the argv evidence — never for --invite
123
+ // (no 401 attribution on that path) and never for a malformed --api-key
124
+ // (handed to the installer's parser above, so nothing was lifted).
125
+ provenanceEnv[INSTALL_REEXEC_KEY_SOURCE_ENV] = INSTALL_REEXEC_KEY_SOURCE_FLAG;
112
126
  if (consumedNext)
113
127
  i += 1;
114
128
  continue;
@@ -137,7 +151,7 @@ export function prepareInstallReexecArguments(argv) {
137
151
  }
138
152
  forwardedArgs.push(arg);
139
153
  }
140
- return { forwardedArgs, secretEnv };
154
+ return { forwardedArgs, secretEnv, provenanceEnv };
141
155
  }
142
156
  /**
143
157
  * The public `install` / legacy `install-bridge` entry point.
@@ -164,11 +178,33 @@ export async function runInstallBridgeWithLatestCli(argv, deps = {}) {
164
178
  const errorLog = deps.errorLog ?? ((message) => console.error(message));
165
179
  const localVersion = deps.localVersion ?? VERSION;
166
180
  const { args: cleanedArgs, sentinelPresent } = stripInternalReexecSentinels(argv);
167
- // Terminating condition, the nested-conductor bypass, and `--dry-run`
168
- // (BAPI-818, R2) all skip the registry entirely. A dry run's documented
169
- // contract is "no network" — a version-freshness lookup is still a network
170
- // call, so it cannot run first even though it is fail-open and unauthenticated.
171
- if (sentinelPresent || cleanedArgs[0] === "conductor" || cleanedArgs.includes("--dry-run")) {
181
+ // Terminating condition, the nested-conductor bypass, `--dry-run`
182
+ // (BAPI-818, R2), and help (BAPI-952) all skip the registry entirely. A dry
183
+ // run's documented contract is "no network" — a version-freshness lookup is
184
+ // still a network call, so it cannot run first even though it is fail-open
185
+ // and unauthenticated.
186
+ //
187
+ // HELP IS NOT AN INSTALL (BAPI-952). `-h`/`--help` short-circuits inside
188
+ // `runInstallBridgeCli` (`install-bridge.ts`, `parsed.status === "help"`)
189
+ // having initialized nothing, so a registry lookup on that path buys nothing
190
+ // and costs two live resources: an undici socket from `fetchLatestVersion`'s
191
+ // `fetch` and the timer behind its `AbortSignal.timeout`. `dispatchCliSubcommand`
192
+ // returns straight into `process.exit(cliExitCode)` in `index.ts`, so both were
193
+ // still open at teardown. On `windows-latest` that killed the process with
194
+ // `3221226505` (`0xC0000409`, STATUS_STACK_BUFFER_OVERRUN) AFTER the complete,
195
+ // correct help text had already been printed — the reported symptom exactly.
196
+ // It also made `install --help` need the network to answer, which no help
197
+ // screen should.
198
+ //
199
+ // The predicate MUST stay identical to `parseInstallBridgeArgs`'s own help
200
+ // detection (`argv.includes("-h") || argv.includes("--help")`). If this test
201
+ // were narrower the two would disagree and a genuine install could skip the
202
+ // freshness check; if it were wider, help would still pay for the lookup.
203
+ const isHelpInvocation = cleanedArgs.includes("-h") || cleanedArgs.includes("--help");
204
+ if (sentinelPresent ||
205
+ cleanedArgs[0] === "conductor" ||
206
+ cleanedArgs.includes("--dry-run") ||
207
+ isHelpInvocation) {
172
208
  return runLocal(cleanedArgs);
173
209
  }
174
210
  // Default the comparison target to the local version: an unusable registry
@@ -186,7 +222,7 @@ export async function runInstallBridgeWithLatestCli(argv, deps = {}) {
186
222
  if (!isNewerVersion(localVersion, latestVersion)) {
187
223
  return runLocal(cleanedArgs);
188
224
  }
189
- const { forwardedArgs, secretEnv } = prepareInstallReexecArguments(cleanedArgs);
225
+ const { forwardedArgs, secretEnv, provenanceEnv } = prepareInstallReexecArguments(cleanedArgs);
190
226
  const npxCmd = platform === "win32" ? "npx.cmd" : "npx";
191
227
  const childArgs = [
192
228
  "-y",
@@ -214,7 +250,9 @@ export async function runInstallBridgeWithLatestCli(argv, deps = {}) {
214
250
  stdio: "inherit",
215
251
  cwd,
216
252
  // Explicit CLI values win over an inherited value of the same key.
217
- env: { ...env, ...secretEnv },
253
+ // The provenance label rides alongside so the child can attribute a 401
254
+ // to the flag the operator actually used (BAPI-931).
255
+ env: { ...env, ...secretEnv, ...provenanceEnv },
218
256
  });
219
257
  }
220
258
  catch {
@@ -778,6 +778,11 @@ export const PIPELINES = {
778
778
  "instruction_file": "explore-epic-subtasks.md",
779
779
  "description": "Perform focused exploration for each sub-task"
780
780
  },
781
+ {
782
+ "type": "agent_task",
783
+ "instruction_file": "assess-conductor-eligibility.md",
784
+ "description": "Assessing conductor eligibility"
785
+ },
781
786
  {
782
787
  "type": "agent_task",
783
788
  "instruction_file": "write-epic-summary.md",
@@ -857,6 +862,7 @@ export const PIPELINES = {
857
862
  }
858
863
  };
859
864
  export const INSTRUCTIONS = {
865
+ "assess-conductor-eligibility.md": "Assess conductor merge/review eligibility for the frozen epic child set.\n\n## Instructions\n\n1. Read the approved decomposition from `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md` to\n obtain each sub-task's frozen title and scope. Do NOT re-split or rescope children here —\n the decomposition is frozen; this stage only classifies it.\n\n2. For each sub-task, also read its exploration document\n `{docs_dir}/epic-plans/{epic_slug}/explorations/NN-{subtask-slug}.md` (written by\n `explore-epic-subtasks`) if it exists. Its Context, Relevant Code, and Recommendation\n sections are the sub-task's most complete available text — the closest thing to a\n description/requirements body that exists at this stage, since no Jira ticket has been\n created yet.\n\n3. Build one ordered child list, preserving decomposition order, where each child carries:\n - `id`: the sub-task's placeholder identifier (its position, e.g. `\"1\"`, or a slug — the\n real Jira key does not exist yet).\n - `title`: the sub-task title from `epic-plan.md`.\n - `scope`: the sub-task's Scope field from `epic-plan.md`.\n - `description`/`requirements`: the sub-task's exploration document content, when one\n exists; omit when it does not (missing text is treated as empty, never invented).\n\n4. Call the `assess_epic_conductor_eligibility` MCP tool exactly once with the complete\n ordered child list from step 3. Never call it per-child and never call it with a partial\n subset — a partial call cannot produce a meaningful epic-wide count.\n\n5. Write the tool's result verbatim as a structured JSON artifact to\n `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json`. The artifact's shape mirrors\n the tool's own result type:\n - On `\"status\": \"assessed\"`: `totalChildren`, `predictedWorkflowChildren`, `reason`,\n `reviewSubsetChildren`, and `affectedChildren` (each with `id`, `title`, `matchedPaths`,\n `requiresHandReview`).\n - On `\"status\": \"unavailable\"`: exactly that status, and nothing else. **Never** substitute\n zero counts or an empty `affectedChildren` list for an unavailable assessment — an\n unavailable result and a genuine zero-workflow-children result are different facts, and\n collapsing them into the same shape is exactly the failure this stage exists to prevent.\n\n6. This stage performs no other action. It does not start, dispatch, or spawn any worker, and\n it makes no Jira call — it is read frozen inputs, classify, write one artifact.\n\n## Return\n\nConfirm the artifact was written to `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json`\nand report its status: either the assessed counts (`N of M` children predicted to modify\nworkflow files, `K of those N` predicted to require hand review) or that the assessment was\nunavailable.\n",
860
866
  "assess-epic-research-needs.md": "Analyze the epic description and build a structured research plan.\n\n## Epic Description\n\n{epic_description}\n\n## Instructions\n\n1. Create the directory structure for this epic's artifacts:\n ```\n mkdir -p {docs_dir}/epic-plans/{epic_slug}\n ```\n\n2. Analyze the epic description above. Determine what external knowledge is required to plan this epic effectively. Consider:\n - Unfamiliar technologies, libraries, or frameworks mentioned\n - API documentation or integration specs that need to be consulted\n - Best practices or architectural patterns that require research\n - Domain-specific knowledge gaps\n\n3. Decide on a **Research Mode**:\n - **deep**: Use when the epic involves large, multi-faceted unknowns requiring synthesis from multiple sources (e.g., \"best practices for implementing WebSocket connection pooling in Python asyncio\").\n - **web**: Use for quick factual lookups — library API signatures, configuration syntax, small \"how to\" questions.\n - **none**: Use when the codebase exploration alone will provide sufficient context and no external knowledge is needed.\n\n4. Write a structured research plan to `{docs_dir}/epic-plans/{epic_slug}/research-plan.md` with these sections:\n\n```markdown\n# Research Plan\n\n## Research Mode\n{deep | web | none}\n\n## Deep Research Query\n{If mode is \"deep\": a single, well-crafted query for the deep research tool. Otherwise: \"N/A\"}\n\n## Web Search Topics\n{If mode is \"web\" or as fallback topics for \"deep\": a numbered list of specific search topics. Otherwise: \"N/A\"}\n\n## Rationale\n{Brief explanation of why this research mode was chosen and what knowledge gaps it addresses.}\n```\n\n## Return\n\nConfirm the research plan was written to `{docs_dir}/epic-plans/{epic_slug}/research-plan.md` and report the chosen Research Mode (`deep`, `web`, or `none`) along with a one-line rationale.\n",
861
867
  "capture-review-decisions.md": "Capture user decisions on review findings for {ticket_key} using the HTML decision page, then interpretively rewrite the clarifying questions and critique docs and upload both to Jira.\n\n## Step 1: Read source documents\n\nRead the combined review-and-resolution file:\n- `{docs_dir}/review/{ticket_key}-review-and-resolution.md`\n\nIf the file does not exist or is unreadable, stop and report: \"Combined review-and-resolution file not found or unreadable. Run the earlier pipeline steps first.\"\n\nThe combined file existing but containing no actionable items (empty `Needs Scrutiny` and `Open Questions` sections) is **not** a failure condition — Step 4 handles the no-decisions-needed flow gracefully when `generate_decision_page` is called with empty `actionable_items`.\n\n## Step 2: Map evaluation items to decision page input\n\nTransform the combined review-and-resolution document into `generate_decision_page` JSON input using these mapping rules:\n\n| Evaluation Section | JSON Field | Mapping Rule |\n|---|---|---|\n| Open Questions | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, combined `**Assessment**` paragraph and `**Codebase Evidence**` bullet list → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Needs Scrutiny | `actionable_items` | E-item title → `question`, `**Source**` → `source`, `**Original question**` → `original_question`, `**Why it matters**` → `why_it_matters`, decision tree branch labels → `options` (string array, labels only), `**Option consequences**` (parallel to branches) → `option_consequences`, `**Recommendation explanation**` → `recommendation_explanation`, combined `**Assessment**` paragraph and `**Codebase Evidence**` bullet list → `codebase_evidence`, `**Recommendation Index**` → `recommendation_index` |\n| Confirmed Improvements | `clear_improvements` | E-item title → `title`, confidence tag → `confidence`, recommended action → `action`, `**Source**` from the combined file → `source` |\n\n**Important**: The `original_question`, `why_it_matters`, `option_consequences`, `recommendation_explanation`, and the collapsed `codebase_evidence` block together replace the old single `context` blob. Each clarity field guides a different facet of the user's decision: `original_question` reminds the reviewer what was asked, `why_it_matters` frames the impact, `option_consequences` describe the behavioral outcome of each branch, `recommendation_explanation` motivates the recommended branch, and the closed-by-default `codebase_evidence` block surfaces the Assessment + file:line citations on demand without overwhelming the card.\n\nFor each actionable item, the `options` array is a list of plain label strings extracted from the combined file's decision tree branches. The tool auto-generates value keys (`opt-0`, `opt-1`, etc.) and auto-appends a \"None of these\" option. Do not generate value keys yourself.\n\n## Step 2.5: Auto-approve fast path\n\nFor this run, `auto_approve` = `{auto_approve}`.\n\nIf `auto_approve` is `true` and Step 2 produced at least one actionable item, skip Steps 3–6 entirely and synthesize the commit JSON directly:\n\n- `ticket_key`: `{ticket_key}`\n- `general_comment`: `\"\"`\n- `decisions`: an object keyed by each `actionable_items[*].id` from Step 2's mapped input. For each item:\n - If `recommendation_index` is a non-negative integer within range of `options`: `choice = \"opt-\" + recommendation_index`, `chosen_label = options[recommendation_index]`, `comment = \"\"`, `source` copied from the item.\n - Otherwise (missing, null, or out of range): `choice = \"opt-0\"`, `chosen_label = options[0]`, `comment = \"\"`, `source` copied. Never emit `\"none\"` and never emit `\"ask\"`.\n\nPost a single chat acknowledgement listing each auto-approved item ID and chosen label, then proceed directly to Step 7 with the synthesized JSON. Step 7's \"Hard rule\" about resolving `ask` items does not apply because no item carries `choice === \"ask\"`.\n\nThe synthesized settled decisions still receive the implications review described under Step 6's \"Implications review and proceed gate\" before Step 7 runs — literal `auto_approve = true` only skips the human proceed gate, not the review itself. \"Skip Steps 3–6\" above means skipping their interactive portions (rendering the page, waiting on chat, the Q&A loop); it does not exempt this fast path from the review obligation.\n\nIf Step 2 produced zero actionable items, fall through to Step 3 — Step 4's existing `no_decisions_needed` branch handles the empty case correctly.\n\nOtherwise (any value of `auto_approve` other than the literal `true` — including empty, `false`, or missing), proceed to Step 3.\n\n## Step 3: Call the MCP tool\n\nCall `generate_decision_page` with `ticket_key` at the root and the review arrays nested under `content`:\n\n**Always pass `content`, even when both arrays are empty.** Send `\"content\": { \"actionable_items\": [], \"clear_improvements\": [] }` rather than omitting the key — that is what reaches the `no_decisions_needed` branch Step 2 relies on. Omitting `content` entirely is rejected with a `VALIDATION_ERROR`, because root-level arrays are silently dropped by the tool's lean input schema and a missing wrapper is far more often a mistake than a deliberate empty call.\n\n```typescript\ninterface ReviewDecisionsContent {\n actionable_items?: Array<{\n id: string;\n question: string;\n why_it_matters: string; // required — concrete one-sentence impact\n recommendation_explanation: string; // required — why the recommended branch is best\n options: string[]; // 2-4 option labels\n option_consequences: string[]; // same length as options\n recommendation_index: number; // 0-based index into options\n original_question?: string; // optional display field\n codebase_evidence?: string; // optional display field — assessment + file:line\n source?: string; // optional source reference\n }>;\n clear_improvements?: Array<{\n id: string;\n title: string;\n action: string;\n confidence: string;\n source: string; // required for clear_improvements\n }>;\n}\n```\n\nExample call:\n```json\n{\n \"ticket_key\": \"{ticket_key}\",\n \"content\": {\n \"actionable_items\": [\n {\n \"id\": \"E-1\",\n \"question\": \"Should we add a configurable timeout?\",\n \"why_it_matters\": \"Timeout behavior affects retry paths and user-visible latency.\",\n \"recommendation_explanation\": \"Configurable matches existing latency-branching code.\",\n \"options\": [\"Keep existing\", \"Add configurable timeout\"],\n \"option_consequences\": [\"No new work.\", \"Implementers add config + tests.\"],\n \"recommendation_index\": 1,\n \"original_question\": \"Does the ticket specify timeout behavior?\",\n \"source\": \"Clarifying Q1\"\n }\n ],\n \"clear_improvements\": [\n { \"id\": \"ci-1\", \"title\": \"Tidy logging\", \"action\": \"Use the logger.\", \"confidence\": \"high\", \"source\": \"Eval 1\" }\n ]\n }\n}\n```\n\n## Step 4: Check tool response\n\nThe tool returns a JSON response with a `status` field:\n- If `status` is `\"no_decisions_needed\"`: skip Steps 5, 6, 7, and 8 entirely. Output a success message: \"No actionable review decisions needed — skipping doc rewrite and upload.\" This covers both the case where every item was confirmed as a Confirmed Improvement and the case where no items were emitted (e.g., both upstream source documents were absent).\n- If `status` is `\"decision_page_generated\"`: continue to Step 5. The response includes `file_path`.\n\n## Step 5: Direct user to the decision page\n\nTell the user to open the generated HTML file in their browser. Provide the `file_path` from the tool response. Then say to the user, verbatim: `Open the page. For any item you're unsure about, choose \"Ask about this\" — when you submit, I'll talk through those before we proceed. You can also ask me questions in chat before submitting if you prefer.`\n\nThis step only directs the user to the page and explains the two allowed next actions (submit selections, or ask questions first). Do not describe Step 7's rewrite semantics here; that belongs to the rewrite step.\n\n## Step 6: Q&A loop and commit signal\n\nEnter an open-ended Q&A loop. There is no turn cap — the user may ask any number of questions in any number of turns. Do not stop and wait silently; engage with each user message as either a commit signal or a discussion turn.\n\n### Proceed signal (commit)\n\nTrim the full user message and attempt to parse the entire trimmed message as JSON. The message is a commit only when the parsed value is an object with all three of these top-level fields:\n\n- `ticket_key` — must be a string\n- `decisions` — must be an object\n- `general_comment` — must be a string\n\nThe first valid commit-shaped JSON paste commits immediately. Proceed to Step 7 without prompting for additional confirmation. Any combination of `decisions` keys is accepted (the page may submit a partial set if the user only resolved some items conversationally). Do not over-validate the per-card fields beyond the top-level commit-shape check — the page guarantees the per-card schema, and over-validating risks rejecting valid pastes if the page schema evolves.\n\n### Discussion signal (Q&A turn)\n\nAnything that is not commit-shaped JSON is a discussion turn. This includes:\n\n- Freeform questions (with or without other text).\n- Questions pasted alongside other text or alongside JSON.\n- Malformed JSON (parse failure).\n- Well-formed JSON missing one or more of the required top-level keys (`ticket_key`, `decisions`, `general_comment`).\n\nFor JSON-shaped input that is missing required top-level fields, call this out in the reply — explain which fields are missing and ask whether the user intended to submit or share partial state — rather than silently treating it as a freeform question.\n\nAnswer discussion turns using these sources, in priority order:\n\n1. The combined `{ticket_key}-review-and-resolution.md` file already read in Step 1.\n2. The original `{ticket_key}-clarifying-questions.md` and `{ticket_key}-ticket-quality-critique.md` documents.\n3. Codebase lookups when the question requires verifying current code state.\n\nFallback: if running on a pre-PR1 branch where the combined review-and-resolution document does not exist, use the pre-PR1 `{ticket_key}-review-evaluation.md` and `{ticket_key}-resolution-guide.md` pair in its place.\n\nFor plain freeform questions, infer the item from chat context when possible.\n\n### In-flight decision state\n\nDuring the Q&A loop, maintain in-flight JSON state — agent-owned working memory representing the user's current intent for `decisions` and `general_comment`. This in-flight JSON state lives only in the agent's working memory for the duration of the loop; do not persist it server-side.\n\n- When the user clearly changes their mind about an item, chooses an option conversationally with reasonably explicit decision language (\"choose option B for E-3\", \"go with the configurable timeout\", \"change E-7 to None of these\"), or gives new overarching guidance, record that as an in-flight override.\n- Ambiguous preference language (\"I'm leaning toward...\", \"maybe option B is fine\") should be discussed but not recorded as an override unless the user gives reasonably explicit decision language.\n- `general_comment` may be updated in the in-flight state when the user gives overarching guidance during Q&A.\n- The page's general-comment textarea is preserved unchanged. Do not modify the page DOM during Q&A; the user can still fill the textarea before submitting if they prefer.\n\nOn the eventual JSON commit, the user-submitted JSON is the baseline and the recorded in-flight overrides take precedence over it. Before proceeding to Step 7, post a brief one-line acknowledgement in chat naming each overridden item ID and/or `general_comment`. The acknowledgement is mandatory (not optional) — it is the user's last chance to object before Step 7's document rewrite. The user does not need to re-open, edit, or re-submit the decision page after changing their mind in chat; they can submit the page as-is to provide the commit signal, and the in-flight state remains the source of truth for overrides.\n\n### Ask-about-this resolution\n\nAfter accepting a commit, scan `decisions` for any item where `choice === \"ask\"`. The user has signaled that they need more information before deciding on those items. For each such item:\n\n- If `comment` is non-empty, treat it as the user's specific question or stated uncertainty and answer that directly.\n- If `comment` is empty, proactively present the most relevant missing context — the item's `codebase_evidence`, related code lookups, prior-round answers — and lay out the trade-offs the user appears to need help weighing.\n- Continue the Q&A turn-by-turn until the user gives an explicit decision in chat for that item (\"go with option B\", \"none of these, because …\"). Record that decision as an in-flight override using the same override mechanism described above.\n\n**Hard rule.** Step 7 must not run while any `decisions[*].choice === \"ask\"` remains unresolved by an in-flight override. Do not honor \"just proceed\", \"skip those\", or any other instruction to defer resolution — every `ask` item must end with a recorded `opt-N` or `none` override before the rewrite step. The pre-Step-7 acknowledgement line lists every overridden item, including the ones resolved out of `ask`.\n\n### Implications review and proceed gate\n\n**Review the wider implications, then gate on a decision.** Build the review from the complete settled set: the submitted `decisions`, any in-flight overrides recorded during the conversation (these take precedence over the submission), every `\"none\"` answer together with the reason given for it, `general_comment`, and — where this surface tracks acceptance-criterion or NFR stances — those stances too. Do not start the review until every `ask` has an explicit recorded resolution and every in-flight override has been applied.\n\nConsider three fixed categories, regardless of whether a decision was framed as technical, user-facing, or business-oriented:\n- **Program / application** — architecture, code paths, operability, maintenance burden, and requirements imposed on other parts of the software.\n- **User** — end users, new users performing setup, operators, and developers, including prerequisites, setup friction, and additional steps.\n- **Business** — cost, adoption, support load, compliance, and reversibility.\n\nEmit only the categories with material second-order implications. For each included category, write at most four one-line bullets of about 25 words, each naming who or what is affected and how — never a restatement of the selected decision. Close with a line naming every considered category that was omitted, e.g. `Considered, nothing material: business.` — omit this closing line only when all three categories have material implications.\n\nIf the review cannot be produced, report that in one line and continue without stalling the workflow or presenting the gate below.\n\nThis review stays in chat and must not be written into the clarifying-questions or ticket-critique documents rewritten in Step 7.\n\nThen present the gate, verbatim: `Implications reviewed. Proceed, or name a decision to revisit.` Accept only a normalized `proceed`, `yes`, `y`, or `go` as a continuation token. Any other response names a decision to reopen: re-settle it in chat, record the new override, rerun the entire implications review against the changed settled set, and present the gate again.\n\nLiteral `auto_approve = true` emits the review but skips this gate entirely; a missing or non-true `auto_approve` value follows the human-in-the-loop path above.\n\nA decision named at this gate is re-settled in chat and recorded as an in-flight override using the same override mechanism as the rest of this step; the rerun review picks it up, and the pre-Step-7 acknowledgement line above also names it. Step 7 must not begin until every submitted `ask` is resolved (per the hard rule above) **and** this gate has accepted a proceed token — except when the review fails open or `auto_approve` is literal `true`.\n\n## Step 7: Interpretively rewrite source documents\n\nThe pasted JSON contains a `decisions` object keyed by item ID. Each decision includes `source`, `choice`, `chosen_label`, and `comment`. Use these fields to locate and rewrite the corresponding sections in:\n- `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n- `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n\nAfter a second-opinion run, each document has this shape:\n\n- A top-level H1 (`# Ticket Analysis` or `# Ticket Quality Critique`) followed by an italic provider-attribution line `_This analysis was generated by GPT|Claude|Gemini._` naming the first-round LLM family. **Preserve this attribution line verbatim** — do not move, edit, or remove it during the rewrite step.\n- The first-round questions / critique items, exactly as written by the first-round model.\n- **Inline second-opinion blockquotes** (`> **Second opinion (<provider>) - concurrence|refinement|disagreement.** ... > *Citations: ...*`) nested directly under each prior item the second round addressed. The `(<provider>)` parenthetical is the second-round LLM family (`GPT|Claude|Gemini`). Items the second round did not comment on have no blockquote — that is the \"weak concurrence\" signal.\n- A **`## New in Second Opinion`** tail block listing items the second round added on top of the first round. Immediately under the H2 there is a second italic attribution line `_These additional points were raised by GPT|Claude|Gemini._` naming the second-round family — **also preserve this verbatim**. Then agent-specific sub-headings:\n - Clarifier docs: `### New Requirements Questions` / `### New Technical Questions` (numbering continues from the prior section).\n - Critique docs: `### New Requested Changes` / `### New Points to Consider` (numbering continues from the prior section).\n- A final **`## Second Opinion Summary`** footer (1-3 sentences). **This footer must be preserved verbatim** — it is the canonical record of the second round's overall position and should not be edited.\n\nThe `source` field on each decision tells you where the item lives:\n\n- `Clarifying Q3 (prior round, weak concurrence)` → the prior section, no inline blockquote. Rewrite the prior item's answer.\n- `Clarifying Q9 (prior round, concurrence inline)` → the prior section, prior item carries an explicit `concurrence` blockquote. Rewrite the prior answer; the blockquote can be removed once the answer absorbs the resolution.\n- `Clarifying Q3 (prior round, refinement inline)` / `(prior round, disagreement inline)` → the prior section, prior item carries an explicit `refinement` or `disagreement` blockquote. Rewrite the prior answer to reconcile the dispute, then handle the blockquote per the rule below.\n- `Clarifying Q11 (new in second opinion → New Requirements Questions)` → the `## New in Second Opinion > ### New Requirements Questions` sub-section. Rewrite the item in place inside that sub-section, not at the top of the prior analysis.\n- Equivalent forms for critique items: `Critique: Requested Change 2 (prior round, refinement inline)`, `Critique: Points to Consider N+1 (new in second opinion → New Points to Consider)`, etc.\n\n**Legacy fallback shape**: if the document instead ends with `\\n\\n---\\n\\n` followed by a `## Second Opinion` section (because the JSON pipeline fell back), apply decisions to the equivalent location: `### Response to Prior Items` for inline-style responses, `### Additional Points > New X` for tail-style new items. Preserve the `\\n\\n---\\n\\n` separator and the `## Second Opinion` heading verbatim.\n\nApply the decision to the item in its home location. Then apply the decision:\n\n### Actionable item decisions\n\n- **Selected option** (`choice` is `opt-N`): Add `**Review Decision**: Accepted. <chosen_label>.` to the corresponding section. Integrate the selected direction into the section text so it reads as a final recommendation or resolved answer.\n- **None of these** (`choice` is `none`): Add `**Review Decision**: Rejected — none of the proposed options accepted.` Include the user's `comment` explaining why. Rewrite the section to reflect this decision.\n\nFor actionable items sourced from clarifying questions, rewrite the question's best-guess answer so it reads as the final resolved direction chosen by the reviewer. Do not leave the item framed as an unresolved accept/reject/modify prompt.\n\nFor items sourced from `(prior round, refinement inline)` or `(prior round, disagreement inline)` — disputes of a prior-round item carried in an inline blockquote — the prior-round item is the canonical home: rewrite its answer to absorb the resolution. Then handle the blockquote in one of two ways: (a) remove the blockquote outright if the rewritten answer fully absorbs the second-opinion content, or (b) shorten the blockquote to a single sentence noting the resolution while preserving the `(<provider>)` attribution (e.g. `> **Second opinion (Claude) - refinement.** Resolved by reviewer decision E-N.`). Citations from the original blockquote may be promoted into the rewritten prior-item answer if useful — keep the strongest 1-2 grounding refs.\n\nFor items sourced from `(new in second opinion → ...)` — gap-captured items that received a decision — rewrite the item in place inside its tail-block sub-section (`## New in Second Opinion > ### New X`), not at the top of the prior analysis. Preserve the sub-section heading and continued numbering.\n\n### General comment handling\n\nTreat `general_comment` as overarching guidance that informs the tone and direction of both document rewrites. If it contains specific actionable feedback, weave it into the relevant sections. If it is broad or general, use it as context for how the rewrites should read. Do not create a separate \"General Comment\" or \"Reviewer Notes\" section — the goal is \"final draft\" form.\n\n### Rewrite principles\n\nThe goal is a **final draft** — the documents should read as if they were written with the decisions already made. Do not mechanically append decisions. Instead, lightly rewrite affected sections so they reflect the decisions naturally. Preserve all non-affected sections unchanged. The prior-round content should still read as coherent standalone analysis after integration. Preserve the `## New in Second Opinion` tail block intact for any items that weren't decided. **Always preserve the `## Second Opinion Summary` footer verbatim** — it is the canonical record of the second round's overall position and should not be edited even when individual items it references have been resolved.\n\n## Step 8: Upload to Jira\n\nUpload both updated documents to Jira using `attachment` (operation: `\"upload\"`):\n\n1. Upload clarifying questions:\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/clarifying-questions/{ticket_key}-clarifying-questions.md`\n - `link_type`: `clarifying-questions.md`\n\n2. Upload ticket quality critique:\n - `ticket_number`: `{ticket_key}`\n - `file_path`: `{docs_dir}/ticket-critiques/{ticket_key}-ticket-quality-critique.md`\n - `link_type`: `ticket-quality-critique.md`\n\n## Step 9: Complete\n\nConfirm: \"Review decisions captured and uploaded to {ticket_key}.\"\n\n## Return\n\nConfirm \"Review decisions captured and uploaded to {ticket_key}.\" and list the two attachments uploaded (`{ticket_key}-clarifying-questions.md` and `{ticket_key}-ticket-quality-critique.md`). Note any decisions that could not be applied.\n",
862
868
  "checkpoint-work.md": "Checkpoint the work produced for ticket {ticket_key}.\n\nThis is the **durability boundary**. The production phase has just authored its\nartifacts and they exist only in the worktree. The pre-PR verification phase that\nruns next executes the plan's review steps, its test commands, and — for a\nfrontend ticket — a remediation loop of up to three cycles. That is the long part of\nthe run, and it is exactly where a session runs out of budget.\n\nSo the work is pushed to origin *first*. After this step, a worker that dies mid\nverification has still left its implementation recoverable on a remote ref.\n\nThis step is deliberately narrow. It makes **no branch decision**, opens **no pull\nrequest**, and asks for **no approval**. Branch selection and the pull request belong\nto `commit-and-push.md` and `create-pr.md`, which run later on the same branch. Doing\nany of that here would put a decision — and a possible pause — in front of the very\ndurability guarantee this step exists to provide.\n\n**Execution mode for this run: `{execution_mode}`.** Under `orchestrated`\n(a server-side orchestrator) the checkpoint is returned as a fenced text block\nthat orchestration parses. Under `inline` (`get_pipeline_recipe`) there is no\norchestrator, so the checkpoint is recorded with a tool call instead. Follow the\nbranch that matches wherever the two are named.\n\n---\n\n## Step 1 — Assess the Worktree\n\nRun and note the results:\n\n- `git rev-parse --abbrev-ref HEAD` — the current branch.\n- `git status --porcelain` — everything modified, added, or untracked.\n\n**Use the branch you are on.** Do not create, rename, switch, or select a branch.\n\nStop immediately, reporting the reason, if any of these hold:\n\n- HEAD is detached (`git rev-parse --abbrev-ref HEAD` reports `HEAD`).\n- A merge, rebase, or cherry-pick is in progress.\n\nNeither is a state to commit into, and both need a human.\n\n## Step 2 — Clean Tree\n\nAn empty `git status --porcelain` means there is nothing new to checkpoint. It does\n**not** automatically mean everything is safe: the point of this step is that the work\nis on origin, so prove it rather than assume it.\n\n1. Run `git rev-parse HEAD`.\n2. Run `git ls-remote --heads origin <branch>` and compare the remote tip to HEAD.\n3. If the remote already contains HEAD, the checkpoint is satisfied. Report it and\n return.\n4. If HEAD is not on origin, there are local commits that were never pushed. Push them\n now with `git push origin <branch>` and re-verify.\n\nDo **not** create an empty commit to represent a checkpoint. An empty commit records\nnothing and proves nothing.\n\n## Step 3 — Commit and Push\n\nWhen there are changes to checkpoint:\n\n1. Stage the produced ticket work explicitly with `git add <file1> <file2> ...`. Do not use `git add -A` or `git add .` — a blanket stage sweeps in unrelated\n local files, and this step runs without an approval gate to catch that.\n2. Commit with:\n\n ```\n {ticket_key}: checkpoint produced work before verification\n ```\n\n3. Push the current branch immediately: `git push origin <branch>`. Add `-u` only if\n the branch has no upstream yet.\n\nUse the plain push command — do **not** add `--no-verify`. A Conductor worker already\nreceives `BRIDGE_SKIP_PREPUSH=1` from the executor, so bypassing hooks here is never\nnecessary.\n\n## Step 4 — Prove Durability\n\n1. Run `git rev-parse HEAD` and record the SHA.\n2. Run `git ls-remote --heads origin <branch>` and confirm the remote tip equals that\n SHA.\n\n**Stop the pipeline** and report the failure if the commit fails, the push fails or is\nrejected, or the remote tip does not match HEAD. The phase that follows is the long\none; entering it without durable work is precisely the failure this step prevents.\n\n## Return\n\nRecord the checkpoint the way this run's executor can actually read.\n\n### orchestrated\n\nReturn a machine-readable result as a fenced block tagged `bapi-checkpoint`, followed\nby a one-line human summary:\n\n```bapi-checkpoint\n{\"version\":1,\"branch\":\"<current branch>\",\"sha\":\"<checkpoint HEAD sha>\",\"pushed\":true,\"remoteMatchesHead\":true}\n```\n\nIf nothing needed committing because HEAD was already on origin, report the same shape\nwith the existing SHA and note that no new commit was required.\n\n### inline\n\nCall the `record_checkpoint` tool with `ticket_key` `{ticket_key}`, the `branch` and\n`sha` you verified in Step 4, and `pushed` / `remote_matches_head` set from what you\nactually observed. The tool refuses anything that does not report the work durable on\norigin, which is the point: a checkpoint that is not on the remote is not a checkpoint.\nReport the one-line human summary as well, but **do not emit a fenced `bapi-checkpoint`\nblock** — nothing parses one on this path.\n\nThe same applies when HEAD was already on origin and no new commit was needed: record\nthat existing SHA. The checkpoint is a claim about durability, not about having made a\ncommit.\n\nThe tool call is this step's final action, **not the end of your turn.** When it\nreturns successfully, continue immediately to the next recipe step —\n`execute-plan-verification.md`, the long pre-PR verification phase this checkpoint\nexists to protect.\n",
@@ -893,7 +899,7 @@ export const INSTRUCTIONS = {
893
899
  "learn-style-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for style files, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `style_correctness`\n- **Field name**: `style_correctness_standards`\n- **Scope**: Style files: CSS, SCSS, SASS, LESS, Styled Components, Tailwind configs.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.css`, `**/*.scss`, `**/*.sass`, `**/*.less` (excluding `node_modules/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative style files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/style_correctness_standards.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``style_correctness_standards`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about style-file correctness conventions (structure, naming, methodology), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/style_correctness_standards.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
894
900
  "learn-template-correctness.md": "## Objective\n\nExplore the codebase to identify correctness standards for template files, then draft the corresponding correctness standards document.\n\n## Target Type\n\n- **Type**: `template_correctness`\n- **Field name**: `template_correctness_standards`\n- **Scope**: Template files: HTML, Jinja2, Handlebars, EJS, ERB, Blade, Pug, Twig.\n\n## Instructions\n\n### Phase 1 — Explore Correctness Patterns\n\nFocus on implementation correctness: how to write code that is correct, idiomatic, and robust within this project's conventions.\n\n1. **File Type Detection**: Search by filename pattern for files matching `**/*.html`, `**/*.jinja2`, `**/*.j2` in `templates/` and similar directories (excluding `node_modules/`). If very few or no files exist, note this and draft minimal instructions.\n\n2. **Convention Analysis**: Read 3-5 representative template files to identify:\n - Structure patterns (imports, exports, class structure, function ordering)\n - Naming conventions (variables, functions, classes, files)\n - Framework conventions and idioms\n - Best practices followed\n - Issues and inconsistencies\n\n### Phase 2 — Draft\n\nDraft correctness standards as clear, actionable instructions for an AI code generation agent. Cover:\n- Code structure and organization requirements\n- Naming conventions to follow\n- Framework-specific patterns and idioms\n- Security requirements relevant to this code type\n- Performance considerations\n- Common mistakes to avoid\n- Guards against common AI weaknesses: duplicative code, verbose implementations, security vulnerabilities\n\nWrite the draft to `{docs_dir}/standards/template_correctness_standards.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``template_correctness_standards`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about template-file correctness conventions (structure, naming, framework idioms), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/template_correctness_standards.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
895
901
  "learn-unit-testing.md": "## Objective\n\nExplore the codebase to identify the test runner, assertion library, mocking framework, and testing patterns, then draft `unit_testing_instructions` for the project config.\n\n## Instructions\n\n### Phase 1 — Explore Testing Infrastructure\n\n1. **Test Runner and Framework Detection**: Search for test runner configs (`pytest.ini`, `pyproject.toml` `[tool.pytest]` section, `jest.config.*`) and read `package.json` test scripts. Read the `tests/` directory structure.\n\n2. **Testing Patterns**: Read 3-5 representative test files in `tests/pytest/` to identify:\n - Assertion library and style (`assert`, `expect`, custom matchers)\n - Mocking framework (`unittest.mock`, `jest.mock`, `sinon`, etc.)\n - Fixture patterns (setup/teardown)\n - Test organization (by module, feature, layer)\n - Exemplary tests vs. weak tests\n\n3. **How to Run Tests**: Read `pyproject.toml`, `package.json`, and `Makefile` (if present) to determine exact commands for: full suite, single file, by name pattern, with verbose output.\n\n4. **Mocking vs. Fidelity**: Read test helper files in `tests/pytest/helpers/` to document how external APIs are mocked, whether integration tests exist alongside unit tests, and patterns for avoiding third-party calls in tests.\n\n### Phase 2 — Draft\n\nIf there is nothing repository-specific to cite, say that no repository evidence was found and write a short conservative standard; do not invent files, commands, or conventions.\n\nDraft `unit_testing_instructions` as clear, actionable instructions for an AI agent writing unit tests. Cover:\n- How to run tests (exact commands)\n- Which test framework and assertion library to use\n- How to mock external dependencies without calling third parties\n- How to structure test files and test functions\n- What constitutes a thorough test (not just happy path)\n- How to avoid shallow tests that pass but don't verify meaningful behavior\n- Guards against common AI weaknesses: tests that mock the thing being tested, trivially passing assertions, overly complex setup\n\nWrite the draft to `{docs_dir}/standards/unit_testing_instructions.md`.\n\n## Length Budget\n\nThe platform ceiling for a configuration field is **40,000 characters**, and the server enforces it\nat upload time: a draft over the limit is rejected outright and ``unit_testing_instructions`` is simply not learned. Bound\nthe draft here, at write time, rather than discovering the limit through a failed upload.\n\n1. When the draft is complete, measure its length in **characters** — not tokens, not words, not\n bytes.\n2. If it exceeds 40,000 characters, condense it: remove redundancy, collapse repetitive passages, and\n shorten or drop verbose code examples.\n3. Keep every required section listed above. Never meet the budget by deleting a required section,\n and never truncate the draft mid-sentence.\n4. Re-measure after condensing and repeat until the draft is at most 40,000 characters.\n\n## Return\n\nReturn a brief summary of what was learned about the project's unit testing setup (test runner, assertion library, mocking framework, run commands), citing the key files inspected, and confirm the draft was written to `{docs_dir}/standards/unit_testing_instructions.md`.\n\nAlso report the draft's final length in characters, the applicable maximum (40,000 characters),\nwhether the draft was condensed to meet that maximum (`condensed`: true/false), and — when it was\ncondensed — the reason it needed condensing.\n",
896
- "monitor-ci-checks.md": "Monitor CI checks for the most recent commit. The behavior is dispatched on the repo-specific `ci_followup_config` JSON value: `poll_only`, `fix_and_iterate`, or `custom`. Read this entire file once before doing anything, then follow only the matching branch.\n\n\n**Required-check source**: Both the `poll_only` (Step 5) and `fix_and_iterate` (Step 6) branches gate progression on the *required* check subset, not the aggregate `all_passed` flag. Each check returned by `resolve_ci_checks`/`poll_ci_checks` carries a `required` field (from GitHub Branch Protection, or an LLM classification fallback) — treat `required: false` as non-required (e.g. `pip-audit`) and a missing field or `required: true` as required. This is the tool-provided proxy for the Conductor done-gate's authoritative required-checks set (`mcp_server/src/conductor/done-gate.ts`); do not re-derive required/non-required status in prose.\n\n## Entry state and ownership boundary\n\n**Re-resolve the git context before anything else.** Run `git branch --show-current`\nand `git rev-parse HEAD` when this step begins. In the implement pipeline the pull\nrequest is opened *before* the bounded post-finalization verification phase, and\nthat phase may have pushed a correction on top of the commit the PR was originally\nopened at. The head you monitor must be the branch's current pushed head, not a SHA\ncarried over from PR creation.\n\n**Ownership is split, and the split matters.** The bounded verification phase\n(`verify-plan.md`) owns findings produced by its own local, touched-area commands.\nThis step owns everything subsequently reported by the authoritative `ci` and\n`code_review` gates — failing required checks and requested review changes. Do not\nre-adjudicate the other phase's findings, and do not assume a finding it reported\nhas been fixed unless a pushed commit shows it.\n\n**Do not substitute a broad local run for the authoritative checks.** Running the\nfull local suite here does not establish that CI passed; it duplicates the work the\n`conductor-ci` gate already performs on the pull request, and it is exactly the\nbudget sink this protocol was reordered to avoid. Use the structured CI failure\ndetail from `poll_ci_checks` to target a fix, and scope any local reproduction to\nthe failing area.\n\n**Keep every correction on the existing pull request branch**, and commit *and push*\neach correction before polling resumes. Polling always restarts against the new\npushed head — an unpushed correction is invisible to CI, to review, and to the\nreconciler.\n\nThroughout, report the check and review states you **observed**. Observing that a\ncheck is green is not the same as issuing a verdict: the done-gate evaluation is\nserver-side and the reconciler decides. This step never emits a control signal of\nits own.\n\n## Step 3 — Parse `ci_followup_config`\n\nLook at the response from the immediately preceding `config_field` call (the pipeline step that ran right before this one). The response envelope's `value` field is itself a JSON string and must be parsed again with `JSON.parse` (i.e., the `value` is double-encoded — the outer envelope is JSON, and the inner `value` is a JSON-encoded string of the actual config object).\n\nIf ANY of the following hold, log a warning and use the defaults `{\"strategy\":\"poll_only\",\"max_iterations\":1,\"max_minutes\":10}`:\n\n- The `config_field` response is missing or unavailable (e.g., the step warned-and-continued).\n- The response `value` is `null`.\n- Parsing `value` with `JSON.parse` fails (the persisted text is not valid JSON).\n- The parsed result is not a JSON object.\n- One or more of the required keys (`strategy`, `max_iterations`, `max_minutes`, `instructions`) is missing.\n- `strategy` is not one of `poll_only`, `fix_and_iterate`, or `custom`.\n\n## Step 4 — Dispatch on `strategy`\n\nRead this whole file once and then follow only the matching branch:\n\n- `poll_only` → follow Step 5.\n- `fix_and_iterate` → follow Step 6.\n- `custom` → follow Step 7.\n\nIf `strategy` is unrecognized, log a warning and fall through to Step 5 (`poll_only`).\n\n## Step 5 — `poll_only`\n\nPreserve the baseline polling behavior. The configured `max_minutes` is IGNORED in this branch — `poll_only` always uses the existing 10-minute baseline.\n\n1. Run `git rev-parse HEAD` to get the current commit SHA.\n2. Call the `resolve_ci_checks` tool with `commit_ref` set to that SHA. This discovers and classifies the CI checks for the repository, including each check's `required` field.\n3. Poll CI status by calling `poll_ci_checks` with `commit_ref` set to the same SHA. Check the response for `all_complete`, and note each check's own `required`/green status — do not use the aggregate `all_passed` flag to decide pass/fail (see step 6 below).\n4. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the current branch's pull request. A `mergeable` value of `CONFLICTING` or a `mergeStateStatus` of `DIRTY` means GitHub will not start `pull_request` workflows for that head, so continuing to poll the same SHA is futile — merge the current base branch into your branch, resolve any conflicts, push the result, obtain the new head SHA (`git rev-parse HEAD`), and restart polling from step 1 against that new SHA. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary wait behavior below.\n5. If checks are not yet complete, wait 30 seconds and poll again. Repeat until all checks are complete or 10 minutes have elapsed.\n6. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green. If `required_green` is `true`, report success — non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility only and never flip the Passed/Failed classification.\n7. **Review verdict gating**: if `claude-review` is one of the required checks, its GitHub check reaching a non-pending/\"success\" state means only that the review action *ran* — this is transport completion, not approval. Fetch the PR's comments and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` all mean the review is not yet approved and success is not yet reached. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n8. If any required checks fail, report which required checks failed (and any non-required failures for visibility) and include any available annotations or log details from the poll response. Do NOT attempt to fix failures — just report them clearly.\n9. If CI status is unavailable (resolver/poll returns `available: false`), report unavailable status and exit; do not attempt fixes.\n10. 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). Each returned check carries a `required` field — this is the tool-provided proxy for the done-gate's authoritative required-checks set.\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. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the current branch's pull request. A `mergeable` value of `CONFLICTING` or a `mergeStateStatus` of `DIRTY` means GitHub will not start `pull_request` workflows for that head, so continuing to poll the same SHA is futile — merge the current base branch into your branch, resolve any conflicts, push the result, obtain the new head SHA (`git rev-parse HEAD`), and restart polling from step 1 of this per-iteration block against that new SHA. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary per-iteration behavior below.\n6. If CI status is unavailable (`available: false`), warn and exit the loop — automated remediation cannot make reliable progress without CI signals.\n7. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green; non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility but never gate exit condition 1 below. If `claude-review` is a required check, its GitHub check reaching a non-pending/\"success\" state is transport completion only, not approval — fetch the PR's comments and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` mean the review is not yet approved. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n8. Apply repo-specific `instructions` ONLY when the `instructions` field is non-empty. If the repo `instructions` reference templated placeholder tokens for the GitHub owner, repo, or PR number — e.g., the literal tokens written as a left brace, the word `owner`/`repo`/`pr`, then a right brace — resolve them from the local git/VCS context. Use `gh pr list --head <branch> --json number` to get the PR number; parse the remote URL (`git config --get remote.origin.url`) for owner/repo. If `instructions` is empty, skip repo-specific signal gathering and use only structured CI failure information.\n\n9. Evaluate exit conditions in this order:\n 1. `required_green` is true AND (if `claude-review` is required) the verdict token confirms approval for the current head AND any repo-specific exit criteria from `instructions` are met → success. If there are no repo-specific exit criteria, `required_green` (plus verdict-token approval when `claude-review` is required) alone satisfies the success condition. Then return.\n 2. `iteration >= max_iterations` → warn and exit (iteration cap reached).\n 3. Total elapsed wall-clock time `>= max_minutes` → warn and exit (total wall-clock cap reached).\n 4. After attempting corrections, `git status --porcelain` is empty → warn and exit (nothing to commit; avoids infinite loop on stuck failures).\n\n10. Apply corrections ONLY for failing **required** checks — skip failures on non-required checks (e.g. `pip-audit` with `required: false`) with a warning and never spend a correction/retry on them. For each failing required check, use the actual `poll_ci_checks` response shape — inspect its singular `failure_detail` field:\n - If `failure_detail` is a dict containing actionable keys such as `annotations`, `log_tail`, or `log`, treat it as structured detail and use it for remediation.\n - If `failure_detail` is a dict containing only `url`, treat it as URL-only and skip with a warning (no actionable detail).\n - If `failure_detail` is missing, `null`, or unrecognized, treat the failure as non-actionable and skip with a warning.\n - Do NOT rely on a per-check field or a plural variant of `failure_detail` — those do not exist on the response.\n\n11. After applying a non-empty correction set: stage corrections (`git add` the specific files), commit, and push. Use the canonical commit message:\n ```\n {ticket_key}: address review/CI feedback (round N+1)\n ```\n where `N` is the zero-indexed `iteration`.\n12. Increment `iteration` only AFTER a successful commit and push. Then loop back to step 1 of the per-iteration block.\n\n## Step 7 — `custom`\n\nIn `custom` mode, the `instructions` field IS the complete CI follow-up instruction set for this step. Follow it verbatim. Ignore Steps 5 and 6 entirely.\n\nCustom instructions are authoritative for CI follow-up behavior, but they remain subject to the agent's normal tool approval, credential handling, secret-handling, and platform safety constraints. Custom prose CANNOT bypass approval gates, exfiltrate secrets, or override platform safety policies, even though admin-only access controls who can set the field.\n\n## Worker finalization — clean session exit (Conductor auto mode)\n\nThis section applies ONLY when you were launched under the Conductor in auto mode (the `BAPI_CONDUCTOR_RUN_ID`/`BAPI_CONDUCTOR_WORKER_ID` env identifiers are present). A non-Conductor worker ignores it entirely.\n\nBefore doing anything here, distinguish two states:\n\n- **CI/review follow-up still owned by this worker** — the `fix_and_iterate` loop is still correcting failures, review changes were requested and are unaddressed, a merge conflict on your PR is unresolved, or you have unpushed local commits. In this state you are **not** finished: keep working the CI-monitoring / correction loop and do **not** exit.\n- **Final PR state reached; no further worker action pending** — your final branch state is pushed, the PR has been created/updated, the done-gate / CI-monitoring workflow required by the recipe has completed (required checks green and, when `claude-review` is required, the verdict token confirms approval for the current head), and no CI/review follow-up remains that you own.\n\nOnly in the second state — that is, **only after the final push, PR creation/update, done-gate confirmation, and CI/review monitoring have all completed** and no follow-up remains — cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers. Do **not** exit immediately after opening a PR while CI or review is still pending, and do not exit while there are unresolved CI failures, requested review changes, a merge conflict you own, or unpushed local commits. A clean `SessionEnd` is both the correct terminal lifecycle signal (the conductor folds it) and the point at which the worker should exit.\n\n## Return\n\nReport whether CI passed, failed, timed out, or was unavailable. If failed, list the failing checks with their failure summaries. For `fix_and_iterate`, also report the iteration count and whether iteration/wall-clock caps were hit. If you finalized (cleanly exited) as a Conductor worker, note that the session ended after all follow-up completed.\n",
902
+ "monitor-ci-checks.md": "Monitor CI checks for the most recent commit. The behavior is dispatched on the repo-specific `ci_followup_config` JSON value: `poll_only`, `fix_and_iterate`, or `custom`. Read this entire file once before doing anything, then follow only the matching branch.\n\n\n**Required-check source**: Both the `poll_only` (Step 5) and `fix_and_iterate` (Step 6) branches gate progression on the *required* check subset, not the aggregate `all_passed` flag. Each check returned by `resolve_ci_checks`/`poll_ci_checks` carries a `required` field (from GitHub Branch Protection, or an LLM classification fallback) — treat `required: false` as non-required (e.g. `pip-audit`) and a missing field or `required: true` as required. This is the tool-provided proxy for the Conductor done-gate's authoritative required-checks set (`mcp_server/src/conductor/done-gate.ts`); do not re-derive required/non-required status in prose.\n\n## Entry state and ownership boundary\n\n**Re-resolve the git context before anything else.** Run `git branch --show-current`\nand `git rev-parse HEAD` when this step begins. In the implement pipeline the pull\nrequest is opened *before* the bounded post-finalization verification phase, and\nthat phase may have pushed a correction on top of the commit the PR was originally\nopened at. The head you monitor must be the branch's current pushed head, not a SHA\ncarried over from PR creation.\n\n**Ownership is split, and the split matters.** The bounded verification phase\n(`verify-plan.md`) owns findings produced by its own local, touched-area commands.\nThis step owns everything subsequently reported by the authoritative `ci` and\n`code_review` gates — failing required checks and requested review changes. Do not\nre-adjudicate the other phase's findings, and do not assume a finding it reported\nhas been fixed unless a pushed commit shows it.\n\n**Do not substitute a broad local run for the authoritative checks.** Running the\nfull local suite here does not establish that CI passed; it duplicates the work the\n`conductor-ci` gate already performs on the pull request, and it is exactly the\nbudget sink this protocol was reordered to avoid. Use the structured CI failure\ndetail from `poll_ci_checks` to target a fix, and scope any local reproduction to\nthe failing area.\n\n**Keep every correction on the existing pull request branch**, and commit *and push*\neach correction before polling resumes. Polling always restarts against the new\npushed head — an unpushed correction is invisible to CI, to review, and to the\nreconciler.\n\nThroughout, report the check and review states you **observed**. Observing that a\ncheck is green is not the same as issuing a verdict: the done-gate evaluation is\nserver-side and the reconciler decides. This step never emits a control signal of\nits own.\n\n## Step 3 — Parse `ci_followup_config`\n\nLook at the response from the immediately preceding `config_field` call (the pipeline step that ran right before this one). The response envelope's `value` field is itself a JSON string and must be parsed again with `JSON.parse` (i.e., the `value` is double-encoded — the outer envelope is JSON, and the inner `value` is a JSON-encoded string of the actual config object).\n\nIf ANY of the following hold, log a warning and use the defaults `{\"strategy\":\"poll_only\",\"max_iterations\":1,\"max_minutes\":10}`:\n\n- The `config_field` response is missing or unavailable (e.g., the step warned-and-continued).\n- The response `value` is `null`.\n- Parsing `value` with `JSON.parse` fails (the persisted text is not valid JSON).\n- The parsed result is not a JSON object.\n- One or more of the required keys (`strategy`, `max_iterations`, `max_minutes`, `instructions`) is missing.\n- `strategy` is not one of `poll_only`, `fix_and_iterate`, or `custom`.\n\n## Step 4 — Dispatch on `strategy`\n\nRead this whole file once and then follow only the matching branch:\n\n- `poll_only` → follow Step 5.\n- `fix_and_iterate` → follow Step 6.\n- `custom` → follow Step 7.\n\nIf `strategy` is unrecognized, log a warning and fall through to Step 5 (`poll_only`).\n\n## Step 5 — `poll_only`\n\nPreserve the baseline polling behavior. The configured `max_minutes` is IGNORED in this branch — `poll_only` always uses the existing 10-minute baseline.\n\n1. Run `git rev-parse HEAD` to get the current commit SHA.\n2. Call the `resolve_ci_checks` tool with `commit_ref` set to that SHA. This discovers and classifies the CI checks for the repository, including each check's `required` field.\n3. Poll CI status by calling `poll_ci_checks` with `commit_ref` set to the same SHA. Check the response for `all_complete`, and note each check's own `required`/green status — do not use the aggregate `all_passed` flag to decide pass/fail (see step 6 below).\n4. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the current branch's pull request. A `mergeable` value of `CONFLICTING` or a `mergeStateStatus` of `DIRTY` means GitHub will not start `pull_request` workflows for that head, so continuing to poll the same SHA is futile — merge the current base branch into your branch, resolve any conflicts, push the result, obtain the new head SHA (`git rev-parse HEAD`), and restart polling from step 1 against that new SHA. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary wait behavior below.\n5. If checks are not yet complete, wait 30 seconds and poll again. Repeat until all checks are complete or 10 minutes have elapsed.\n6. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green. If `required_green` is `true`, report success — non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility only and never flip the Passed/Failed classification.\n7. **Review verdict gating**: if `claude-review` is one of the required checks, its GitHub check reaching a non-pending/\"success\" state means only that the review action *ran* — this is transport completion, not approval. Fetch the PR's comments and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` all mean the review is not yet approved and success is not yet reached. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n7a. **A `claude-review` failure blocked by the preflight is TERMINAL — never retry it.** When `claude-review` fails within about twelve seconds and no verdict comment was ever posted, read the run's `Preflight the review action` step. The review action refuses to run whenever this workflow differs from the repository's default-branch copy, and the preflight names which of the two causes it found:\n - `status=workflow_modified` — the pull request's own commits changed `.github/workflows/claude-review.yml`. Such a pull request must be reviewed and merged by hand; it can never be conductor-merged.\n - `status=base_workflow_stale` — the pull request changed no workflow file and **inherited** a stale copy from its base branch. The stale ref is the **base branch**, not the pull request.\n\n Both aggregate to `reason=review_action_blocked_by_workflow_modification`. In both cases the preflight is a deterministic blob-SHA comparison over unchanged inputs, so **retrying, re-running, or continuing to poll reproduces byte-identical evidence and can never succeed** — stop the retry loop and report it rather than spending the remaining budget. For the stale-base case the only remedy is to reconcile the base branch with the default branch; see `docs/claude/runbooks/claude-review-base-branch-drift.md` for detection, diagnosis, and both remediation paths.\n\n8. If any required checks fail, report which required checks failed (and any non-required failures for visibility) and include any available annotations or log details from the poll response. Do NOT attempt to fix failures — just report them clearly.\n9. If CI status is unavailable (resolver/poll returns `available: false`), report unavailable status and exit; do not attempt fixes.\n10. 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). Each returned check carries a `required` field — this is the tool-provided proxy for the done-gate's authoritative required-checks set.\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. **Conflicting-head escalation**: if the poll shows zero check-runs for the current head after roughly two minutes of polling, call `gh pr view --json mergeable,mergeStateStatus` for the current branch's pull request. A `mergeable` value of `CONFLICTING` or a `mergeStateStatus` of `DIRTY` means GitHub will not start `pull_request` workflows for that head, so continuing to poll the same SHA is futile — merge the current base branch into your branch, resolve any conflicts, push the result, obtain the new head SHA (`git rev-parse HEAD`), and restart polling from step 1 of this per-iteration block against that new SHA. When the mergeability response is not `CONFLICTING`/`DIRTY`, zero checks alone is not a CI failure — continue with the ordinary per-iteration behavior below.\n6. If CI status is unavailable (`available: false`), warn and exit the loop — automated remediation cannot make reliable progress without CI signals.\n7. Compute `required_green` = every check with `required: true` (or a missing `required` field) is complete and green; non-required failures (e.g. `pip-audit` with `required: false`) are reported for visibility but never gate exit condition 1 below. If `claude-review` is a required check, its GitHub check reaching a non-pending/\"success\" state is transport completion only, not approval — fetch the PR's comments and confirm the sticky comment contains `claude-review-verdict: approved` on its own line with a `Reviewed-SHA:` line matching the current commit SHA before treating the review as approved; `claude-review-verdict: changes_requested`, a missing verdict, or a stale `Reviewed-SHA:` mean the review is not yet approved. `docs/claude/claude-review-verdict-contract.md` is the authoritative definition of this grammar, of which comment wins when several carry a verdict, and of every fail-closed outcome; consult it rather than re-deriving the rules, and do not restate a different version of them here. A verdict can also be *temporarily* absent because a running review rewrote the sticky comment in place (the vanishing window, contract §7): while a `claude-review` run is still pending, treat absence as transient and keep polling; once it has completed, absence is final and fails closed.\n7a. **A `claude-review` failure blocked by the preflight is TERMINAL — never spend a correction or a retry on it.** When `claude-review` fails within about twelve seconds and no verdict comment was ever posted, read the run's `Preflight the review action` step. The review action refuses to run whenever this workflow differs from the repository's default-branch copy, and the preflight names which of the two causes it found:\n - `status=workflow_modified` — the pull request's own commits changed `.github/workflows/claude-review.yml`. It must be reviewed and merged by hand; it can never be conductor-merged.\n - `status=base_workflow_stale` — the pull request changed no workflow file and **inherited** a stale copy from its base branch. The stale ref is the **base branch**, not the pull request, so no correction you can make on this branch will clear it.\n\n Both aggregate to `reason=review_action_blocked_by_workflow_modification`, and the preflight is a deterministic blob-SHA comparison over unchanged inputs — every iteration reproduces byte-identical evidence. Treat it as non-actionable, exit the loop with that reason reported, and do not consume the remaining iteration or wall-clock budget. Remedy for the stale-base case: reconcile the base branch with the default branch. See `docs/claude/runbooks/claude-review-base-branch-drift.md`.\n\n8. Apply repo-specific `instructions` ONLY when the `instructions` field is non-empty. If the repo `instructions` reference templated placeholder tokens for the GitHub owner, repo, or PR number — e.g., the literal tokens written as a left brace, the word `owner`/`repo`/`pr`, then a right brace — resolve them from the local git/VCS context. Use `gh pr list --head <branch> --json number` to get the PR number; parse the remote URL (`git config --get remote.origin.url`) for owner/repo. If `instructions` is empty, skip repo-specific signal gathering and use only structured CI failure information.\n\n9. Evaluate exit conditions in this order:\n 1. `required_green` is true AND (if `claude-review` is required) the verdict token confirms approval for the current head AND any repo-specific exit criteria from `instructions` are met → success. If there are no repo-specific exit criteria, `required_green` (plus verdict-token approval when `claude-review` is required) alone satisfies the success condition. Then return.\n 2. `iteration >= max_iterations` → warn and exit (iteration cap reached).\n 3. Total elapsed wall-clock time `>= max_minutes` → warn and exit (total wall-clock cap reached).\n 4. After attempting corrections, `git status --porcelain` is empty → warn and exit (nothing to commit; avoids infinite loop on stuck failures).\n\n10. Apply corrections ONLY for failing **required** checks — skip failures on non-required checks (e.g. `pip-audit` with `required: false`) with a warning and never spend a correction/retry on them. For each failing required check, use the actual `poll_ci_checks` response shape — inspect its singular `failure_detail` field:\n - If `failure_detail` is a dict containing actionable keys such as `annotations`, `log_tail`, or `log`, treat it as structured detail and use it for remediation.\n - If `failure_detail` is a dict containing only `url`, treat it as URL-only and skip with a warning (no actionable detail).\n - If `failure_detail` is missing, `null`, or unrecognized, treat the failure as non-actionable and skip with a warning.\n - Do NOT rely on a per-check field or a plural variant of `failure_detail` — those do not exist on the response.\n\n11. After applying a non-empty correction set: stage corrections (`git add` the specific files), commit, and push. Use the canonical commit message:\n ```\n {ticket_key}: address review/CI feedback (round N+1)\n ```\n where `N` is the zero-indexed `iteration`.\n12. Increment `iteration` only AFTER a successful commit and push. Then loop back to step 1 of the per-iteration block.\n\n## Step 7 — `custom`\n\nIn `custom` mode, the `instructions` field IS the complete CI follow-up instruction set for this step. Follow it verbatim. Ignore Steps 5 and 6 entirely.\n\nCustom instructions are authoritative for CI follow-up behavior, but they remain subject to the agent's normal tool approval, credential handling, secret-handling, and platform safety constraints. Custom prose CANNOT bypass approval gates, exfiltrate secrets, or override platform safety policies, even though admin-only access controls who can set the field.\n\n## Worker finalization — clean session exit (Conductor auto mode)\n\nThis section applies ONLY when you were launched under the Conductor in auto mode (the `BAPI_CONDUCTOR_RUN_ID`/`BAPI_CONDUCTOR_WORKER_ID` env identifiers are present). A non-Conductor worker ignores it entirely.\n\nBefore doing anything here, distinguish two states:\n\n- **CI/review follow-up still owned by this worker** — the `fix_and_iterate` loop is still correcting failures, review changes were requested and are unaddressed, a merge conflict on your PR is unresolved, or you have unpushed local commits. In this state you are **not** finished: keep working the CI-monitoring / correction loop and do **not** exit.\n- **Final PR state reached; no further worker action pending** — your final branch state is pushed, the PR has been created/updated, the done-gate / CI-monitoring workflow required by the recipe has completed (required checks green and, when `claude-review` is required, the verdict token confirms approval for the current head), and no CI/review follow-up remains that you own.\n\nOnly in the second state — that is, **only after the final push, PR creation/update, done-gate confirmation, and CI/review monitoring have all completed** and no follow-up remains — cleanly end your worker session (for example by issuing the `/exit` command) so the worktree is released and no idle process lingers. Do **not** exit immediately after opening a PR while CI or review is still pending, and do not exit while there are unresolved CI failures, requested review changes, a merge conflict you own, or unpushed local commits. A clean `SessionEnd` is both the correct terminal lifecycle signal (the conductor folds it) and the point at which the worker should exit.\n\n## Return\n\nReport whether CI passed, failed, timed out, or was unavailable. If failed, list the failing checks with their failure summaries. For `fix_and_iterate`, also report the iteration count and whether iteration/wall-clock caps were hit. If you finalized (cleanly exited) as a Conductor worker, note that the session ended after all follow-up completed.\n",
897
903
  "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",
898
904
  "render-ticket-manifest.md": "Render every entry of an approved ticket manifest into a drafted body, one `jira-ticket-writer` invocation per entry, without re-deciding the split.\n\nThis stage sits between **decomposition** (which decided the split once and froze\nit) and **creation** (`upload-epic-hierarchy.md` for an epic, ordinary\n`create_ticket` calls for siblings). It owns exactly one job: turning frozen\nmanifest entries into drafted bodies. It decides nothing.\n\n## Inputs\n\n- **The approved manifest** — the ordered plan the decomposition pass froze.\n On the `idea-to-ticket` path this is `decomposition-plan.json` in the run\n directory, produced by `decompose-epic-candidate.md`. On the `plan-epic` path\n it is the sub-task list in `epic-plan.md`, produced by `decompose-epic.md`. On\n a client-driven path (`/explore-ticket` Stage 9) it is the approved outline\n shown at the gate.\n- **The approval state** for that manifest. Whether approval is an explicit\n affirmative or a pipeline auto-approval variable is the calling surface's own\n rule; this stage only needs to know it resolved.\n- **The research and framing artifacts** each entry's writer invocation needs —\n the same set the standalone drafting path passes.\n\n## Preflight — fail closed, before any body is rendered\n\nCheck all of the following first. Each one is a **hard failure** that stops the\nflow before a single writer invocation and long before any Jira mutation. None\nof them degrades to a partial run:\n\n1. **Approval unresolved.** The manifest's approval gate has not resolved, or\n resolved as declined. Nothing renders and nothing is created.\n2. **Manifest identity changed.** The manifest presented here is not the one that\n was approved — a different entry count, different ordering, renumbered\n entries, or an altered scope boundary. A substituted split never silently\n replaces the approved one.\n3. **An entry past the size ceiling.** An entry whose stated scope runs beyond\n roughly 40 files or ~3000 LOC is over the bound wherever it sits — a\n standalone ticket and an epic child alike. Fail and name the entry; do not\n draft it, do not split it here, and do not shrink its stated scope to fit. An\n `XL` entry below that ceiling is not a violation and needs no supporting\n evidence: `XL` is the preferred shape for work that does not fit in `L`.\n4. **An entry with no writer draft after rendering.** Every entry must have\n produced a body. A missing draft is a failure, not an entry to skip.\n\nFail-open applies **only** to enrichment and context gathering — an optional\nresearch artifact that is missing may degrade the context a body is written\nfrom. Approval, manifest identity, sizing validity, and writer-output\ncompleteness all fail closed.\n\n## Instructions\n\n1. Read the approved manifest in full. Record its entry count, its order, and\n each entry's identity, so the identity check above has something to compare\n against.\n\n2. **Fan out one `jira-ticket-writer` invocation per entry**, in manifest order —\n the epic parent, then each child; or each ordinary sibling. Each invocation\n is bound to exactly one entry and receives, verbatim:\n - the entry's identity, position in the order, scope boundary, **size band**,\n parent relationship, `depends_on`, and `recommended_after`;\n - the acceptance criteria and design material for that entry's slice;\n - the shared research, materials, and framing artifacts;\n - an explicit output path for the drafted body.\n\n State in every prompt that the decomposition is **frozen**: the invocation\n renders its entry and may not add, remove, merge, reorder, renumber, or\n rescope anything, and may not write about sibling entries as though it were\n deciding them.\n\n3. **Do not batch.** One invocation per entry, never one invocation asked to emit\n every body. Two failure modes are both real and this shape avoids both: N\n fully independent decisions overlap, omit dependencies, and contradict the\n parent — which is why the decomposition is frozen upstream rather than\n re-derived per child; and one invocation emitting the whole set produces\n unreliable output as the manifest grows. Separating the decision from the\n rendering is what lets the rendering fan out safely, so a large manifest is\n handled by *more* invocations, never by collapsing back to one.\n\n4. Every entry type gets the same treatment — the same research protocol,\n materials inventory, secret-redaction rules, required sections, and Jira\n description bounds. An epic parent is not a thinner document than a\n standalone ticket, and an epic child is not a thinner document than either.\n\n5. When every entry has a draft, re-run the preflight checks against the\n rendered set and hand off to creation:\n - **Epic** — `upload-epic-hierarchy.md`, unchanged. It creates the parent with\n `issue_type = \"Epic\"`, captures the resolved epic key, then calls\n `create_ticket(parent_key=<epic_key>)` per child in manifest order with\n `track_ticket` after each, and carries idempotency labels throughout so a\n partial failure resumes rather than duplicating. Nothing new is required to\n *create* an epic — only to propose one.\n - **Siblings** — ordinary `create_ticket` calls in manifest order, unparented,\n with no epic parent synthesized.\n\n6. This stage emits **no conductor invocation**. The handoff belongs to the\n calling surface and names exactly one entry point, `drive-epic`.\n\n## Return\n\nReport the manifest's shape (`epic` or `siblings`), the entry count, each entry's\ndrafted body path in manifest order, and the preflight verdict. On a preflight\nfailure, name the violated prerequisite and the offending entry, and confirm that\nno ticket was created.\n",
899
905
  "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",
@@ -903,5 +909,5 @@ export const INSTRUCTIONS = {
903
909
  "upload-and-track.md": "Step-10 umbrella upload instruction. Idempotently create the Jira ticket(s) for this run, attach the full draft(s), and call `track_ticket`.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json`.\n- Draft metadata: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/draft-metadata.json`.\n- For epic runs, this instruction is also responsible for producing or refreshing `{docs_dir}/idea-to-ticket/{slug}-{run_id}/decomposition-plan.json` before any Jira mutation, by following `decompose-epic-candidate.md` (hard cap `{max_children}`).\n- Pipeline variable `auto_approve_external` controls whether the external-mutation pause is skipped (for this run, `auto_approve_external` = `{auto_approve_external}`). Treat the literal string `\"true\"` as skip; any other value (including `\"false\"`, missing, or empty) means pause and ask.\n\n## Instructions\n\n> **Orchestrator-directed step.** This agent task is part of the full-automation chain and is authorized to call `get_tickets`, `create_ticket`, `attachment` (operations: `upload`, `list`), `update_ticket_description`, `track_ticket`, and `add_comment`, and to execute the shared `gather-and-attach-materials.md` instruction, as directed below — performing orchestrator-directed tool calls is not \"re-orchestrating\".\n\n1. Read `run-manifest.json` and `draft-metadata.json`. Branch internally based on the manifest's `scope`:\n - `task` or `spike` → follow the **Single-ticket path** below.\n - `epic_candidate` → follow the **Epic path** below.\n The orchestrator does not support conditional steps; this branching lives in agent logic.\n\n2. External approval gate, applied before any mutating MCP tool call:\n - If `auto_approve_external` is `\"false\"` (or any non-`\"true\"` value), summarize the exact planned Jira mutations — list every `create_ticket`, `attachment` (operation: `\"upload\"`), and `track_ticket` call with its key arguments — and ask the user for explicit confirmation in this agent task before proceeding.\n - If `auto_approve_external` is `\"true\"`, proceed without the confirmation pause.\n\n3. **Single-ticket path** (`scope` is `task` or `spike`):\n 1. Idempotency lookup. Call `get_tickets` with its `labels` parameter set to both the per-run label `<idempotency_label>` and the stable `bapi-idea-hash-{idea_hash}` label from `draft-metadata.json` (comma-separated). If a match is found by either label, reuse that ticket key and skip `create_ticket`.\n 2. If no match was found, call `create_ticket` with `summary`, `slim_description` as the description, `issue_type`, and `labels` exactly as written in the metadata. Capture the returned `ticket_key`.\n 3. Upload the full markdown draft via `attachment` (operation: `\"upload\"`) using `attachment_path`.\n 4. **Gather and attach referenced materials.** Execute the shared `gather-and-attach-materials.md` instruction as an `agent_task`, passing `ticket_number` = the resolved ticket key, `draft_file_path` = `attachment_path`, and `auto_approve_external` = the inherited `{auto_approve_external}` value. It attaches phase-eligible local materials (Planning Assets, Downloadable Assets, and Planning & Downloadable Assets) and records external/auth-gated and binary/image materials per its own warn-not-halt rules. Any attach failure it reports is recorded (via `update_ticket_description`) as `partial_success` and never halts this step.\n 5. Call `track_ticket` with the resolved ticket key so Bridge API picks the new ticket up.\n 6. Write `{docs_dir}/idea-to-ticket/{slug}-{run_id}/upload-state.json` describing the final state.\n\n4. **Epic path** (`scope` is `epic_candidate`):\n 1. If `decomposition-plan.json` does not yet exist for this run, follow `decompose-epic-candidate.md` first to produce it (hard cap `{max_children}`). If it **does** already exist, it is the approved manifest and this step performs **no fresh decomposition** — read it and use it as written. Decomposition happens once; re-deriving the split immediately before upload is how children end up overlapping or contradicting the parent they are about to be created under.\n 2. Render bodies against the frozen manifest by following `render-ticket-manifest.md`: one `jira-ticket-writer` invocation per entry that lacks a draft on disk, each bound to its own entry's fixed boundary and size band, using the `draft_path` from the decomposition plan. A rendering invocation may not re-split, merge, reorder, renumber, or rescope. Its preflight is fail-closed — an unapproved manifest, a changed manifest identity, an XL epic child, or a missing writer draft stops the flow **before** any Jira mutation. After drafting, extend `draft-metadata.json` so `children[]` mirrors the final list from the decomposition plan.\n 3. Create only from writer-produced drafts. Every ticket body uploaded below came from `jira-ticket-writer`; nothing here composes a description inline.\n 4. Parent first. Look up the Epic parent by `bapi-idea-to-ticket-{run_id}-parent` via `get_tickets`. If found, reuse that key; otherwise call `create_ticket` with the parent's summary, slim description, issue type `Epic`, and parent labels. Attach the Epic draft via `attachment` (operation: `\"upload\"`) using `parent.attachment_path`. Then **gather and attach the Epic parent's referenced materials** by executing the shared `gather-and-attach-materials.md` instruction as an `agent_task`, passing `ticket_number` = the Epic key, `draft_file_path` = `parent.attachment_path`, and `auto_approve_external` = the inherited `{auto_approve_external}` value. Then call `track_ticket` for the Epic key.\n 5. Children next. For each child in order:\n - Look up by the child's `idempotency_label`. If found, reuse that key.\n - Otherwise call `create_ticket(parent_key=<epic_key>)` with the child's `summary`, `slim_description`, `issue_type`, and `labels`. The `parent_key` is required so Jira's modern parent linkage is set.\n - Upload the child draft via `attachment` (operation: `\"upload\"`) using `draft_path`.\n - **Gather and attach this child's referenced materials** by executing the shared `gather-and-attach-materials.md` instruction as an `agent_task`, passing `ticket_number` = the child key, `draft_file_path` = `draft_path`, and `auto_approve_external` = the inherited `{auto_approve_external}` value.\n - Call `track_ticket` for the child key.\n 6. After every parent or child mutation, write partial progress to `{docs_dir}/idea-to-ticket/{slug}-{run_id}/upload-state.json` so a later resume can pick up exactly where the run stopped.\n 7. **Recommended implementation order comment.** Once the Epic parent and all surviving children exist (real keys known), post a single comment on the Epic via `add_comment` with `ticket_number` set to the Epic key. The comment carries (a) a short System Goals / Non-Functional Requirements summary from `goals-and-nfrs.md`, and (b) the **Recommended Implementation Order** — the children in order, each referenced by its real Jira key, derived from the `depends_on` / `recommended_after` / `order_rationale` fields in `decomposition-plan.json`. State that this is recommended sequencing only — do **not** create Jira dependency links and do **not** attach a separate markdown doc. Skip this only if the run reused a pre-existing comment for the same run (idempotency); do not post duplicate order comments on resume.\n\n5. Required child label set whenever any child is created: `ai-generated`, `idea-to-ticket`, `idea-to-ticket-child`, and `bapi-idea-to-ticket-{run_id}-child-<N>` (1-based index from the decomposition plan).\n\n6. Partial-failure recovery rules:\n - If `create_ticket` succeeds but `attachment` (operation: `\"upload\"`) fails, record the outcome as `partial_success` in `upload-state.json` and continue with the next planned mutation; do not retry inside this step.\n - If the Epic parent is created successfully but one or more children fail, preserve the parent key and any completed child keys in `upload-state.json` before raising the failure.\n - On resume of any prior run, search by every relevant idempotency label first (`bapi-idea-to-ticket-{run_id}` for single tickets, `bapi-idea-to-ticket-{run_id}-parent`, and each `bapi-idea-to-ticket-{run_id}-child-<N>`) before considering any `create_ticket` call. Idempotency labels are how this pipeline avoids creating duplicate tickets across retries.\n\n## Return\n\nConfirm the run's final upload outcome: attachment results, `track_ticket` outcome, and any `partial_success` rows recorded in `upload-state.json`.\n\nThen, as the FINAL content of your reply, emit a fenced ```json block holding the authoritative payload for this run — and nothing else. The chain reads ONLY this final fenced JSON block to pick its review / start-tickets targets, so it must contain exactly the keys from `upload-state.json` and never any key you merely looked up during duplicate detection. Duplicate-detection / looked-up keys must not appear in this authoritative payload unless they are the final created/reused ticket for this run.\n\nThere are exactly two authoritative final payload shapes:\n\n- **Single-ticket path** (`scope` is `task` or `spike`): emit strictly `created_ticket_keys` containing **exactly one** implementable ticket key. `created_ticket_keys` is only for the single-ticket `task`/`spike` path and must contain exactly one implementable ticket key:\n\n ```json\n {\"created_ticket_keys\": [\"BAPI-331\"]}\n ```\n\n- **Epic path** (`scope` is `epic_candidate`): emit the Epic parent key separately as `epic_parent_key`, and the implementable children as `child_ticket_keys`:\n\n ```json\n {\"epic_parent_key\": \"BAPI-400\", \"child_ticket_keys\": [\"BAPI-401\", \"BAPI-402\"]}\n ```\n\n `child_ticket_keys` contains **only** implementable child Task/Spike ticket keys, listed in final decomposition order. `child_ticket_keys` must **never** include the Epic parent key.\n",
904
910
  "upload-epic-hierarchy.md": "Standalone Epic upload protocol. Use as the detailed reference for the Epic path triggered from `upload-and-track.md`.\n\n## Inputs\n\n- Run manifest: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/run-manifest.json` with `scope == \"epic_candidate\"`.\n- Draft metadata: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/draft-metadata.json` with a populated `parent` and `children`.\n- Decomposition plan: `{docs_dir}/idea-to-ticket/{slug}-{run_id}/decomposition-plan.json`.\n- Pipeline variable `auto_approve_external` governs the external-mutation pause as in `upload-and-track.md` (for this run, `auto_approve_external` = `{auto_approve_external}`).\n\n## Instructions\n\n1. Parent idempotency lookup. Search Jira via `get_tickets` for issues carrying the label `bapi-idea-to-ticket-{run_id}-parent`. If a match exists, reuse that ticket key as the Epic parent and skip `create_ticket` for the parent. Otherwise call `create_ticket` with the parent's summary, slim description, `issue_type = \"Epic\"`, and labels including `ai-generated`, `idea-to-ticket`, and `bapi-idea-to-ticket-{run_id}-parent`. After creation or reuse, upload the Epic draft via `attachment` (operation: `\"upload\"`) and call `track_ticket`.\n\n2. Capture the resolved Epic key into a local variable `epic_key`. Every subsequent child mutation must reference this exact key.\n\n3. Per-child idempotency lookup. For each child in `decomposition-plan.json` (in order), search Jira by the child's `idempotency_label` (`bapi-idea-to-ticket-{run_id}-child-<N>`). If a match exists, reuse that key and skip `create_ticket` for that child. Otherwise call `create_ticket(parent_key=<epic_key>)` with:\n - `summary` — child summary.\n - `slim_description` — child slim description.\n - `issue_type` — typically `Task` (or `Spike` when the child is primarily discovery).\n - `labels` — `ai-generated`, `idea-to-ticket`, `idea-to-ticket-child`, and the child's own `bapi-idea-to-ticket-{run_id}-child-<N>` label.\n The `parent_key` argument is REQUIRED for every child `create_ticket` call so Jira sets the modern parent relationship; never omit it.\n\n4. After each child is created or reused, upload its draft via `attachment` (operation: `\"upload\"`) using the child's `draft_path`, then call `track_ticket` for that child key, then append the child outcome to `upload-state.json` in the run directory.\n\n5. On partial failure (e.g., parent succeeded, third child failed), preserve `epic_key` plus every completed child key in `upload-state.json`. The next run of this protocol must rediscover those keys via the idempotency-label lookups in steps 1 and 3 before considering any new `create_ticket` call.\n\n## Return\n\nConfirm the Epic key, the number of children created vs reused vs failed, and the path of the updated `upload-state.json`.\n",
905
911
  "verify-plan.md": "Close the remaining plan gaps for ticket {ticket_key}, now that the pull request is open.\n\nThe plan's work has already been executed. The production phase authored the\nartifacts, the checkpoint pushed them, and the pre-PR verification phase ran the\nplan's review steps, test commands, and rendered-UI remediation — publishing each\nmaterial correction as it went. The pull request was then opened on top of all of it.\n\nThis phase exists for the narrow remainder: the plan obligations that genuinely could\n**not** be reached before a pull request existed, plus corrections attributable to this\nticket.\n\nTwo consequences follow, and both are deliberate:\n\n- **This phase does not re-run completed work.** A step the durable ledger records as\n `executed` or `adapted` stays settled unless a later correction invalidated its\n evidence. Re-running it duplicates work the pre-PR phase already did and burns the\n budget this protocol was reordered to protect.\n- **This phase never issues a verdict.** You report what you observed. The\n authoritative pass/fail belongs to the pipeline's `ci` and `code_review` gates,\n which the reconciler observes independently. Worker self-verification has\n demonstrably reported green while the full suite was red; that is exactly why the\n gates, not this phase, decide.\n\n**Tool and scope boundary.** Use only the tools this recipe names, the repo's own tooling, and MCP\ncapabilities provisioned for this repository. Keep all work confined to this worktree unless\nexplicitly told to do otherwise.\n\n**Execution mode for this run: `{execution_mode}`.** Under `orchestrated`\n(a server-side orchestrator) orchestration appends the routed phase context and\nparses the fenced result envelope you return. Under `inline` (`get_pipeline_recipe`)\nthere is no orchestrator: the ledger is read with a tool call and written with one.\nFollow the branch that matches wherever the two are named.\n\n---\n\n## Step 1 — Establish that the durable artifact exists\n\nBefore running any check:\n\n1. Run `git branch --show-current` and `git rev-parse HEAD`, then verify the branch\n has been pushed and the local head is present on the remote (for example via\n `git status -sb` showing no unpushed ahead-count, or `git ls-remote origin <branch>`).\n2. Verify that a usable pull request URL was obtained by the preceding PR step —\n either a newly opened pull request or an already-open one on this head branch.\n\nIf the branch is not pushed, or no usable pull request URL exists, then\n**stop this phase** and report the missing prerequisite. This phase exists only to\nadd work on top of an open pull request.\n\n## Step 2 — Recover what remains from durable state\n\n1. Call the `get_plan` tool for `{ticket_key}`. The local copy at\n `{docs_dir}/plans/{ticket_key}-plan.md` may be used as a reference.\n2. Recover the durable ledger, by mode:\n - **orchestrated** — read the **Routed phase context** block appended to this\n instruction: `ledger` carries every disposition earlier phases recorded, and\n `ownedSteps` carries anything routed directly to this phase.\n - **inline** — call `get_phase_context` with `ticket_key` `{ticket_key}` and\n `phase` `post_pr_gap_close`. It returns the same `ownedSteps` plus the `ledger`\n merged from every earlier phase's artifact, `terminalStepIds` for what is\n already settled, and `unresolved` for the escalations that are this phase's\n actual subject.\n\nRecover the remaining work from that durable record, not from conversation. A\ncompaction or a resumed session loses the conversation; the ledger survives both,\nwhich is why it replaced the conversational hand-off.\n\n## Step 3 — Select only genuine gaps\n\nRun only:\n\n- Steps the ledger records as `escalated` **because a capability was unavailable\n before the pull request existed**, or **because the check genuinely requires an open pull request** — and\n which are now satisfiable.\n- Corrections clearly attributable to this ticket's change.\n\nReport — do not attempt to fix — failures that are unrelated to this ticket, flaky,\nenvironmental, pre-existing on the base branch, or outside the declared file scope.\nSpeculative edits made under budget pressure are how a correction round turns into a\nregression.\n\nApply the same adaptation boundary the earlier phases use: **locator-correction**,\n**repository-command-correction**, and **equivalent-implementation-recognized** are\nmechanical and may be applied; anything touching design, schema, public API,\ndependencies, or security escalates instead.\n\nBefore beginning any correction, apply the **low-budget guard**: only start if enough\nsession budget clearly remains to make the edit, commit it, *and* push it. Starting a\nfix you cannot finish and publish is strictly worse than reporting the finding and\nletting the CI and review gates handle it — an unpushed correction is invisible to\nthose gates.\n\n## Step 4 — Report what you observed, honestly\n\nFor every check you run, record the exact command, its observed result, and — on\nfailure — the relevant failure detail (the failing test names, the error output, the\ndiagnostic lines).\n\nReport all of it, including failures you did not fix. Never soften or omit a failing\nresult.\n\nDescribe only what you observed. Do not write that CI passed, that the gate is met,\nthat the review is approved, or any equivalent claim about the pipeline's verdict —\nthose states are decided by the `ci` and `code_review` gates and observed by the\nreconciler, never asserted by this phase.\n\n## Step 5 — Correct only what is clearly yours, and push it immediately\n\nFor each accepted correction:\n\n1. Make the edit.\n2. Stage the specific files, commit, and **push immediately** — the commit and its\n push are one consecutive sequence, never separated by another check. A local\n commit that is never pushed is not visible to the pull request, to CI, or to the\n reconciler.\n3. Run `git rev-parse HEAD` again and record the new pushed head as\n `last_commit_sha`.\n\n## Step 6 — Final git-state audit\n\nBefore returning, run `git status --porcelain` and resolve the working tree:\n\n- Legitimate corrections still uncommitted → commit and push them (Step 5's\n commit-then-push-immediately rule applies).\n- Accidental diagnostic edits — debug prints, scratch files, temporary config\n tweaks made while investigating a failure → revert them when it is safe to do so.\n- Anything you cannot safely resolve → leave it and **report it explicitly**,\n naming each remaining dirty path.\n\nNever return leaving unpushed commits unreported.\n\n## Step 7 — Hand unresolved findings forward\n\nAn unresolved local finding is normally **not** a reason to stop the pipeline. The\npull request is open and the authoritative gates will evaluate it. Report the\nfinding and let CI monitoring and code review take it from there.\n\nStop only when continuing would be unsafe or impossible — for example the durable\nartifact from Step 1 turned out to be missing, or the working tree is in a state you\ncannot resolve without risking the pushed branch.\n\n## Return\n\nReturn a summary containing:\n\n- the branch and the pull request URL,\n- the latest pushed `last_commit_sha`,\n- every gap-closing command run, with its observed outcome,\n- the correction commit, if one was made and pushed,\n- every unresolved finding and every unresolved dirty path.\n\nState these as worker observations. Do not include a pass/fail verdict for the `ci`\nor `code_review` gates.\n\nThen record the machine-readable phase result, by mode, so the durable ledger records\nhow the remaining gaps closed.\n\n### orchestrated\n\nEnd your result with the envelope in a fenced block tagged `bapi-phase-result`, which\norchestration parses, validates, and persists:\n\n```bapi-phase-result\n{\"version\":1,\"phase\":\"post_pr_gap_close\",\"lastCommitSha\":\"<sha>\",\"records\":[]}\n```\n\n### inline\n\nCall the `record_phase_result` tool with `ticket_key` `{ticket_key}` and\n`phase_result` set to that same envelope object; the tool validates and persists it.\n**Do not also emit a fenced `bapi-phase-result` block** — nothing parses one on this\npath.\n\nThe tool call is this phase's final action, **not the end of your turn.** When it\nreturns successfully, continue immediately with the next recipe step — the ticket\nstatus transition, the CI follow-up config, and CI monitoring. The pull request is\nopen and its gates are still pending; stopping here abandons the run before anything\nobserves them.\n\nIf the call fails, fix what it reports and call it again.\n",
906
- "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 handed directly to the Jira Ticket Writer / ticket-authoring workflow as input. Each line should be a self-contained ticket description.}\n```\n\n4. After writing the overview, display the file path to the user and summarize the epic plan.\n\n5. **Push the goals/NFRs + recommended order into the Jira epic (only when `{epic_key}` is non-empty).** The `epic_key` is empty when this run was started from free-form text rather than an existing Epic; in that case skip this step. When `{epic_key}` is a real Jira key, post the System Goals, the final NFRs, 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"
912
+ "write-epic-summary.md": "Synthesize all sub-task explorations into a final overview document.\n\n## Instructions\n\n1. First, use a terminal command or glob pattern to list all files in `{docs_dir}/epic-plans/{epic_slug}/explorations/`. Then read each file. Do not guess filenames — discover them dynamically.\n\n2. Also read:\n - `{docs_dir}/epic-plans/{epic_slug}/research-findings.md`\n - `{docs_dir}/epic-plans/{epic_slug}/epic-plan.md`\n - `{docs_dir}/epic-plans/{epic_slug}/goals-and-nfrs.md` (the goals/NFR framing; carry its System Goals, NFRs, and any Recommended Implementation Order through to the overview).\n - `{docs_dir}/epic-plans/{epic_slug}/conductor-eligibility.json` (written by `assess-conductor-eligibility`; required for the Conductor Eligibility section below).\n\n3. Synthesize the information into an overview and write it to `{docs_dir}/epic-plans/{epic_slug}/overview.md` with the following required sections:\n\n```markdown\n# Epic Overview: {epic title derived from description}\n\n## Epic Description and Goals\n{Summary of the epic's purpose, scope, and desired outcomes. Lead with the business goal and desired end-state from goals-and-nfrs.md.}\n\n## Non-Functional Requirements\n{The classified NFRs from goals-and-nfrs.md — each with its category, requirement, implication, and final status (confirmed/assumed). Any NFRs the user clarified should now read as confirmed/assumed, not open.}\n\n## Research Summary\n{Key external findings that informed the decomposition. If no research was performed, state \"No external research was needed.\"}\n\n## Sub-task List\n{Numbered list of all sub-tasks with relative markdown links to their exploration docs.}\n1. [Sub-task title](explorations/01-subtask-slug.md) — one-line summary\n2. [Sub-task title](explorations/02-subtask-slug.md) — one-line summary\n...\n\n## Dependency Graph\n{Textual list showing execution ordering and dependencies between sub-tasks.}\n- Sub-task 1: No dependencies (start here)\n- Sub-task 2: Depends on Sub-task 1\n- Sub-task 3: Depends on Sub-task 1\n- Sub-task 4: Depends on Sub-tasks 2, 3\n...\n\n## Recommended Implementation Order\n{The recommended order in which to implement the sub-tasks, reconciling the provisional order from goals-and-nfrs.md with the approved decomposition. For each sub-task give the position, its hard prerequisites (depends on), any soft sequencing preferences (recommended after), and a one-line rationale. This is recommended sequencing only — no Jira dependency links are created.}\n\n## Conductor Eligibility\n{Render from `conductor-eligibility.json`, written by `assess-conductor-eligibility`. This section\nis about predicted HUMAN WORKLOAD before any execution starts — it never blocks planning\ncompletion, on any status.\n\n**When status is `\"assessed\"` and `predictedWorkflowChildren` > 0:**\n\n- A merge row stating: \"At least N of M children are predicted to modify workflow files and\n require human merge.\" where N is `predictedWorkflowChildren` and M is `totalChildren`. Include\n the canonical `reason` identifier (e.g. `workflow_files_modified`) inline in monospace when it\n is present, e.g. \"(reason: `workflow_files_modified`)\". This is a lower bound, not a promise —\n see the caveat below.\n- If `reviewSubsetChildren` > 0, a second, more severe row stating: \"K of those N are predicted to\n modify `.github/workflows/claude-review.yml`; automated review will not run and hand review is\n required.\" where K is `reviewSubsetChildren`. Use the literal path from the artifact's affected-child\n matched paths, never a hard-coded literal in this instruction. State plainly that this narrower set\n is also part of the human-merge set above — it can obtain neither an automated merge nor an\n automated review verdict.\n- The caveat, always present when the count is non-zero: \"Predictions are based on ticket text;\n implementation choices, including defense-in-depth workflow changes, can increase the final\n count.\"\n- A note on why this matters more than it might look: human merge and hand-review requirements are\n especially consequential for unattended v2 execution, where no operator is watching for a stalled\n child.\n- A collapsed \"Show affected children\" section (a Markdown `<details>` block) listing, for each\n entry in `affectedChildren`: its compact `id`, `title`, `matchedPaths`, and whether it requires\n human merge only or both human merge and hand review (`requiresHandReview`).\n\n**When status is `\"assessed\"` and `predictedWorkflowChildren` == 0:** render \"No workflow-file\nchanges predicted from current ticket text.\" Still include the ticket-text-prediction caveat above\n— a zero count is not a guarantee that automated merge/review will succeed once implemented.\n\n**When status is `\"unavailable\"`:** render a non-blocking warning: eligibility could not be\npredicted from the current planning data, and workflow-related merge/review eligibility must be\nverified manually before execution. Do not report `0 of M` or any other count when the status is\nunavailable — an unresolved assessment and a genuine zero are different facts.\n\n## Next Steps\n{One-line summaries for each sub-task, specifically formatted so they can be handed directly to the Jira Ticket Writer / ticket-authoring workflow as input. Each line should be a self-contained ticket description.}\n```\n\n4. After writing the overview, display the file path to the user and summarize the epic plan.\n\n5. **Push the goals/NFRs + recommended order into the Jira epic (only when `{epic_key}` is non-empty).** The `epic_key` is empty when this run was started from free-form text rather than an existing Epic; in that case skip this step. When `{epic_key}` is a real Jira key, post the System Goals, the final NFRs, the Recommended Implementation Order, and a concise Conductor Eligibility summary as a **comment** on that epic by calling the `add_comment` MCP tool with `ticket_number` set to `{epic_key}` and a comment containing those four parts. The eligibility summary is one or two lines derived the same way as the overview's Conductor Eligibility section (the \"At least N of M\" / \"K of those N\" wording, or the zero/unavailable wording) — never the full affected-children detail. Do not create Jira dependency links and do not attach a separate markdown doc — the comment is the delivery. Display: `\"Posted epic goals/NFRs, recommended implementation order, and conductor eligibility to {epic_key}\"`.\n\n## Return\n\nConfirm the overview was written to `{docs_dir}/epic-plans/{epic_slug}/overview.md` and report the total sub-task count along with a one-line summary of the epic plan. State whether the goals/NFRs + recommended order were posted as a comment on `{epic_key}` or skipped because no epic key was provided.\n"
907
913
  };