@bridge_gpt/mcp-server 0.2.55 → 0.2.56
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 +12 -10
- package/build/agent-capabilities/types.js +4 -0
- package/build/agent-registry.js +20 -3
- package/build/codex-launch-scope.js +49 -0
- package/build/codex-skill-adapter.js +8 -5
- package/build/command-host.js +9 -0
- package/build/command-provisioning.js +35 -4
- package/build/commands.generated.js +5 -5
- package/build/doctor.js +4 -1
- package/build/epic-implementer-bin.js +2 -2
- package/build/index.js +78 -77
- package/build/init.js +2 -1
- package/build/install-bridge.js +22 -9
- package/build/mcp-host-targets.js +18 -11
- package/build/mcp-provisioning.js +1 -1
- package/build/mcp-registration-doctor.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +50 -35
- package/build/start-tickets.js +266 -48
- package/build/version.generated.js +3 -3
- package/package.json +1 -1
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.
|
|
354
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.56", "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.
|
|
376
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.56", "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.
|
|
398
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.56", "serve"],
|
|
399
399
|
"env": {
|
|
400
400
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
401
401
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -418,7 +418,7 @@ Windsurf only supports global MCP configuration.
|
|
|
418
418
|
"mcpServers": {
|
|
419
419
|
"bridge": {
|
|
420
420
|
"command": "npx",
|
|
421
|
-
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
421
|
+
"args": ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.56", "serve"],
|
|
422
422
|
"env": {
|
|
423
423
|
"BAPI_BASE_URL": "https://bridgegpt-api.com",
|
|
424
424
|
"BAPI_REPO_NAME": "your-repo",
|
|
@@ -437,7 +437,7 @@ Windsurf only supports global MCP configuration.
|
|
|
437
437
|
```toml
|
|
438
438
|
[mcp_servers.bridge]
|
|
439
439
|
command = "npx"
|
|
440
|
-
args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
440
|
+
args = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.56", "serve"]
|
|
441
441
|
|
|
442
442
|
[mcp_servers.bridge.env]
|
|
443
443
|
BAPI_BASE_URL = "https://bridgegpt-api.com"
|
|
@@ -985,9 +985,9 @@ npx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]
|
|
|
985
985
|
| Flag | Default | Meaning |
|
|
986
986
|
|---|---|---|
|
|
987
987
|
| `--agent claude\|cursor-agent` | `claude` | Agent command to launch in each worktree |
|
|
988
|
-
| `--workflow implement\|review-and-implement` | `implement` | Slash command each spawned worktree runs. `
|
|
988
|
+
| `--workflow implement\|review-and-implement` | `implement` | Slash command each spawned worktree runs. `review-and-implement` reviews the ticket, then hands approval to a fresh implementation session in the same worktree. `--auto` applies to both phases. |
|
|
989
989
|
| `--rounds 1\|2` | unset | Review round count forwarded to the review phase. Review-only — valid only with `--workflow review-and-implement`. |
|
|
990
|
-
| `--tier cheap\|basic\|premium` | unset (difficulty routing) | Coarse model-routing override. Bypasses **only** the per-ticket difficulty/tier lookup (`GET /jira/tickets/{KEY}/model-tier`) and applies this one tier to every ticket;
|
|
990
|
+
| `--tier cheap\|basic\|premium` | unset (difficulty routing) | Coarse model-routing override. Bypasses **only** the per-ticket difficulty/tier lookup (`GET /jira/tickets/{KEY}/model-tier`) and applies this one tier to every ticket; Claude/Cursor use their registry aliases and repository overrides, while Codex uses the authenticated Bridge catalog. It is **not** a raw `--model` alias and never carries an API key or credential. A malformed value fails open to premium routing when its alias is available. Used by the `/review-and-implement` handoff to reuse the review-time tier. |
|
|
991
991
|
| `--terminal terminal\|iterm` | auto-detect via `$TERM_PROGRAM` | Override the macOS terminal app (honored on macOS only) |
|
|
992
992
|
| `--dry-run` | off | Print intended actions; create no worktrees, open no tabs (any OS) |
|
|
993
993
|
| `--branch KEY=BRANCH` | `feature/<KEY>` | Use a custom branch for that ticket (repeatable) |
|
|
@@ -997,15 +997,17 @@ npx -y @bridge_gpt/mcp-server start-tickets [flags] KEY [KEY ...]
|
|
|
997
997
|
| `--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>'`. |
|
|
998
998
|
| `-h`, `--help` | — | Show usage |
|
|
999
999
|
|
|
1000
|
-
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
|
|
1000
|
+
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. Claude is the default. Cursor with `--auto` runs headlessly after a bounded authentication/readiness check; manual Cursor runs remain interactive for human review gates. To launch Cursor Agent instead of Claude Code:
|
|
1001
1001
|
|
|
1002
1002
|
```
|
|
1003
1003
|
npx -y @bridge_gpt/mcp-server start-tickets --agent cursor-agent BAPI-248
|
|
1004
1004
|
```
|
|
1005
1005
|
|
|
1006
|
-
**Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket's `difficulty` (1-2 → cheap, 3-6 → basic, 7-10 → premium) and
|
|
1006
|
+
**Difficulty-based model routing.** Before launching each agent, the CLI selects an implementation **model tier** from the ticket's `difficulty` (1-2 → cheap, 3-6 → basic, 7-10 → premium) and may inject `--model` at the spawn boundary. The Python backend returns only the coarse tier (`GET /jira/tickets/{KEY}/model-tier`, computing + caching difficulty on demand). Claude and Cursor map that tier through their agent registry aliases; Cursor validates against `cursor-agent --list-models`. Routing is gated per repo by `difficulty_model_routing_enabled` (default **ON**). Claude/Cursor may use `difficulty_model_tier_overrides` (tier → alias) and retain their premium fallback behavior on an unknown tier or transient failure. When an alias cannot be validated, the CLI omits `--model` and launches the agent default. `--dry-run` resolves routing read-only to preview the command without creating worktrees or opening tabs.
|
|
1007
1007
|
|
|
1008
|
-
|
|
1008
|
+
For Codex, `GET /jira/codex-model-catalog?repo_name=...` is the sole tier-to-model authority. Bridge derives its three bare CLI IDs and SHA-256 mapping revision at request time from `MODEL_REGISTRY`; the launcher fetches and validates that catalog once per invocation. Repository `difficulty_model_tier_overrides` do not apply to Codex. A failed access, config, tier, catalog, revision, or alias check omits `--model` and launches the configured Codex default. The catalog expresses **Bridge provider policy only**: it cannot confirm that the local Codex login or subscription can use a model. The CLI has no supported non-interactive entitlement probe, so routing emits that limitation as a diagnostic and never uses Bridge's OpenAI API key as proof of local access.
|
|
1009
|
+
|
|
1010
|
+
**Coarse `--tier` override.** Passing `--tier cheap|basic|premium` bypasses only the per-ticket difficulty lookup and applies that tier to every ticket. Claude/Cursor use the same alias resolution as normal routing; Codex uses the fetched catalog. It is never a raw model alias, and no API key or credential belongs in the spawned command. A malformed tier requests the premium fallback when its alias is available; otherwise the agent default launches. `/review-and-implement` uses this flag to hand its review-time tier snapshot to the fresh implementation session.
|
|
1009
1011
|
|
|
1010
1012
|
**Conductor observability (opt-in via `--conductor`, BAPI-394).** Conductor is **off by default**. With `--conductor`, a run mints a conductor `run_id`, emits events into the local ledger (`~/.config/bridge/events.db`), opens a supervisor peer tab, and (for Claude Code) injects a secret-free lifecycle hook into each worktree; it also sets `BRIDGE_MCP_PROFILE=conductor` so workers get the conductor MCP tools. Observability is best-effort — a conductor failure never blocks a spawn, and `--dry-run` performs no conductor side effects. (Epic-tick dispatch always enables conductor internally.) See **[CONDUCTOR.md](./docs/CONDUCTOR.md)** for the full observability, git-hook, done-gate, and auto-merge reference.
|
|
1011
1013
|
|
|
@@ -5,6 +5,10 @@ export const AGENT_AUTH = {
|
|
|
5
5
|
note: "Claude Code authenticates via keychain/OAuth — cannot be verified from the environment.",
|
|
6
6
|
},
|
|
7
7
|
"cursor-agent": { kind: "env", varName: "CURSOR_API_KEY" },
|
|
8
|
+
codex: {
|
|
9
|
+
kind: "interactive",
|
|
10
|
+
note: "Codex authenticates via login/OAuth — cannot be verified from the environment.",
|
|
11
|
+
},
|
|
8
12
|
};
|
|
9
13
|
/** Default hard timeout per headless probe run (ms). The cursor `-p` hang bug is version-sensitive. */
|
|
10
14
|
export const DEFAULT_PROBE_TIMEOUT_MS = 90_000;
|
package/build/agent-registry.js
CHANGED
|
@@ -26,7 +26,7 @@ export function isModelTier(value) {
|
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* The registry: the ONLY place mapping an agent name to its command/spec. Seeded
|
|
29
|
-
* with
|
|
29
|
+
* with `claude` (default), `cursor-agent`, and `codex`. `as const satisfies` keeps
|
|
30
30
|
* the literal keys (so `AgentName` is a precise union) while type-checking each
|
|
31
31
|
* value against `AgentSpec`.
|
|
32
32
|
*/
|
|
@@ -108,6 +108,20 @@ export const AGENT_REGISTRY = {
|
|
|
108
108
|
premium: "claude-opus-4-8-thinking-high",
|
|
109
109
|
},
|
|
110
110
|
},
|
|
111
|
+
codex: {
|
|
112
|
+
name: "codex",
|
|
113
|
+
command: "codex",
|
|
114
|
+
promptArgStyle: "positional",
|
|
115
|
+
installHint: {
|
|
116
|
+
darwin: "npm install -g @openai/codex",
|
|
117
|
+
linux: "npm install -g @openai/codex",
|
|
118
|
+
win32: "npm install -g @openai/codex",
|
|
119
|
+
},
|
|
120
|
+
authNote: "Codex authenticates interactively on first run — follow its login prompt if asked.",
|
|
121
|
+
// The launch boundary validates a fresh Bridge catalog before injection.
|
|
122
|
+
supportsModelOverride: true,
|
|
123
|
+
modelFlag: "--model",
|
|
124
|
+
},
|
|
111
125
|
};
|
|
112
126
|
/** The default agent used when `--agent` is omitted. */
|
|
113
127
|
export const DEFAULT_AGENT_NAME = "claude";
|
|
@@ -130,7 +144,7 @@ export function resolveAgentSpec(name) {
|
|
|
130
144
|
return null;
|
|
131
145
|
return AGENT_REGISTRY[resolved];
|
|
132
146
|
}
|
|
133
|
-
/** Comma-delimited
|
|
147
|
+
/** Comma-delimited registry names for CLI validation errors (`claude, cursor-agent, codex`). */
|
|
134
148
|
export function formatValidAgentNames() {
|
|
135
149
|
return listAgentNames().join(", ");
|
|
136
150
|
}
|
|
@@ -154,10 +168,13 @@ export function resolveModelAlias(agent, tier, overrides) {
|
|
|
154
168
|
return null;
|
|
155
169
|
if (!tier)
|
|
156
170
|
return null;
|
|
171
|
+
// Codex has no static or repository-override model authority.
|
|
172
|
+
if (agent.name === "codex")
|
|
173
|
+
return null;
|
|
157
174
|
const override = overrides?.[tier];
|
|
158
175
|
const candidate = typeof override === "string" && override.trim().length > 0
|
|
159
176
|
? override.trim()
|
|
160
|
-
: agent.tierModels[tier];
|
|
177
|
+
: agent.tierModels?.[tier];
|
|
161
178
|
if (typeof candidate !== "string" || !isValidModelAlias(candidate))
|
|
162
179
|
return null;
|
|
163
180
|
if (agent.staticModelAliasAllowlist && !agent.staticModelAliasAllowlist.includes(candidate)) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Per-process Codex MCP scope, derived from the same manifest and shim as worktree provisioning. */
|
|
2
|
+
import { readBridgeConfig } from "./bridge-config.js";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { codexBridgeTableNames } from "./mcp-host-config.js";
|
|
5
|
+
import { MCP_HOST_TARGETS, resolveHostTargetPath } from "./mcp-host-targets.js";
|
|
6
|
+
import { buildMcpServerEntriesForManifest, normalizeWorktreePathForRegistration, } from "./mcp-provisioning.js";
|
|
7
|
+
import { MCP_SERVER_NAME } from "./mcp-identity.js";
|
|
8
|
+
function tomlEntry(entry) {
|
|
9
|
+
return `{ command = ${JSON.stringify(entry.command)}, args = [${entry.args.map((arg) => JSON.stringify(arg)).join(", ")}] }`;
|
|
10
|
+
}
|
|
11
|
+
/** A missing Bridge target is a launch failure, never a fallback to user-scoped config. */
|
|
12
|
+
export async function resolveCodexLaunchScope(root, deps, codexConfigPath = resolveHostTargetPath(MCP_HOST_TARGETS.codex, { cwd: root, homedir: os.homedir() })) {
|
|
13
|
+
const normalized = normalizeWorktreePathForRegistration(root, deps);
|
|
14
|
+
if (!normalized.ok)
|
|
15
|
+
return { ok: false, error: "Codex MCP scope unavailable: invalid repository path." };
|
|
16
|
+
let manifest;
|
|
17
|
+
try {
|
|
18
|
+
const read = await readBridgeConfig(normalized.path, { readFile: deps.readFile });
|
|
19
|
+
if (!read.ok)
|
|
20
|
+
return { ok: false, error: "Codex MCP scope unavailable: .bridge/config is missing or invalid." };
|
|
21
|
+
manifest = read.manifest;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { ok: false, error: "Codex MCP scope unavailable: could not read .bridge/config." };
|
|
25
|
+
}
|
|
26
|
+
const built = buildMcpServerEntriesForManifest(manifest, normalized.path, deps.mcpServerInvocation, deps.bridgeApiBaseUrl);
|
|
27
|
+
const bridge = built.entries[MCP_SERVER_NAME];
|
|
28
|
+
if (!bridge)
|
|
29
|
+
return { ok: false, error: "Codex MCP scope unavailable: .bridge/config has no Bridge target." };
|
|
30
|
+
let existingConfig = "";
|
|
31
|
+
try {
|
|
32
|
+
existingConfig = await deps.readFile(codexConfigPath);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code !== "ENOENT") {
|
|
36
|
+
return { ok: false, error: "Codex MCP scope unavailable: could not inspect Codex host config." };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const registeredNames = codexBridgeTableNames(existingConfig);
|
|
40
|
+
if (registeredNames.length > 1) {
|
|
41
|
+
return { ok: false, error: "Codex MCP scope unavailable: duplicate Bridge registrations in Codex host config." };
|
|
42
|
+
}
|
|
43
|
+
const bridgeKey = registeredNames[0] ?? MCP_SERVER_NAME;
|
|
44
|
+
const argv = ["-C", normalized.path];
|
|
45
|
+
for (const [name, entry] of Object.entries(built.entries)) {
|
|
46
|
+
argv.push("-c", `mcp_servers.${name === MCP_SERVER_NAME ? bridgeKey : name}=${tomlEntry(entry)}`);
|
|
47
|
+
}
|
|
48
|
+
return { ok: true, argv };
|
|
49
|
+
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Codex discovers repository-local skills from `.agents/skills/<name>/SKILL.md`.
|
|
5
5
|
* Bridge command sources use a standalone `$ARGUMENTS` marker, which is a
|
|
6
6
|
* Claude/Cursor command convention rather than a Codex input variable. This
|
|
7
|
-
* adapter replaces
|
|
8
|
-
*
|
|
7
|
+
* adapter replaces that marker with the explicit-skill input contract and
|
|
8
|
+
* translates references to shipped slash commands into Codex skill names.
|
|
9
9
|
*/
|
|
10
10
|
export const CODEX_SKILLS_DIR = ".agents/skills";
|
|
11
11
|
function commandStem(filename) {
|
|
@@ -19,7 +19,7 @@ export function codexSkillNameForCommand(filename) {
|
|
|
19
19
|
return `bridge-${commandStem(filename)}`;
|
|
20
20
|
}
|
|
21
21
|
/** Render the one Codex-native skill artifact for a canonical command. */
|
|
22
|
-
export function renderCodexSkill(filename, command) {
|
|
22
|
+
export function renderCodexSkill(filename, command, knownCommands = [commandStem(filename)]) {
|
|
23
23
|
const stem = commandStem(filename);
|
|
24
24
|
const skillName = codexSkillNameForCommand(filename);
|
|
25
25
|
const invocation = `$${skillName}`;
|
|
@@ -28,10 +28,12 @@ export function renderCodexSkill(filename, command) {
|
|
|
28
28
|
`Treat the text after \`${invocation}\` in the user's message as this workflow's input.`,
|
|
29
29
|
"If no input follows the invocation, follow the canonical workflow's existing usage/error behavior.",
|
|
30
30
|
].join("\n");
|
|
31
|
+
const commandPattern = new RegExp(`(?<![A-Za-z0-9_./-])/(${knownCommands.join("|")})(?=[^A-Za-z0-9-]|$)`, "g");
|
|
31
32
|
const adaptedCommand = command
|
|
32
33
|
.split("\n")
|
|
33
34
|
.map((line) => (line.trim() === "$ARGUMENTS" ? inputContract : line))
|
|
34
|
-
.join("\n")
|
|
35
|
+
.join("\n")
|
|
36
|
+
.replace(commandPattern, (_match, name) => `$bridge-${name}`);
|
|
35
37
|
return {
|
|
36
38
|
skillName,
|
|
37
39
|
relativePath: `${CODEX_SKILLS_DIR}/${skillName}/SKILL.md`,
|
|
@@ -49,7 +51,8 @@ export function renderCodexSkill(filename, command) {
|
|
|
49
51
|
}
|
|
50
52
|
/** Build the complete, deterministically ordered Codex skill bundle. */
|
|
51
53
|
export function codexSkillAssets(commands) {
|
|
54
|
+
const names = Object.keys(commands).map(commandStem).sort((a, b) => b.length - a.length);
|
|
52
55
|
return Object.entries(commands)
|
|
53
56
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
54
|
-
.map(([filename, command]) => renderCodexSkill(filename, command));
|
|
57
|
+
.map(([filename, command]) => renderCodexSkill(filename, command, names));
|
|
55
58
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Specialize the shared front-door command for a known installation host. */
|
|
2
|
+
export function renderCommandForHost(filename, content, host) {
|
|
3
|
+
if (filename !== "review-and-start.md" || host !== "cursor")
|
|
4
|
+
return content;
|
|
5
|
+
const marker = "**Command host default: none.**";
|
|
6
|
+
if (!content.includes(marker))
|
|
7
|
+
throw new Error("review-and-start host default marker is missing");
|
|
8
|
+
return content.replace(marker, "**Command host default: cursor.**");
|
|
9
|
+
}
|
|
@@ -26,9 +26,11 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import path from "path";
|
|
28
28
|
import { COMMANDS } from "./commands.generated.js";
|
|
29
|
+
import { codexSkillAssets } from "./codex-skill-adapter.js";
|
|
29
30
|
import { ensureGitInfoExcluded } from "./git-ignore-utils.js";
|
|
30
31
|
/** The exact exclude entry appended for the command directory (POSIX-relative). */
|
|
31
32
|
const COMMAND_DIR_EXCLUDE_ENTRY = ".claude/commands/";
|
|
33
|
+
const CODEX_SKILLS_EXCLUDE_ENTRY = ".agents/skills/";
|
|
32
34
|
/** Bounded, secret-free error surfaced when the packaged bundle is empty. */
|
|
33
35
|
const EMPTY_BUNDLE_ERROR = "Command provisioning failed: the packaged command bundle is empty — reinstall or rebuild the MCP server package.";
|
|
34
36
|
/**
|
|
@@ -51,7 +53,7 @@ function isEnoentError(err) {
|
|
|
51
53
|
* for an expected filesystem/Git failure, and never leaks raw exception text,
|
|
52
54
|
* command output, or file contents.
|
|
53
55
|
*/
|
|
54
|
-
export async function provisionCommandsForWorktree(worktreeRoot, deps) {
|
|
56
|
+
export async function provisionCommandsForWorktree(worktreeRoot, deps, includeCodexSkills = false) {
|
|
55
57
|
const api = pathApiForCommandProvisioningPlatform(deps.platform);
|
|
56
58
|
const normalizedRoot = api.isAbsolute(worktreeRoot)
|
|
57
59
|
? api.normalize(worktreeRoot)
|
|
@@ -97,6 +99,26 @@ export async function provisionCommandsForWorktree(worktreeRoot, deps) {
|
|
|
97
99
|
"Command provisioning failed: could not write one or more packaged command assets.";
|
|
98
100
|
}
|
|
99
101
|
}
|
|
102
|
+
// Codex launch requires the full bundle. Fill only, preserving customer assets.
|
|
103
|
+
if (!fillError && includeCodexSkills) {
|
|
104
|
+
try {
|
|
105
|
+
for (const asset of codexSkillAssets(COMMANDS)) {
|
|
106
|
+
const target = api.join(normalizedRoot, asset.relativePath);
|
|
107
|
+
try {
|
|
108
|
+
await deps.readFile(target);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (!isEnoentError(err))
|
|
112
|
+
throw err;
|
|
113
|
+
await deps.mkdir(api.dirname(target), { recursive: true });
|
|
114
|
+
await deps.writeFile(target, asset.content);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
fillError = "Command provisioning failed: could not write one or more Codex skill assets.";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
100
122
|
// Phase 3 — ALWAYS attempt exclusion, even after a partial or failed fill, so
|
|
101
123
|
// partially bootstrapped files are never left visible to Git. The primary fill
|
|
102
124
|
// failure is preserved if both the fill and the exclusion fail.
|
|
@@ -109,10 +131,19 @@ export async function provisionCommandsForWorktree(worktreeRoot, deps) {
|
|
|
109
131
|
runCommand: deps.runCommand,
|
|
110
132
|
platform: deps.platform,
|
|
111
133
|
});
|
|
134
|
+
if (includeCodexSkills) {
|
|
135
|
+
await ensureGitInfoExcluded(normalizedRoot, CODEX_SKILLS_EXCLUDE_ENTRY, {
|
|
136
|
+
readFile: deps.readFile,
|
|
137
|
+
writeFile: deps.writeFile,
|
|
138
|
+
mkdir: deps.mkdir,
|
|
139
|
+
runCommand: deps.runCommand,
|
|
140
|
+
platform: deps.platform,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
112
143
|
}
|
|
113
144
|
catch {
|
|
114
145
|
excludeError =
|
|
115
|
-
"Command provisioning failed: could not add
|
|
146
|
+
"Command provisioning failed: could not add generated command assets to the worktree Git exclude file.";
|
|
116
147
|
}
|
|
117
148
|
if (fillError)
|
|
118
149
|
return { ok: false, error: fillError };
|
|
@@ -129,7 +160,7 @@ export async function provisionCommandsForWorktree(worktreeRoot, deps) {
|
|
|
129
160
|
* proceed. A defensive per-row catch guarantees one unexpected failure cannot
|
|
130
161
|
* abort later rows or reject the overall call.
|
|
131
162
|
*/
|
|
132
|
-
export async function provisionCommandsForCreatedWorktrees(rows, deps) {
|
|
163
|
+
export async function provisionCommandsForCreatedWorktrees(rows, deps, includeCodexSkills = false) {
|
|
133
164
|
const out = [];
|
|
134
165
|
for (const row of rows) {
|
|
135
166
|
if (row.status !== "created" || !row.path) {
|
|
@@ -137,7 +168,7 @@ export async function provisionCommandsForCreatedWorktrees(rows, deps) {
|
|
|
137
168
|
continue;
|
|
138
169
|
}
|
|
139
170
|
try {
|
|
140
|
-
const result = await provisionCommandsForWorktree(row.path, deps);
|
|
171
|
+
const result = await provisionCommandsForWorktree(row.path, deps, includeCodexSkills);
|
|
141
172
|
if (result.ok) {
|
|
142
173
|
out.push(row);
|
|
143
174
|
}
|