@bridge_gpt/mcp-server 0.2.2 → 0.2.3
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 +97 -15
- package/build/agent-config-credential-migration.js +272 -0
- package/build/agents.generated.js +1 -1
- package/build/chain-orchestrator.js +16 -1
- package/build/commands.generated.js +9 -7
- package/build/conductor/bridge-api-client.js +625 -0
- package/build/conductor/claude-hook.js +251 -0
- package/build/conductor/cli.js +1048 -0
- package/build/conductor/data-normalization.js +114 -0
- package/build/conductor/doctor.js +164 -0
- package/build/conductor/done-gate.js +325 -0
- package/build/conductor/epic-reconcile.js +139 -0
- package/build/conductor/epic-runtime.js +611 -0
- package/build/conductor/epic-state.js +125 -0
- package/build/conductor/errors.js +85 -0
- package/build/conductor/git-ci-types.js +129 -0
- package/build/conductor/git-hooks.js +218 -0
- package/build/conductor/git-inspection.js +185 -0
- package/build/conductor/git-producer.js +137 -0
- package/build/conductor/merge-ledger.js +198 -0
- package/build/conductor/paths.js +224 -0
- package/build/conductor/plan.js +77 -0
- package/build/conductor/pr-ci-producer.js +427 -0
- package/build/conductor/pr-discovery.js +135 -0
- package/build/conductor/producer-ledger.js +125 -0
- package/build/conductor/redaction.js +112 -0
- package/build/conductor/store.js +1156 -0
- package/build/conductor/supervisor-config.js +150 -0
- package/build/conductor/supervisor-escalation.js +244 -0
- package/build/conductor/supervisor-judgment-python.js +141 -0
- package/build/conductor/supervisor-judgment.js +215 -0
- package/build/conductor/supervisor-ledger.js +119 -0
- package/build/conductor/supervisor-merge.js +127 -0
- package/build/conductor/supervisor-message-relay.js +61 -0
- package/build/conductor/supervisor-notification.js +39 -0
- package/build/conductor/supervisor-runtime.js +351 -0
- package/build/conductor/supervisor-state.js +572 -0
- package/build/conductor/supervisor-types.js +16 -0
- package/build/conductor/taxonomy.js +58 -0
- package/build/conductor/tools.js +367 -0
- package/build/conductor/types.js +9 -0
- package/build/conductor-bin.js +21 -0
- package/build/conductor-claude-hook-bin.js +21 -0
- package/build/credential-store.js +175 -4
- package/build/credentials-cli.js +223 -0
- package/build/decision-page-schema.js +60 -0
- package/build/decision-page-template.js +262 -10
- package/build/doctor.js +5 -1
- package/build/index.js +468 -59
- package/build/pipeline-orchestrator.js +5 -1
- package/build/pipeline-utils.js +45 -5
- package/build/pipelines.generated.js +37 -9
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +596 -0
- package/build/scheduled-prompt.js +16 -10
- package/build/start-tickets-conductor.js +496 -0
- package/build/start-tickets-prereqs.js +32 -23
- package/build/start-tickets-repo.js +49 -0
- package/build/start-tickets.js +682 -81
- package/build/version.generated.js +1 -1
- package/design-assets/favicon/android-chrome-192x192.png +0 -0
- package/design-assets/favicon/android-chrome-512x512.png +0 -0
- package/design-assets/favicon/apple-touch-icon.png +0 -0
- package/design-assets/favicon/favicon-16x16.png +0 -0
- package/design-assets/favicon/favicon-32x32.png +0 -0
- package/design-assets/favicon/favicon.ico +0 -0
- package/design-assets/favicon/site.webmanifest +1 -0
- package/design-assets/just-logo-rough-draft.png +0 -0
- package/package.json +17 -5
- package/pipelines/idea-to-ticket.json +5 -0
- package/pipelines/plan-epic.json +16 -1
- package/pipelines/review-ticket.json +2 -1
- package/public/css/main.min.css +2 -0
- package/public/css/main.min.css.map +1 -0
- package/public/fonts/OFL.txt +93 -0
- package/public/fonts/SourceSansPro-Black.ttf +0 -0
- package/public/fonts/SourceSansPro-BlackItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-Bold.ttf +0 -0
- package/public/fonts/SourceSansPro-BoldItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-ExtraLight.ttf +0 -0
- package/public/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-Italic.ttf +0 -0
- package/public/fonts/SourceSansPro-Light.ttf +0 -0
- package/public/fonts/SourceSansPro-LightItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-Regular.ttf +0 -0
- package/public/fonts/SourceSansPro-SemiBold.ttf +0 -0
- package/public/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
- package/public/img/bridge-logo-160x51.webp +0 -0
- package/public/img/bridge-logo-300x92.webp +0 -0
- package/public/img/favicon/android-chrome-192x192.png +0 -0
- package/public/img/favicon/android-chrome-512x512.png +0 -0
- package/public/img/favicon/apple-touch-icon.png +0 -0
- package/public/img/favicon/favicon-16x16.png +0 -0
- package/public/img/favicon/favicon-32x32.png +0 -0
- package/public/img/favicon/favicon.ico +0 -0
- package/public/img/favicon/site.webmanifest +1 -0
- package/public/img/installation/bitbucket/app-password-1.png +0 -0
- package/public/img/installation/bitbucket/app-password-2.png +0 -0
- package/public/img/installation/bitbucket/create-token-1.png +0 -0
- package/public/img/installation/bitbucket/create-token-2.png +0 -0
- package/public/img/installation/bitbucket/webhook-1.png +0 -0
- package/public/img/installation/github/github-review-webhook.png +0 -0
- package/public/img/installation/jira/credentials/api-key.png +0 -0
- package/public/img/installation/jira/webhook/create-rule.png +0 -0
- package/public/img/installation/jira/webhook/project-settings.png +0 -0
- package/public/img/installation/jira/webhook/rule-create-1.png +0 -0
- package/public/img/installation/jira/webhook/rule-create-2.png +0 -0
- package/public/img/installation/jira/webhook/rule-create-3.png +0 -0
- package/public/img/installation/pinecone/pinecone-api-key.png +0 -0
- package/public/img/installation/pinecone/pinecone-index.png +0 -0
- package/public/js/main.min.js +2 -0
- package/public/js/main.min.js.map +1 -0
- package/smoke-test/SMOKE-TEST.md +16 -8
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code → conductor lifecycle hook writer.
|
|
3
|
+
*
|
|
4
|
+
* This is a dependency-light command-hook entrypoint registered into a spawned
|
|
5
|
+
* Claude worker's `.claude/settings.local.json` by `start-tickets`. Claude Code
|
|
6
|
+
* invokes it for lifecycle events (SessionStart, Stop, SubagentStop,
|
|
7
|
+
* Notification, optionally PreToolUse), passing the native hook JSON on stdin.
|
|
8
|
+
* The writer:
|
|
9
|
+
*
|
|
10
|
+
* 1. reads + parses the native Claude hook payload from stdin,
|
|
11
|
+
* 2. maps the Claude lifecycle event to a canonical conductor semantic type,
|
|
12
|
+
* 3. builds a normalized {@link ConductorEventInput} — Claude-native fields are
|
|
13
|
+
* kept ONLY under `data.raw`, never promoted to the envelope or normalized
|
|
14
|
+
* data fields,
|
|
15
|
+
* 4. shells out to the local `conductor emit-event` CLI using list-based
|
|
16
|
+
* subprocess arguments (never `shell: true`) and writes the normalized
|
|
17
|
+
* `data` object to the child's stdin via `--data-json-stdin`, so raw
|
|
18
|
+
* payloads and possible secrets never appear in the process argument list.
|
|
19
|
+
*
|
|
20
|
+
* The hook is strictly non-blocking: unmapped events, missing conductor
|
|
21
|
+
* identity, parse failures, and emission failures all resolve to exit code `0`
|
|
22
|
+
* with at most a generic, secret-free warning so the Claude tool loop is never
|
|
23
|
+
* blocked or polluted with diagnostics.
|
|
24
|
+
*/
|
|
25
|
+
import { spawnSync } from "node:child_process";
|
|
26
|
+
import { readFileSync } from "node:fs";
|
|
27
|
+
/** Generic, secret-free warning emitted when an emission attempt fails. */
|
|
28
|
+
export const CONDUCTOR_HOOK_EMIT_FAILED_WARNING = "Warning: conductor hook emit failed.";
|
|
29
|
+
/**
|
|
30
|
+
* Extract the Claude lifecycle event name from a hook payload. The verified
|
|
31
|
+
* Claude hook contract field (`hook_event_name`) takes priority; a small set of
|
|
32
|
+
* defensive alternatives is accepted only as internal parsing inputs in case the
|
|
33
|
+
* field name drifts. Returns `null` when no recognizable name is present.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveClaudeHookEventName(payload) {
|
|
36
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const record = payload;
|
|
40
|
+
// Verified contract field first, then defensive-only alternatives.
|
|
41
|
+
const candidates = ["hook_event_name", "hookEventName", "event_name", "event"];
|
|
42
|
+
for (const key of candidates) {
|
|
43
|
+
const value = record[key];
|
|
44
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
45
|
+
return value.trim();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Map a Claude lifecycle event name to a canonical conductor semantic type.
|
|
52
|
+
* Unknown events map to `null` (the caller then skips emission).
|
|
53
|
+
*/
|
|
54
|
+
export function mapClaudeHookEventToSemanticType(eventName) {
|
|
55
|
+
switch (eventName) {
|
|
56
|
+
case "SessionStart":
|
|
57
|
+
return "run.started";
|
|
58
|
+
case "Stop":
|
|
59
|
+
return "run.stopped";
|
|
60
|
+
case "SubagentStop":
|
|
61
|
+
return "run.stopped";
|
|
62
|
+
case "Notification":
|
|
63
|
+
return "agent.notification";
|
|
64
|
+
case "PreToolUse":
|
|
65
|
+
return "tool.intent";
|
|
66
|
+
default:
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Conductor identity env required before any worker event may be emitted. */
|
|
71
|
+
const REQUIRED_IDENTITY_ENV = [
|
|
72
|
+
"BAPI_CONDUCTOR_RUN_ID",
|
|
73
|
+
"BAPI_CONDUCTOR_WORKER_ID",
|
|
74
|
+
"BAPI_CONDUCTOR_TICKET_KEY",
|
|
75
|
+
"BAPI_CONDUCTOR_WORKTREE_PATH",
|
|
76
|
+
];
|
|
77
|
+
function nonEmpty(value) {
|
|
78
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a normalized {@link ConductorEventInput} from a Claude hook payload and
|
|
82
|
+
* the process environment, or `null` when the event is unmapped or the
|
|
83
|
+
* conductor identity is incomplete.
|
|
84
|
+
*
|
|
85
|
+
* Identity gating: requires `BAPI_CONDUCTOR_ENABLED=1` and all of
|
|
86
|
+
* {@link REQUIRED_IDENTITY_ENV}. Any missing value returns `null` WITHOUT
|
|
87
|
+
* emitting — this prevents accidental emissions from unrelated worktrees that
|
|
88
|
+
* happen to have the hook on PATH.
|
|
89
|
+
*
|
|
90
|
+
* Secret/normalization boundary: the COMPLETE native Claude payload is placed
|
|
91
|
+
* only under `data.raw`. No Claude-native field (session id, transcript path,
|
|
92
|
+
* tool name, tool input, notification text) is promoted to the envelope or to
|
|
93
|
+
* any normalized `data` field.
|
|
94
|
+
*/
|
|
95
|
+
export function buildClaudeHookConductorEvent(payload, env) {
|
|
96
|
+
const eventName = resolveClaudeHookEventName(payload);
|
|
97
|
+
const type = mapClaudeHookEventToSemanticType(eventName);
|
|
98
|
+
if (type === null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (env.BAPI_CONDUCTOR_ENABLED !== "1") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
for (const key of REQUIRED_IDENTITY_ENV) {
|
|
105
|
+
if (!nonEmpty(env[key])) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const ticketKey = env.BAPI_CONDUCTOR_TICKET_KEY.trim();
|
|
110
|
+
const worktreePath = env.BAPI_CONDUCTOR_WORKTREE_PATH.trim();
|
|
111
|
+
const details = {
|
|
112
|
+
ticket_key: ticketKey,
|
|
113
|
+
worktree_path: worktreePath,
|
|
114
|
+
};
|
|
115
|
+
if (nonEmpty(env.BAPI_CONDUCTOR_REPO_NAME)) {
|
|
116
|
+
details.repo = env.BAPI_CONDUCTOR_REPO_NAME.trim();
|
|
117
|
+
}
|
|
118
|
+
// The native payload lives ONLY under data.raw. Wrap non-object payloads so the
|
|
119
|
+
// raw contract is always an object, never echoing native fields elsewhere.
|
|
120
|
+
const raw = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
121
|
+
? payload
|
|
122
|
+
: { payload };
|
|
123
|
+
return {
|
|
124
|
+
source: "claude-code",
|
|
125
|
+
type,
|
|
126
|
+
subject: ticketKey,
|
|
127
|
+
run_id: env.BAPI_CONDUCTOR_RUN_ID.trim(),
|
|
128
|
+
worker_id: env.BAPI_CONDUCTOR_WORKER_ID.trim(),
|
|
129
|
+
producer: "claude-code-hook",
|
|
130
|
+
observed_via: "claude-code-hook",
|
|
131
|
+
data: {
|
|
132
|
+
details,
|
|
133
|
+
raw,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build the list-based `emit-event` arguments for a normalized event. The
|
|
139
|
+
* normalized `data` object is intentionally NOT serialized into the argument
|
|
140
|
+
* list — it is delivered to the child via stdin through `--data-json-stdin`, so
|
|
141
|
+
* raw payloads/secrets never reach the process table.
|
|
142
|
+
*/
|
|
143
|
+
export function buildConductorEmitEventArgs(event) {
|
|
144
|
+
const args = ["--type", event.type, "--source", event.source];
|
|
145
|
+
if (nonEmpty(event.subject ?? undefined))
|
|
146
|
+
args.push("--subject", event.subject);
|
|
147
|
+
if (nonEmpty(event.run_id ?? undefined))
|
|
148
|
+
args.push("--run-id", event.run_id);
|
|
149
|
+
if (nonEmpty(event.worker_id ?? undefined))
|
|
150
|
+
args.push("--worker-id", event.worker_id);
|
|
151
|
+
if (nonEmpty(event.producer ?? undefined))
|
|
152
|
+
args.push("--producer", event.producer);
|
|
153
|
+
if (nonEmpty(event.observed_via ?? undefined)) {
|
|
154
|
+
args.push("--observed-via", event.observed_via);
|
|
155
|
+
}
|
|
156
|
+
args.push("--data-json-stdin", "--json");
|
|
157
|
+
return args;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Resolve how to invoke `conductor emit-event`. Prefers `BAPI_CONDUCTOR_CLI_FILE`
|
|
161
|
+
* (run via the current Node executable so no global install is required),
|
|
162
|
+
* otherwise falls back to `BAPI_CONDUCTOR_BIN` or the bare `conductor` binary on
|
|
163
|
+
* PATH. `emitArgs` are appended after the `emit-event` subcommand.
|
|
164
|
+
*/
|
|
165
|
+
export function resolveConductorEmitCommand(env, emitArgs) {
|
|
166
|
+
const cliFile = env.BAPI_CONDUCTOR_CLI_FILE;
|
|
167
|
+
if (nonEmpty(cliFile)) {
|
|
168
|
+
return {
|
|
169
|
+
command: process.execPath,
|
|
170
|
+
args: [cliFile.trim(), "emit-event", ...emitArgs],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const bin = nonEmpty(env.BAPI_CONDUCTOR_BIN) ? env.BAPI_CONDUCTOR_BIN.trim() : "conductor";
|
|
174
|
+
return { command: bin, args: ["emit-event", ...emitArgs] };
|
|
175
|
+
}
|
|
176
|
+
const defaultConductorSpawn = (command, args, input) => {
|
|
177
|
+
const result = spawnSync(command, args, {
|
|
178
|
+
input,
|
|
179
|
+
encoding: "utf-8",
|
|
180
|
+
// Never use a shell: list args + stdin keep raw payloads/secrets out of any
|
|
181
|
+
// shell-interpreted command string.
|
|
182
|
+
shell: false,
|
|
183
|
+
});
|
|
184
|
+
return { status: result.status, error: result.error };
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Emit a normalized conductor event by shelling to the local `conductor`
|
|
188
|
+
* CLI. The normalized `data` object is written to the child's stdin; envelope
|
|
189
|
+
* fields are passed as list arguments. Returns `true` on a clean exit, `false`
|
|
190
|
+
* on any spawn error or non-zero exit. Never throws.
|
|
191
|
+
*/
|
|
192
|
+
export function emitClaudeHookEventWithConductorCli(event, deps = {}) {
|
|
193
|
+
const env = deps.env ?? process.env;
|
|
194
|
+
const spawn = deps.spawn ?? defaultConductorSpawn;
|
|
195
|
+
const emitArgs = buildConductorEmitEventArgs(event);
|
|
196
|
+
const { command, args } = resolveConductorEmitCommand(env, emitArgs);
|
|
197
|
+
// Only the normalized data object crosses the stdin boundary (mirrors the
|
|
198
|
+
// CLI's --data-json-stdin contract); raw payload material is inside it.
|
|
199
|
+
const stdinPayload = JSON.stringify(event.data ?? {});
|
|
200
|
+
try {
|
|
201
|
+
const result = spawn(command, args, stdinPayload);
|
|
202
|
+
if (result.error)
|
|
203
|
+
return false;
|
|
204
|
+
return result.status === 0;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Full hook runner: read the native Claude payload from stdin, map/normalize it,
|
|
212
|
+
* and emit via the conductor CLI. ALWAYS returns `0` (success) — unmapped
|
|
213
|
+
* events, missing identity, invalid JSON, and emission failures are swallowed so
|
|
214
|
+
* the Claude tool loop is never blocked. Any warning is generic and secret-free.
|
|
215
|
+
*/
|
|
216
|
+
export function runClaudeConductorHookCli(deps = {}) {
|
|
217
|
+
const env = deps.env ?? process.env;
|
|
218
|
+
const readStdin = deps.readStdin ?? (() => readStdinSync());
|
|
219
|
+
const warn = deps.warn ?? ((m) => process.stderr.write(`${m}\n`));
|
|
220
|
+
let payload;
|
|
221
|
+
try {
|
|
222
|
+
const raw = readStdin();
|
|
223
|
+
payload = JSON.parse(raw);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Invalid/empty hook JSON: warn generically (no raw fragment) and exit 0.
|
|
227
|
+
warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING);
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
let event;
|
|
231
|
+
try {
|
|
232
|
+
event = buildClaudeHookConductorEvent(payload, env);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING);
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
// Unmapped event or incomplete identity: nothing to emit, silently succeed.
|
|
239
|
+
if (event === null) {
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
const ok = emitClaudeHookEventWithConductorCli(event, { env, spawn: deps.spawn });
|
|
243
|
+
if (!ok) {
|
|
244
|
+
warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING);
|
|
245
|
+
}
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
/** Blocking read of the full stdin contents (fd 0). */
|
|
249
|
+
function readStdinSync() {
|
|
250
|
+
return readFileSync(0, "utf-8");
|
|
251
|
+
}
|