@stackwright-pro/mcp 0.2.0-alpha.102 → 0.2.0-alpha.104

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/dist/server.mjs CHANGED
@@ -556,7 +556,7 @@ function registerDashboardTools(server2) {
556
556
  yamlLines.push(` header: Status`);
557
557
  yamlLines.push(` type: badge`);
558
558
  }
559
- const yaml2 = yamlLines.join("\n");
559
+ const yaml3 = yamlLines.join("\n");
560
560
  return {
561
561
  content: [
562
562
  {
@@ -566,7 +566,7 @@ function registerDashboardTools(server2) {
566
566
  Layout: ${layout}
567
567
 
568
568
  \`\`\`yaml
569
- ${yaml2}
569
+ ${yaml3}
570
570
  \`\`\`
571
571
 
572
572
  \u{1F4A1} Add this to pages/dashboard/content.yml and validate with stackwright_validate_pages.`
@@ -647,7 +647,7 @@ ${yaml2}
647
647
  yamlLines.push(" action: navigate");
648
648
  yamlLines.push(` href: "/${entity}"`);
649
649
  yamlLines.push(" style: secondary");
650
- const yaml2 = yamlLines.join("\n");
650
+ const yaml3 = yamlLines.join("\n");
651
651
  return {
652
652
  content: [
653
653
  {
@@ -655,7 +655,7 @@ ${yaml2}
655
655
  text: `\u{1F4C4} Generated Detail Page for: ${entity}
656
656
 
657
657
  \`\`\`yaml
658
- ${yaml2}
658
+ ${yaml3}
659
659
  \`\`\`
660
660
 
661
661
  \u{1F4A1} Add this to pages/${entity}/[${slugField}]/content.yml and validate.`
@@ -2455,7 +2455,7 @@ function enrichErrors(issues, synonyms) {
2455
2455
  });
2456
2456
  }
2457
2457
  function handleValidateYamlFragment(input) {
2458
- const { schemaName, yaml: yaml2 } = input;
2458
+ const { schemaName, yaml: yaml3 } = input;
2459
2459
  if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
2460
2460
  return {
2461
2461
  valid: false,
@@ -2465,7 +2465,7 @@ function handleValidateYamlFragment(input) {
2465
2465
  const name = schemaName;
2466
2466
  let parsed;
2467
2467
  try {
2468
- parsed = yamlLoad(yaml2);
2468
+ parsed = yamlLoad(yaml3);
2469
2469
  } catch (err) {
2470
2470
  return {
2471
2471
  valid: false,
@@ -2491,8 +2491,8 @@ function registerValidateYamlFragmentTool(server2) {
2491
2491
  ),
2492
2492
  yaml: z11.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
2493
2493
  },
2494
- async ({ schemaName, yaml: yaml2 }) => {
2495
- const result = handleValidateYamlFragment({ schemaName, yaml: yaml2 });
2494
+ async ({ schemaName, yaml: yaml3 }) => {
2495
+ const result = handleValidateYamlFragment({ schemaName, yaml: yaml3 });
2496
2496
  return {
2497
2497
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
2498
2498
  };
@@ -4198,20 +4198,20 @@ function handleEmitEvent(input) {
4198
4198
  }
4199
4199
  }
4200
4200
  function registerPipelineTools(server2) {
4201
- const DESC2 = "Writes state to .stackwright/ \u2014 the filesystem is the state machine.";
4201
+ const DESC = "Writes state to .stackwright/ \u2014 the filesystem is the state machine.";
4202
4202
  const res = (r) => ({
4203
4203
  content: [{ type: "text", text: r.text }],
4204
4204
  isError: r.isError
4205
4205
  });
4206
4206
  server2.tool(
4207
4207
  "stackwright_pro_get_pipeline_state",
4208
- `Read pipeline state from .stackwright/pipeline-state.json. ${DESC2}`,
4208
+ `Read pipeline state from .stackwright/pipeline-state.json. ${DESC}`,
4209
4209
  {},
4210
4210
  async () => res(handleGetPipelineState())
4211
4211
  );
4212
4212
  server2.tool(
4213
4213
  "stackwright_pro_set_pipeline_state",
4214
- `Atomic read\u2192modify\u2192write pipeline state. ${DESC2}`,
4214
+ `Atomic read\u2192modify\u2192write pipeline state. ${DESC}`,
4215
4215
  {
4216
4216
  phase: z13.string().optional().describe('Phase to update, e.g. "designer"'),
4217
4217
  field: z13.enum(["questionsCollected", "answered", "executed", "artifactWritten", "inFlight"]).optional().describe("Boolean field to set"),
@@ -4252,7 +4252,7 @@ function registerPipelineTools(server2) {
4252
4252
  );
4253
4253
  server2.tool(
4254
4254
  "stackwright_pro_check_execution_ready",
4255
- `Check all phases have answer files in .stackwright/answers/. If phase is provided, check only that phase. ${DESC2}`,
4255
+ `Check all phases have answer files in .stackwright/answers/. If phase is provided, check only that phase. ${DESC}`,
4256
4256
  {
4257
4257
  phase: z13.string().optional().describe("If provided, check only this phase's readiness. Omit to check all phases.")
4258
4258
  },
@@ -4260,19 +4260,19 @@ function registerPipelineTools(server2) {
4260
4260
  );
4261
4261
  server2.tool(
4262
4262
  "stackwright_pro_get_ready_phases",
4263
- `Return phases whose dependencies are all satisfied (executed=true). Use to determine which phases can run next \u2014 enables wave-based execution. ${DESC2}`,
4263
+ `Return phases whose dependencies are all satisfied (executed=true). Use to determine which phases can run next \u2014 enables wave-based execution. ${DESC}`,
4264
4264
  {},
4265
4265
  async () => res(handleGetReadyPhases())
4266
4266
  );
4267
4267
  server2.tool(
4268
4268
  "stackwright_pro_list_artifacts",
4269
- `List phase artifacts in .stackwright/artifacts/ with completedCount/totalCount. ${DESC2}`,
4269
+ `List phase artifacts in .stackwright/artifacts/ with completedCount/totalCount. ${DESC}`,
4270
4270
  {},
4271
4271
  async () => res(handleListArtifacts())
4272
4272
  );
4273
4273
  server2.tool(
4274
4274
  "stackwright_pro_write_phase_questions",
4275
- `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. Specialists may also call this directly with a parsed questions array. ${DESC2}`,
4275
+ `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. Specialists may also call this directly with a parsed questions array. ${DESC}`,
4276
4276
  {
4277
4277
  phase: z13.string().optional().describe('Phase name, e.g. "designer" (required for direct write)'),
4278
4278
  responseText: z13.string().optional().describe("Raw LLM response from QUESTION_COLLECTION_MODE"),
@@ -4317,13 +4317,13 @@ function registerPipelineTools(server2) {
4317
4317
  );
4318
4318
  server2.tool(
4319
4319
  "stackwright_pro_build_specialist_prompt",
4320
- `Assemble execution prompt from answers + upstream artifacts. Foreman passes verbatim. ${DESC2}`,
4320
+ `Assemble execution prompt from answers + upstream artifacts. Foreman passes verbatim. ${DESC}`,
4321
4321
  { phase: z13.string().describe('Phase to build prompt for, e.g. "pages"') },
4322
4322
  async ({ phase }) => res(handleBuildSpecialistPrompt({ phase }))
4323
4323
  );
4324
4324
  server2.tool(
4325
4325
  "stackwright_pro_validate_artifact",
4326
- `Validate and write artifact to .stackwright/artifacts/. Returns retryPrompt on failure. ${DESC2}`,
4326
+ `Validate and write artifact to .stackwright/artifacts/. Returns retryPrompt on failure. ${DESC}`,
4327
4327
  {
4328
4328
  phase: z13.string().describe('Phase that produced this artifact, e.g. "designer"'),
4329
4329
  responseText: z13.string().optional().describe("Raw response text from the specialist otter (Foreman-mediated path, legacy)"),
@@ -4355,7 +4355,7 @@ function registerPipelineTools(server2) {
4355
4355
  );
4356
4356
  server2.tool(
4357
4357
  "stackwright_pro_emit_event",
4358
- `FOREMAN ONLY \u2014 specialist otters that call this will receive an authorization rejection (no-op, not an error). Emit a telemetry event to .stackwright/pipeline-events.ndjson. Foreman calls this at phase boundaries and agent-invoke boundaries. Best-effort: failures are silent and never block. ${DESC2}`,
4358
+ `FOREMAN ONLY \u2014 specialist otters that call this will receive an authorization rejection (no-op, not an error). Emit a telemetry event to .stackwright/pipeline-events.ndjson. Foreman calls this at phase boundaries and agent-invoke boundaries. Best-effort: failures are silent and never block. ${DESC}`,
4359
4359
  {
4360
4360
  type: z13.enum([
4361
4361
  "phase_start",
@@ -4854,10 +4854,10 @@ function handleSafeWrite(input) {
4854
4854
  return result;
4855
4855
  }
4856
4856
  function registerSafeWriteTools(server2) {
4857
- const DESC2 = "Controlled file-write chokepoint. Every write from specialist otters goes through this tool with per-otter path allowlists. The LLM cannot write to arbitrary filesystem paths.";
4857
+ const DESC = "Controlled file-write chokepoint. Every write from specialist otters goes through this tool with per-otter path allowlists. The LLM cannot write to arbitrary filesystem paths.";
4858
4858
  server2.tool(
4859
4859
  "stackwright_pro_safe_write",
4860
- DESC2,
4860
+ DESC,
4861
4861
  {
4862
4862
  callerOtter: z14.string().describe('The otter agent name requesting the write, e.g. "stackwright-pro-page-otter"'),
4863
4863
  filePath: z14.string().describe('Relative path from project root, e.g. "pages/dashboard/content.yml"'),
@@ -5801,7 +5801,7 @@ import { join as join8, basename } from "path";
5801
5801
  var _checksums = /* @__PURE__ */ new Map([
5802
5802
  [
5803
5803
  "stackwright-pro-api-otter.json",
5804
- "175cca39127beea84ed8f1b264b8cd5cf58733db050d32328c039cca0cf0b981"
5804
+ "023352bac8303d86b33559719dc3f8d06d3a5a1458719eb74e1dc76019cf9d2f"
5805
5805
  ],
5806
5806
  [
5807
5807
  "stackwright-pro-auth-otter.json",
@@ -5809,7 +5809,7 @@ var _checksums = /* @__PURE__ */ new Map([
5809
5809
  ],
5810
5810
  [
5811
5811
  "stackwright-pro-dashboard-otter.json",
5812
- "6918b51d1007c69e635f252b20b82774fa7c942fa1d2399769c14f8d1df4df70"
5812
+ "f8d594b14e05efea30cf253052d7c7df3e8c4174d739be4cd33f9c96cecdc451"
5813
5813
  ],
5814
5814
  [
5815
5815
  "stackwright-pro-data-otter.json",
@@ -5825,7 +5825,7 @@ var _checksums = /* @__PURE__ */ new Map([
5825
5825
  ],
5826
5826
  [
5827
5827
  "stackwright-pro-foreman-otter.json",
5828
- "c23436769a9db06fa6f9d312b4fdd8b2cef6646461020eeaf227303350073593"
5828
+ "9d68b7b20af7a8a1668e2a693b4a2b05cf37acce1e9f6298a0966c97a3417e9f"
5829
5829
  ],
5830
5830
  [
5831
5831
  "stackwright-pro-form-wizard-otter.json",
@@ -5849,11 +5849,11 @@ var _checksums = /* @__PURE__ */ new Map([
5849
5849
  ],
5850
5850
  [
5851
5851
  "stackwright-pro-scaffold-otter.json",
5852
- "c2ae10733d8bd66bd965b29a77193b0abf0ea6765cd02886df1413bf598e0588"
5852
+ "92930c732547c90a19e89ee0e25b5f037b7366f770941f0a01e148ff240a1b6d"
5853
5853
  ],
5854
5854
  [
5855
5855
  "stackwright-pro-theme-otter.json",
5856
- "b0eccc1945e30b80a0f9a3209c5428e8e32cd09063bd9b2ef7ecb891343774f2"
5856
+ "01d16d0597d475db60362e3b8020035e140817d197c36f4307a48b95f4b28b60"
5857
5857
  ],
5858
5858
  [
5859
5859
  "stackwright-services-otter.json",
@@ -6930,10 +6930,10 @@ function handleCleanupScaffold(_cwd) {
6930
6930
  };
6931
6931
  }
6932
6932
  function registerScaffoldCleanupTools(server2) {
6933
- const DESC2 = "Deterministic scaffold remnant cleanup \u2014 removes stale OSS scaffold files after raft run.";
6933
+ const DESC = "Deterministic scaffold remnant cleanup \u2014 removes stale OSS scaffold files after raft run.";
6934
6934
  server2.tool(
6935
6935
  "stackwright_pro_cleanup_scaffold",
6936
- `Remove/rewrite stale scaffold files (blog posts, hardcoded not-found page) after the raft pipeline completes. ${DESC2}`,
6936
+ `Remove/rewrite stale scaffold files (blog posts, hardcoded not-found page) after the raft pipeline completes. ${DESC}`,
6937
6937
  {},
6938
6938
  async () => {
6939
6939
  const r = handleCleanupScaffold();
@@ -7506,7 +7506,7 @@ import { z as z22 } from "zod";
7506
7506
  import { readFileSync as readFileSync12, existsSync as existsSync12, readdirSync as readdirSync5 } from "fs";
7507
7507
  import { join as join13 } from "path";
7508
7508
  import yaml from "js-yaml";
7509
- import { emitEnvLocal } from "@stackwright-pro/emitters";
7509
+ import { emitEnvLocal, emitProvidersFile, emitMockPorts } from "@stackwright-pro/emitters";
7510
7510
  function readIntegrations(cwd) {
7511
7511
  const filePath = join13(cwd, "stackwright.integrations.yml");
7512
7512
  if (!existsSync12(filePath)) return [];
@@ -7546,7 +7546,9 @@ function readServiceTokenRefs(cwd) {
7546
7546
  return [];
7547
7547
  }
7548
7548
  }
7549
- var DESC = "Returns { envLocal, envLocalExample } as JSON. The otter writes both files via stackwright_pro_safe_write.";
7549
+ var ENV_LOCAL_DESC = "Returns { envLocal, envLocalExample } as JSON. The otter writes both files via stackwright_pro_safe_write.";
7550
+ var PROVIDERS_DESC = "Returns { providersFile } as JSON. The otter writes the file via stackwright_pro_safe_write.";
7551
+ var PORTS_DESC = "Returns { ports } as JSON \u2014 map of integration name \u2192 port number.";
7550
7552
  function registerEmitterTools(server2) {
7551
7553
  server2.tool(
7552
7554
  "stackwright_pro_emit_env_local",
@@ -7557,7 +7559,7 @@ Call this tool, then write both files with stackwright_pro_safe_write:
7557
7559
  2. stackwright_pro_safe_write({ filePath: '.env.local', content: envLocal, ... })
7558
7560
  3. stackwright_pro_safe_write({ filePath: '.env.local.example', content: envLocalExample, ... })
7559
7561
 
7560
- Do NOT compose the contents by hand. This emitter is the source of truth. If an env var is missing, update integrations.yml or the emitter \u2014 not the file. This is the first concrete instance of the swp-zf9y deterministic emitter pattern. ${DESC}`,
7562
+ Do NOT compose the contents by hand. This emitter is the source of truth. If an env var is missing, update integrations.yml or the emitter \u2014 not the file. This is the first concrete instance of the swp-zf9y deterministic emitter pattern. ${ENV_LOCAL_DESC}`,
7561
7563
  {
7562
7564
  cwd: z22.string().describe(
7563
7565
  "Absolute path to the project root containing stackwright.integrations.yml and optionally services/*.yml"
@@ -7589,6 +7591,91 @@ Do NOT compose the contents by hand. This emitter is the source of truth. If an
7589
7591
  };
7590
7592
  }
7591
7593
  );
7594
+ server2.tool(
7595
+ "stackwright_pro_emit_providers_file",
7596
+ `Emit app/_components/providers.tsx from stackwright.integrations.yml. Deterministic emitter \u2014 uses LITERAL env access per integration so Next.js can inline NEXT_PUBLIC_* vars at build time. Also registers the map provider (default: Cesium for Pro).
7597
+
7598
+ \u26A0\uFE0F CRITICAL: DO NOT compose providers.tsx by hand. Computed env access (process.env[varName]) returns undefined in browser bundles for NEXT_PUBLIC_* vars \u2014 this silently drops all auth headers and causes 401s on every integration.
7599
+
7600
+ Workflow:
7601
+ 1. stackwright_pro_emit_providers_file({ cwd, mapProvider: 'cesium' }) \u2192 { providersFile }
7602
+ 2. stackwright_pro_safe_write({ filePath: 'app/_components/providers.tsx', content: providersFile, ... })
7603
+
7604
+ mapProvider options:
7605
+ - 'cesium' (default for Pro): 3D globe + terrain, @stackwright-pro/cesium + cesium peer dep
7606
+ - 'maplibre': free-tier 2D maps, no API key \u2014 OSS-style 2D dashboards only (opt-out)
7607
+ - 'none': skip map registration (ONLY if no pages use map content_items)
7608
+
7609
+ This is the second concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-57ku, swp-2m89, swp-zf9y, drawer 957. ${PROVIDERS_DESC}`,
7610
+ {
7611
+ cwd: z22.string().describe("Absolute path to the project root containing stackwright.integrations.yml"),
7612
+ mapProvider: z22.enum(["maplibre", "cesium", "none"]).optional().describe(
7613
+ "Map provider to register (default: cesium for Pro \u2014 3D globe + terrain). Use maplibre for OSS-style 2D dashboards (explicit opt-out). Use none if pages have no map content_items."
7614
+ )
7615
+ },
7616
+ async ({ cwd, mapProvider }) => {
7617
+ const integrations = readIntegrations(cwd);
7618
+ const providersFile = emitProvidersFile({ integrations, mapProvider });
7619
+ return {
7620
+ content: [
7621
+ {
7622
+ type: "text",
7623
+ text: JSON.stringify(
7624
+ {
7625
+ providersFile,
7626
+ meta: {
7627
+ integrationsRead: integrations.length,
7628
+ integrationsWithAuth: integrations.filter((i) => i.auth?.envVar).length,
7629
+ mapProvider: mapProvider ?? "cesium"
7630
+ }
7631
+ },
7632
+ null,
7633
+ 2
7634
+ )
7635
+ }
7636
+ ]
7637
+ };
7638
+ }
7639
+ );
7640
+ server2.tool(
7641
+ "stackwright_pro_emit_mock_ports",
7642
+ `Emit mock backend port assignments from stackwright.integrations.yml. Deterministic emitter \u2014 assigns consecutive ports starting at 4011 to each integration in declaration order. Skipped integrations (skipMockPort=true) are omitted and do not consume a port number.
7643
+
7644
+ \u26A0\uFE0F DO NOT pick mockUrl port numbers yourself. Port collisions occur when different models pick different defaults (Opus always picked 4010 for all integrations in run-4).
7645
+
7646
+ Workflow:
7647
+ 1. stackwright_pro_emit_mock_ports({ cwd }) \u2192 { ports }
7648
+ 2. For each integration, set mockUrl to http://localhost:<ports[name]>
7649
+ 3. Write stackwright.integrations.yml via stackwright_pro_safe_write
7650
+
7651
+ This is the third concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-phkh, swp-zf9y. ${PORTS_DESC}`,
7652
+ {
7653
+ cwd: z22.string().describe("Absolute path to the project root containing stackwright.integrations.yml")
7654
+ },
7655
+ async ({ cwd }) => {
7656
+ const integrations = readIntegrations(cwd);
7657
+ const { ports } = emitMockPorts({ integrations });
7658
+ return {
7659
+ content: [
7660
+ {
7661
+ type: "text",
7662
+ text: JSON.stringify(
7663
+ {
7664
+ ports,
7665
+ meta: {
7666
+ integrationsRead: integrations.length,
7667
+ portsAssigned: Object.keys(ports).length,
7668
+ portRange: Object.keys(ports).length > 0 ? `${Math.min(...Object.values(ports))}\u2013${Math.max(...Object.values(ports))}` : "none"
7669
+ }
7670
+ },
7671
+ null,
7672
+ 2
7673
+ )
7674
+ }
7675
+ ]
7676
+ };
7677
+ }
7678
+ );
7592
7679
  }
7593
7680
 
7594
7681
  // src/tools/list-specs.ts
@@ -7668,6 +7755,140 @@ function registerListSpecsTool(server2) {
7668
7755
  );
7669
7756
  }
7670
7757
 
7758
+ // src/tools/consolidate-integrations.ts
7759
+ import { z as z24 } from "zod";
7760
+ import { readdirSync as readdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9 } from "fs";
7761
+ import { lockSync as lockSync2 } from "proper-lockfile";
7762
+ import { join as join15, basename as basename3 } from "path";
7763
+ import yaml2 from "js-yaml";
7764
+ var BASE_MOCK_PORT = 4011;
7765
+ var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
7766
+ function deriveNameFromFilename(filename) {
7767
+ const base = basename3(filename, ".json").replace(/^api-config-/, "");
7768
+ for (const suffix of STRIP_SUFFIXES2) {
7769
+ if (base.endsWith(suffix)) return base.slice(0, -suffix.length);
7770
+ }
7771
+ return base;
7772
+ }
7773
+ function handleConsolidateIntegrations(input) {
7774
+ const { projectRoot } = input;
7775
+ const artifactsDir = join15(projectRoot, ".stackwright", "artifacts");
7776
+ if (!existsSync14(artifactsDir)) {
7777
+ return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
7778
+ }
7779
+ let entries;
7780
+ try {
7781
+ entries = readdirSync7(artifactsDir).filter(
7782
+ (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
7783
+ );
7784
+ } catch {
7785
+ return { written: false, integrationCount: 0, reason: "could not read artifacts directory" };
7786
+ }
7787
+ if (entries.length === 0) {
7788
+ return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
7789
+ }
7790
+ entries.sort();
7791
+ const integrationsByName = /* @__PURE__ */ new Map();
7792
+ entries.forEach((filename, index) => {
7793
+ let artifact = {};
7794
+ try {
7795
+ const raw = readFileSync14(join15(artifactsDir, filename), "utf-8");
7796
+ artifact = JSON.parse(raw);
7797
+ } catch {
7798
+ }
7799
+ const name = artifact.integrationName ?? deriveNameFromFilename(filename);
7800
+ const spec = artifact.integrationSpec ?? artifact.specPath ?? `./specs/${name}.yaml`;
7801
+ const mockUrl = artifact.mockUrl ?? `http://localhost:${BASE_MOCK_PORT + index}`;
7802
+ const baseUrl = artifact.baseUrl ?? "";
7803
+ const auth = artifact.auth ?? { type: "none" };
7804
+ const isAsyncApi = artifact.skipped?.some((s) => s.format === "asyncapi") ?? false;
7805
+ const integrationType = artifact.integrationFormat ?? (isAsyncApi ? "asyncapi" : "openapi");
7806
+ const entry = {
7807
+ type: integrationType,
7808
+ name,
7809
+ spec,
7810
+ mockUrl,
7811
+ baseUrl,
7812
+ auth
7813
+ };
7814
+ if (artifact.integrationEndpoints) {
7815
+ entry.endpoints = artifact.integrationEndpoints;
7816
+ }
7817
+ integrationsByName.set(name, entry);
7818
+ });
7819
+ const integrations = Array.from(integrationsByName.values());
7820
+ const dedupedCount = entries.length - integrations.length;
7821
+ const integrationsYmlPath = join15(projectRoot, "stackwright.integrations.yml");
7822
+ let existing = {};
7823
+ if (existsSync14(integrationsYmlPath)) {
7824
+ try {
7825
+ const raw = readFileSync14(integrationsYmlPath, "utf-8");
7826
+ existing = yaml2.load(raw) ?? {};
7827
+ } catch {
7828
+ existing = {};
7829
+ }
7830
+ }
7831
+ const merged = { ...existing, integrations };
7832
+ mkdirSync9(join15(projectRoot), { recursive: true });
7833
+ if (!existsSync14(integrationsYmlPath)) {
7834
+ writeFileSync10(integrationsYmlPath, "", "utf-8");
7835
+ }
7836
+ const release = lockSync2(integrationsYmlPath, {
7837
+ realpath: false,
7838
+ stale: 1e4
7839
+ });
7840
+ try {
7841
+ const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
7842
+ `;
7843
+ const body = yaml2.dump(merged, { lineWidth: 120, noRefs: true });
7844
+ writeFileSync10(integrationsYmlPath, header + body, "utf-8");
7845
+ } finally {
7846
+ release();
7847
+ }
7848
+ return {
7849
+ written: true,
7850
+ integrationCount: integrations.length,
7851
+ path: "stackwright.integrations.yml",
7852
+ ...dedupedCount > 0 ? { dedupedCount } : {}
7853
+ };
7854
+ }
7855
+ function registerConsolidateIntegrationsTool(server2) {
7856
+ server2.tool(
7857
+ "stackwright_pro_consolidate_integrations",
7858
+ [
7859
+ "Assemble stackwright.integrations.yml from per-spec api-config-*.json artifacts.",
7860
+ "Called by the foreman AFTER all parallel api-otter invocations complete (swp-3l9j).",
7861
+ "Reads .stackwright/artifacts/api-config-{name}.json for each spec, deduplicates by",
7862
+ "integration name (last-write-wins), preserves any non-integrations top-level keys in",
7863
+ "an existing stackwright.integrations.yml, and writes the merged result atomically.",
7864
+ "Returns { written, integrationCount, path } on success or { written: false, reason } if",
7865
+ "no per-spec artifacts are found."
7866
+ ].join(" "),
7867
+ {
7868
+ projectRoot: z24.string().describe("Absolute path to the project root directory")
7869
+ },
7870
+ async ({ projectRoot }) => {
7871
+ try {
7872
+ const result = handleConsolidateIntegrations({ projectRoot });
7873
+ return {
7874
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
7875
+ };
7876
+ } catch (err) {
7877
+ const message = err instanceof Error ? err.message : String(err);
7878
+ return {
7879
+ content: [
7880
+ {
7881
+ type: "text",
7882
+ text: JSON.stringify({ written: false, error: message })
7883
+ }
7884
+ ],
7885
+ isError: true
7886
+ };
7887
+ }
7888
+ }
7889
+ );
7890
+ }
7891
+
7671
7892
  // package.json
7672
7893
  var package_default = {
7673
7894
  dependencies: {
@@ -7680,7 +7901,7 @@ var package_default = {
7680
7901
  "@stackwright-pro/pulse": "workspace:*",
7681
7902
  "@stackwright-pro/telemetry": "workspace:*",
7682
7903
  "@stackwright-pro/types": "workspace:*",
7683
- "@stackwright/mcp": "0.6.0-alpha.1",
7904
+ "@stackwright/mcp": "0.6.0-alpha.2",
7684
7905
  "@types/js-yaml": "^4.0.9",
7685
7906
  "js-yaml": "^4.2.0",
7686
7907
  "proper-lockfile": "^4.1.2",
@@ -7702,7 +7923,7 @@ var package_default = {
7702
7923
  "test:coverage": "vitest run --coverage"
7703
7924
  },
7704
7925
  name: "@stackwright-pro/mcp",
7705
- version: "0.2.0-alpha.102",
7926
+ version: "0.2.0-alpha.104",
7706
7927
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
7707
7928
  license: "SEE LICENSE IN LICENSE",
7708
7929
  main: "./dist/server.js",
@@ -7784,6 +8005,7 @@ registerCompileTools(server);
7784
8005
  registerStripLegacyIntegrationsTool(server);
7785
8006
  registerEmitterTools(server);
7786
8007
  registerListSpecsTool(server);
8008
+ registerConsolidateIntegrationsTool(server);
7787
8009
  registerContentTypeTools(server);
7788
8010
  registerPageTools(server);
7789
8011
  registerSiteTools(server);