@bridge_gpt/mcp-server 0.2.52 → 0.2.53

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.
@@ -13,10 +13,16 @@
13
13
  * condition it guards is already reported loudly by the server at dispatch time.
14
14
  */
15
15
  import path from "path";
16
+ import { planeCheckFinding, planeCheckPassed } from "./types.js";
16
17
  /** Remediation embedded in a confirmed-mismatch diagnostic. */
17
18
  export const PLANE_ALEMBIC_REMEDIATION = "alembic -c alembic.ini upgrade head";
18
19
  /** Fixed prefix of the fail-open warning, so tests and greps can match it. */
19
20
  export const PLANE_ALEMBIC_UNVERIFIED_PREFIX = "could not verify database migration head";
21
+ /** Structured fix for a confirmed migration mismatch. */
22
+ export const PLANE_ALEMBIC_MISMATCH_REMEDIATION = `run \`${PLANE_ALEMBIC_REMEDIATION}\` to bring the database up to the migration head, then retry.`;
23
+ /** Structured fix for an unverifiable migration head. Never blocking. */
24
+ export const PLANE_ALEMBIC_UNVERIFIED_REMEDIATION = "verify by hand with `alembic -c alembic.ini heads` and `alembic -c alembic.ini current`; " +
25
+ "startup continues, and the server reports CONTRACT_MIGRATION_BEHIND at dispatch if the database is behind.";
20
26
  /**
21
27
  * Path to the repository virtualenv's Alembic executable.
22
28
  *
@@ -72,39 +78,62 @@ export function parseAlembicRevisions(output) {
72
78
  export async function checkAlembicHead(repoRoot, deps) {
73
79
  const alembic = resolveRepositoryAlembicCommand(repoRoot, deps.platform);
74
80
  if (!(await deps.fileExists(alembic))) {
75
- return unverified("the repository virtualenv Alembic executable was not found");
81
+ return unverified("the repository virtualenv Alembic executable was not found", "the repository virtualenv Alembic executable was not found");
76
82
  }
77
83
  const heads = await deps.execFile(alembic, ["-c", "alembic.ini", "heads"], { cwd: repoRoot });
78
84
  if (!heads.ok) {
79
- return unverified(`\`alembic heads\` did not complete (${heads.error})`);
85
+ // The interpolated `error` stays in the legacy message for the operator at
86
+ // the terminal; the consolidated detail is fixed prose. In production that
87
+ // field is an errno code, but this outcome flows into a wider report and
88
+ // must not depend on every caller keeping it that way.
89
+ return unverified(`\`alembic heads\` did not complete (${heads.error})`, "`alembic heads` did not complete");
80
90
  }
81
91
  const current = await deps.execFile(alembic, ["-c", "alembic.ini", "current"], { cwd: repoRoot });
82
92
  if (!current.ok) {
83
- return unverified(`\`alembic current\` did not complete (${current.error})`);
93
+ return unverified(`\`alembic current\` did not complete (${current.error})`, "`alembic current` did not complete");
84
94
  }
85
95
  const headRevisions = parseAlembicRevisions(heads.stdout);
86
96
  const currentRevisions = parseAlembicRevisions(current.stdout);
87
97
  if (headRevisions === null || currentRevisions === null) {
88
- return unverified("Alembic output did not contain a readable revision");
98
+ return unverified("Alembic output did not contain a readable revision", "Alembic output did not contain a readable revision");
89
99
  }
90
100
  const matches = headRevisions.length === currentRevisions.length &&
91
101
  headRevisions.every((rev, i) => rev === currentRevisions[i]);
92
- if (matches)
93
- return null;
94
- return {
102
+ if (matches) {
103
+ return planeCheckPassed("alembic-head", ALEMBIC_CHECK_LABEL, "the database is at the repository migration head");
104
+ }
105
+ return planeCheckFinding({
95
106
  check: "alembic-head",
96
107
  severity: "blocking",
97
108
  message: `the database is not at the migration head — heads [${headRevisions.join(", ")}], ` +
98
109
  `current [${currentRevisions.join(", ")}]. A database behind the head blocks ` +
99
110
  `conductor dispatch. Run \`${PLANE_ALEMBIC_REMEDIATION}\``,
100
- };
111
+ remediation: PLANE_ALEMBIC_MISMATCH_REMEDIATION,
112
+ }, ALEMBIC_CHECK_LABEL,
113
+ // Revision ids are repository-authored slugs, not secrets, and they are the
114
+ // one fact that makes this finding actionable.
115
+ `database at [${currentRevisions.join(", ")}], repository head [${headRevisions.join(", ")}]`);
101
116
  }
102
- function unverified(detail) {
103
- return {
117
+ /** Human-readable name for the migration-head prerequisite. */
118
+ const ALEMBIC_CHECK_LABEL = "Database migration head";
119
+ /**
120
+ * The fail-open branch: warning severity, and never blocking.
121
+ *
122
+ * TWO details, deliberately. `messageDetail` is the legacy operator-facing prose
123
+ * and may name the command's own `error` field; `outcomeDetail` is fixed prose
124
+ * only, because the outcome flows into the consolidated readiness report. The
125
+ * production `execFile` adapter puts an errno code in `error`, but this
126
+ * separation means the consolidated report stays safe even if some caller ever
127
+ * puts raw stderr, an interpreter path, or a thrown message there.
128
+ */
129
+ function unverified(messageDetail, outcomeDetail) {
130
+ const diagnostic = {
104
131
  check: "alembic-head",
105
132
  severity: "warning",
106
- message: `${PLANE_ALEMBIC_UNVERIFIED_PREFIX}: ${detail}. Startup continues — verify with ` +
133
+ message: `${PLANE_ALEMBIC_UNVERIFIED_PREFIX}: ${messageDetail}. Startup continues — verify with ` +
107
134
  `\`alembic -c alembic.ini heads\` and \`alembic -c alembic.ini current\` if a run ` +
108
135
  "later reports CONTRACT_MIGRATION_BEHIND.",
136
+ remediation: PLANE_ALEMBIC_UNVERIFIED_REMEDIATION,
109
137
  };
138
+ return planeCheckFinding(diagnostic, ALEMBIC_CHECK_LABEL, `${PLANE_ALEMBIC_UNVERIFIED_PREFIX}: ${outcomeDetail}`);
110
139
  }
@@ -13,6 +13,7 @@
13
13
  * can be older than a rebuild that overwrote files in place.
14
14
  */
15
15
  import path from "path";
16
+ import { planeCheckFinding, planeCheckPassed } from "./types.js";
16
17
  /** Remediation text embedded in every blocking build diagnostic. */
17
18
  export const PLANE_BUILD_REMEDIATION = "cd mcp_server && npm run build";
18
19
  /**
@@ -93,42 +94,49 @@ export async function findBuildMtime(executorEntrypoint, deps) {
93
94
  * stale or absent build is exactly the failure this check exists to prevent, so
94
95
  * unlike the Alembic check there is no fail-open branch.
95
96
  */
97
+ export const PLANE_BUILD_FIX_REMEDIATION = "run `cd mcp_server && npm run build` to rebuild the executor entrypoint, then retry.";
96
98
  export async function checkPlaneBuildFreshness(repoRoot, deps) {
99
+ const label = "Executor build freshness";
97
100
  const packageRoot = path.join(repoRoot, "mcp_server");
98
101
  const buildDir = path.join(packageRoot, "build");
99
102
  const executorEntrypoint = path.join(buildDir, "index.js");
100
103
  const srcDir = path.join(packageRoot, "src");
101
104
  const buildMtime = await findBuildMtime(executorEntrypoint, deps);
102
105
  if (!buildMtime.ok) {
103
- return {
106
+ return planeCheckFinding({
104
107
  check: "executor-build",
105
108
  severity: "blocking",
106
109
  message: `the executor build entrypoint is missing or unreadable at ` +
107
110
  `mcp_server/build/index.js — run \`${PLANE_BUILD_REMEDIATION}\``,
108
- };
111
+ remediation: PLANE_BUILD_FIX_REMEDIATION,
112
+ }, label, "the executor build entrypoint is missing or unreadable");
109
113
  }
110
114
  const sourceMtime = await findNewestSourceMtime(srcDir, deps);
111
115
  if (!sourceMtime.ok) {
112
- return {
116
+ return planeCheckFinding({
113
117
  check: "executor-build",
114
118
  severity: "blocking",
115
119
  message: `could not read mcp_server/src to compare against the build ` +
116
120
  `(${sourceMtime.error}) — run \`${PLANE_BUILD_REMEDIATION}\``,
117
- };
121
+ remediation: PLANE_BUILD_FIX_REMEDIATION,
122
+ }, label,
123
+ // `sourceMtime.error` is an errno code, already sanitized by `sanitize()`.
124
+ `the executor sources could not be read for comparison (${sourceMtime.error})`);
118
125
  }
119
126
  // Equality passes: a build written in the same millisecond as its newest
120
127
  // source is current, and treating the boundary as stale would refuse a
121
128
  // perfectly good build on a coarse-resolution filesystem.
122
129
  if (sourceMtime.mtimeMs !== null && sourceMtime.mtimeMs > buildMtime.mtimeMs) {
123
- return {
130
+ return planeCheckFinding({
124
131
  check: "executor-build",
125
132
  severity: "blocking",
126
133
  message: "mcp_server/build/ is STALE — a source file under mcp_server/src is newer " +
127
134
  `than mcp_server/build/index.js, so executors would run old code. ` +
128
135
  `Run \`${PLANE_BUILD_REMEDIATION}\``,
129
- };
136
+ remediation: PLANE_BUILD_FIX_REMEDIATION,
137
+ }, label, "mcp_server/build/ is stale — a source file is newer than the build entrypoint");
130
138
  }
131
- return null;
139
+ return planeCheckPassed("executor-build", label, "the executor build is present and current");
132
140
  }
133
141
  /**
134
142
  * Fixed refusal prose for an unresolvable runtime re-exec target (BAPI-768).
@@ -153,13 +161,16 @@ export const PLANE_RUNTIME_ENTRYPOINT_REFUSAL = "the currently executing MCP bui
153
161
  * degraded plane, it is no plane at all.
154
162
  */
155
163
  export function checkPlaneRuntimeEntrypoint(resolution) {
156
- if (resolution.ok)
157
- return null;
158
- return {
164
+ const label = "Runtime re-exec entrypoint";
165
+ if (resolution.ok) {
166
+ return planeCheckPassed("runtime-entrypoint", label, "the executing build can re-enter itself");
167
+ }
168
+ return planeCheckFinding({
159
169
  check: "runtime-entrypoint",
160
170
  severity: "blocking",
161
171
  message: PLANE_RUNTIME_ENTRYPOINT_REFUSAL,
162
- };
172
+ remediation: PLANE_BUILD_FIX_REMEDIATION,
173
+ }, label, "the currently executing MCP build could not be re-entered");
163
174
  }
164
175
  function sanitize(err) {
165
176
  const code = err?.code;