@bridge_gpt/mcp-server 0.2.52 → 0.2.54

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 (52) hide show
  1. package/README.md +121 -15
  2. package/build/agent-launchers/claude.js +3 -3
  3. package/build/agent-launchers/prompt.js +8 -11
  4. package/build/base-ref.js +33 -9
  5. package/build/bounded-wait.js +174 -0
  6. package/build/commands.generated.js +7 -5
  7. package/build/conductor/bridge-api-client.js +97 -8
  8. package/build/conductor/cli.js +23 -0
  9. package/build/conductor/doctor.js +428 -5
  10. package/build/conductor/epic-runtime.js +133 -97
  11. package/build/conductor/install-doctor.js +65 -656
  12. package/build/conductor/readiness-cli.js +152 -0
  13. package/build/conductor/readiness-sections.js +666 -0
  14. package/build/conductor/readiness.js +795 -0
  15. package/build/conductor/run-branch.js +137 -0
  16. package/build/conductor/test-run-branch-vectors.js +165 -0
  17. package/build/conductor/tools.js +56 -3
  18. package/build/conductor-bin.js +21 -17
  19. package/build/doctor.js +68 -1
  20. package/build/drive-epic.js +287 -51
  21. package/build/executor/claim-scope.js +104 -0
  22. package/build/executor/cli.js +14 -25
  23. package/build/executor/env-file-guard.js +82 -3
  24. package/build/executor/job-runner.js +60 -0
  25. package/build/index.js +4496 -4697
  26. package/build/install-doctor.js +154 -2
  27. package/build/local-artifact-storage.js +130 -0
  28. package/build/pipelines.generated.js +17 -10
  29. package/build/plane/alembic-head.js +40 -11
  30. package/build/plane/build-freshness.js +22 -11
  31. package/build/plane/cli.js +285 -36
  32. package/build/plane/manifest.js +209 -1
  33. package/build/plane/member-roster.js +70 -0
  34. package/build/plane/preflight.js +363 -48
  35. package/build/plane/shutdown.js +14 -1
  36. package/build/plane/status.js +35 -1
  37. package/build/plane/supervisor.js +546 -164
  38. package/build/plane/types.js +61 -2
  39. package/build/polling-policy.js +72 -0
  40. package/build/readiness-check.js +412 -0
  41. package/build/readme.generated.js +1 -1
  42. package/build/review-generation.js +219 -0
  43. package/build/run-unit-tests-launcher.js +5 -0
  44. package/build/setup-epic.js +514 -23
  45. package/build/ticket-key-utils.js +4 -3
  46. package/build/ticket-review-artifact-gate.js +461 -0
  47. package/build/upgrade-cli.js +5 -26
  48. package/build/version.generated.js +3 -3
  49. package/docs/install/mcp-tool-integrations.md +23 -1
  50. package/package.json +2 -2
  51. package/pipelines/{full-automation.json → idea-to-pr.json} +1 -1
  52. package/pipelines/review-ticket.json +17 -4
package/README.md CHANGED
@@ -352,7 +352,7 @@ later boot.
352
352
  "mcpServers": {
353
353
  "bridge": {
354
354
  "command": "npx",
355
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"],
355
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"],
356
356
  "env": {
357
357
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
358
358
  "BAPI_REPO_NAME": "your-repo",
@@ -374,7 +374,7 @@ later boot.
374
374
  "bridge": {
375
375
  "type": "stdio",
376
376
  "command": "npx",
377
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"],
377
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"],
378
378
  "env": {
379
379
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
380
380
  "BAPI_REPO_NAME": "your-repo",
@@ -396,7 +396,7 @@ later boot.
396
396
  "bridge": {
397
397
  "type": "stdio",
398
398
  "command": "npx",
399
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"],
399
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"],
400
400
  "env": {
401
401
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
402
402
  "BAPI_REPO_NAME": "your-repo",
@@ -421,7 +421,7 @@ you select `copilot-cli`; the shape below is what it produces.
421
421
  "bridge": {
422
422
  "type": "local",
423
423
  "command": "npx",
424
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"],
424
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"],
425
425
  "tools": ["*"],
426
426
  "env": {
427
427
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
@@ -445,7 +445,7 @@ Windsurf only supports global MCP configuration.
445
445
  "mcpServers": {
446
446
  "bridge": {
447
447
  "command": "npx",
448
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"],
448
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"],
449
449
  "env": {
450
450
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
451
451
  "BAPI_REPO_NAME": "your-repo",
@@ -464,7 +464,7 @@ Windsurf only supports global MCP configuration.
464
464
  ```toml
465
465
  [mcp_servers.bridge]
466
466
  command = "npx"
467
- args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.52", "serve"]
467
+ args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"]
468
468
 
469
469
  [mcp_servers.bridge.env]
470
470
  BAPI_BASE_URL = "https://bridgegpt-api.com"
@@ -570,7 +570,7 @@ The tools worth knowing for most tickets.
570
570
  **1. Review Ticket**
571
571
  - **What it does:** Runs a full quality review of a ticket: clarifying questions and a critique plus an alternate-model second opinion, then evaluates the findings and produces a decision page for accepting or rejecting them.
572
572
  - **When it's useful:** (Refinement) Right after a ticket is drafted, before anyone starts building — to surface gaps and tighten it.
573
- - **How to use it:** `/review-ticket BAPI-123` (command only — "review" as free text is easily mistaken for a freehand agent review). For several tickets at once, `/review-tickets BAPI-123 BAPI-456` opens one terminal tab per ticket and reviews them in parallel with no worktrees; every `/review-ticket` flag applies, and `--review KEY=auto,rounds=N` sets per-ticket overrides.
573
+ - **How to use it:** `/review-tickets BAPI-123` (command only — "review" as free text is easily mistaken for a freehand agent review). A single key reviews inline in the current session — no terminal tab, no worktree. For several tickets at once, `/review-tickets BAPI-123 BAPI-456` opens one terminal tab per ticket and reviews them in parallel with no worktrees; every review flag applies, and `--review KEY=auto,rounds=N` sets per-ticket overrides.
574
574
  - **Flags:** `--auto` auto-accept findings and skip the approval gates · `--rounds=1` a cheaper single-pass review that still evaluates findings and captures decisions · `--rounds=2` force the full second-opinion review · omit `--rounds` to let the difficulty-adaptive review policy decide.
575
575
 
576
576
  **2. Start Tickets**
@@ -581,7 +581,7 @@ The tools worth knowing for most tickets.
581
581
 
582
582
  **3. Review and Start**
583
583
  - **What it does:** Spawns one worktree per ticket; each session reviews the ticket inline and, after a per-ticket proceed/halt gate, hands off to a **fresh implementation session** that reuses the same worktree — review and implementation run in two separate agent contexts, not one shared session.
584
- - **When it's useful:** (Refinement | Implementation | Automation) The **recommended front door** for "review these tickets, then implement the ones that pass," starting from existing ticket keys (unlike `/full-automation`, which only accepts an idea).
584
+ - **When it's useful:** (Refinement | Implementation | Automation) The **recommended front door** for "review these tickets, then implement the ones that pass," starting from existing ticket keys (unlike `/idea-to-pr`, which only accepts an idea).
585
585
  - **How to use it:** `/review-and-start BAPI-248 BAPI-250` (single or multiple keys flow through the identical code path).
586
586
  - **Flags:** `--auto` auto-approves both the review and the implementation phase of every spawned session · `--rounds=1|2` forwarded to the review phase · `--agent`, `--base-branch`, `--max-parallel`, `--dry-run` mirror `/start-tickets`.
587
587
 
@@ -637,6 +637,12 @@ Good to know, but not needed every day.
637
637
  - **How to use it:** Ask your agent — "Generate an image of a dashboard showing SOC2 evidence freshness as a traffic-light grid."
638
638
  - **Flags:** `provider` openai (`gpt-image-2`) / gemini (Imagen, which adds an invisible SynthID watermark) · `quality` low (default, cheapest) / medium / high · `size` 1024x1024 / 1024x1536 / 1536x1024. The image is saved to `BAPI_DOCS_DIR/images/` and also returned inline.
639
639
 
640
+ **6. Create Decision Page**
641
+ - **What it does:** Turns open decisions from the current conversation into an interactive HTML decision page, then folds the submitted answers back into the conversation.
642
+ - **When it's useful:** (Refinement) Use it whenever a conversation has accumulated more open choices than are comfortable to settle in prose — not for a single simple question, which is faster to just ask directly.
643
+ - **How to use it:** `/create-decision-page <topic or open questions>` — e.g. *"how we handle rate limiting"* — or leave it empty to frame the open choices already raised earlier in the conversation.
644
+ - **Flags:** None.
645
+
640
646
  ### Now and then
641
647
 
642
648
  Useful once in a while.
@@ -688,11 +694,35 @@ Useful once in a while.
688
694
  - **When it's useful:** (Architecture | Refinement) When you need a sizing pass across an epic, or across a set of tickets you name explicitly.
689
695
  - **How to use it:** `/estimate-epic BAPI-123`
690
696
  - **Flags:** Pass an epic key, or an explicit list of ticket keys to estimate as one group.
697
+
698
+ **9. Regression Check**
699
+ - **What it does:** Runs the deterministic regression-reviewer (lightweight mode) against a proposed code change and reports its blast radius — which real call-sites, tests, mocks, or config the change does and doesn't account for.
700
+ - **When it's useful:** (Correctness) Before merging a change, to see which real callers, tests, or config it leaves unaccounted for.
701
+ - **How to use it:** `/regression-check` reviews the working tree against `HEAD` by default; `/regression-check --diff main...HEAD` reviews an explicit range.
702
+ - **Flags:** `--diff <range>` review a specific diff/PR range instead of the working tree vs. `HEAD` · `--symbols a,b,c` review a not-yet-diffed planned change by naming its symbols directly.
703
+
704
+ **10. Scan Test Coverage**
705
+ - **What it does:** Scans recently shipped tickets from git history and reports which features have or could gain integration tests, and which can only be smoke tested.
706
+ - **When it's useful:** (Testing) Periodically, to find recently shipped features still missing integration-test coverage and how each could be smoke tested instead.
707
+ - **How to use it:** `/scan-test-coverage` runs incrementally from the last recorded marker in `docs/test-coverage/STATE.md`.
708
+ - **Flags:** `--since=YYYY-MM-DD` override the window start date · `--full` ignore the marker and use a 6-month lookback · `--limit=N` cap the number of features investigated.
709
+
710
+ **11. Estimate Ticket**
711
+ - **What it does:** One-ticket shortcut over the existing epic estimation capability: estimates a single Jira ticket via the same `estimate_epic` tool.
712
+ - **When it's useful:** (Estimation) When you want one ticket's estimate without typing out an epic key or an explicit ticket list.
713
+ - **How to use it:** `/estimate-ticket BAPI-123`
714
+ - **Flags:** `--allow-partial` the sole optional flag.
715
+
716
+ **12. Draft Ticket**
717
+ - **What it does:** Delegates to the Jira Ticket Writer to create only a local Markdown draft — it does not create or update a Jira issue.
718
+ - **When it's useful:** (Refinement) When you want a reviewable local ticket draft before committing to an upload workflow.
719
+ - **How to use it:** `/draft-ticket <description>`
720
+ - **Flags:** None.
691
721
  <!-- END GENERATED: mcp-tool-documentation -->
692
722
 
693
723
  ### Workflow commands
694
724
 
695
- Slash commands that drive several tools at once. Start Tickets, Review and Start, and Explore Ticket are documented above under [Regularly useful](#regularly-useful) — the rest live here.
725
+ Slash commands that drive several tools at once. Start Tickets, Review and Start, and Explore Ticket are documented above under [Regularly useful](#regularly-useful); Create Decision Page is documented above under [Occasionally useful](#occasionally-useful); Regression Check and Scan Test Coverage are documented above under [Now and then](#now-and-then) — the rest live here.
696
726
 
697
727
  **1. Implement Ticket**
698
728
  - **What it does:** Full build for one ticket: generate a plan, write the code, commit, open a PR, and monitor CI.
@@ -1060,12 +1090,54 @@ all, because an unknown owner is not the same as a not-ready one. No branch,
1060
1090
  including every error path, ever offers you two paths.
1061
1091
 
1062
1092
  `drive-epic` owns no branch behavior of its own — it forwards `--feature-branch
1063
- <name>` and `--into-base` verbatim to `setup-epic`, which decides the strategy (see
1064
- below). With neither flag, a multi-ticket epic runs on `epic/<KEY>`.
1093
+ <name>`, `--into-base`, and `--attended` verbatim to `setup-epic`, which decides
1094
+ the strategy and the posture (see below). With none of them, a multi-ticket epic
1095
+ runs on `epic/<KEY>`, unattended.
1096
+
1097
+ **The composed bring-up is two-phase (BAPI-1102).** With `--plan-file` on a
1098
+ repository whose readiness is missing only the runtime facts, one invocation runs,
1099
+ in order: the **control plane** (Bridge API server + reconciler, each gated on its
1100
+ own readiness), then `setup-epic` to create and approve the run, then **executor
1101
+ lanes scoped to that run** with `--epic-run-id <run>`, then the **dead-man
1102
+ observer** last, then the ready banner. The order is forced: an executor lane must
1103
+ carry an explicit claim scope, and the run it is scoped to does not exist until the
1104
+ server is up and `setup-epic` has run.
1105
+
1106
+ A composed plane serves **exactly one** run; start a second plane for a second
1107
+ epic. If `setup-epic` fails, the control plane is left running and **no executor
1108
+ lane is spawned**. If the lanes fail to become ready, the control plane and the
1109
+ committed run are both left intact and the observer is not started — nothing is
1110
+ rolled back, because a local process that failed to start says nothing about
1111
+ whether the run is valid.
1112
+
1113
+ "Plane ready" reports **processes**, not dispatchability: if the run cuts an epic
1114
+ branch, the reconciler holds ticket dispatch until that branch's index scope
1115
+ finishes preparing.
1065
1116
 
1066
1117
  Two conductors is a transitional state. When one is eliminated, `drive-epic` is
1067
1118
  the only thing that changes.
1068
1119
 
1120
+ ### `plane up` — claim scope is required
1121
+
1122
+ `plane up` starts the runtime on its own, and every executor lane it starts needs
1123
+ an explicit claim scope:
1124
+
1125
+ ```
1126
+ npx -y @bridge_gpt/mcp-server plane up --epic-run-id <run-id> # repeatable
1127
+ npx -y @bridge_gpt/mcp-server plane up --repo-wide # or repository-wide
1128
+ ```
1129
+
1130
+ The two are mutually exclusive. Asked to start lanes with neither, `plane up`
1131
+ **refuses before spawning anything**, in the executor's own words. That is not a
1132
+ new restriction — an executor started with no claim scope has refused to start
1133
+ since BAPI-1026 — it is that refusal moved to where it is actionable, instead of
1134
+ surfacing as a member crash and a full plane rollback.
1135
+
1136
+ `plane status` reports the lifecycle phase (control plane starting/ready, lanes
1137
+ starting/ready, observer active, plane ready) and the epic run the plane is bound
1138
+ to. `plane down` handles every intermediate phase: it stops the bound run first,
1139
+ then signals the recorded process group.
1140
+
1069
1141
  ### `setup-epic`
1070
1142
 
1071
1143
  Bootstraps an Epic Conductor v2 run in one command — creates the epic run, stores the plan DAG, and approves it:
@@ -1088,6 +1160,39 @@ branch, matching plain `start-tickets`.
1088
1160
  > name, or to force a branch for a single-node plan. Passing both is a parse error,
1089
1161
  > refused before any file read, credential resolution, or network call.
1090
1162
 
1163
+ **A run that selects an epic branch is created UNATTENDED (BAPI-1102).** Its
1164
+ `policy_json` carries `posture: "unattended"` and `v2_auto_merge_enabled: true`, so
1165
+ child PRs merge into the epic branch without parking for a human at every gate. The
1166
+ **integration PR into the repository base branch stays human-gated.** The run's CI
1167
+ gate is stamped **server-side at first approval** — from your repository's declared
1168
+ default, or, when it declares none, as an explicit recorded `no_ci_gate` mode.
1169
+
1170
+ > **Behavior change (BAPI-1102).** Pass `--attended` to restore the previous
1171
+ > composition: `base_branch` plus an explicitly requested `--review-policy`, with no
1172
+ > posture and no auto-merge authorization. `--into-base`, single-node plans, and
1173
+ > branch-silent invocations are **unchanged** — they send no `policy_json` at all,
1174
+ > byte for byte as before. A `--policy-file` still supersedes composition entirely;
1175
+ > combining `--attended` with a file that declares `posture: "unattended"` is a named
1176
+ > refusal rather than a silent winner.
1177
+
1178
+ An unattended run has two one-time **repository** prerequisites — consent
1179
+ (`unattended_conductor_allowed`) and a verified notify webhook default — plus
1180
+ confirmed default-branch review/CI workflows. Each is refused by name **before any
1181
+ run row is created** (`target_not_allowed`, `webhook_unverified`,
1182
+ `repository_readiness_unconfirmed`), and `drive-epic` readiness and `doctor` both
1183
+ list them with their current state. `notify.local_sink` does not satisfy the webhook
1184
+ prerequisite.
1185
+
1186
+ > **Rollout note.** The client-side default and the server-side admission,
1187
+ > stamping, and readiness changes must be released as **one compatible unit**. The
1188
+ > new default's shape (`v2_auto_merge_enabled: true` with no `required_checks`) is
1189
+ > exactly what the create route refused before BAPI-1102, so shipping the client
1190
+ > half alone would 422 every unattended run at `POST /jira/epic-runs/runs`.
1191
+ > Deploying only the server half is safe and inert: it narrows two refusals and adds
1192
+ > a first-approval stamp, and **rewrites no existing run**. No database migration and
1193
+ > no new environment variable is introduced — `config_projects.unattended_conductor_allowed`
1194
+ > and `epic_supervisor_setup.done_gate_config` already exist and are read as-is.
1195
+
1091
1196
  Once the plan is approved, the **server-side reconciler** picks the run up within ~30s. To execute claimed jobs on your machine, run `executor`:
1092
1197
 
1093
1198
  ```
@@ -1457,17 +1562,18 @@ The full surface, for when you need the complete enumeration. Day-to-day, use [U
1457
1562
 
1458
1563
  ### MCP tools
1459
1564
 
1460
- The authoritative tool catalog covers **72 tools** (enumerated below). What's actually registered in a session depends on `BRIDGE_MCP_PROFILE`: the default `core` profile loads a trimmed subset, and the conductor/pipeline-authoring/SFCC tools are added only under their respective profiles (see [Environment Variables](#environment-variables)). 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).
1565
+ The authoritative tool catalog covers **75 tools** (enumerated below). What's actually registered in a session depends on `BRIDGE_MCP_PROFILE`: the default `core` profile loads a trimmed subset, and the conductor/pipeline-authoring/SFCC tools are added only under their respective profiles (see [Environment Variables](#environment-variables)). 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).
1461
1566
 
1462
1567
  - **Connectivity & identity** — `ping` (its JSON also carries `docs_dir`, `role`, and `customer_type`)
1463
1568
  - **Team & access** — `invite_member` (admin-only; mints a scoped access key for a teammate on an already-configured project — the plaintext key is shown exactly once)
1464
1569
  - **Jira tickets** — `get_tickets`, `get_ticket`, `create_ticket`, `update_ticket_description`, `add_comment`, `get_comments`
1465
1570
  - **Attachments** — `attachment` (operations: `upload`, `download`, `list`)
1466
1571
  - **AI generation (request/get)** — `request_plan_generation`/`get_plan`, `create_doc`/`get_doc` (design docs by `doc_type`: tdd/architecture/fsd/prd, where `architecture` is an alias of `tdd`), `request_ticket_review` (writes both `get_clarifying_questions` and `get_ticket_critique`), `request_reimplement_context`/`get_reimplement_context`, `request_council`/`get_council`, `request_deep_research`/`get_deep_research`
1572
+ - **Bounded waits** — `wait_for_ticket_review`, `wait_for_ci_checks`. Each call waits at most 240 s and returns `state: "ready" | "pending" | "error"`; `pending` is a normal, resumable result you call again on, never a failure. They exist so an agent can wait without a foreground `sleep` and without binding a long server-side operation to the 900 s MCP client deadline. `wait_for_ci_checks` reports only that checks are **terminal** — deciding pass or fail stays with the caller.
1467
1573
  - **Other AI** — `second_opinion`, `generate_image`, `generate_decision_page`, `visual_diff` (deterministic pixel diff of a rendered URL vs a design comp)
1468
1574
  - **Ticket lifecycle** — `track_ticket`, `update_ticket_state`, `get_ticket_state`, `get_ticket_state_tree` (live repo-wide lifecycle + dependency tree; read-only, no mutation parameter)
1469
1575
  - **Jira status** — `update_jira_status` (pass `status: "auto"` to resolve the configured post-PR status server-side)
1470
- - **Repository & CI** — `parse_repository` (`action`: `start`, `status`), `regenerate_directory_map`, `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`
1576
+ - **Repository & CI** — `parse_repository` (`action`: `start`, `status`), `regenerate_directory_map`, `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`, `wait_for_ci_checks` (the last two are hidden until `ci_check_config` resolves, and share one visibility gate)
1471
1577
  - **Pipelines & automation** — `get_pipeline_recipe` (returns a fully resolved recipe the agent executes step-by-step)
1472
1578
  - **Config** — `get_project_standards`, `config_field` (operations: `get`, `update`, `list`)
1473
1579
 
@@ -1478,10 +1584,10 @@ Pipelines are declarative, multi-step workflows your AI agent executes step-by-s
1478
1584
  | Pipeline | Description | Invoke with |
1479
1585
  |---|---|---|
1480
1586
  | `implement-ticket` | Generate a plan, execute the implementation, commit, open a PR, and monitor CI | `/implement-ticket PROJ-123` |
1481
- | `review-ticket` | Full ticket quality review: clarifying questions + critique plus an automatic second-opinion pass, then evaluation and decision capture. The backend owns round orchestration; pass `--rounds=1` for a single-pass review or `--rounds=2` to force the full second-opinion review, or omit `--rounds` to let the backend decide adaptively. | `/review-ticket PROJ-123` |
1587
+ | `review-ticket` | Full ticket quality review: clarifying questions + critique plus an automatic second-opinion pass, then evaluation and decision capture. The backend owns round orchestration; pass `--rounds=1` for a single-pass review or `--rounds=2` to force the full second-opinion review, or omit `--rounds` to let the backend decide adaptively. Internal atomic pipeline invoked by `/review-tickets` (inline for one key, spawned per tab for several) and by `/review-and-implement`, `/review-and-start`, `/start-tickets`, and `/conduct-epic`. | `/review-tickets PROJ-123` |
1482
1588
  | `idea-to-ticket` | Turn an idea into a Jira Task/Spike (or Epic + children) with research, dedup, and critique | `/idea-to-ticket "<idea>"` |
1483
1589
  | `plan-epic` | Decompose an epic into sub-tasks with a structured exploration doc for each | `/plan-epic "<epic>"` |
1484
- | `full-automation` | Client-side wrapper: runs the `idea-to-ticket` recipe, then hands the created keys to `/review-and-start --auto` | `/full-automation "<idea>"` |
1590
+ | `full-automation` | Client-side wrapper: runs the `idea-to-ticket` recipe, then hands the created keys to `/review-and-start --auto` | `/idea-to-pr "<idea>"` |
1485
1591
  | `learn-repository` | Analyze codebase architecture, testing, review, and documentation standards, then upload to Bridge | `/learn-repository` |
1486
1592
 
1487
1593
  ### Pipeline response envelope
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Resolves the `claude` binary against the *explicitly supplied PATH* (not the
5
5
  * ambient process default) and builds the launched-run invocation by delegating
6
- * to the shared `renderScheduledPrompt` renderer (no hard-coded `/full-automation`
6
+ * to the shared `renderScheduledPrompt` renderer (no hard-coded per-command
7
7
  * prompt). It emits `{ exe, args: ["-p", prompt] }`. Claude Code has no
8
8
  * working-directory flag, so the cwd is always set by the launching unit — this
9
9
  * adapter must never add a working-directory argument to the invocation.
@@ -41,8 +41,8 @@ export async function resolveCommandOnPath(command, envPath, deps) {
41
41
  return pathApi.normalize(candidate);
42
42
  }
43
43
  /**
44
- * Backward-compatibility shim for callers that quoted an idea-file path for a
45
- * `/full-automation` prompt. Generalized prompt-token quoting now lives in
44
+ * Backward-compatibility shim for callers that quoted an idea-file path for the
45
+ * pre-rename automation prompt. Generalized prompt-token quoting now lives in
46
46
  * `quotePromptToken`; this preserves the original quote semantics (always wrap in
47
47
  * double quotes, escape embedded quotes) for any remaining path callers.
48
48
  */
@@ -32,15 +32,14 @@ function schemaSupportsAutoFlag(schema) {
32
32
  * the normalized `input.args`, plus (only where the command can parse them)
33
33
  * `--scheduled-at <ISO>` and `--auto`.
34
34
  *
35
- * `--scheduled-at` is appended for `full-automation` (legacy) and `epic-tick`
36
- * (BAPI-418), both of whose parsers accept it as a first-class argument. Every
37
- * other command rejects unrecognized flags and halts (e.g. `start-tickets` stops
38
- * on any unsupported flag), and the shared late-fire gate already embeds the
39
- * scheduled time so injecting `--scheduled-at` into their argv would break an
40
- * otherwise launchable command for no benefit.
35
+ * `--scheduled-at` is appended for `epic-tick` (BAPI-418), whose parser accepts
36
+ * it as a first-class argument. Every other command rejects unrecognized flags
37
+ * and halts (e.g. `start-tickets` stops on any unsupported flag), and the shared
38
+ * late-fire gate already embeds the scheduled time so injecting `--scheduled-at`
39
+ * into their argv would break an otherwise launchable command for no benefit.
41
40
  *
42
41
  * `--auto` is appended when auto-approve is set AND the command supports it (its
43
- * schema declares a boolean `--auto` flag, or it is `full-automation` / `epic-tick`).
42
+ * schema declares a boolean `--auto` flag, or it is `epic-tick`).
44
43
  * It is never duplicated if already present in `input.args`.
45
44
  *
46
45
  * This is the SINGLE source of the delegated argv: both the rendered target
@@ -54,12 +53,10 @@ function buildAugmentedArgs(input) {
54
53
  // epic-tick already accepts --scheduled-at (parseEpicTickArgs, cli.ts) so it
55
54
  // receives the scheduled time as a structured arg for the late-fire decision,
56
55
  // not just via the embedded gate text.
57
- if (input.commandName === "full-automation" || input.commandName === "epic-tick") {
56
+ if (input.commandName === "epic-tick") {
58
57
  args.push("--scheduled-at", input.runAtIso);
59
58
  }
60
- const supportsAuto = schemaSupportsAutoFlag(input.schema) ||
61
- input.commandName === "full-automation" ||
62
- input.commandName === "epic-tick";
59
+ const supportsAuto = schemaSupportsAutoFlag(input.schema) || input.commandName === "epic-tick";
63
60
  const alreadyHasAuto = input.args.includes("--auto");
64
61
  if (input.autoApprove && supportsAuto && !alreadyHasAuto) {
65
62
  args.push("--auto");
package/build/base-ref.js CHANGED
@@ -28,16 +28,38 @@ import { commandSucceeded } from "./start-tickets-prereqs.js";
28
28
  /**
29
29
  * Returns an error string for an unsafe branch name, or null when valid.
30
30
  *
31
- * Rejects names that Git itself refuses (`git check-ref-format`) as well as
32
- * injection-shaped inputs, WITHOUT invoking Git: an empty/whitespace-only name,
33
- * a leading `-` (which git would parse as a flag, e.g. `--upload-pack=evil`),
34
- * ASCII control characters, a `..` sequence, and a `.lock` suffix. Every failure
35
- * is a short, secret-free reason string that names only the rule that failed.
31
+ * This is the project's BOUNDED SAFETY SUBSET, not an implementation of
32
+ * `git check-ref-format`. It rejects, without invoking Git: an empty or
33
+ * whitespace-only name, a name longer than 255 Unicode code points, a leading
34
+ * `-` (which git would parse as a flag, e.g. `--upload-pack=evil`), a `..`
35
+ * sequence, a `.lock` suffix, and ASCII control characters. Every failure is a
36
+ * short, secret-free reason string naming only the rule that failed — never the
37
+ * offending value.
38
+ *
39
+ * The rules Git also refuses but this subset does NOT — `~`, `^`, `:`, `?`,
40
+ * `*`, `[`, `@{`, a backslash, and a trailing dot — are deliberate follow-up
41
+ * work, recorded here so nobody reads this as comprehensive ref validation. The
42
+ * subset is chosen to stop flag injection and command-shaped input, which is the
43
+ * safety property the callers actually depend on.
44
+ *
45
+ * BAPI-1127: this validator has a hand-written Python mirror,
46
+ * `feature_branch_provisioning._validate_provisioned_branch`, and the two are
47
+ * pinned rule-for-rule by the shared vectors in
48
+ * `tests/pytest/fixtures/run_branch_vectors.json`. The length rule counts
49
+ * UNICODE CODE POINTS, matching Python's `len()`. It previously used
50
+ * `String.length`, which counts UTF-16 code units, so a name of non-BMP
51
+ * characters (each a surrogate PAIR) measured double and the two runtimes
52
+ * disagreed about the same name at the boundary.
53
+ *
54
+ * Rule ORDER is part of the contract: the first failing rule is the one
55
+ * reported, so the vectors can name exactly one `error_type` per invalid input.
36
56
  */
37
57
  export function validateBranchName(branch) {
38
58
  if (branch.trim().length === 0)
39
59
  return "branch name must not be empty.";
40
- if (branch.length > 255)
60
+ // Spread to code points (never `.length`) so this measures the same units
61
+ // Python's `len()` does.
62
+ if ([...branch].length > 255)
41
63
  return "branch name must be 255 characters or fewer.";
42
64
  if (branch.startsWith("-"))
43
65
  return "branch name must not start with '-'.";
@@ -46,9 +68,11 @@ export function validateBranchName(branch) {
46
68
  if (branch.endsWith(".lock"))
47
69
  return "branch name must not end with '.lock'.";
48
70
  // Reject ASCII control characters (0x00-0x1F and 0x7F) without embedding
49
- // raw control bytes in source.
50
- for (let i = 0; i < branch.length; i++) {
51
- const code = branch.charCodeAt(i);
71
+ // raw control bytes in source. Iterated by code point for the same reason the
72
+ // length rule is: a surrogate pair is one character, and neither half of one
73
+ // can be an ASCII control character anyway.
74
+ for (const ch of branch) {
75
+ const code = ch.codePointAt(0);
52
76
  if (code <= 0x1f || code === 0x7f) {
53
77
  return "branch name must not contain control characters.";
54
78
  }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * A bounded, resumable wait primitive (BAPI-1104, R77).
3
+ *
4
+ * WHY THIS EXISTS. Across the BAPI-1061 and BAPI-1085 unattended runs the single
5
+ * largest source of wasted attempts was workers that could not wait. The Claude
6
+ * Code harness refuses a foreground `sleep`, and the MCP client deadline is 900
7
+ * seconds — so a recipe that waited by sleeping could not run at all, and one
8
+ * that waited by blocking a single tool call for 900 s died mid-wait, exited
9
+ * without its verdict artifact, and was requeued. Both failures cost a full
10
+ * attempt from a two-attempt budget for work that had already been done.
11
+ *
12
+ * The fix is a wait the caller can RESUME. `boundedWait` occupies at most
13
+ * {@link MAX_BUDGET_MS} (240 s — comfortably inside the 900 s cap), and when that
14
+ * window elapses it returns `{state: "pending"}`, which is a NORMAL, expected
15
+ * result, not an error. The caller loops: each call is a fresh bounded window,
16
+ * and an arbitrarily long upstream operation is covered by however many windows
17
+ * the caller's own budget allows. Nothing about the job's lease, heartbeat, or
18
+ * dead-man timing changes, because the worker never stops making tool calls.
19
+ *
20
+ * WHAT THIS MODULE IS NOT. It performs no I/O of its own. It imports no HTTP
21
+ * helper, knows no endpoint path, and names no agent CLI — every upstream read
22
+ * happens inside the caller-supplied `probe`. That is what lets the planned
23
+ * `cursor-agent` and Codex adapters use the identical primitive, and it is what
24
+ * keeps the reason vocabulary below closed: the primitive can only report states
25
+ * it can itself observe.
26
+ *
27
+ * NO RAW UPSTREAM DATA ESCAPES. A result carries a closed-vocabulary reason and
28
+ * nothing else. Exception text, response bodies, headers, URLs, and credentials
29
+ * are never read into a result — a caller that needs upstream detail must obtain
30
+ * it inside its own probe and sanitize it there, the same bar
31
+ * `formatRecoverablePollGiveUp` sets for the existing poller.
32
+ *
33
+ * TIMER DISCIPLINE. Every timer this module creates is registered and cancelled
34
+ * in an outer `finally`, on every exit path including an unexpected throw. The
35
+ * timer seams are injectable because `VirtualClock.tickUntil` in
36
+ * `executor/test-clock.ts` flushes microtasks only and cannot advance a real
37
+ * `setTimeout` — a primitive that reached for the global timer would make its own
38
+ * deterministic tests hang rather than fail, which is the worse of the two.
39
+ */
40
+ import { MAX_CONSECUTIVE_POLL_FAILURES, MAX_PROBE_TIMEOUT_MS, MAX_POLL_JITTER_MS, defaultPollDelayMs, } from "./polling-policy.js";
41
+ /** Hard ceiling on one wait window. Well inside the 900 s MCP client deadline. */
42
+ export const MAX_BUDGET_MS = 240_000;
43
+ const monotonicNow = () => typeof performance !== "undefined" && typeof performance.now === "function"
44
+ ? performance.now()
45
+ : Date.now();
46
+ /**
47
+ * Wait for `probe` to report ready, for at most `budgetMs` (≤ 240 s).
48
+ *
49
+ * Returns `ready` with the probe's value, `pending` when the window closed or
50
+ * transport kept failing, or `error` over the closed reason vocabulary. Never
51
+ * throws: an unexpected exception from a caller's `isReady` or timer seam still
52
+ * unwinds through the `finally` that cancels every outstanding timer.
53
+ */
54
+ export async function boundedWait(options) {
55
+ const { budgetMs, probe, isReady, signal, deps } = options;
56
+ // Caller error, reported rather than clamped: a budget that is NaN, infinite,
57
+ // or sub-millisecond is a computed value that went wrong upstream, and
58
+ // silently substituting 240 s would hide the bug behind a working wait.
59
+ if (typeof budgetMs !== "number" || !Number.isFinite(budgetMs) || budgetMs < 1) {
60
+ return { state: "error", reason: "INVALID_ARGUMENT" };
61
+ }
62
+ const effectiveBudgetMs = Math.min(budgetMs, MAX_BUDGET_MS);
63
+ const now = deps?.now ?? monotonicNow;
64
+ const setTimer = deps?.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
65
+ const clearTimer = deps?.clearTimer ?? ((handle) => clearTimeout(handle));
66
+ const jitterMs = deps?.jitterMs ?? (() => Math.random() * MAX_POLL_JITTER_MS);
67
+ // EVERY timer created below is registered here and cancelled in the outer
68
+ // `finally`. A leaked timer in an MCP server process keeps the event loop
69
+ // alive and, in a test, keeps a virtual clock advancing past the assertion.
70
+ const liveTimers = new Set();
71
+ const track = (handle) => {
72
+ liveTimers.add(handle);
73
+ return handle;
74
+ };
75
+ const release = (handle) => {
76
+ liveTimers.delete(handle);
77
+ clearTimer(handle);
78
+ };
79
+ const startedAt = now();
80
+ const deadlineAt = startedAt + effectiveBudgetMs;
81
+ const remaining = () => deadlineAt - now();
82
+ /** Sleep through an injected timer, resolving early if the caller aborts. */
83
+ const delay = (ms) => new Promise((resolve) => {
84
+ if (ms <= 0) {
85
+ resolve();
86
+ return;
87
+ }
88
+ let handle;
89
+ const finish = () => {
90
+ signal?.removeEventListener("abort", finish);
91
+ if (handle !== undefined)
92
+ release(handle);
93
+ resolve();
94
+ };
95
+ handle = track(setTimer(finish, ms));
96
+ signal?.addEventListener("abort", finish, { once: true });
97
+ });
98
+ let consecutiveFailures = 0;
99
+ try {
100
+ while (true) {
101
+ if (signal?.aborted)
102
+ return { state: "error", reason: "ABORTED" };
103
+ if (remaining() <= 0) {
104
+ return { state: "pending", reason: "WAIT_WINDOW_ELAPSED" };
105
+ }
106
+ // One controller per probe, aborted by EITHER the per-probe timeout or the
107
+ // caller's signal. The probe therefore never outlives its own budget, and a
108
+ // hung upstream costs one probe timeout rather than the whole window.
109
+ const probeController = new AbortController();
110
+ const abortProbe = () => probeController.abort();
111
+ signal?.addEventListener("abort", abortProbe, { once: true });
112
+ const probeTimeoutMs = Math.max(1, Math.min(MAX_PROBE_TIMEOUT_MS, remaining()));
113
+ let timedOut = false;
114
+ const timeoutHandle = track(setTimer(() => {
115
+ timedOut = true;
116
+ probeController.abort();
117
+ }, probeTimeoutMs));
118
+ let outcome = null;
119
+ let threw = false;
120
+ try {
121
+ outcome = await probe(probeController.signal);
122
+ }
123
+ catch {
124
+ // The probe's exception NEVER reaches a result. A transport throw and a
125
+ // probe timeout are the same observation — "no answer this time" — and
126
+ // both are transient until the shared ceiling says otherwise.
127
+ threw = true;
128
+ }
129
+ finally {
130
+ release(timeoutHandle);
131
+ signal?.removeEventListener("abort", abortProbe);
132
+ }
133
+ // Caller cancellation outranks whatever the in-flight probe reported: the
134
+ // caller stopped caring, and it is not an upstream observation.
135
+ if (signal?.aborted)
136
+ return { state: "error", reason: "ABORTED" };
137
+ if (threw || timedOut || outcome === null) {
138
+ consecutiveFailures += 1;
139
+ if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
140
+ return { state: "pending", reason: "UPSTREAM_TRANSIENT" };
141
+ }
142
+ }
143
+ else if (outcome.kind === "terminal") {
144
+ return { state: "error", reason: "UPSTREAM_TERMINAL" };
145
+ }
146
+ else if (outcome.kind === "malformed") {
147
+ return { state: "error", reason: "INVALID_RESPONSE" };
148
+ }
149
+ else {
150
+ // The server answered. Reset the streak BEFORE evaluating readiness — an
151
+ // answer of "not yet" is still proof the transport is healthy.
152
+ consecutiveFailures = 0;
153
+ if (outcome.ready === true) {
154
+ const value = outcome.value;
155
+ if (isReady === undefined || isReady(value)) {
156
+ return { state: "ready", result: value };
157
+ }
158
+ }
159
+ }
160
+ if (remaining() <= 0) {
161
+ return { state: "pending", reason: "WAIT_WINDOW_ELAPSED" };
162
+ }
163
+ // The existing poller's curve plus bounded jitter, clamped to whatever is
164
+ // actually left so a backoff can never overrun the deadline it is inside.
165
+ const backoff = defaultPollDelayMs(now() - startedAt) + jitterMs();
166
+ await delay(Math.min(backoff, remaining()));
167
+ }
168
+ }
169
+ finally {
170
+ for (const handle of liveTimers)
171
+ clearTimer(handle);
172
+ liveTimers.clear();
173
+ }
174
+ }