@stackwright-pro/mcp 0.2.0-alpha.107 → 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.js CHANGED
@@ -1334,6 +1334,10 @@ function answersToManifestFormat(answers, questions) {
1334
1334
  // src/validation.ts
1335
1335
  var SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
1336
1336
  var SAFE_QUESTION_ID = /^[a-z0-9][a-z0-9-]{0,63}$/i;
1337
+ var SAFE_SLUG = /^[a-z0-9][a-z0-9-]{0,79}$/;
1338
+ function isValidSlug(slug) {
1339
+ return SAFE_SLUG.test(slug);
1340
+ }
1337
1341
  function isValidPhase(phase) {
1338
1342
  return SAFE_PHASE.test(phase);
1339
1343
  }
@@ -2023,9 +2027,9 @@ function registerOrchestrationTools(server2) {
2023
2027
 
2024
2028
  // src/tools/pipeline.ts
2025
2029
  var import_zod13 = require("zod");
2026
- var import_fs7 = require("fs");
2030
+ var import_fs8 = require("fs");
2027
2031
  var import_proper_lockfile = require("proper-lockfile");
2028
- var import_path7 = require("path");
2032
+ var import_path8 = require("path");
2029
2033
  var import_crypto3 = require("crypto");
2030
2034
 
2031
2035
  // src/artifact-signing.ts
@@ -2552,6 +2556,234 @@ function formatPulseRetryPrompt(result) {
2552
2556
  return lines.join("\n");
2553
2557
  }
2554
2558
 
2559
+ // src/tools/validate-api-integration.ts
2560
+ var CANONICAL_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2", "oidc", "none"];
2561
+ var CANONICAL_SET = new Set(CANONICAL_API_AUTH_TYPES);
2562
+ var VALID_SET_DISPLAY = CANONICAL_API_AUTH_TYPES.map((t) => `\`${t}\``).join(", ");
2563
+ var RENAME_MAP = {
2564
+ // apiKey variants
2565
+ "api-key": "apiKey",
2566
+ api_key: "apiKey",
2567
+ apikey: "apiKey",
2568
+ // lowercase of 'apiKey' / 'APIKEY'
2569
+ // oauth2 variants
2570
+ "oauth-2": "oauth2",
2571
+ oauth_2: "oauth2",
2572
+ // oidc variants
2573
+ "open-id-connect": "oidc",
2574
+ "openid-connect": "oidc",
2575
+ openidconnect: "oidc",
2576
+ // lowercase of 'openIdConnect'
2577
+ openidconnect_: "oidc",
2578
+ // belt-and-suspenders
2579
+ openIdConnect: "oidc",
2580
+ // exact camelCase as emitted by some tooling
2581
+ // bearer variants
2582
+ "bearer-token": "bearer",
2583
+ bearer_token: "bearer"
2584
+ };
2585
+ function renameSuggestion(badLiteral) {
2586
+ return RENAME_MAP[badLiteral] ?? RENAME_MAP[badLiteral.toLowerCase()];
2587
+ }
2588
+ function validateAuthType(authType, yamlPath, artifactPath) {
2589
+ if (authType === void 0 || authType === null) return null;
2590
+ if (typeof authType !== "string") {
2591
+ return [
2592
+ `${artifactPath}: \`${yamlPath}\` must be a string, got \`${typeof authType}\`.`,
2593
+ `Valid values: ${VALID_SET_DISPLAY}.`,
2594
+ "Fix this value and re-call stackwright_pro_validate_artifact."
2595
+ ].join("\n");
2596
+ }
2597
+ if (CANONICAL_SET.has(authType)) return null;
2598
+ const suggestion = renameSuggestion(authType);
2599
+ const lines = [
2600
+ `${artifactPath}: \`${yamlPath}\` has non-canonical value \`"${authType}"\`.`,
2601
+ `Valid values: ${VALID_SET_DISPLAY}.`
2602
+ ];
2603
+ if (suggestion) {
2604
+ lines.push(`Did you mean: \`${suggestion}\`?`);
2605
+ }
2606
+ lines.push("Fix this value and re-call stackwright_pro_validate_artifact.");
2607
+ return lines.join("\n");
2608
+ }
2609
+ function validateApiIntegration(artifact, integrationName) {
2610
+ const artifactFile = integrationName ? `api-config-${integrationName}.json` : "api-config.json";
2611
+ const artifactPath = `.stackwright/artifacts/${artifactFile}`;
2612
+ if (artifact === null || typeof artifact !== "object") {
2613
+ return { valid: true };
2614
+ }
2615
+ const obj = artifact;
2616
+ const errors = [];
2617
+ if ("auth" in obj && obj["auth"] !== null && typeof obj["auth"] === "object") {
2618
+ const auth = obj["auth"];
2619
+ const err = validateAuthType(auth["type"], "auth.type", artifactPath);
2620
+ if (err) errors.push(err);
2621
+ }
2622
+ if ("securitySchemes" in obj && obj["securitySchemes"] !== null && typeof obj["securitySchemes"] === "object" && !Array.isArray(obj["securitySchemes"])) {
2623
+ const schemes = obj["securitySchemes"];
2624
+ for (const [schemeName, scheme] of Object.entries(schemes)) {
2625
+ if (scheme !== null && typeof scheme === "object") {
2626
+ const s = scheme;
2627
+ const err = validateAuthType(s["type"], `securitySchemes.${schemeName}.type`, artifactPath);
2628
+ if (err) errors.push(err);
2629
+ }
2630
+ }
2631
+ }
2632
+ if (errors.length === 0) return { valid: true };
2633
+ const retryPrompt = [
2634
+ `API integration auth validation failed: ${errors.length} violation(s) in ${artifactPath}.`,
2635
+ "",
2636
+ "You MUST fix ALL violations and re-call stackwright_pro_validate_artifact.",
2637
+ "",
2638
+ ...errors.flatMap((e) => [e, ""]),
2639
+ `Canonical auth.type values: ${VALID_SET_DISPLAY}.`,
2640
+ "Do NOT use hyphenated or underscored variants (api-key, oauth-2, bearer-token, etc.)."
2641
+ ].join("\n");
2642
+ return { valid: false, retryPrompt };
2643
+ }
2644
+
2645
+ // src/tools/validate-auth-config.ts
2646
+ var KNOWN_NESTED_STRATEGIES = ["oidc", "pki"];
2647
+ var NESTED_STRATEGY_FIELD_EXAMPLES = {
2648
+ oidc: ["discoveryUrl", "clientId", "clientSecret"],
2649
+ pki: ["profile", "source", "headerPrefix"]
2650
+ };
2651
+ function nestedBlockError(strategy, nestedObj, artifactPath) {
2652
+ const exampleFields = NESTED_STRATEGY_FIELD_EXAMPLES[strategy];
2653
+ const actualKeys = Object.keys(nestedObj).slice(0, 2);
2654
+ const showField0 = actualKeys[0] ?? exampleFields[0];
2655
+ const showField1 = actualKeys[1] ?? exampleFields[1];
2656
+ return [
2657
+ `${artifactPath}: \`authConfig.${strategy}\` must not be a nested provider block.`,
2658
+ `Hoist \`authConfig.${strategy}.*\` fields to \`authConfig.*\`. The canonical AuthConfig`,
2659
+ `schema does not nest provider-specific fields under a named sub-object.`,
2660
+ ``,
2661
+ `Bad (nested block):`,
2662
+ ` "authConfig": {`,
2663
+ ` "type": "${strategy}",`,
2664
+ ` "${strategy}": {`,
2665
+ ` "${showField0}": "...",`,
2666
+ ` "${showField1}": "...",`,
2667
+ ` ...`,
2668
+ ` }`,
2669
+ ` }`,
2670
+ ``,
2671
+ `Correct (hoisted to root of authConfig):`,
2672
+ ` "authConfig": {`,
2673
+ ` "type": "${strategy}",`,
2674
+ ` "${exampleFields[0]}": "...",`,
2675
+ ` "${exampleFields[1]}": "...",`,
2676
+ ` "${exampleFields[2]}": "...",`,
2677
+ ` ...`,
2678
+ ` }`,
2679
+ ``,
2680
+ `Move every key from \`authConfig.${strategy}\` up to \`authConfig\` and remove the \`"${strategy}"\` sub-object.`
2681
+ ].join("\n");
2682
+ }
2683
+ function discriminatorKeyError(methodVal, artifactPath) {
2684
+ return [
2685
+ `${artifactPath}: \`authConfig.method\` is not a valid key in the canonical AuthConfig schema.`,
2686
+ `Rename \`authConfig.method\` \u2192 \`authConfig.type\`. The canonical AuthConfig schema uses`,
2687
+ `\`type\` as the auth-strategy discriminator, matching the api-config \`auth.type\` convention`,
2688
+ `shipped in swp-fimq.`,
2689
+ ``,
2690
+ `Bad:`,
2691
+ ` "authConfig": { "method": "${methodVal}", ... }`,
2692
+ ``,
2693
+ `Correct:`,
2694
+ ` "authConfig": { "type": "${methodVal}", ... }`
2695
+ ].join("\n");
2696
+ }
2697
+ function ambiguousDiscriminatorError(methodVal, typeVal, artifactPath) {
2698
+ return [
2699
+ `${artifactPath}: \`authConfig\` has both \`method: "${methodVal}"\` and \`type: "${typeVal}"\` \u2014 ambiguous discriminator.`,
2700
+ `Remove \`authConfig.method\` and keep only \`authConfig.type\`. The canonical AuthConfig`,
2701
+ `schema uses \`type\` as the sole auth-strategy discriminator.`
2702
+ ].join("\n");
2703
+ }
2704
+ function validateAuthConfig(artifact, artifactPath) {
2705
+ if (artifact === null || typeof artifact !== "object") {
2706
+ return { valid: true };
2707
+ }
2708
+ const obj = artifact;
2709
+ if (!("authConfig" in obj) || obj["authConfig"] === void 0 || obj["authConfig"] === null) {
2710
+ return { valid: true };
2711
+ }
2712
+ if (typeof obj["authConfig"] !== "object" || Array.isArray(obj["authConfig"])) {
2713
+ return { valid: true };
2714
+ }
2715
+ const authConfig = obj["authConfig"];
2716
+ const errors = [];
2717
+ const hasMethod = "method" in authConfig && authConfig["method"] !== void 0;
2718
+ const hasType = "type" in authConfig && authConfig["type"] !== void 0;
2719
+ if (hasMethod && hasType) {
2720
+ errors.push(
2721
+ ambiguousDiscriminatorError(
2722
+ String(authConfig["method"]),
2723
+ String(authConfig["type"]),
2724
+ artifactPath
2725
+ )
2726
+ );
2727
+ } else if (hasMethod && !hasType) {
2728
+ errors.push(discriminatorKeyError(String(authConfig["method"]), artifactPath));
2729
+ }
2730
+ for (const strategy of KNOWN_NESTED_STRATEGIES) {
2731
+ const nested = authConfig[strategy];
2732
+ if (nested !== null && nested !== void 0 && typeof nested === "object" && !Array.isArray(nested)) {
2733
+ errors.push(nestedBlockError(strategy, nested, artifactPath));
2734
+ }
2735
+ }
2736
+ if (errors.length === 0) return { valid: true };
2737
+ const retryPrompt = [
2738
+ `Auth config validation failed: ${errors.length} violation(s) in ${artifactPath}.`,
2739
+ ``,
2740
+ `You MUST fix ALL violations and re-call stackwright_pro_validate_artifact.`,
2741
+ ``,
2742
+ ...errors.flatMap((e) => [e, ""]),
2743
+ `The canonical authConfig shape uses \`type\` (not \`method\`) as the discriminator,`,
2744
+ `with all provider-specific fields hoisted to the root of \`authConfig\`.`,
2745
+ `AuthConfig is a discriminated union on \`type: 'oidc' | 'pki'\`.`
2746
+ ].join("\n");
2747
+ return { valid: false, retryPrompt };
2748
+ }
2749
+
2750
+ // src/tools/auth-manifest-aggregator.ts
2751
+ var import_fs6 = require("fs");
2752
+ var import_path6 = require("path");
2753
+ function extractPatternBaseSlug(pattern) {
2754
+ const stripped = pattern.replace(/^\//, "");
2755
+ if (!stripped) return null;
2756
+ return stripped.split("/")[0] ?? null;
2757
+ }
2758
+ function extractManifestSlugs(artifactsDir) {
2759
+ const slugs = /* @__PURE__ */ new Set();
2760
+ if (!(0, import_fs6.existsSync)(artifactsDir)) return slugs;
2761
+ let entries;
2762
+ try {
2763
+ entries = (0, import_fs6.readdirSync)(artifactsDir);
2764
+ } catch {
2765
+ return slugs;
2766
+ }
2767
+ for (const entry of entries) {
2768
+ if (!entry.endsWith("-manifest.json")) continue;
2769
+ try {
2770
+ const content = JSON.parse((0, import_fs6.readFileSync)((0, import_path6.join)(artifactsDir, entry), "utf-8"));
2771
+ const pages = content.pages;
2772
+ if (!Array.isArray(pages)) continue;
2773
+ for (const page of pages) {
2774
+ if (typeof page.slug !== "string" || !page.slug) continue;
2775
+ const firstSeg = page.slug.split("/")[0];
2776
+ if (firstSeg) slugs.add(firstSeg);
2777
+ if (!page.slug.includes("[") && !page.slug.includes(":")) {
2778
+ slugs.add(page.slug);
2779
+ }
2780
+ }
2781
+ } catch {
2782
+ }
2783
+ }
2784
+ return slugs;
2785
+ }
2786
+
2555
2787
  // src/tools/pipeline.ts
2556
2788
  var import_types3 = require("@stackwright-pro/types");
2557
2789
 
@@ -2890,8 +3122,8 @@ function registerGetSchemaTool(server2) {
2890
3122
  }
2891
3123
 
2892
3124
  // src/tools/pipeline-graph.ts
2893
- var import_fs6 = require("fs");
2894
- var import_path6 = require("path");
3125
+ var import_fs7 = require("fs");
3126
+ var import_path7 = require("path");
2895
3127
  var MANIFEST_NAME_TO_PHASE = {
2896
3128
  "stackwright-pro-designer-otter": "designer",
2897
3129
  "stackwright-pro-theme-otter": "theme",
@@ -2921,9 +3153,9 @@ var OTTER_SEARCH_PATHS = [
2921
3153
  function resolveOtterDirFromCwd() {
2922
3154
  const cwd = process.cwd();
2923
3155
  for (const relative2 of OTTER_SEARCH_PATHS) {
2924
- const candidate = (0, import_path6.join)(cwd, relative2);
3156
+ const candidate = (0, import_path7.join)(cwd, relative2);
2925
3157
  try {
2926
- (0, import_fs6.lstatSync)(candidate);
3158
+ (0, import_fs7.lstatSync)(candidate);
2927
3159
  return candidate;
2928
3160
  } catch {
2929
3161
  }
@@ -2933,14 +3165,14 @@ function resolveOtterDirFromCwd() {
2933
3165
  );
2934
3166
  }
2935
3167
  function loadOtterManifestsFromDir(otterDir) {
2936
- const entries = (0, import_fs6.readdirSync)(otterDir);
3168
+ const entries = (0, import_fs7.readdirSync)(otterDir);
2937
3169
  const manifests = [];
2938
3170
  for (const filename of entries) {
2939
3171
  if (!filename.endsWith("-otter.json")) continue;
2940
- const filePath = (0, import_path6.join)(otterDir, filename);
2941
- if ((0, import_fs6.lstatSync)(filePath).isSymbolicLink()) continue;
3172
+ const filePath = (0, import_path7.join)(otterDir, filename);
3173
+ if ((0, import_fs7.lstatSync)(filePath).isSymbolicLink()) continue;
2942
3174
  try {
2943
- const raw = (0, import_fs6.readFileSync)(filePath, "utf-8");
3175
+ const raw = (0, import_fs7.readFileSync)(filePath, "utf-8");
2944
3176
  const manifest = JSON.parse(raw);
2945
3177
  manifests.push(manifest);
2946
3178
  } catch (err) {
@@ -3045,19 +3277,19 @@ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
3045
3277
  function loadDistributedOtterManifests(cwd) {
3046
3278
  const results = [];
3047
3279
  for (const namespace of DISTRIBUTED_NAMESPACES) {
3048
- const nsDir = (0, import_path6.join)(cwd, "node_modules", namespace);
3280
+ const nsDir = (0, import_path7.join)(cwd, "node_modules", namespace);
3049
3281
  let pkgDirNames;
3050
3282
  try {
3051
- pkgDirNames = (0, import_fs6.readdirSync)(nsDir);
3283
+ pkgDirNames = (0, import_fs7.readdirSync)(nsDir);
3052
3284
  } catch {
3053
3285
  continue;
3054
3286
  }
3055
3287
  for (const pkgDirName of pkgDirNames) {
3056
- const pkgDir = (0, import_path6.join)(nsDir, pkgDirName);
3288
+ const pkgDir = (0, import_path7.join)(nsDir, pkgDirName);
3057
3289
  const packageName = `${namespace}/${pkgDirName}`;
3058
3290
  let otterRelPaths;
3059
3291
  try {
3060
- const raw = (0, import_fs6.readFileSync)((0, import_path6.join)(pkgDir, "package.json"), "utf-8");
3292
+ const raw = (0, import_fs7.readFileSync)((0, import_path7.join)(pkgDir, "package.json"), "utf-8");
3061
3293
  const pkgJson = JSON.parse(raw);
3062
3294
  const declared = pkgJson.stackwright?.otters;
3063
3295
  if (!Array.isArray(declared) || declared.length === 0) continue;
@@ -3066,9 +3298,9 @@ function loadDistributedOtterManifests(cwd) {
3066
3298
  continue;
3067
3299
  }
3068
3300
  for (const relPath of otterRelPaths) {
3069
- const otterFilePath = (0, import_path6.join)(pkgDir, relPath);
3301
+ const otterFilePath = (0, import_path7.join)(pkgDir, relPath);
3070
3302
  try {
3071
- const raw = (0, import_fs6.readFileSync)(otterFilePath, "utf-8");
3303
+ const raw = (0, import_fs7.readFileSync)(otterFilePath, "utf-8");
3072
3304
  const manifest = JSON.parse(raw);
3073
3305
  results.push({ manifest, packageName });
3074
3306
  } catch (err) {
@@ -3186,11 +3418,11 @@ function createDefaultState() {
3186
3418
  };
3187
3419
  }
3188
3420
  function statePath(cwd) {
3189
- return (0, import_path7.join)(cwd, ".stackwright", "pipeline-state.json");
3421
+ return (0, import_path8.join)(cwd, ".stackwright", "pipeline-state.json");
3190
3422
  }
3191
3423
  function readState(cwd) {
3192
3424
  const p = statePath(cwd);
3193
- if (!(0, import_fs7.existsSync)(p)) return createDefaultState();
3425
+ if (!(0, import_fs8.existsSync)(p)) return createDefaultState();
3194
3426
  const raw = JSON.parse(safeReadSync(p));
3195
3427
  if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
3196
3428
  return createDefaultState();
@@ -3198,34 +3430,34 @@ function readState(cwd) {
3198
3430
  return raw;
3199
3431
  }
3200
3432
  function safeWriteSync(filePath, content) {
3201
- if ((0, import_fs7.existsSync)(filePath)) {
3202
- const stat = (0, import_fs7.lstatSync)(filePath);
3433
+ if ((0, import_fs8.existsSync)(filePath)) {
3434
+ const stat = (0, import_fs8.lstatSync)(filePath);
3203
3435
  if (stat.isSymbolicLink()) {
3204
3436
  throw new Error(`Refusing to write to symlink: ${filePath}`);
3205
3437
  }
3206
3438
  }
3207
- (0, import_fs7.writeFileSync)(filePath, content);
3439
+ (0, import_fs8.writeFileSync)(filePath, content);
3208
3440
  }
3209
3441
  function safeReadSync(filePath) {
3210
- if ((0, import_fs7.existsSync)(filePath)) {
3211
- const stat = (0, import_fs7.lstatSync)(filePath);
3442
+ if ((0, import_fs8.existsSync)(filePath)) {
3443
+ const stat = (0, import_fs8.lstatSync)(filePath);
3212
3444
  if (stat.isSymbolicLink()) {
3213
3445
  throw new Error(`Refusing to read symlink: ${filePath}`);
3214
3446
  }
3215
3447
  }
3216
- return (0, import_fs7.readFileSync)(filePath, "utf-8");
3448
+ return (0, import_fs8.readFileSync)(filePath, "utf-8");
3217
3449
  }
3218
3450
  function writeState(cwd, state) {
3219
- const dir = (0, import_path7.join)(cwd, ".stackwright");
3220
- (0, import_fs7.mkdirSync)(dir, { recursive: true });
3451
+ const dir = (0, import_path8.join)(cwd, ".stackwright");
3452
+ (0, import_fs8.mkdirSync)(dir, { recursive: true });
3221
3453
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3222
3454
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
3223
3455
  }
3224
3456
  function updateStateAtomic(cwd, mutator) {
3225
- const dir = (0, import_path7.join)(cwd, ".stackwright");
3226
- (0, import_fs7.mkdirSync)(dir, { recursive: true });
3457
+ const dir = (0, import_path8.join)(cwd, ".stackwright");
3458
+ (0, import_fs8.mkdirSync)(dir, { recursive: true });
3227
3459
  const path5 = statePath(cwd);
3228
- if (!(0, import_fs7.existsSync)(path5)) {
3460
+ if (!(0, import_fs8.existsSync)(path5)) {
3229
3461
  safeWriteSync(path5, JSON.stringify(createDefaultState(), null, 2) + "\n");
3230
3462
  }
3231
3463
  const release = (0, import_proper_lockfile.lockSync)(path5, {
@@ -3259,8 +3491,8 @@ function handleGetPipelineState(_cwd) {
3259
3491
  const cwd = _cwd ?? process.cwd();
3260
3492
  try {
3261
3493
  const state = readState(cwd);
3262
- const keyPath = (0, import_path7.join)(cwd, ".stackwright", "pipeline-keys.json");
3263
- if (!(0, import_fs7.existsSync)(keyPath)) {
3494
+ const keyPath = (0, import_path8.join)(cwd, ".stackwright", "pipeline-keys.json");
3495
+ if (!(0, import_fs8.existsSync)(keyPath)) {
3264
3496
  try {
3265
3497
  const { fingerprint } = initPipelineKeys(cwd);
3266
3498
  state["signingKeyFingerprint"] = fingerprint;
@@ -3391,8 +3623,8 @@ function handleCheckExecutionReady(_cwd, phase) {
3391
3623
  isError: true
3392
3624
  };
3393
3625
  }
3394
- const answerFile = (0, import_path7.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3395
- if (!(0, import_fs7.existsSync)(answerFile)) {
3626
+ const answerFile = (0, import_path8.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3627
+ if (!(0, import_fs8.existsSync)(answerFile)) {
3396
3628
  return {
3397
3629
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
3398
3630
  isError: false
@@ -3416,12 +3648,12 @@ function handleCheckExecutionReady(_cwd, phase) {
3416
3648
  }
3417
3649
  }
3418
3650
  try {
3419
- const answersDir = (0, import_path7.join)(cwd, ".stackwright", "answers");
3651
+ const answersDir = (0, import_path8.join)(cwd, ".stackwright", "answers");
3420
3652
  const answeredPhases = [];
3421
3653
  const missingPhases = [];
3422
3654
  for (const phase2 of PHASE_ORDER) {
3423
- const answerFile = (0, import_path7.join)(answersDir, `${phase2}.json`);
3424
- if ((0, import_fs7.existsSync)(answerFile)) {
3655
+ const answerFile = (0, import_path8.join)(answersDir, `${phase2}.json`);
3656
+ if ((0, import_fs8.existsSync)(answerFile)) {
3425
3657
  try {
3426
3658
  const raw = safeReadSync(answerFile);
3427
3659
  const parsed = JSON.parse(raw);
@@ -3490,7 +3722,7 @@ function handleGetReadyPhases(_cwd) {
3490
3722
  function handleListArtifacts(_cwd) {
3491
3723
  const cwd = _cwd ?? process.cwd();
3492
3724
  try {
3493
- const artifactsDir = (0, import_path7.join)(cwd, ".stackwright", "artifacts");
3725
+ const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
3494
3726
  let manifest = null;
3495
3727
  try {
3496
3728
  manifest = loadSignatureManifest(cwd);
@@ -3500,8 +3732,8 @@ function handleListArtifacts(_cwd) {
3500
3732
  let completedCount = 0;
3501
3733
  for (const phase of PHASE_ORDER) {
3502
3734
  const expectedFile = PHASE_ARTIFACT[phase];
3503
- const fullPath = (0, import_path7.join)(artifactsDir, expectedFile);
3504
- const exists = (0, import_fs7.existsSync)(fullPath);
3735
+ const fullPath = (0, import_path8.join)(artifactsDir, expectedFile);
3736
+ const exists = (0, import_fs8.existsSync)(fullPath);
3505
3737
  if (exists) completedCount++;
3506
3738
  let signed = false;
3507
3739
  let signatureValid = null;
@@ -3554,9 +3786,9 @@ function handleWritePhaseQuestions(input) {
3554
3786
  }
3555
3787
  } catch {
3556
3788
  }
3557
- const questionsDir = (0, import_path7.join)(cwd, ".stackwright", "questions");
3558
- (0, import_fs7.mkdirSync)(questionsDir, { recursive: true });
3559
- const filePath = (0, import_path7.join)(questionsDir, `${phase}.json`);
3789
+ const questionsDir = (0, import_path8.join)(cwd, ".stackwright", "questions");
3790
+ (0, import_fs8.mkdirSync)(questionsDir, { recursive: true });
3791
+ const filePath = (0, import_path8.join)(questionsDir, `${phase}.json`);
3560
3792
  const payload = {
3561
3793
  version: "1.0",
3562
3794
  phase,
@@ -3596,14 +3828,14 @@ function handleBuildSpecialistPrompt(input) {
3596
3828
  };
3597
3829
  }
3598
3830
  try {
3599
- const answersPath = (0, import_path7.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3831
+ const answersPath = (0, import_path8.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3600
3832
  let answers = {};
3601
- if ((0, import_fs7.existsSync)(answersPath)) {
3833
+ if ((0, import_fs8.existsSync)(answersPath)) {
3602
3834
  answers = JSON.parse(safeReadSync(answersPath));
3603
3835
  }
3604
3836
  let buildContextText = "";
3605
- const buildContextPath = (0, import_path7.join)(cwd, ".stackwright", "build-context.json");
3606
- if ((0, import_fs7.existsSync)(buildContextPath)) {
3837
+ const buildContextPath = (0, import_path8.join)(cwd, ".stackwright", "build-context.json");
3838
+ if ((0, import_fs8.existsSync)(buildContextPath)) {
3607
3839
  try {
3608
3840
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
3609
3841
  if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
@@ -3618,8 +3850,8 @@ function handleBuildSpecialistPrompt(input) {
3618
3850
  const missingDependencies = [];
3619
3851
  for (const dep of deps) {
3620
3852
  const artifactFile = PHASE_ARTIFACT[dep];
3621
- const artifactPath = (0, import_path7.join)(cwd, ".stackwright", "artifacts", artifactFile);
3622
- if ((0, import_fs7.existsSync)(artifactPath)) {
3853
+ const artifactPath = (0, import_path8.join)(cwd, ".stackwright", "artifacts", artifactFile);
3854
+ if ((0, import_fs8.existsSync)(artifactPath)) {
3623
3855
  const rawContent = safeReadSync(artifactPath);
3624
3856
  const rawBytes = Buffer.from(rawContent, "utf-8");
3625
3857
  const content = JSON.parse(rawContent);
@@ -3684,8 +3916,8 @@ ${JSON.stringify(content, null, 2)}`
3684
3916
  parts.push("BUILD_CONTEXT:", buildContextText, "");
3685
3917
  }
3686
3918
  if (phase === "auth") {
3687
- const initContextPath = (0, import_path7.join)(cwd, ".stackwright", "init-context.json");
3688
- if ((0, import_fs7.existsSync)(initContextPath)) {
3919
+ const initContextPath = (0, import_path8.join)(cwd, ".stackwright", "init-context.json");
3920
+ if ((0, import_fs8.existsSync)(initContextPath)) {
3689
3921
  try {
3690
3922
  const initContext = JSON.parse(safeReadSync(initContextPath));
3691
3923
  if (initContext.devOnly === true || initContext.nonInteractive === true) {
@@ -4136,6 +4368,61 @@ function _validateArtifactInner(input) {
4136
4368
  };
4137
4369
  return { text: JSON.stringify(result), isError: false };
4138
4370
  }
4371
+ if (phase === "auth") {
4372
+ const authArtifactPath = `.stackwright/artifacts/${PHASE_ARTIFACT["auth"]}`;
4373
+ const authShapeResult = validateAuthConfig(artifact, authArtifactPath);
4374
+ if (!authShapeResult.valid) {
4375
+ const result = {
4376
+ valid: false,
4377
+ phase,
4378
+ violation: "schema-mismatch",
4379
+ retryPrompt: authShapeResult.retryPrompt
4380
+ };
4381
+ return { text: JSON.stringify(result), isError: false };
4382
+ }
4383
+ }
4384
+ if (phase === "auth") {
4385
+ const rawUncovered = artifact["uncoveredSlugs"];
4386
+ if (Array.isArray(rawUncovered) && rawUncovered.length > 0) {
4387
+ const otterFromSource = (source) => {
4388
+ if (source === "geo-manifest.json") return "geo-otter";
4389
+ if (source === "dashboard-manifest.json") return "dashboard-otter";
4390
+ if (source === "pages-manifest.json") return "page-otter";
4391
+ return source || "(unknown)";
4392
+ };
4393
+ const slugLines = rawUncovered.map((entry) => {
4394
+ const e = entry ?? {};
4395
+ const source = typeof e["source"] === "string" ? e["source"] : "(unknown)";
4396
+ const slug = typeof e["slug"] === "string" ? e["slug"] : "(unknown)";
4397
+ return ` ${source} \u2192 ${slug} (emitted by ${otterFromSource(source)})`;
4398
+ });
4399
+ const retryPrompt = [
4400
+ "Auth-config cannot be finalized: the following page slugs have no auth declaration in their emitting otter's manifest:",
4401
+ "",
4402
+ ...slugLines,
4403
+ "",
4404
+ "This is a HARD build-gate failure \u2014 auth-otter cannot self-correct because the root cause is upstream.",
4405
+ "",
4406
+ "Fix: update the offending emitter otter to declare `authRequired: true` + `requiredRoles: [...]`",
4407
+ "(and optionally `tier`, `authRationale`) in the manifest entry for each listed slug.",
4408
+ "",
4409
+ "For map pages: geo-otter (see swp-1114).",
4410
+ "For dashboard pages: dashboard-otter (see swp-52lk).",
4411
+ "For page pages: page-otter (already does this \u2014 check for regressions).",
4412
+ "For workflow pages: form-wizard-otter emits routes but auth is derived from the pages-manifest",
4413
+ "workflow-wrapper entry \u2014 check page-otter didn't skip a workflow-wrapper.",
4414
+ "",
4415
+ "After updating the offending manifest, re-run the pipeline from the affected phase."
4416
+ ].join("\n");
4417
+ const result = {
4418
+ valid: false,
4419
+ phase,
4420
+ violation: "uncovered-slugs",
4421
+ retryPrompt
4422
+ };
4423
+ return { text: JSON.stringify(result), isError: false };
4424
+ }
4425
+ }
4139
4426
  const PHASE_ZOD_VALIDATORS = {
4140
4427
  workflow: (artifact2) => {
4141
4428
  const workflowData = artifact2["workflow"];
@@ -4186,33 +4473,60 @@ function _validateArtifactInner(input) {
4186
4473
  return { success: true };
4187
4474
  },
4188
4475
  // swp-4071 — opt-in cross-reference validator for the services phase.
4189
- // If workflow-config exists and declares serviceHooks, every hook ref must resolve
4190
- // to a flow or state-machine in this services-config artifact.
4476
+ // Collects serviceHooks from ALL workflow-config-*.json artifacts (swp-x8sm fan-out)
4477
+ // plus the legacy single-file workflow-config.json (backward compat).
4478
+ // If any workflow declared serviceHooks, every ref must resolve to a flow or
4479
+ // state-machine in this services-config artifact.
4191
4480
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
4192
4481
  services: (artifact2) => {
4193
- const workflowArtifactPath = (0, import_path7.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
4194
- if (!(0, import_fs7.existsSync)(workflowArtifactPath)) {
4195
- return { success: true };
4482
+ const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
4483
+ const workflowArtifactFiles = [];
4484
+ if ((0, import_fs8.existsSync)(artifactsDir)) {
4485
+ try {
4486
+ const entries = (0, import_fs8.readdirSync)(artifactsDir);
4487
+ for (const entry of entries) {
4488
+ if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
4489
+ workflowArtifactFiles.push((0, import_path8.join)(artifactsDir, entry));
4490
+ }
4491
+ }
4492
+ } catch {
4493
+ }
4196
4494
  }
4197
- let workflowArtifact;
4198
- try {
4199
- workflowArtifact = JSON.parse((0, import_fs7.readFileSync)(workflowArtifactPath, "utf-8"));
4200
- } catch {
4495
+ const legacyPath = (0, import_path8.join)(artifactsDir, "workflow-config.json");
4496
+ if ((0, import_fs8.existsSync)(legacyPath)) {
4497
+ workflowArtifactFiles.push(legacyPath);
4498
+ }
4499
+ if (workflowArtifactFiles.length === 0) {
4201
4500
  return { success: true };
4202
4501
  }
4203
- const declaredHooks = workflowArtifact.serviceHooks ?? [];
4204
- if (declaredHooks.length === 0) {
4502
+ const declaredHooks = [];
4503
+ for (const filePath of workflowArtifactFiles) {
4504
+ try {
4505
+ const parsed = JSON.parse((0, import_fs8.readFileSync)(filePath, "utf-8"));
4506
+ if (Array.isArray(parsed.serviceHooks)) {
4507
+ declaredHooks.push(...parsed.serviceHooks);
4508
+ }
4509
+ } catch {
4510
+ }
4511
+ }
4512
+ const seenRefs = /* @__PURE__ */ new Set();
4513
+ const uniqueHooks = declaredHooks.filter((h) => {
4514
+ if (seenRefs.has(h.ref)) return false;
4515
+ seenRefs.add(h.ref);
4516
+ return true;
4517
+ });
4518
+ if (uniqueHooks.length === 0) {
4205
4519
  return { success: true };
4206
4520
  }
4207
4521
  const flows = artifact2["flows"] ?? [];
4208
4522
  const workflows = artifact2["workflows"] ?? [];
4209
4523
  const flowNames = /* @__PURE__ */ new Set([...flows.map((f) => f.name), ...workflows.map((w) => w.name)]);
4210
- const unresolved = declaredHooks.filter((h) => !flowNames.has(h.ref));
4524
+ const unresolved = uniqueHooks.filter((h) => !flowNames.has(h.ref));
4211
4525
  if (unresolved.length > 0) {
4212
4526
  return {
4213
4527
  success: false,
4214
4528
  error: {
4215
- 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."
4529
+ 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."
4216
4530
  }
4217
4531
  };
4218
4532
  }
@@ -4278,11 +4592,55 @@ function _validateArtifactInner(input) {
4278
4592
  return { text: JSON.stringify(result), isError: false };
4279
4593
  }
4280
4594
  }
4595
+ if (phase === "api") {
4596
+ const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
4597
+ if (!apiAuthResult.valid) {
4598
+ const result = {
4599
+ valid: false,
4600
+ phase,
4601
+ violation: "schema-mismatch",
4602
+ retryPrompt: apiAuthResult.retryPrompt
4603
+ };
4604
+ return { text: JSON.stringify(result), isError: false };
4605
+ }
4606
+ }
4607
+ if (phase === "workflow" && input.workflowName !== void 0) {
4608
+ if (!isValidSlug(input.workflowName)) {
4609
+ const result = {
4610
+ valid: false,
4611
+ phase,
4612
+ violation: "schema-mismatch",
4613
+ 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".`
4614
+ };
4615
+ return { text: JSON.stringify(result), isError: false };
4616
+ }
4617
+ }
4618
+ let authOrphanWarning;
4619
+ if (phase === "auth") {
4620
+ const authCfg = artifact["authConfig"];
4621
+ const routes = authCfg?.["protectedRoutes"];
4622
+ if (Array.isArray(routes) && routes.length > 0) {
4623
+ const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
4624
+ const knownSlugs = extractManifestSlugs(artifactsDir);
4625
+ const orphanPatterns = [];
4626
+ for (const route of routes) {
4627
+ const pattern = typeof route === "string" ? route : route.pattern ?? "";
4628
+ if (!pattern) continue;
4629
+ const baseSlug = extractPatternBaseSlug(pattern);
4630
+ if (baseSlug && !knownSlugs.has(baseSlug)) {
4631
+ orphanPatterns.push(pattern);
4632
+ }
4633
+ }
4634
+ if (orphanPatterns.length > 0) {
4635
+ 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.`;
4636
+ }
4637
+ }
4638
+ }
4281
4639
  try {
4282
- const artifactsDir = (0, import_path7.join)(cwd, ".stackwright", "artifacts");
4283
- (0, import_fs7.mkdirSync)(artifactsDir, { recursive: true });
4284
- const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : PHASE_ARTIFACT[phase];
4285
- const artifactPath = (0, import_path7.join)(artifactsDir, artifactFile);
4640
+ const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
4641
+ (0, import_fs8.mkdirSync)(artifactsDir, { recursive: true });
4642
+ const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
4643
+ const artifactPath = (0, import_path8.join)(artifactsDir, artifactFile);
4286
4644
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
4287
4645
  const artifactBytes = Buffer.from(serialized, "utf-8");
4288
4646
  safeWriteSync(artifactPath, serialized);
@@ -4305,7 +4663,8 @@ function _validateArtifactInner(input) {
4305
4663
  valid: true,
4306
4664
  phase,
4307
4665
  artifactPath,
4308
- summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`
4666
+ summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`,
4667
+ ...authOrphanWarning ? { retryPrompt: authOrphanWarning } : {}
4309
4668
  };
4310
4669
  return { text: JSON.stringify(result), isError: false };
4311
4670
  } catch (err) {
@@ -4501,10 +4860,10 @@ function registerPipelineTools(server2) {
4501
4860
  isError: true
4502
4861
  };
4503
4862
  }
4504
- const questionsDir = (0, import_path7.join)(process.cwd(), ".stackwright", "questions");
4505
- (0, import_fs7.mkdirSync)(questionsDir, { recursive: true });
4506
- const outPath = (0, import_path7.join)(questionsDir, `${phase}.json`);
4507
- (0, import_fs7.writeFileSync)(
4863
+ const questionsDir = (0, import_path8.join)(process.cwd(), ".stackwright", "questions");
4864
+ (0, import_fs8.mkdirSync)(questionsDir, { recursive: true });
4865
+ const outPath = (0, import_path8.join)(questionsDir, `${phase}.json`);
4866
+ (0, import_fs8.writeFileSync)(
4508
4867
  outPath,
4509
4868
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
4510
4869
  );
@@ -4543,15 +4902,19 @@ function registerPipelineTools(server2) {
4543
4902
  ),
4544
4903
  integrationName: import_zod13.z.string().optional().describe(
4545
4904
  'When phase="api": write to api-config-<integrationName>.json instead of api-config.json. Enables concurrent per-spec fan-out without collision (swp-530y).'
4905
+ ),
4906
+ workflowName: import_zod13.z.string().optional().describe(
4907
+ `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.`
4546
4908
  )
4547
4909
  },
4548
- async ({ phase, responseText, artifact, integrationName }) => {
4910
+ async ({ phase, responseText, artifact, integrationName, workflowName }) => {
4549
4911
  if (artifact) {
4550
4912
  return res(
4551
4913
  handleValidateArtifact({
4552
4914
  phase,
4553
4915
  artifact,
4554
- ...integrationName ? { integrationName } : {}
4916
+ ...integrationName ? { integrationName } : {},
4917
+ ...workflowName ? { workflowName } : {}
4555
4918
  })
4556
4919
  );
4557
4920
  }
@@ -4559,7 +4922,8 @@ function registerPipelineTools(server2) {
4559
4922
  handleValidateArtifact({
4560
4923
  phase,
4561
4924
  responseText: responseText ?? "",
4562
- ...integrationName ? { integrationName } : {}
4925
+ ...integrationName ? { integrationName } : {},
4926
+ ...workflowName ? { workflowName } : {}
4563
4927
  })
4564
4928
  );
4565
4929
  }
@@ -4591,8 +4955,8 @@ function registerPipelineTools(server2) {
4591
4955
 
4592
4956
  // src/tools/safe-write.ts
4593
4957
  var import_zod14 = require("zod");
4594
- var import_fs8 = require("fs");
4595
- var import_path8 = require("path");
4958
+ var import_fs9 = require("fs");
4959
+ var import_path9 = require("path");
4596
4960
  var import_telemetry2 = require("@stackwright-pro/telemetry");
4597
4961
  var OTTER_WRITE_ALLOWLISTS = {
4598
4962
  "stackwright-pro-designer-otter": [
@@ -4604,6 +4968,11 @@ var OTTER_WRITE_ALLOWLISTS = {
4604
4968
  prefix: "stackwright.theme.",
4605
4969
  suffix: ".yml",
4606
4970
  description: "Theme config YAML (merged at build time)"
4971
+ },
4972
+ {
4973
+ prefix: "stackwright.yml",
4974
+ suffix: "",
4975
+ description: "Stackwright config (themeName + customTheme writeback)"
4607
4976
  }
4608
4977
  ],
4609
4978
  "stackwright-pro-auth-otter": [
@@ -4658,6 +5027,17 @@ var OTTER_WRITE_ALLOWLISTS = {
4658
5027
  prefix: ".stackwright/artifacts/",
4659
5028
  suffix: ".json",
4660
5029
  description: "Scaffold manifest artifact"
5030
+ },
5031
+ // swp-89w5: scaffold-otter initialises mock backend wiring via stackwright_pro_emit_env_local
5032
+ {
5033
+ prefix: ".env.local",
5034
+ suffix: "",
5035
+ description: "Dotenv runtime config (mock backend wiring: NEXT_PUBLIC_MOCK_BACKEND + per-integration mock keys)"
5036
+ },
5037
+ {
5038
+ prefix: ".env.local.example",
5039
+ suffix: "",
5040
+ description: "Dotenv template (schema for real production values)"
4661
5041
  }
4662
5042
  ],
4663
5043
  "stackwright-pro-api-otter": [
@@ -4758,7 +5138,7 @@ function getMaxBytesForPath(filePath) {
4758
5138
  }
4759
5139
  var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
4760
5140
  function extractPageSlug(filePath) {
4761
- const match = (0, import_path8.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
5141
+ const match = (0, import_path9.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
4762
5142
  return match?.[1] ?? null;
4763
5143
  }
4764
5144
  function extractContentTypes(yamlContent) {
@@ -4774,27 +5154,27 @@ function extractContentTypes(yamlContent) {
4774
5154
  return types.length > 0 ? types : ["unknown"];
4775
5155
  }
4776
5156
  function readPageRegistry(cwd) {
4777
- const regPath = (0, import_path8.join)(cwd, PAGE_REGISTRY_FILE);
4778
- if (!(0, import_fs8.existsSync)(regPath)) return {};
5157
+ const regPath = (0, import_path9.join)(cwd, PAGE_REGISTRY_FILE);
5158
+ if (!(0, import_fs9.existsSync)(regPath)) return {};
4779
5159
  try {
4780
- const stat = (0, import_fs8.lstatSync)(regPath);
5160
+ const stat = (0, import_fs9.lstatSync)(regPath);
4781
5161
  if (stat.isSymbolicLink()) return {};
4782
- return JSON.parse((0, import_fs8.readFileSync)(regPath, "utf-8"));
5162
+ return JSON.parse((0, import_fs9.readFileSync)(regPath, "utf-8"));
4783
5163
  } catch {
4784
5164
  return {};
4785
5165
  }
4786
5166
  }
4787
5167
  function writePageRegistry(cwd, registry) {
4788
- const dir = (0, import_path8.join)(cwd, ".stackwright");
4789
- (0, import_fs8.mkdirSync)(dir, { recursive: true });
4790
- const regPath = (0, import_path8.join)(cwd, PAGE_REGISTRY_FILE);
4791
- if ((0, import_fs8.existsSync)(regPath)) {
4792
- const stat = (0, import_fs8.lstatSync)(regPath);
5168
+ const dir = (0, import_path9.join)(cwd, ".stackwright");
5169
+ (0, import_fs9.mkdirSync)(dir, { recursive: true });
5170
+ const regPath = (0, import_path9.join)(cwd, PAGE_REGISTRY_FILE);
5171
+ if ((0, import_fs9.existsSync)(regPath)) {
5172
+ const stat = (0, import_fs9.lstatSync)(regPath);
4793
5173
  if (stat.isSymbolicLink()) {
4794
5174
  throw new Error("Refusing to write page registry through symlink");
4795
5175
  }
4796
5176
  }
4797
- (0, import_fs8.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
5177
+ (0, import_fs9.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
4798
5178
  }
4799
5179
  function checkPageOwnership(cwd, slug, callerOtter) {
4800
5180
  const registry = readPageRegistry(cwd);
@@ -4813,11 +5193,11 @@ function stripNextjsBracketSegments(path5) {
4813
5193
  return path5.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
4814
5194
  }
4815
5195
  function checkPathAllowed(callerOtter, filePath) {
4816
- const normalized = (0, import_path8.normalize)(filePath);
5196
+ const normalized = (0, import_path9.normalize)(filePath);
4817
5197
  if (stripNextjsBracketSegments(normalized).includes("..")) {
4818
5198
  return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
4819
5199
  }
4820
- if ((0, import_path8.isAbsolute)(normalized)) {
5200
+ if ((0, import_path9.isAbsolute)(normalized)) {
4821
5201
  return {
4822
5202
  allowed: false,
4823
5203
  error: "Absolute paths are not allowed \u2014 use paths relative to project root"
@@ -4959,11 +5339,11 @@ function _safeWriteInner(input) {
4959
5339
  };
4960
5340
  return { text: JSON.stringify(result), isError: true };
4961
5341
  }
4962
- const normalized = (0, import_path8.normalize)(filePath);
4963
- const fullPath = (0, import_path8.join)(cwd, normalized);
4964
- if ((0, import_fs8.existsSync)(fullPath)) {
5342
+ const normalized = (0, import_path9.normalize)(filePath);
5343
+ const fullPath = (0, import_path9.join)(cwd, normalized);
5344
+ if ((0, import_fs9.existsSync)(fullPath)) {
4965
5345
  try {
4966
- const stat = (0, import_fs8.lstatSync)(fullPath);
5346
+ const stat = (0, import_fs9.lstatSync)(fullPath);
4967
5347
  if (stat.isSymbolicLink()) {
4968
5348
  const result = {
4969
5349
  success: false,
@@ -5004,9 +5384,9 @@ function _safeWriteInner(input) {
5004
5384
  }
5005
5385
  try {
5006
5386
  if (createDirectories) {
5007
- (0, import_fs8.mkdirSync)((0, import_path8.dirname)(fullPath), { recursive: true });
5387
+ (0, import_fs9.mkdirSync)((0, import_path9.dirname)(fullPath), { recursive: true });
5008
5388
  }
5009
- (0, import_fs8.writeFileSync)(fullPath, content, { encoding: "utf-8" });
5389
+ (0, import_fs9.writeFileSync)(fullPath, content, { encoding: "utf-8" });
5010
5390
  if (pageSlug) {
5011
5391
  try {
5012
5392
  const registry = readPageRegistry(cwd);
@@ -5091,8 +5471,8 @@ function registerSafeWriteTools(server2) {
5091
5471
 
5092
5472
  // src/tools/auth.ts
5093
5473
  var import_zod15 = require("zod");
5094
- var import_fs9 = require("fs");
5095
- var import_path9 = require("path");
5474
+ var import_fs10 = require("fs");
5475
+ var import_path10 = require("path");
5096
5476
  function buildHierarchy(roles) {
5097
5477
  const h = {};
5098
5478
  for (let i = 0; i < roles.length - 1; i++) {
@@ -5119,9 +5499,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
5119
5499
  }
5120
5500
  function detectNextMajorVersion(cwd) {
5121
5501
  try {
5122
- const pkgPath = (0, import_path9.join)(cwd, "package.json");
5123
- if (!(0, import_fs9.existsSync)(pkgPath)) return null;
5124
- const pkg = JSON.parse((0, import_fs9.readFileSync)(pkgPath, "utf8"));
5502
+ const pkgPath = (0, import_path10.join)(cwd, "package.json");
5503
+ if (!(0, import_fs10.existsSync)(pkgPath)) return null;
5504
+ const pkg = JSON.parse((0, import_fs10.readFileSync)(pkgPath, "utf8"));
5125
5505
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
5126
5506
  if (!nextVersion) return null;
5127
5507
  const match = nextVersion.match(/(\d+)/);
@@ -5785,7 +6165,7 @@ async function configureAuthHandler(params, cwd) {
5785
6165
  auditRetentionDays,
5786
6166
  normalizedRoutes
5787
6167
  );
5788
- (0, import_fs9.writeFileSync)((0, import_path9.join)(cwd, conventionFile), authFileContent, "utf8");
6168
+ (0, import_fs10.writeFileSync)((0, import_path10.join)(cwd, conventionFile), authFileContent, "utf8");
5789
6169
  filesWritten.push(conventionFile);
5790
6170
  } catch (err) {
5791
6171
  const msg = err instanceof Error ? err.message : String(err);
@@ -5805,12 +6185,12 @@ async function configureAuthHandler(params, cwd) {
5805
6185
  if (!devOnly) {
5806
6186
  try {
5807
6187
  const envBlock = generateEnvBlock(effectiveMethod, params);
5808
- const envPath = (0, import_path9.join)(cwd, ".env.example");
5809
- if ((0, import_fs9.existsSync)(envPath)) {
5810
- const existing = (0, import_fs9.readFileSync)(envPath, "utf8");
5811
- (0, import_fs9.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
6188
+ const envPath = (0, import_path10.join)(cwd, ".env.example");
6189
+ if ((0, import_fs10.existsSync)(envPath)) {
6190
+ const existing = (0, import_fs10.readFileSync)(envPath, "utf8");
6191
+ (0, import_fs10.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5812
6192
  } else {
5813
- (0, import_fs9.writeFileSync)(envPath, envBlock, "utf8");
6193
+ (0, import_fs10.writeFileSync)(envPath, envBlock, "utf8");
5814
6194
  }
5815
6195
  filesWritten.push(".env.example");
5816
6196
  } catch (err) {
@@ -5828,10 +6208,10 @@ async function configureAuthHandler(params, cwd) {
5828
6208
  }
5829
6209
  if (devOnly) {
5830
6210
  try {
5831
- const mockAuthDir = (0, import_path9.join)(cwd, "lib");
5832
- (0, import_fs9.mkdirSync)(mockAuthDir, { recursive: true });
6211
+ const mockAuthDir = (0, import_path10.join)(cwd, "lib");
6212
+ (0, import_fs10.mkdirSync)(mockAuthDir, { recursive: true });
5833
6213
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
5834
- (0, import_fs9.writeFileSync)((0, import_path9.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
6214
+ (0, import_fs10.writeFileSync)((0, import_path10.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5835
6215
  filesWritten.push("lib/mock-auth.ts");
5836
6216
  } catch (err) {
5837
6217
  const msg = err instanceof Error ? err.message : String(err);
@@ -5849,11 +6229,11 @@ async function configureAuthHandler(params, cwd) {
5849
6229
  };
5850
6230
  }
5851
6231
  try {
5852
- const pkgPath = (0, import_path9.join)(cwd, "package.json");
5853
- if ((0, import_fs9.existsSync)(pkgPath)) {
5854
- const existingPkg = (0, import_fs9.readFileSync)(pkgPath, "utf8");
6232
+ const pkgPath = (0, import_path10.join)(cwd, "package.json");
6233
+ if ((0, import_fs10.existsSync)(pkgPath)) {
6234
+ const existingPkg = (0, import_fs10.readFileSync)(pkgPath, "utf8");
5855
6235
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
5856
- (0, import_fs9.writeFileSync)(pkgPath, updatedPkg, "utf8");
6236
+ (0, import_fs10.writeFileSync)(pkgPath, updatedPkg, "utf8");
5857
6237
  filesWritten.push("package.json");
5858
6238
  }
5859
6239
  } catch (err) {
@@ -5894,7 +6274,7 @@ async function configureAuthHandler(params, cwd) {
5894
6274
  normalizedRoutes,
5895
6275
  useProxy
5896
6276
  );
5897
- (0, import_fs9.writeFileSync)((0, import_path9.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
6277
+ (0, import_fs10.writeFileSync)((0, import_path10.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
5898
6278
  filesWritten.push("stackwright.auth.yml");
5899
6279
  } catch (err) {
5900
6280
  const msg = err instanceof Error ? err.message : String(err);
@@ -6007,8 +6387,8 @@ function registerAuthTools(server2) {
6007
6387
 
6008
6388
  // src/integrity.ts
6009
6389
  var import_crypto4 = require("crypto");
6010
- var import_fs10 = require("fs");
6011
- var import_path10 = require("path");
6390
+ var import_fs11 = require("fs");
6391
+ var import_path11 = require("path");
6012
6392
  var _checksums = /* @__PURE__ */ new Map([
6013
6393
  [
6014
6394
  "stackwright-pro-api-otter.json",
@@ -6016,11 +6396,11 @@ var _checksums = /* @__PURE__ */ new Map([
6016
6396
  ],
6017
6397
  [
6018
6398
  "stackwright-pro-auth-otter.json",
6019
- "1a21d9f23e250f23291124307e5a2a32af5a716566319a3290372dae3d02e637"
6399
+ "69fa03c5532ef617c86fe7af41e8174c168d282baf602b1b269a7e933ea86b7c"
6020
6400
  ],
6021
6401
  [
6022
6402
  "stackwright-pro-dashboard-otter.json",
6023
- "4a9ac39e929866ae3c929aa5bcbba3d9483a1c251c35f26f267f3e7433506f0c"
6403
+ "f55081bf4d6545e5435128919f6f9233956ba37671fc9f85300f240ad2497ab6"
6024
6404
  ],
6025
6405
  [
6026
6406
  "stackwright-pro-data-otter.json",
@@ -6036,19 +6416,19 @@ var _checksums = /* @__PURE__ */ new Map([
6036
6416
  ],
6037
6417
  [
6038
6418
  "stackwright-pro-foreman-otter.json",
6039
- "9d68b7b20af7a8a1668e2a693b4a2b05cf37acce1e9f6298a0966c97a3417e9f"
6419
+ "88c4101154d44a7c5470ded13562402a39323ee570ed3a90a73b8d85d7fd2817"
6040
6420
  ],
6041
6421
  [
6042
6422
  "stackwright-pro-form-wizard-otter.json",
6043
- "7f6f94192f26face57dc8b2e22e0a49d23ceeeb356ca4ebde05492f1b25e3c47"
6423
+ "cb350297a0068ead2424e703e8c775d91c0b87249ea6904f43b903baf8a84b79"
6044
6424
  ],
6045
6425
  [
6046
6426
  "stackwright-pro-geo-otter.json",
6047
- "fc3d18e02a6147d95d3dd9093ce74c0e8fffaecc2f9ecdbd19c163a80129d264"
6427
+ "3032fbd27de36c0cdc0e19e46a32d771208d8e86714482f5a368d46342c8317a"
6048
6428
  ],
6049
6429
  [
6050
6430
  "stackwright-pro-page-otter.json",
6051
- "879e8ff4aa645bac2c63dc962cad1a549876983c257aec756e95293c3b22ce7e"
6431
+ "cc0db24b00f0130f8b4ec8d385c73ea1bcf5d57ba45aca6b986bcbd5da328930"
6052
6432
  ],
6053
6433
  [
6054
6434
  "stackwright-pro-polish-otter.json",
@@ -6060,7 +6440,7 @@ var _checksums = /* @__PURE__ */ new Map([
6060
6440
  ],
6061
6441
  [
6062
6442
  "stackwright-pro-scaffold-otter.json",
6063
- "92930c732547c90a19e89ee0e25b5f037b7366f770941f0a01e148ff240a1b6d"
6443
+ "90272bab51bd8ed9e25c0fb6481cdd4252386850f64192edfe1e2eff44166e2d"
6064
6444
  ],
6065
6445
  [
6066
6446
  "stackwright-pro-theme-otter.json",
@@ -6068,7 +6448,7 @@ var _checksums = /* @__PURE__ */ new Map([
6068
6448
  ],
6069
6449
  [
6070
6450
  "stackwright-services-otter.json",
6071
- "2a50ceb3fad166251d0ad8709a34a32118645713e0e34628165d16dced1d5c93"
6451
+ "4351348806aeb98404a2bacb044209ca411be5e35e2f2c3d03eb4d28a07fde78"
6072
6452
  ]
6073
6453
  ]);
6074
6454
  Object.freeze(_checksums);
@@ -6091,14 +6471,14 @@ function safeEqual(a, b) {
6091
6471
  return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
6092
6472
  }
6093
6473
  function verifyOtterFile(filePath) {
6094
- const filename = (0, import_path10.basename)(filePath);
6474
+ const filename = (0, import_path11.basename)(filePath);
6095
6475
  const expected = CANONICAL_CHECKSUMS.get(filename);
6096
6476
  if (expected === void 0) {
6097
6477
  return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
6098
6478
  }
6099
6479
  let stat;
6100
6480
  try {
6101
- stat = (0, import_fs10.lstatSync)(filePath);
6481
+ stat = (0, import_fs11.lstatSync)(filePath);
6102
6482
  } catch (err) {
6103
6483
  const msg = err instanceof Error ? err.message : String(err);
6104
6484
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -6116,7 +6496,7 @@ function verifyOtterFile(filePath) {
6116
6496
  }
6117
6497
  let raw;
6118
6498
  try {
6119
- raw = (0, import_fs10.readFileSync)(filePath);
6499
+ raw = (0, import_fs11.readFileSync)(filePath);
6120
6500
  } catch (err) {
6121
6501
  const msg = err instanceof Error ? err.message : String(err);
6122
6502
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -6166,7 +6546,7 @@ function verifyAllOtters(otterDir) {
6166
6546
  const unknown = [];
6167
6547
  let entries;
6168
6548
  try {
6169
- entries = (0, import_fs10.readdirSync)(otterDir);
6549
+ entries = (0, import_fs11.readdirSync)(otterDir);
6170
6550
  } catch (err) {
6171
6551
  const msg = err instanceof Error ? err.message : String(err);
6172
6552
  return {
@@ -6177,9 +6557,9 @@ function verifyAllOtters(otterDir) {
6177
6557
  }
6178
6558
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
6179
6559
  for (const filename of otterFiles) {
6180
- const filePath = (0, import_path10.join)(otterDir, filename);
6560
+ const filePath = (0, import_path11.join)(otterDir, filename);
6181
6561
  try {
6182
- if ((0, import_fs10.lstatSync)(filePath).isSymbolicLink()) {
6562
+ if ((0, import_fs11.lstatSync)(filePath).isSymbolicLink()) {
6183
6563
  failed.push({ filename, error: "Skipped: symlink" });
6184
6564
  continue;
6185
6565
  }
@@ -6205,9 +6585,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
6205
6585
  function resolveOtterDir() {
6206
6586
  const cwd = process.cwd();
6207
6587
  for (const relative2 of DEFAULT_SEARCH_PATHS) {
6208
- const candidate = (0, import_path10.join)(cwd, relative2);
6588
+ const candidate = (0, import_path11.join)(cwd, relative2);
6209
6589
  try {
6210
- (0, import_fs10.lstatSync)(candidate);
6590
+ (0, import_fs11.lstatSync)(candidate);
6211
6591
  return candidate;
6212
6592
  } catch {
6213
6593
  }
@@ -6286,13 +6666,13 @@ function registerIntegrityTools(server2) {
6286
6666
 
6287
6667
  // src/tools/domain.ts
6288
6668
  var import_zod16 = require("zod");
6289
- var import_fs11 = require("fs");
6290
- var import_path11 = require("path");
6669
+ var import_fs12 = require("fs");
6670
+ var import_path12 = require("path");
6291
6671
  function handleListCollections(input) {
6292
6672
  const cwd = input._cwd ?? process.cwd();
6293
6673
  const sources = [
6294
6674
  {
6295
- path: (0, import_path11.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
6675
+ path: (0, import_path12.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
6296
6676
  source: "data-config.json",
6297
6677
  parse: (raw) => {
6298
6678
  const parsed = JSON.parse(raw);
@@ -6303,15 +6683,15 @@ function handleListCollections(input) {
6303
6683
  }
6304
6684
  },
6305
6685
  {
6306
- path: (0, import_path11.join)(cwd, "stackwright.yml"),
6686
+ path: (0, import_path12.join)(cwd, "stackwright.yml"),
6307
6687
  source: "stackwright.yml",
6308
6688
  parse: extractCollectionsFromYaml
6309
6689
  }
6310
6690
  ];
6311
6691
  for (const { path: path5, source, parse } of sources) {
6312
- if (!(0, import_fs11.existsSync)(path5)) continue;
6692
+ if (!(0, import_fs12.existsSync)(path5)) continue;
6313
6693
  try {
6314
- const collections = parse((0, import_fs11.readFileSync)(path5, "utf8"));
6694
+ const collections = parse((0, import_fs12.readFileSync)(path5, "utf8"));
6315
6695
  return {
6316
6696
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
6317
6697
  isError: false
@@ -6462,8 +6842,8 @@ function handleValidateWorkflow(input) {
6462
6842
  if (input.workflow && Object.keys(input.workflow).length > 0) {
6463
6843
  raw = input.workflow;
6464
6844
  } else {
6465
- const artifactPath = (0, import_path11.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
6466
- if (!(0, import_fs11.existsSync)(artifactPath)) {
6845
+ const artifactPath = (0, import_path12.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
6846
+ if (!(0, import_fs12.existsSync)(artifactPath)) {
6467
6847
  return fail([
6468
6848
  {
6469
6849
  code: "NO_WORKFLOW",
@@ -6472,7 +6852,7 @@ function handleValidateWorkflow(input) {
6472
6852
  ]);
6473
6853
  }
6474
6854
  try {
6475
- raw = JSON.parse((0, import_fs11.readFileSync)(artifactPath, "utf8"));
6855
+ raw = JSON.parse((0, import_fs12.readFileSync)(artifactPath, "utf8"));
6476
6856
  } catch (err) {
6477
6857
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
6478
6858
  }
@@ -6909,8 +7289,8 @@ function registerProPageTools(server2) {
6909
7289
  }
6910
7290
 
6911
7291
  // src/tools/scaffold-cleanup.ts
6912
- var import_fs12 = require("fs");
6913
- var import_path12 = require("path");
7292
+ var import_fs13 = require("fs");
7293
+ var import_path13 = require("path");
6914
7294
  var SCAFFOLD_FILES_TO_DELETE = [
6915
7295
  "content/posts/getting-started.yaml",
6916
7296
  "content/posts/hello-world.yaml"
@@ -6935,12 +7315,12 @@ var FALLBACK_COLORS = {
6935
7315
  mutedForeground: "#737373"
6936
7316
  };
6937
7317
  function readThemeColors(cwd) {
6938
- const tokenPath = (0, import_path12.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6939
- if (!(0, import_fs12.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
6940
- const stat = (0, import_fs12.lstatSync)(tokenPath);
7318
+ const tokenPath = (0, import_path13.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
7319
+ if (!(0, import_fs13.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
7320
+ const stat = (0, import_fs13.lstatSync)(tokenPath);
6941
7321
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
6942
7322
  try {
6943
- const raw = JSON.parse((0, import_fs12.readFileSync)(tokenPath, "utf-8"));
7323
+ const raw = JSON.parse((0, import_fs13.readFileSync)(tokenPath, "utf-8"));
6944
7324
  const css = raw?.cssVariables ?? {};
6945
7325
  const toHsl = (hslValue) => {
6946
7326
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -7000,15 +7380,15 @@ function generateNotFoundPage(colors) {
7000
7380
  `;
7001
7381
  }
7002
7382
  function isSafePath(fullPath) {
7003
- if (!(0, import_fs12.existsSync)(fullPath)) return true;
7004
- const stat = (0, import_fs12.lstatSync)(fullPath);
7383
+ if (!(0, import_fs13.existsSync)(fullPath)) return true;
7384
+ const stat = (0, import_fs13.lstatSync)(fullPath);
7005
7385
  return !stat.isSymbolicLink();
7006
7386
  }
7007
7387
  function isDirEmpty(dirPath) {
7008
- if (!(0, import_fs12.existsSync)(dirPath)) return false;
7009
- const stat = (0, import_fs12.lstatSync)(dirPath);
7388
+ if (!(0, import_fs13.existsSync)(dirPath)) return false;
7389
+ const stat = (0, import_fs13.lstatSync)(dirPath);
7010
7390
  if (!stat.isDirectory()) return false;
7011
- return (0, import_fs12.readdirSync)(dirPath).length === 0;
7391
+ return (0, import_fs13.readdirSync)(dirPath).length === 0;
7012
7392
  }
7013
7393
  function handleCleanupScaffold(_cwd) {
7014
7394
  const cwd = _cwd ?? process.cwd();
@@ -7021,8 +7401,8 @@ function handleCleanupScaffold(_cwd) {
7021
7401
  cleanupWarnings: []
7022
7402
  };
7023
7403
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
7024
- const fullPath = (0, import_path12.join)(cwd, relPath);
7025
- if (!(0, import_fs12.existsSync)(fullPath)) {
7404
+ const fullPath = (0, import_path13.join)(cwd, relPath);
7405
+ if (!(0, import_fs13.existsSync)(fullPath)) {
7026
7406
  result.skipped.push(relPath);
7027
7407
  continue;
7028
7408
  }
@@ -7031,7 +7411,7 @@ function handleCleanupScaffold(_cwd) {
7031
7411
  continue;
7032
7412
  }
7033
7413
  try {
7034
- (0, import_fs12.unlinkSync)(fullPath);
7414
+ (0, import_fs13.unlinkSync)(fullPath);
7035
7415
  result.deleted.push(relPath);
7036
7416
  } catch (err) {
7037
7417
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -7039,19 +7419,19 @@ function handleCleanupScaffold(_cwd) {
7039
7419
  }
7040
7420
  {
7041
7421
  const relPath = "pages/getting-started/content.yml";
7042
- const fullPath = (0, import_path12.join)(cwd, relPath);
7043
- if (!(0, import_fs12.existsSync)(fullPath)) {
7422
+ const fullPath = (0, import_path13.join)(cwd, relPath);
7423
+ if (!(0, import_fs13.existsSync)(fullPath)) {
7044
7424
  result.skipped.push(relPath);
7045
7425
  } else if (!isSafePath(fullPath)) {
7046
7426
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7047
7427
  } else {
7048
- const content = (0, import_fs12.readFileSync)(fullPath, "utf-8");
7428
+ const content = (0, import_fs13.readFileSync)(fullPath, "utf-8");
7049
7429
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
7050
7430
  (marker) => content.includes(marker)
7051
7431
  );
7052
7432
  if (isScaffoldDefault) {
7053
7433
  try {
7054
- (0, import_fs12.unlinkSync)(fullPath);
7434
+ (0, import_fs13.unlinkSync)(fullPath);
7055
7435
  result.deleted.push(relPath);
7056
7436
  } catch (err) {
7057
7437
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -7065,21 +7445,21 @@ function handleCleanupScaffold(_cwd) {
7065
7445
  }
7066
7446
  {
7067
7447
  const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
7068
- const fullPath = (0, import_path12.join)(cwd, relPath);
7069
- const postsDir = (0, import_path12.join)(cwd, "content/posts");
7070
- if (!(0, import_fs12.existsSync)(fullPath)) {
7448
+ const fullPath = (0, import_path13.join)(cwd, relPath);
7449
+ const postsDir = (0, import_path13.join)(cwd, "content/posts");
7450
+ if (!(0, import_fs13.existsSync)(fullPath)) {
7071
7451
  result.skipped.push(relPath);
7072
7452
  } else if (!isSafePath(fullPath)) {
7073
7453
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7074
- } else if (!(0, import_fs12.existsSync)(postsDir)) {
7454
+ } else if (!(0, import_fs13.existsSync)(postsDir)) {
7075
7455
  result.skipped.push(relPath);
7076
7456
  } else {
7077
- const userPostFiles = (0, import_fs12.readdirSync)(postsDir).filter(
7457
+ const userPostFiles = (0, import_fs13.readdirSync)(postsDir).filter(
7078
7458
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
7079
7459
  );
7080
7460
  if (userPostFiles.length === 0) {
7081
7461
  try {
7082
- (0, import_fs12.unlinkSync)(fullPath);
7462
+ (0, import_fs13.unlinkSync)(fullPath);
7083
7463
  result.deleted.push(relPath);
7084
7464
  } catch (err) {
7085
7465
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -7092,15 +7472,15 @@ function handleCleanupScaffold(_cwd) {
7092
7472
  }
7093
7473
  }
7094
7474
  for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
7095
- const fullDir = (0, import_path12.join)(cwd, relDir);
7096
- if (!(0, import_fs12.existsSync)(fullDir)) continue;
7475
+ const fullDir = (0, import_path13.join)(cwd, relDir);
7476
+ if (!(0, import_fs13.existsSync)(fullDir)) continue;
7097
7477
  if (!isSafePath(fullDir)) {
7098
7478
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
7099
7479
  continue;
7100
7480
  }
7101
7481
  if (isDirEmpty(fullDir)) {
7102
7482
  try {
7103
- (0, import_fs12.rmdirSync)(fullDir);
7483
+ (0, import_fs13.rmdirSync)(fullDir);
7104
7484
  result.prunedDirs.push(relDir);
7105
7485
  } catch (err) {
7106
7486
  result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
@@ -7108,8 +7488,8 @@ function handleCleanupScaffold(_cwd) {
7108
7488
  }
7109
7489
  }
7110
7490
  {
7111
- const fullPath = (0, import_path12.join)(cwd, NOT_FOUND_PATH);
7112
- if (!(0, import_fs12.existsSync)(fullPath)) {
7491
+ const fullPath = (0, import_path13.join)(cwd, NOT_FOUND_PATH);
7492
+ if (!(0, import_fs13.existsSync)(fullPath)) {
7113
7493
  result.skipped.push(NOT_FOUND_PATH);
7114
7494
  } else if (!isSafePath(fullPath)) {
7115
7495
  result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
@@ -7117,9 +7497,9 @@ function handleCleanupScaffold(_cwd) {
7117
7497
  try {
7118
7498
  const colors = readThemeColors(cwd);
7119
7499
  const content = generateNotFoundPage(colors);
7120
- const dir = (0, import_path12.dirname)(fullPath);
7121
- (0, import_fs12.mkdirSync)(dir, { recursive: true });
7122
- (0, import_fs12.writeFileSync)(fullPath, content, "utf-8");
7500
+ const dir = (0, import_path13.dirname)(fullPath);
7501
+ (0, import_fs13.mkdirSync)(dir, { recursive: true });
7502
+ (0, import_fs13.writeFileSync)(fullPath, content, "utf-8");
7123
7503
  result.rewritten.push(NOT_FOUND_PATH);
7124
7504
  } catch (err) {
7125
7505
  result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
@@ -7388,23 +7768,23 @@ function registerContrastTools(server2) {
7388
7768
 
7389
7769
  // src/tools/compile.ts
7390
7770
  var import_zod20 = require("zod");
7391
- var import_fs13 = __toESM(require("fs"));
7392
- var import_path13 = __toESM(require("path"));
7771
+ var import_fs14 = __toESM(require("fs"));
7772
+ var import_path14 = __toESM(require("path"));
7393
7773
  var import_server = require("@stackwright-pro/pulse/server");
7394
7774
  var import_server2 = require("@stackwright-pro/auth-nextjs/server");
7395
7775
  var import_server3 = require("@stackwright-pro/openapi/server");
7396
7776
  function buildContext(projectRoot) {
7397
7777
  return {
7398
7778
  projectRoot,
7399
- contentOutDir: import_path13.default.join(projectRoot, "public", "stackwright-content"),
7400
- imagesDir: import_path13.default.join(projectRoot, "public", "images"),
7779
+ contentOutDir: import_path14.default.join(projectRoot, "public", "stackwright-content"),
7780
+ imagesDir: import_path14.default.join(projectRoot, "public", "images"),
7401
7781
  siteConfig: loadSiteConfig(projectRoot)
7402
7782
  };
7403
7783
  }
7404
7784
  function loadSiteConfig(projectRoot) {
7405
7785
  try {
7406
- const sitePath = import_path13.default.join(projectRoot, "public", "stackwright-content", "_site.json");
7407
- return JSON.parse(import_fs13.default.readFileSync(sitePath, "utf8"));
7786
+ const sitePath = import_path14.default.join(projectRoot, "public", "stackwright-content", "_site.json");
7787
+ return JSON.parse(import_fs14.default.readFileSync(sitePath, "utf8"));
7408
7788
  } catch {
7409
7789
  return {};
7410
7790
  }
@@ -7415,8 +7795,8 @@ function formatDuration(startMs) {
7415
7795
  }
7416
7796
  async function tryOssCompile(fn, ctx, extra) {
7417
7797
  try {
7418
- const bsPath = import_path13.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
7419
- if (!import_fs13.default.existsSync(bsPath)) {
7798
+ const bsPath = import_path14.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
7799
+ if (!import_fs14.default.existsSync(bsPath)) {
7420
7800
  return {
7421
7801
  ok: false,
7422
7802
  error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
@@ -7630,14 +8010,14 @@ ${results.join("\n")}`;
7630
8010
 
7631
8011
  // src/tools/strip-legacy-integrations.ts
7632
8012
  var import_zod21 = require("zod");
7633
- var import_fs14 = require("fs");
7634
- var import_path14 = require("path");
8013
+ var import_fs15 = require("fs");
8014
+ var import_path15 = require("path");
7635
8015
  function handleStripLegacyIntegrations(input) {
7636
8016
  const { projectRoot } = input;
7637
- const ymlPath = (0, import_path14.join)(projectRoot, "stackwright.yml");
7638
- const yamlPath = (0, import_path14.join)(projectRoot, "stackwright.yaml");
7639
- if (!(0, import_fs14.existsSync)(ymlPath)) {
7640
- if ((0, import_fs14.existsSync)(yamlPath)) {
8017
+ const ymlPath = (0, import_path15.join)(projectRoot, "stackwright.yml");
8018
+ const yamlPath = (0, import_path15.join)(projectRoot, "stackwright.yaml");
8019
+ if (!(0, import_fs15.existsSync)(ymlPath)) {
8020
+ if ((0, import_fs15.existsSync)(yamlPath)) {
7641
8021
  return {
7642
8022
  changed: false,
7643
8023
  removedEntries: 0,
@@ -7650,7 +8030,7 @@ function handleStripLegacyIntegrations(input) {
7650
8030
  message: "stackwright.yml not found \u2014 no-op."
7651
8031
  };
7652
8032
  }
7653
- const raw = (0, import_fs14.readFileSync)(ymlPath, "utf-8");
8033
+ const raw = (0, import_fs15.readFileSync)(ymlPath, "utf-8");
7654
8034
  const isCRLF = raw.includes("\r\n");
7655
8035
  const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
7656
8036
  const lines = content.split("\n");
@@ -7680,7 +8060,7 @@ function handleStripLegacyIntegrations(input) {
7680
8060
  outputContent += "\n";
7681
8061
  }
7682
8062
  const outputRaw = isCRLF ? outputContent.replace(/\n/g, "\r\n") : outputContent;
7683
- (0, import_fs14.writeFileSync)(ymlPath, outputRaw, "utf-8");
8063
+ (0, import_fs15.writeFileSync)(ymlPath, outputRaw, "utf-8");
7684
8064
  return {
7685
8065
  changed: true,
7686
8066
  removedEntries,
@@ -7705,15 +8085,15 @@ function registerStripLegacyIntegrationsTool(server2) {
7705
8085
 
7706
8086
  // src/tools/emitters.ts
7707
8087
  var import_zod22 = require("zod");
7708
- var import_fs15 = require("fs");
7709
- var import_path15 = require("path");
8088
+ var import_fs16 = require("fs");
8089
+ var import_path16 = require("path");
7710
8090
  var import_js_yaml4 = __toESM(require("js-yaml"));
7711
8091
  var import_emitters = require("@stackwright-pro/emitters");
7712
8092
  function readIntegrations(cwd) {
7713
- const filePath = (0, import_path15.join)(cwd, "stackwright.integrations.yml");
7714
- if (!(0, import_fs15.existsSync)(filePath)) return [];
8093
+ const filePath = (0, import_path16.join)(cwd, "stackwright.integrations.yml");
8094
+ if (!(0, import_fs16.existsSync)(filePath)) return [];
7715
8095
  try {
7716
- const raw = import_js_yaml4.default.load((0, import_fs15.readFileSync)(filePath, "utf-8"));
8096
+ const raw = import_js_yaml4.default.load((0, import_fs16.readFileSync)(filePath, "utf-8"));
7717
8097
  if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
7718
8098
  return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
7719
8099
  name: i.name,
@@ -7724,15 +8104,15 @@ function readIntegrations(cwd) {
7724
8104
  }
7725
8105
  }
7726
8106
  function readServiceTokenRefs(cwd) {
7727
- const servicesDir = (0, import_path15.join)(cwd, "services");
7728
- if (!(0, import_fs15.existsSync)(servicesDir)) return [];
8107
+ const servicesDir = (0, import_path16.join)(cwd, "services");
8108
+ if (!(0, import_fs16.existsSync)(servicesDir)) return [];
7729
8109
  try {
7730
- const files = (0, import_fs15.readdirSync)(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
8110
+ const files = (0, import_fs16.readdirSync)(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
7731
8111
  const refs = [];
7732
8112
  for (const file of files) {
7733
8113
  try {
7734
8114
  const raw = import_js_yaml4.default.load(
7735
- (0, import_fs15.readFileSync)((0, import_path15.join)(servicesDir, file), "utf-8")
8115
+ (0, import_fs16.readFileSync)((0, import_path16.join)(servicesDir, file), "utf-8")
7736
8116
  );
7737
8117
  if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
7738
8118
  refs.push({
@@ -7882,12 +8262,12 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
7882
8262
 
7883
8263
  // src/tools/list-specs.ts
7884
8264
  var import_zod23 = require("zod");
7885
- var import_fs16 = require("fs");
7886
- var import_path16 = require("path");
8265
+ var import_fs17 = require("fs");
8266
+ var import_path17 = require("path");
7887
8267
  var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
7888
8268
  var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
7889
8269
  function deriveIntegrationName(fileName) {
7890
- let name = (0, import_path16.basename)(fileName, (0, import_path16.extname)(fileName));
8270
+ let name = (0, import_path17.basename)(fileName, (0, import_path17.extname)(fileName));
7891
8271
  for (const suffix of STRIP_SUFFIXES) {
7892
8272
  if (name.endsWith(suffix)) {
7893
8273
  name = name.slice(0, -suffix.length);
@@ -7905,29 +8285,29 @@ function detectFormat(snippet) {
7905
8285
  }
7906
8286
  function handleListSpecs(input) {
7907
8287
  const { projectRoot } = input;
7908
- const specsDir = (0, import_path16.join)(projectRoot, "specs");
8288
+ const specsDir = (0, import_path17.join)(projectRoot, "specs");
7909
8289
  const empty = { projectRoot, specs: [], totalCount: 0 };
7910
- if (!(0, import_fs16.existsSync)(specsDir)) return empty;
8290
+ if (!(0, import_fs17.existsSync)(specsDir)) return empty;
7911
8291
  let entries;
7912
8292
  try {
7913
- entries = (0, import_fs16.readdirSync)(specsDir);
8293
+ entries = (0, import_fs17.readdirSync)(specsDir);
7914
8294
  } catch {
7915
8295
  return empty;
7916
8296
  }
7917
8297
  const specs = [];
7918
8298
  for (const entry of entries) {
7919
- const fullPath = (0, import_path16.join)(specsDir, entry);
8299
+ const fullPath = (0, import_path17.join)(specsDir, entry);
7920
8300
  let stat;
7921
8301
  try {
7922
- stat = (0, import_fs16.statSync)(fullPath);
8302
+ stat = (0, import_fs17.statSync)(fullPath);
7923
8303
  } catch {
7924
8304
  continue;
7925
8305
  }
7926
8306
  if (!stat.isFile()) continue;
7927
- if (!ALLOWED_EXTS.has((0, import_path16.extname)(entry).toLowerCase())) continue;
8307
+ if (!ALLOWED_EXTS.has((0, import_path17.extname)(entry).toLowerCase())) continue;
7928
8308
  let snippet = "";
7929
8309
  try {
7930
- snippet = (0, import_fs16.readFileSync)(fullPath, "utf-8").slice(0, 2048);
8310
+ snippet = (0, import_fs17.readFileSync)(fullPath, "utf-8").slice(0, 2048);
7931
8311
  } catch {
7932
8312
  }
7933
8313
  specs.push({
@@ -7959,14 +8339,14 @@ function registerListSpecsTool(server2) {
7959
8339
 
7960
8340
  // src/tools/consolidate-integrations.ts
7961
8341
  var import_zod24 = require("zod");
7962
- var import_fs17 = require("fs");
8342
+ var import_fs18 = require("fs");
7963
8343
  var import_proper_lockfile2 = require("proper-lockfile");
7964
- var import_path17 = require("path");
8344
+ var import_path18 = require("path");
7965
8345
  var import_js_yaml5 = __toESM(require("js-yaml"));
7966
8346
  var BASE_MOCK_PORT = 4011;
7967
8347
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
7968
8348
  function deriveNameFromFilename(filename) {
7969
- const base = (0, import_path17.basename)(filename, ".json").replace(/^api-config-/, "");
8349
+ const base = (0, import_path18.basename)(filename, ".json").replace(/^api-config-/, "");
7970
8350
  for (const suffix of STRIP_SUFFIXES2) {
7971
8351
  if (base.endsWith(suffix)) return base.slice(0, -suffix.length);
7972
8352
  }
@@ -7974,13 +8354,13 @@ function deriveNameFromFilename(filename) {
7974
8354
  }
7975
8355
  function handleConsolidateIntegrations(input) {
7976
8356
  const { projectRoot } = input;
7977
- const artifactsDir = (0, import_path17.join)(projectRoot, ".stackwright", "artifacts");
7978
- if (!(0, import_fs17.existsSync)(artifactsDir)) {
8357
+ const artifactsDir = (0, import_path18.join)(projectRoot, ".stackwright", "artifacts");
8358
+ if (!(0, import_fs18.existsSync)(artifactsDir)) {
7979
8359
  return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
7980
8360
  }
7981
8361
  let entries;
7982
8362
  try {
7983
- entries = (0, import_fs17.readdirSync)(artifactsDir).filter(
8363
+ entries = (0, import_fs18.readdirSync)(artifactsDir).filter(
7984
8364
  (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
7985
8365
  );
7986
8366
  } catch {
@@ -7991,14 +8371,22 @@ function handleConsolidateIntegrations(input) {
7991
8371
  }
7992
8372
  entries.sort();
7993
8373
  const integrationsByName = /* @__PURE__ */ new Map();
8374
+ const skippedIntegrationsList = [];
7994
8375
  entries.forEach((filename, index) => {
7995
8376
  let artifact = {};
7996
8377
  try {
7997
- const raw = (0, import_fs17.readFileSync)((0, import_path17.join)(artifactsDir, filename), "utf-8");
8378
+ const raw = (0, import_fs18.readFileSync)((0, import_path18.join)(artifactsDir, filename), "utf-8");
7998
8379
  artifact = JSON.parse(raw);
7999
8380
  } catch {
8000
8381
  }
8001
8382
  const name = artifact.integrationName ?? deriveNameFromFilename(filename);
8383
+ if (artifact.skipped && artifact.skipped.length > 0) {
8384
+ skippedIntegrationsList.push({
8385
+ name,
8386
+ reason: artifact.skipped[0]?.reason ?? "explicitly skipped by api-otter"
8387
+ });
8388
+ return;
8389
+ }
8002
8390
  const spec = artifact.integrationSpec ?? artifact.specPath ?? `./specs/${name}.yaml`;
8003
8391
  const mockUrl = artifact.mockUrl ?? `http://localhost:${BASE_MOCK_PORT + index}`;
8004
8392
  const baseUrl = artifact.baseUrl ?? "";
@@ -8019,21 +8407,21 @@ function handleConsolidateIntegrations(input) {
8019
8407
  integrationsByName.set(name, entry);
8020
8408
  });
8021
8409
  const integrations = Array.from(integrationsByName.values());
8022
- const dedupedCount = entries.length - integrations.length;
8023
- const integrationsYmlPath = (0, import_path17.join)(projectRoot, "stackwright.integrations.yml");
8410
+ const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
8411
+ const integrationsYmlPath = (0, import_path18.join)(projectRoot, "stackwright.integrations.yml");
8024
8412
  let existing = {};
8025
- if ((0, import_fs17.existsSync)(integrationsYmlPath)) {
8413
+ if ((0, import_fs18.existsSync)(integrationsYmlPath)) {
8026
8414
  try {
8027
- const raw = (0, import_fs17.readFileSync)(integrationsYmlPath, "utf-8");
8415
+ const raw = (0, import_fs18.readFileSync)(integrationsYmlPath, "utf-8");
8028
8416
  existing = import_js_yaml5.default.load(raw) ?? {};
8029
8417
  } catch {
8030
8418
  existing = {};
8031
8419
  }
8032
8420
  }
8033
8421
  const merged = { ...existing, integrations };
8034
- (0, import_fs17.mkdirSync)((0, import_path17.join)(projectRoot), { recursive: true });
8035
- if (!(0, import_fs17.existsSync)(integrationsYmlPath)) {
8036
- (0, import_fs17.writeFileSync)(integrationsYmlPath, "", "utf-8");
8422
+ (0, import_fs18.mkdirSync)((0, import_path18.join)(projectRoot), { recursive: true });
8423
+ if (!(0, import_fs18.existsSync)(integrationsYmlPath)) {
8424
+ (0, import_fs18.writeFileSync)(integrationsYmlPath, "", "utf-8");
8037
8425
  }
8038
8426
  const release = (0, import_proper_lockfile2.lockSync)(integrationsYmlPath, {
8039
8427
  realpath: false,
@@ -8043,7 +8431,7 @@ function handleConsolidateIntegrations(input) {
8043
8431
  const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
8044
8432
  `;
8045
8433
  const body = import_js_yaml5.default.dump(merged, { lineWidth: 120, noRefs: true });
8046
- (0, import_fs17.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8434
+ (0, import_fs18.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8047
8435
  } finally {
8048
8436
  release();
8049
8437
  }
@@ -8051,7 +8439,11 @@ function handleConsolidateIntegrations(input) {
8051
8439
  written: true,
8052
8440
  integrationCount: integrations.length,
8053
8441
  path: "stackwright.integrations.yml",
8054
- ...dedupedCount > 0 ? { dedupedCount } : {}
8442
+ ...dedupedCount > 0 ? { dedupedCount } : {},
8443
+ ...skippedIntegrationsList.length > 0 ? {
8444
+ skippedCount: skippedIntegrationsList.length,
8445
+ skippedIntegrations: skippedIntegrationsList
8446
+ } : {}
8055
8447
  };
8056
8448
  }
8057
8449
  function registerConsolidateIntegrationsTool(server2) {
@@ -8125,7 +8517,7 @@ var package_default = {
8125
8517
  "test:coverage": "vitest run --coverage"
8126
8518
  },
8127
8519
  name: "@stackwright-pro/mcp",
8128
- version: "0.2.0-alpha.107",
8520
+ version: "0.2.0-alpha.108",
8129
8521
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8130
8522
  license: "SEE LICENSE IN LICENSE",
8131
8523
  main: "./dist/server.js",