@stackwright-pro/mcp 0.2.0-alpha.107 → 0.2.0-alpha.109

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,7 +4372,87 @@ 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 = {
4431
+ // 5.0 Theme defaultColorMode hygiene (swp-0ryn / swp-mw97)
4432
+ //
4433
+ // DHL raft runs produced `defaultColorMode: "auto"` (not a valid value) and
4434
+ // `defaultColorMode: "dark"` when the designer said "respect OS preference".
4435
+ // This validator catches the `auto` literal at write-time so the otter
4436
+ // self-corrects before the artifact lands on disk.
4437
+ //
4438
+ // Valid values: 'light' | 'dark' | 'system'
4439
+ // Omitting the key entirely is also valid (app shell honors prefers-color-scheme).
4440
+ theme: (artifact2) => {
4441
+ const dcm = artifact2["defaultColorMode"];
4442
+ if (dcm === void 0 || dcm === null) {
4443
+ return { success: true };
4444
+ }
4445
+ const VALID_COLOR_MODES = ["light", "dark", "system"];
4446
+ if (!VALID_COLOR_MODES.includes(dcm)) {
4447
+ return {
4448
+ success: false,
4449
+ error: {
4450
+ message: `theme-tokens.json: \`defaultColorMode: "${String(dcm)}"\` is not a valid value. Valid values are \`light\`, \`dark\`, or \`system\`. Use \`system\` for OS-following behavior (respects \`prefers-color-scheme\`). Alternatively, OMIT the \`defaultColorMode\` key entirely when the designer has specified in design-language.json operationalNotes that OS preference should be respected (trigger phrases: "respect OS preference", "no forced default").`
4451
+ }
4452
+ };
4453
+ }
4454
+ return { success: true };
4455
+ },
4144
4456
  workflow: (artifact2) => {
4145
4457
  const workflowData = artifact2["workflow"];
4146
4458
  if (!workflowData) {
@@ -4190,33 +4502,60 @@ function _validateArtifactInner(input) {
4190
4502
  return { success: true };
4191
4503
  },
4192
4504
  // 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.
4505
+ // Collects serviceHooks from ALL workflow-config-*.json artifacts (swp-x8sm fan-out)
4506
+ // plus the legacy single-file workflow-config.json (backward compat).
4507
+ // If any workflow declared serviceHooks, every ref must resolve to a flow or
4508
+ // state-machine in this services-config artifact.
4195
4509
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
4196
4510
  services: (artifact2) => {
4197
- const workflowArtifactPath = join6(cwd, ".stackwright", "artifacts", "workflow-config.json");
4198
- if (!existsSync6(workflowArtifactPath)) {
4199
- return { success: true };
4511
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
4512
+ const workflowArtifactFiles = [];
4513
+ if (existsSync7(artifactsDir)) {
4514
+ try {
4515
+ const entries = readdirSync5(artifactsDir);
4516
+ for (const entry of entries) {
4517
+ if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
4518
+ workflowArtifactFiles.push(join7(artifactsDir, entry));
4519
+ }
4520
+ }
4521
+ } catch {
4522
+ }
4200
4523
  }
4201
- let workflowArtifact;
4202
- try {
4203
- workflowArtifact = JSON.parse(readFileSync6(workflowArtifactPath, "utf-8"));
4204
- } catch {
4524
+ const legacyPath = join7(artifactsDir, "workflow-config.json");
4525
+ if (existsSync7(legacyPath)) {
4526
+ workflowArtifactFiles.push(legacyPath);
4527
+ }
4528
+ if (workflowArtifactFiles.length === 0) {
4205
4529
  return { success: true };
4206
4530
  }
4207
- const declaredHooks = workflowArtifact.serviceHooks ?? [];
4208
- if (declaredHooks.length === 0) {
4531
+ const declaredHooks = [];
4532
+ for (const filePath of workflowArtifactFiles) {
4533
+ try {
4534
+ const parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
4535
+ if (Array.isArray(parsed.serviceHooks)) {
4536
+ declaredHooks.push(...parsed.serviceHooks);
4537
+ }
4538
+ } catch {
4539
+ }
4540
+ }
4541
+ const seenRefs = /* @__PURE__ */ new Set();
4542
+ const uniqueHooks = declaredHooks.filter((h) => {
4543
+ if (seenRefs.has(h.ref)) return false;
4544
+ seenRefs.add(h.ref);
4545
+ return true;
4546
+ });
4547
+ if (uniqueHooks.length === 0) {
4209
4548
  return { success: true };
4210
4549
  }
4211
4550
  const flows = artifact2["flows"] ?? [];
4212
4551
  const workflows = artifact2["workflows"] ?? [];
4213
4552
  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));
4553
+ const unresolved = uniqueHooks.filter((h) => !flowNames.has(h.ref));
4215
4554
  if (unresolved.length > 0) {
4216
4555
  return {
4217
4556
  success: false,
4218
4557
  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."
4558
+ 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
4559
  }
4221
4560
  };
4222
4561
  }
@@ -4282,11 +4621,55 @@ function _validateArtifactInner(input) {
4282
4621
  return { text: JSON.stringify(result), isError: false };
4283
4622
  }
4284
4623
  }
4624
+ if (phase === "api") {
4625
+ const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
4626
+ if (!apiAuthResult.valid) {
4627
+ const result = {
4628
+ valid: false,
4629
+ phase,
4630
+ violation: "schema-mismatch",
4631
+ retryPrompt: apiAuthResult.retryPrompt
4632
+ };
4633
+ return { text: JSON.stringify(result), isError: false };
4634
+ }
4635
+ }
4636
+ if (phase === "workflow" && input.workflowName !== void 0) {
4637
+ if (!isValidSlug(input.workflowName)) {
4638
+ const result = {
4639
+ valid: false,
4640
+ phase,
4641
+ violation: "schema-mismatch",
4642
+ 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".`
4643
+ };
4644
+ return { text: JSON.stringify(result), isError: false };
4645
+ }
4646
+ }
4647
+ let authOrphanWarning;
4648
+ if (phase === "auth") {
4649
+ const authCfg = artifact["authConfig"];
4650
+ const routes = authCfg?.["protectedRoutes"];
4651
+ if (Array.isArray(routes) && routes.length > 0) {
4652
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
4653
+ const knownSlugs = extractManifestSlugs(artifactsDir);
4654
+ const orphanPatterns = [];
4655
+ for (const route of routes) {
4656
+ const pattern = typeof route === "string" ? route : route.pattern ?? "";
4657
+ if (!pattern) continue;
4658
+ const baseSlug = extractPatternBaseSlug(pattern);
4659
+ if (baseSlug && !knownSlugs.has(baseSlug)) {
4660
+ orphanPatterns.push(pattern);
4661
+ }
4662
+ }
4663
+ if (orphanPatterns.length > 0) {
4664
+ 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.`;
4665
+ }
4666
+ }
4667
+ }
4285
4668
  try {
4286
- const artifactsDir = join6(cwd, ".stackwright", "artifacts");
4669
+ const artifactsDir = join7(cwd, ".stackwright", "artifacts");
4287
4670
  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);
4671
+ const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
4672
+ const artifactPath = join7(artifactsDir, artifactFile);
4290
4673
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
4291
4674
  const artifactBytes = Buffer.from(serialized, "utf-8");
4292
4675
  safeWriteSync(artifactPath, serialized);
@@ -4309,7 +4692,8 @@ function _validateArtifactInner(input) {
4309
4692
  valid: true,
4310
4693
  phase,
4311
4694
  artifactPath,
4312
- summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`
4695
+ summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`,
4696
+ ...authOrphanWarning ? { retryPrompt: authOrphanWarning } : {}
4313
4697
  };
4314
4698
  return { text: JSON.stringify(result), isError: false };
4315
4699
  } catch (err) {
@@ -4505,9 +4889,9 @@ function registerPipelineTools(server2) {
4505
4889
  isError: true
4506
4890
  };
4507
4891
  }
4508
- const questionsDir = join6(process.cwd(), ".stackwright", "questions");
4892
+ const questionsDir = join7(process.cwd(), ".stackwright", "questions");
4509
4893
  mkdirSync4(questionsDir, { recursive: true });
4510
- const outPath = join6(questionsDir, `${phase}.json`);
4894
+ const outPath = join7(questionsDir, `${phase}.json`);
4511
4895
  writeFileSync4(
4512
4896
  outPath,
4513
4897
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
@@ -4547,15 +4931,19 @@ function registerPipelineTools(server2) {
4547
4931
  ),
4548
4932
  integrationName: z13.string().optional().describe(
4549
4933
  'When phase="api": write to api-config-<integrationName>.json instead of api-config.json. Enables concurrent per-spec fan-out without collision (swp-530y).'
4934
+ ),
4935
+ workflowName: z13.string().optional().describe(
4936
+ `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
4937
  )
4551
4938
  },
4552
- async ({ phase, responseText, artifact, integrationName }) => {
4939
+ async ({ phase, responseText, artifact, integrationName, workflowName }) => {
4553
4940
  if (artifact) {
4554
4941
  return res(
4555
4942
  handleValidateArtifact({
4556
4943
  phase,
4557
4944
  artifact,
4558
- ...integrationName ? { integrationName } : {}
4945
+ ...integrationName ? { integrationName } : {},
4946
+ ...workflowName ? { workflowName } : {}
4559
4947
  })
4560
4948
  );
4561
4949
  }
@@ -4563,7 +4951,8 @@ function registerPipelineTools(server2) {
4563
4951
  handleValidateArtifact({
4564
4952
  phase,
4565
4953
  responseText: responseText ?? "",
4566
- ...integrationName ? { integrationName } : {}
4954
+ ...integrationName ? { integrationName } : {},
4955
+ ...workflowName ? { workflowName } : {}
4567
4956
  })
4568
4957
  );
4569
4958
  }
@@ -4595,8 +4984,8 @@ function registerPipelineTools(server2) {
4595
4984
 
4596
4985
  // src/tools/safe-write.ts
4597
4986
  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";
4987
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
4988
+ import { normalize, isAbsolute, dirname, join as join8 } from "path";
4600
4989
  import { emit as emit2 } from "@stackwright-pro/telemetry";
4601
4990
  var OTTER_WRITE_ALLOWLISTS = {
4602
4991
  "stackwright-pro-designer-otter": [
@@ -4608,6 +4997,11 @@ var OTTER_WRITE_ALLOWLISTS = {
4608
4997
  prefix: "stackwright.theme.",
4609
4998
  suffix: ".yml",
4610
4999
  description: "Theme config YAML (merged at build time)"
5000
+ },
5001
+ {
5002
+ prefix: "stackwright.yml",
5003
+ suffix: "",
5004
+ description: "Stackwright config (themeName + customTheme writeback)"
4611
5005
  }
4612
5006
  ],
4613
5007
  "stackwright-pro-auth-otter": [
@@ -4662,6 +5056,17 @@ var OTTER_WRITE_ALLOWLISTS = {
4662
5056
  prefix: ".stackwright/artifacts/",
4663
5057
  suffix: ".json",
4664
5058
  description: "Scaffold manifest artifact"
5059
+ },
5060
+ // swp-89w5: scaffold-otter initialises mock backend wiring via stackwright_pro_emit_env_local
5061
+ {
5062
+ prefix: ".env.local",
5063
+ suffix: "",
5064
+ description: "Dotenv runtime config (mock backend wiring: NEXT_PUBLIC_MOCK_BACKEND + per-integration mock keys)"
5065
+ },
5066
+ {
5067
+ prefix: ".env.local.example",
5068
+ suffix: "",
5069
+ description: "Dotenv template (schema for real production values)"
4665
5070
  }
4666
5071
  ],
4667
5072
  "stackwright-pro-api-otter": [
@@ -4778,21 +5183,21 @@ function extractContentTypes(yamlContent) {
4778
5183
  return types.length > 0 ? types : ["unknown"];
4779
5184
  }
4780
5185
  function readPageRegistry(cwd) {
4781
- const regPath = join7(cwd, PAGE_REGISTRY_FILE);
4782
- if (!existsSync7(regPath)) return {};
5186
+ const regPath = join8(cwd, PAGE_REGISTRY_FILE);
5187
+ if (!existsSync8(regPath)) return {};
4783
5188
  try {
4784
5189
  const stat = lstatSync7(regPath);
4785
5190
  if (stat.isSymbolicLink()) return {};
4786
- return JSON.parse(readFileSync7(regPath, "utf-8"));
5191
+ return JSON.parse(readFileSync8(regPath, "utf-8"));
4787
5192
  } catch {
4788
5193
  return {};
4789
5194
  }
4790
5195
  }
4791
5196
  function writePageRegistry(cwd, registry) {
4792
- const dir = join7(cwd, ".stackwright");
5197
+ const dir = join8(cwd, ".stackwright");
4793
5198
  mkdirSync5(dir, { recursive: true });
4794
- const regPath = join7(cwd, PAGE_REGISTRY_FILE);
4795
- if (existsSync7(regPath)) {
5199
+ const regPath = join8(cwd, PAGE_REGISTRY_FILE);
5200
+ if (existsSync8(regPath)) {
4796
5201
  const stat = lstatSync7(regPath);
4797
5202
  if (stat.isSymbolicLink()) {
4798
5203
  throw new Error("Refusing to write page registry through symlink");
@@ -4964,8 +5369,8 @@ function _safeWriteInner(input) {
4964
5369
  return { text: JSON.stringify(result), isError: true };
4965
5370
  }
4966
5371
  const normalized = normalize(filePath);
4967
- const fullPath = join7(cwd, normalized);
4968
- if (existsSync7(fullPath)) {
5372
+ const fullPath = join8(cwd, normalized);
5373
+ if (existsSync8(fullPath)) {
4969
5374
  try {
4970
5375
  const stat = lstatSync7(fullPath);
4971
5376
  if (stat.isSymbolicLink()) {
@@ -5095,8 +5500,8 @@ function registerSafeWriteTools(server2) {
5095
5500
 
5096
5501
  // src/tools/auth.ts
5097
5502
  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";
5503
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
5504
+ import { join as join9 } from "path";
5100
5505
  function buildHierarchy(roles) {
5101
5506
  const h = {};
5102
5507
  for (let i = 0; i < roles.length - 1; i++) {
@@ -5123,9 +5528,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
5123
5528
  }
5124
5529
  function detectNextMajorVersion(cwd) {
5125
5530
  try {
5126
- const pkgPath = join8(cwd, "package.json");
5127
- if (!existsSync8(pkgPath)) return null;
5128
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
5531
+ const pkgPath = join9(cwd, "package.json");
5532
+ if (!existsSync9(pkgPath)) return null;
5533
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
5129
5534
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
5130
5535
  if (!nextVersion) return null;
5131
5536
  const match = nextVersion.match(/(\d+)/);
@@ -5789,7 +6194,7 @@ async function configureAuthHandler(params, cwd) {
5789
6194
  auditRetentionDays,
5790
6195
  normalizedRoutes
5791
6196
  );
5792
- writeFileSync6(join8(cwd, conventionFile), authFileContent, "utf8");
6197
+ writeFileSync6(join9(cwd, conventionFile), authFileContent, "utf8");
5793
6198
  filesWritten.push(conventionFile);
5794
6199
  } catch (err) {
5795
6200
  const msg = err instanceof Error ? err.message : String(err);
@@ -5809,9 +6214,9 @@ async function configureAuthHandler(params, cwd) {
5809
6214
  if (!devOnly) {
5810
6215
  try {
5811
6216
  const envBlock = generateEnvBlock(effectiveMethod, params);
5812
- const envPath = join8(cwd, ".env.example");
5813
- if (existsSync8(envPath)) {
5814
- const existing = readFileSync8(envPath, "utf8");
6217
+ const envPath = join9(cwd, ".env.example");
6218
+ if (existsSync9(envPath)) {
6219
+ const existing = readFileSync9(envPath, "utf8");
5815
6220
  writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5816
6221
  } else {
5817
6222
  writeFileSync6(envPath, envBlock, "utf8");
@@ -5832,10 +6237,10 @@ async function configureAuthHandler(params, cwd) {
5832
6237
  }
5833
6238
  if (devOnly) {
5834
6239
  try {
5835
- const mockAuthDir = join8(cwd, "lib");
6240
+ const mockAuthDir = join9(cwd, "lib");
5836
6241
  mkdirSync6(mockAuthDir, { recursive: true });
5837
6242
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
5838
- writeFileSync6(join8(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
6243
+ writeFileSync6(join9(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5839
6244
  filesWritten.push("lib/mock-auth.ts");
5840
6245
  } catch (err) {
5841
6246
  const msg = err instanceof Error ? err.message : String(err);
@@ -5853,9 +6258,9 @@ async function configureAuthHandler(params, cwd) {
5853
6258
  };
5854
6259
  }
5855
6260
  try {
5856
- const pkgPath = join8(cwd, "package.json");
5857
- if (existsSync8(pkgPath)) {
5858
- const existingPkg = readFileSync8(pkgPath, "utf8");
6261
+ const pkgPath = join9(cwd, "package.json");
6262
+ if (existsSync9(pkgPath)) {
6263
+ const existingPkg = readFileSync9(pkgPath, "utf8");
5859
6264
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
5860
6265
  writeFileSync6(pkgPath, updatedPkg, "utf8");
5861
6266
  filesWritten.push("package.json");
@@ -5898,7 +6303,7 @@ async function configureAuthHandler(params, cwd) {
5898
6303
  normalizedRoutes,
5899
6304
  useProxy
5900
6305
  );
5901
- writeFileSync6(join8(cwd, "stackwright.auth.yml"), authYaml, "utf8");
6306
+ writeFileSync6(join9(cwd, "stackwright.auth.yml"), authYaml, "utf8");
5902
6307
  filesWritten.push("stackwright.auth.yml");
5903
6308
  } catch (err) {
5904
6309
  const msg = err instanceof Error ? err.message : String(err);
@@ -6011,8 +6416,8 @@ function registerAuthTools(server2) {
6011
6416
 
6012
6417
  // src/integrity.ts
6013
6418
  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";
6419
+ import { readFileSync as readFileSync10, readdirSync as readdirSync6, lstatSync as lstatSync8 } from "fs";
6420
+ import { join as join10, basename } from "path";
6016
6421
  var _checksums = /* @__PURE__ */ new Map([
6017
6422
  [
6018
6423
  "stackwright-pro-api-otter.json",
@@ -6020,11 +6425,11 @@ var _checksums = /* @__PURE__ */ new Map([
6020
6425
  ],
6021
6426
  [
6022
6427
  "stackwright-pro-auth-otter.json",
6023
- "1a21d9f23e250f23291124307e5a2a32af5a716566319a3290372dae3d02e637"
6428
+ "69fa03c5532ef617c86fe7af41e8174c168d282baf602b1b269a7e933ea86b7c"
6024
6429
  ],
6025
6430
  [
6026
6431
  "stackwright-pro-dashboard-otter.json",
6027
- "4a9ac39e929866ae3c929aa5bcbba3d9483a1c251c35f26f267f3e7433506f0c"
6432
+ "f55081bf4d6545e5435128919f6f9233956ba37671fc9f85300f240ad2497ab6"
6028
6433
  ],
6029
6434
  [
6030
6435
  "stackwright-pro-data-otter.json",
@@ -6040,19 +6445,19 @@ var _checksums = /* @__PURE__ */ new Map([
6040
6445
  ],
6041
6446
  [
6042
6447
  "stackwright-pro-foreman-otter.json",
6043
- "9d68b7b20af7a8a1668e2a693b4a2b05cf37acce1e9f6298a0966c97a3417e9f"
6448
+ "88c4101154d44a7c5470ded13562402a39323ee570ed3a90a73b8d85d7fd2817"
6044
6449
  ],
6045
6450
  [
6046
6451
  "stackwright-pro-form-wizard-otter.json",
6047
- "7f6f94192f26face57dc8b2e22e0a49d23ceeeb356ca4ebde05492f1b25e3c47"
6452
+ "cb350297a0068ead2424e703e8c775d91c0b87249ea6904f43b903baf8a84b79"
6048
6453
  ],
6049
6454
  [
6050
6455
  "stackwright-pro-geo-otter.json",
6051
- "fc3d18e02a6147d95d3dd9093ce74c0e8fffaecc2f9ecdbd19c163a80129d264"
6456
+ "3032fbd27de36c0cdc0e19e46a32d771208d8e86714482f5a368d46342c8317a"
6052
6457
  ],
6053
6458
  [
6054
6459
  "stackwright-pro-page-otter.json",
6055
- "879e8ff4aa645bac2c63dc962cad1a549876983c257aec756e95293c3b22ce7e"
6460
+ "cc0db24b00f0130f8b4ec8d385c73ea1bcf5d57ba45aca6b986bcbd5da328930"
6056
6461
  ],
6057
6462
  [
6058
6463
  "stackwright-pro-polish-otter.json",
@@ -6064,15 +6469,15 @@ var _checksums = /* @__PURE__ */ new Map([
6064
6469
  ],
6065
6470
  [
6066
6471
  "stackwright-pro-scaffold-otter.json",
6067
- "92930c732547c90a19e89ee0e25b5f037b7366f770941f0a01e148ff240a1b6d"
6472
+ "90272bab51bd8ed9e25c0fb6481cdd4252386850f64192edfe1e2eff44166e2d"
6068
6473
  ],
6069
6474
  [
6070
6475
  "stackwright-pro-theme-otter.json",
6071
- "01d16d0597d475db60362e3b8020035e140817d197c36f4307a48b95f4b28b60"
6476
+ "afb47f8381907145c0e098bdcaae4dd9f5c4ff825a8e88d00a218d2f6858820b"
6072
6477
  ],
6073
6478
  [
6074
6479
  "stackwright-services-otter.json",
6075
- "2a50ceb3fad166251d0ad8709a34a32118645713e0e34628165d16dced1d5c93"
6480
+ "4351348806aeb98404a2bacb044209ca411be5e35e2f2c3d03eb4d28a07fde78"
6076
6481
  ]
6077
6482
  ]);
6078
6483
  Object.freeze(_checksums);
@@ -6120,7 +6525,7 @@ function verifyOtterFile(filePath) {
6120
6525
  }
6121
6526
  let raw;
6122
6527
  try {
6123
- raw = readFileSync9(filePath);
6528
+ raw = readFileSync10(filePath);
6124
6529
  } catch (err) {
6125
6530
  const msg = err instanceof Error ? err.message : String(err);
6126
6531
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -6170,7 +6575,7 @@ function verifyAllOtters(otterDir) {
6170
6575
  const unknown = [];
6171
6576
  let entries;
6172
6577
  try {
6173
- entries = readdirSync4(otterDir);
6578
+ entries = readdirSync6(otterDir);
6174
6579
  } catch (err) {
6175
6580
  const msg = err instanceof Error ? err.message : String(err);
6176
6581
  return {
@@ -6181,7 +6586,7 @@ function verifyAllOtters(otterDir) {
6181
6586
  }
6182
6587
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
6183
6588
  for (const filename of otterFiles) {
6184
- const filePath = join9(otterDir, filename);
6589
+ const filePath = join10(otterDir, filename);
6185
6590
  try {
6186
6591
  if (lstatSync8(filePath).isSymbolicLink()) {
6187
6592
  failed.push({ filename, error: "Skipped: symlink" });
@@ -6209,7 +6614,7 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
6209
6614
  function resolveOtterDir() {
6210
6615
  const cwd = process.cwd();
6211
6616
  for (const relative2 of DEFAULT_SEARCH_PATHS) {
6212
- const candidate = join9(cwd, relative2);
6617
+ const candidate = join10(cwd, relative2);
6213
6618
  try {
6214
6619
  lstatSync8(candidate);
6215
6620
  return candidate;
@@ -6290,13 +6695,13 @@ function registerIntegrityTools(server2) {
6290
6695
 
6291
6696
  // src/tools/domain.ts
6292
6697
  import { z as z16 } from "zod";
6293
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
6294
- import { join as join10 } from "path";
6698
+ import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
6699
+ import { join as join11 } from "path";
6295
6700
  function handleListCollections(input) {
6296
6701
  const cwd = input._cwd ?? process.cwd();
6297
6702
  const sources = [
6298
6703
  {
6299
- path: join10(cwd, ".stackwright", "artifacts", "data-config.json"),
6704
+ path: join11(cwd, ".stackwright", "artifacts", "data-config.json"),
6300
6705
  source: "data-config.json",
6301
6706
  parse: (raw) => {
6302
6707
  const parsed = JSON.parse(raw);
@@ -6307,15 +6712,15 @@ function handleListCollections(input) {
6307
6712
  }
6308
6713
  },
6309
6714
  {
6310
- path: join10(cwd, "stackwright.yml"),
6715
+ path: join11(cwd, "stackwright.yml"),
6311
6716
  source: "stackwright.yml",
6312
6717
  parse: extractCollectionsFromYaml
6313
6718
  }
6314
6719
  ];
6315
6720
  for (const { path: path5, source, parse } of sources) {
6316
- if (!existsSync9(path5)) continue;
6721
+ if (!existsSync10(path5)) continue;
6317
6722
  try {
6318
- const collections = parse(readFileSync10(path5, "utf8"));
6723
+ const collections = parse(readFileSync11(path5, "utf8"));
6319
6724
  return {
6320
6725
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
6321
6726
  isError: false
@@ -6466,8 +6871,8 @@ function handleValidateWorkflow(input) {
6466
6871
  if (input.workflow && Object.keys(input.workflow).length > 0) {
6467
6872
  raw = input.workflow;
6468
6873
  } else {
6469
- const artifactPath = join10(cwd, ".stackwright", "artifacts", "workflow-config.json");
6470
- if (!existsSync9(artifactPath)) {
6874
+ const artifactPath = join11(cwd, ".stackwright", "artifacts", "workflow-config.json");
6875
+ if (!existsSync10(artifactPath)) {
6471
6876
  return fail([
6472
6877
  {
6473
6878
  code: "NO_WORKFLOW",
@@ -6476,7 +6881,7 @@ function handleValidateWorkflow(input) {
6476
6881
  ]);
6477
6882
  }
6478
6883
  try {
6479
- raw = JSON.parse(readFileSync10(artifactPath, "utf8"));
6884
+ raw = JSON.parse(readFileSync11(artifactPath, "utf8"));
6480
6885
  } catch (err) {
6481
6886
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
6482
6887
  }
@@ -6914,16 +7319,16 @@ function registerProPageTools(server2) {
6914
7319
 
6915
7320
  // src/tools/scaffold-cleanup.ts
6916
7321
  import {
6917
- existsSync as existsSync11,
7322
+ existsSync as existsSync12,
6918
7323
  lstatSync as lstatSync9,
6919
7324
  unlinkSync as unlinkSync2,
6920
- readFileSync as readFileSync11,
7325
+ readFileSync as readFileSync12,
6921
7326
  writeFileSync as writeFileSync8,
6922
- readdirSync as readdirSync5,
7327
+ readdirSync as readdirSync7,
6923
7328
  rmdirSync,
6924
7329
  mkdirSync as mkdirSync8
6925
7330
  } from "fs";
6926
- import { join as join12, dirname as dirname3 } from "path";
7331
+ import { join as join13, dirname as dirname3 } from "path";
6927
7332
  var SCAFFOLD_FILES_TO_DELETE = [
6928
7333
  "content/posts/getting-started.yaml",
6929
7334
  "content/posts/hello-world.yaml"
@@ -6948,12 +7353,12 @@ var FALLBACK_COLORS = {
6948
7353
  mutedForeground: "#737373"
6949
7354
  };
6950
7355
  function readThemeColors(cwd) {
6951
- const tokenPath = join12(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6952
- if (!existsSync11(tokenPath)) return { ...FALLBACK_COLORS };
7356
+ const tokenPath = join13(cwd, ".stackwright", "artifacts", "theme-tokens.json");
7357
+ if (!existsSync12(tokenPath)) return { ...FALLBACK_COLORS };
6953
7358
  const stat = lstatSync9(tokenPath);
6954
7359
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
6955
7360
  try {
6956
- const raw = JSON.parse(readFileSync11(tokenPath, "utf-8"));
7361
+ const raw = JSON.parse(readFileSync12(tokenPath, "utf-8"));
6957
7362
  const css = raw?.cssVariables ?? {};
6958
7363
  const toHsl = (hslValue) => {
6959
7364
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -7013,15 +7418,15 @@ function generateNotFoundPage(colors) {
7013
7418
  `;
7014
7419
  }
7015
7420
  function isSafePath(fullPath) {
7016
- if (!existsSync11(fullPath)) return true;
7421
+ if (!existsSync12(fullPath)) return true;
7017
7422
  const stat = lstatSync9(fullPath);
7018
7423
  return !stat.isSymbolicLink();
7019
7424
  }
7020
7425
  function isDirEmpty(dirPath) {
7021
- if (!existsSync11(dirPath)) return false;
7426
+ if (!existsSync12(dirPath)) return false;
7022
7427
  const stat = lstatSync9(dirPath);
7023
7428
  if (!stat.isDirectory()) return false;
7024
- return readdirSync5(dirPath).length === 0;
7429
+ return readdirSync7(dirPath).length === 0;
7025
7430
  }
7026
7431
  function handleCleanupScaffold(_cwd) {
7027
7432
  const cwd = _cwd ?? process.cwd();
@@ -7034,8 +7439,8 @@ function handleCleanupScaffold(_cwd) {
7034
7439
  cleanupWarnings: []
7035
7440
  };
7036
7441
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
7037
- const fullPath = join12(cwd, relPath);
7038
- if (!existsSync11(fullPath)) {
7442
+ const fullPath = join13(cwd, relPath);
7443
+ if (!existsSync12(fullPath)) {
7039
7444
  result.skipped.push(relPath);
7040
7445
  continue;
7041
7446
  }
@@ -7052,13 +7457,13 @@ function handleCleanupScaffold(_cwd) {
7052
7457
  }
7053
7458
  {
7054
7459
  const relPath = "pages/getting-started/content.yml";
7055
- const fullPath = join12(cwd, relPath);
7056
- if (!existsSync11(fullPath)) {
7460
+ const fullPath = join13(cwd, relPath);
7461
+ if (!existsSync12(fullPath)) {
7057
7462
  result.skipped.push(relPath);
7058
7463
  } else if (!isSafePath(fullPath)) {
7059
7464
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7060
7465
  } else {
7061
- const content = readFileSync11(fullPath, "utf-8");
7466
+ const content = readFileSync12(fullPath, "utf-8");
7062
7467
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
7063
7468
  (marker) => content.includes(marker)
7064
7469
  );
@@ -7078,16 +7483,16 @@ function handleCleanupScaffold(_cwd) {
7078
7483
  }
7079
7484
  {
7080
7485
  const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
7081
- const fullPath = join12(cwd, relPath);
7082
- const postsDir = join12(cwd, "content/posts");
7083
- if (!existsSync11(fullPath)) {
7486
+ const fullPath = join13(cwd, relPath);
7487
+ const postsDir = join13(cwd, "content/posts");
7488
+ if (!existsSync12(fullPath)) {
7084
7489
  result.skipped.push(relPath);
7085
7490
  } else if (!isSafePath(fullPath)) {
7086
7491
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7087
- } else if (!existsSync11(postsDir)) {
7492
+ } else if (!existsSync12(postsDir)) {
7088
7493
  result.skipped.push(relPath);
7089
7494
  } else {
7090
- const userPostFiles = readdirSync5(postsDir).filter(
7495
+ const userPostFiles = readdirSync7(postsDir).filter(
7091
7496
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
7092
7497
  );
7093
7498
  if (userPostFiles.length === 0) {
@@ -7105,8 +7510,8 @@ function handleCleanupScaffold(_cwd) {
7105
7510
  }
7106
7511
  }
7107
7512
  for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
7108
- const fullDir = join12(cwd, relDir);
7109
- if (!existsSync11(fullDir)) continue;
7513
+ const fullDir = join13(cwd, relDir);
7514
+ if (!existsSync12(fullDir)) continue;
7110
7515
  if (!isSafePath(fullDir)) {
7111
7516
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
7112
7517
  continue;
@@ -7121,8 +7526,8 @@ function handleCleanupScaffold(_cwd) {
7121
7526
  }
7122
7527
  }
7123
7528
  {
7124
- const fullPath = join12(cwd, NOT_FOUND_PATH);
7125
- if (!existsSync11(fullPath)) {
7529
+ const fullPath = join13(cwd, NOT_FOUND_PATH);
7530
+ if (!existsSync12(fullPath)) {
7126
7531
  result.skipped.push(NOT_FOUND_PATH);
7127
7532
  } else if (!isSafePath(fullPath)) {
7128
7533
  result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
@@ -7643,14 +8048,14 @@ ${results.join("\n")}`;
7643
8048
 
7644
8049
  // src/tools/strip-legacy-integrations.ts
7645
8050
  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";
8051
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
8052
+ import { join as join14 } from "path";
7648
8053
  function handleStripLegacyIntegrations(input) {
7649
8054
  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)) {
8055
+ const ymlPath = join14(projectRoot, "stackwright.yml");
8056
+ const yamlPath = join14(projectRoot, "stackwright.yaml");
8057
+ if (!existsSync13(ymlPath)) {
8058
+ if (existsSync13(yamlPath)) {
7654
8059
  return {
7655
8060
  changed: false,
7656
8061
  removedEntries: 0,
@@ -7663,7 +8068,7 @@ function handleStripLegacyIntegrations(input) {
7663
8068
  message: "stackwright.yml not found \u2014 no-op."
7664
8069
  };
7665
8070
  }
7666
- const raw = readFileSync12(ymlPath, "utf-8");
8071
+ const raw = readFileSync13(ymlPath, "utf-8");
7667
8072
  const isCRLF = raw.includes("\r\n");
7668
8073
  const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
7669
8074
  const lines = content.split("\n");
@@ -7718,15 +8123,15 @@ function registerStripLegacyIntegrationsTool(server2) {
7718
8123
 
7719
8124
  // src/tools/emitters.ts
7720
8125
  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";
8126
+ import { readFileSync as readFileSync14, existsSync as existsSync14, readdirSync as readdirSync8 } from "fs";
8127
+ import { join as join15 } from "path";
7723
8128
  import yaml from "js-yaml";
7724
8129
  import { emitEnvLocal, emitProvidersFile, emitMockPorts } from "@stackwright-pro/emitters";
7725
8130
  function readIntegrations(cwd) {
7726
- const filePath = join14(cwd, "stackwright.integrations.yml");
7727
- if (!existsSync13(filePath)) return [];
8131
+ const filePath = join15(cwd, "stackwright.integrations.yml");
8132
+ if (!existsSync14(filePath)) return [];
7728
8133
  try {
7729
- const raw = yaml.load(readFileSync13(filePath, "utf-8"));
8134
+ const raw = yaml.load(readFileSync14(filePath, "utf-8"));
7730
8135
  if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
7731
8136
  return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
7732
8137
  name: i.name,
@@ -7737,15 +8142,15 @@ function readIntegrations(cwd) {
7737
8142
  }
7738
8143
  }
7739
8144
  function readServiceTokenRefs(cwd) {
7740
- const servicesDir = join14(cwd, "services");
7741
- if (!existsSync13(servicesDir)) return [];
8145
+ const servicesDir = join15(cwd, "services");
8146
+ if (!existsSync14(servicesDir)) return [];
7742
8147
  try {
7743
- const files = readdirSync6(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
8148
+ const files = readdirSync8(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
7744
8149
  const refs = [];
7745
8150
  for (const file of files) {
7746
8151
  try {
7747
8152
  const raw = yaml.load(
7748
- readFileSync13(join14(servicesDir, file), "utf-8")
8153
+ readFileSync14(join15(servicesDir, file), "utf-8")
7749
8154
  );
7750
8155
  if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
7751
8156
  refs.push({
@@ -7895,8 +8300,8 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
7895
8300
 
7896
8301
  // src/tools/list-specs.ts
7897
8302
  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";
8303
+ import { readdirSync as readdirSync9, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync15 } from "fs";
8304
+ import { join as join16, extname, basename as basename2 } from "path";
7900
8305
  var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
7901
8306
  var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
7902
8307
  function deriveIntegrationName(fileName) {
@@ -7918,18 +8323,18 @@ function detectFormat(snippet) {
7918
8323
  }
7919
8324
  function handleListSpecs(input) {
7920
8325
  const { projectRoot } = input;
7921
- const specsDir = join15(projectRoot, "specs");
8326
+ const specsDir = join16(projectRoot, "specs");
7922
8327
  const empty = { projectRoot, specs: [], totalCount: 0 };
7923
- if (!existsSync14(specsDir)) return empty;
8328
+ if (!existsSync15(specsDir)) return empty;
7924
8329
  let entries;
7925
8330
  try {
7926
- entries = readdirSync7(specsDir);
8331
+ entries = readdirSync9(specsDir);
7927
8332
  } catch {
7928
8333
  return empty;
7929
8334
  }
7930
8335
  const specs = [];
7931
8336
  for (const entry of entries) {
7932
- const fullPath = join15(specsDir, entry);
8337
+ const fullPath = join16(specsDir, entry);
7933
8338
  let stat;
7934
8339
  try {
7935
8340
  stat = statSync2(fullPath);
@@ -7940,7 +8345,7 @@ function handleListSpecs(input) {
7940
8345
  if (!ALLOWED_EXTS.has(extname(entry).toLowerCase())) continue;
7941
8346
  let snippet = "";
7942
8347
  try {
7943
- snippet = readFileSync14(fullPath, "utf-8").slice(0, 2048);
8348
+ snippet = readFileSync15(fullPath, "utf-8").slice(0, 2048);
7944
8349
  } catch {
7945
8350
  }
7946
8351
  specs.push({
@@ -7972,9 +8377,9 @@ function registerListSpecsTool(server2) {
7972
8377
 
7973
8378
  // src/tools/consolidate-integrations.ts
7974
8379
  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";
8380
+ import { readdirSync as readdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
7976
8381
  import { lockSync as lockSync2 } from "proper-lockfile";
7977
- import { join as join16, basename as basename3 } from "path";
8382
+ import { join as join17, basename as basename3 } from "path";
7978
8383
  import yaml2 from "js-yaml";
7979
8384
  var BASE_MOCK_PORT = 4011;
7980
8385
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
@@ -7987,13 +8392,13 @@ function deriveNameFromFilename(filename) {
7987
8392
  }
7988
8393
  function handleConsolidateIntegrations(input) {
7989
8394
  const { projectRoot } = input;
7990
- const artifactsDir = join16(projectRoot, ".stackwright", "artifacts");
7991
- if (!existsSync15(artifactsDir)) {
8395
+ const artifactsDir = join17(projectRoot, ".stackwright", "artifacts");
8396
+ if (!existsSync16(artifactsDir)) {
7992
8397
  return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
7993
8398
  }
7994
8399
  let entries;
7995
8400
  try {
7996
- entries = readdirSync8(artifactsDir).filter(
8401
+ entries = readdirSync10(artifactsDir).filter(
7997
8402
  (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
7998
8403
  );
7999
8404
  } catch {
@@ -8004,14 +8409,22 @@ function handleConsolidateIntegrations(input) {
8004
8409
  }
8005
8410
  entries.sort();
8006
8411
  const integrationsByName = /* @__PURE__ */ new Map();
8412
+ const skippedIntegrationsList = [];
8007
8413
  entries.forEach((filename, index) => {
8008
8414
  let artifact = {};
8009
8415
  try {
8010
- const raw = readFileSync15(join16(artifactsDir, filename), "utf-8");
8416
+ const raw = readFileSync16(join17(artifactsDir, filename), "utf-8");
8011
8417
  artifact = JSON.parse(raw);
8012
8418
  } catch {
8013
8419
  }
8014
8420
  const name = artifact.integrationName ?? deriveNameFromFilename(filename);
8421
+ if (artifact.skipped && artifact.skipped.length > 0) {
8422
+ skippedIntegrationsList.push({
8423
+ name,
8424
+ reason: artifact.skipped[0]?.reason ?? "explicitly skipped by api-otter"
8425
+ });
8426
+ return;
8427
+ }
8015
8428
  const spec = artifact.integrationSpec ?? artifact.specPath ?? `./specs/${name}.yaml`;
8016
8429
  const mockUrl = artifact.mockUrl ?? `http://localhost:${BASE_MOCK_PORT + index}`;
8017
8430
  const baseUrl = artifact.baseUrl ?? "";
@@ -8032,20 +8445,20 @@ function handleConsolidateIntegrations(input) {
8032
8445
  integrationsByName.set(name, entry);
8033
8446
  });
8034
8447
  const integrations = Array.from(integrationsByName.values());
8035
- const dedupedCount = entries.length - integrations.length;
8036
- const integrationsYmlPath = join16(projectRoot, "stackwright.integrations.yml");
8448
+ const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
8449
+ const integrationsYmlPath = join17(projectRoot, "stackwright.integrations.yml");
8037
8450
  let existing = {};
8038
- if (existsSync15(integrationsYmlPath)) {
8451
+ if (existsSync16(integrationsYmlPath)) {
8039
8452
  try {
8040
- const raw = readFileSync15(integrationsYmlPath, "utf-8");
8453
+ const raw = readFileSync16(integrationsYmlPath, "utf-8");
8041
8454
  existing = yaml2.load(raw) ?? {};
8042
8455
  } catch {
8043
8456
  existing = {};
8044
8457
  }
8045
8458
  }
8046
8459
  const merged = { ...existing, integrations };
8047
- mkdirSync9(join16(projectRoot), { recursive: true });
8048
- if (!existsSync15(integrationsYmlPath)) {
8460
+ mkdirSync9(join17(projectRoot), { recursive: true });
8461
+ if (!existsSync16(integrationsYmlPath)) {
8049
8462
  writeFileSync10(integrationsYmlPath, "", "utf-8");
8050
8463
  }
8051
8464
  const release = lockSync2(integrationsYmlPath, {
@@ -8064,7 +8477,11 @@ function handleConsolidateIntegrations(input) {
8064
8477
  written: true,
8065
8478
  integrationCount: integrations.length,
8066
8479
  path: "stackwright.integrations.yml",
8067
- ...dedupedCount > 0 ? { dedupedCount } : {}
8480
+ ...dedupedCount > 0 ? { dedupedCount } : {},
8481
+ ...skippedIntegrationsList.length > 0 ? {
8482
+ skippedCount: skippedIntegrationsList.length,
8483
+ skippedIntegrations: skippedIntegrationsList
8484
+ } : {}
8068
8485
  };
8069
8486
  }
8070
8487
  function registerConsolidateIntegrationsTool(server2) {
@@ -8138,7 +8555,7 @@ var package_default = {
8138
8555
  "test:coverage": "vitest run --coverage"
8139
8556
  },
8140
8557
  name: "@stackwright-pro/mcp",
8141
- version: "0.2.0-alpha.107",
8558
+ version: "0.2.0-alpha.109",
8142
8559
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8143
8560
  license: "SEE LICENSE IN LICENSE",
8144
8561
  main: "./dist/server.js",