@bridge_gpt/mcp-server 0.2.46 → 0.2.49

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 (39) hide show
  1. package/README.md +101 -85
  2. package/build/agent-capabilities/default-deps.js +2 -2
  3. package/build/agent-launchers/claude.js +10 -19
  4. package/build/agent-launchers/cursor.js +4 -12
  5. package/build/agent-launchers/prompt.js +117 -0
  6. package/build/commands.generated.js +16 -22
  7. package/build/conduct-epic/bridge-client.js +73 -0
  8. package/build/conduct-epic/cli.js +152 -6
  9. package/build/conductor/cli.js +6 -7
  10. package/build/conductor/doctor.js +13 -116
  11. package/build/conductor/tools.js +18 -349
  12. package/build/conductor-bin.js +6 -30
  13. package/build/docs.generated.js +1 -1
  14. package/build/executor/deps.js +1 -0
  15. package/build/executor/service-lifecycle.js +6 -6
  16. package/build/executor/service-unit.js +13 -16
  17. package/build/index.js +214 -755
  18. package/build/init.js +15 -17
  19. package/build/install-doctor.js +1 -1
  20. package/build/learn-tool-gating.js +283 -0
  21. package/build/mcp-profile.js +13 -3
  22. package/build/mcp-server-invocation.js +14 -0
  23. package/build/pipelines.generated.js +19 -139
  24. package/build/platform-escaping.js +72 -0
  25. package/build/readme.generated.js +1 -1
  26. package/build/review-tickets.js +1 -1
  27. package/build/run-unit-tests-launcher.js +0 -1
  28. package/build/sfcc/register.js +41 -31
  29. package/build/sfcc/registration-inventory.js +44 -20
  30. package/build/start-tickets-conductor.js +2 -2
  31. package/build/start-tickets.js +8 -38
  32. package/build/version.generated.js +2 -2
  33. package/docs/CONDUCTOR.md +10 -12
  34. package/docs/install/mcp-tool-integrations.md +9 -55
  35. package/package.json +1 -1
  36. package/pipelines/idea-to-ticket.json +2 -2
  37. package/pipelines/review-ticket.json +9 -8
  38. package/pipelines/check-ci-ticket.json +0 -36
  39. package/pipelines/pr-ticket.json +0 -24
package/README.md CHANGED
@@ -351,7 +351,7 @@ later boot.
351
351
  "mcpServers": {
352
352
  "bridge": {
353
353
  "command": "npx",
354
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.45", "serve"],
354
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.49", "serve"],
355
355
  "env": {
356
356
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
357
357
  "BAPI_REPO_NAME": "your-repo",
@@ -373,7 +373,7 @@ later boot.
373
373
  "bridge": {
374
374
  "type": "stdio",
375
375
  "command": "npx",
376
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.45", "serve"],
376
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.49", "serve"],
377
377
  "env": {
378
378
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
379
379
  "BAPI_REPO_NAME": "your-repo",
@@ -395,7 +395,7 @@ later boot.
395
395
  "bridge": {
396
396
  "type": "stdio",
397
397
  "command": "npx",
398
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.45", "serve"],
398
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.49", "serve"],
399
399
  "env": {
400
400
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
401
401
  "BAPI_REPO_NAME": "your-repo",
@@ -420,7 +420,7 @@ you select `copilot-cli`; the shape below is what it produces.
420
420
  "bridge": {
421
421
  "type": "local",
422
422
  "command": "npx",
423
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.45", "serve"],
423
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.49", "serve"],
424
424
  "tools": ["*"],
425
425
  "env": {
426
426
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
@@ -444,7 +444,7 @@ Windsurf only supports global MCP configuration.
444
444
  "mcpServers": {
445
445
  "bridge": {
446
446
  "command": "npx",
447
- "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.45", "serve"],
447
+ "args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.49", "serve"],
448
448
  "env": {
449
449
  "BAPI_BASE_URL": "https://bridgegpt-api.com",
450
450
  "BAPI_REPO_NAME": "your-repo",
@@ -463,7 +463,7 @@ Windsurf only supports global MCP configuration.
463
463
  ```toml
464
464
  [mcp_servers.bridge]
465
465
  command = "npx"
466
- args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.45", "serve"]
466
+ args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.49", "serve"]
467
467
 
468
468
  [mcp_servers.bridge.env]
469
469
  BAPI_BASE_URL = "https://bridgegpt-api.com"
@@ -617,54 +617,24 @@ Good to know, but not needed every day.
617
617
  - **How to use it:** `/plan-ticket BAPI-123`
618
618
  - **Flags:** `--provider <name>` choose the model provider · `--second-opinion <provider>` cross-check the plan with a second provider.
619
619
 
620
- **3. Clarify Ticket**
621
- - **What it does:** Generates clarifying questions for a ticket, or debugging guidance when the ticket is a bug.
622
- - **When it's useful:** (Refinement) When a ticket feels under-specified and you want the open questions made explicit.
623
- - **How to use it:** `/clarify-ticket BAPI-123`
624
- - **Flags:** `--provider <name>` choose the model provider · `--second-opinion <provider>` cross-check with a second provider.
625
-
626
- **4. Critique Ticket**
627
- - **What it does:** Critiques a ticket against your project's standards and lists the deviations and improvements it found.
628
- - **When it's useful:** (Refinement) When you want a quality gate on a ticket before anyone works it.
629
- - **How to use it:** `/critique-ticket BAPI-123`
630
- - **Flags:** `--provider <name>` choose the model provider · `--second-opinion <provider>` cross-check with a second provider.
631
-
632
- **5. Create Doc**
620
+ **3. Create Doc**
633
621
  - **What it does:** Generates a design document for a ticket — a technical design document, a functional spec, or a product requirements document.
634
622
  - **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.
635
623
  - **How to use it:** `/create-doc BAPI-123 --doc-type tdd` (or `fsd` / `prd`)
636
624
  - **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.
637
625
 
638
- **6. Second Opinion**
626
+ **4. Second Opinion**
639
627
  - **What it does:** Gets an immediate critique of any text from a different model family, without saving an artifact.
640
628
  - **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.
641
629
  - **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 production."
642
630
  - **Flags:** Pick the provider (anthropic / openai / gemini) and the tier (cheap / basic / premium).
643
631
 
644
- **7. Generate Image**
632
+ **5. Generate Image**
645
633
  - **What it does:** Generates an image from a text prompt using a provider image model, spending provider credits on every call.
646
634
  - **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.
647
635
  - **How to use it:** Ask your agent — "Generate an image of a dashboard showing SOC2 evidence freshness as a traffic-light grid."
648
636
  - **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.
649
637
 
650
- **8. Request PRD**
651
- - **What it does:** Generates a product requirements document for a ticket covering the problem, the goals, and the success metrics.
652
- - **When it's useful:** (Architecture | Refinement) When a piece of work needs its problem, goals, and success metrics written down before anyone designs a solution.
653
- - **How to use it:** `/create-doc BAPI-123 --doc-type prd`
654
- - **Flags:** `--provider <name>` choose the model provider · `--second-opinion <provider>` cross-check with a second provider.
655
-
656
- **9. Full Automation**
657
- - **What it does:** Drives the whole chain from a raw idea through tickets and reviews to implementation sessions.
658
- - **When it's useful:** (Automation) When you want to go from a raw idea to in-progress implementation with minimal hands-on steps.
659
- - **How to use it:** `/full-automation <idea>` (command only — it creates tickets, spawns worktrees, and carries scheduling flags free text cannot).
660
- - **Flags:** `--require-approval` re-enable the approval gates; the chain runs end to end by default · `--max-children <n>` cap how many child tickets an epic decomposes into.
661
-
662
- **10. Update Ticket Description**
663
- - **What it does:** Rewrites a ticket's description with AI, using the ticket's own content and its reference material. A rewrite that changes more than 60% of the description is held for review instead of applied.
664
- - **When it's useful:** (Refinement) When a ticket has accumulated comments, attachments, or links and its description no longer reflects them.
665
- - **How to use it:** Ask your agent — "Update the description for BAPI-123."
666
- - **Flags:** None. Poll the ticket's state for the outcome; if the update was held for review, read the proposal instead of applying it blind.
667
-
668
638
  ### Now and then
669
639
 
670
640
  Useful once in a while.
@@ -682,10 +652,10 @@ Useful once in a while.
682
652
  - **Flags:** None.
683
653
 
684
654
  **3. Get Ticket**
685
- - **What it does:** Retrieves the full details of a ticket, including its summary, status, and description.
655
+ - **What it does:** Retrieves the full details of a ticket, including its summary, status, and description, and optionally the model tier its difficulty routes to.
686
656
  - **When it's useful:** (Refinement | Implementation) Any time you want the agent to read a ticket before acting on it.
687
657
  - **How to use it:** Ask your agent — "Pull up BAPI-123 and show me its description, status, and acceptance criteria."
688
- - **Flags:** None.
658
+ - **Flags:** `include_model_tier` adds the model tier the ticket's difficulty routes to; leave it off for an ordinary read.
689
659
 
690
660
  **4. Search Tickets**
691
661
  - **What it does:** Searches across the tickets in your project.
@@ -711,13 +681,7 @@ Useful once in a while.
711
681
  - **How to use it:** Ask your agent — "Download the design mockups attached to BAPI-123 into my docs folder," or "Attach build-log.txt to BAPI-123."
712
682
  - **Flags:** Choose the direction (download from the ticket, or upload to it) and, for a download, where the files should land.
713
683
 
714
- **8. Estimate Ticket**
715
- - **What it does:** Estimates the development effort for one ticket. Use Estimate Epic instead for a whole epic or a named group of tickets.
716
- - **When it's useful:** (Refinement) When you need a size for a single ticket before committing to it.
717
- - **How to use it:** Ask your agent — "Estimate BAPI-123."
718
- - **Flags:** Ask for a fresh estimate to regenerate rather than reuse a stored one.
719
-
720
- **9. Estimate Epic**
684
+ **8. Estimate Epic**
721
685
  - **What it does:** Estimates an epic, or an explicit group of tickets you name.
722
686
  - **When it's useful:** (Architecture | Refinement) When you need a sizing pass across an epic, or across a set of tickets you name explicitly.
723
687
  - **How to use it:** `/estimate-epic BAPI-123`
@@ -799,14 +763,10 @@ step and no server call; the full rationale and the closed exception list ship a
799
763
 
800
764
  ### Operational commands
801
765
 
802
- Workflow commands you'll reach for during implementation and CI, beyond the tiers above:
766
+ Repository and ticket housekeeping commands, beyond the tiers above. Implementation, commit/push, PR creation, and CI monitoring are steps of the `implement-ticket` recipe rather than standalone commands:
803
767
 
804
768
  | Command | What it does |
805
769
  |---|---|
806
- | `/code-ticket PROJ-123` | Download the implementation plan and questions, then execute the plan inline |
807
- | `/commit-ticket PROJ-123` | Stage, commit, and push changes; transition Jira status; post a smoke-test comment |
808
- | `/create-pr PROJ-123` | Resolve the base branch and open a pull request for the ticket's branch (run after `/commit-ticket`) |
809
- | `/check-ci PROJ-123` | Monitor CI checks for the current branch, triage failures, apply fixes, and report results |
810
770
  | `/parse-repository` | Queue a background job to index the repository for Bridge AI agents |
811
771
  | `/check-parse-status` | Check whether a background repository parse job is still running |
812
772
  | `/scan-tickets` | Sync recently-updated Jira tickets and backfill workflow timestamps |
@@ -817,15 +777,15 @@ Workflow commands you'll reach for during implementation and CI, beyond the tier
817
777
 
818
778
  Behind-the-scenes capabilities an agent gains from the MCP tools — mostly invoked automatically by the commands above, rarely requested by name:
819
779
 
820
- - **Ship a PR end-to-end:** commit & push, open a pull request, transition the Jira status, and discover/poll CI checks (powers `commit-ticket`, `create-pr`, `check-ci`, `implement-ticket`).
821
- - **Architecture plan** for a ticket (design-level guidance, separate from the implementation plan).
822
- - **Index the codebase** so Bridge's agents can reason about it: queue/parse the repo, check parse status, regenerate the directory map.
780
+ - **Ship a PR end-to-end:** commit & push, open a pull request, transition the Jira status, and discover/poll CI checks the commit-and-push, PR-creation, and CI-monitoring steps of the `implement-ticket` recipe, driven by its bundled instruction assets.
781
+ - **Design documents** for a ticket (TDD / FSD / PRD, generated and retrieved through `create_doc` / `get_doc`).
782
+ - **Index the codebase** so Bridge's agents can reason about it: queue/parse the repo and check parse status (`parse_repository`), regenerate the directory map.
823
783
  - **Read & tune project config/standards:** list/read/update config fields, fetch project standards, and the per-topic `learn-*` commands that populate them.
824
784
  - **Ticket lifecycle bookkeeping:** track tickets and backfill workflow-state timestamps (`scan-tickets`), search across tickets, read comments, list attachments.
825
- - **Pipeline machinery:** list/inspect pipeline recipes, run/resume/list/delete pipeline runs (the engine under the orchestration commands), and resume a full-automation chain that stopped at an approval gate or was interrupted.
785
+ - **Pipeline machinery:** fetch a resolved pipeline recipe (the step list behind the orchestration commands) and execute its steps agent-side.
826
786
  - **Decision page** generation for capturing human review decisions as structured data.
827
- - **Connectivity & identity checks:** ping Bridge, check your role, resolve the local docs directory.
828
- - **Retrieve any generated artifact** (`get_*` for plans, critiques, questions, councils, research, architecture) without regenerating it.
787
+ - **Connectivity check:** ping Bridge to confirm the API key and repository resolve.
788
+ - **Retrieve any generated artifact** (`get_*` for plans, critiques, questions, councils, research, design docs) without regenerating it.
829
789
  - **Tiered-section execution telemetry** recording (internal measurement).
830
790
 
831
791
  ## Salesforce B2C Commerce (SFCC) Tools
@@ -841,7 +801,7 @@ For a step-by-step OCAPI client setup guide (including the Business Manager perm
841
801
  <details>
842
802
  <summary><strong>Setup</strong></summary>
843
803
 
844
- The two diagnostic tools (`sfcc_setup_status`, `check_permissions`) are always available. The read tools, the write tools, and `sfcc_log_query` must be enabled with a profile (step 3). Changing `BRIDGE_MCP_PROFILE` requires an MCP client restart.
804
+ Every SFCC tool must be enabled with a profile (step 3) — including the two diagnostics, `sfcc_setup_status` and `check_permissions`, which belong to the `sfcc` profile. A session on the default `core` profile registers no SFCC tool at all. Changing `BRIDGE_MCP_PROFILE` requires an MCP client restart.
845
805
 
846
806
  **Prerequisites:** a running SFCC **sandbox** and its hostname, plus an Account Manager API client (`client-id` + `client-secret`).
847
807
 
@@ -877,7 +837,7 @@ Because tier 2 outranks tier 3, a stale `SFCC_HOSTNAME` left in the environment
877
837
 
878
838
  `sfcc` gives the eight read tools plus `sfcc_log_query`. For the nine destructive write tools as well, use `"sfcc,sfcc-write"`; `full` expands to every group and is therefore write-capable. Without any of these, only the diagnostic tools are registered.
879
839
 
880
- **4. Verify and grant OCAPI access.** Ask your agent to run `sfcc_setup_status` (expect all checks ✓), then `check_permissions`. If it reports HTTP 401/403, it prints the exact OCAPI Settings JSON to paste in Business Manager (**Administration → Site Development → Open Commerce API Settings → Data API** tab); save it there and re-run. Restart the MCP client after any credential or env change — a running session does not pick them up.
840
+ **4. Verify and grant OCAPI access.** With `sfcc` selected in step 3 (both diagnostics live in that profile), ask your agent to run `sfcc_setup_status` (expect all checks ✓), then `check_permissions`. If it reports HTTP 401/403, it prints the exact OCAPI Settings JSON to paste in Business Manager (**Administration → Site Development → Open Commerce API Settings → Data API** tab); save it there and re-run. Restart the MCP client after any credential or env change — a running session does not pick them up.
881
841
 
882
842
  Read what it prints before pasting it. The output is **two labelled blocks**, and they are not equivalent:
883
843
 
@@ -926,11 +886,24 @@ domain with no suffix that separates them.
926
886
 
927
887
  | `BRIDGE_MCP_PROFILE` | SFCC tools registered |
928
888
  |---|---|
929
- | unset / `core` | `sfcc_setup_status`, `check_permissions` only |
930
- | `sfcc` | the above + 8 OCAPI read tools + `sfcc_log_query` |
931
- | `sfcc-write` | the above diagnostics + the 9 destructive write tools |
889
+ | unset / `core` | none |
890
+ | `estimation` | none this group adds only `estimate_epic`, which is not an SFCC tool |
891
+ | `sfcc` | `sfcc_setup_status`, `check_permissions`, the 8 OCAPI read tools, and `sfcc_log_query` |
892
+ | `sfcc-write` | the 9 destructive write tools **only** — it does not imply `sfcc`, so neither diagnostic and no read tool is registered |
932
893
  | `sfcc,sfcc-write` | all 20 |
933
- | `full` | all 20 — `full` includes `sfcc-write` and is therefore write-capable |
894
+ | `full` | all 20 — `full` includes `sfcc-write` and is therefore write-capable, and it also includes `estimation` |
895
+
896
+ **`check_permissions` needs `sfcc`, even when you are diagnosing writes.** It is
897
+ the tool that prints the mutation-grant OCAPI Settings JSON on a 401/403, but it
898
+ is registered by the `sfcc` group, not `sfcc-write`. An `sfcc-write`-only session
899
+ therefore cannot run it. Use `BRIDGE_MCP_PROFILE=sfcc,sfcc-write` whenever you
900
+ need to diagnose write grants.
901
+
902
+ **Diagnostics moved into `sfcc`.** `sfcc_setup_status` and `check_permissions`
903
+ used to register unconditionally, on every session including non-SFCC ones. They
904
+ no longer do. The accepted consequence is that a session without `sfcc` can no
905
+ longer ask the server why SFCC tools are missing — run `npx -y @bridge_gpt/mcp-server doctor`
906
+ for that instead, which reports the resolved profile without needing any tool.
934
907
 
935
908
  **Migration.** Enabling `sfcc` used to register the nine write tools as well. It
936
909
  no longer does. If you were relying on SFCC writes through
@@ -939,13 +912,13 @@ Users of `BRIDGE_MCP_PROFILE=full` keep write access and need no change.
939
912
 
940
913
  ### Tools
941
914
 
942
- Twenty tools in total: two always-on diagnostics, the `sfcc` profile's **read-only** surface (eight OCAPI reads plus `sfcc_log_query`), and the nine destructive writes that only the separate `sfcc-write` profile registers — see [Read and write profiles](#read-and-write-profiles). Every one of them is bounded to a developer sandbox by the same invocation-time check. All twenty are enumerated below.
915
+ Twenty tools in total, none of them registered unconditionally: the `sfcc` profile's **read-only** surface (the two diagnostics, eight OCAPI reads, and `sfcc_log_query`), plus the nine destructive writes that only the separate `sfcc-write` profile registers — see [Read and write profiles](#read-and-write-profiles). Every one of them is bounded to a developer sandbox by the same invocation-time check. All twenty are enumerated below.
943
916
 
944
917
  An oversized response is saved in full to `BAPI_DOCS_DIR/sfcc/` and replaced by a parseable JSON descriptor — `truncated: true`, the `saved_path` it was written to, and the `page` metadata (`returned`, `total` when OCAPI supplied one, `has_more`) — so the collection metadata survives even though the data itself is on disk. If that save fails, the complete payload is returned inline instead, still as parseable JSON.
945
918
 
946
919
  Attribute-definition reads and writes can return an attribute's `default_value` at `projection: "full"`, and Bridge withholds it — every key is preserved except that one, whose value becomes `[REDACTED_BY_BRIDGE]` — from the inline response, the saved file, and a successful write echo alike. Attribute defaults are intentionally unavailable through this MCP surface; Business Manager is the supported path to read one.
947
920
 
948
- **Diagnostics** (always available, no profile needed)
921
+ **Diagnostics** (registered by the `sfcc` profile)
949
922
  - `sfcc_setup_status` — report on every prerequisite: Bridge API key, repo name, `version` config, `dw.json` presence/uniqueness, AM (OCAPI) token acquisition, and the independent **SFCC Log Query (WebDAV)** capability that gates `sfcc_log_query`.
950
923
  - `check_permissions` — probe OCAPI access via `GET /system_object_definitions`; on 401/403, print the exact OCAPI Settings JSON to paste in Business Manager (read/search grants for the `sfcc` tools, mutation grants for the `sfcc-write` tools). An explicit `instance` hostname is still subject to the sandbox check below.
951
924
 
@@ -1008,7 +981,7 @@ npx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]
1008
981
  | `--base-branch <BRANCH>` | `main` | Cut new worktrees from `<BRANCH>` and refresh `origin/<BRANCH>` instead of `main` |
1009
982
  | `--no-refresh-main` | off (the configured base branch is refreshed) | Skip refresh of the configured base branch (default `main`). Historical flag name preserved for backward compatibility — despite the name, it now skips refresh of whatever `--base-branch` resolves to. |
1010
983
  | `--max-parallel N` | `3` | Max worktrees created concurrently |
1011
- | `--conductor` | off | Opt into the Conductor system (per-worker `BAPI_CONDUCTOR_*` env + Claude hook injection, a supervisor peer tab, and the `check_messages` message-relay prompt). **Default off** — a plain run spawns `cd <worktree> && <agent> '/implement-ticket <KEY>'`. |
984
+ | `--conductor` | off | Opt into the Conductor system (per-worker `BAPI_CONDUCTOR_*` env + Claude hook injection, lifecycle ledger events, and a supervisor peer tab). **Default off** — a plain run spawns `cd <worktree> && <agent> '/implement-ticket <KEY>'`. |
1012
985
  | `-h`, `--help` | — | Show usage |
1013
986
 
1014
987
  Each `KEY` must match `[A-Z]+-[0-9]+` (e.g., `BAPI-248`). The CLI creates/switches each worktree up front (throttled by `--max-parallel`), then opens one tab/session per successful worktree running the selected agent's `'/implement-ticket <KEY>'` — `claude '/implement-ticket <KEY>'` by default, or `cursor-agent '/implement-ticket <KEY>'` with `--agent cursor-agent`. The `/implement-ticket <KEY>` prompt is unchanged for both agents. To launch Cursor Agent instead of Claude Code:
@@ -1234,12 +1207,58 @@ If a custom pipeline has the same key as a built-in pipeline, the custom version
1234
1207
  | `BAPI_MCP_TOOL_SURFACE_POLL_ENABLED` | No | _(disabled)_ | Opt IN to the recurring tool-surface **poll**. Default-**off**: a session gates once via the startup probe and never re-probes. Set to `true`/`1`/`yes`/`on`/`enabled` to restore the jittered 12–18 s heartbeat that pushes `notifications/tools/list_changed` on mid-session capability changes. No effect when gating itself is disabled |
1235
1208
  | `BAPI_MCP_UPDATE_CHECK_ENABLED` | No | _(enabled)_ | MCP-local kill switch for the cached update check run at startup. Default-on; set to `false`/`0`/`no`/`off`/`disabled` to skip it entirely — no npm registry request, no update-cache read/write, no stderr advice, and no `tools/list` advisory decoration. Any other value is treated as enabled. Useful behind a firewall/registry-restricted network, and used by the payload-measurement harness so a stale local cache can never perturb a capture |
1236
1209
  | `CURSOR_API_KEY` | No | _(none)_ | API key used to authenticate `cursor-agent` when launching `start-tickets`/scheduled runs with `--agent cursor-agent`. Not needed for the default Claude Code agent |
1237
- | `BRIDGE_MCP_PROFILE` | No | `core` | Startup-time tool registration profile — a **comma-separated** list of groups controlling which tool groups are registered when the server starts. `core` is always implicitly included. Valid groups: `core` (default — normal coding tools only), `conductor` (+ 8 conductor/event/supervisor tools), `pipeline-authoring` (+ 5 pipeline run/admin tools — `get_pipeline_recipe` is NOT gated; it stays in `core` because the recipe-driven slash commands depend on it), `sfcc` (+ the 8 heavy SFCC read tools and `sfcc_log_query` — read-only, see [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools); the `sfcc_setup_status`/`check_permissions` diagnostics are always registered regardless), `sfcc-write` (+ the 9 destructive SFCC write tools — independent of `sfcc`, which does not enable them; see [Read and write profiles](#read-and-write-profiles)), and `full` (shortcut that expands to every group, **including `sfcc-write`**). Example: `sfcc,conductor`; use `sfcc,sfcc-write` for reads plus writes. Unknown, blank, or malformed tokens are dropped (falling back to `core`). Dynamic mid-session switching via `tools/list_changed` is unsupported — groups are resolved once at process startup. Conductor/epic sessions launched via `start-tickets --conductor` **merge** `conductor` into the parent process's already-resolved groups at the spawn boundary rather than replacing them — a project running on `sfcc` spawns workers on `core,sfcc,conductor`. A normal `start-tickets` run stays on `core`. |
1210
+ | `BRIDGE_MCP_PROFILE` | No | `core` | Startup-time tool registration profile — a **comma-separated** list of groups controlling which tool groups are registered when the server starts. `core` is always implicitly included. Valid groups: `core` (default — normal coding tools only), `conductor` (+ 8 conductor/event/supervisor tools), `pipeline-authoring` (+ 5 pipeline run/admin tools — `get_pipeline_recipe` is NOT gated; it stays in `core` because the recipe-driven slash commands depend on it), `estimation` (+ `estimate_epic`, which is **hidden by default** — `/estimate-epic` needs this group), `sfcc` (+ the 8 heavy SFCC read tools, `sfcc_log_query`, and the `sfcc_setup_status`/`check_permissions` diagnostics — read-only, see [Salesforce B2C Commerce (SFCC) Tools](#salesforce-b2c-commerce-sfcc-tools)), `sfcc-write` (+ the 9 destructive SFCC write tools — independent of `sfcc`, which does not enable them; see [Read and write profiles](#read-and-write-profiles)), and `full` (shortcut that expands to every group, **including `estimation` and `sfcc-write`**). Example: `sfcc,conductor`; use `sfcc,sfcc-write` for reads plus writes. Unknown, blank, or malformed tokens are dropped (falling back to `core`). Dynamic mid-session switching via `tools/list_changed` is unsupported — groups are resolved once at process startup, so **changing this variable requires restarting the MCP server**. That is separate from the learn/onboarding tools, which are gated per session and can appear mid-session without a restart (see [Learn/onboarding tool availability](#learnonboarding-tool-availability)). Conductor/epic sessions launched via `start-tickets --conductor` **merge** `conductor` into the parent process's already-resolved groups at the spawn boundary rather than replacing them — a project running on `sfcc` spawns workers on `core,sfcc,conductor`. A normal `start-tickets` run stays on `core`. |
1238
1211
 
1239
1212
  Environment values are **trimmed**, and only a non-empty result wins. A
1240
1213
  whitespace-only `BAPI_API_KEY` therefore does not override anything: it falls
1241
1214
  through to credential-store resolution exactly as an unset variable would.
1242
1215
 
1216
+ ## Learn/onboarding tool availability
1217
+
1218
+ Three tools are **gated per session** rather than by profile:
1219
+
1220
+ - `apply_install_manifest`
1221
+ - `invite_member`
1222
+ - `regenerate_directory_map`
1223
+
1224
+ They are registered in the `core` profile like any other core tool, but the
1225
+ server starts them **disabled**, so a client that honors mid-session tool-list
1226
+ changes does not see them in its first `tools/list`. They are revealed — all
1227
+ three together, once per session — by either of two successful requests:
1228
+
1229
+ 1. a successful **`get_install_manifest`** call, or
1230
+ 2. a **`get_pipeline_recipe`** request for the **`learn-repository`** pipeline.
1231
+
1232
+ On reveal the server emits `notifications/tools/list_changed`, so a client that
1233
+ honors it picks the three tools up **without a restart**.
1234
+
1235
+ `get_install_manifest` and `persist_routing_credential` stay ungated in `core` at
1236
+ all times, so the install and `start-tickets` flows always have their entry
1237
+ points. Only the first of those two is a trigger — `persist_routing_credential`
1238
+ never reveals anything; it is listed here because `/start-tickets` calls it and
1239
+ it therefore must never be gated.
1240
+
1241
+ **Unrecognized clients get all three at startup.** The hide-then-reveal behavior
1242
+ applies only to clients verified to honor mid-session `tools/list_changed`. Any
1243
+ other client — including one that sends no name, a blank name, or a name the
1244
+ server does not recognize — is enabled at session initialization instead and
1245
+ therefore sees all three tools in its **first** `tools/list`. This is deliberate:
1246
+ the failure mode of guessing wrong should be an extra tool, never a permanently
1247
+ missing one.
1248
+
1249
+ **A failed request changes nothing.** Only a genuinely successful manifest
1250
+ reveals the tools — the response must parse as JSON and carry a nonblank
1251
+ `snapshot_token`. An error response, a non-JSON body, or a manifest without a
1252
+ token leaves the three tools hidden, and the response you receive is unchanged by
1253
+ the check either way. Likewise, only the exact `learn-repository` pipeline is a
1254
+ trigger; other recipes and failed lookups reveal nothing.
1255
+
1256
+ **Capability filtering still applies independently.** Revealing these tools does
1257
+ not override the backend capability gate described below. The effective surface
1258
+ is the *intersection* of the two, so a tool can be enabled for the session and
1259
+ still be absent from `tools/list` because this repository lacks the integration
1260
+ it needs. That is expected behavior, not a bug — check the integration first.
1261
+
1243
1262
  ## Dynamic tool-surface gating (capability availability)
1244
1263
 
1245
1264
  The **effective advertised tool surface** is the intersection of three things:
@@ -1405,18 +1424,18 @@ The full surface, for when you need the complete enumeration. Day-to-day, use [U
1405
1424
 
1406
1425
  ### MCP tools
1407
1426
 
1408
- The authoritative tool catalog covers **92 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).
1427
+ The authoritative tool catalog covers **69 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).
1409
1428
 
1410
- - **Connectivity & identity** — `ping`, `get_my_role`, `get_docs_dir`
1429
+ - **Connectivity & identity** — `ping` (its JSON also carries `docs_dir`, `role`, and `customer_type`)
1411
1430
  - **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)
1412
1431
  - **Jira tickets** — `get_tickets`, `get_ticket`, `create_ticket`, `update_ticket_description`, `add_comment`, `get_comments`
1413
1432
  - **Attachments** — `attachment` (operations: `upload`, `download`, `list`)
1414
- - **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_council`/`get_council`, `request_deep_research`/`get_deep_research`
1433
+ - **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`
1415
1434
  - **Other AI** — `second_opinion`, `generate_image`, `generate_decision_page`, `visual_diff` (deterministic pixel diff of a rendered URL vs a design comp)
1416
1435
  - **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)
1417
- - **Jira status** — `get_jira_transitions`, `update_jira_status`, `resolve_target_status`
1418
- - **Repository & CI** — `parse_repository`, `get_parse_status`, `regenerate_directory_map`, `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`
1419
- - **Pipelines & automation** — `list_pipelines`, `get_pipeline_recipe`, `run_pipeline`, `resume_pipeline`, `list_pipeline_runs`, `delete_pipeline_run`, `run_full_automation`, `resume_full_automation`
1436
+ - **Jira status** — `update_jira_status` (pass `status: "auto"` to resolve the configured post-PR status server-side)
1437
+ - **Repository & CI** — `parse_repository` (`action`: `start`, `status`), `regenerate_directory_map`, `create_pull_request`, `merge_pull_request`, `resolve_ci_checks`, `poll_ci_checks`
1438
+ - **Pipelines & automation** — `get_pipeline_recipe` (returns a fully resolved recipe the agent executes step-by-step)
1420
1439
  - **Config** — `get_project_standards`, `config_field` (operations: `get`, `update`, `list`)
1421
1440
 
1422
1441
  ### Bundled pipelines
@@ -1429,17 +1448,14 @@ Pipelines are declarative, multi-step workflows your AI agent executes step-by-s
1429
1448
  | `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` |
1430
1449
  | `idea-to-ticket` | Turn an idea into a Jira Task/Spike (or Epic + children) with research, dedup, and critique | `/idea-to-ticket "<idea>"` |
1431
1450
  | `plan-epic` | Decompose an epic into sub-tasks with a structured exploration doc for each | `/plan-epic "<epic>"` |
1432
- | `full-automation` | Chain: idea ticket(s) review each spawn worktrees to implement | `/full-automation "<idea>"` |
1433
- | `pr-ticket` | Commit changes and open a pull request | `/create-pr PROJ-123` |
1434
- | `check-ci-ticket` | Commit, open a PR, then monitor CI checks until they pass or fail | `/check-ci PROJ-123` |
1451
+ | `full-automation` | Client-side wrapper: runs the `idea-to-ticket` recipe, then hands the created keys to `/review-and-start --auto` | `/full-automation "<idea>"` |
1435
1452
  | `learn-repository` | Analyze codebase architecture, testing, review, and documentation standards, then upload to Bridge | `/learn-repository` |
1436
1453
 
1437
1454
  ### Pipeline response envelope
1438
1455
 
1439
- `run_pipeline`, `resume_pipeline`, and `list_pipeline_runs` share a unified envelope keyed on `status`:
1440
-
1441
- - `completed` terminal success; `results` holds per-step output.
1442
- - `needs_agent_task` the orchestrator paused. Read `instruction`, perform the task, then call `resume_pipeline` with `pipeline_run_id` and a string `agent_result`.
1443
- - `failed` terminal failure. `error_code` is one of `VALIDATION`, `NOT_FOUND`, `EXPIRED`, `REPO_MISMATCH`, `TOOL_ERROR`.
1444
-
1445
- Paused runs auto-expire after an idle TTL (default 24 hours; override with `ttl_seconds`). The TTL is reset on every state transition. List output is metadata-only — it never includes resolved recipes, params, instructions, results, or agent outputs.
1456
+ `get_pipeline_recipe` returns a fully resolved recipe: an ordered `steps` array
1457
+ plus `total_steps`, `auto_approve`, and `execution_mode`. Each step is either an
1458
+ `mcp_call` (a tool name with resolved params) or an `agent_task` (instruction
1459
+ text). `execution_mode` is `inline` the calling agent executes every step
1460
+ itself and keeps its own durable record through `record_phase_result` /
1461
+ `record_checkpoint`. There is no server-side orchestrator and no run to resume.
@@ -5,8 +5,8 @@
5
5
  * timeout guard in `probe-context.ts` can actually kill a hung agent (the
6
6
  * version-sensitive `cursor-agent -p` non-exit bug) instead of leaking it.
7
7
  *
8
- * Mirrors `createDefaultScheduleRunDeps()` in `schedule-run.ts`, adding the temp-dir
9
- * filesystem ops and a unique-suffix source the probes need.
8
+ * Mirrors the executor's own default deps, adding the temp-dir filesystem ops
9
+ * and a unique-suffix source the probes need.
10
10
  */
11
11
  import { execFile } from "node:child_process";
12
12
  import { mkdtemp, rm, writeFile, mkdir } from "node:fs/promises";
@@ -1,21 +1,20 @@
1
1
  /**
2
2
  * The `claude` agent launcher (BAPI-327, generalized in BAPI-351).
3
3
  *
4
- * Resolves the `claude` binary against the *baked schedule-time PATH* (not the
5
- * ambient process default) and builds the scheduled-run invocation by delegating
4
+ * Resolves the `claude` binary against the *explicitly supplied PATH* (not the
5
+ * ambient process default) and builds the launched-run invocation by delegating
6
6
  * to the shared `renderScheduledPrompt` renderer (no hard-coded `/full-automation`
7
7
  * prompt). It emits `{ exe, args: ["-p", prompt] }`. Claude Code has no
8
- * working-directory flag, so the cwd is always set by the scheduler unit — this
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.
10
10
  */
11
- import { pathApiForPlatform } from "../scheduler-backends/types.js";
12
- import { posixShellQuote, windowsCmdQuote } from "../scheduler-backends/escaping.js";
13
- import { renderScheduledPrompt } from "../scheduled-prompt.js";
14
- import { quotePromptToken } from "../scheduled-prompt.js";
11
+ import { pathApiForPlatform } from "../worktree-core.js";
12
+ import { posixShellQuote, windowsCmdQuote } from "../platform-escaping.js";
13
+ import { renderScheduledPrompt, quotePromptToken } from "./prompt.js";
15
14
  /**
16
15
  * Resolve a bare command to an absolute path using the platform PATH-probe,
17
- * forcing the baked PATH so the schedule resolves the same binary the user had
18
- * at creation time. On Windows both `PATH` and `Path` are overridden (Node and
16
+ * forcing the supplied PATH so the launch resolves the same binary the caller
17
+ * captured. On Windows both `PATH` and `Path` are overridden (Node and
19
18
  * `where.exe` disagree on casing). Returns the first non-empty *absolute* path
20
19
  * from stdout, or `null` when the probe fails or yields only relative paths.
21
20
  */
@@ -57,15 +56,7 @@ export { quotePromptToken };
57
56
  * shared renderer. Exposed for focused unit testing of the launcher wiring.
58
57
  */
59
58
  export function buildClaudePrompt(input) {
60
- return renderScheduledPrompt({
61
- scheduleId: input.scheduleId,
62
- scheduledAt: input.runAtIso,
63
- autoApprove: input.autoApprove,
64
- commandName: input.commandName,
65
- args: input.args,
66
- commandBody: input.commandBody,
67
- schema: input.schema,
68
- });
59
+ return renderScheduledPrompt(input);
69
60
  }
70
61
  const CLAUDE_CAPABILITY = {
71
62
  name: "claude",
@@ -82,7 +73,7 @@ export function createClaudeLauncher() {
82
73
  },
83
74
  buildInvocation(exe, input) {
84
75
  const prompt = buildClaudePrompt(input);
85
- // No working-directory flag: cwd is owned by the scheduler unit.
76
+ // No working-directory flag: cwd is owned by the launching unit.
86
77
  return { exe, args: [CLAUDE_CAPABILITY.promptFlag, prompt], prompt };
87
78
  },
88
79
  formatInvocationLine(invocation, platform) {
@@ -2,7 +2,7 @@
2
2
  * The `cursor-agent` agent launcher (BAPI-351).
3
3
  *
4
4
  * A near-mirror of the Claude launcher: it resolves the `cursor-agent` binary
5
- * against the baked schedule-time PATH and builds the scheduled-run prompt via
5
+ * against the explicitly supplied PATH and builds the launched-run prompt via
6
6
  * the shared `renderScheduledPrompt` renderer (so the drift gate and command body
7
7
  * are identical across agents). A local spike confirmed cursor-agent resolves the
8
8
  * same `.claude/commands` catalog and exits cleanly in headless mode.
@@ -14,20 +14,12 @@
14
14
  * - headless auth is via the `CURSOR_API_KEY` environment variable. This adapter
15
15
  * never reads or prints that value — auth readiness is a prerequisite check.
16
16
  */
17
- import { posixShellQuote, windowsCmdQuote } from "../scheduler-backends/escaping.js";
18
- import { renderScheduledPrompt } from "../scheduled-prompt.js";
17
+ import { posixShellQuote, windowsCmdQuote } from "../platform-escaping.js";
18
+ import { renderScheduledPrompt } from "./prompt.js";
19
19
  import { resolveCommandOnPath } from "./claude.js";
20
20
  /** Build the scheduled-run prompt for the Cursor launcher via the shared renderer. */
21
21
  export function buildCursorPrompt(input) {
22
- return renderScheduledPrompt({
23
- scheduleId: input.scheduleId,
24
- scheduledAt: input.runAtIso,
25
- autoApprove: input.autoApprove,
26
- commandName: input.commandName,
27
- args: input.args,
28
- commandBody: input.commandBody,
29
- schema: input.schema,
30
- });
22
+ return renderScheduledPrompt(input);
31
23
  }
32
24
  const CURSOR_CAPABILITY = {
33
25
  name: "cursor-agent",
@@ -0,0 +1,117 @@
1
+ /** The fixed late-fire threshold, in seconds, shared by every launched run. */
2
+ export const LATE_FIRE_THRESHOLD_SECONDS = 60;
3
+ /**
4
+ * Quote a single argument token for safe inclusion in the rendered prompt while
5
+ * preserving its boundary. Simple tokens (alphanumerics plus a small set of
6
+ * path/flag-safe punctuation) are left bare; anything containing whitespace or
7
+ * prompt/markup-sensitive characters is wrapped in double quotes with embedded
8
+ * double quotes escaped. This is prompt-token quoting, NOT shell quoting — no
9
+ * shell ever sees these strings.
10
+ */
11
+ export function quotePromptToken(token) {
12
+ if (token === "")
13
+ return '""';
14
+ // Safe: letters, digits, and characters that never need quoting in a prompt
15
+ // (path separators, ISO-timestamp punctuation, flag dashes, etc.).
16
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(token))
17
+ return token;
18
+ return `"${token.replace(/"/g, '\\"')}"`;
19
+ }
20
+ /**
21
+ * True when a command's declared argument schema can parse a boolean `--auto`
22
+ * flag. Private to this module: the launchers only need the rendering decision,
23
+ * not a general command-schema query API.
24
+ */
25
+ function schemaSupportsAutoFlag(schema) {
26
+ if (!schema)
27
+ return false;
28
+ return schema.flags.some((f) => f.flag === "--auto" && f.type === "boolean");
29
+ }
30
+ /**
31
+ * Compute the augmented argv tokens the launched run delegates to the command:
32
+ * the normalized `input.args`, plus (only where the command can parse them)
33
+ * `--scheduled-at <ISO>` and `--auto`.
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.
41
+ *
42
+ * `--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`).
44
+ * It is never duplicated if already present in `input.args`.
45
+ *
46
+ * This is the SINGLE source of the delegated argv: both the rendered target
47
+ * command line and the `$ARGUMENTS` body substitution derive from it, so the body
48
+ * parse and the command line never disagree (e.g. `review-ticket`, which reads
49
+ * `--auto` out of `$ARGUMENTS`, sees the same `--auto` the command line shows).
50
+ */
51
+ function buildAugmentedArgs(input) {
52
+ const args = [...input.args];
53
+ // Commands whose parsers accept --scheduled-at as a first-class argument.
54
+ // epic-tick already accepts --scheduled-at (parseEpicTickArgs, cli.ts) so it
55
+ // receives the scheduled time as a structured arg for the late-fire decision,
56
+ // not just via the embedded gate text.
57
+ if (input.commandName === "full-automation" || input.commandName === "epic-tick") {
58
+ args.push("--scheduled-at", input.runAtIso);
59
+ }
60
+ const supportsAuto = schemaSupportsAutoFlag(input.schema) ||
61
+ input.commandName === "full-automation" ||
62
+ input.commandName === "epic-tick";
63
+ const alreadyHasAuto = input.args.includes("--auto");
64
+ if (input.autoApprove && supportsAuto && !alreadyHasAuto) {
65
+ args.push("--auto");
66
+ }
67
+ return args;
68
+ }
69
+ /**
70
+ * Render the delegated target command line as
71
+ * `/<commandName> <augmented args...>` (see {@link buildAugmentedArgs}).
72
+ */
73
+ function buildTargetCommandLine(input) {
74
+ const parts = [`/${input.commandName}`];
75
+ for (const arg of buildAugmentedArgs(input))
76
+ parts.push(quotePromptToken(arg));
77
+ return parts.join(" ");
78
+ }
79
+ /** Render only the fixed late-fire gate text. */
80
+ function renderLateFireGate(scheduleId, scheduledAt, autoApprove) {
81
+ const lateAction = autoApprove
82
+ ? "explain that the run is firing late and then PROCEED with the command below (this schedule was created with auto-approve)."
83
+ : "explain that the run is firing late and then HALT WITHOUT EXECUTING the command below (this schedule was created without auto-approve, and a headless run must not proceed unconfirmed).";
84
+ return [
85
+ "## Scheduled-run drift gate",
86
+ "",
87
+ `This is an automated, headless scheduled run (schedule id: ${scheduleId}).`,
88
+ `It was scheduled to fire at ${scheduledAt}.`,
89
+ "",
90
+ `Before doing anything else, check how late this run is firing. If it is more than ${LATE_FIRE_THRESHOLD_SECONDS} seconds later than the scheduled time above, ${lateAction}`,
91
+ `If it is within ${LATE_FIRE_THRESHOLD_SECONDS} seconds of the scheduled time, proceed silently.`,
92
+ "",
93
+ "Then run the following command exactly as written:",
94
+ ].join("\n");
95
+ }
96
+ /**
97
+ * Replace `$ARGUMENTS` in a command body with the safely-quoted, normalized
98
+ * argument string. Every occurrence is replaced (commands often repeat the
99
+ * placeholder in a title and a body line).
100
+ */
101
+ function replaceArgumentsPlaceholder(body, argString) {
102
+ return body.split("$ARGUMENTS").join(argString);
103
+ }
104
+ /**
105
+ * Render the full launched-run prompt: gate + delegated command line + command
106
+ * body (with `$ARGUMENTS` substituted). Deterministic for identical inputs.
107
+ */
108
+ export function renderScheduledPrompt(input) {
109
+ const gate = renderLateFireGate(input.scheduleId, input.runAtIso, input.autoApprove);
110
+ const targetCommandLine = buildTargetCommandLine(input);
111
+ // `$ARGUMENTS` must match what the command line shows — including the appended
112
+ // `--scheduled-at` / `--auto` — so a body that re-parses `$ARGUMENTS` (e.g.
113
+ // review-ticket reading `--auto`) agrees with the rendered command line.
114
+ const argString = buildAugmentedArgs(input).map(quotePromptToken).join(" ");
115
+ const body = replaceArgumentsPlaceholder(input.commandBody, argString);
116
+ return [gate, "", targetCommandLine, "", body].join("\n");
117
+ }