@stackwright-pro/mcp 0.2.0-alpha.106 → 0.2.0-alpha.108

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
@@ -1310,6 +1310,10 @@ function answersToManifestFormat(answers, questions) {
1310
1310
  // src/validation.ts
1311
1311
  var SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
1312
1312
  var SAFE_QUESTION_ID = /^[a-z0-9][a-z0-9-]{0,63}$/i;
1313
+ var SAFE_SLUG = /^[a-z0-9][a-z0-9-]{0,79}$/;
1314
+ function isValidSlug(slug) {
1315
+ return SAFE_SLUG.test(slug);
1316
+ }
1313
1317
  function isValidPhase(phase) {
1314
1318
  return SAFE_PHASE.test(phase);
1315
1319
  }
@@ -1999,9 +2003,9 @@ function registerOrchestrationTools(server2) {
1999
2003
 
2000
2004
  // src/tools/pipeline.ts
2001
2005
  import { z as z13 } from "zod";
2002
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4, lstatSync as lstatSync6 } from "fs";
2006
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4, lstatSync as lstatSync6, readdirSync as readdirSync5 } from "fs";
2003
2007
  import { lockSync } from "proper-lockfile";
2004
- import { join as join6 } from "path";
2008
+ import { join as join7 } from "path";
2005
2009
  import { createHash as createHash3 } from "crypto";
2006
2010
 
2007
2011
  // src/artifact-signing.ts
@@ -2544,6 +2548,234 @@ function formatPulseRetryPrompt(result) {
2544
2548
  return lines.join("\n");
2545
2549
  }
2546
2550
 
2551
+ // src/tools/validate-api-integration.ts
2552
+ var CANONICAL_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2", "oidc", "none"];
2553
+ var CANONICAL_SET = new Set(CANONICAL_API_AUTH_TYPES);
2554
+ var VALID_SET_DISPLAY = CANONICAL_API_AUTH_TYPES.map((t) => `\`${t}\``).join(", ");
2555
+ var RENAME_MAP = {
2556
+ // apiKey variants
2557
+ "api-key": "apiKey",
2558
+ api_key: "apiKey",
2559
+ apikey: "apiKey",
2560
+ // lowercase of 'apiKey' / 'APIKEY'
2561
+ // oauth2 variants
2562
+ "oauth-2": "oauth2",
2563
+ oauth_2: "oauth2",
2564
+ // oidc variants
2565
+ "open-id-connect": "oidc",
2566
+ "openid-connect": "oidc",
2567
+ openidconnect: "oidc",
2568
+ // lowercase of 'openIdConnect'
2569
+ openidconnect_: "oidc",
2570
+ // belt-and-suspenders
2571
+ openIdConnect: "oidc",
2572
+ // exact camelCase as emitted by some tooling
2573
+ // bearer variants
2574
+ "bearer-token": "bearer",
2575
+ bearer_token: "bearer"
2576
+ };
2577
+ function renameSuggestion(badLiteral) {
2578
+ return RENAME_MAP[badLiteral] ?? RENAME_MAP[badLiteral.toLowerCase()];
2579
+ }
2580
+ function validateAuthType(authType, yamlPath, artifactPath) {
2581
+ if (authType === void 0 || authType === null) return null;
2582
+ if (typeof authType !== "string") {
2583
+ return [
2584
+ `${artifactPath}: \`${yamlPath}\` must be a string, got \`${typeof authType}\`.`,
2585
+ `Valid values: ${VALID_SET_DISPLAY}.`,
2586
+ "Fix this value and re-call stackwright_pro_validate_artifact."
2587
+ ].join("\n");
2588
+ }
2589
+ if (CANONICAL_SET.has(authType)) return null;
2590
+ const suggestion = renameSuggestion(authType);
2591
+ const lines = [
2592
+ `${artifactPath}: \`${yamlPath}\` has non-canonical value \`"${authType}"\`.`,
2593
+ `Valid values: ${VALID_SET_DISPLAY}.`
2594
+ ];
2595
+ if (suggestion) {
2596
+ lines.push(`Did you mean: \`${suggestion}\`?`);
2597
+ }
2598
+ lines.push("Fix this value and re-call stackwright_pro_validate_artifact.");
2599
+ return lines.join("\n");
2600
+ }
2601
+ function validateApiIntegration(artifact, integrationName) {
2602
+ const artifactFile = integrationName ? `api-config-${integrationName}.json` : "api-config.json";
2603
+ const artifactPath = `.stackwright/artifacts/${artifactFile}`;
2604
+ if (artifact === null || typeof artifact !== "object") {
2605
+ return { valid: true };
2606
+ }
2607
+ const obj = artifact;
2608
+ const errors = [];
2609
+ if ("auth" in obj && obj["auth"] !== null && typeof obj["auth"] === "object") {
2610
+ const auth = obj["auth"];
2611
+ const err = validateAuthType(auth["type"], "auth.type", artifactPath);
2612
+ if (err) errors.push(err);
2613
+ }
2614
+ if ("securitySchemes" in obj && obj["securitySchemes"] !== null && typeof obj["securitySchemes"] === "object" && !Array.isArray(obj["securitySchemes"])) {
2615
+ const schemes = obj["securitySchemes"];
2616
+ for (const [schemeName, scheme] of Object.entries(schemes)) {
2617
+ if (scheme !== null && typeof scheme === "object") {
2618
+ const s = scheme;
2619
+ const err = validateAuthType(s["type"], `securitySchemes.${schemeName}.type`, artifactPath);
2620
+ if (err) errors.push(err);
2621
+ }
2622
+ }
2623
+ }
2624
+ if (errors.length === 0) return { valid: true };
2625
+ const retryPrompt = [
2626
+ `API integration auth validation failed: ${errors.length} violation(s) in ${artifactPath}.`,
2627
+ "",
2628
+ "You MUST fix ALL violations and re-call stackwright_pro_validate_artifact.",
2629
+ "",
2630
+ ...errors.flatMap((e) => [e, ""]),
2631
+ `Canonical auth.type values: ${VALID_SET_DISPLAY}.`,
2632
+ "Do NOT use hyphenated or underscored variants (api-key, oauth-2, bearer-token, etc.)."
2633
+ ].join("\n");
2634
+ return { valid: false, retryPrompt };
2635
+ }
2636
+
2637
+ // src/tools/validate-auth-config.ts
2638
+ var KNOWN_NESTED_STRATEGIES = ["oidc", "pki"];
2639
+ var NESTED_STRATEGY_FIELD_EXAMPLES = {
2640
+ oidc: ["discoveryUrl", "clientId", "clientSecret"],
2641
+ pki: ["profile", "source", "headerPrefix"]
2642
+ };
2643
+ function nestedBlockError(strategy, nestedObj, artifactPath) {
2644
+ const exampleFields = NESTED_STRATEGY_FIELD_EXAMPLES[strategy];
2645
+ const actualKeys = Object.keys(nestedObj).slice(0, 2);
2646
+ const showField0 = actualKeys[0] ?? exampleFields[0];
2647
+ const showField1 = actualKeys[1] ?? exampleFields[1];
2648
+ return [
2649
+ `${artifactPath}: \`authConfig.${strategy}\` must not be a nested provider block.`,
2650
+ `Hoist \`authConfig.${strategy}.*\` fields to \`authConfig.*\`. The canonical AuthConfig`,
2651
+ `schema does not nest provider-specific fields under a named sub-object.`,
2652
+ ``,
2653
+ `Bad (nested block):`,
2654
+ ` "authConfig": {`,
2655
+ ` "type": "${strategy}",`,
2656
+ ` "${strategy}": {`,
2657
+ ` "${showField0}": "...",`,
2658
+ ` "${showField1}": "...",`,
2659
+ ` ...`,
2660
+ ` }`,
2661
+ ` }`,
2662
+ ``,
2663
+ `Correct (hoisted to root of authConfig):`,
2664
+ ` "authConfig": {`,
2665
+ ` "type": "${strategy}",`,
2666
+ ` "${exampleFields[0]}": "...",`,
2667
+ ` "${exampleFields[1]}": "...",`,
2668
+ ` "${exampleFields[2]}": "...",`,
2669
+ ` ...`,
2670
+ ` }`,
2671
+ ``,
2672
+ `Move every key from \`authConfig.${strategy}\` up to \`authConfig\` and remove the \`"${strategy}"\` sub-object.`
2673
+ ].join("\n");
2674
+ }
2675
+ function discriminatorKeyError(methodVal, artifactPath) {
2676
+ return [
2677
+ `${artifactPath}: \`authConfig.method\` is not a valid key in the canonical AuthConfig schema.`,
2678
+ `Rename \`authConfig.method\` \u2192 \`authConfig.type\`. The canonical AuthConfig schema uses`,
2679
+ `\`type\` as the auth-strategy discriminator, matching the api-config \`auth.type\` convention`,
2680
+ `shipped in swp-fimq.`,
2681
+ ``,
2682
+ `Bad:`,
2683
+ ` "authConfig": { "method": "${methodVal}", ... }`,
2684
+ ``,
2685
+ `Correct:`,
2686
+ ` "authConfig": { "type": "${methodVal}", ... }`
2687
+ ].join("\n");
2688
+ }
2689
+ function ambiguousDiscriminatorError(methodVal, typeVal, artifactPath) {
2690
+ return [
2691
+ `${artifactPath}: \`authConfig\` has both \`method: "${methodVal}"\` and \`type: "${typeVal}"\` \u2014 ambiguous discriminator.`,
2692
+ `Remove \`authConfig.method\` and keep only \`authConfig.type\`. The canonical AuthConfig`,
2693
+ `schema uses \`type\` as the sole auth-strategy discriminator.`
2694
+ ].join("\n");
2695
+ }
2696
+ function validateAuthConfig(artifact, artifactPath) {
2697
+ if (artifact === null || typeof artifact !== "object") {
2698
+ return { valid: true };
2699
+ }
2700
+ const obj = artifact;
2701
+ if (!("authConfig" in obj) || obj["authConfig"] === void 0 || obj["authConfig"] === null) {
2702
+ return { valid: true };
2703
+ }
2704
+ if (typeof obj["authConfig"] !== "object" || Array.isArray(obj["authConfig"])) {
2705
+ return { valid: true };
2706
+ }
2707
+ const authConfig = obj["authConfig"];
2708
+ const errors = [];
2709
+ const hasMethod = "method" in authConfig && authConfig["method"] !== void 0;
2710
+ const hasType = "type" in authConfig && authConfig["type"] !== void 0;
2711
+ if (hasMethod && hasType) {
2712
+ errors.push(
2713
+ ambiguousDiscriminatorError(
2714
+ String(authConfig["method"]),
2715
+ String(authConfig["type"]),
2716
+ artifactPath
2717
+ )
2718
+ );
2719
+ } else if (hasMethod && !hasType) {
2720
+ errors.push(discriminatorKeyError(String(authConfig["method"]), artifactPath));
2721
+ }
2722
+ for (const strategy of KNOWN_NESTED_STRATEGIES) {
2723
+ const nested = authConfig[strategy];
2724
+ if (nested !== null && nested !== void 0 && typeof nested === "object" && !Array.isArray(nested)) {
2725
+ errors.push(nestedBlockError(strategy, nested, artifactPath));
2726
+ }
2727
+ }
2728
+ if (errors.length === 0) return { valid: true };
2729
+ const retryPrompt = [
2730
+ `Auth config validation failed: ${errors.length} violation(s) in ${artifactPath}.`,
2731
+ ``,
2732
+ `You MUST fix ALL violations and re-call stackwright_pro_validate_artifact.`,
2733
+ ``,
2734
+ ...errors.flatMap((e) => [e, ""]),
2735
+ `The canonical authConfig shape uses \`type\` (not \`method\`) as the discriminator,`,
2736
+ `with all provider-specific fields hoisted to the root of \`authConfig\`.`,
2737
+ `AuthConfig is a discriminated union on \`type: 'oidc' | 'pki'\`.`
2738
+ ].join("\n");
2739
+ return { valid: false, retryPrompt };
2740
+ }
2741
+
2742
+ // src/tools/auth-manifest-aggregator.ts
2743
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "fs";
2744
+ import { join as join5 } from "path";
2745
+ function extractPatternBaseSlug(pattern) {
2746
+ const stripped = pattern.replace(/^\//, "");
2747
+ if (!stripped) return null;
2748
+ return stripped.split("/")[0] ?? null;
2749
+ }
2750
+ function extractManifestSlugs(artifactsDir) {
2751
+ const slugs = /* @__PURE__ */ new Set();
2752
+ if (!existsSync6(artifactsDir)) return slugs;
2753
+ let entries;
2754
+ try {
2755
+ entries = readdirSync3(artifactsDir);
2756
+ } catch {
2757
+ return slugs;
2758
+ }
2759
+ for (const entry of entries) {
2760
+ if (!entry.endsWith("-manifest.json")) continue;
2761
+ try {
2762
+ const content = JSON.parse(readFileSync5(join5(artifactsDir, entry), "utf-8"));
2763
+ const pages = content.pages;
2764
+ if (!Array.isArray(pages)) continue;
2765
+ for (const page of pages) {
2766
+ if (typeof page.slug !== "string" || !page.slug) continue;
2767
+ const firstSeg = page.slug.split("/")[0];
2768
+ if (firstSeg) slugs.add(firstSeg);
2769
+ if (!page.slug.includes("[") && !page.slug.includes(":")) {
2770
+ slugs.add(page.slug);
2771
+ }
2772
+ }
2773
+ } catch {
2774
+ }
2775
+ }
2776
+ return slugs;
2777
+ }
2778
+
2547
2779
  // src/tools/pipeline.ts
2548
2780
  import { WorkflowFileSchema, authConfigSchema as authConfigSchema3 } from "@stackwright-pro/types";
2549
2781
 
@@ -2894,8 +3126,8 @@ function registerGetSchemaTool(server2) {
2894
3126
  }
2895
3127
 
2896
3128
  // src/tools/pipeline-graph.ts
2897
- import { readFileSync as readFileSync5, readdirSync as readdirSync3, lstatSync as lstatSync5 } from "fs";
2898
- import { join as join5 } from "path";
3129
+ import { readFileSync as readFileSync6, readdirSync as readdirSync4, lstatSync as lstatSync5 } from "fs";
3130
+ import { join as join6 } from "path";
2899
3131
  var MANIFEST_NAME_TO_PHASE = {
2900
3132
  "stackwright-pro-designer-otter": "designer",
2901
3133
  "stackwright-pro-theme-otter": "theme",
@@ -2925,7 +3157,7 @@ var OTTER_SEARCH_PATHS = [
2925
3157
  function resolveOtterDirFromCwd() {
2926
3158
  const cwd = process.cwd();
2927
3159
  for (const relative2 of OTTER_SEARCH_PATHS) {
2928
- const candidate = join5(cwd, relative2);
3160
+ const candidate = join6(cwd, relative2);
2929
3161
  try {
2930
3162
  lstatSync5(candidate);
2931
3163
  return candidate;
@@ -2937,14 +3169,14 @@ function resolveOtterDirFromCwd() {
2937
3169
  );
2938
3170
  }
2939
3171
  function loadOtterManifestsFromDir(otterDir) {
2940
- const entries = readdirSync3(otterDir);
3172
+ const entries = readdirSync4(otterDir);
2941
3173
  const manifests = [];
2942
3174
  for (const filename of entries) {
2943
3175
  if (!filename.endsWith("-otter.json")) continue;
2944
- const filePath = join5(otterDir, filename);
3176
+ const filePath = join6(otterDir, filename);
2945
3177
  if (lstatSync5(filePath).isSymbolicLink()) continue;
2946
3178
  try {
2947
- const raw = readFileSync5(filePath, "utf-8");
3179
+ const raw = readFileSync6(filePath, "utf-8");
2948
3180
  const manifest = JSON.parse(raw);
2949
3181
  manifests.push(manifest);
2950
3182
  } catch (err) {
@@ -3049,19 +3281,19 @@ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
3049
3281
  function loadDistributedOtterManifests(cwd) {
3050
3282
  const results = [];
3051
3283
  for (const namespace of DISTRIBUTED_NAMESPACES) {
3052
- const nsDir = join5(cwd, "node_modules", namespace);
3284
+ const nsDir = join6(cwd, "node_modules", namespace);
3053
3285
  let pkgDirNames;
3054
3286
  try {
3055
- pkgDirNames = readdirSync3(nsDir);
3287
+ pkgDirNames = readdirSync4(nsDir);
3056
3288
  } catch {
3057
3289
  continue;
3058
3290
  }
3059
3291
  for (const pkgDirName of pkgDirNames) {
3060
- const pkgDir = join5(nsDir, pkgDirName);
3292
+ const pkgDir = join6(nsDir, pkgDirName);
3061
3293
  const packageName = `${namespace}/${pkgDirName}`;
3062
3294
  let otterRelPaths;
3063
3295
  try {
3064
- const raw = readFileSync5(join5(pkgDir, "package.json"), "utf-8");
3296
+ const raw = readFileSync6(join6(pkgDir, "package.json"), "utf-8");
3065
3297
  const pkgJson = JSON.parse(raw);
3066
3298
  const declared = pkgJson.stackwright?.otters;
3067
3299
  if (!Array.isArray(declared) || declared.length === 0) continue;
@@ -3070,9 +3302,9 @@ function loadDistributedOtterManifests(cwd) {
3070
3302
  continue;
3071
3303
  }
3072
3304
  for (const relPath of otterRelPaths) {
3073
- const otterFilePath = join5(pkgDir, relPath);
3305
+ const otterFilePath = join6(pkgDir, relPath);
3074
3306
  try {
3075
- const raw = readFileSync5(otterFilePath, "utf-8");
3307
+ const raw = readFileSync6(otterFilePath, "utf-8");
3076
3308
  const manifest = JSON.parse(raw);
3077
3309
  results.push({ manifest, packageName });
3078
3310
  } catch (err) {
@@ -3190,11 +3422,11 @@ function createDefaultState() {
3190
3422
  };
3191
3423
  }
3192
3424
  function statePath(cwd) {
3193
- return join6(cwd, ".stackwright", "pipeline-state.json");
3425
+ return join7(cwd, ".stackwright", "pipeline-state.json");
3194
3426
  }
3195
3427
  function readState(cwd) {
3196
3428
  const p = statePath(cwd);
3197
- if (!existsSync6(p)) return createDefaultState();
3429
+ if (!existsSync7(p)) return createDefaultState();
3198
3430
  const raw = JSON.parse(safeReadSync(p));
3199
3431
  if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
3200
3432
  return createDefaultState();
@@ -3202,7 +3434,7 @@ function readState(cwd) {
3202
3434
  return raw;
3203
3435
  }
3204
3436
  function safeWriteSync(filePath, content) {
3205
- if (existsSync6(filePath)) {
3437
+ if (existsSync7(filePath)) {
3206
3438
  const stat = lstatSync6(filePath);
3207
3439
  if (stat.isSymbolicLink()) {
3208
3440
  throw new Error(`Refusing to write to symlink: ${filePath}`);
@@ -3211,25 +3443,25 @@ function safeWriteSync(filePath, content) {
3211
3443
  writeFileSync4(filePath, content);
3212
3444
  }
3213
3445
  function safeReadSync(filePath) {
3214
- if (existsSync6(filePath)) {
3446
+ if (existsSync7(filePath)) {
3215
3447
  const stat = lstatSync6(filePath);
3216
3448
  if (stat.isSymbolicLink()) {
3217
3449
  throw new Error(`Refusing to read symlink: ${filePath}`);
3218
3450
  }
3219
3451
  }
3220
- return readFileSync6(filePath, "utf-8");
3452
+ return readFileSync7(filePath, "utf-8");
3221
3453
  }
3222
3454
  function writeState(cwd, state) {
3223
- const dir = join6(cwd, ".stackwright");
3455
+ const dir = join7(cwd, ".stackwright");
3224
3456
  mkdirSync4(dir, { recursive: true });
3225
3457
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3226
3458
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
3227
3459
  }
3228
3460
  function updateStateAtomic(cwd, mutator) {
3229
- const dir = join6(cwd, ".stackwright");
3461
+ const dir = join7(cwd, ".stackwright");
3230
3462
  mkdirSync4(dir, { recursive: true });
3231
3463
  const path5 = statePath(cwd);
3232
- if (!existsSync6(path5)) {
3464
+ if (!existsSync7(path5)) {
3233
3465
  safeWriteSync(path5, JSON.stringify(createDefaultState(), null, 2) + "\n");
3234
3466
  }
3235
3467
  const release = lockSync(path5, {
@@ -3263,8 +3495,8 @@ function handleGetPipelineState(_cwd) {
3263
3495
  const cwd = _cwd ?? process.cwd();
3264
3496
  try {
3265
3497
  const state = readState(cwd);
3266
- const keyPath = join6(cwd, ".stackwright", "pipeline-keys.json");
3267
- if (!existsSync6(keyPath)) {
3498
+ const keyPath = join7(cwd, ".stackwright", "pipeline-keys.json");
3499
+ if (!existsSync7(keyPath)) {
3268
3500
  try {
3269
3501
  const { fingerprint } = initPipelineKeys(cwd);
3270
3502
  state["signingKeyFingerprint"] = fingerprint;
@@ -3395,8 +3627,8 @@ function handleCheckExecutionReady(_cwd, phase) {
3395
3627
  isError: true
3396
3628
  };
3397
3629
  }
3398
- const answerFile = join6(cwd, ".stackwright", "answers", `${phase}.json`);
3399
- if (!existsSync6(answerFile)) {
3630
+ const answerFile = join7(cwd, ".stackwright", "answers", `${phase}.json`);
3631
+ if (!existsSync7(answerFile)) {
3400
3632
  return {
3401
3633
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
3402
3634
  isError: false
@@ -3420,12 +3652,12 @@ function handleCheckExecutionReady(_cwd, phase) {
3420
3652
  }
3421
3653
  }
3422
3654
  try {
3423
- const answersDir = join6(cwd, ".stackwright", "answers");
3655
+ const answersDir = join7(cwd, ".stackwright", "answers");
3424
3656
  const answeredPhases = [];
3425
3657
  const missingPhases = [];
3426
3658
  for (const phase2 of PHASE_ORDER) {
3427
- const answerFile = join6(answersDir, `${phase2}.json`);
3428
- if (existsSync6(answerFile)) {
3659
+ const answerFile = join7(answersDir, `${phase2}.json`);
3660
+ if (existsSync7(answerFile)) {
3429
3661
  try {
3430
3662
  const raw = safeReadSync(answerFile);
3431
3663
  const parsed = JSON.parse(raw);
@@ -3494,7 +3726,7 @@ function handleGetReadyPhases(_cwd) {
3494
3726
  function handleListArtifacts(_cwd) {
3495
3727
  const cwd = _cwd ?? process.cwd();
3496
3728
  try {
3497
- const artifactsDir = join6(cwd, ".stackwright", "artifacts");
3729
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
3498
3730
  let manifest = null;
3499
3731
  try {
3500
3732
  manifest = loadSignatureManifest(cwd);
@@ -3504,8 +3736,8 @@ function handleListArtifacts(_cwd) {
3504
3736
  let completedCount = 0;
3505
3737
  for (const phase of PHASE_ORDER) {
3506
3738
  const expectedFile = PHASE_ARTIFACT[phase];
3507
- const fullPath = join6(artifactsDir, expectedFile);
3508
- const exists = existsSync6(fullPath);
3739
+ const fullPath = join7(artifactsDir, expectedFile);
3740
+ const exists = existsSync7(fullPath);
3509
3741
  if (exists) completedCount++;
3510
3742
  let signed = false;
3511
3743
  let signatureValid = null;
@@ -3558,9 +3790,9 @@ function handleWritePhaseQuestions(input) {
3558
3790
  }
3559
3791
  } catch {
3560
3792
  }
3561
- const questionsDir = join6(cwd, ".stackwright", "questions");
3793
+ const questionsDir = join7(cwd, ".stackwright", "questions");
3562
3794
  mkdirSync4(questionsDir, { recursive: true });
3563
- const filePath = join6(questionsDir, `${phase}.json`);
3795
+ const filePath = join7(questionsDir, `${phase}.json`);
3564
3796
  const payload = {
3565
3797
  version: "1.0",
3566
3798
  phase,
@@ -3600,14 +3832,14 @@ function handleBuildSpecialistPrompt(input) {
3600
3832
  };
3601
3833
  }
3602
3834
  try {
3603
- const answersPath = join6(cwd, ".stackwright", "answers", `${phase}.json`);
3835
+ const answersPath = join7(cwd, ".stackwright", "answers", `${phase}.json`);
3604
3836
  let answers = {};
3605
- if (existsSync6(answersPath)) {
3837
+ if (existsSync7(answersPath)) {
3606
3838
  answers = JSON.parse(safeReadSync(answersPath));
3607
3839
  }
3608
3840
  let buildContextText = "";
3609
- const buildContextPath = join6(cwd, ".stackwright", "build-context.json");
3610
- if (existsSync6(buildContextPath)) {
3841
+ const buildContextPath = join7(cwd, ".stackwright", "build-context.json");
3842
+ if (existsSync7(buildContextPath)) {
3611
3843
  try {
3612
3844
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
3613
3845
  if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
@@ -3622,8 +3854,8 @@ function handleBuildSpecialistPrompt(input) {
3622
3854
  const missingDependencies = [];
3623
3855
  for (const dep of deps) {
3624
3856
  const artifactFile = PHASE_ARTIFACT[dep];
3625
- const artifactPath = join6(cwd, ".stackwright", "artifacts", artifactFile);
3626
- if (existsSync6(artifactPath)) {
3857
+ const artifactPath = join7(cwd, ".stackwright", "artifacts", artifactFile);
3858
+ if (existsSync7(artifactPath)) {
3627
3859
  const rawContent = safeReadSync(artifactPath);
3628
3860
  const rawBytes = Buffer.from(rawContent, "utf-8");
3629
3861
  const content = JSON.parse(rawContent);
@@ -3688,8 +3920,8 @@ ${JSON.stringify(content, null, 2)}`
3688
3920
  parts.push("BUILD_CONTEXT:", buildContextText, "");
3689
3921
  }
3690
3922
  if (phase === "auth") {
3691
- const initContextPath = join6(cwd, ".stackwright", "init-context.json");
3692
- if (existsSync6(initContextPath)) {
3923
+ const initContextPath = join7(cwd, ".stackwright", "init-context.json");
3924
+ if (existsSync7(initContextPath)) {
3693
3925
  try {
3694
3926
  const initContext = JSON.parse(safeReadSync(initContextPath));
3695
3927
  if (initContext.devOnly === true || initContext.nonInteractive === true) {
@@ -4140,6 +4372,61 @@ function _validateArtifactInner(input) {
4140
4372
  };
4141
4373
  return { text: JSON.stringify(result), isError: false };
4142
4374
  }
4375
+ if (phase === "auth") {
4376
+ const authArtifactPath = `.stackwright/artifacts/${PHASE_ARTIFACT["auth"]}`;
4377
+ const authShapeResult = validateAuthConfig(artifact, authArtifactPath);
4378
+ if (!authShapeResult.valid) {
4379
+ const result = {
4380
+ valid: false,
4381
+ phase,
4382
+ violation: "schema-mismatch",
4383
+ retryPrompt: authShapeResult.retryPrompt
4384
+ };
4385
+ return { text: JSON.stringify(result), isError: false };
4386
+ }
4387
+ }
4388
+ if (phase === "auth") {
4389
+ const rawUncovered = artifact["uncoveredSlugs"];
4390
+ if (Array.isArray(rawUncovered) && rawUncovered.length > 0) {
4391
+ const otterFromSource = (source) => {
4392
+ if (source === "geo-manifest.json") return "geo-otter";
4393
+ if (source === "dashboard-manifest.json") return "dashboard-otter";
4394
+ if (source === "pages-manifest.json") return "page-otter";
4395
+ return source || "(unknown)";
4396
+ };
4397
+ const slugLines = rawUncovered.map((entry) => {
4398
+ const e = entry ?? {};
4399
+ const source = typeof e["source"] === "string" ? e["source"] : "(unknown)";
4400
+ const slug = typeof e["slug"] === "string" ? e["slug"] : "(unknown)";
4401
+ return ` ${source} \u2192 ${slug} (emitted by ${otterFromSource(source)})`;
4402
+ });
4403
+ const retryPrompt = [
4404
+ "Auth-config cannot be finalized: the following page slugs have no auth declaration in their emitting otter's manifest:",
4405
+ "",
4406
+ ...slugLines,
4407
+ "",
4408
+ "This is a HARD build-gate failure \u2014 auth-otter cannot self-correct because the root cause is upstream.",
4409
+ "",
4410
+ "Fix: update the offending emitter otter to declare `authRequired: true` + `requiredRoles: [...]`",
4411
+ "(and optionally `tier`, `authRationale`) in the manifest entry for each listed slug.",
4412
+ "",
4413
+ "For map pages: geo-otter (see swp-1114).",
4414
+ "For dashboard pages: dashboard-otter (see swp-52lk).",
4415
+ "For page pages: page-otter (already does this \u2014 check for regressions).",
4416
+ "For workflow pages: form-wizard-otter emits routes but auth is derived from the pages-manifest",
4417
+ "workflow-wrapper entry \u2014 check page-otter didn't skip a workflow-wrapper.",
4418
+ "",
4419
+ "After updating the offending manifest, re-run the pipeline from the affected phase."
4420
+ ].join("\n");
4421
+ const result = {
4422
+ valid: false,
4423
+ phase,
4424
+ violation: "uncovered-slugs",
4425
+ retryPrompt
4426
+ };
4427
+ return { text: JSON.stringify(result), isError: false };
4428
+ }
4429
+ }
4143
4430
  const PHASE_ZOD_VALIDATORS = {
4144
4431
  workflow: (artifact2) => {
4145
4432
  const workflowData = artifact2["workflow"];
@@ -4190,33 +4477,60 @@ function _validateArtifactInner(input) {
4190
4477
  return { success: true };
4191
4478
  },
4192
4479
  // swp-4071 — opt-in cross-reference validator for the services phase.
4193
- // If workflow-config exists and declares serviceHooks, every hook ref must resolve
4194
- // to a flow or state-machine in this services-config artifact.
4480
+ // Collects serviceHooks from ALL workflow-config-*.json artifacts (swp-x8sm fan-out)
4481
+ // plus the legacy single-file workflow-config.json (backward compat).
4482
+ // If any workflow declared serviceHooks, every ref must resolve to a flow or
4483
+ // state-machine in this services-config artifact.
4195
4484
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
4196
4485
  services: (artifact2) => {
4197
- const workflowArtifactPath = join6(cwd, ".stackwright", "artifacts", "workflow-config.json");
4198
- if (!existsSync6(workflowArtifactPath)) {
4199
- return { success: true };
4486
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
4487
+ const workflowArtifactFiles = [];
4488
+ if (existsSync7(artifactsDir)) {
4489
+ try {
4490
+ const entries = readdirSync5(artifactsDir);
4491
+ for (const entry of entries) {
4492
+ if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
4493
+ workflowArtifactFiles.push(join7(artifactsDir, entry));
4494
+ }
4495
+ }
4496
+ } catch {
4497
+ }
4200
4498
  }
4201
- let workflowArtifact;
4202
- try {
4203
- workflowArtifact = JSON.parse(readFileSync6(workflowArtifactPath, "utf-8"));
4204
- } catch {
4499
+ const legacyPath = join7(artifactsDir, "workflow-config.json");
4500
+ if (existsSync7(legacyPath)) {
4501
+ workflowArtifactFiles.push(legacyPath);
4502
+ }
4503
+ if (workflowArtifactFiles.length === 0) {
4205
4504
  return { success: true };
4206
4505
  }
4207
- const declaredHooks = workflowArtifact.serviceHooks ?? [];
4208
- if (declaredHooks.length === 0) {
4506
+ const declaredHooks = [];
4507
+ for (const filePath of workflowArtifactFiles) {
4508
+ try {
4509
+ const parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
4510
+ if (Array.isArray(parsed.serviceHooks)) {
4511
+ declaredHooks.push(...parsed.serviceHooks);
4512
+ }
4513
+ } catch {
4514
+ }
4515
+ }
4516
+ const seenRefs = /* @__PURE__ */ new Set();
4517
+ const uniqueHooks = declaredHooks.filter((h) => {
4518
+ if (seenRefs.has(h.ref)) return false;
4519
+ seenRefs.add(h.ref);
4520
+ return true;
4521
+ });
4522
+ if (uniqueHooks.length === 0) {
4209
4523
  return { success: true };
4210
4524
  }
4211
4525
  const flows = artifact2["flows"] ?? [];
4212
4526
  const workflows = artifact2["workflows"] ?? [];
4213
4527
  const flowNames = /* @__PURE__ */ new Set([...flows.map((f) => f.name), ...workflows.map((w) => w.name)]);
4214
- const unresolved = declaredHooks.filter((h) => !flowNames.has(h.ref));
4528
+ const unresolved = uniqueHooks.filter((h) => !flowNames.has(h.ref));
4215
4529
  if (unresolved.length > 0) {
4216
4530
  return {
4217
4531
  success: false,
4218
4532
  error: {
4219
- message: `Workflow declared ${declaredHooks.length} service hook(s) but ${unresolved.length} unresolved by services-config: ` + unresolved.map((h) => `${h.ref} (${h.kind ?? "unknown"}: ${h.purpose ?? "no purpose"})`).join("; ") + ". Either add matching flows to services-config OR remove the hooks from the workflow YAML."
4533
+ message: `Workflow(s) declared ${uniqueHooks.length} service hook(s) but ${unresolved.length} unresolved by services-config: ` + unresolved.map((h) => `${h.ref} (${h.kind ?? "unknown"}: ${h.purpose ?? "no purpose"})`).join("; ") + ". Either add matching flows to services-config OR remove the hooks from the workflow YAML."
4220
4534
  }
4221
4535
  };
4222
4536
  }
@@ -4282,11 +4596,55 @@ function _validateArtifactInner(input) {
4282
4596
  return { text: JSON.stringify(result), isError: false };
4283
4597
  }
4284
4598
  }
4599
+ if (phase === "api") {
4600
+ const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
4601
+ if (!apiAuthResult.valid) {
4602
+ const result = {
4603
+ valid: false,
4604
+ phase,
4605
+ violation: "schema-mismatch",
4606
+ retryPrompt: apiAuthResult.retryPrompt
4607
+ };
4608
+ return { text: JSON.stringify(result), isError: false };
4609
+ }
4610
+ }
4611
+ if (phase === "workflow" && input.workflowName !== void 0) {
4612
+ if (!isValidSlug(input.workflowName)) {
4613
+ const result = {
4614
+ valid: false,
4615
+ phase,
4616
+ violation: "schema-mismatch",
4617
+ retryPrompt: `workflowName "${input.workflowName}" is not a valid slug. Use lowercase kebab-case (a-z, 0-9, hyphens only, no spaces or path separators). Example: "patient-evacuation", "ambulance-crew-dispatch".`
4618
+ };
4619
+ return { text: JSON.stringify(result), isError: false };
4620
+ }
4621
+ }
4622
+ let authOrphanWarning;
4623
+ if (phase === "auth") {
4624
+ const authCfg = artifact["authConfig"];
4625
+ const routes = authCfg?.["protectedRoutes"];
4626
+ if (Array.isArray(routes) && routes.length > 0) {
4627
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
4628
+ const knownSlugs = extractManifestSlugs(artifactsDir);
4629
+ const orphanPatterns = [];
4630
+ for (const route of routes) {
4631
+ const pattern = typeof route === "string" ? route : route.pattern ?? "";
4632
+ if (!pattern) continue;
4633
+ const baseSlug = extractPatternBaseSlug(pattern);
4634
+ if (baseSlug && !knownSlugs.has(baseSlug)) {
4635
+ orphanPatterns.push(pattern);
4636
+ }
4637
+ }
4638
+ if (orphanPatterns.length > 0) {
4639
+ authOrphanWarning = `The following protectedRoute patterns don't match any known page slug: ${orphanPatterns.map((p) => `"${p}"`).join(", ")}. If these are intentional cross-cutting rules (e.g., /admin/:path*, /audit/:path*), keep them; otherwise remove.`;
4640
+ }
4641
+ }
4642
+ }
4285
4643
  try {
4286
- const artifactsDir = join6(cwd, ".stackwright", "artifacts");
4644
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
4287
4645
  mkdirSync4(artifactsDir, { recursive: true });
4288
- const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : PHASE_ARTIFACT[phase];
4289
- const artifactPath = join6(artifactsDir, artifactFile);
4646
+ const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
4647
+ const artifactPath = join7(artifactsDir, artifactFile);
4290
4648
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
4291
4649
  const artifactBytes = Buffer.from(serialized, "utf-8");
4292
4650
  safeWriteSync(artifactPath, serialized);
@@ -4309,7 +4667,8 @@ function _validateArtifactInner(input) {
4309
4667
  valid: true,
4310
4668
  phase,
4311
4669
  artifactPath,
4312
- summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`
4670
+ summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`,
4671
+ ...authOrphanWarning ? { retryPrompt: authOrphanWarning } : {}
4313
4672
  };
4314
4673
  return { text: JSON.stringify(result), isError: false };
4315
4674
  } catch (err) {
@@ -4505,9 +4864,9 @@ function registerPipelineTools(server2) {
4505
4864
  isError: true
4506
4865
  };
4507
4866
  }
4508
- const questionsDir = join6(process.cwd(), ".stackwright", "questions");
4867
+ const questionsDir = join7(process.cwd(), ".stackwright", "questions");
4509
4868
  mkdirSync4(questionsDir, { recursive: true });
4510
- const outPath = join6(questionsDir, `${phase}.json`);
4869
+ const outPath = join7(questionsDir, `${phase}.json`);
4511
4870
  writeFileSync4(
4512
4871
  outPath,
4513
4872
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
@@ -4547,15 +4906,19 @@ function registerPipelineTools(server2) {
4547
4906
  ),
4548
4907
  integrationName: z13.string().optional().describe(
4549
4908
  'When phase="api": write to api-config-<integrationName>.json instead of api-config.json. Enables concurrent per-spec fan-out without collision (swp-530y).'
4909
+ ),
4910
+ workflowName: z13.string().optional().describe(
4911
+ `When phase="workflow": write to workflow-config-<workflowName>.json instead of workflow-config.json. Enables concurrent per-workflow fan-out without collision (swp-x8sm). Must be a kebab-case slug matching the workflow's id field.`
4550
4912
  )
4551
4913
  },
4552
- async ({ phase, responseText, artifact, integrationName }) => {
4914
+ async ({ phase, responseText, artifact, integrationName, workflowName }) => {
4553
4915
  if (artifact) {
4554
4916
  return res(
4555
4917
  handleValidateArtifact({
4556
4918
  phase,
4557
4919
  artifact,
4558
- ...integrationName ? { integrationName } : {}
4920
+ ...integrationName ? { integrationName } : {},
4921
+ ...workflowName ? { workflowName } : {}
4559
4922
  })
4560
4923
  );
4561
4924
  }
@@ -4563,7 +4926,8 @@ function registerPipelineTools(server2) {
4563
4926
  handleValidateArtifact({
4564
4927
  phase,
4565
4928
  responseText: responseText ?? "",
4566
- ...integrationName ? { integrationName } : {}
4929
+ ...integrationName ? { integrationName } : {},
4930
+ ...workflowName ? { workflowName } : {}
4567
4931
  })
4568
4932
  );
4569
4933
  }
@@ -4595,8 +4959,8 @@ function registerPipelineTools(server2) {
4595
4959
 
4596
4960
  // src/tools/safe-write.ts
4597
4961
  import { z as z14 } from "zod";
4598
- import { writeFileSync as writeFileSync5, readFileSync as readFileSync7, existsSync as existsSync7, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
4599
- import { normalize, isAbsolute, dirname, join as join7 } from "path";
4962
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
4963
+ import { normalize, isAbsolute, dirname, join as join8 } from "path";
4600
4964
  import { emit as emit2 } from "@stackwright-pro/telemetry";
4601
4965
  var OTTER_WRITE_ALLOWLISTS = {
4602
4966
  "stackwright-pro-designer-otter": [
@@ -4608,6 +4972,11 @@ var OTTER_WRITE_ALLOWLISTS = {
4608
4972
  prefix: "stackwright.theme.",
4609
4973
  suffix: ".yml",
4610
4974
  description: "Theme config YAML (merged at build time)"
4975
+ },
4976
+ {
4977
+ prefix: "stackwright.yml",
4978
+ suffix: "",
4979
+ description: "Stackwright config (themeName + customTheme writeback)"
4611
4980
  }
4612
4981
  ],
4613
4982
  "stackwright-pro-auth-otter": [
@@ -4662,6 +5031,17 @@ var OTTER_WRITE_ALLOWLISTS = {
4662
5031
  prefix: ".stackwright/artifacts/",
4663
5032
  suffix: ".json",
4664
5033
  description: "Scaffold manifest artifact"
5034
+ },
5035
+ // swp-89w5: scaffold-otter initialises mock backend wiring via stackwright_pro_emit_env_local
5036
+ {
5037
+ prefix: ".env.local",
5038
+ suffix: "",
5039
+ description: "Dotenv runtime config (mock backend wiring: NEXT_PUBLIC_MOCK_BACKEND + per-integration mock keys)"
5040
+ },
5041
+ {
5042
+ prefix: ".env.local.example",
5043
+ suffix: "",
5044
+ description: "Dotenv template (schema for real production values)"
4665
5045
  }
4666
5046
  ],
4667
5047
  "stackwright-pro-api-otter": [
@@ -4778,21 +5158,21 @@ function extractContentTypes(yamlContent) {
4778
5158
  return types.length > 0 ? types : ["unknown"];
4779
5159
  }
4780
5160
  function readPageRegistry(cwd) {
4781
- const regPath = join7(cwd, PAGE_REGISTRY_FILE);
4782
- if (!existsSync7(regPath)) return {};
5161
+ const regPath = join8(cwd, PAGE_REGISTRY_FILE);
5162
+ if (!existsSync8(regPath)) return {};
4783
5163
  try {
4784
5164
  const stat = lstatSync7(regPath);
4785
5165
  if (stat.isSymbolicLink()) return {};
4786
- return JSON.parse(readFileSync7(regPath, "utf-8"));
5166
+ return JSON.parse(readFileSync8(regPath, "utf-8"));
4787
5167
  } catch {
4788
5168
  return {};
4789
5169
  }
4790
5170
  }
4791
5171
  function writePageRegistry(cwd, registry) {
4792
- const dir = join7(cwd, ".stackwright");
5172
+ const dir = join8(cwd, ".stackwright");
4793
5173
  mkdirSync5(dir, { recursive: true });
4794
- const regPath = join7(cwd, PAGE_REGISTRY_FILE);
4795
- if (existsSync7(regPath)) {
5174
+ const regPath = join8(cwd, PAGE_REGISTRY_FILE);
5175
+ if (existsSync8(regPath)) {
4796
5176
  const stat = lstatSync7(regPath);
4797
5177
  if (stat.isSymbolicLink()) {
4798
5178
  throw new Error("Refusing to write page registry through symlink");
@@ -4964,8 +5344,8 @@ function _safeWriteInner(input) {
4964
5344
  return { text: JSON.stringify(result), isError: true };
4965
5345
  }
4966
5346
  const normalized = normalize(filePath);
4967
- const fullPath = join7(cwd, normalized);
4968
- if (existsSync7(fullPath)) {
5347
+ const fullPath = join8(cwd, normalized);
5348
+ if (existsSync8(fullPath)) {
4969
5349
  try {
4970
5350
  const stat = lstatSync7(fullPath);
4971
5351
  if (stat.isSymbolicLink()) {
@@ -5095,8 +5475,8 @@ function registerSafeWriteTools(server2) {
5095
5475
 
5096
5476
  // src/tools/auth.ts
5097
5477
  import { z as z15 } from "zod";
5098
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
5099
- import { join as join8 } from "path";
5478
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
5479
+ import { join as join9 } from "path";
5100
5480
  function buildHierarchy(roles) {
5101
5481
  const h = {};
5102
5482
  for (let i = 0; i < roles.length - 1; i++) {
@@ -5123,9 +5503,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
5123
5503
  }
5124
5504
  function detectNextMajorVersion(cwd) {
5125
5505
  try {
5126
- const pkgPath = join8(cwd, "package.json");
5127
- if (!existsSync8(pkgPath)) return null;
5128
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
5506
+ const pkgPath = join9(cwd, "package.json");
5507
+ if (!existsSync9(pkgPath)) return null;
5508
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
5129
5509
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
5130
5510
  if (!nextVersion) return null;
5131
5511
  const match = nextVersion.match(/(\d+)/);
@@ -5789,7 +6169,7 @@ async function configureAuthHandler(params, cwd) {
5789
6169
  auditRetentionDays,
5790
6170
  normalizedRoutes
5791
6171
  );
5792
- writeFileSync6(join8(cwd, conventionFile), authFileContent, "utf8");
6172
+ writeFileSync6(join9(cwd, conventionFile), authFileContent, "utf8");
5793
6173
  filesWritten.push(conventionFile);
5794
6174
  } catch (err) {
5795
6175
  const msg = err instanceof Error ? err.message : String(err);
@@ -5809,9 +6189,9 @@ async function configureAuthHandler(params, cwd) {
5809
6189
  if (!devOnly) {
5810
6190
  try {
5811
6191
  const envBlock = generateEnvBlock(effectiveMethod, params);
5812
- const envPath = join8(cwd, ".env.example");
5813
- if (existsSync8(envPath)) {
5814
- const existing = readFileSync8(envPath, "utf8");
6192
+ const envPath = join9(cwd, ".env.example");
6193
+ if (existsSync9(envPath)) {
6194
+ const existing = readFileSync9(envPath, "utf8");
5815
6195
  writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5816
6196
  } else {
5817
6197
  writeFileSync6(envPath, envBlock, "utf8");
@@ -5832,10 +6212,10 @@ async function configureAuthHandler(params, cwd) {
5832
6212
  }
5833
6213
  if (devOnly) {
5834
6214
  try {
5835
- const mockAuthDir = join8(cwd, "lib");
6215
+ const mockAuthDir = join9(cwd, "lib");
5836
6216
  mkdirSync6(mockAuthDir, { recursive: true });
5837
6217
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
5838
- writeFileSync6(join8(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
6218
+ writeFileSync6(join9(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5839
6219
  filesWritten.push("lib/mock-auth.ts");
5840
6220
  } catch (err) {
5841
6221
  const msg = err instanceof Error ? err.message : String(err);
@@ -5853,9 +6233,9 @@ async function configureAuthHandler(params, cwd) {
5853
6233
  };
5854
6234
  }
5855
6235
  try {
5856
- const pkgPath = join8(cwd, "package.json");
5857
- if (existsSync8(pkgPath)) {
5858
- const existingPkg = readFileSync8(pkgPath, "utf8");
6236
+ const pkgPath = join9(cwd, "package.json");
6237
+ if (existsSync9(pkgPath)) {
6238
+ const existingPkg = readFileSync9(pkgPath, "utf8");
5859
6239
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
5860
6240
  writeFileSync6(pkgPath, updatedPkg, "utf8");
5861
6241
  filesWritten.push("package.json");
@@ -5898,7 +6278,7 @@ async function configureAuthHandler(params, cwd) {
5898
6278
  normalizedRoutes,
5899
6279
  useProxy
5900
6280
  );
5901
- writeFileSync6(join8(cwd, "stackwright.auth.yml"), authYaml, "utf8");
6281
+ writeFileSync6(join9(cwd, "stackwright.auth.yml"), authYaml, "utf8");
5902
6282
  filesWritten.push("stackwright.auth.yml");
5903
6283
  } catch (err) {
5904
6284
  const msg = err instanceof Error ? err.message : String(err);
@@ -6011,8 +6391,8 @@ function registerAuthTools(server2) {
6011
6391
 
6012
6392
  // src/integrity.ts
6013
6393
  import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
6014
- import { readFileSync as readFileSync9, readdirSync as readdirSync4, lstatSync as lstatSync8 } from "fs";
6015
- import { join as join9, basename } from "path";
6394
+ import { readFileSync as readFileSync10, readdirSync as readdirSync6, lstatSync as lstatSync8 } from "fs";
6395
+ import { join as join10, basename } from "path";
6016
6396
  var _checksums = /* @__PURE__ */ new Map([
6017
6397
  [
6018
6398
  "stackwright-pro-api-otter.json",
@@ -6020,15 +6400,15 @@ var _checksums = /* @__PURE__ */ new Map([
6020
6400
  ],
6021
6401
  [
6022
6402
  "stackwright-pro-auth-otter.json",
6023
- "1a21d9f23e250f23291124307e5a2a32af5a716566319a3290372dae3d02e637"
6403
+ "69fa03c5532ef617c86fe7af41e8174c168d282baf602b1b269a7e933ea86b7c"
6024
6404
  ],
6025
6405
  [
6026
6406
  "stackwright-pro-dashboard-otter.json",
6027
- "4a9ac39e929866ae3c929aa5bcbba3d9483a1c251c35f26f267f3e7433506f0c"
6407
+ "f55081bf4d6545e5435128919f6f9233956ba37671fc9f85300f240ad2497ab6"
6028
6408
  ],
6029
6409
  [
6030
6410
  "stackwright-pro-data-otter.json",
6031
- "1ad3ed99bbe7b550f654c679a8c0ea3363b2c52031042cd177c6e5f9e1c50a21"
6411
+ "be4e3d6f530ae2d6b79d6412e8d2d6defb3caffddedf5be4f928976a72206074"
6032
6412
  ],
6033
6413
  [
6034
6414
  "stackwright-pro-designer-otter.json",
@@ -6040,19 +6420,19 @@ var _checksums = /* @__PURE__ */ new Map([
6040
6420
  ],
6041
6421
  [
6042
6422
  "stackwright-pro-foreman-otter.json",
6043
- "9d68b7b20af7a8a1668e2a693b4a2b05cf37acce1e9f6298a0966c97a3417e9f"
6423
+ "88c4101154d44a7c5470ded13562402a39323ee570ed3a90a73b8d85d7fd2817"
6044
6424
  ],
6045
6425
  [
6046
6426
  "stackwright-pro-form-wizard-otter.json",
6047
- "7f6f94192f26face57dc8b2e22e0a49d23ceeeb356ca4ebde05492f1b25e3c47"
6427
+ "cb350297a0068ead2424e703e8c775d91c0b87249ea6904f43b903baf8a84b79"
6048
6428
  ],
6049
6429
  [
6050
6430
  "stackwright-pro-geo-otter.json",
6051
- "fc3d18e02a6147d95d3dd9093ce74c0e8fffaecc2f9ecdbd19c163a80129d264"
6431
+ "3032fbd27de36c0cdc0e19e46a32d771208d8e86714482f5a368d46342c8317a"
6052
6432
  ],
6053
6433
  [
6054
6434
  "stackwright-pro-page-otter.json",
6055
- "879e8ff4aa645bac2c63dc962cad1a549876983c257aec756e95293c3b22ce7e"
6435
+ "cc0db24b00f0130f8b4ec8d385c73ea1bcf5d57ba45aca6b986bcbd5da328930"
6056
6436
  ],
6057
6437
  [
6058
6438
  "stackwright-pro-polish-otter.json",
@@ -6064,7 +6444,7 @@ var _checksums = /* @__PURE__ */ new Map([
6064
6444
  ],
6065
6445
  [
6066
6446
  "stackwright-pro-scaffold-otter.json",
6067
- "92930c732547c90a19e89ee0e25b5f037b7366f770941f0a01e148ff240a1b6d"
6447
+ "90272bab51bd8ed9e25c0fb6481cdd4252386850f64192edfe1e2eff44166e2d"
6068
6448
  ],
6069
6449
  [
6070
6450
  "stackwright-pro-theme-otter.json",
@@ -6072,7 +6452,7 @@ var _checksums = /* @__PURE__ */ new Map([
6072
6452
  ],
6073
6453
  [
6074
6454
  "stackwright-services-otter.json",
6075
- "2a50ceb3fad166251d0ad8709a34a32118645713e0e34628165d16dced1d5c93"
6455
+ "4351348806aeb98404a2bacb044209ca411be5e35e2f2c3d03eb4d28a07fde78"
6076
6456
  ]
6077
6457
  ]);
6078
6458
  Object.freeze(_checksums);
@@ -6120,7 +6500,7 @@ function verifyOtterFile(filePath) {
6120
6500
  }
6121
6501
  let raw;
6122
6502
  try {
6123
- raw = readFileSync9(filePath);
6503
+ raw = readFileSync10(filePath);
6124
6504
  } catch (err) {
6125
6505
  const msg = err instanceof Error ? err.message : String(err);
6126
6506
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -6170,7 +6550,7 @@ function verifyAllOtters(otterDir) {
6170
6550
  const unknown = [];
6171
6551
  let entries;
6172
6552
  try {
6173
- entries = readdirSync4(otterDir);
6553
+ entries = readdirSync6(otterDir);
6174
6554
  } catch (err) {
6175
6555
  const msg = err instanceof Error ? err.message : String(err);
6176
6556
  return {
@@ -6181,7 +6561,7 @@ function verifyAllOtters(otterDir) {
6181
6561
  }
6182
6562
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
6183
6563
  for (const filename of otterFiles) {
6184
- const filePath = join9(otterDir, filename);
6564
+ const filePath = join10(otterDir, filename);
6185
6565
  try {
6186
6566
  if (lstatSync8(filePath).isSymbolicLink()) {
6187
6567
  failed.push({ filename, error: "Skipped: symlink" });
@@ -6209,7 +6589,7 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
6209
6589
  function resolveOtterDir() {
6210
6590
  const cwd = process.cwd();
6211
6591
  for (const relative2 of DEFAULT_SEARCH_PATHS) {
6212
- const candidate = join9(cwd, relative2);
6592
+ const candidate = join10(cwd, relative2);
6213
6593
  try {
6214
6594
  lstatSync8(candidate);
6215
6595
  return candidate;
@@ -6290,13 +6670,13 @@ function registerIntegrityTools(server2) {
6290
6670
 
6291
6671
  // src/tools/domain.ts
6292
6672
  import { z as z16 } from "zod";
6293
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
6294
- import { join as join10 } from "path";
6673
+ import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
6674
+ import { join as join11 } from "path";
6295
6675
  function handleListCollections(input) {
6296
6676
  const cwd = input._cwd ?? process.cwd();
6297
6677
  const sources = [
6298
6678
  {
6299
- path: join10(cwd, ".stackwright", "artifacts", "data-config.json"),
6679
+ path: join11(cwd, ".stackwright", "artifacts", "data-config.json"),
6300
6680
  source: "data-config.json",
6301
6681
  parse: (raw) => {
6302
6682
  const parsed = JSON.parse(raw);
@@ -6307,15 +6687,15 @@ function handleListCollections(input) {
6307
6687
  }
6308
6688
  },
6309
6689
  {
6310
- path: join10(cwd, "stackwright.yml"),
6690
+ path: join11(cwd, "stackwright.yml"),
6311
6691
  source: "stackwright.yml",
6312
6692
  parse: extractCollectionsFromYaml
6313
6693
  }
6314
6694
  ];
6315
6695
  for (const { path: path5, source, parse } of sources) {
6316
- if (!existsSync9(path5)) continue;
6696
+ if (!existsSync10(path5)) continue;
6317
6697
  try {
6318
- const collections = parse(readFileSync10(path5, "utf8"));
6698
+ const collections = parse(readFileSync11(path5, "utf8"));
6319
6699
  return {
6320
6700
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
6321
6701
  isError: false
@@ -6466,8 +6846,8 @@ function handleValidateWorkflow(input) {
6466
6846
  if (input.workflow && Object.keys(input.workflow).length > 0) {
6467
6847
  raw = input.workflow;
6468
6848
  } else {
6469
- const artifactPath = join10(cwd, ".stackwright", "artifacts", "workflow-config.json");
6470
- if (!existsSync9(artifactPath)) {
6849
+ const artifactPath = join11(cwd, ".stackwright", "artifacts", "workflow-config.json");
6850
+ if (!existsSync10(artifactPath)) {
6471
6851
  return fail([
6472
6852
  {
6473
6853
  code: "NO_WORKFLOW",
@@ -6476,7 +6856,7 @@ function handleValidateWorkflow(input) {
6476
6856
  ]);
6477
6857
  }
6478
6858
  try {
6479
- raw = JSON.parse(readFileSync10(artifactPath, "utf8"));
6859
+ raw = JSON.parse(readFileSync11(artifactPath, "utf8"));
6480
6860
  } catch (err) {
6481
6861
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
6482
6862
  }
@@ -6914,16 +7294,16 @@ function registerProPageTools(server2) {
6914
7294
 
6915
7295
  // src/tools/scaffold-cleanup.ts
6916
7296
  import {
6917
- existsSync as existsSync11,
7297
+ existsSync as existsSync12,
6918
7298
  lstatSync as lstatSync9,
6919
7299
  unlinkSync as unlinkSync2,
6920
- readFileSync as readFileSync11,
7300
+ readFileSync as readFileSync12,
6921
7301
  writeFileSync as writeFileSync8,
6922
- readdirSync as readdirSync5,
7302
+ readdirSync as readdirSync7,
6923
7303
  rmdirSync,
6924
7304
  mkdirSync as mkdirSync8
6925
7305
  } from "fs";
6926
- import { join as join12, dirname as dirname3 } from "path";
7306
+ import { join as join13, dirname as dirname3 } from "path";
6927
7307
  var SCAFFOLD_FILES_TO_DELETE = [
6928
7308
  "content/posts/getting-started.yaml",
6929
7309
  "content/posts/hello-world.yaml"
@@ -6948,12 +7328,12 @@ var FALLBACK_COLORS = {
6948
7328
  mutedForeground: "#737373"
6949
7329
  };
6950
7330
  function readThemeColors(cwd) {
6951
- const tokenPath = join12(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6952
- if (!existsSync11(tokenPath)) return { ...FALLBACK_COLORS };
7331
+ const tokenPath = join13(cwd, ".stackwright", "artifacts", "theme-tokens.json");
7332
+ if (!existsSync12(tokenPath)) return { ...FALLBACK_COLORS };
6953
7333
  const stat = lstatSync9(tokenPath);
6954
7334
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
6955
7335
  try {
6956
- const raw = JSON.parse(readFileSync11(tokenPath, "utf-8"));
7336
+ const raw = JSON.parse(readFileSync12(tokenPath, "utf-8"));
6957
7337
  const css = raw?.cssVariables ?? {};
6958
7338
  const toHsl = (hslValue) => {
6959
7339
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -7013,15 +7393,15 @@ function generateNotFoundPage(colors) {
7013
7393
  `;
7014
7394
  }
7015
7395
  function isSafePath(fullPath) {
7016
- if (!existsSync11(fullPath)) return true;
7396
+ if (!existsSync12(fullPath)) return true;
7017
7397
  const stat = lstatSync9(fullPath);
7018
7398
  return !stat.isSymbolicLink();
7019
7399
  }
7020
7400
  function isDirEmpty(dirPath) {
7021
- if (!existsSync11(dirPath)) return false;
7401
+ if (!existsSync12(dirPath)) return false;
7022
7402
  const stat = lstatSync9(dirPath);
7023
7403
  if (!stat.isDirectory()) return false;
7024
- return readdirSync5(dirPath).length === 0;
7404
+ return readdirSync7(dirPath).length === 0;
7025
7405
  }
7026
7406
  function handleCleanupScaffold(_cwd) {
7027
7407
  const cwd = _cwd ?? process.cwd();
@@ -7034,8 +7414,8 @@ function handleCleanupScaffold(_cwd) {
7034
7414
  cleanupWarnings: []
7035
7415
  };
7036
7416
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
7037
- const fullPath = join12(cwd, relPath);
7038
- if (!existsSync11(fullPath)) {
7417
+ const fullPath = join13(cwd, relPath);
7418
+ if (!existsSync12(fullPath)) {
7039
7419
  result.skipped.push(relPath);
7040
7420
  continue;
7041
7421
  }
@@ -7052,13 +7432,13 @@ function handleCleanupScaffold(_cwd) {
7052
7432
  }
7053
7433
  {
7054
7434
  const relPath = "pages/getting-started/content.yml";
7055
- const fullPath = join12(cwd, relPath);
7056
- if (!existsSync11(fullPath)) {
7435
+ const fullPath = join13(cwd, relPath);
7436
+ if (!existsSync12(fullPath)) {
7057
7437
  result.skipped.push(relPath);
7058
7438
  } else if (!isSafePath(fullPath)) {
7059
7439
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7060
7440
  } else {
7061
- const content = readFileSync11(fullPath, "utf-8");
7441
+ const content = readFileSync12(fullPath, "utf-8");
7062
7442
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
7063
7443
  (marker) => content.includes(marker)
7064
7444
  );
@@ -7078,16 +7458,16 @@ function handleCleanupScaffold(_cwd) {
7078
7458
  }
7079
7459
  {
7080
7460
  const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
7081
- const fullPath = join12(cwd, relPath);
7082
- const postsDir = join12(cwd, "content/posts");
7083
- if (!existsSync11(fullPath)) {
7461
+ const fullPath = join13(cwd, relPath);
7462
+ const postsDir = join13(cwd, "content/posts");
7463
+ if (!existsSync12(fullPath)) {
7084
7464
  result.skipped.push(relPath);
7085
7465
  } else if (!isSafePath(fullPath)) {
7086
7466
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7087
- } else if (!existsSync11(postsDir)) {
7467
+ } else if (!existsSync12(postsDir)) {
7088
7468
  result.skipped.push(relPath);
7089
7469
  } else {
7090
- const userPostFiles = readdirSync5(postsDir).filter(
7470
+ const userPostFiles = readdirSync7(postsDir).filter(
7091
7471
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
7092
7472
  );
7093
7473
  if (userPostFiles.length === 0) {
@@ -7105,8 +7485,8 @@ function handleCleanupScaffold(_cwd) {
7105
7485
  }
7106
7486
  }
7107
7487
  for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
7108
- const fullDir = join12(cwd, relDir);
7109
- if (!existsSync11(fullDir)) continue;
7488
+ const fullDir = join13(cwd, relDir);
7489
+ if (!existsSync12(fullDir)) continue;
7110
7490
  if (!isSafePath(fullDir)) {
7111
7491
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
7112
7492
  continue;
@@ -7121,8 +7501,8 @@ function handleCleanupScaffold(_cwd) {
7121
7501
  }
7122
7502
  }
7123
7503
  {
7124
- const fullPath = join12(cwd, NOT_FOUND_PATH);
7125
- if (!existsSync11(fullPath)) {
7504
+ const fullPath = join13(cwd, NOT_FOUND_PATH);
7505
+ if (!existsSync12(fullPath)) {
7126
7506
  result.skipped.push(NOT_FOUND_PATH);
7127
7507
  } else if (!isSafePath(fullPath)) {
7128
7508
  result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
@@ -7643,14 +8023,14 @@ ${results.join("\n")}`;
7643
8023
 
7644
8024
  // src/tools/strip-legacy-integrations.ts
7645
8025
  import { z as z21 } from "zod";
7646
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
7647
- import { join as join13 } from "path";
8026
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
8027
+ import { join as join14 } from "path";
7648
8028
  function handleStripLegacyIntegrations(input) {
7649
8029
  const { projectRoot } = input;
7650
- const ymlPath = join13(projectRoot, "stackwright.yml");
7651
- const yamlPath = join13(projectRoot, "stackwright.yaml");
7652
- if (!existsSync12(ymlPath)) {
7653
- if (existsSync12(yamlPath)) {
8030
+ const ymlPath = join14(projectRoot, "stackwright.yml");
8031
+ const yamlPath = join14(projectRoot, "stackwright.yaml");
8032
+ if (!existsSync13(ymlPath)) {
8033
+ if (existsSync13(yamlPath)) {
7654
8034
  return {
7655
8035
  changed: false,
7656
8036
  removedEntries: 0,
@@ -7663,7 +8043,7 @@ function handleStripLegacyIntegrations(input) {
7663
8043
  message: "stackwright.yml not found \u2014 no-op."
7664
8044
  };
7665
8045
  }
7666
- const raw = readFileSync12(ymlPath, "utf-8");
8046
+ const raw = readFileSync13(ymlPath, "utf-8");
7667
8047
  const isCRLF = raw.includes("\r\n");
7668
8048
  const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
7669
8049
  const lines = content.split("\n");
@@ -7718,15 +8098,15 @@ function registerStripLegacyIntegrationsTool(server2) {
7718
8098
 
7719
8099
  // src/tools/emitters.ts
7720
8100
  import { z as z22 } from "zod";
7721
- import { readFileSync as readFileSync13, existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
7722
- import { join as join14 } from "path";
8101
+ import { readFileSync as readFileSync14, existsSync as existsSync14, readdirSync as readdirSync8 } from "fs";
8102
+ import { join as join15 } from "path";
7723
8103
  import yaml from "js-yaml";
7724
8104
  import { emitEnvLocal, emitProvidersFile, emitMockPorts } from "@stackwright-pro/emitters";
7725
8105
  function readIntegrations(cwd) {
7726
- const filePath = join14(cwd, "stackwright.integrations.yml");
7727
- if (!existsSync13(filePath)) return [];
8106
+ const filePath = join15(cwd, "stackwright.integrations.yml");
8107
+ if (!existsSync14(filePath)) return [];
7728
8108
  try {
7729
- const raw = yaml.load(readFileSync13(filePath, "utf-8"));
8109
+ const raw = yaml.load(readFileSync14(filePath, "utf-8"));
7730
8110
  if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
7731
8111
  return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
7732
8112
  name: i.name,
@@ -7737,15 +8117,15 @@ function readIntegrations(cwd) {
7737
8117
  }
7738
8118
  }
7739
8119
  function readServiceTokenRefs(cwd) {
7740
- const servicesDir = join14(cwd, "services");
7741
- if (!existsSync13(servicesDir)) return [];
8120
+ const servicesDir = join15(cwd, "services");
8121
+ if (!existsSync14(servicesDir)) return [];
7742
8122
  try {
7743
- const files = readdirSync6(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
8123
+ const files = readdirSync8(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
7744
8124
  const refs = [];
7745
8125
  for (const file of files) {
7746
8126
  try {
7747
8127
  const raw = yaml.load(
7748
- readFileSync13(join14(servicesDir, file), "utf-8")
8128
+ readFileSync14(join15(servicesDir, file), "utf-8")
7749
8129
  );
7750
8130
  if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
7751
8131
  refs.push({
@@ -7895,8 +8275,8 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
7895
8275
 
7896
8276
  // src/tools/list-specs.ts
7897
8277
  import { z as z23 } from "zod";
7898
- import { readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync2, existsSync as existsSync14 } from "fs";
7899
- import { join as join15, extname, basename as basename2 } from "path";
8278
+ import { readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync15 } from "fs";
8279
+ import { join as join16, extname, basename as basename2 } from "path";
7900
8280
  var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
7901
8281
  var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
7902
8282
  function deriveIntegrationName(fileName) {
@@ -7918,18 +8298,18 @@ function detectFormat(snippet) {
7918
8298
  }
7919
8299
  function handleListSpecs(input) {
7920
8300
  const { projectRoot } = input;
7921
- const specsDir = join15(projectRoot, "specs");
8301
+ const specsDir = join16(projectRoot, "specs");
7922
8302
  const empty = { projectRoot, specs: [], totalCount: 0 };
7923
- if (!existsSync14(specsDir)) return empty;
8303
+ if (!existsSync15(specsDir)) return empty;
7924
8304
  let entries;
7925
8305
  try {
7926
- entries = readdirSync7(specsDir);
8306
+ entries = readdirSync9(specsDir);
7927
8307
  } catch {
7928
8308
  return empty;
7929
8309
  }
7930
8310
  const specs = [];
7931
8311
  for (const entry of entries) {
7932
- const fullPath = join15(specsDir, entry);
8312
+ const fullPath = join16(specsDir, entry);
7933
8313
  let stat;
7934
8314
  try {
7935
8315
  stat = statSync2(fullPath);
@@ -7940,7 +8320,7 @@ function handleListSpecs(input) {
7940
8320
  if (!ALLOWED_EXTS.has(extname(entry).toLowerCase())) continue;
7941
8321
  let snippet = "";
7942
8322
  try {
7943
- snippet = readFileSync14(fullPath, "utf-8").slice(0, 2048);
8323
+ snippet = readFileSync15(fullPath, "utf-8").slice(0, 2048);
7944
8324
  } catch {
7945
8325
  }
7946
8326
  specs.push({
@@ -7972,9 +8352,9 @@ function registerListSpecsTool(server2) {
7972
8352
 
7973
8353
  // src/tools/consolidate-integrations.ts
7974
8354
  import { z as z24 } from "zod";
7975
- import { readdirSync as readdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
8355
+ import { readdirSync as readdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
7976
8356
  import { lockSync as lockSync2 } from "proper-lockfile";
7977
- import { join as join16, basename as basename3 } from "path";
8357
+ import { join as join17, basename as basename3 } from "path";
7978
8358
  import yaml2 from "js-yaml";
7979
8359
  var BASE_MOCK_PORT = 4011;
7980
8360
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
@@ -7987,13 +8367,13 @@ function deriveNameFromFilename(filename) {
7987
8367
  }
7988
8368
  function handleConsolidateIntegrations(input) {
7989
8369
  const { projectRoot } = input;
7990
- const artifactsDir = join16(projectRoot, ".stackwright", "artifacts");
7991
- if (!existsSync15(artifactsDir)) {
8370
+ const artifactsDir = join17(projectRoot, ".stackwright", "artifacts");
8371
+ if (!existsSync16(artifactsDir)) {
7992
8372
  return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
7993
8373
  }
7994
8374
  let entries;
7995
8375
  try {
7996
- entries = readdirSync8(artifactsDir).filter(
8376
+ entries = readdirSync10(artifactsDir).filter(
7997
8377
  (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
7998
8378
  );
7999
8379
  } catch {
@@ -8004,14 +8384,22 @@ function handleConsolidateIntegrations(input) {
8004
8384
  }
8005
8385
  entries.sort();
8006
8386
  const integrationsByName = /* @__PURE__ */ new Map();
8387
+ const skippedIntegrationsList = [];
8007
8388
  entries.forEach((filename, index) => {
8008
8389
  let artifact = {};
8009
8390
  try {
8010
- const raw = readFileSync15(join16(artifactsDir, filename), "utf-8");
8391
+ const raw = readFileSync16(join17(artifactsDir, filename), "utf-8");
8011
8392
  artifact = JSON.parse(raw);
8012
8393
  } catch {
8013
8394
  }
8014
8395
  const name = artifact.integrationName ?? deriveNameFromFilename(filename);
8396
+ if (artifact.skipped && artifact.skipped.length > 0) {
8397
+ skippedIntegrationsList.push({
8398
+ name,
8399
+ reason: artifact.skipped[0]?.reason ?? "explicitly skipped by api-otter"
8400
+ });
8401
+ return;
8402
+ }
8015
8403
  const spec = artifact.integrationSpec ?? artifact.specPath ?? `./specs/${name}.yaml`;
8016
8404
  const mockUrl = artifact.mockUrl ?? `http://localhost:${BASE_MOCK_PORT + index}`;
8017
8405
  const baseUrl = artifact.baseUrl ?? "";
@@ -8032,20 +8420,20 @@ function handleConsolidateIntegrations(input) {
8032
8420
  integrationsByName.set(name, entry);
8033
8421
  });
8034
8422
  const integrations = Array.from(integrationsByName.values());
8035
- const dedupedCount = entries.length - integrations.length;
8036
- const integrationsYmlPath = join16(projectRoot, "stackwright.integrations.yml");
8423
+ const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
8424
+ const integrationsYmlPath = join17(projectRoot, "stackwright.integrations.yml");
8037
8425
  let existing = {};
8038
- if (existsSync15(integrationsYmlPath)) {
8426
+ if (existsSync16(integrationsYmlPath)) {
8039
8427
  try {
8040
- const raw = readFileSync15(integrationsYmlPath, "utf-8");
8428
+ const raw = readFileSync16(integrationsYmlPath, "utf-8");
8041
8429
  existing = yaml2.load(raw) ?? {};
8042
8430
  } catch {
8043
8431
  existing = {};
8044
8432
  }
8045
8433
  }
8046
8434
  const merged = { ...existing, integrations };
8047
- mkdirSync9(join16(projectRoot), { recursive: true });
8048
- if (!existsSync15(integrationsYmlPath)) {
8435
+ mkdirSync9(join17(projectRoot), { recursive: true });
8436
+ if (!existsSync16(integrationsYmlPath)) {
8049
8437
  writeFileSync10(integrationsYmlPath, "", "utf-8");
8050
8438
  }
8051
8439
  const release = lockSync2(integrationsYmlPath, {
@@ -8064,7 +8452,11 @@ function handleConsolidateIntegrations(input) {
8064
8452
  written: true,
8065
8453
  integrationCount: integrations.length,
8066
8454
  path: "stackwright.integrations.yml",
8067
- ...dedupedCount > 0 ? { dedupedCount } : {}
8455
+ ...dedupedCount > 0 ? { dedupedCount } : {},
8456
+ ...skippedIntegrationsList.length > 0 ? {
8457
+ skippedCount: skippedIntegrationsList.length,
8458
+ skippedIntegrations: skippedIntegrationsList
8459
+ } : {}
8068
8460
  };
8069
8461
  }
8070
8462
  function registerConsolidateIntegrationsTool(server2) {
@@ -8138,7 +8530,7 @@ var package_default = {
8138
8530
  "test:coverage": "vitest run --coverage"
8139
8531
  },
8140
8532
  name: "@stackwright-pro/mcp",
8141
- version: "0.2.0-alpha.106",
8533
+ version: "0.2.0-alpha.108",
8142
8534
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8143
8535
  license: "SEE LICENSE IN LICENSE",
8144
8536
  main: "./dist/server.js",