@bridge_gpt/mcp-server 0.2.2 → 0.2.4

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 (113) hide show
  1. package/README.md +97 -15
  2. package/build/agent-config-credential-migration.js +272 -0
  3. package/build/agents.generated.js +1 -1
  4. package/build/chain-orchestrator.js +16 -1
  5. package/build/commands.generated.js +9 -7
  6. package/build/conductor/bridge-api-client.js +625 -0
  7. package/build/conductor/claude-hook.js +251 -0
  8. package/build/conductor/cli.js +1048 -0
  9. package/build/conductor/data-normalization.js +114 -0
  10. package/build/conductor/doctor.js +164 -0
  11. package/build/conductor/done-gate.js +325 -0
  12. package/build/conductor/epic-reconcile.js +139 -0
  13. package/build/conductor/epic-runtime.js +611 -0
  14. package/build/conductor/epic-state.js +125 -0
  15. package/build/conductor/errors.js +85 -0
  16. package/build/conductor/git-ci-types.js +129 -0
  17. package/build/conductor/git-hooks.js +218 -0
  18. package/build/conductor/git-inspection.js +185 -0
  19. package/build/conductor/git-producer.js +137 -0
  20. package/build/conductor/merge-ledger.js +198 -0
  21. package/build/conductor/paths.js +224 -0
  22. package/build/conductor/plan.js +77 -0
  23. package/build/conductor/pr-ci-producer.js +427 -0
  24. package/build/conductor/pr-discovery.js +135 -0
  25. package/build/conductor/producer-ledger.js +125 -0
  26. package/build/conductor/redaction.js +112 -0
  27. package/build/conductor/store.js +1156 -0
  28. package/build/conductor/supervisor-config.js +150 -0
  29. package/build/conductor/supervisor-escalation.js +244 -0
  30. package/build/conductor/supervisor-judgment-python.js +141 -0
  31. package/build/conductor/supervisor-judgment.js +215 -0
  32. package/build/conductor/supervisor-ledger.js +119 -0
  33. package/build/conductor/supervisor-merge.js +127 -0
  34. package/build/conductor/supervisor-message-relay.js +61 -0
  35. package/build/conductor/supervisor-notification.js +39 -0
  36. package/build/conductor/supervisor-runtime.js +351 -0
  37. package/build/conductor/supervisor-state.js +572 -0
  38. package/build/conductor/supervisor-types.js +16 -0
  39. package/build/conductor/taxonomy.js +58 -0
  40. package/build/conductor/tools.js +367 -0
  41. package/build/conductor/types.js +9 -0
  42. package/build/conductor-bin.js +21 -0
  43. package/build/conductor-claude-hook-bin.js +21 -0
  44. package/build/credential-store.js +175 -4
  45. package/build/credentials-cli.js +223 -0
  46. package/build/decision-page-schema.js +60 -0
  47. package/build/decision-page-template.js +262 -10
  48. package/build/doctor.js +5 -1
  49. package/build/index.js +554 -66
  50. package/build/pipeline-orchestrator.js +5 -1
  51. package/build/pipeline-utils.js +45 -5
  52. package/build/pipelines.generated.js +37 -9
  53. package/build/readme.generated.js +1 -1
  54. package/build/review-tickets.js +596 -0
  55. package/build/scheduled-prompt.js +16 -10
  56. package/build/start-tickets-conductor.js +496 -0
  57. package/build/start-tickets-prereqs.js +32 -23
  58. package/build/start-tickets-repo.js +49 -0
  59. package/build/start-tickets.js +682 -81
  60. package/build/version.generated.js +1 -1
  61. package/design-assets/favicon/android-chrome-192x192.png +0 -0
  62. package/design-assets/favicon/android-chrome-512x512.png +0 -0
  63. package/design-assets/favicon/apple-touch-icon.png +0 -0
  64. package/design-assets/favicon/favicon-16x16.png +0 -0
  65. package/design-assets/favicon/favicon-32x32.png +0 -0
  66. package/design-assets/favicon/favicon.ico +0 -0
  67. package/design-assets/favicon/site.webmanifest +1 -0
  68. package/design-assets/just-logo-rough-draft.png +0 -0
  69. package/package.json +17 -5
  70. package/pipelines/idea-to-ticket.json +5 -0
  71. package/pipelines/plan-epic.json +16 -1
  72. package/pipelines/review-ticket.json +2 -1
  73. package/public/css/main.min.css +2 -0
  74. package/public/css/main.min.css.map +1 -0
  75. package/public/fonts/OFL.txt +93 -0
  76. package/public/fonts/SourceSansPro-Black.ttf +0 -0
  77. package/public/fonts/SourceSansPro-BlackItalic.ttf +0 -0
  78. package/public/fonts/SourceSansPro-Bold.ttf +0 -0
  79. package/public/fonts/SourceSansPro-BoldItalic.ttf +0 -0
  80. package/public/fonts/SourceSansPro-ExtraLight.ttf +0 -0
  81. package/public/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
  82. package/public/fonts/SourceSansPro-Italic.ttf +0 -0
  83. package/public/fonts/SourceSansPro-Light.ttf +0 -0
  84. package/public/fonts/SourceSansPro-LightItalic.ttf +0 -0
  85. package/public/fonts/SourceSansPro-Regular.ttf +0 -0
  86. package/public/fonts/SourceSansPro-SemiBold.ttf +0 -0
  87. package/public/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
  88. package/public/img/bridge-logo-160x51.webp +0 -0
  89. package/public/img/bridge-logo-300x92.webp +0 -0
  90. package/public/img/favicon/android-chrome-192x192.png +0 -0
  91. package/public/img/favicon/android-chrome-512x512.png +0 -0
  92. package/public/img/favicon/apple-touch-icon.png +0 -0
  93. package/public/img/favicon/favicon-16x16.png +0 -0
  94. package/public/img/favicon/favicon-32x32.png +0 -0
  95. package/public/img/favicon/favicon.ico +0 -0
  96. package/public/img/favicon/site.webmanifest +1 -0
  97. package/public/img/installation/bitbucket/app-password-1.png +0 -0
  98. package/public/img/installation/bitbucket/app-password-2.png +0 -0
  99. package/public/img/installation/bitbucket/create-token-1.png +0 -0
  100. package/public/img/installation/bitbucket/create-token-2.png +0 -0
  101. package/public/img/installation/bitbucket/webhook-1.png +0 -0
  102. package/public/img/installation/github/github-review-webhook.png +0 -0
  103. package/public/img/installation/jira/credentials/api-key.png +0 -0
  104. package/public/img/installation/jira/webhook/create-rule.png +0 -0
  105. package/public/img/installation/jira/webhook/project-settings.png +0 -0
  106. package/public/img/installation/jira/webhook/rule-create-1.png +0 -0
  107. package/public/img/installation/jira/webhook/rule-create-2.png +0 -0
  108. package/public/img/installation/jira/webhook/rule-create-3.png +0 -0
  109. package/public/img/installation/pinecone/pinecone-api-key.png +0 -0
  110. package/public/img/installation/pinecone/pinecone-index.png +0 -0
  111. package/public/js/main.min.js +2 -0
  112. package/public/js/main.min.js.map +1 -0
  113. package/smoke-test/SMOKE-TEST.md +17 -9
package/README.md CHANGED
@@ -174,10 +174,11 @@ For invocation, prefer the slash command — it's deterministic. A free-text exa
174
174
  These features are useful for most tickets.
175
175
 
176
176
  **1. Review Ticket**
177
- - **What it does:** Runs a two-round quality review of a ticket (clarifying questions + critique, plus an alternate-model second opinion) and produces a decision page to accept/reject findings.
177
+ - **What it does:** Runs a full quality review of a ticket: clarifying questions + critique (initial round), an automatic alternate-model second opinion, then evaluates findings and produces a decision page to accept/reject them. The second-opinion pass is included by default and can be skipped with `--rounds=1`.
178
178
  - **When it's useful:** (Refinement) Right after a ticket is drafted, before anyone starts building — to surface gaps and tighten it.
179
179
  - **How to use it:** `/review-ticket BAPI-123` (command only — "review" as free text is easily mistaken for a freehand agent review).
180
- - **Flags:** `--auto` auto-accept findings / skip the approval gates.
180
+ - **Flags:** `--auto` auto-accept findings / skip the approval gates · `--rounds=1` skip the automatic second-opinion review step while preserving downstream evaluation and decision-capture work (cheaper single-pass review). `--rounds=2` (default) runs the full two-round review.
181
+ - **Multi-ticket fan-out:** `/review-tickets BAPI-123 BAPI-456` opens one terminal tab per ticket for parallel review with no worktrees (terminal launcher only — no `wt`/`git`). All `/review-ticket` flags apply; `--review KEY=auto,rounds=N` sets per-ticket overrides. Packaged CLI: `npx -y @bridge_gpt/mcp-server review-tickets KEY [KEY ...]`.
181
182
 
182
183
  **2. Start Tickets**
183
184
  - **What it does:** Creates one git worktree per ticket and spawns an agent session in each to implement them in parallel.
@@ -186,9 +187,9 @@ These features are useful for most tickets.
186
187
  - **Flags:** `--auto` skip the approval gates · `--base-branch <branch>` branch off something other than the default.
187
188
 
188
189
  **3. Brainstorm**
189
- - **What it does:** Fans your problem out to two different LLMs and synthesizes their approaches into one set of options. Runs in one of two modes: a **standard** brainstorm (implementation/architecture approaches) or a **design** brainstorm (UI/UX and visual direction).
190
- - **When it's useful:** (Architecture | Refinement) Early, when you want a spread of approaches — standard for *how to build it*, design for *how it should look*.
191
- - **How to use it:** ask your agent to brainstorm — *"Brainstorm approaches for adding rate limiting to the LLM client; fan it out to multiple models and synthesize."* For a design pass: *"Run a design brainstorm for the evidence-freshness dashboard UI."*
190
+ - **What it does:** Fans your problem out to two different LLMs and returns their approaches directly. Runs in one of three modes, selected via `mode`: **`technical`** (default — implementation/architecture approaches), **`design`** (UI/UX and visual direction), or **`discovery`** (stakeholder discovery questions for early/vague tasks, grouped into `Technical Discovery Questions` and `Business / Stakeholder Discovery Questions` and tagged `[HUMAN]`/`[CODE]`/`[TICKET]`). Discovery needs no extra configuration. The legacy boolean `design=true` still works and maps to `mode: "design"`.
191
+ - **When it's useful:** (Architecture | Refinement) Early, when you want a spread of approaches — `technical` for *how to build it*, `design` for *how it should look*, `discovery` for *what we still need to figure out* before a real ticket exists.
192
+ - **How to use it:** ask your agent to brainstorm — *"Brainstorm approaches for adding rate limiting to the LLM client; fan it out to multiple models."* For a design pass: *"Run a design brainstorm for the evidence-freshness dashboard UI."* For early discovery: *"Run a discovery brainstorm — `request_brainstorm` with `mode: "discovery"` — for this vague request so we can collect the questions stakeholders need to answer first."*
192
193
 
193
194
  **4. Deep Research**
194
195
  - **What it does:** Runs multi-source, fact-checked web research on a technical topic and returns a cited report.
@@ -229,36 +230,42 @@ These features are good to know, but you probably won't use them every day.
229
230
  - **How to use it:** `/critique-ticket BAPI-123` — *"Critique BAPI-123 against our project standards and list what's missing or deviating."*
230
231
  - **Flags:** `--provider <name>` choose the model provider · `--second-opinion <provider>` cross-check with a second provider.
231
232
 
232
- **4. Explore Ticket**
233
+ **4. Create Doc**
234
+ - **What it does:** Generates a design document for a ticket — a TDD (technical design, engineer audience), an FSD (functional spec, for product/design/QA), or a PRD (product requirements: problem, goals, success metrics) — and saves it locally.
235
+ - **When it's useful:** (Architecture | Refinement) When a ticket needs a fuller design write-up before planning or implementation, in the shape that fits your audience.
236
+ - **How to use it:** `/create-doc BAPI-123 --doc-type tdd` (or `fsd` / `prd`)
237
+ - **Flags:** `--doc-type tdd|fsd|prd` which document to generate (required) · `--provider <name>` choose the model provider · `--second-opinion <provider>` cross-check with a second provider.
238
+
239
+ **5. Explore Ticket**
233
240
  - **What it does:** Explores the codebase for a task and recommends implementation options or surfaces clarifying questions, with optional research.
234
241
  - **When it's useful:** (Architecture | Refinement) Before writing a ticket or plan, when you're unsure how a change would fit the existing code.
235
242
  - **How to use it:** `/explore-ticket <task>` — *"Explore the codebase for how we'd add a Mistral LLM provider and recommend 2–3 implementation options."*
236
243
 
237
- **5. Second Opinion**
244
+ **6. Second Opinion**
238
245
  - **What it does:** Gets an immediate critique of any text from a different model family — no artifact saved, just the reply.
239
246
  - **When it's useful:** (Architecture | Refinement | Implementation) Any time you want a quick sanity check on a plan, draft, or decision from a fresh perspective.
240
247
  - **How to use it:** ask your agent — *"Get a second opinion from Gemini on whether the BAPI-123 plan's migration step is safe to run against prod."*
241
248
  - **Options:** pick the provider (anthropic / openai / gemini) and the tier (cheap / basic / premium).
242
249
 
243
- **6. Generate Image**
250
+ **7. Generate Image**
244
251
  - **What it does:** Generates an image from a text prompt using a provider image model (OpenAI `gpt-image-2` by default, or Google Imagen) and returns the image directly. Spends provider credits on every call.
245
252
  - **When it's useful:** (Architecture | Refinement) When you want a quick visual — a UI mockup, diagram, or illustration — to anchor a design discussion or attach to a ticket.
246
253
  - **How to use it:** ask your agent — *"Generate an image of a dashboard showing SOC2 evidence freshness as a traffic-light grid."*
247
254
  - **Options:** `provider` openai (`gpt-image-2`) / gemini (Imagen — adds an invisible SynthID watermark) · `quality` low (default, cheapest) / medium / high · `size` 1024x1024 / 1024x1536 / 1536x1024. The image is always saved to `BAPI_DOCS_DIR/images/` and also returned inline.
248
255
 
249
- **7. Implement Ticket**
256
+ **8. Implement Ticket**
250
257
  - **What it does:** Full build for one ticket: generate a plan, write the code, commit, open a PR, and monitor CI.
251
258
  - **When it's useful:** (Implementation) When a ticket is ready and you want it taken from plan to open PR in one go.
252
259
  - **How to use it:** `/implement-ticket BAPI-123` (command only — "implement X" as free text almost always triggers a freehand build instead of the Bridge plan→code→PR→CI pipeline).
253
260
  - **Flags:** `--auto` skip the approval gates (e.g. auto-commit/push).
254
261
 
255
- **8. Full Automation**
262
+ **9. Full Automation**
256
263
  - **What it does:** Drives the whole chain end-to-end: idea → ticket(s) → review each → spawn worktrees to implement.
257
264
  - **When it's useful:** (Automation) When you want to go from a raw idea to in-progress implementation with minimal hands-on steps.
258
265
  - **How to use it:** `/full-automation <idea>` (command only — creates tickets, spawns worktrees, and carries scheduling/`--max-children` flags that free text can't).
259
266
  - **Flags:** `--require-approval` toggle the approval gates, full automation runs end to end by default.
260
267
 
261
- **9. Idea to Ticket**
268
+ **10. Idea to Ticket**
262
269
  - **What it does:** Turns a one-line idea into a Jira Task/Spike (or an Epic plus child tickets), with research, duplicate detection, and a critique pass built in.
263
270
  - **When it's useful:** (Refinement | Automation) When you have a rough idea and want a fully-formed, uploaded ticket without the manual draft-and-refine loop.
264
271
  - **How to use it:** `/idea-to-ticket <idea>`
@@ -374,6 +381,8 @@ npx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248
374
381
 
375
382
  **Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket's `difficulty` (1-2 → cheap, 3-5 → basic, 6+ → premium) and injects it as a `--model` flag at the spawn boundary. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand); this CLI alone maps a tier to the agent-specific alias (`claude`: `haiku`/`sonnet`/`opus`; `cursor-agent`: version-suffixed strings validated against `cursor-agent --list-models`). It is gated per repo by `difficulty_model_routing_enabled` (default **ON**) with an optional `difficulty_model_tier_overrides` JSON map (tier → alias). Routing is **fail-open**: missing credentials, an evaluation failure/timeout, a backend `fallback`, an invalid/unavailable alias, an unadvertised Cursor model, or an agent without `--model` support all omit `--model` (the agent uses its default) and surface a per-ticket warning rather than failing the spawn. `--dry-run` does **not** fetch tiers or inject `--model`.
376
383
 
384
+ **Conductor observability (BAPI-394).** A real run mints a single conductor `run_id` and emits one canonical `run.started` event into the local conductor ledger (`~/.config/bridge/events.db`), attributing each worker by `worker_id`, ticket key, and worktree path. When the selected agent is **Claude Code**, the CLI also injects a conductor lifecycle hook into each created worktree's `.claude/settings.local.json` (preserving any existing hooks) so the spawned session streams local `run.started` / `run.stopped` / `agent.notification` (and, with `BAPI_CONDUCTOR_ENABLE_PRE_TOOL_USE=1`, `tool.intent`) events. Per-worker conductor identity is passed only via secret-free environment scoped to that one terminal/tab/session — no credentials are ever placed in the env, hook command, or run metadata. Override the gate/supervisor labels with `BAPI_CONDUCTOR_GATE_NAME` / `BAPI_CONDUCTOR_SUPERVISOR_MODE`. Inspect the stream with `conductor doctor`. Observability is best-effort: a conductor failure never blocks or aborts a spawn, and `--dry-run` performs no conductor side effects.
385
+
377
386
  **Cross-platform spawning.** The CLI routes spawning per platform; `--dry-run` previews the platform-correct command form on any OS. An unsupported `process.platform` (not `darwin`/`win32`/`linux`) fails fast with a clear "unsupported platform" message.
378
387
 
379
388
  - **macOS** — opens a Terminal.app or iTerm tab via `osascript`.
@@ -392,6 +401,56 @@ npx -y @bridge_gpt/mcp-server doctor [--agent <name>]
392
401
 
393
402
  It is **read-only**: it never installs anything, modifies your system, adds an npm `postinstall`, spawns a terminal, or starts the MCP server, and there is no `--fix`. For each prerequisite it prints found/missing and, when missing, the exact per-OS install command **as a manual instruction you run yourself**. The checked set is the `start-tickets` preflight prerequisites **plus `uv`** **plus the selected agent's command** (`claude` by default, or `cursor-agent` with `--agent cursor-agent`). The Worktrunk binary is probed via the resolved name (honoring `BAPI_WORKTRUNK_BIN`), not a hard-coded one. **Exit code:** `0` when all required prerequisites are present, non-zero when any is missing or the platform is unsupported. A failing `start-tickets` preflight now hints you to run `doctor` for an actionable diagnostics report.
394
403
 
404
+ ### `conductor install-git-hooks` (BAPI-395)
405
+
406
+ Installs local git hooks that opportunistically emit conductor git/PR/CI events into the local ledger:
407
+
408
+ ```
409
+ conductor install-git-hooks [--json]
410
+ ```
411
+
412
+ The installed hooks are **local, unversioned, opportunistic, and bypassable**: they live in the worktree's git hooks directory (resolved via `git rev-parse --git-common-dir`), insert only a clearly-delimited managed block (preserving any existing user hook content), launch the producer **detached in the background** so a commit or ref update is never blocked, and tolerate every failure (`|| true`). A directory that is not a git worktree, or an existing hook that looks binary/unsafe, is left untouched and reported as a **degraded optional capability** — never a fatal error. The hooks installed are `post-commit` (emits `git.commit_created`) and `reference-transaction` (emits `worktree.changed` for committed ref updates).
413
+
414
+ Missing hooks do **not** prevent PR/CI gate evaluation — `conductor doctor` reads hook presence and managed-snippet status **read-only** (a new `git hooks` section / `git_hooks` JSON object alongside the ledger report), and the `wait_for_done_gate` MCP tool drives CI polling and gate evaluation regardless of whether hooks are installed.
415
+
416
+ #### `conductor_done_gate` config
417
+
418
+ The per-repo `conductor_done_gate` config field (read through the existing config-field route) defines the v1 done gate. It supports exactly one condition, `required_ci_checks_green`:
419
+
420
+ ```json
421
+ {
422
+ "enabled": true,
423
+ "conditions": [
424
+ { "type": "required_ci_checks_green", "required_checks": ["build", "test"] }
425
+ ]
426
+ }
427
+ ```
428
+
429
+ `gate.met` is emitted (exactly once per `repo + pr_number + head_sha + effective config`) only when every listed required check is present, complete, and green for the bound PR head SHA. The gate **fails closed**: an unset, disabled (`enabled` not strictly `true`), malformed, empty, or unsupported config emits no `gate.met`.
430
+
431
+ #### `conductor_auto_merge_enabled` config (C6 conditional auto-merge)
432
+
433
+ When a worker's PR meets the done gate (`gate.met`), the supervisor can autonomously merge it — but **only** when the repo has explicitly opted in. The per-repo `conductor_auto_merge_enabled` config field (read through the same config-field route as `conductor_done_gate`) is the opt-in switch:
434
+
435
+ ```json
436
+ { "enabled": true }
437
+ ```
438
+
439
+ A bare JSON boolean (`true`) is also accepted. **Auto-merge is disabled by default.** Behavior:
440
+
441
+ - **Disabled / unset / malformed → dry-run.** Anything other than `true` or `{"enabled": true}` — including unset, `false`, `{"enabled": false}`, or any malformed value — fails **closed**: the supervisor records a `merge.dry_run` event and **no PR is ever merged**.
442
+ - **Enabled → autonomous merge** when the gate is met and the deterministic guards pass.
443
+ - **Kill-switch.** Set `conductor_auto_merge_enabled` to `false` or remove the field to immediately stop autonomous merges. The protected merge endpoint **independently re-enforces** the flag, so even a conductor that calls it cannot merge while the flag is off.
444
+
445
+ Merge authority is **deterministic code, never an LLM**. The deterministic guards, all bound to **PR number + expected head SHA (never a branch name)**:
446
+
447
+ - the per-repo enablement flag (off → dry-run),
448
+ - the PR is still open,
449
+ - the merge is bound to the PR number plus the expected head SHA — **head-SHA drift between gate evaluation and merge aborts the merge**,
450
+ - required CI checks are **revalidated green immediately before merge**.
451
+
452
+ Idempotency is crash-safe and race-safe: a TTL lease keyed by the deterministic action key `merge:{repo}:{pr}:{head_sha}:{gate}` is claimed before acting, and an existing `merge.succeeded` for that key is terminal — the supervisor never double-merges across a crash/restart or two racing instances. The conductor never holds VCS write credentials: it calls the protected Bridge API endpoint `POST /vcs/pull-requests/{pr_number}/merge`, which owns the privileged merge, and records the returned `merge.dry_run` / `merge.attempted` / `merge.succeeded` / `merge.failed` / `merge.pending_approval` events into the local ledger. `merge.failed` is **retryable** (a drifted head SHA produces a new action key); `merge.pending_approval` is **nonterminal** — the worker remains active until a human redeems the approval token and the server returns `merge.succeeded`. **`merge.succeeded` is the only terminal merge event.** The local SQLite conductor store uses schema version 5 (BAPI-413) to accommodate the `merge.pending_approval` type in the `events.type` CHECK constraint.
453
+
395
454
  ## Custom Pipelines
396
455
 
397
456
  You can create your own pipelines by adding JSON files to `.bridge/pipelines/`. Running `--init` scaffolds this directory with a `README.md` and an example pipeline to get you started.
@@ -444,7 +503,7 @@ records a PASS/FAIL verdict for each one in a markdown report.
444
503
  - `smoke-test/SMOKE-TEST.md` **ships with the npm package** and is the
445
504
  **canonical** source of truth for the smoke test.
446
505
  - The smoke test **adds no MCP tool** and **does not change the registered
447
- tool surface** (the server still registers its existing 55 tools).
506
+ tool surface** (the server still registers its existing 62 tools).
448
507
  - It is **opt-in**: default `--init` **does not scaffold `/smoke-test-mcp`**, so
449
508
  consumer command palettes are not polluted.
450
509
 
@@ -479,6 +538,7 @@ Reports are written to `<BAPI_DOCS_DIR>/smoke-test/REPORT-<host>-<timestamp>.md`
479
538
  | `BAPI_PIPELINES_DIR` | No | `.bridge/pipelines` | Directory for user-defined custom pipeline JSON files |
480
539
  | `BAPI_WORKTRUNK_BIN` | No | `wt` (`git-wt` on Windows) | Override the Worktrunk executable name/path used by `start-tickets` for nonstandard installs |
481
540
  | `BAPI_TMUX_SESSION` | No | `bridge-start-tickets` | Override the tmux session-name prefix used by `start-tickets` on Linux |
541
+ | `BAPI_MCP_UPGRADE_ADVICE_ENABLED` | No | _(enabled)_ | MCP-local opt-out for proactively surfacing upgrade advice in pipeline recipe preambles. Set to `false`/`0`/`no`/`off`/`disabled` to suppress. Disabling it does **not** change the `/jira/ping` response or server-side upgrade computation — it only gates the recipe-preamble convention |
482
542
 
483
543
  ## Worktree credentials and the `mcp-invoke` shim
484
544
 
@@ -525,18 +585,40 @@ chmod 600 ~/.config/bridge/credentials.json
525
585
  `mcp-invoke` warns (but continues) if the file is group/world-readable, and it
526
586
  never creates or initializes the credential file for you.
527
587
 
588
+ ### Populating the credential store
589
+
590
+ The same store also backs the shell-spawned `start-tickets` CLI (its
591
+ difficulty→model routing runs in a Bash process that cannot see an `env` block in
592
+ `.mcp.json` / `.cursor/mcp.json`), so the store must hold the key for routing to
593
+ work. Two supported paths write it for you:
594
+
595
+ - **Install-time upsert.** `/install-bridge`'s final stage persists the validated
596
+ routing credential into `~/.config/bridge/credentials.json` (target
597
+ `bapi:<repo>`) via the `persist_routing_credential` MCP tool — the tool resolves
598
+ the key inside the MCP process and writes the store, so no secret crosses the
599
+ wire.
600
+ - **One-shot migration.** If a key currently lives only in `.mcp.json` /
601
+ `.cursor/mcp.json`, migrate it into the user-scoped store with the consent-gated
602
+ command (a compatibility aid, not a live fallback):
603
+
604
+ ```bash
605
+ npx -y @bridge_gpt/mcp-server credentials migrate-agent-config [--write-credentials]
606
+ ```
607
+
608
+ Run it without `--write-credentials` to preview; add the flag to write the store.
609
+
528
610
  ## Reference
529
611
 
530
612
  The full surface, for when you need the complete enumeration. Day-to-day, use [Usage Documentation](#usage-documentation) instead — you don't call MCP tools directly; you ask your AI assistant to perform a task, or compose tools into a pipeline.
531
613
 
532
614
  ### MCP tools
533
615
 
534
- The server registers **55 tools**. Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).
616
+ The server registers **62 tools**. Async AI tools follow a request/get pattern: call the `request_*` tool to kick off generation, then the matching `get_*` tool to retrieve the result (or pass `wait_for_result: true` to poll automatically).
535
617
 
536
618
  - **Connectivity & identity** — `ping`, `get_my_role`, `get_docs_dir`
537
619
  - **Jira tickets** — `get_tickets`, `get_ticket`, `create_ticket`, `update_ticket_description`, `add_comment`, `get_comments`
538
620
  - **Attachments** — `list_attachments`, `upload_attachment`, `download_attachment`
539
- - **AI generation (request/get)** — `request_plan_generation`/`get_plan`, `request_architecture`/`get_architecture`, `request_clarifying_questions`/`get_clarifying_questions`, `request_ticket_critique`/`get_ticket_critique`, `request_ticket_review`, `request_reimplement_context`/`get_reimplement_context`, `request_brainstorm`/`get_brainstorm`, `request_deep_research`/`get_deep_research`
621
+ - **AI generation (request/get)** — `request_plan_generation`/`get_plan`, `request_architecture`/`get_architecture`, `create_doc`/`get_doc` (design docs by `doc_type`: tdd/fsd/prd), `request_prd`/`get_prd`, `request_clarifying_questions`/`get_clarifying_questions`, `request_ticket_critique`/`get_ticket_critique`, `request_ticket_review`, `request_reimplement_context`/`get_reimplement_context`, `request_brainstorm`/`get_brainstorm`, `request_deep_research`/`get_deep_research`
540
622
  - **Other AI** — `second_opinion`, `generate_image`, `generate_decision_page`
541
623
  - **Ticket lifecycle** — `track_ticket`, `update_ticket_state`, `get_ticket_state`
542
624
  - **Jira status** — `get_jira_transitions`, `update_jira_status`, `resolve_target_status`
@@ -551,7 +633,7 @@ Pipelines are declarative, multi-step workflows your AI agent executes step-by-s
551
633
  | Pipeline | Description | Invoke with |
552
634
  |---|---|---|
553
635
  | `implement-ticket` | Generate a plan, execute the implementation, commit, open a PR, and monitor CI | `/implement-ticket PROJ-123` |
554
- | `review-ticket` | Two-round ticket quality review: clarifying questions and critique from multiple providers | `/review-ticket PROJ-123` |
636
+ | `review-ticket` | Full ticket quality review: initial clarifying questions + critique, automatic second-opinion pass (default), then evaluation and decision capture. Pass `--rounds=1` to skip the second-opinion step (`--rounds=2` is the default). | `/review-ticket PROJ-123` |
555
637
  | `idea-to-ticket` | Turn an idea into a Jira Task/Spike (or Epic + children) with research, dedup, and critique | `/idea-to-ticket "<idea>"` |
556
638
  | `plan-epic` | Decompose an epic into sub-tasks with a structured exploration doc for each | `/plan-epic "<epic>"` |
557
639
  | `full-automation` | Chain: idea → ticket(s) → review each → spawn worktrees to implement | `/full-automation "<idea>"` |
@@ -0,0 +1,272 @@
1
+ /**
2
+ * agent-config-credential-migration — read-only scan + consent-gated migration of
3
+ * a `BAPI_API_KEY` found in an agent's MCP config file (`.mcp.json` /
4
+ * `.cursor/mcp.json`) into the user-scoped, durable credential store.
5
+ *
6
+ * Why this exists (BAPI-377): a secret pasted into `.mcp.json`/`.cursor/mcp.json`
7
+ * is visible to the MCP server process but NOT to a Bash-spawned CLI (e.g.
8
+ * `start-tickets`), which is a different runtime surface. Difficulty→model
9
+ * routing runs in the spawned CLI, so it needs the credential in the durable
10
+ * `~/.config/bridge/credentials.json` store. This module locates that pasted key
11
+ * and, ONLY with explicit consent, upserts it into the store via the single
12
+ * mutation primitive {@link upsertBapiCredential}.
13
+ *
14
+ * Secret-safety contract: the extracted API key value is INTERNAL. It is carried
15
+ * only as far as the writer and NEVER returned to display/printing code paths,
16
+ * never logged, and never placed in any error message. Callers may only ever
17
+ * DISPLAY a candidate's `filePath`/`serverName`.
18
+ */
19
+ import path from "path";
20
+ import { upsertBapiCredential, } from "./credential-store.js";
21
+ import { resolveRequiredStartTicketsRepoName } from "./start-tickets-repo.js";
22
+ // ---------------------------------------------------------------------------
23
+ // Read-only parsing
24
+ // ---------------------------------------------------------------------------
25
+ /**
26
+ * Read-only parser for a single `.mcp.json` / `.cursor/mcp.json`. A missing file
27
+ * (`ENOENT`) yields `{state:"missing"}`. Invalid JSON yields `{state:"error"}`
28
+ * with a generic message that NEVER echoes file contents (the raw text could
29
+ * contain the secret). Otherwise the parsed (untyped) JSON is returned.
30
+ */
31
+ export async function readAgentMcpConfigIfPresent(filePath, readFile) {
32
+ let raw;
33
+ try {
34
+ raw = await readFile(filePath);
35
+ }
36
+ catch (err) {
37
+ const code = err && typeof err === "object" ? err.code : undefined;
38
+ if (code === "ENOENT") {
39
+ return { state: "missing" };
40
+ }
41
+ return { state: "error", error: `Unable to read agent MCP config at ${filePath}.` };
42
+ }
43
+ let json;
44
+ try {
45
+ json = JSON.parse(raw);
46
+ }
47
+ catch {
48
+ // Never echo the raw text — it could contain the secret.
49
+ return { state: "error", error: `Agent MCP config at ${filePath} is not valid JSON.` };
50
+ }
51
+ return { state: "present", json };
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Candidate extraction
55
+ // ---------------------------------------------------------------------------
56
+ /** Narrow an unknown to a plain (non-array) object. */
57
+ function isPlainObject(value) {
58
+ return value !== null && typeof value === "object" && !Array.isArray(value);
59
+ }
60
+ /**
61
+ * Walk the standard `mcpServers` object (and tolerate a top-level `servers` key,
62
+ * used by some configs). For each server whose `env.BAPI_API_KEY` is a non-empty
63
+ * string, emit a candidate retaining the source `filePath`, the `serverName` (the
64
+ * mcpServers key), the `topLevelKey` (`"mcpServers"` or `"servers"`), and the
65
+ * secret `apiKey`. Servers without a non-empty `BAPI_API_KEY` are ignored.
66
+ */
67
+ export function extractBapiApiKeyCandidates(filePath, json) {
68
+ const candidates = [];
69
+ if (!isPlainObject(json)) {
70
+ return candidates;
71
+ }
72
+ for (const topLevelKey of ["mcpServers", "servers"]) {
73
+ const serversBlock = json[topLevelKey];
74
+ if (!isPlainObject(serversBlock)) {
75
+ continue;
76
+ }
77
+ for (const [serverName, serverConfig] of Object.entries(serversBlock)) {
78
+ if (!isPlainObject(serverConfig)) {
79
+ continue;
80
+ }
81
+ const env = serverConfig.env;
82
+ if (!isPlainObject(env)) {
83
+ continue;
84
+ }
85
+ const rawKey = env.BAPI_API_KEY;
86
+ if (typeof rawKey !== "string") {
87
+ continue;
88
+ }
89
+ const apiKey = rawKey.trim();
90
+ if (apiKey.length === 0) {
91
+ continue;
92
+ }
93
+ candidates.push({ filePath, serverName, topLevelKey, apiKey });
94
+ }
95
+ }
96
+ return candidates;
97
+ }
98
+ export const AGENT_CONFIG_SOURCE_NAMES = [
99
+ ".mcp.json",
100
+ ".cursor/mcp.json",
101
+ ];
102
+ /**
103
+ * Resolve which agent-config files to scan under `cwd`. When `sources` is empty
104
+ * or omitted, BOTH files are scanned (the default). Otherwise only the named
105
+ * sources are scanned, preserving the canonical scan order. Unknown source
106
+ * tokens are ignored here (the CLI validates them before they reach this point).
107
+ */
108
+ export function resolveAgentConfigScanTargets(cwd, sources) {
109
+ const selected = sources && sources.length > 0
110
+ ? AGENT_CONFIG_SOURCE_NAMES.filter((name) => sources.includes(name))
111
+ : AGENT_CONFIG_SOURCE_NAMES;
112
+ return selected.map((name) => ({
113
+ name,
114
+ filePath: name === ".mcp.json"
115
+ ? path.join(cwd, ".mcp.json")
116
+ : path.join(cwd, ".cursor", "mcp.json"),
117
+ }));
118
+ }
119
+ /**
120
+ * Scan the selected agent-config files (`<cwd>/.mcp.json` and/or
121
+ * `<cwd>/.cursor/mcp.json`, one-shot via `path.join`) and aggregate candidates.
122
+ * `sources` restricts which files are scanned; empty/omitted scans BOTH. Missing
123
+ * files are skipped; parse errors are skipped (non-fatal) so one malformed config
124
+ * never blocks a migration from the other.
125
+ */
126
+ export async function scanAgentMcpConfigsForBapiApiKey(deps) {
127
+ const targets = resolveAgentConfigScanTargets(deps.cwd, deps.sources);
128
+ const candidates = [];
129
+ for (const { filePath } of targets) {
130
+ const result = await readAgentMcpConfigIfPresent(filePath, deps.readFile);
131
+ if (result.state !== "present") {
132
+ // Missing or malformed configs are simply skipped (not fatal).
133
+ continue;
134
+ }
135
+ candidates.push(...extractBapiApiKeyCandidates(filePath, result.json));
136
+ }
137
+ return candidates;
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Classification
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Classify a candidate set: `none` when empty; `single-value` when every
144
+ * candidate shares the same `apiKey` (returns that shared value + candidates);
145
+ * `conflicting-values` when candidates disagree on the key value. The returned
146
+ * object is internal — callers must only ever DISPLAY `filePath`/`serverName`.
147
+ */
148
+ export function classifyAgentConfigCredentialCandidates(candidates) {
149
+ if (candidates.length === 0) {
150
+ return { kind: "none" };
151
+ }
152
+ const firstValue = candidates[0].apiKey;
153
+ const allSame = candidates.every((c) => c.apiKey === firstValue);
154
+ if (allSame) {
155
+ return { kind: "single-value", value: firstValue, candidates };
156
+ }
157
+ return { kind: "conflicting-values", candidates };
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Migration (consent-gated)
161
+ // ---------------------------------------------------------------------------
162
+ /** Render the actually-scanned paths for a secret-free message (no values). */
163
+ function describeScannedFiles(cwd, sources) {
164
+ return resolveAgentConfigScanTargets(cwd, sources)
165
+ .map((t) => t.filePath)
166
+ .join(" and ");
167
+ }
168
+ /** Render candidate source locations for a secret-free message (no values). */
169
+ function describeCandidateSources(candidates) {
170
+ return candidates.map((c) => `${c.serverName} in ${c.filePath}`).join(", ");
171
+ }
172
+ /**
173
+ * Scan, classify, and (only with explicit consent) migrate a `BAPI_API_KEY` from
174
+ * `.mcp.json`/`.cursor/mcp.json` into the user-scoped credential store.
175
+ *
176
+ * Flow:
177
+ * 1. Resolve the repo name; failure → `repo-missing`.
178
+ * 2. Scan + classify.
179
+ * 3. `none` → `no-candidates` (message names the two scanned files, secret-free).
180
+ * 4. `!writeCredentials` → `consent-required` WITHOUT writing (re-run with
181
+ * `--write-credentials`); lists only source paths/server names.
182
+ * 5. `conflicting-values` → with `promptChoice`, pick by index (null → aborted);
183
+ * without it, refuse with `conflicting-values` (lists only filePath/serverName).
184
+ * 6. `single-value` → use that candidate.
185
+ * 7. `upsertBapiCredential` and map its result.
186
+ *
187
+ * The api key value never appears in any returned field or message.
188
+ */
189
+ export async function migrateAgentConfigCredentialToStore(deps) {
190
+ // 1. Repo identity (shared resolver so the store target cannot drift).
191
+ const repoResult = await resolveRequiredStartTicketsRepoName({
192
+ env: deps.env,
193
+ cwd: deps.cwd,
194
+ readFile: deps.readFile,
195
+ });
196
+ if (!repoResult.ok) {
197
+ return { ok: false, kind: "repo-missing", message: repoResult.error };
198
+ }
199
+ // 2. Scan + classify (honoring any --source restriction).
200
+ const candidates = await scanAgentMcpConfigsForBapiApiKey({
201
+ cwd: deps.cwd,
202
+ readFile: deps.readFile,
203
+ sources: deps.sources,
204
+ });
205
+ const classification = classifyAgentConfigCredentialCandidates(candidates);
206
+ // 3. Nothing found.
207
+ if (classification.kind === "none") {
208
+ return {
209
+ ok: false,
210
+ kind: "no-candidates",
211
+ message: `No BAPI_API_KEY found in ${describeScannedFiles(deps.cwd, deps.sources)}.`,
212
+ };
213
+ }
214
+ // 4. Consent gate — preview only, no write.
215
+ if (!deps.writeCredentials) {
216
+ return {
217
+ ok: false,
218
+ kind: "consent-required",
219
+ message: `Found a BAPI_API_KEY to migrate (${describeCandidateSources(classification.candidates)}). ` +
220
+ `Re-run with --write-credentials to store it in the user-scoped credential store.`,
221
+ candidates: classification.candidates,
222
+ };
223
+ }
224
+ // 5/6. Resolve a single chosen candidate.
225
+ let chosenCandidate;
226
+ if (classification.kind === "conflicting-values") {
227
+ if (!deps.promptChoice) {
228
+ return {
229
+ ok: false,
230
+ kind: "conflicting-values",
231
+ message: `Found conflicting BAPI_API_KEY values across ` +
232
+ `${describeCandidateSources(classification.candidates)}. ` +
233
+ `Re-run interactively to choose, or reconcile the configs so they agree.`,
234
+ candidates: classification.candidates,
235
+ };
236
+ }
237
+ const chosenIndex = await deps.promptChoice(classification.candidates);
238
+ if (chosenIndex === null) {
239
+ return {
240
+ ok: false,
241
+ kind: "aborted",
242
+ message: "Migration aborted: no credential source was chosen.",
243
+ };
244
+ }
245
+ const picked = classification.candidates[chosenIndex];
246
+ if (!picked) {
247
+ return {
248
+ ok: false,
249
+ kind: "aborted",
250
+ message: "Migration aborted: the chosen credential source was out of range.",
251
+ };
252
+ }
253
+ chosenCandidate = picked;
254
+ }
255
+ else {
256
+ // single-value — use the first candidate (all share the same value).
257
+ chosenCandidate = classification.candidates[0];
258
+ }
259
+ // 7. Write via the single mutation primitive.
260
+ const result = await upsertBapiCredential(repoResult.repoName, chosenCandidate.apiKey, deps);
261
+ if (!result.ok) {
262
+ return { ok: false, kind: result.kind, message: result.error };
263
+ }
264
+ return {
265
+ ok: true,
266
+ action: result.action,
267
+ target: result.target,
268
+ path: result.path,
269
+ sourceFilePath: chosenCandidate.filePath,
270
+ sourceServerName: chosenCandidate.serverName,
271
+ };
272
+ }
@@ -8,6 +8,6 @@ export const AGENTS = {
8
8
  "model": "opus",
9
9
  "color": "blue"
10
10
  },
11
- "body": "\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user's problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the key source files thoroughly — do not skim\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific — reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` — `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` — [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n```\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the \"why\" — what problem does this solve or what value does it add?\n- Mention the general technical approach if it's clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: \"extend this function\", \"follow this pattern\", \"reuse this helper\", \"modify this configuration\"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira's ADF has no native checkbox, so `- [ ]` renders as literal text)\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links.\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace \"should handle errors properly\" with \"should catch LLM provider timeouts and return a normalized error response with errorType 'TimeoutError'\"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you're unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn't recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Do NOT skip or abbreviate the research phase. The quality of the ticket depends entirely on the depth of your codebase understanding.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file — do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket's requirements align with those conventions.\n"
11
+ "body": "\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user's problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the key source files thoroughly — do not skim\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific — reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` — `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` — [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n\n## Materials & Access\n\n[Trailing audit-trail section — always the LAST section of the draft. Inventory every material the ticket references, grouped by source. Use monospace backticks for file paths and other technical provenance. Redact any embedded secrets.]\n\n### Reachable Local Files\n\n- `path/to/local/file.ext` — [what it is; will be gathered and attached post-create]\n\n### External/Auth-Gated Links\n\n- [Name or purpose] — `https://example.com/...` (record-only; external/auth-gated)\n\n### Binary/Image Materials (Record-Only)\n\n- `path/to/screenshot.png` — [sanitized location/access note; not attached]\n```\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the \"why\" — what problem does this solve or what value does it add?\n- Mention the general technical approach if it's clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: \"extend this function\", \"follow this pattern\", \"reuse this helper\", \"modify this configuration\"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira's ADF has no native checkbox, so `- [ ]` renders as literal text)\n\n**Materials Completeness Inventory**:\n- After the draft is written, INVENTORY every material the ticket references: local file paths, URLs/links, named docs/designs, screenshots, and specs. This pass only INVENTORIES and RECORDS — it does NOT attach anything. The actual attachment of reachable local files happens post-create (after the Jira `ticket_key` exists) via a separate gather-and-attach step.\n- Classify each material by source using a scheme-based rule (no network probe required):\n - **Local filesystem paths** named in the ticket body are the only **low-risk** materials — eligible to be gathered and attached post-create.\n - Every **`http(s)` URI is external/auth-gated** — regardless of whether the user explicitly linked it (an explicitly-linked Confluence or Google Doc URL is still external/auth-gated) — and is **record-only** here.\n - **Binary/image materials** (screenshots, PDFs, etc.) are **record-only** — document them with sanitized location/access notes; do NOT attempt to attach them.\n- Write the trailing `## Materials & Access` section (the LAST section of the draft) grouping items under the sub-headings *Reachable Local Files*, *External/Auth-Gated Links*, and *Binary/Image Materials (Record-Only)*, using bulleted lists. Use monospace formatting (backticks) for technical provenance such as file paths.\n- **Redact secrets before writing anything**: before writing any URL or access note, sanitize and redact embedded credentials, SAS tokens, API keys, and basic-auth secrets using a high-visibility placeholder such as `[REDACTED_TOKEN]`. A location/access note must NEVER expose a plaintext secret.\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links. This \"No images\" rule applies strictly to inline images in the description body; it does NOT restrict the attachments produced by the Materials Completeness Inventory / gather-and-attach pass.\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace \"should handle errors properly\" with \"should catch LLM provider timeouts and return a normalized error response with errorType 'TimeoutError'\"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you're unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn't recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Do NOT skip or abbreviate the research phase. The quality of the ticket depends entirely on the depth of your codebase understanding.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file — do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket's requirements align with those conventions.\n"
12
12
  }
13
13
  };
@@ -721,6 +721,20 @@ export function resolveStartTicketKeys(row, stageIndex, fanOutInput) {
721
721
  * only records the completed child and increments current_child_index; the
722
722
  * caller's loop decides when the whole stage is finished.
723
723
  */
724
+ /**
725
+ * Format a child pipeline's recipe preamble for inclusion in a chain pause
726
+ * instruction (BAPI-375). Returns an empty string when the child carries no
727
+ * preamble (or only whitespace); otherwise the trimmed preamble followed by a
728
+ * blank-line separator. This is what surfaces the child recipe's shared
729
+ * conventions — including the upgrade-advice surfacing convention — to the
730
+ * agent driving a paused full-automation chain.
731
+ */
732
+ function formatChildPreamble(childEnv) {
733
+ const preamble = childEnv.preamble;
734
+ if (typeof preamble !== "string" || preamble.trim() === "")
735
+ return "";
736
+ return `${preamble.trim()}\n\n`;
737
+ }
724
738
  async function handleChildPipelineEnvelope(persistence, recipe, row, childEnv, opts) {
725
739
  const idx = row.current_stage_index;
726
740
  const stageRecipe = recipe.stages[idx];
@@ -748,7 +762,8 @@ async function handleChildPipelineEnvelope(persistence, recipe, row, childEnv, o
748
762
  catch (err) {
749
763
  return { kind: "fail", envelope: persistenceFailEnvelope(err, row, recipe) };
750
764
  }
751
- const instruction = `${childEnv.instruction}` +
765
+ const instruction = `${formatChildPreamble(childEnv)}` +
766
+ `${childEnv.instruction}` +
752
767
  `${buildPriorResultsAppendix(childEnv.results)}\n\n` +
753
768
  `When done, call \`resume_full_automation\` with \`chain_run_id\` ` +
754
769
  `"${row.chain_run_id}" and \`agent_result\` set to the result described above.`;