@bridge_gpt/mcp-server 0.2.48 → 0.2.50

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 (47) hide show
  1. package/README.md +24 -7
  2. package/build/base-ref.js +28 -3
  3. package/build/claude-review-workflow-drift-probe.js +130 -0
  4. package/build/claude-review-workflow-drift.js +173 -0
  5. package/build/claude-review-workflow.js +81 -16
  6. package/build/commands.generated.js +5 -5
  7. package/build/conductor/done-gate.js +25 -3
  8. package/build/conductor/install-doctor.js +65 -5
  9. package/build/conductor/latest-check-selector.js +170 -0
  10. package/build/conductor/local-merge.js +8 -6
  11. package/build/conductor-bin.js +1 -1
  12. package/build/{brainstorm-files.js → council-files.js} +15 -15
  13. package/build/decision-page-schema.js +1 -1
  14. package/build/docs.generated.js +1 -1
  15. package/build/doctor.js +162 -4
  16. package/build/executor/worktree.js +46 -1
  17. package/build/index.js +92 -51
  18. package/build/init.js +9 -2
  19. package/build/install-bridge.js +60 -2
  20. package/build/install-reexec.js +47 -9
  21. package/build/pipelines.generated.js +1 -1
  22. package/build/plane/cli.js +12 -2
  23. package/build/plane/manifest.js +25 -1
  24. package/build/plane/member-roster.js +61 -7
  25. package/build/plane/preflight.js +24 -9
  26. package/build/plane/supervisor.js +77 -5
  27. package/build/plane/types.js +23 -3
  28. package/build/readme.generated.js +1 -1
  29. package/build/run-unit-tests-launcher.js +2 -1
  30. package/build/stale-worktree-doctor.js +120 -0
  31. package/build/start-tickets-prereqs.js +70 -0
  32. package/build/start-tickets.js +91 -3
  33. package/build/version.generated.js +3 -2
  34. package/package.json +4 -2
  35. package/build/chain-orchestrator.js +0 -1457
  36. package/build/chain-utils.js +0 -68
  37. package/build/command-catalog.js +0 -376
  38. package/build/schedule-run.js +0 -1300
  39. package/build/schedule-store.js +0 -172
  40. package/build/scheduled-prompt.js +0 -115
  41. package/build/scheduler-backends/at-fallback.js +0 -139
  42. package/build/scheduler-backends/escaping.js +0 -143
  43. package/build/scheduler-backends/index.js +0 -72
  44. package/build/scheduler-backends/launchd.js +0 -225
  45. package/build/scheduler-backends/systemd-user.js +0 -250
  46. package/build/scheduler-backends/task-scheduler.js +0 -214
  47. package/build/scheduler-backends/types.js +0 -23
@@ -11,6 +11,7 @@
11
11
  * throws for caller input.
12
12
  */
13
13
  import { DEFAULT_GATE_NAME, REQUIRED_CI_CHECKS_GREEN, REVIEW_STATE, VERDICTLESS_DISPOSITIONS, normalizeCheckName, normalizeSha, stableJsonHash, } from "./git-ci-types.js";
14
+ import { selectLatestChecks } from "./latest-check-selector.js";
14
15
  // ---------------------------------------------------------------------------
15
16
  // Config parsing
16
17
  // ---------------------------------------------------------------------------
@@ -351,6 +352,16 @@ function normalizeOneCheck(name, raw) {
351
352
  const check = { name: checkName, complete, green };
352
353
  if (state !== undefined)
353
354
  check.state = state;
355
+ // BAPI-933: carry the producer's recency evidence through additively so a
356
+ // downstream reader of a normalized snapshot can see WHY this record won its
357
+ // name, without reaching back to the raw payload.
358
+ if (typeof raw.started_at === "string")
359
+ check.started_at = raw.started_at;
360
+ if (typeof raw.completed_at === "string")
361
+ check.completed_at = raw.completed_at;
362
+ if (typeof raw.recency_id === "number" && Number.isFinite(raw.recency_id)) {
363
+ check.recency_id = raw.recency_id;
364
+ }
354
365
  return check;
355
366
  }
356
367
  /**
@@ -374,11 +385,22 @@ export function normalizeCiSnapshot(response) {
374
385
  if (source) {
375
386
  const rawChecks = source.checks ?? detail?.checks;
376
387
  if (Array.isArray(rawChecks)) {
377
- for (const entry of rawChecks) {
378
- if (!isPlainObject(entry))
379
- continue;
388
+ // BAPI-933: latest-wins per name. This loop previously kept whichever
389
+ // duplicate arrived FIRST while `allRequiredChecksGreen` kept the LAST —
390
+ // two order-dependent rules that could disagree with each other AND with
391
+ // GitHub. Both now read the same selector.
392
+ const { selected, ambiguousNames } = selectLatestChecks(rawChecks);
393
+ for (const entry of selected) {
380
394
  const normalized = normalizeOneCheck(entry.name, entry);
381
395
  if (normalized && !byName.has(normalized.name)) {
396
+ if (ambiguousNames.has(normalized.name)) {
397
+ // Unorderable, contradictory duplicates: neither complete nor green,
398
+ // so the gate keeps waiting rather than manufacturing a verdict the
399
+ // evidence does not support. The raw record is never mutated.
400
+ normalized.complete = false;
401
+ normalized.green = false;
402
+ normalized.recency_ambiguous = true;
403
+ }
382
404
  byName.set(normalized.name, normalized);
383
405
  checks.push(normalized);
384
406
  }
@@ -36,6 +36,9 @@ import { buildConductorDoctorReport, describeNativeLedgerAvailability, formatCon
36
36
  import { MANAGED_HOOK_NAMES } from "./git-hooks.js";
37
37
  import { inspectBridgeApiProfileToken, } from "../mcp-host-config.js";
38
38
  import { MCP_PACKAGE_NAME } from "../mcp-identity.js";
39
+ // BAPI-941: the shared drift classifier — a leaf module with no git, network, or
40
+ // CLI dependency, so it is safe to import from every diagnostic surface.
41
+ import { CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION, summarizeClaudeReviewWorkflowDrift, } from "../claude-review-workflow-drift.js";
39
42
  import { collectExecutorServiceDiagnostics, formatExecutorServiceDiagnosticsReport, } from "../doctor.js";
40
43
  import { collectInstallStatusChecks, formatInstallStatusReport, } from "../install-doctor.js";
41
44
  import { ConductorBridgeApiError, fetchConductorReadiness, } from "./bridge-api-client.js";
@@ -245,13 +248,36 @@ function reconcilerSection(readiness) {
245
248
  remediation: "start the reconciler (`conductor epic-tick` schedule) for this repository.",
246
249
  };
247
250
  }
248
- function workflowSection(presence, reviewPolicySource) {
251
+ function workflowSection(presence, reviewPolicySource, driftDetail) {
249
252
  if (presence === "present") {
250
253
  return {
251
254
  id: "claude-review-workflow",
252
255
  label: "claude-review workflow",
253
256
  status: "ok",
254
- detail: ".github/workflows/claude-review.yml present",
257
+ // BAPI-941: when the lineage comparison ran but could not be completed,
258
+ // that uncertainty rides on the SAME informational detail line rather than
259
+ // becoming a status of its own. The section stays `ok`: the workflow is
260
+ // installed and readable, which is all this section has ever asserted.
261
+ detail: driftDetail
262
+ ? `.github/workflows/claude-review.yml present — ${driftDetail}`
263
+ : ".github/workflows/claude-review.yml present",
264
+ };
265
+ }
266
+ // BAPI-941: present but stale. Reported through the SAME section, status
267
+ // vocabulary, and detail/remediation shape as every other degraded state — no
268
+ // new health component, no bespoke styling, and no color dependence: the
269
+ // detail's own wording carries the warning. The file IS installed, so this is
270
+ // never `absent`, and the policy-applicability branch below does not apply
271
+ // (a stale workflow only matters to a policy that consumes its verdict, and
272
+ // the caller only classifies drift when it does).
273
+ if (presence === "drifted") {
274
+ return {
275
+ id: "claude-review-workflow",
276
+ label: "claude-review workflow",
277
+ status: "degraded",
278
+ detail: driftDetail ??
279
+ ".github/workflows/claude-review.yml differs from the repository default branch",
280
+ remediation: CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION,
255
281
  };
256
282
  }
257
283
  // Applicability is policy-dependent: a run whose review signal is GitHub's own
@@ -593,7 +619,41 @@ function conductorLedgerLoadabilitySection(legacyConductor) {
593
619
  // ---------------------------------------------------------------------------
594
620
  // Runner
595
621
  // ---------------------------------------------------------------------------
596
- async function inspectWorkflowPresence(readWorkflowFile) {
622
+ /**
623
+ * BAPI-941: the workflow's local state AND, when the seam is supplied and the
624
+ * file is genuinely present, its lineage relative to the default branch. The
625
+ * drift detail is bounded text from the shared classifier — never git stderr and
626
+ * never file content.
627
+ */
628
+ async function inspectWorkflowPresence(readWorkflowFile, classifyWorkflowDrift) {
629
+ const local = await inspectWorkflowLocalPresence(readWorkflowFile);
630
+ if (local !== "present" || !classifyWorkflowDrift)
631
+ return { presence: local };
632
+ try {
633
+ const classification = await classifyWorkflowDrift();
634
+ if (classification.state === "drifted") {
635
+ return {
636
+ presence: "drifted",
637
+ driftDetail: summarizeClaudeReviewWorkflowDrift(classification),
638
+ };
639
+ }
640
+ if (classification.state === "unverified") {
641
+ // Inconclusive, so the presence state stays `present` and non-failing. The
642
+ // uncertainty is still reported, through the existing detail line rather
643
+ // than by inventing a failure the probe did not establish.
644
+ return {
645
+ presence: "present",
646
+ driftDetail: summarizeClaudeReviewWorkflowDrift(classification),
647
+ };
648
+ }
649
+ return { presence: "present" };
650
+ }
651
+ catch {
652
+ // A thrown probe is an unavailable comparison, not a fault in the workflow.
653
+ return { presence: "present" };
654
+ }
655
+ }
656
+ async function inspectWorkflowLocalPresence(readWorkflowFile) {
597
657
  try {
598
658
  await readWorkflowFile();
599
659
  return "present";
@@ -700,8 +760,8 @@ export async function runConductorInstallDoctor(deps) {
700
760
  sections.push(githubCredentialsSection(readiness));
701
761
  sections.push(githubActionsSection(readiness));
702
762
  }
703
- const presence = await inspectWorkflowPresence(deps.readWorkflowFile);
704
- sections.push(workflowSection(presence, deps.reviewPolicySource));
763
+ const workflow = await inspectWorkflowPresence(deps.readWorkflowFile, deps.classifyWorkflowDrift);
764
+ sections.push(workflowSection(workflow.presence, deps.reviewPolicySource, workflow.driftDetail));
705
765
  if (readiness) {
706
766
  sections.push(reconcilerSection(readiness));
707
767
  }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * BAPI-933 — one deterministic, recency-aware interpretation of duplicate CI
3
+ * check records, shared by every conductor consumer.
4
+ *
5
+ * GitHub returns EVERY check run at a head, stale ones included. Both
6
+ * `claude-review.yml` and conductor-ci use `concurrency: cancel-in-progress`,
7
+ * so an ordinary rapid push leaves a cancelled/failed run beside the newer
8
+ * successful run for the SAME required context. GitHub evaluates
9
+ * latest-per-context and reports the PR MERGEABLE/CLEAN; a consumer that keeps
10
+ * the wrong duplicate disagrees with GitHub and wedges the merge. That is
11
+ * exactly what happened to BAPI-912 / PR #1107, which parked three merge
12
+ * attempts on `ci_not_green` against a CLEAN pull request.
13
+ *
14
+ * Before this module the two consumers held two DIFFERENT order-dependent
15
+ * rules — `allRequiredChecksGreen` kept the LAST array entry per name,
16
+ * `normalizeCiSnapshot` kept the FIRST — so they could also disagree with each
17
+ * other. Neither was time-ordered. This selector replaces both.
18
+ *
19
+ * The rule is latest-wins, never any-success-wins: a newer `failure`,
20
+ * `cancelled`, or timeout still supersedes an older `success` and still blocks.
21
+ */
22
+ /** Normalize a check name, or `null` when it can never identify a context. */
23
+ export function normalizeSelectorCheckName(value) {
24
+ if (typeof value !== "string")
25
+ return null;
26
+ const trimmed = value.trim();
27
+ return trimmed.length > 0 ? trimmed : null;
28
+ }
29
+ /** Parse an ISO-8601 timestamp to epoch millis, or `null` when unusable. */
30
+ function parseTimestamp(value) {
31
+ if (typeof value !== "string")
32
+ return null;
33
+ const trimmed = value.trim();
34
+ if (trimmed.length === 0)
35
+ return null;
36
+ const millis = Date.parse(trimmed);
37
+ return Number.isNaN(millis) ? null : millis;
38
+ }
39
+ /**
40
+ * The producer's numeric recency id (check-run id, else check-suite id). Both
41
+ * increase monotonically on GitHub, so a higher id is a later run. Used only as
42
+ * the deterministic tie-breaker when no timestamp orders a pair.
43
+ */
44
+ function parseRecencyId(record) {
45
+ const candidates = [record.recency_id, record.id, record.check_suite_id];
46
+ const suite = record.check_suite;
47
+ if (suite !== null && typeof suite === "object") {
48
+ candidates.push(suite.id);
49
+ }
50
+ for (const candidate of candidates) {
51
+ if (typeof candidate === "number" && Number.isFinite(candidate))
52
+ return candidate;
53
+ if (typeof candidate === "string" && INTEGER_TEXT.test(candidate.trim())) {
54
+ return Number.parseInt(candidate.trim(), 10);
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ /** A run of ASCII digits and nothing else. */
60
+ const INTEGER_TEXT = /^[0-9]+$/;
61
+ /**
62
+ * Recency evidence for one record, ranked highest-confidence first: when the
63
+ * run STARTED (the property that actually distinguishes a re-run from the run
64
+ * it replaced), then when it completed, then the numeric id. Each component
65
+ * pairs a presence flag with its value so an absent field always sorts BELOW a
66
+ * present one.
67
+ */
68
+ function recencyKey(record) {
69
+ const started = parseTimestamp(record.started_at);
70
+ const completed = parseTimestamp(record.completed_at);
71
+ const id = parseRecencyId(record);
72
+ return [
73
+ started !== null ? [1, started] : [0, 0],
74
+ completed !== null ? [1, completed] : [0, 0],
75
+ id !== null ? [1, id] : [0, 0],
76
+ ];
77
+ }
78
+ /** Lexicographic compare of two recency keys. Negative when `a` is older. */
79
+ function compareRecency(a, b) {
80
+ const ka = recencyKey(a);
81
+ const kb = recencyKey(b);
82
+ for (let i = 0; i < ka.length; i += 1) {
83
+ if (ka[i][0] !== kb[i][0])
84
+ return ka[i][0] - kb[i][0];
85
+ if (ka[i][1] !== kb[i][1])
86
+ return ka[i][1] < kb[i][1] ? -1 : 1;
87
+ }
88
+ return 0;
89
+ }
90
+ /** The `(status, conclusion)` pair two duplicates must agree on to be non-ambiguous. */
91
+ function outcomeKey(record) {
92
+ const status = typeof record.status === "string" ? record.status.toLowerCase() : "";
93
+ const conclusion = typeof record.conclusion === "string" ? record.conclusion.toLowerCase() : "";
94
+ return status + " " + conclusion;
95
+ }
96
+ /** True when a record reads as a successful conclusion. */
97
+ function looksSuccessful(record) {
98
+ const conclusion = typeof record.conclusion === "string" ? record.conclusion.toLowerCase() : "";
99
+ const status = typeof record.status === "string" ? record.status.toLowerCase() : "";
100
+ const bucket = typeof record.bucket === "string" ? record.bucket.toLowerCase() : "";
101
+ return (record.green === true || conclusion === "success" || status === "success" || bucket === "pass");
102
+ }
103
+ /**
104
+ * Collapse `records` to at most one LATEST record per non-blank check name.
105
+ *
106
+ * Input order is irrelevant by construction — the first entry, the last entry,
107
+ * and any `success` among the duplicates carry no weight. Neither the input
108
+ * array nor any record in it is mutated; `selected` is a new array holding the
109
+ * same record references.
110
+ */
111
+ export function selectLatestChecks(records) {
112
+ const grouped = new Map();
113
+ const order = [];
114
+ for (const record of records) {
115
+ if (record === null || typeof record !== "object" || Array.isArray(record))
116
+ continue;
117
+ const name = normalizeSelectorCheckName(record.name);
118
+ if (name === null)
119
+ continue;
120
+ const bucket = grouped.get(name);
121
+ if (bucket === undefined) {
122
+ grouped.set(name, [record]);
123
+ order.push(name);
124
+ }
125
+ else {
126
+ bucket.push(record);
127
+ }
128
+ }
129
+ const selected = [];
130
+ const ambiguousNames = new Set();
131
+ for (const name of order) {
132
+ const candidates = grouped.get(name);
133
+ if (candidates.length === 1) {
134
+ selected.push(candidates[0]);
135
+ continue;
136
+ }
137
+ let best = candidates[0];
138
+ for (let i = 1; i < candidates.length; i += 1) {
139
+ if (compareRecency(candidates[i], best) > 0)
140
+ best = candidates[i];
141
+ }
142
+ const tied = candidates.filter((c) => compareRecency(c, best) === 0);
143
+ if (tied.length > 1 && new Set(tied.map(outcomeKey)).size > 1) {
144
+ // Indistinguishable recency, contradictory outcomes: fail closed on the
145
+ // least-green tied record and flag the name so consumers render it
146
+ // non-green AND incomplete.
147
+ ambiguousNames.add(name);
148
+ best = tied.find((c) => !looksSuccessful(c)) ?? tied[0];
149
+ }
150
+ selected.push(best);
151
+ }
152
+ return { selected, ambiguousNames };
153
+ }
154
+ /**
155
+ * Convenience view for a required-check evaluator: the selected record per
156
+ * name, with ambiguous names deliberately ABSENT so a caller that already
157
+ * treats a missing required context as not-green fails closed with no extra
158
+ * branching.
159
+ */
160
+ export function selectLatestChecksByName(records) {
161
+ const { selected, ambiguousNames } = selectLatestChecks(records);
162
+ const byName = new Map();
163
+ for (const record of selected) {
164
+ const name = normalizeSelectorCheckName(record.name);
165
+ if (name === null || ambiguousNames.has(name))
166
+ continue;
167
+ byName.set(name, record);
168
+ }
169
+ return { byName, ambiguousNames };
170
+ }
@@ -35,6 +35,7 @@
35
35
  import { spawn } from "child_process";
36
36
  import { pollCiChecksForCommit, ConductorBridgeApiError, safeDiagnosticMessage, } from "./bridge-api-client.js";
37
37
  import { isLikelyGhMergeConflictOutput, isPrMergeConflict, parseGhPrMergeabilityFields, } from "./github-mergeability.js";
38
+ import { selectLatestChecksByName } from "./latest-check-selector.js";
38
39
  const MERGE_METHODS = new Set(["squash", "merge", "rebase"]);
39
40
  /**
40
41
  * `gh pr view` field set for a merged-state / head-drift read. Includes
@@ -191,12 +192,13 @@ export function allRequiredChecksGreen(pollResponse, requiredChecks) {
191
192
  if (requiredChecks.length === 0) {
192
193
  return obj.all_passed === true;
193
194
  }
194
- const byName = new Map();
195
- for (const c of rawChecks) {
196
- const name = typeof c.name === "string" ? c.name : null;
197
- if (name !== null)
198
- byName.set(name, c);
199
- }
195
+ // BAPI-933: latest-wins per name, never array-order. This map previously kept
196
+ // whichever duplicate happened to sort LAST, so a stale `failure` beside a
197
+ // newer `success` for one required context refused a merge GitHub itself
198
+ // reported MERGEABLE/CLEAN (BAPI-912 / PR #1107, three parked attempts).
199
+ // `selectLatestChecksByName` also OMITS a name whose duplicates could not be
200
+ // ordered, so the `every(...)` below fails it closed as a missing context.
201
+ const { byName } = selectLatestChecksByName(rawChecks);
200
202
  const isGreen = (c) => {
201
203
  if (!c)
202
204
  return false;
@@ -100,7 +100,7 @@ var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyN
100
100
  `)){let norm=normalizeRepoRelativePath(line);norm&&!seen.has(norm)&&(seen.add(norm),files.push(norm))}return{ok:!0,files}}function analyzeDiffScope(input){if(!input.declared.specified)return{checked:!1,outOfScopeFiles:[],warning:null};let declaredSet=new Set(input.declared.files),outOfScope=input.changedFiles.filter(f=>!declaredSet.has(f)).sort();if(outOfScope.length===0)return{checked:!0,outOfScopeFiles:[],warning:null};let warning=`[file-scope-guard] ${input.ticketKey&&input.ticketKey.trim().length>0?input.ticketKey.trim():"unknown-ticket"}: ${outOfScope.length} file(s) changed outside the declared touched-file set (${input.declared.files.length} declared): ${outOfScope.join(", ")}. Warn-only \u2014 PR creation continues.`;return{checked:!0,outOfScopeFiles:outOfScope,warning}}function runFileScopeGuardCli(deps={}){let env=deps.env??process.env,writeOut=deps.writeOut??(m=>process.stdout.write(`${m}
101
101
  `)),writeErr=deps.writeErr??(m=>process.stderr.write(`${m}
102
102
  `)),ticketKey=env[FILE_SCOPE_GUARD_TICKET_KEY_ENV],declared=parseDeclaredTouchedFilesFromEnv(env);if(!declared.specified)return 0;let collected=collectBranchChangedFiles({cwd:deps.cwd,spawnSyncFn:deps.spawnSyncFn});if(!collected.ok)return writeErr(`[file-scope-guard] ${ticketKey??"unknown-ticket"}: unable to check file scope (git diff failed); continuing \u2014 PR creation is not blocked.`),0;let analysis=analyzeDiffScope({ticketKey,declared,changedFiles:collected.files});return analysis.warning&&writeOut(analysis.warning),0}var DECLARED_TOUCHED_FILES_ENV,FILE_SCOPE_GUARD_TICKET_KEY_ENV,FILE_SCOPE_GUARD_BASE_REF,defaultSpawnSync,init_file_scope_guard=__esm({"src/conductor/file-scope-guard.ts"(){"use strict";DECLARED_TOUCHED_FILES_ENV="BAPI_CONDUCTOR_DECLARED_TOUCHED_FILES_JSON",FILE_SCOPE_GUARD_TICKET_KEY_ENV="BAPI_CONDUCTOR_TICKET_KEY",FILE_SCOPE_GUARD_BASE_REF="origin/main";defaultSpawnSync=(command,args,options)=>spawnSync(command,args,options)}});var MCP_SERVER_NAME,MCP_PACKAGE_NAME,MCP_README_URI,LEGACY_SERVER_NAMES,RECOGNIZED_SERVER_NAMES,init_mcp_identity=__esm({"src/mcp-identity.ts"(){"use strict";MCP_SERVER_NAME="bridge",MCP_PACKAGE_NAME="@bridge_gpt/mcp-server",MCP_README_URI=`${MCP_SERVER_NAME}://readme`,LEGACY_SERVER_NAMES=["bridge-api"],RECOGNIZED_SERVER_NAMES=[MCP_SERVER_NAME,...LEGACY_SERVER_NAMES]}});import path4 from"node:path";function resolvePackageRootFromModuleUrl(moduleUrl){let pathname;try{pathname=decodeURIComponent(new URL(moduleUrl).pathname)}catch{return null}/^\/[A-Za-z]:/.test(pathname)&&(pathname=pathname.slice(1));let segments=pathname.split(/[\\/]/),markerIndex=-1;for(let i=segments.length-1;i>=0;i--)if(segments[i]==="src"||segments[i]==="build"){markerIndex=i;break}return markerIndex<=0?null:segments.slice(0,markerIndex).join("/")}function basenameAnySep(filePath){let segments=filePath.split(/[\\/]/);return segments[segments.length-1]??""}function resolveMcpShimInvocationForRuntime(deps){let nodeExecutable=deps.nodeExecutable??"node",packageRoot=resolvePackageRootFromModuleUrl(deps.moduleUrl);if(packageRoot){let candidate=`${packageRoot}/build/index.js`;if(deps.fileExists(candidate))return{form:"absolute-build-path",nodeExecutable,serverEntryPath:candidate}}let argv1=deps.argv1;return typeof argv1=="string"&&argv1.length>0&&path4.isAbsolute(argv1)&&basenameAnySep(argv1)==="index.js"&&deps.fileExists(argv1)?{form:"absolute-build-path",nodeExecutable,serverEntryPath:argv1}:{form:"npm-channel",command:"npx",packageSpec:deps.npmPackageSpec??DEFAULT_MCP_SERVER_NPM_CHANNEL_SPEC}}var DEFAULT_MCP_SERVER_NPM_CHANNEL_SPEC,init_mcp_server_invocation=__esm({"src/mcp-server-invocation.ts"(){"use strict";init_mcp_identity();DEFAULT_MCP_SERVER_NPM_CHANNEL_SPEC=`${MCP_PACKAGE_NAME}@latest`}});function resolveProfiles(raw){let baseline=new Set(["core"]);if(raw===void 0)return baseline;let trimmed=raw.trim();if(trimmed==="")return baseline;let tokens=trimmed.split(",").map(t=>t.trim().toLowerCase());if(tokens.some(t=>t==="full"))return new Set(["core","conductor","estimation","pipeline-authoring","sfcc","sfcc-write"]);for(let token of tokens)VALID_GROUPS.has(token)&&baseline.add(token);return baseline}var VALID_GROUPS,init_mcp_profile=__esm({"src/mcp-profile.ts"(){"use strict";VALID_GROUPS=new Set(["core","conductor","estimation","pipeline-authoring","sfcc","sfcc-write"])}});function listAgentNames(){return Object.keys(AGENT_REGISTRY)}function isAgentName(value){return listAgentNames().includes(value)}function resolveAgentSpec(name){let resolved=name??DEFAULT_AGENT_NAME;return isAgentName(resolved)?AGENT_REGISTRY[resolved]:null}var AGENT_REGISTRY,DEFAULT_AGENT_NAME,init_agent_registry=__esm({"src/agent-registry.ts"(){"use strict";AGENT_REGISTRY={claude:{name:"claude",command:"claude",promptArgStyle:"positional",installHint:{darwin:"npm install -g @anthropic-ai/claude-code",linux:"npm install -g @anthropic-ai/claude-code",win32:"npm install -g @anthropic-ai/claude-code"},authNote:"Claude Code authenticates interactively on first run \u2014 follow its login/auth prompt if asked.",supportsModelOverride:!0,modelFlag:"--model",tierModels:{cheap:"haiku",basic:"sonnet",premium:"opus"},staticModelAliasAllowlist:["haiku","sonnet","opus"],executorAdapter:{adapterId:"claude-reference",strategyId:"claude-strict-mcp-v1",adapterVersion:"1.0.0",supportedPlatforms:["darwin","linux"],capabilityStrategyIds:{mcpScoping:"strict-mcp-config",mcpInitParsing:"claude-system-init",authFailureDetection:"claude-stream-json-result",denyEnforcement:"claude-settings-deny",redaction:"claude-env-name-redaction",lifecycle:"claude-no-lifecycle"},managedCarriers:[],passthroughs:[{passthroughId:"claude-code-oauth-token",envName:"CLAUDE_CODE_OAUTH_TOKEN",billingClass:"subscription",ownership:"operator-owned",rule:"forward-when-present"}]}},"cursor-agent":{name:"cursor-agent",command:"cursor-agent",promptArgStyle:"positional",installHint:{darwin:"curl https://cursor.com/install -fsSL | bash",linux:"curl https://cursor.com/install -fsSL | bash",win32:"irm 'https://cursor.com/install?win32=true' | iex"},authNote:"Run cursor-agent login to authenticate; doctor checks PATH presence only, not login state.",supportsModelOverride:!0,modelFlag:"--model",tierModels:{cheap:"auto",basic:"claude-4.6-sonnet-medium",premium:"claude-opus-4-8-thinking-high"}}},DEFAULT_AGENT_NAME="claude"}});import path5 from"path";function hasControlChars(value){for(let i=0;i<value.length;i++){let code=value.charCodeAt(i);if(code<=31||code===127)return!0}return!1}function validateRepoName(raw){if(typeof raw!="string")return{ok:!1,error:"repo_name must be a string"};let value=raw.trim();return value.length===0?{ok:!1,error:"repo_name must be a non-empty string"}:value.includes("/")||value.includes("\\")?{ok:!1,error:"repo_name must not contain path separators"}:hasControlChars(value)?{ok:!1,error:"repo_name must not contain control characters"}:{ok:!0,value}}function validateMcpTarget(raw){if(typeof raw!="string")return{ok:!1,error:"mcp target must be a string"};let value=raw.trim();return value.length===0?{ok:!1,error:"mcp target must be a non-empty string"}:value.includes("/")||value.includes("\\")?{ok:!1,error:"mcp target must not contain path separators"}:hasControlChars(value)?{ok:!1,error:"mcp target must not contain control characters"}:{ok:!0,value}}function parseQuotedString(value){if(value.length<2||value[0]!=='"'||value[value.length-1]!=='"')return null;let inner=value.slice(1,-1);return inner.includes('"')?null:inner}function parseStringArray(value){let trimmed=value.trim();if(trimmed.length<2||trimmed[0]!=="["||trimmed[trimmed.length-1]!=="]")return null;let inner=trimmed.slice(1,-1).trim();if(inner.length===0)return[];let parts=inner.split(","),out=[];for(let part of parts){let element=parseQuotedString(part.trim());if(element===null)return null;out.push(element)}return out}function parseBridgeConfigToml(text){let lines=text.split(`
103
- `),repoName,sawRepoName=!1,mcp=[],currentMcp=null;for(let i=0;i<lines.length;i++){let lineNo=i+1,line=lines[i].trim();if(line.length===0||line.startsWith("#"))continue;if(line==="[[mcp]]"){currentMcp={headerLine:lineNo},mcp.push(currentMcp);continue}if(line.startsWith("["))return{ok:!1,kind:"parse-error",error:`Unsupported table header on line ${lineNo}; only [[mcp]] is allowed`};let eq=line.indexOf("=");if(eq===-1)return{ok:!1,kind:"parse-error",error:`Malformed line ${lineNo}; expected key = "value"`};let key=line.slice(0,eq).trim(),rawValue=line.slice(eq+1).trim();if(key.length===0)return{ok:!1,kind:"parse-error",error:`Malformed line ${lineNo}; missing key`};if(currentMcp===null){if(key==="repo_name"){if(sawRepoName)return{ok:!1,kind:"parse-error",error:`Duplicate repo_name on line ${lineNo}`};sawRepoName=!0;let stringValue=parseQuotedString(rawValue);if(stringValue===null)return{ok:!1,kind:"parse-error",error:`Expected a double-quoted string for '${key}' on line ${lineNo}`};let validated=validateRepoName(stringValue);if(!validated.ok)return{ok:!1,kind:"validation-error",error:validated.error};repoName=validated.value;continue}return key==="target"?{ok:!1,kind:"parse-error",error:`target on line ${lineNo} must appear inside an [[mcp]] section`}:{ok:!1,kind:"parse-error",error:`Unsupported key '${key}' on line ${lineNo}`}}if(key==="args"){if(currentMcp.args!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate args on line ${lineNo}`};let arr=parseStringArray(rawValue);if(arr===null)return{ok:!1,kind:"parse-error",error:`Expected a string array for 'args' on line ${lineNo}`};currentMcp.args=arr;continue}if(key==="target"||key==="command"||key==="secret_bundle"){let stringValue=parseQuotedString(rawValue);if(stringValue===null)return{ok:!1,kind:"parse-error",error:`Expected a double-quoted string for '${key}' on line ${lineNo}`};if(key==="target"){if(currentMcp.target!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate target on line ${lineNo}`};let validated=validateMcpTarget(stringValue);if(!validated.ok)return{ok:!1,kind:"validation-error",error:validated.error};currentMcp.target=validated.value;continue}if(key==="command"){if(currentMcp.command!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate command on line ${lineNo}`};currentMcp.command=stringValue;continue}if(currentMcp.secretBundle!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate secret_bundle on line ${lineNo}`};currentMcp.secretBundle=stringValue;continue}return{ok:!1,kind:"parse-error",error:`Unsupported key '${key}' inside [[mcp]] on line ${lineNo}`}}if(!sawRepoName||repoName===void 0)return{ok:!1,kind:"validation-error",error:"Missing required repo_name"};let cleaned=[];for(let entry of mcp){if(entry.target===void 0)return{ok:!1,kind:"validation-error",error:`An [[mcp]] section on line ${entry.headerLine} is missing its target`};if(entry.target!=="bapi"){if(entry.command===void 0||entry.command.trim().length===0)return{ok:!1,kind:"validation-error",error:`[[mcp]] target '${entry.target}' on line ${entry.headerLine} requires a non-empty command`};if(entry.args===void 0)return{ok:!1,kind:"validation-error",error:`[[mcp]] target '${entry.target}' on line ${entry.headerLine} requires an args array`};if(entry.secretBundle===void 0||entry.secretBundle.trim().length===0)return{ok:!1,kind:"validation-error",error:`[[mcp]] target '${entry.target}' on line ${entry.headerLine} requires a non-empty secret_bundle`}}let clean={target:entry.target};entry.command!==void 0&&(clean.command=entry.command),entry.args!==void 0&&(clean.args=entry.args),entry.secretBundle!==void 0&&(clean.secretBundle=entry.secretBundle),cleaned.push(clean)}return{ok:!0,manifest:{repoName,mcp:cleaned}}}function bridgeConfigPath(projectRoot){return path5.join(projectRoot,".bridge","config")}async function readBridgeConfig(projectRoot,deps){let filePath=bridgeConfigPath(projectRoot),raw;try{raw=await deps.readFile(filePath)}catch(err){return err&&typeof err=="object"&&err.code==="ENOENT"?{ok:!1,kind:"missing"}:{ok:!1,kind:"parse-error",error:"Unable to read .bridge/config"}}return parseBridgeConfigToml(raw)}var init_bridge_config=__esm({"src/bridge-config.ts"(){"use strict"}});async function resolveStartTicketsRepoName(deps){let fromEnv=deps.env.BAPI_REPO_NAME;if(typeof fromEnv=="string"&&fromEnv.trim().length>0)return fromEnv.trim();try{let result=await readBridgeConfig(deps.cwd,{readFile:deps.readFile});if(result.ok&&result.manifest.repoName)return result.manifest.repoName}catch{}return null}var init_start_tickets_repo=__esm({"src/start-tickets-repo.ts"(){"use strict";init_bridge_config()}});function joinPath(base,rel){let trimmedBase=base.endsWith("/")?base.slice(0,-1):base,trimmedRel=rel.startsWith("/")?rel.slice(1):rel;return`${trimmedBase}/${trimmedRel}`}function hostAdapterForTarget(target){return HOST_ADAPTERS[target.hostKind]}function allHostTargets(){return HOST_PLATFORM_ORDER.map(id=>MCP_HOST_TARGETS[id])}function getProjectJsonTargets(){return allHostTargets().filter(t=>t.scope==="project"&&hostAdapterForTarget(t).format==="json")}var HOST_ADAPTERS,MCP_HOST_TARGETS,HOST_PLATFORM_ORDER,init_mcp_host_targets=__esm({"src/mcp-host-targets.ts"(){"use strict";HOST_ADAPTERS={"claude-code-json":{format:"json",topLevelKey:"mcpServers",transportType:void 0},"cursor-json":{format:"json",topLevelKey:"mcpServers",transportType:"stdio"},"vscode-json":{format:"json",topLevelKey:"servers",transportType:"stdio"},"codex-toml":{format:"toml",topLevelKey:"mcp_servers",transportType:void 0},"copilot-cli":{format:"json",topLevelKey:"mcpServers",transportType:"local",extraEntryKeys:{tools:["*"]}}};MCP_HOST_TARGETS={"claude-code":{id:"claude-code",label:"Claude Code",scope:"project",relPath:".mcp.json",displayPath:".mcp.json",hostKind:"claude-code-json",vendorCli:{bin:"claude",kind:"claude-add-json"},launchAgent:"claude",worktreeSupported:!0,writeStrategy:"vendor-first",detect:()=>!0},cursor:{id:"cursor",label:"Cursor",scope:"project",relPath:".cursor/mcp.json",displayPath:".cursor/mcp.json",hostKind:"cursor-json",launchAgent:"cursor-agent",worktreeSupported:!0,writeStrategy:"direct",detect:ctx=>ctx.exists(joinPath(ctx.cwd,".cursor"))||typeof ctx.env.CURSOR_TRACE_DIR=="string"&&ctx.env.CURSOR_TRACE_DIR.length>0},"copilot-vscode":{id:"copilot-vscode",label:"GitHub Copilot (VS Code)",scope:"project",relPath:".vscode/mcp.json",displayPath:".vscode/mcp.json",hostKind:"vscode-json",worktreeSupported:!1,writeStrategy:"direct",detect:ctx=>ctx.exists(joinPath(ctx.cwd,".vscode"))},"copilot-cli":{id:"copilot-cli",label:"GitHub Copilot CLI",scope:"global",absPathResolver:homedir=>joinPath(homedir,".copilot/mcp-config.json"),displayPath:"~/.copilot/mcp-config.json",hostKind:"copilot-cli",vendorCli:{bin:"copilot",kind:"copilot-add"},worktreeSupported:!1,writeStrategy:"vendor-first",detect:()=>!1},codex:{id:"codex",label:"OpenAI Codex",scope:"global",absPathResolver:homedir=>joinPath(homedir,".codex/config.toml"),displayPath:"~/.codex/config.toml",hostKind:"codex-toml",vendorCli:{bin:"codex",kind:"codex-add"},worktreeSupported:!1,writeStrategy:"vendor-first",detect:ctx=>ctx.exists(joinPath(ctx.homedir,".codex"))},windsurf:{id:"windsurf",label:"Windsurf",scope:"global",absPathResolver:homedir=>joinPath(homedir,".codeium/windsurf/mcp_config.json"),displayPath:"~/.codeium/windsurf/mcp_config.json",hostKind:"claude-code-json",worktreeSupported:!1,writeStrategy:"manual-instructions",detect:ctx=>ctx.exists(joinPath(ctx.cwd,".windsurf"))||ctx.exists(joinPath(ctx.cwd,".windsurfrules"))}},HOST_PLATFORM_ORDER=["claude-code","cursor","copilot-vscode","copilot-cli","codex","windsurf"]}});var LAUNCHER_CONFIG_TARGETS,DUPLICATE_REGISTRATION_GUIDANCE,REASON_TEXT,init_launcher_config_inspection=__esm({"src/launcher-config-inspection.ts"(){"use strict";init_mcp_host_targets();init_mcp_identity();LAUNCHER_CONFIG_TARGETS=getProjectJsonTargets().map(target=>({relPath:target.relPath,topLevelKey:hostAdapterForTarget(target).topLevelKey})),DUPLICATE_REGISTRATION_GUIDANCE=`duplicate Bridge registrations: this config carries both \`${MCP_SERVER_NAME}\` and \`${LEGACY_SERVER_NAMES.join("`, `")}\`. Confirm which registration you want to keep, then remove the unintended entry manually. Bridge will not choose for you.`,REASON_TEXT={"file-unreadable":"the file exists but could not be read","invalid-json":"the file is not valid JSON","root-not-object":"the JSON root is not an object","servers-not-object":"the servers section is not an object","entry-not-object":"the Bridge entry is not an object","no-package-token":`the Bridge launcher has no recognizable ${MCP_PACKAGE_NAME} token`,"version-range":"the launcher pin is a version range, which an automated repin must not rewrite","non-release-specifier":"the launcher pin is a dist-tag or channel, not an exact release","malformed-version":"the launcher pin is not a valid MAJOR.MINOR.PATCH release","pinned-ahead-of-target":"the launcher pin is newer than the target version and must not be downgraded","duplicate-registration":DUPLICATE_REGISTRATION_GUIDANCE}}});var init_mcp_registration_doctor=__esm({"src/mcp-registration-doctor.ts"(){"use strict";init_mcp_host_targets();init_mcp_identity();init_launcher_config_inspection()}});var init_commands_generated=__esm({"src/commands.generated.ts"(){"use strict"}});var init_command_assets_doctor=__esm({"src/command-assets-doctor.ts"(){"use strict";init_commands_generated()}});var init_third_party_mcp_targets=__esm({"src/third-party-mcp-targets.ts"(){"use strict";init_credential_store()}});var init_mcp_provisioning=__esm({"src/mcp-provisioning.ts"(){"use strict";init_bridge_config();init_third_party_mcp_targets();init_mcp_server_invocation();init_mcp_host_targets();init_mcp_identity()}});function claudeMcpShadowingRemediationCommand(registrationKey){return`claude mcp remove ${registrationKey} -s local`}var CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING_ENV,CLAUDE_MCP_SHADOWING_OVERRIDE_WARNING,CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND,init_claude_user_config_doctor=__esm({"src/claude-user-config-doctor.ts"(){"use strict";init_mcp_registration_doctor();init_mcp_provisioning();init_mcp_identity();init_launcher_config_inspection();CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING_ENV="BAPI_CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING",CLAUDE_MCP_SHADOWING_OVERRIDE_WARNING=`Proceeding anyway because ${CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING_ENV} is set`;CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND=claudeMcpShadowingRemediationCommand(MCP_SERVER_NAME)}});var START_TICKETS_DOCTOR_COMMAND,CONDUCTOR_LIVE_SOURCE_PATH_ENV,CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV,GIT_INSTALL_HINTS,REVIEW_TICKETS_GIT_INSTALL_HINTS,RIPGREP_SHELL_FUNCTION_CAVEAT,RIPGREP_INSTALL_HINTS,CREDENTIAL_RESOLUTION_HINT,CLAUDE_MCP_SHADOWING_HINT,LIVE_SOURCE_GUARD_HINT,init_start_tickets_prereqs=__esm({"src/start-tickets-prereqs.ts"(){"use strict";init_credential_store();init_start_tickets_repo();init_mcp_registration_doctor();init_command_assets_doctor();init_mcp_identity();init_claude_user_config_doctor();START_TICKETS_DOCTOR_COMMAND=`npx -y ${MCP_PACKAGE_NAME} doctor`,CONDUCTOR_LIVE_SOURCE_PATH_ENV="BAPI_CONDUCTOR_LIVE_SOURCE_PATH",CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV="BAPI_CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH",GIT_INSTALL_HINTS={darwin:"xcode-select --install (or brew install git)",linux:"Install git with your distro package manager, e.g. apt install git",win32:"Install Git for Windows: https://git-scm.com/download/win"},REVIEW_TICKETS_GIT_INSTALL_HINTS={darwin:`${GIT_INSTALL_HINTS.darwin} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,linux:`${GIT_INSTALL_HINTS.linux} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,win32:`${GIT_INSTALL_HINTS.win32} (or rerun review-tickets with --no-refresh-base to skip the fetch)`},RIPGREP_SHELL_FUNCTION_CAVEAT="a shell function/alias will not satisfy this check \u2014 ripgrep must be installed as a real PATH binary.",RIPGREP_INSTALL_HINTS={darwin:`brew install ripgrep (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,linux:`Install ripgrep with your distro package manager, e.g. apt install ripgrep (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,win32:`winget install BurntSushi.ripgrep.MSVC (${RIPGREP_SHELL_FUNCTION_CAVEAT})`},CREDENTIAL_RESOLUTION_HINT=`Rerun /install-bridge to persist the routing credential, set BAPI_API_KEY in the environment, or add it under "bapi:<repo_name>" in ~/.config/bridge/credentials.json. To migrate a key that only lives in .mcp.json / .cursor/mcp.json, run: npx -y ${MCP_PACKAGE_NAME} credentials migrate-agent-config --write-credentials.`,CLAUDE_MCP_SHADOWING_HINT=`Run \`${CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND}\` to clear the '${MCP_SERVER_NAME}' MCP registration in ~/.claude.json (use the matching scope if it is registered at user scope, or under the projects entry for this repository/worktree). This is ADVISORY: workers load MCP servers with --strict-mcp-config from their own worktree registration, so this entry does not reach a worker and does not block a conductor run.`,LIVE_SOURCE_GUARD_HINT=`Point ${CONDUCTOR_LIVE_SOURCE_PATH_ENV} at your live dev server's checkout ONLY when it differs from this conductor base checkout, or dispatch the conductor from a separate clone. To override the guard and dispatch anyway (not recommended), set ${CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV}=1.`}});import path6 from"path";function pathApiForPlatform(platform){return platform==="win32"?path6.win32:path6.posix}var init_worktree_core=__esm({"src/worktree-core.ts"(){"use strict";init_start_tickets_prereqs()}});var init_platform_escaping=__esm({"src/platform-escaping.ts"(){"use strict"}});var init_prompt=__esm({"src/agent-launchers/prompt.ts"(){"use strict"}});async function resolveCommandOnPath(command,envPath,deps){let pathApi=pathApiForPlatform(deps.platform),probe=deps.platform==="win32"?"where.exe":"which",env={...deps.env,PATH:envPath};deps.platform==="win32"&&(env.Path=envPath);let result=await deps.runCommand(probe,[command],{env});if(result.exitCode!==0)return null;let candidate=result.stdout.split(/\r?\n/).map(line=>line.trim()).find(line=>line.length>0);return!candidate||!pathApi.isAbsolute(candidate)?null:pathApi.normalize(candidate)}var init_claude=__esm({"src/agent-launchers/claude.ts"(){"use strict";init_worktree_core();init_platform_escaping();init_prompt()}});var DEFAULT_PROBE_TIMEOUT_MS,init_types=__esm({"src/agent-capabilities/types.ts"(){"use strict";DEFAULT_PROBE_TIMEOUT_MS=9e4}});import{join as join2}from"node:path";function buildHeadlessArgs(agentName,opts){let fmt=opts.outputFormat??"text";if(agentName==="cursor-agent")return["-p","--output-format",fmt,"--trust","--workspace",opts.cwd,opts.prompt];let args=["-p"];return opts.permissionMode==="acceptEdits"?args.push("--permission-mode","acceptEdits"):opts.skipPermissions===!0&&args.push("--dangerously-skip-permissions"),typeof opts.model=="string"&&opts.model.trim().length>0&&args.push("--model",opts.model),fmt==="json"?args.push("--output-format","json"):fmt==="stream-json"&&args.push("--output-format","stream-json","--verbose"),args.push(opts.prompt),args}async function createProbeContext(deps,agent,defaultTimeoutMs=DEFAULT_PROBE_TIMEOUT_MS){let launcherDeps={platform:deps.platform,env:deps.env,runCommand:(file,args,options)=>deps.runCommand(file,args,options)},resolvedBinary=await resolveCommandOnPath(agent.command,deps.env.PATH??"",launcherDeps),createdDirs=[],counter=0;return{ctx:{agent,deps,resolvedBinary,marker(name){return`${name}_${deps.uniqueSuffix()}_${counter++}`},async makeTempProject(seed){let dir=await deps.mkdtemp(join2(deps.tmpRoot,"agent-cap-"));return createdDirs.push(dir),seed&&await seed(dir),dir},async runHeadless(opts){let exe=resolvedBinary??agent.command,args=buildHeadlessArgs(agent.name,opts),timeoutMs=opts.timeoutMs??defaultTimeoutMs,controller=new AbortController,timedOut=!1,timer=setTimeout(()=>{timedOut=!0,controller.abort()},timeoutMs),start=deps.now();try{let result=await deps.runCommand(exe,args,{cwd:opts.cwd,env:deps.env,signal:controller.signal}),elapsedMs=deps.now()-start;return timedOut?{kind:"hang",elapsedMs,partialStdout:result.stdout??""}:{kind:"exited",exitCode:result.exitCode,stdout:result.stdout??"",stderr:result.stderr??"",elapsedMs}}catch(err){let elapsedMs=deps.now()-start;return timedOut?{kind:"hang",elapsedMs,partialStdout:""}:{kind:"spawn-error",message:err instanceof Error?err.message:String(err)}}finally{clearTimeout(timer)}}},cleanup:async()=>{for(let dir of createdDirs.splice(0))try{await deps.rm(dir,{recursive:!0,force:!0})}catch{}}}}var init_probe_context=__esm({"src/agent-capabilities/probe-context.ts"(){"use strict";init_claude();init_types()}});import{execFile}from"node:child_process";import{mkdtemp,rm,writeFile,mkdir}from"node:fs/promises";import os2 from"node:os";import{randomBytes}from"node:crypto";function createDefaultAgentCapabilitiesDeps(){let runCommand=(file,args,options)=>new Promise(resolve2=>{execFile(file,args,{cwd:options?.cwd,env:options?.env??process.env,signal:options?.signal,killSignal:"SIGKILL",maxBuffer:67108864,encoding:"utf-8"},(error,stdout,stderr)=>{let exitCode=error&&typeof error.code=="number"?error.code:error?1:0;resolve2({stdout:stdout??"",stderr:stderr??"",exitCode})})});return{platform:process.platform,env:process.env,runCommand,tmpRoot:os2.tmpdir(),mkdtemp:prefix=>mkdtemp(prefix),rm:(target,opts)=>rm(target,opts),writeFile:(target,data)=>writeFile(target,data,"utf-8"),mkdir:(target,opts)=>mkdir(target,opts).then(()=>{}),now:()=>Date.now(),uniqueSuffix:()=>randomBytes(3).toString("hex").toUpperCase()}}var init_default_deps=__esm({"src/agent-capabilities/default-deps.ts"(){"use strict"}});import{join as join3}from"node:path";function truncate(text){let flat=text.replace(/\s+/g," ").trim();return flat.length>EVIDENCE_MAX?`${flat.slice(0,EVIDENCE_MAX)}\u2026`:flat}function nonExitedResult(run){return run.kind==="hang"?{status:"hang",detail:`agent did not exit within the timeout (${run.elapsedMs}ms) \u2014 likely the version-sensitive -p hang`,elapsedMs:run.elapsedMs,evidence:run.partialStdout?truncate(run.partialStdout):void 0}:run.kind==="spawn-error"?{status:"fail",detail:`could not spawn agent: ${run.message}`}:null}function denyHookCommand(){return`printf '%s' '${JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"agent-capability deny-enforcement probe fallback: tool call denied."}})}'`}async function seedDenyTargetFile(ctx,dir,marker){await ctx.deps.writeFile(join3(dir,DENY_TARGET_FILE),`${marker}
103
+ `),repoName,sawRepoName=!1,mcp=[],currentMcp=null;for(let i=0;i<lines.length;i++){let lineNo=i+1,line=lines[i].trim();if(line.length===0||line.startsWith("#"))continue;if(line==="[[mcp]]"){currentMcp={headerLine:lineNo},mcp.push(currentMcp);continue}if(line.startsWith("["))return{ok:!1,kind:"parse-error",error:`Unsupported table header on line ${lineNo}; only [[mcp]] is allowed`};let eq=line.indexOf("=");if(eq===-1)return{ok:!1,kind:"parse-error",error:`Malformed line ${lineNo}; expected key = "value"`};let key=line.slice(0,eq).trim(),rawValue=line.slice(eq+1).trim();if(key.length===0)return{ok:!1,kind:"parse-error",error:`Malformed line ${lineNo}; missing key`};if(currentMcp===null){if(key==="repo_name"){if(sawRepoName)return{ok:!1,kind:"parse-error",error:`Duplicate repo_name on line ${lineNo}`};sawRepoName=!0;let stringValue=parseQuotedString(rawValue);if(stringValue===null)return{ok:!1,kind:"parse-error",error:`Expected a double-quoted string for '${key}' on line ${lineNo}`};let validated=validateRepoName(stringValue);if(!validated.ok)return{ok:!1,kind:"validation-error",error:validated.error};repoName=validated.value;continue}return key==="target"?{ok:!1,kind:"parse-error",error:`target on line ${lineNo} must appear inside an [[mcp]] section`}:{ok:!1,kind:"parse-error",error:`Unsupported key '${key}' on line ${lineNo}`}}if(key==="args"){if(currentMcp.args!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate args on line ${lineNo}`};let arr=parseStringArray(rawValue);if(arr===null)return{ok:!1,kind:"parse-error",error:`Expected a string array for 'args' on line ${lineNo}`};currentMcp.args=arr;continue}if(key==="target"||key==="command"||key==="secret_bundle"){let stringValue=parseQuotedString(rawValue);if(stringValue===null)return{ok:!1,kind:"parse-error",error:`Expected a double-quoted string for '${key}' on line ${lineNo}`};if(key==="target"){if(currentMcp.target!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate target on line ${lineNo}`};let validated=validateMcpTarget(stringValue);if(!validated.ok)return{ok:!1,kind:"validation-error",error:validated.error};currentMcp.target=validated.value;continue}if(key==="command"){if(currentMcp.command!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate command on line ${lineNo}`};currentMcp.command=stringValue;continue}if(currentMcp.secretBundle!==void 0)return{ok:!1,kind:"parse-error",error:`Duplicate secret_bundle on line ${lineNo}`};currentMcp.secretBundle=stringValue;continue}return{ok:!1,kind:"parse-error",error:`Unsupported key '${key}' inside [[mcp]] on line ${lineNo}`}}if(!sawRepoName||repoName===void 0)return{ok:!1,kind:"validation-error",error:"Missing required repo_name"};let cleaned=[];for(let entry of mcp){if(entry.target===void 0)return{ok:!1,kind:"validation-error",error:`An [[mcp]] section on line ${entry.headerLine} is missing its target`};if(entry.target!=="bapi"){if(entry.command===void 0||entry.command.trim().length===0)return{ok:!1,kind:"validation-error",error:`[[mcp]] target '${entry.target}' on line ${entry.headerLine} requires a non-empty command`};if(entry.args===void 0)return{ok:!1,kind:"validation-error",error:`[[mcp]] target '${entry.target}' on line ${entry.headerLine} requires an args array`};if(entry.secretBundle===void 0||entry.secretBundle.trim().length===0)return{ok:!1,kind:"validation-error",error:`[[mcp]] target '${entry.target}' on line ${entry.headerLine} requires a non-empty secret_bundle`}}let clean={target:entry.target};entry.command!==void 0&&(clean.command=entry.command),entry.args!==void 0&&(clean.args=entry.args),entry.secretBundle!==void 0&&(clean.secretBundle=entry.secretBundle),cleaned.push(clean)}return{ok:!0,manifest:{repoName,mcp:cleaned}}}function bridgeConfigPath(projectRoot){return path5.join(projectRoot,".bridge","config")}async function readBridgeConfig(projectRoot,deps){let filePath=bridgeConfigPath(projectRoot),raw;try{raw=await deps.readFile(filePath)}catch(err){return err&&typeof err=="object"&&err.code==="ENOENT"?{ok:!1,kind:"missing"}:{ok:!1,kind:"parse-error",error:"Unable to read .bridge/config"}}return parseBridgeConfigToml(raw)}var init_bridge_config=__esm({"src/bridge-config.ts"(){"use strict"}});async function resolveStartTicketsRepoName(deps){let fromEnv=deps.env.BAPI_REPO_NAME;if(typeof fromEnv=="string"&&fromEnv.trim().length>0)return fromEnv.trim();try{let result=await readBridgeConfig(deps.cwd,{readFile:deps.readFile});if(result.ok&&result.manifest.repoName)return result.manifest.repoName}catch{}return null}var init_start_tickets_repo=__esm({"src/start-tickets-repo.ts"(){"use strict";init_bridge_config()}});function joinPath(base,rel){let trimmedBase=base.endsWith("/")?base.slice(0,-1):base,trimmedRel=rel.startsWith("/")?rel.slice(1):rel;return`${trimmedBase}/${trimmedRel}`}function hostAdapterForTarget(target){return HOST_ADAPTERS[target.hostKind]}function allHostTargets(){return HOST_PLATFORM_ORDER.map(id=>MCP_HOST_TARGETS[id])}function getProjectJsonTargets(){return allHostTargets().filter(t=>t.scope==="project"&&hostAdapterForTarget(t).format==="json")}var HOST_ADAPTERS,MCP_HOST_TARGETS,HOST_PLATFORM_ORDER,init_mcp_host_targets=__esm({"src/mcp-host-targets.ts"(){"use strict";HOST_ADAPTERS={"claude-code-json":{format:"json",topLevelKey:"mcpServers",transportType:void 0},"cursor-json":{format:"json",topLevelKey:"mcpServers",transportType:"stdio"},"vscode-json":{format:"json",topLevelKey:"servers",transportType:"stdio"},"codex-toml":{format:"toml",topLevelKey:"mcp_servers",transportType:void 0},"copilot-cli":{format:"json",topLevelKey:"mcpServers",transportType:"local",extraEntryKeys:{tools:["*"]}}};MCP_HOST_TARGETS={"claude-code":{id:"claude-code",label:"Claude Code",scope:"project",relPath:".mcp.json",displayPath:".mcp.json",hostKind:"claude-code-json",vendorCli:{bin:"claude",kind:"claude-add-json"},launchAgent:"claude",worktreeSupported:!0,writeStrategy:"vendor-first",detect:()=>!0},cursor:{id:"cursor",label:"Cursor",scope:"project",relPath:".cursor/mcp.json",displayPath:".cursor/mcp.json",hostKind:"cursor-json",launchAgent:"cursor-agent",worktreeSupported:!0,writeStrategy:"direct",detect:ctx=>ctx.exists(joinPath(ctx.cwd,".cursor"))||typeof ctx.env.CURSOR_TRACE_DIR=="string"&&ctx.env.CURSOR_TRACE_DIR.length>0},"copilot-vscode":{id:"copilot-vscode",label:"GitHub Copilot (VS Code)",scope:"project",relPath:".vscode/mcp.json",displayPath:".vscode/mcp.json",hostKind:"vscode-json",worktreeSupported:!1,writeStrategy:"direct",detect:ctx=>ctx.exists(joinPath(ctx.cwd,".vscode"))},"copilot-cli":{id:"copilot-cli",label:"GitHub Copilot CLI",scope:"global",absPathResolver:homedir=>joinPath(homedir,".copilot/mcp-config.json"),displayPath:"~/.copilot/mcp-config.json",hostKind:"copilot-cli",vendorCli:{bin:"copilot",kind:"copilot-add"},worktreeSupported:!1,writeStrategy:"vendor-first",detect:()=>!1},codex:{id:"codex",label:"OpenAI Codex",scope:"global",absPathResolver:homedir=>joinPath(homedir,".codex/config.toml"),displayPath:"~/.codex/config.toml",hostKind:"codex-toml",vendorCli:{bin:"codex",kind:"codex-add"},worktreeSupported:!1,writeStrategy:"vendor-first",detect:ctx=>ctx.exists(joinPath(ctx.homedir,".codex"))},windsurf:{id:"windsurf",label:"Windsurf",scope:"global",absPathResolver:homedir=>joinPath(homedir,".codeium/windsurf/mcp_config.json"),displayPath:"~/.codeium/windsurf/mcp_config.json",hostKind:"claude-code-json",worktreeSupported:!1,writeStrategy:"manual-instructions",detect:ctx=>ctx.exists(joinPath(ctx.cwd,".windsurf"))||ctx.exists(joinPath(ctx.cwd,".windsurfrules"))}},HOST_PLATFORM_ORDER=["claude-code","cursor","copilot-vscode","copilot-cli","codex","windsurf"]}});var LAUNCHER_CONFIG_TARGETS,DUPLICATE_REGISTRATION_GUIDANCE,REASON_TEXT,init_launcher_config_inspection=__esm({"src/launcher-config-inspection.ts"(){"use strict";init_mcp_host_targets();init_mcp_identity();LAUNCHER_CONFIG_TARGETS=getProjectJsonTargets().map(target=>({relPath:target.relPath,topLevelKey:hostAdapterForTarget(target).topLevelKey})),DUPLICATE_REGISTRATION_GUIDANCE=`duplicate Bridge registrations: this config carries both \`${MCP_SERVER_NAME}\` and \`${LEGACY_SERVER_NAMES.join("`, `")}\`. Confirm which registration you want to keep, then remove the unintended entry manually. Bridge will not choose for you.`,REASON_TEXT={"file-unreadable":"the file exists but could not be read","invalid-json":"the file is not valid JSON","root-not-object":"the JSON root is not an object","servers-not-object":"the servers section is not an object","entry-not-object":"the Bridge entry is not an object","no-package-token":`the Bridge launcher has no recognizable ${MCP_PACKAGE_NAME} token`,"version-range":"the launcher pin is a version range, which an automated repin must not rewrite","non-release-specifier":"the launcher pin is a dist-tag or channel, not an exact release","malformed-version":"the launcher pin is not a valid MAJOR.MINOR.PATCH release","pinned-ahead-of-target":"the launcher pin is newer than the target version and must not be downgraded","duplicate-registration":DUPLICATE_REGISTRATION_GUIDANCE}}});var init_mcp_registration_doctor=__esm({"src/mcp-registration-doctor.ts"(){"use strict";init_mcp_host_targets();init_mcp_identity();init_launcher_config_inspection()}});var init_commands_generated=__esm({"src/commands.generated.ts"(){"use strict"}});var init_command_assets_doctor=__esm({"src/command-assets-doctor.ts"(){"use strict";init_commands_generated()}});var CLAUDE_REVIEW_WORKFLOW_RELPATH,CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION,init_claude_review_workflow_drift=__esm({"src/claude-review-workflow-drift.ts"(){"use strict";CLAUDE_REVIEW_WORKFLOW_RELPATH=".github/workflows/claude-review.yml",CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION=`Reconcile the base branch with the repository default branch \u2014 merge the default branch in, or sync ${CLAUDE_REVIEW_WORKFLOW_RELPATH} together with tests/pytest/commands/test_claude_review_workflow_prompt.py. See docs/claude/runbooks/claude-review-base-branch-drift.md.`}});var init_claude_review_workflow_drift_probe=__esm({"src/claude-review-workflow-drift-probe.ts"(){"use strict";init_claude_review_workflow_drift()}});var init_third_party_mcp_targets=__esm({"src/third-party-mcp-targets.ts"(){"use strict";init_credential_store()}});var init_mcp_provisioning=__esm({"src/mcp-provisioning.ts"(){"use strict";init_bridge_config();init_third_party_mcp_targets();init_mcp_server_invocation();init_mcp_host_targets();init_mcp_identity()}});function claudeMcpShadowingRemediationCommand(registrationKey){return`claude mcp remove ${registrationKey} -s local`}var CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING_ENV,CLAUDE_MCP_SHADOWING_OVERRIDE_WARNING,CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND,init_claude_user_config_doctor=__esm({"src/claude-user-config-doctor.ts"(){"use strict";init_mcp_registration_doctor();init_mcp_provisioning();init_mcp_identity();init_launcher_config_inspection();CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING_ENV="BAPI_CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING",CLAUDE_MCP_SHADOWING_OVERRIDE_WARNING=`Proceeding anyway because ${CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING_ENV} is set`;CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND=claudeMcpShadowingRemediationCommand(MCP_SERVER_NAME)}});var START_TICKETS_DOCTOR_COMMAND,CONDUCTOR_LIVE_SOURCE_PATH_ENV,CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV,GIT_INSTALL_HINTS,REVIEW_TICKETS_GIT_INSTALL_HINTS,RIPGREP_SHELL_FUNCTION_CAVEAT,RIPGREP_INSTALL_HINTS,CREDENTIAL_RESOLUTION_HINT,CLAUDE_MCP_SHADOWING_HINT,LIVE_SOURCE_GUARD_HINT,init_start_tickets_prereqs=__esm({"src/start-tickets-prereqs.ts"(){"use strict";init_credential_store();init_start_tickets_repo();init_mcp_registration_doctor();init_command_assets_doctor();init_mcp_identity();init_claude_review_workflow_drift();init_claude_review_workflow_drift_probe();init_claude_user_config_doctor();START_TICKETS_DOCTOR_COMMAND=`npx -y ${MCP_PACKAGE_NAME} doctor`,CONDUCTOR_LIVE_SOURCE_PATH_ENV="BAPI_CONDUCTOR_LIVE_SOURCE_PATH",CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV="BAPI_CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH",GIT_INSTALL_HINTS={darwin:"xcode-select --install (or brew install git)",linux:"Install git with your distro package manager, e.g. apt install git",win32:"Install Git for Windows: https://git-scm.com/download/win"},REVIEW_TICKETS_GIT_INSTALL_HINTS={darwin:`${GIT_INSTALL_HINTS.darwin} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,linux:`${GIT_INSTALL_HINTS.linux} (or rerun review-tickets with --no-refresh-base to skip the fetch)`,win32:`${GIT_INSTALL_HINTS.win32} (or rerun review-tickets with --no-refresh-base to skip the fetch)`},RIPGREP_SHELL_FUNCTION_CAVEAT="a shell function/alias will not satisfy this check \u2014 ripgrep must be installed as a real PATH binary.",RIPGREP_INSTALL_HINTS={darwin:`brew install ripgrep (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,linux:`Install ripgrep with your distro package manager, e.g. apt install ripgrep (${RIPGREP_SHELL_FUNCTION_CAVEAT})`,win32:`winget install BurntSushi.ripgrep.MSVC (${RIPGREP_SHELL_FUNCTION_CAVEAT})`},CREDENTIAL_RESOLUTION_HINT=`Rerun /install-bridge to persist the routing credential, set BAPI_API_KEY in the environment, or add it under "bapi:<repo_name>" in ~/.config/bridge/credentials.json. To migrate a key that only lives in .mcp.json / .cursor/mcp.json, run: npx -y ${MCP_PACKAGE_NAME} credentials migrate-agent-config --write-credentials.`,CLAUDE_MCP_SHADOWING_HINT=`Run \`${CLAUDE_MCP_SHADOWING_REMEDIATION_COMMAND}\` to clear the '${MCP_SERVER_NAME}' MCP registration in ~/.claude.json (use the matching scope if it is registered at user scope, or under the projects entry for this repository/worktree). This is ADVISORY: workers load MCP servers with --strict-mcp-config from their own worktree registration, so this entry does not reach a worker and does not block a conductor run.`,LIVE_SOURCE_GUARD_HINT=`Point ${CONDUCTOR_LIVE_SOURCE_PATH_ENV} at your live dev server's checkout ONLY when it differs from this conductor base checkout, or dispatch the conductor from a separate clone. To override the guard and dispatch anyway (not recommended), set ${CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV}=1.`}});import path6 from"path";function pathApiForPlatform(platform){return platform==="win32"?path6.win32:path6.posix}var init_worktree_core=__esm({"src/worktree-core.ts"(){"use strict";init_start_tickets_prereqs()}});var init_platform_escaping=__esm({"src/platform-escaping.ts"(){"use strict"}});var init_prompt=__esm({"src/agent-launchers/prompt.ts"(){"use strict"}});async function resolveCommandOnPath(command,envPath,deps){let pathApi=pathApiForPlatform(deps.platform),probe=deps.platform==="win32"?"where.exe":"which",env={...deps.env,PATH:envPath};deps.platform==="win32"&&(env.Path=envPath);let result=await deps.runCommand(probe,[command],{env});if(result.exitCode!==0)return null;let candidate=result.stdout.split(/\r?\n/).map(line=>line.trim()).find(line=>line.length>0);return!candidate||!pathApi.isAbsolute(candidate)?null:pathApi.normalize(candidate)}var init_claude=__esm({"src/agent-launchers/claude.ts"(){"use strict";init_worktree_core();init_platform_escaping();init_prompt()}});var DEFAULT_PROBE_TIMEOUT_MS,init_types=__esm({"src/agent-capabilities/types.ts"(){"use strict";DEFAULT_PROBE_TIMEOUT_MS=9e4}});import{join as join2}from"node:path";function buildHeadlessArgs(agentName,opts){let fmt=opts.outputFormat??"text";if(agentName==="cursor-agent")return["-p","--output-format",fmt,"--trust","--workspace",opts.cwd,opts.prompt];let args=["-p"];return opts.permissionMode==="acceptEdits"?args.push("--permission-mode","acceptEdits"):opts.skipPermissions===!0&&args.push("--dangerously-skip-permissions"),typeof opts.model=="string"&&opts.model.trim().length>0&&args.push("--model",opts.model),fmt==="json"?args.push("--output-format","json"):fmt==="stream-json"&&args.push("--output-format","stream-json","--verbose"),args.push(opts.prompt),args}async function createProbeContext(deps,agent,defaultTimeoutMs=DEFAULT_PROBE_TIMEOUT_MS){let launcherDeps={platform:deps.platform,env:deps.env,runCommand:(file,args,options)=>deps.runCommand(file,args,options)},resolvedBinary=await resolveCommandOnPath(agent.command,deps.env.PATH??"",launcherDeps),createdDirs=[],counter=0;return{ctx:{agent,deps,resolvedBinary,marker(name){return`${name}_${deps.uniqueSuffix()}_${counter++}`},async makeTempProject(seed){let dir=await deps.mkdtemp(join2(deps.tmpRoot,"agent-cap-"));return createdDirs.push(dir),seed&&await seed(dir),dir},async runHeadless(opts){let exe=resolvedBinary??agent.command,args=buildHeadlessArgs(agent.name,opts),timeoutMs=opts.timeoutMs??defaultTimeoutMs,controller=new AbortController,timedOut=!1,timer=setTimeout(()=>{timedOut=!0,controller.abort()},timeoutMs),start=deps.now();try{let result=await deps.runCommand(exe,args,{cwd:opts.cwd,env:deps.env,signal:controller.signal}),elapsedMs=deps.now()-start;return timedOut?{kind:"hang",elapsedMs,partialStdout:result.stdout??""}:{kind:"exited",exitCode:result.exitCode,stdout:result.stdout??"",stderr:result.stderr??"",elapsedMs}}catch(err){let elapsedMs=deps.now()-start;return timedOut?{kind:"hang",elapsedMs,partialStdout:""}:{kind:"spawn-error",message:err instanceof Error?err.message:String(err)}}finally{clearTimeout(timer)}}},cleanup:async()=>{for(let dir of createdDirs.splice(0))try{await deps.rm(dir,{recursive:!0,force:!0})}catch{}}}}var init_probe_context=__esm({"src/agent-capabilities/probe-context.ts"(){"use strict";init_claude();init_types()}});import{execFile}from"node:child_process";import{mkdtemp,rm,writeFile,mkdir}from"node:fs/promises";import os2 from"node:os";import{randomBytes}from"node:crypto";function createDefaultAgentCapabilitiesDeps(){let runCommand=(file,args,options)=>new Promise(resolve2=>{execFile(file,args,{cwd:options?.cwd,env:options?.env??process.env,signal:options?.signal,killSignal:"SIGKILL",maxBuffer:67108864,encoding:"utf-8"},(error,stdout,stderr)=>{let exitCode=error&&typeof error.code=="number"?error.code:error?1:0;resolve2({stdout:stdout??"",stderr:stderr??"",exitCode})})});return{platform:process.platform,env:process.env,runCommand,tmpRoot:os2.tmpdir(),mkdtemp:prefix=>mkdtemp(prefix),rm:(target,opts)=>rm(target,opts),writeFile:(target,data)=>writeFile(target,data,"utf-8"),mkdir:(target,opts)=>mkdir(target,opts).then(()=>{}),now:()=>Date.now(),uniqueSuffix:()=>randomBytes(3).toString("hex").toUpperCase()}}var init_default_deps=__esm({"src/agent-capabilities/default-deps.ts"(){"use strict"}});import{join as join3}from"node:path";function truncate(text){let flat=text.replace(/\s+/g," ").trim();return flat.length>EVIDENCE_MAX?`${flat.slice(0,EVIDENCE_MAX)}\u2026`:flat}function nonExitedResult(run){return run.kind==="hang"?{status:"hang",detail:`agent did not exit within the timeout (${run.elapsedMs}ms) \u2014 likely the version-sensitive -p hang`,elapsedMs:run.elapsedMs,evidence:run.partialStdout?truncate(run.partialStdout):void 0}:run.kind==="spawn-error"?{status:"fail",detail:`could not spawn agent: ${run.message}`}:null}function denyHookCommand(){return`printf '%s' '${JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"agent-capability deny-enforcement probe fallback: tool call denied."}})}'`}async function seedDenyTargetFile(ctx,dir,marker){await ctx.deps.writeFile(join3(dir,DENY_TARGET_FILE),`${marker}
104
104
  `)}async function seedDenySettings(ctx,dir,marker){await seedDenyTargetFile(ctx,dir,marker);let claudeDir=join3(dir,".claude");await ctx.deps.mkdir(claudeDir,{recursive:!0});let settings={permissions:{deny:[DENY_BASH_RULE]}};await ctx.deps.writeFile(join3(claudeDir,"settings.local.json"),`${JSON.stringify(settings,null,2)}
105
105
  `)}async function seedDenyHook(ctx,dir,marker){await seedDenyTargetFile(ctx,dir,marker);let claudeDir=join3(dir,".claude");await ctx.deps.mkdir(claudeDir,{recursive:!0});let settings={hooks:{PreToolUse:[{matcher:DENY_HOOK_MATCHER,hooks:[{type:"command",command:denyHookCommand()}]}]}};await ctx.deps.writeFile(join3(claudeDir,"settings.local.json"),`${JSON.stringify(settings,null,2)}
106
106
  `)}function denyProbePrompt(){return["This is an agent-capability probe of permission enforcement.",`Use the Bash tool to run exactly this command: cat ${DENY_TARGET_FILE}`,"Then output the command's exact stdout on its own line.","Use ONLY the Bash tool for this \u2014 do not use Read, Grep, or any other tool, and do not open the file yourself.","If the Bash command is blocked, denied, or errors, output the single word DENIED and nothing else.","Do not guess, fabricate, or infer the file contents \u2014 only report what the command actually returned."].join(`
@@ -1,7 +1,7 @@
1
1
  // ---------------------------------------------------------------------------
2
- // brainstorm-files
2
+ // council-files
3
3
  // ---------------------------------------------------------------------------
4
- // Pure, side-effect-free helpers for naming and writing brainstorm result
4
+ // Pure, side-effect-free helpers for naming and writing Council result
5
5
  // files. Extracted from ``index.ts`` so they can be unit-tested directly —
6
6
  // ``index.ts`` self-executes its CLI/server at module top level and therefore
7
7
  // cannot be imported from a test. ``index.ts`` imports these as the single
@@ -34,47 +34,47 @@ export function sanitizeProviderForFilename(provider) {
34
34
  return cleaned || "provider";
35
35
  }
36
36
  /**
37
- * Build the on-disk filename for one brainstorm result row.
37
+ * Build the on-disk filename for one Council result row.
38
38
  *
39
39
  * When the original task ``subject`` is available and slugifies to a non-empty
40
40
  * string, use a human-readable semantic name:
41
- * ``{slugified-subject}-{short_brainstorm_id}-{provider}.md``
42
- * where ``short_brainstorm_id`` is the first 8 chars of the brainstorm UUID
41
+ * ``{slugified-subject}-{short_council_id}-{provider}.md``
42
+ * where ``short_council_id`` is the first 8 chars of the Council UUID
43
43
  * (enough for practical uniqueness / idempotent overwrite on re-runs).
44
44
  *
45
- * The UUID-only pattern ``{brainstorm_id}-{provider}.md`` is the intentional
45
+ * The UUID-only pattern ``{council_id}-{provider}.md`` is the intentional
46
46
  * backward-compatible fallback, used only when no subject is provided, the
47
47
  * subject is whitespace-only, or ``slugify(subject)`` returns an empty string
48
48
  * (e.g. punctuation-only). This keeps callers without a subject — notably the
49
- * ``get_brainstorm`` retrieval path, whose result envelope does not echo
49
+ * ``get_council`` retrieval path, whose result envelope does not echo
50
50
  * ``task_description`` — safe and unchanged.
51
51
  */
52
- export function buildBrainstormResultFilename(envelope, row, subject) {
52
+ export function buildCouncilResultFilename(envelope, row, subject) {
53
53
  const providerSegment = sanitizeProviderForFilename(row.provider);
54
54
  const subjectSlug = subject ? slugify(subject) : "";
55
- const shortId = envelope.brainstorm_id.slice(0, 8);
55
+ const shortId = envelope.council_id.slice(0, 8);
56
56
  if (subjectSlug) {
57
57
  return `${subjectSlug}-${shortId}-${providerSegment}.md`;
58
58
  }
59
- return `${envelope.brainstorm_id}-${providerSegment}.md`;
59
+ return `${envelope.council_id}-${providerSegment}.md`;
60
60
  }
61
61
  /**
62
- * Write each brainstorm result row's markdown into ``dir`` using
63
- * ``buildBrainstormResultFilename`` for the name. ``index.ts`` wraps this with
64
- * the resolved ``BAPI_DOCS_DIR/brainstorm`` directory.
62
+ * Write each Council result row's markdown into ``dir`` using
63
+ * ``buildCouncilResultFilename`` for the name. ``index.ts`` wraps this with
64
+ * the resolved ``BAPI_DOCS_DIR/council`` directory.
65
65
  *
66
66
  * Behavior is fail-open: rows without markdown are skipped, the directory is
67
67
  * created recursively, markdown is written as UTF-8, and per-row write failures
68
68
  * are swallowed so saving never blocks the tool response.
69
69
  */
70
- export async function saveBrainstormResultsToDir(envelope, dir, subject) {
70
+ export async function saveCouncilResultsToDir(envelope, dir, subject) {
71
71
  const savedPaths = [];
72
72
  for (const row of envelope.results) {
73
73
  const markdown = row.markdown;
74
74
  if (!markdown) {
75
75
  continue;
76
76
  }
77
- const filename = buildBrainstormResultFilename(envelope, row, subject);
77
+ const filename = buildCouncilResultFilename(envelope, row, subject);
78
78
  const filePath = path.join(dir, filename);
79
79
  try {
80
80
  await mkdir(dir, { recursive: true });
@@ -155,7 +155,7 @@ export const SystemGoalsSchema = z.object({
155
155
  });
156
156
  // Read-only recommended implementation order for epic-planning surfaces. Hard
157
157
  // prerequisites (`depends_on`) are modelled separately from soft sequencing
158
- // (`recommended_after`) per the brainstorm; neither becomes a Jira link in this
158
+ // (`recommended_after`) per the agreed design; neither becomes a Jira link in this
159
159
  // pass — order is delivered into the epic (comment/description) downstream.
160
160
  export const ImplementationOrderItemSchema = z.object({
161
161
  title: z.string().min(1).describe("Short title of the slice / child ticket."),