@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.
- package/README.md +121 -15
- package/build/agent-launchers/claude.js +3 -3
- package/build/agent-launchers/prompt.js +8 -11
- package/build/base-ref.js +33 -9
- package/build/bounded-wait.js +174 -0
- package/build/commands.generated.js +7 -5
- package/build/conductor/bridge-api-client.js +97 -8
- package/build/conductor/cli.js +23 -0
- package/build/conductor/doctor.js +428 -5
- package/build/conductor/epic-runtime.js +133 -97
- package/build/conductor/install-doctor.js +65 -656
- package/build/conductor/readiness-cli.js +152 -0
- package/build/conductor/readiness-sections.js +666 -0
- package/build/conductor/readiness.js +795 -0
- package/build/conductor/run-branch.js +137 -0
- package/build/conductor/test-run-branch-vectors.js +165 -0
- package/build/conductor/tools.js +56 -3
- package/build/conductor-bin.js +21 -17
- package/build/doctor.js +68 -1
- package/build/drive-epic.js +287 -51
- package/build/executor/claim-scope.js +104 -0
- package/build/executor/cli.js +14 -25
- package/build/executor/env-file-guard.js +82 -3
- package/build/executor/job-runner.js +60 -0
- package/build/index.js +4496 -4697
- package/build/install-doctor.js +154 -2
- package/build/local-artifact-storage.js +130 -0
- package/build/pipelines.generated.js +17 -10
- package/build/plane/alembic-head.js +40 -11
- package/build/plane/build-freshness.js +22 -11
- package/build/plane/cli.js +285 -36
- package/build/plane/manifest.js +209 -1
- package/build/plane/member-roster.js +70 -0
- package/build/plane/preflight.js +363 -48
- package/build/plane/shutdown.js +14 -1
- package/build/plane/status.js +35 -1
- package/build/plane/supervisor.js +546 -164
- package/build/plane/types.js +61 -2
- package/build/polling-policy.js +72 -0
- package/build/readiness-check.js +412 -0
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +219 -0
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +514 -23
- package/build/ticket-key-utils.js +4 -3
- package/build/ticket-review-artifact-gate.js +461 -0
- package/build/upgrade-cli.js +5 -26
- package/build/version.generated.js +3 -3
- package/docs/install/mcp-tool-integrations.md +23 -1
- package/package.json +2 -2
- package/pipelines/{full-automation.json → idea-to-pr.json} +1 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
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 `/
|
|
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
|
|
1064
|
-
below). With
|
|
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 **
|
|
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-
|
|
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` | `/
|
|
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
|
|
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
|
|
45
|
-
*
|
|
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 `
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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 `
|
|
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 === "
|
|
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
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
+
}
|