@stackwright-pro/mcp 0.2.0-alpha.113 → 0.2.0-alpha.115

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
@@ -2030,9 +2030,9 @@ function registerOrchestrationTools(server2) {
2030
2030
 
2031
2031
  // src/tools/pipeline.ts
2032
2032
  var import_zod14 = require("zod");
2033
- var import_fs8 = require("fs");
2033
+ var import_fs12 = require("fs");
2034
2034
  var import_proper_lockfile = require("proper-lockfile");
2035
- var import_path8 = require("path");
2035
+ var import_path12 = require("path");
2036
2036
  var import_crypto3 = require("crypto");
2037
2037
 
2038
2038
  // src/artifact-signing.ts
@@ -2559,6 +2559,585 @@ function formatPulseRetryPrompt(result) {
2559
2559
  return lines.join("\n");
2560
2560
  }
2561
2561
 
2562
+ // src/tools/validate-theme-tokens.ts
2563
+ var import_fs7 = require("fs");
2564
+ var import_path7 = require("path");
2565
+ var import_js_yaml2 = require("js-yaml");
2566
+
2567
+ // src/tools/schema-registry.ts
2568
+ var import_zod11 = require("zod");
2569
+ var import_fs6 = require("fs");
2570
+ var import_path6 = require("path");
2571
+ var import_types = require("@stackwright-pro/types");
2572
+ var import_pulse2 = require("@stackwright-pro/pulse");
2573
+ function getSchemaForEntry(entry, ctx) {
2574
+ if (entry.schema) return entry.schema;
2575
+ if (entry.schemaFactory) {
2576
+ if (ctx) return entry.schemaFactory(ctx);
2577
+ console.warn(
2578
+ "[schema-registry] schemaFactory entry requires projectRoot context. Pass { projectRoot } to get the project-specific schema. Falling back to z.string() (no enforcement)."
2579
+ );
2580
+ return import_zod11.z.string();
2581
+ }
2582
+ return import_zod11.z.unknown();
2583
+ }
2584
+ var NavigationLinkSchema = import_zod11.z.lazy(
2585
+ () => import_zod11.z.object({
2586
+ label: import_zod11.z.string().min(1),
2587
+ href: import_zod11.z.string().min(1),
2588
+ children: import_zod11.z.array(NavigationLinkSchema).optional()
2589
+ })
2590
+ );
2591
+ var NavigationSectionSchema = import_zod11.z.object({
2592
+ section: import_zod11.z.string().min(1),
2593
+ items: import_zod11.z.array(NavigationLinkSchema)
2594
+ });
2595
+ var NavigationItemSchema = import_zod11.z.union([
2596
+ NavigationLinkSchema,
2597
+ NavigationSectionSchema
2598
+ ]);
2599
+ var THEME_TOKENS_PATHS = [
2600
+ (0, import_path6.join)(".stackwright", "artifacts", "theme-tokens.json"),
2601
+ (0, import_path6.join)("public", "stackwright-content", "_theme.json")
2602
+ ];
2603
+ function resolveStatusLadder(projectRoot) {
2604
+ for (const relPath of THEME_TOKENS_PATHS) {
2605
+ const fullPath = (0, import_path6.join)(projectRoot, relPath);
2606
+ if (!(0, import_fs6.existsSync)(fullPath)) continue;
2607
+ try {
2608
+ const raw = (0, import_fs6.readFileSync)(fullPath, "utf-8");
2609
+ const parsed = JSON.parse(raw);
2610
+ if (Array.isArray(parsed["statusLadder"]) && parsed["statusLadder"].length > 0) {
2611
+ return parsed["statusLadder"].filter(
2612
+ (t) => typeof t === "string" && t.startsWith("status-")
2613
+ );
2614
+ }
2615
+ const colors = parsed["tokens"]?.["colors"];
2616
+ if (colors && typeof colors === "object") {
2617
+ const statusKeys = Object.keys(colors).filter((k) => k.startsWith("status-"));
2618
+ if (statusKeys.length > 0) return statusKeys;
2619
+ }
2620
+ } catch {
2621
+ }
2622
+ }
2623
+ return [];
2624
+ }
2625
+ function buildThemeStatusTokenSchema(ctx) {
2626
+ const ladder = resolveStatusLadder(ctx.projectRoot);
2627
+ if (ladder.length === 0) {
2628
+ return import_zod11.z.string();
2629
+ }
2630
+ return import_zod11.z.enum(ladder);
2631
+ }
2632
+ var REGISTRY = /* @__PURE__ */ new Map([
2633
+ // ── Workflow schemas ──────────────────────────────────────────────────────
2634
+ [
2635
+ "workflow_step",
2636
+ {
2637
+ schema: import_types.WorkflowStepSchema,
2638
+ synonyms: {
2639
+ label: ["title", "name"],
2640
+ message: ["description"],
2641
+ "theme.variant": ["style"],
2642
+ "on_submit.transition": ["transitions"]
2643
+ },
2644
+ phaseAffinity: ["workflow"]
2645
+ }
2646
+ ],
2647
+ [
2648
+ "workflow_definition",
2649
+ {
2650
+ schema: import_types.WorkflowDefinitionSchema,
2651
+ synonyms: {
2652
+ label: ["title", "name"],
2653
+ id: ["type"]
2654
+ },
2655
+ phaseAffinity: ["workflow"]
2656
+ }
2657
+ ],
2658
+ [
2659
+ "workflow_field",
2660
+ {
2661
+ schema: import_types.WorkflowFieldSchema,
2662
+ synonyms: {
2663
+ name: ["id"],
2664
+ label: ["title", "description"]
2665
+ },
2666
+ phaseAffinity: ["workflow"]
2667
+ }
2668
+ ],
2669
+ [
2670
+ "workflow_action",
2671
+ {
2672
+ schema: import_types.WorkflowActionSchema,
2673
+ synonyms: {
2674
+ label: ["title", "name"],
2675
+ transition: ["then"],
2676
+ "theme.variant": ["style"],
2677
+ action: ["on_click"]
2678
+ },
2679
+ phaseAffinity: ["workflow"]
2680
+ }
2681
+ ],
2682
+ // ── Navigation ────────────────────────────────────────────────────────────
2683
+ [
2684
+ "navigation_item",
2685
+ {
2686
+ schema: NavigationItemSchema,
2687
+ synonyms: {
2688
+ items: ["children"],
2689
+ section: ["label", "title"]
2690
+ },
2691
+ phaseAffinity: ["pages", "dashboard", "polish"]
2692
+ }
2693
+ ],
2694
+ // ── Auth ──────────────────────────────────────────────────────────────────
2695
+ [
2696
+ "auth_config",
2697
+ {
2698
+ schema: import_types.authConfigSchema,
2699
+ synonyms: {
2700
+ type: ["method", "provider_type", "strategy"]
2701
+ },
2702
+ phaseAffinity: ["auth"]
2703
+ }
2704
+ ],
2705
+ // ── Pulse content types ───────────────────────────────────────────────────
2706
+ [
2707
+ "metric_card_pulse",
2708
+ {
2709
+ schema: import_pulse2.MetricCardPulseSchema,
2710
+ synonyms: {
2711
+ field: ["valueField", "valuePath", "dataField", "value"]
2712
+ },
2713
+ phaseAffinity: ["dashboard", "pages"]
2714
+ }
2715
+ ],
2716
+ [
2717
+ "data_table_pulse",
2718
+ {
2719
+ schema: import_pulse2.DataTablePulseSchema,
2720
+ synonyms: {
2721
+ header: ["label"],
2722
+ type: ["renderAs"],
2723
+ colorMap: ["severityMap"]
2724
+ },
2725
+ phaseAffinity: ["dashboard", "pages"]
2726
+ }
2727
+ ],
2728
+ [
2729
+ "status_badge_pulse",
2730
+ {
2731
+ schema: import_pulse2.StatusBadgePulseSchema,
2732
+ synonyms: {},
2733
+ phaseAffinity: ["dashboard", "pages"]
2734
+ }
2735
+ ],
2736
+ [
2737
+ "map_pulse",
2738
+ {
2739
+ schema: import_pulse2.MapPulseSchema,
2740
+ synonyms: {},
2741
+ phaseAffinity: ["geo", "dashboard", "pages"]
2742
+ }
2743
+ ],
2744
+ // ── Workflow container page content type (swp-dvde) ───────────────────────
2745
+ [
2746
+ "workflow_wizard",
2747
+ {
2748
+ schema: import_types.WorkflowWizardContentSchema,
2749
+ synonyms: {
2750
+ workflowFile: ["workflowPath", "file", "path", "yamlFile"],
2751
+ workflowId: ["id", "workflowName", "name"],
2752
+ persistenceKey: ["stateKey", "storageKey"]
2753
+ },
2754
+ phaseAffinity: ["workflow"]
2755
+ }
2756
+ ],
2757
+ // ── Theme schemas (swp-cxwu) ──────────────────────────────────────────────
2758
+ [
2759
+ "theme_tokens",
2760
+ {
2761
+ schema: import_types.themeTokensFileSchema,
2762
+ synonyms: {},
2763
+ phaseAffinity: ["theme"]
2764
+ }
2765
+ ],
2766
+ [
2767
+ "theme_status_token",
2768
+ {
2769
+ // No static schema — this is a project-specific runtime enum.
2770
+ // Call getSchemaForEntry(entry, { projectRoot }) to get the project's ladder.
2771
+ schemaFactory: buildThemeStatusTokenSchema,
2772
+ synonyms: {},
2773
+ phaseAffinity: ["pages", "dashboard", "geo"]
2774
+ }
2775
+ ]
2776
+ ]);
2777
+ var SCHEMA_REGISTRY = REGISTRY;
2778
+ var REGISTRY_SCHEMA_NAMES = Array.from(REGISTRY.keys());
2779
+ function buildReverseSynonyms(synonyms) {
2780
+ const reverse = {};
2781
+ for (const [correct, wrongs] of Object.entries(synonyms)) {
2782
+ for (const wrong of wrongs) {
2783
+ reverse[wrong] = correct;
2784
+ }
2785
+ }
2786
+ return reverse;
2787
+ }
2788
+ function buildPhaseSchemaNames() {
2789
+ const phaseMap = {};
2790
+ for (const [name, entry] of REGISTRY.entries()) {
2791
+ for (const phase of entry.phaseAffinity ?? []) {
2792
+ if (!phaseMap[phase]) phaseMap[phase] = [];
2793
+ phaseMap[phase].push(name);
2794
+ }
2795
+ }
2796
+ return phaseMap;
2797
+ }
2798
+
2799
+ // src/tools/validate-theme-tokens.ts
2800
+ var COLOR_FIELD_KEYS = /* @__PURE__ */ new Set([
2801
+ "color",
2802
+ "colorMap",
2803
+ "layerColor",
2804
+ "borderColor",
2805
+ "backgroundColor",
2806
+ "iconColor",
2807
+ "fill",
2808
+ "stroke",
2809
+ "markerColor",
2810
+ "fillColor",
2811
+ "outlineColor",
2812
+ "statusColor",
2813
+ "badgeColor",
2814
+ "tagColor"
2815
+ ]);
2816
+ var LEGACY_OSS_TOKENS = /* @__PURE__ */ new Set(["success", "warning", "danger", "info", "muted"]);
2817
+ var LEGACY_MAPPING_HINT = "Suggested mapping: success->status-safe, warning->status-warning, danger->status-critical, info->status-watch, muted->status-at-risk.";
2818
+ var HEX_RE = /^#[0-9a-f]{3,8}$/i;
2819
+ var STATUS_TOKEN_RE = /^status-[a-z0-9-]+$/;
2820
+ function walkYaml(node, fieldName, pathParts, inColorContext, visitor) {
2821
+ if (typeof node === "string") {
2822
+ visitor(node, fieldName, pathParts.join(".") || "(root)", inColorContext);
2823
+ return;
2824
+ }
2825
+ if (Array.isArray(node)) {
2826
+ node.forEach((item, idx) => {
2827
+ walkYaml(item, fieldName, [...pathParts, `[${idx}]`], inColorContext, visitor);
2828
+ });
2829
+ return;
2830
+ }
2831
+ if (node !== null && typeof node === "object") {
2832
+ for (const [key, val] of Object.entries(node)) {
2833
+ const thisIsColorField = COLOR_FIELD_KEYS.has(key);
2834
+ walkYaml(val, key, [...pathParts, key], inColorContext || thisIsColorField, visitor);
2835
+ }
2836
+ }
2837
+ }
2838
+ function detectViolationsInYaml(yamlContent, ladder, ladderSet, relPath) {
2839
+ const violations = [];
2840
+ walkYaml(yamlContent, null, [], false, (value, fieldName, path5, inColorContext) => {
2841
+ const isColorField = inColorContext || fieldName !== null && COLOR_FIELD_KEYS.has(fieldName);
2842
+ if (STATUS_TOKEN_RE.test(value) && !ladderSet.has(value)) {
2843
+ violations.push({
2844
+ type: "undefined_token",
2845
+ message: `Undefined theme token: "${value}" not in declared theme ladder. Declared tokens: ${ladder.join(", ")}`,
2846
+ file: relPath,
2847
+ path: path5,
2848
+ value
2849
+ });
2850
+ return;
2851
+ }
2852
+ if (!isColorField) return;
2853
+ if (HEX_RE.test(value)) {
2854
+ violations.push({
2855
+ type: "raw_hex",
2856
+ message: `Raw hex color "${value}" in color field "${fieldName ?? "<color context>"}". Use declared theme tokens instead. Available: ${ladder.join(", ")}`,
2857
+ file: relPath,
2858
+ path: path5,
2859
+ value
2860
+ });
2861
+ return;
2862
+ }
2863
+ if (LEGACY_OSS_TOKENS.has(value)) {
2864
+ violations.push({
2865
+ type: "legacy_vocab",
2866
+ message: `Legacy status token "${value}" in color field "${fieldName ?? "<color context>"}". This project's ladder is: ${ladder.join(", ")}. ` + LEGACY_MAPPING_HINT,
2867
+ file: relPath,
2868
+ path: path5,
2869
+ value
2870
+ });
2871
+ }
2872
+ });
2873
+ return violations;
2874
+ }
2875
+ function findContentFiles(pagesRoot) {
2876
+ if (!(0, import_fs7.existsSync)(pagesRoot)) return [];
2877
+ const files = [];
2878
+ try {
2879
+ const slugDirs = (0, import_fs7.readdirSync)(pagesRoot, { withFileTypes: true });
2880
+ for (const entry of slugDirs) {
2881
+ if (!entry.isDirectory()) continue;
2882
+ const slugDir = (0, import_path7.join)(pagesRoot, entry.name);
2883
+ for (const candidate of ["content.yml", "content.yaml"]) {
2884
+ const p = (0, import_path7.join)(slugDir, candidate);
2885
+ if ((0, import_fs7.existsSync)(p)) files.push(p);
2886
+ }
2887
+ }
2888
+ } catch {
2889
+ }
2890
+ return files;
2891
+ }
2892
+ function validateThemeTokenUsage(projectRoot) {
2893
+ const ladder = resolveStatusLadder(projectRoot);
2894
+ if (ladder.length === 0) {
2895
+ return {
2896
+ valid: true,
2897
+ violations: [],
2898
+ skipped: true
2899
+ };
2900
+ }
2901
+ const ladderSet = new Set(ladder);
2902
+ const pagesRoot = (0, import_path7.join)(projectRoot, "pages");
2903
+ const contentFiles = findContentFiles(pagesRoot);
2904
+ const allViolations = [];
2905
+ for (const absPath of contentFiles) {
2906
+ const relPath = (0, import_path7.relative)(projectRoot, absPath);
2907
+ try {
2908
+ const raw = (0, import_fs7.readFileSync)(absPath, "utf-8");
2909
+ const parsed = (0, import_js_yaml2.load)(raw);
2910
+ const fileViolations = detectViolationsInYaml(parsed, ladder, ladderSet, relPath);
2911
+ allViolations.push(...fileViolations);
2912
+ } catch {
2913
+ }
2914
+ }
2915
+ if (allViolations.length === 0) {
2916
+ return { valid: true, violations: [] };
2917
+ }
2918
+ const lines = [
2919
+ `Theme token enforcement found ${allViolations.length} violation(s):`,
2920
+ "",
2921
+ ...allViolations.map((v, i) => {
2922
+ const typeLabel = v.type === "undefined_token" ? "UNDEFINED TOKEN" : v.type === "raw_hex" ? "RAW HEX" : "LEGACY VOCAB";
2923
+ return ` ${i + 1}. [${typeLabel}] ${v.message}
2924
+ Emitted in: ${v.file} at ${v.path}`;
2925
+ }),
2926
+ "",
2927
+ `Fix all violations to use declared theme tokens: ${ladder.join(", ")}`
2928
+ ];
2929
+ return {
2930
+ valid: false,
2931
+ violations: allViolations,
2932
+ retryPrompt: lines.join("\n")
2933
+ };
2934
+ }
2935
+
2936
+ // src/tools/validate-build-context-globals.ts
2937
+ var import_fs8 = require("fs");
2938
+ var import_path8 = require("path");
2939
+ var import_js_yaml3 = require("js-yaml");
2940
+ var DATA_WIDGET_TYPES = /* @__PURE__ */ new Set([
2941
+ "data_table_pulse",
2942
+ "stats_grid",
2943
+ "metric_card_pulse",
2944
+ "map_pulse"
2945
+ ]);
2946
+ function readGlobalDefaults(projectRoot) {
2947
+ const bcPath = (0, import_path8.join)(projectRoot, ".stackwright", "build-context.json");
2948
+ if (!(0, import_fs8.existsSync)(bcPath)) return null;
2949
+ try {
2950
+ const raw = (0, import_fs8.readFileSync)(bcPath, "utf-8");
2951
+ const parsed = JSON.parse(raw);
2952
+ const gd = parsed.globalDefaults;
2953
+ if (!gd || typeof gd !== "object") return null;
2954
+ return gd;
2955
+ } catch {
2956
+ return null;
2957
+ }
2958
+ }
2959
+ function findDataWidgets(node, pathParts, out) {
2960
+ if (node === null || node === void 0 || typeof node !== "object") return;
2961
+ if (Array.isArray(node)) {
2962
+ node.forEach((item, idx) => {
2963
+ findDataWidgets(item, [...pathParts, `[${idx}]`], out);
2964
+ });
2965
+ return;
2966
+ }
2967
+ const obj = node;
2968
+ const contentType = typeof obj["type"] === "string" ? obj["type"] : null;
2969
+ if (contentType && DATA_WIDGET_TYPES.has(contentType)) {
2970
+ out.push({
2971
+ type: contentType,
2972
+ label: typeof obj["label"] === "string" ? obj["label"] : void 0,
2973
+ data: obj,
2974
+ path: pathParts.join(".") || "(root)"
2975
+ });
2976
+ }
2977
+ for (const [key, val] of Object.entries(obj)) {
2978
+ findDataWidgets(val, [...pathParts, key], out);
2979
+ }
2980
+ }
2981
+ function findContentFiles2(pagesRoot) {
2982
+ if (!(0, import_fs8.existsSync)(pagesRoot)) return [];
2983
+ const files = [];
2984
+ try {
2985
+ const slugDirs = (0, import_fs8.readdirSync)(pagesRoot, { withFileTypes: true });
2986
+ for (const entry of slugDirs) {
2987
+ if (!entry.isDirectory()) continue;
2988
+ for (const candidate of ["content.yml", "content.yaml"]) {
2989
+ const p = (0, import_path8.join)(pagesRoot, entry.name, candidate);
2990
+ if ((0, import_fs8.existsSync)(p)) files.push(p);
2991
+ }
2992
+ }
2993
+ } catch {
2994
+ }
2995
+ return files;
2996
+ }
2997
+ function validateBuildContextGlobals(projectRoot) {
2998
+ const globalDefaults = readGlobalDefaults(projectRoot);
2999
+ if (!globalDefaults) {
3000
+ return { valid: true, violations: [], skipped: true };
3001
+ }
3002
+ const requiredFields = Object.entries(globalDefaults).filter(([, v]) => v === true).map(([k]) => k);
3003
+ if (requiredFields.length === 0) {
3004
+ return { valid: true, violations: [], skipped: true };
3005
+ }
3006
+ const pagesRoot = (0, import_path8.join)(projectRoot, "pages");
3007
+ const contentFiles = findContentFiles2(pagesRoot);
3008
+ const allViolations = [];
3009
+ for (const absPath of contentFiles) {
3010
+ const relPath = (0, import_path8.relative)(projectRoot, absPath);
3011
+ try {
3012
+ const raw = (0, import_fs8.readFileSync)(absPath, "utf-8");
3013
+ const parsed = (0, import_js_yaml3.load)(raw);
3014
+ const widgets = [];
3015
+ findDataWidgets(parsed, [], widgets);
3016
+ for (const widget of widgets) {
3017
+ for (const field of requiredFields) {
3018
+ if (widget.data[field] !== true) {
3019
+ allViolations.push({
3020
+ field,
3021
+ widgetType: widget.type,
3022
+ widgetLabel: widget.label,
3023
+ file: relPath,
3024
+ path: widget.path
3025
+ });
3026
+ }
3027
+ }
3028
+ }
3029
+ } catch {
3030
+ }
3031
+ }
3032
+ if (allViolations.length === 0) {
3033
+ return { valid: true, violations: [] };
3034
+ }
3035
+ const lines = [
3036
+ `BUILD_CONTEXT global defaults: ${allViolations.length} violation(s) found.`,
3037
+ "",
3038
+ ...allViolations.map((v, i) => {
3039
+ const widgetId = v.widgetLabel ? `"${v.widgetLabel}" (${v.widgetType})` : `${v.widgetType}`;
3040
+ return ` ${i + 1}. BUILD_CONTEXT.${v.field} is globally true but widget ${widgetId} omits it.
3041
+ Set ${v.field}: true on this widget.
3042
+ Emitted in: ${v.file} at ${v.path}`;
3043
+ }),
3044
+ "",
3045
+ `Required global defaults: ${requiredFields.map((f) => `${f}: true`).join(", ")}`
3046
+ ];
3047
+ return {
3048
+ valid: false,
3049
+ violations: allViolations,
3050
+ retryPrompt: lines.join("\n")
3051
+ };
3052
+ }
3053
+
3054
+ // src/tools/validate-workflow-container-pages.ts
3055
+ var import_fs9 = require("fs");
3056
+ var import_path9 = require("path");
3057
+ var import_js_yaml4 = require("js-yaml");
3058
+ function discoverWorkflowIds(projectRoot) {
3059
+ const artifactsDir = (0, import_path9.join)(projectRoot, ".stackwright", "artifacts");
3060
+ if (!(0, import_fs9.existsSync)(artifactsDir)) return [];
3061
+ const entries = [];
3062
+ let files;
3063
+ try {
3064
+ files = (0, import_fs9.readdirSync)(artifactsDir);
3065
+ } catch {
3066
+ return [];
3067
+ }
3068
+ for (const filename of files) {
3069
+ if (filename !== "workflow-config.json" && !filename.startsWith("workflow-config-")) continue;
3070
+ if (!filename.endsWith(".json")) continue;
3071
+ const fullPath = (0, import_path9.join)(artifactsDir, filename);
3072
+ try {
3073
+ const raw = JSON.parse((0, import_fs9.readFileSync)(fullPath, "utf-8"));
3074
+ const workflowData = raw["workflow"] ?? raw["workflowConfig"];
3075
+ const workflowId = typeof workflowData?.["id"] === "string" ? workflowData["id"] : null;
3076
+ if (workflowId && /^[a-z0-9-]+$/.test(workflowId)) {
3077
+ entries.push({ workflowId, sourceFile: filename });
3078
+ }
3079
+ } catch {
3080
+ }
3081
+ }
3082
+ return entries;
3083
+ }
3084
+ function hasWorkflowWizardType(filePath) {
3085
+ try {
3086
+ const raw = (0, import_fs9.readFileSync)(filePath, "utf-8");
3087
+ const parsed = (0, import_js_yaml4.load)(raw);
3088
+ if (!parsed) return false;
3089
+ const content = parsed["content"];
3090
+ if (!content) return false;
3091
+ const items = content["content_items"];
3092
+ if (!Array.isArray(items)) return false;
3093
+ return items.some((item) => item["type"] === "workflow_wizard");
3094
+ } catch {
3095
+ return false;
3096
+ }
3097
+ }
3098
+ function validateWorkflowContainerPages(projectRoot) {
3099
+ const workflows = discoverWorkflowIds(projectRoot);
3100
+ if (workflows.length === 0) {
3101
+ return { valid: true, violations: [], skipped: true };
3102
+ }
3103
+ const violations = [];
3104
+ for (const { workflowId, sourceFile: _sourceFile } of workflows) {
3105
+ const expectedPath = `pages/workflows/${workflowId}/content.yml`;
3106
+ const fullPath = (0, import_path9.join)(projectRoot, expectedPath);
3107
+ if (!(0, import_fs9.existsSync)(fullPath)) {
3108
+ violations.push({ workflowId, expectedPath, reason: "missing-file" });
3109
+ continue;
3110
+ }
3111
+ if (!hasWorkflowWizardType(fullPath)) {
3112
+ violations.push({ workflowId, expectedPath, reason: "missing-workflow-wizard-type" });
3113
+ }
3114
+ }
3115
+ if (violations.length === 0) {
3116
+ return { valid: true, violations: [] };
3117
+ }
3118
+ const lines = [
3119
+ `Workflow container page cross-check failed: ${violations.length} workflow(s) missing container pages.`,
3120
+ "",
3121
+ ...violations.map((v, i) => {
3122
+ const reason = v.reason === "missing-file" ? `File does not exist. Create it via stackwright_pro_write_workflow_container_page.` : `File exists but has no workflow_wizard content item. Must contain type: workflow_wizard.`;
3123
+ return ` ${i + 1}. workflowId "${v.workflowId}"
3124
+ Expected: ${v.expectedPath}
3125
+ Issue: ${reason}`;
3126
+ }),
3127
+ "",
3128
+ "Fix: workflow-otter must call stackwright_pro_write_workflow_container_page immediately",
3129
+ "after writing each workflow YAML. Required args:",
3130
+ ' { workflowId, workflowFile, pageSlug: "workflows/<workflowId>", label, requiredRoles }',
3131
+ "",
3132
+ "Without the container page, the route 404s and auth-reconcile cannot protect it."
3133
+ ];
3134
+ return {
3135
+ valid: false,
3136
+ violations,
3137
+ retryPrompt: lines.join("\n")
3138
+ };
3139
+ }
3140
+
2562
3141
  // src/tools/validate-api-integration.ts
2563
3142
  var CANONICAL_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2", "oidc", "none"];
2564
3143
  var CANONICAL_SET = new Set(CANONICAL_API_AUTH_TYPES);
@@ -2751,9 +3330,9 @@ function validateAuthConfig(artifact, artifactPath) {
2751
3330
  }
2752
3331
 
2753
3332
  // src/tools/auth-manifest-aggregator.ts
2754
- var import_fs6 = require("fs");
2755
- var import_path6 = require("path");
2756
- var import_zod11 = require("zod");
3333
+ var import_fs10 = require("fs");
3334
+ var import_path10 = require("path");
3335
+ var import_zod12 = require("zod");
2757
3336
  function computeLowestRole(requiredRoles, rbacRoles) {
2758
3337
  let lowestIndex = -1;
2759
3338
  let lowestRole = requiredRoles[0];
@@ -2773,17 +3352,17 @@ function extractPatternBaseSlug(pattern) {
2773
3352
  }
2774
3353
  function extractManifestSlugs(artifactsDir) {
2775
3354
  const slugs = /* @__PURE__ */ new Set();
2776
- if (!(0, import_fs6.existsSync)(artifactsDir)) return slugs;
3355
+ if (!(0, import_fs10.existsSync)(artifactsDir)) return slugs;
2777
3356
  let entries;
2778
3357
  try {
2779
- entries = (0, import_fs6.readdirSync)(artifactsDir);
3358
+ entries = (0, import_fs10.readdirSync)(artifactsDir);
2780
3359
  } catch {
2781
3360
  return slugs;
2782
3361
  }
2783
3362
  for (const entry of entries) {
2784
3363
  if (!entry.endsWith("-manifest.json")) continue;
2785
3364
  try {
2786
- const content = JSON.parse((0, import_fs6.readFileSync)((0, import_path6.join)(artifactsDir, entry), "utf-8"));
3365
+ const content = JSON.parse((0, import_fs10.readFileSync)((0, import_path10.join)(artifactsDir, entry), "utf-8"));
2787
3366
  const pages = content.pages;
2788
3367
  if (!Array.isArray(pages)) continue;
2789
3368
  for (const page of pages) {
@@ -2826,18 +3405,18 @@ function deriveProtectedRoutes(pages, rbacRoles) {
2826
3405
  }
2827
3406
  function readAllManifestPages(artifactsDir) {
2828
3407
  const allPages = [];
2829
- if (!(0, import_fs6.existsSync)(artifactsDir)) return allPages;
3408
+ if (!(0, import_fs10.existsSync)(artifactsDir)) return allPages;
2830
3409
  let entries;
2831
3410
  try {
2832
- entries = (0, import_fs6.readdirSync)(artifactsDir);
3411
+ entries = (0, import_fs10.readdirSync)(artifactsDir);
2833
3412
  } catch {
2834
3413
  return allPages;
2835
3414
  }
2836
3415
  for (const entry of entries) {
2837
3416
  if (entry.endsWith("-manifest.json")) {
2838
- const filePath = (0, import_path6.join)(artifactsDir, entry);
3417
+ const filePath = (0, import_path10.join)(artifactsDir, entry);
2839
3418
  try {
2840
- const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
3419
+ const raw = JSON.parse((0, import_fs10.readFileSync)(filePath, "utf-8"));
2841
3420
  if (!Array.isArray(raw.pages)) continue;
2842
3421
  for (const page of raw.pages) {
2843
3422
  if (typeof page.slug !== "string" || !page.slug) continue;
@@ -2853,9 +3432,9 @@ function readAllManifestPages(artifactsDir) {
2853
3432
  continue;
2854
3433
  }
2855
3434
  if (entry === "workflow-config.json" || entry.startsWith("workflow-config-")) {
2856
- const filePath = (0, import_path6.join)(artifactsDir, entry);
3435
+ const filePath = (0, import_path10.join)(artifactsDir, entry);
2857
3436
  try {
2858
- const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
3437
+ const raw = JSON.parse((0, import_fs10.readFileSync)(filePath, "utf-8"));
2859
3438
  const route = raw.workflow?.route;
2860
3439
  if (typeof route !== "string" || !route) continue;
2861
3440
  const slug = route.startsWith("/") ? route.slice(1) : route;
@@ -2877,16 +3456,16 @@ function registerReadAuthManifestsTool(server2) {
2877
3456
  "stackwright_pro_read_auth_manifests",
2878
3457
  "Read ALL route-producing artifacts from the pipeline artifacts directory: *-manifest.json files (pages, dashboard, geo, form-wizard) AND workflow-config.json / workflow-config-*.json (different shape: workflow.route). Deterministically derives protectedRoutes and uncoveredSlugs. Uses the both-forms invariant (swp-n7kk): every authRequired route registers BOTH /{slug} (exact) AND /{slug}/:path* (sub-path). Workflow routes appear in uncoveredSlugs (no requiredRoles declared) for auth-reconcile to resolve via cross-cutting rules from auth-config.json. Extended by swp-hng0 to cover all four manifest types (was: pages+dashboard only).",
2879
3458
  {
2880
- rbacRoles: import_zod11.z.array(import_zod11.z.string()).describe(
3459
+ rbacRoles: import_zod12.z.array(import_zod12.z.string()).describe(
2881
3460
  'RBAC role hierarchy in descending privilege order, e.g. ["ESF8_COORDINATOR", "HOSPITAL_EM", "OBSERVER"]. Used to compute the lowestRole for each authRequired page.'
2882
3461
  ),
2883
- artifactsDir: import_zod11.z.string().optional().describe(
3462
+ artifactsDir: import_zod12.z.string().optional().describe(
2884
3463
  "Absolute or relative path to the pipeline artifacts directory. Defaults to .stackwright/artifacts relative to cwd."
2885
3464
  )
2886
3465
  },
2887
3466
  (params) => {
2888
3467
  const cwd = process.cwd();
2889
- const artifactsDir = params.artifactsDir ? params.artifactsDir : (0, import_path6.join)(cwd, ".stackwright", "artifacts");
3468
+ const artifactsDir = params.artifactsDir ? params.artifactsDir : (0, import_path10.join)(cwd, ".stackwright", "artifacts");
2890
3469
  const allPages = readAllManifestPages(artifactsDir);
2891
3470
  const { protectedRoutes, uncoveredSlugs } = deriveProtectedRoutes(allPages, params.rbacRoles);
2892
3471
  const manifestsRead = [...new Set(allPages.map((p) => p.source))].sort();
@@ -2909,183 +3488,11 @@ function registerReadAuthManifestsTool(server2) {
2909
3488
  }
2910
3489
 
2911
3490
  // src/tools/pipeline.ts
2912
- var import_types3 = require("@stackwright-pro/types");
2913
-
2914
- // src/tools/get-schema.ts
2915
- var import_zod13 = require("zod");
2916
3491
  var import_types2 = require("@stackwright-pro/types");
2917
3492
 
2918
- // src/tools/validate-yaml-fragment.ts
2919
- var import_zod12 = require("zod");
2920
- var import_js_yaml2 = require("js-yaml");
2921
- var import_types = require("@stackwright-pro/types");
2922
- var SUPPORTED_SCHEMA_NAMES = [
2923
- "workflow_step",
2924
- "workflow_definition",
2925
- "workflow_field",
2926
- "workflow_action",
2927
- "navigation_item",
2928
- "auth_config"
2929
- ];
2930
- var NavigationLinkSchema = import_zod12.z.lazy(
2931
- () => import_zod12.z.object({
2932
- label: import_zod12.z.string().min(1),
2933
- href: import_zod12.z.string().min(1),
2934
- children: import_zod12.z.array(NavigationLinkSchema).optional()
2935
- })
2936
- );
2937
- var NavigationSectionSchema = import_zod12.z.object({
2938
- section: import_zod12.z.string().min(1),
2939
- items: import_zod12.z.array(NavigationLinkSchema)
2940
- });
2941
- var NavigationItemSchema = import_zod12.z.union([
2942
- NavigationLinkSchema,
2943
- NavigationSectionSchema
2944
- ]);
2945
- var FIELD_SYNONYMS = {
2946
- workflow_step: {
2947
- title: "label",
2948
- name: "label (at step level, use label: not name:)",
2949
- description: "message (use message: for step-level descriptive text)",
2950
- style: 'theme.variant \u2014 use theme: { variant: "primary" } not style: "primary"',
2951
- transitions: 'on_submit.transition \u2014 use on_submit: { transition: "next_step" } not transitions: [{then:...}]'
2952
- },
2953
- workflow_definition: {
2954
- title: "label",
2955
- name: "label (workflow-level label, not name:)",
2956
- description: "description is valid here \u2014 but message: is not (that is a step field)",
2957
- type: "id (workflows are identified by id:, not type:)"
2958
- },
2959
- workflow_field: {
2960
- id: "name (field-level identifier is name:, NOT id:)",
2961
- title: "label (field display text is label:, NOT title:)",
2962
- description: "placeholder or label (no description field on WorkflowField)"
2963
- },
2964
- workflow_action: {
2965
- style: 'theme.variant \u2014 use theme: { variant: "primary" } not style: "primary"',
2966
- then: "transition (action-level next-step is transition:, NOT then:)",
2967
- on_click: 'action: "service:..." (extract the service reference from on_click.action)',
2968
- title: "label (action display text is label:, NOT title:)",
2969
- name: "label (action display text is label:, NOT name:)"
2970
- },
2971
- navigation_item: {
2972
- children: "items (NavigationSection uses items: not children: \u2014 also use section: instead of label: for section headers)",
2973
- label: 'section (if this is a nav group/section, use section: "Title" and items: [...], not label:)',
2974
- title: "section (NavigationSection header field is section:, NOT title:)"
2975
- },
2976
- auth_config: {
2977
- method: 'type \u2014 auth config discriminates on type: "oidc" | "pki", NOT method:',
2978
- provider_type: 'type \u2014 use type: "oidc" or type: "pki"',
2979
- strategy: "type \u2014 top-level discriminator is type:, not strategy:"
2980
- }
2981
- };
2982
- function getSchema(name) {
2983
- switch (name) {
2984
- case "workflow_step":
2985
- return import_types.WorkflowStepSchema;
2986
- case "workflow_definition":
2987
- return import_types.WorkflowDefinitionSchema;
2988
- case "workflow_field":
2989
- return import_types.WorkflowFieldSchema;
2990
- case "workflow_action":
2991
- return import_types.WorkflowActionSchema;
2992
- case "navigation_item":
2993
- return NavigationItemSchema;
2994
- case "auth_config":
2995
- return import_types.authConfigSchema;
2996
- }
2997
- }
2998
- function enrichErrors(issues, synonyms) {
2999
- return issues.map((issue) => {
3000
- const pathStr = issue.path.join(".");
3001
- const lastSegment = issue.path[issue.path.length - 1];
3002
- const fieldName = typeof lastSegment === "string" ? lastSegment : void 0;
3003
- const didYouMean = fieldName ? synonyms[fieldName] : void 0;
3004
- return {
3005
- path: pathStr || "(root)",
3006
- message: issue.message,
3007
- ...didYouMean ? { didYouMean } : {}
3008
- };
3009
- });
3010
- }
3011
- function handleValidateYamlFragment(input) {
3012
- const { schemaName, yaml: yaml4 } = input;
3013
- if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
3014
- return {
3015
- valid: false,
3016
- parseError: `Unknown schemaName: "${schemaName}". Supported: ${SUPPORTED_SCHEMA_NAMES.join(", ")}`
3017
- };
3018
- }
3019
- const name = schemaName;
3020
- let parsed;
3021
- try {
3022
- parsed = (0, import_js_yaml2.load)(yaml4);
3023
- } catch (err) {
3024
- return {
3025
- valid: false,
3026
- parseError: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`
3027
- };
3028
- }
3029
- const schema = getSchema(name);
3030
- const result = schema.safeParse(parsed);
3031
- if (result.success) {
3032
- return { valid: true };
3033
- }
3034
- const synonyms = FIELD_SYNONYMS[name];
3035
- const errors = enrichErrors(result.error.issues, synonyms);
3036
- return { valid: false, errors };
3037
- }
3038
- function registerValidateYamlFragmentTool(server2) {
3039
- server2.tool(
3040
- "stackwright_pro_validate_yaml_fragment",
3041
- 'Validates a YAML snippet against a canonical Zod schema before writing to disk. Returns actionable errors with "did you mean?" suggestions for common field-name drift (title vs label, id vs name, style vs theme.variant, etc.). Call this before stackwright_pro_safe_write to catch schema violations in-session.',
3042
- {
3043
- schemaName: import_zod12.z.enum(SUPPORTED_SCHEMA_NAMES).describe(
3044
- "Schema to validate against. One of: workflow_step, workflow_definition, workflow_field, workflow_action, navigation_item, auth_config"
3045
- ),
3046
- yaml: import_zod12.z.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
3047
- },
3048
- async ({ schemaName, yaml: yaml4 }) => {
3049
- const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
3050
- return {
3051
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
3052
- };
3053
- }
3054
- );
3055
- }
3056
-
3057
3493
  // src/tools/get-schema.ts
3058
- var NavigationLinkSchema2 = import_zod13.z.lazy(
3059
- () => import_zod13.z.object({
3060
- label: import_zod13.z.string().min(1),
3061
- href: import_zod13.z.string().min(1),
3062
- children: import_zod13.z.array(NavigationLinkSchema2).optional()
3063
- })
3064
- );
3065
- var NavigationSectionSchema2 = import_zod13.z.object({
3066
- section: import_zod13.z.string().min(1),
3067
- items: import_zod13.z.array(NavigationLinkSchema2)
3068
- });
3069
- var NavigationItemSchema2 = import_zod13.z.union([
3070
- NavigationLinkSchema2,
3071
- NavigationSectionSchema2
3072
- ]);
3073
- function getZodSchema(name) {
3074
- switch (name) {
3075
- case "workflow_step":
3076
- return import_types2.WorkflowStepSchema;
3077
- case "workflow_definition":
3078
- return import_types2.WorkflowDefinitionSchema;
3079
- case "workflow_field":
3080
- return import_types2.WorkflowFieldSchema;
3081
- case "workflow_action":
3082
- return import_types2.WorkflowActionSchema;
3083
- case "navigation_item":
3084
- return NavigationItemSchema2;
3085
- case "auth_config":
3086
- return import_types2.authConfigSchema;
3087
- }
3088
- }
3494
+ var import_zod13 = require("zod");
3495
+ var PHASE_SCHEMA_NAMES = buildPhaseSchemaNames();
3089
3496
  function formatType(node, depth = 0) {
3090
3497
  if (node.const !== void 0) return JSON.stringify(node.const);
3091
3498
  if (node.enum) return node.enum.map((v) => JSON.stringify(v)).join(" | ");
@@ -3112,7 +3519,7 @@ function formatType(node, depth = 0) {
3112
3519
  if (node.maximum !== void 0) constraints.push(`max ${node.maximum}`);
3113
3520
  return constraints.length > 0 ? `${base} (${constraints.join(", ")})` : base;
3114
3521
  }
3115
- function formatObjectSchema(node, schemaName, synonyms) {
3522
+ function formatObjectSchema(node, schemaName, reverseSynonyms) {
3116
3523
  const lines = [];
3117
3524
  const displayName = schemaName.split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join("");
3118
3525
  lines.push(`${displayName}:`);
@@ -3126,7 +3533,8 @@ function formatObjectSchema(node, schemaName, synonyms) {
3126
3533
  for (const [field, fieldNode] of Object.entries(branch.properties ?? {})) {
3127
3534
  if (fieldNode.const !== void 0) continue;
3128
3535
  const isRequired = req.has(field);
3129
- const hint = synonyms[field] ? ` -- WARNING: use ${field}: not ${synonyms[field]}` : "";
3536
+ const correctFor = reverseSynonyms[field];
3537
+ const hint = correctFor ? ` -- WARNING: use ${correctFor}: not ${field}:` : "";
3130
3538
  lines.push(
3131
3539
  ` ${isRequired ? "REQUIRED" : "optional"}: ${field}: ${formatType(fieldNode)}${hint}`
3132
3540
  );
@@ -3134,6 +3542,12 @@ function formatObjectSchema(node, schemaName, synonyms) {
3134
3542
  }
3135
3543
  return lines.join("\n");
3136
3544
  }
3545
+ if (!node.properties && !node.oneOf && !node.anyOf) {
3546
+ const typeStr = formatType(node);
3547
+ lines.push(` type: ${typeStr}`);
3548
+ if (node.description) lines.push(` description: ${node.description}`);
3549
+ return lines.join("\n");
3550
+ }
3137
3551
  const required = new Set(node.required ?? []);
3138
3552
  const properties = node.properties ?? {};
3139
3553
  const requiredFields = Object.entries(properties).filter(([k]) => required.has(k));
@@ -3142,8 +3556,7 @@ function formatObjectSchema(node, schemaName, synonyms) {
3142
3556
  lines.push(" REQUIRED:");
3143
3557
  for (const [field, fieldNode] of requiredFields) {
3144
3558
  const typeStr = formatType(fieldNode);
3145
- const warn = synonyms[field] ? ` -- CAUTION: this schema uses ${field}` : "";
3146
- lines.push(` ${field}: ${typeStr}${warn}`);
3559
+ lines.push(` ${field}: ${typeStr}`);
3147
3560
  }
3148
3561
  }
3149
3562
  if (optionalFields.length > 0) {
@@ -3157,24 +3570,30 @@ function formatObjectSchema(node, schemaName, synonyms) {
3157
3570
  }
3158
3571
  function handleGetSchema(input) {
3159
3572
  const { schemaName } = input;
3160
- if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
3573
+ const entry = SCHEMA_REGISTRY.get(schemaName);
3574
+ if (!entry) {
3161
3575
  return {
3162
- error: `Unknown schemaName: "${schemaName}". Supported: ${SUPPORTED_SCHEMA_NAMES.join(", ")}`
3576
+ error: `Unknown schemaName: "${schemaName}". Supported: ${REGISTRY_SCHEMA_NAMES.join(", ")}`
3163
3577
  };
3164
3578
  }
3165
- const name = schemaName;
3166
- const schema = getZodSchema(name);
3167
- const synonyms = FIELD_SYNONYMS[name];
3579
+ const synonyms = entry.synonyms ?? {};
3580
+ const reverseSynonyms = {};
3581
+ for (const [correct, wrongs] of Object.entries(synonyms)) {
3582
+ for (const wrong of wrongs) {
3583
+ reverseSynonyms[wrong] = correct;
3584
+ }
3585
+ }
3586
+ const ctx = input.projectRoot ? { projectRoot: input.projectRoot } : void 0;
3587
+ const resolvedSchema = getSchemaForEntry(entry, ctx);
3168
3588
  let jsonSchema;
3169
3589
  try {
3170
- const raw = import_zod13.z.toJSONSchema(schema, { unrepresentable: "any" });
3171
- jsonSchema = raw;
3590
+ jsonSchema = import_zod13.z.toJSONSchema(resolvedSchema, { unrepresentable: "any" });
3172
3591
  } catch {
3173
3592
  jsonSchema = { type: "object", description: "Schema introspection unavailable for this type" };
3174
3593
  }
3175
- const summary = formatObjectSchema(jsonSchema, name, synonyms);
3594
+ const summary = formatObjectSchema(jsonSchema, schemaName, reverseSynonyms);
3176
3595
  const synonymWarnings = Object.entries(synonyms).map(
3177
- ([wrong, correct]) => ` WARNING: use "${correct}" \u2014 NOT "${wrong}"`
3596
+ ([correct, wrongs]) => ` WARNING: use "${correct}" \u2014 NOT ${wrongs.map((w) => `"${w}"`).join(" or ")}`
3178
3597
  );
3179
3598
  const fullText = [
3180
3599
  summary,
@@ -3184,19 +3603,12 @@ function handleGetSchema(input) {
3184
3603
  ].join("\n");
3185
3604
  const tokenEstimate = Math.ceil(fullText.length / 4);
3186
3605
  return {
3187
- schemaName: name,
3606
+ schemaName,
3188
3607
  summary,
3189
3608
  synonymWarnings,
3190
3609
  tokenEstimate
3191
3610
  };
3192
3611
  }
3193
- var PHASE_SCHEMA_NAMES = {
3194
- workflow: ["workflow_step", "workflow_definition", "workflow_field", "workflow_action"],
3195
- pages: ["navigation_item"],
3196
- dashboard: ["navigation_item"],
3197
- polish: ["navigation_item"],
3198
- auth: ["auth_config"]
3199
- };
3200
3612
  function buildSchemaReferenceBlock(phase) {
3201
3613
  const schemaNames = PHASE_SCHEMA_NAMES[phase];
3202
3614
  if (!schemaNames || schemaNames.length === 0) return null;
@@ -3221,12 +3633,13 @@ function registerGetSchemaTool(server2) {
3221
3633
  "stackwright_pro_get_schema",
3222
3634
  'Returns a compact, LLM-friendly summary of a canonical schema derived programmatically from Zod. Includes required/optional fields, types, constraints, and "did you mean?" warnings for common field-name drift. Use before authoring YAML to confirm exact field names.',
3223
3635
  {
3224
- schemaName: import_zod13.z.enum(SUPPORTED_SCHEMA_NAMES).describe(
3225
- "Schema to summarize. One of: workflow_step, workflow_definition, workflow_field, workflow_action, navigation_item, auth_config"
3636
+ schemaName: import_zod13.z.string().describe(`Schema to summarize. Available: ${REGISTRY_SCHEMA_NAMES.join(", ")}`),
3637
+ projectRoot: import_zod13.z.string().optional().describe(
3638
+ "Absolute path to the project root. Required for factory schemas like theme_status_token. Defaults to cwd."
3226
3639
  )
3227
3640
  },
3228
- async ({ schemaName }) => {
3229
- const result = handleGetSchema({ schemaName });
3641
+ async ({ schemaName, projectRoot }) => {
3642
+ const result = handleGetSchema({ schemaName, projectRoot: projectRoot ?? process.cwd() });
3230
3643
  if ("error" in result) {
3231
3644
  return {
3232
3645
  content: [{ type: "text", text: JSON.stringify({ error: result.error }) }],
@@ -3246,8 +3659,8 @@ function registerGetSchemaTool(server2) {
3246
3659
  }
3247
3660
 
3248
3661
  // src/tools/pipeline-graph.ts
3249
- var import_fs7 = require("fs");
3250
- var import_path7 = require("path");
3662
+ var import_fs11 = require("fs");
3663
+ var import_path11 = require("path");
3251
3664
  var MANIFEST_NAME_TO_PHASE = {
3252
3665
  "stackwright-pro-designer-otter": "designer",
3253
3666
  "stackwright-pro-theme-otter": "theme",
@@ -3279,10 +3692,10 @@ var OTTER_SEARCH_PATHS = [
3279
3692
  ];
3280
3693
  function resolveOtterDirFromCwd() {
3281
3694
  const cwd = process.cwd();
3282
- for (const relative2 of OTTER_SEARCH_PATHS) {
3283
- const candidate = (0, import_path7.join)(cwd, relative2);
3695
+ for (const relative4 of OTTER_SEARCH_PATHS) {
3696
+ const candidate = (0, import_path11.join)(cwd, relative4);
3284
3697
  try {
3285
- (0, import_fs7.lstatSync)(candidate);
3698
+ (0, import_fs11.lstatSync)(candidate);
3286
3699
  return candidate;
3287
3700
  } catch {
3288
3701
  }
@@ -3292,14 +3705,14 @@ function resolveOtterDirFromCwd() {
3292
3705
  );
3293
3706
  }
3294
3707
  function loadOtterManifestsFromDir(otterDir) {
3295
- const entries = (0, import_fs7.readdirSync)(otterDir);
3708
+ const entries = (0, import_fs11.readdirSync)(otterDir);
3296
3709
  const manifests = [];
3297
3710
  for (const filename of entries) {
3298
3711
  if (!filename.endsWith("-otter.json")) continue;
3299
- const filePath = (0, import_path7.join)(otterDir, filename);
3300
- if ((0, import_fs7.lstatSync)(filePath).isSymbolicLink()) continue;
3712
+ const filePath = (0, import_path11.join)(otterDir, filename);
3713
+ if ((0, import_fs11.lstatSync)(filePath).isSymbolicLink()) continue;
3301
3714
  try {
3302
- const raw = (0, import_fs7.readFileSync)(filePath, "utf-8");
3715
+ const raw = (0, import_fs11.readFileSync)(filePath, "utf-8");
3303
3716
  const manifest = JSON.parse(raw);
3304
3717
  manifests.push(manifest);
3305
3718
  } catch (err) {
@@ -3404,19 +3817,19 @@ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
3404
3817
  function loadDistributedOtterManifests(cwd) {
3405
3818
  const results = [];
3406
3819
  for (const namespace of DISTRIBUTED_NAMESPACES) {
3407
- const nsDir = (0, import_path7.join)(cwd, "node_modules", namespace);
3820
+ const nsDir = (0, import_path11.join)(cwd, "node_modules", namespace);
3408
3821
  let pkgDirNames;
3409
3822
  try {
3410
- pkgDirNames = (0, import_fs7.readdirSync)(nsDir);
3823
+ pkgDirNames = (0, import_fs11.readdirSync)(nsDir);
3411
3824
  } catch {
3412
3825
  continue;
3413
3826
  }
3414
3827
  for (const pkgDirName of pkgDirNames) {
3415
- const pkgDir = (0, import_path7.join)(nsDir, pkgDirName);
3828
+ const pkgDir = (0, import_path11.join)(nsDir, pkgDirName);
3416
3829
  const packageName = `${namespace}/${pkgDirName}`;
3417
3830
  let otterRelPaths;
3418
3831
  try {
3419
- const raw = (0, import_fs7.readFileSync)((0, import_path7.join)(pkgDir, "package.json"), "utf-8");
3832
+ const raw = (0, import_fs11.readFileSync)((0, import_path11.join)(pkgDir, "package.json"), "utf-8");
3420
3833
  const pkgJson = JSON.parse(raw);
3421
3834
  const declared = pkgJson.stackwright?.otters;
3422
3835
  if (!Array.isArray(declared) || declared.length === 0) continue;
@@ -3425,9 +3838,9 @@ function loadDistributedOtterManifests(cwd) {
3425
3838
  continue;
3426
3839
  }
3427
3840
  for (const relPath of otterRelPaths) {
3428
- const otterFilePath = (0, import_path7.join)(pkgDir, relPath);
3841
+ const otterFilePath = (0, import_path11.join)(pkgDir, relPath);
3429
3842
  try {
3430
- const raw = (0, import_fs7.readFileSync)(otterFilePath, "utf-8");
3843
+ const raw = (0, import_fs11.readFileSync)(otterFilePath, "utf-8");
3431
3844
  const manifest = JSON.parse(raw);
3432
3845
  results.push({ manifest, packageName });
3433
3846
  } catch (err) {
@@ -3572,45 +3985,45 @@ function createDefaultState() {
3572
3985
  };
3573
3986
  }
3574
3987
  function statePath(cwd) {
3575
- return (0, import_path8.join)(cwd, ".stackwright", "pipeline-state.json");
3988
+ return (0, import_path12.join)(cwd, ".stackwright", "pipeline-state.json");
3576
3989
  }
3577
3990
  function readState(cwd) {
3578
3991
  const p = statePath(cwd);
3579
- if (!(0, import_fs8.existsSync)(p)) return createDefaultState();
3992
+ if (!(0, import_fs12.existsSync)(p)) return createDefaultState();
3580
3993
  const raw = JSON.parse(safeReadSync(p));
3581
3994
  const result = PipelineStateSchema.safeParse(raw);
3582
3995
  if (!result.success) return createDefaultState();
3583
3996
  return result.data;
3584
3997
  }
3585
3998
  function safeWriteSync(filePath, content) {
3586
- if ((0, import_fs8.existsSync)(filePath)) {
3587
- const stat = (0, import_fs8.lstatSync)(filePath);
3999
+ if ((0, import_fs12.existsSync)(filePath)) {
4000
+ const stat = (0, import_fs12.lstatSync)(filePath);
3588
4001
  if (stat.isSymbolicLink()) {
3589
4002
  throw new Error(`Refusing to write to symlink: ${filePath}`);
3590
4003
  }
3591
4004
  }
3592
- (0, import_fs8.writeFileSync)(filePath, content);
4005
+ (0, import_fs12.writeFileSync)(filePath, content);
3593
4006
  }
3594
4007
  function safeReadSync(filePath) {
3595
- if ((0, import_fs8.existsSync)(filePath)) {
3596
- const stat = (0, import_fs8.lstatSync)(filePath);
4008
+ if ((0, import_fs12.existsSync)(filePath)) {
4009
+ const stat = (0, import_fs12.lstatSync)(filePath);
3597
4010
  if (stat.isSymbolicLink()) {
3598
4011
  throw new Error(`Refusing to read symlink: ${filePath}`);
3599
4012
  }
3600
4013
  }
3601
- return (0, import_fs8.readFileSync)(filePath, "utf-8");
4014
+ return (0, import_fs12.readFileSync)(filePath, "utf-8");
3602
4015
  }
3603
4016
  function writeState(cwd, state) {
3604
- const dir = (0, import_path8.join)(cwd, ".stackwright");
3605
- (0, import_fs8.mkdirSync)(dir, { recursive: true });
4017
+ const dir = (0, import_path12.join)(cwd, ".stackwright");
4018
+ (0, import_fs12.mkdirSync)(dir, { recursive: true });
3606
4019
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3607
4020
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
3608
4021
  }
3609
4022
  function updateStateAtomic(cwd, mutator) {
3610
- const dir = (0, import_path8.join)(cwd, ".stackwright");
3611
- (0, import_fs8.mkdirSync)(dir, { recursive: true });
4023
+ const dir = (0, import_path12.join)(cwd, ".stackwright");
4024
+ (0, import_fs12.mkdirSync)(dir, { recursive: true });
3612
4025
  const path5 = statePath(cwd);
3613
- if (!(0, import_fs8.existsSync)(path5)) {
4026
+ if (!(0, import_fs12.existsSync)(path5)) {
3614
4027
  safeWriteSync(path5, JSON.stringify(createDefaultState(), null, 2) + "\n");
3615
4028
  }
3616
4029
  const release = (0, import_proper_lockfile.lockSync)(path5, {
@@ -3644,8 +4057,8 @@ function handleGetPipelineState(_cwd) {
3644
4057
  const cwd = _cwd ?? process.cwd();
3645
4058
  try {
3646
4059
  const state = readState(cwd);
3647
- const keyPath = (0, import_path8.join)(cwd, ".stackwright", "pipeline-keys.json");
3648
- if (!(0, import_fs8.existsSync)(keyPath)) {
4060
+ const keyPath = (0, import_path12.join)(cwd, ".stackwright", "pipeline-keys.json");
4061
+ if (!(0, import_fs12.existsSync)(keyPath)) {
3649
4062
  try {
3650
4063
  const { fingerprint } = initPipelineKeys(cwd);
3651
4064
  state["signingKeyFingerprint"] = fingerprint;
@@ -3784,8 +4197,8 @@ function handleCheckExecutionReady(_cwd, phase) {
3784
4197
  if (AUTO_EXECUTE_PHASES.has(phase)) {
3785
4198
  return { text: JSON.stringify({ ready: true, phase, autoExecute: true }), isError: false };
3786
4199
  }
3787
- const answerFile = (0, import_path8.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3788
- if (!(0, import_fs8.existsSync)(answerFile)) {
4200
+ const answerFile = (0, import_path12.join)(cwd, ".stackwright", "answers", `${phase}.json`);
4201
+ if (!(0, import_fs12.existsSync)(answerFile)) {
3789
4202
  return {
3790
4203
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
3791
4204
  isError: false
@@ -3809,7 +4222,7 @@ function handleCheckExecutionReady(_cwd, phase) {
3809
4222
  }
3810
4223
  }
3811
4224
  try {
3812
- const answersDir = (0, import_path8.join)(cwd, ".stackwright", "answers");
4225
+ const answersDir = (0, import_path12.join)(cwd, ".stackwright", "answers");
3813
4226
  const answeredPhases = [];
3814
4227
  const missingPhases = [];
3815
4228
  for (const phase2 of PHASE_ORDER) {
@@ -3817,8 +4230,8 @@ function handleCheckExecutionReady(_cwd, phase) {
3817
4230
  answeredPhases.push(phase2);
3818
4231
  continue;
3819
4232
  }
3820
- const answerFile = (0, import_path8.join)(answersDir, `${phase2}.json`);
3821
- if ((0, import_fs8.existsSync)(answerFile)) {
4233
+ const answerFile = (0, import_path12.join)(answersDir, `${phase2}.json`);
4234
+ if ((0, import_fs12.existsSync)(answerFile)) {
3822
4235
  try {
3823
4236
  const raw = safeReadSync(answerFile);
3824
4237
  const parsed = JSON.parse(raw);
@@ -3887,7 +4300,7 @@ function handleGetReadyPhases(_cwd) {
3887
4300
  function handleListArtifacts(_cwd) {
3888
4301
  const cwd = _cwd ?? process.cwd();
3889
4302
  try {
3890
- const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
4303
+ const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
3891
4304
  let manifest = null;
3892
4305
  try {
3893
4306
  manifest = loadSignatureManifest(cwd);
@@ -3897,8 +4310,8 @@ function handleListArtifacts(_cwd) {
3897
4310
  let completedCount = 0;
3898
4311
  for (const phase of PHASE_ORDER) {
3899
4312
  const expectedFile = PHASE_ARTIFACT[phase];
3900
- const fullPath = (0, import_path8.join)(artifactsDir, expectedFile);
3901
- const exists = (0, import_fs8.existsSync)(fullPath);
4313
+ const fullPath = (0, import_path12.join)(artifactsDir, expectedFile);
4314
+ const exists = (0, import_fs12.existsSync)(fullPath);
3902
4315
  if (exists) completedCount++;
3903
4316
  let signed = false;
3904
4317
  let signatureValid = null;
@@ -3951,9 +4364,9 @@ function handleWritePhaseQuestions(input) {
3951
4364
  }
3952
4365
  } catch {
3953
4366
  }
3954
- const questionsDir = (0, import_path8.join)(cwd, ".stackwright", "questions");
3955
- (0, import_fs8.mkdirSync)(questionsDir, { recursive: true });
3956
- const filePath = (0, import_path8.join)(questionsDir, `${phase}.json`);
4367
+ const questionsDir = (0, import_path12.join)(cwd, ".stackwright", "questions");
4368
+ (0, import_fs12.mkdirSync)(questionsDir, { recursive: true });
4369
+ const filePath = (0, import_path12.join)(questionsDir, `${phase}.json`);
3957
4370
  const payload = {
3958
4371
  version: "1.0",
3959
4372
  phase,
@@ -3993,14 +4406,14 @@ function handleBuildSpecialistPrompt(input) {
3993
4406
  };
3994
4407
  }
3995
4408
  try {
3996
- const answersPath = (0, import_path8.join)(cwd, ".stackwright", "answers", `${phase}.json`);
4409
+ const answersPath = (0, import_path12.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3997
4410
  let answers = {};
3998
- if ((0, import_fs8.existsSync)(answersPath)) {
4411
+ if ((0, import_fs12.existsSync)(answersPath)) {
3999
4412
  answers = JSON.parse(safeReadSync(answersPath));
4000
4413
  }
4001
4414
  let buildContextText = "";
4002
- const buildContextPath = (0, import_path8.join)(cwd, ".stackwright", "build-context.json");
4003
- if ((0, import_fs8.existsSync)(buildContextPath)) {
4415
+ const buildContextPath = (0, import_path12.join)(cwd, ".stackwright", "build-context.json");
4416
+ if ((0, import_fs12.existsSync)(buildContextPath)) {
4004
4417
  try {
4005
4418
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
4006
4419
  if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
@@ -4015,8 +4428,8 @@ function handleBuildSpecialistPrompt(input) {
4015
4428
  const missingDependencies = [];
4016
4429
  for (const dep of deps) {
4017
4430
  const artifactFile = PHASE_ARTIFACT[dep];
4018
- const artifactPath = (0, import_path8.join)(cwd, ".stackwright", "artifacts", artifactFile);
4019
- if ((0, import_fs8.existsSync)(artifactPath)) {
4431
+ const artifactPath = (0, import_path12.join)(cwd, ".stackwright", "artifacts", artifactFile);
4432
+ if ((0, import_fs12.existsSync)(artifactPath)) {
4020
4433
  const rawContent = safeReadSync(artifactPath);
4021
4434
  const rawBytes = Buffer.from(rawContent, "utf-8");
4022
4435
  const content = JSON.parse(rawContent);
@@ -4081,8 +4494,8 @@ ${JSON.stringify(content, null, 2)}`
4081
4494
  parts.push("BUILD_CONTEXT:", buildContextText, "");
4082
4495
  }
4083
4496
  if (phase === "auth") {
4084
- const initContextPath = (0, import_path8.join)(cwd, ".stackwright", "init-context.json");
4085
- if ((0, import_fs8.existsSync)(initContextPath)) {
4497
+ const initContextPath = (0, import_path12.join)(cwd, ".stackwright", "init-context.json");
4498
+ if ((0, import_fs12.existsSync)(initContextPath)) {
4086
4499
  try {
4087
4500
  const initContext = JSON.parse(safeReadSync(initContextPath));
4088
4501
  if (initContext.devOnly === true || initContext.nonInteractive === true) {
@@ -4223,7 +4636,12 @@ var PHASE_ARTIFACT_SCHEMA = {
4223
4636
  "--surface": "210 40% 98%",
4224
4637
  "--border": "214.3 31.8% 91.4%"
4225
4638
  },
4226
- dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" }
4639
+ dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" },
4640
+ // Optional — include when design language has status semantics (swp-cxwu).
4641
+ // Array of status-* token names emitted in tokens.colors.
4642
+ // Consumed by validate_artifact theme enforcement to reject unknown status tokens.
4643
+ // OMIT if project has no status-coded data.
4644
+ statusLadder: ["status-ok", "status-warning", "status-error", "status-info"]
4227
4645
  },
4228
4646
  null,
4229
4647
  2
@@ -4650,7 +5068,7 @@ function _validateArtifactInner(input) {
4650
5068
  return { success: true };
4651
5069
  }
4652
5070
  if (typeof workflowData === "object" && workflowData !== null && "workflow" in workflowData) {
4653
- const result = import_types3.WorkflowFileSchema.safeParse(workflowData);
5071
+ const result = import_types2.WorkflowFileSchema.safeParse(workflowData);
4654
5072
  if (!result.success) {
4655
5073
  const issues = result.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
4656
5074
  return { success: false, error: { message: issues } };
@@ -4661,7 +5079,7 @@ function _validateArtifactInner(input) {
4661
5079
  auth: (artifact2) => {
4662
5080
  const authConfig = artifact2["authConfig"];
4663
5081
  if (!authConfig) return { success: true };
4664
- const result = import_types3.authConfigSchema.safeParse(authConfig);
5082
+ const result = import_types2.authConfigSchema.safeParse(authConfig);
4665
5083
  if (!result.success) {
4666
5084
  const issues = result.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
4667
5085
  return { success: false, error: { message: issues } };
@@ -4693,21 +5111,21 @@ function _validateArtifactInner(input) {
4693
5111
  // state-machine in this services-config artifact.
4694
5112
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
4695
5113
  services: (artifact2) => {
4696
- const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
5114
+ const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
4697
5115
  const workflowArtifactFiles = [];
4698
- if ((0, import_fs8.existsSync)(artifactsDir)) {
5116
+ if ((0, import_fs12.existsSync)(artifactsDir)) {
4699
5117
  try {
4700
- const entries = (0, import_fs8.readdirSync)(artifactsDir);
5118
+ const entries = (0, import_fs12.readdirSync)(artifactsDir);
4701
5119
  for (const entry of entries) {
4702
5120
  if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
4703
- workflowArtifactFiles.push((0, import_path8.join)(artifactsDir, entry));
5121
+ workflowArtifactFiles.push((0, import_path12.join)(artifactsDir, entry));
4704
5122
  }
4705
5123
  }
4706
5124
  } catch {
4707
5125
  }
4708
5126
  }
4709
- const legacyPath = (0, import_path8.join)(artifactsDir, "workflow-config.json");
4710
- if ((0, import_fs8.existsSync)(legacyPath)) {
5127
+ const legacyPath = (0, import_path12.join)(artifactsDir, "workflow-config.json");
5128
+ if ((0, import_fs12.existsSync)(legacyPath)) {
4711
5129
  workflowArtifactFiles.push(legacyPath);
4712
5130
  }
4713
5131
  if (workflowArtifactFiles.length === 0) {
@@ -4716,7 +5134,7 @@ function _validateArtifactInner(input) {
4716
5134
  const declaredHooks = [];
4717
5135
  for (const filePath of workflowArtifactFiles) {
4718
5136
  try {
4719
- const parsed = JSON.parse((0, import_fs8.readFileSync)(filePath, "utf-8"));
5137
+ const parsed = JSON.parse((0, import_fs12.readFileSync)(filePath, "utf-8"));
4720
5138
  if (Array.isArray(parsed.serviceHooks)) {
4721
5139
  declaredHooks.push(...parsed.serviceHooks);
4722
5140
  }
@@ -4806,6 +5224,42 @@ function _validateArtifactInner(input) {
4806
5224
  return { text: JSON.stringify(result), isError: false };
4807
5225
  }
4808
5226
  }
5227
+ if (phase === "pages" || phase === "dashboard" || phase === "geo") {
5228
+ const themeResult = validateThemeTokenUsage(cwd);
5229
+ if (!themeResult.valid && !themeResult.skipped) {
5230
+ const result = {
5231
+ valid: false,
5232
+ phase,
5233
+ violation: "schema-mismatch",
5234
+ retryPrompt: themeResult.retryPrompt
5235
+ };
5236
+ return { text: JSON.stringify(result), isError: false };
5237
+ }
5238
+ }
5239
+ if (phase === "pages" || phase === "dashboard" || phase === "geo") {
5240
+ const globalsResult = validateBuildContextGlobals(cwd);
5241
+ if (!globalsResult.valid && !globalsResult.skipped) {
5242
+ const result = {
5243
+ valid: false,
5244
+ phase,
5245
+ violation: "schema-mismatch",
5246
+ retryPrompt: globalsResult.retryPrompt
5247
+ };
5248
+ return { text: JSON.stringify(result), isError: false };
5249
+ }
5250
+ }
5251
+ if (phase === "pages") {
5252
+ const workflowCrossCheck = validateWorkflowContainerPages(cwd);
5253
+ if (!workflowCrossCheck.valid) {
5254
+ const result = {
5255
+ valid: false,
5256
+ phase,
5257
+ violation: "schema-mismatch",
5258
+ retryPrompt: workflowCrossCheck.retryPrompt
5259
+ };
5260
+ return { text: JSON.stringify(result), isError: false };
5261
+ }
5262
+ }
4809
5263
  if (phase === "api") {
4810
5264
  const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
4811
5265
  if (!apiAuthResult.valid) {
@@ -4834,7 +5288,7 @@ function _validateArtifactInner(input) {
4834
5288
  const authCfg = artifact["authConfig"];
4835
5289
  const routes = authCfg?.["protectedRoutes"];
4836
5290
  if (Array.isArray(routes) && routes.length > 0) {
4837
- const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
5291
+ const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
4838
5292
  const knownSlugs = extractManifestSlugs(artifactsDir);
4839
5293
  const orphanPatterns = [];
4840
5294
  for (const route of routes) {
@@ -4851,10 +5305,10 @@ function _validateArtifactInner(input) {
4851
5305
  }
4852
5306
  }
4853
5307
  try {
4854
- const artifactsDir = (0, import_path8.join)(cwd, ".stackwright", "artifacts");
4855
- (0, import_fs8.mkdirSync)(artifactsDir, { recursive: true });
5308
+ const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
5309
+ (0, import_fs12.mkdirSync)(artifactsDir, { recursive: true });
4856
5310
  const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
4857
- const artifactPath = (0, import_path8.join)(artifactsDir, artifactFile);
5311
+ const artifactPath = (0, import_path12.join)(artifactsDir, artifactFile);
4858
5312
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
4859
5313
  const artifactBytes = Buffer.from(serialized, "utf-8");
4860
5314
  safeWriteSync(artifactPath, serialized);
@@ -5084,10 +5538,10 @@ function registerPipelineTools(server2) {
5084
5538
  isError: true
5085
5539
  };
5086
5540
  }
5087
- const questionsDir = (0, import_path8.join)(process.cwd(), ".stackwright", "questions");
5088
- (0, import_fs8.mkdirSync)(questionsDir, { recursive: true });
5089
- const outPath = (0, import_path8.join)(questionsDir, `${phase}.json`);
5090
- (0, import_fs8.writeFileSync)(
5541
+ const questionsDir = (0, import_path12.join)(process.cwd(), ".stackwright", "questions");
5542
+ (0, import_fs12.mkdirSync)(questionsDir, { recursive: true });
5543
+ const outPath = (0, import_path12.join)(questionsDir, `${phase}.json`);
5544
+ (0, import_fs12.writeFileSync)(
5091
5545
  outPath,
5092
5546
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
5093
5547
  );
@@ -5179,8 +5633,8 @@ function registerPipelineTools(server2) {
5179
5633
 
5180
5634
  // src/tools/safe-write.ts
5181
5635
  var import_zod15 = require("zod");
5182
- var import_fs9 = require("fs");
5183
- var import_path9 = require("path");
5636
+ var import_fs13 = require("fs");
5637
+ var import_path13 = require("path");
5184
5638
  var import_telemetry2 = require("@stackwright-pro/telemetry");
5185
5639
  var OTTER_WRITE_ALLOWLISTS = {
5186
5640
  "stackwright-pro-designer-otter": [
@@ -5257,7 +5711,18 @@ var OTTER_WRITE_ALLOWLISTS = {
5257
5711
  "stackwright-pro-form-wizard-otter": [
5258
5712
  { prefix: "workflows/", suffix: ".yml", description: "Workflow definition" },
5259
5713
  { prefix: "workflows/", suffix: ".yaml", description: "Workflow definition" },
5260
- { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" }
5714
+ { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" },
5715
+ // swp-dvde: workflow-otter owns the container page to prevent the 404 gap
5716
+ {
5717
+ prefix: "pages/workflows/",
5718
+ suffix: "/content.yml",
5719
+ description: "Workflow container page content (swp-dvde)"
5720
+ },
5721
+ {
5722
+ prefix: "pages/workflows/",
5723
+ suffix: "/content.yaml",
5724
+ description: "Workflow container page content (swp-dvde)"
5725
+ }
5261
5726
  ],
5262
5727
  "stackwright-pro-scaffold-otter": [
5263
5728
  { prefix: "app/", suffix: ".tsx", description: "Next.js App Router shell files" },
@@ -5376,7 +5841,7 @@ function getMaxBytesForPath(filePath) {
5376
5841
  }
5377
5842
  var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
5378
5843
  function extractPageSlug(filePath) {
5379
- const match = (0, import_path9.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
5844
+ const match = (0, import_path13.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
5380
5845
  return match?.[1] ?? null;
5381
5846
  }
5382
5847
  function extractContentTypes(yamlContent) {
@@ -5392,27 +5857,27 @@ function extractContentTypes(yamlContent) {
5392
5857
  return types.length > 0 ? types : ["unknown"];
5393
5858
  }
5394
5859
  function readPageRegistry(cwd) {
5395
- const regPath = (0, import_path9.join)(cwd, PAGE_REGISTRY_FILE);
5396
- if (!(0, import_fs9.existsSync)(regPath)) return {};
5860
+ const regPath = (0, import_path13.join)(cwd, PAGE_REGISTRY_FILE);
5861
+ if (!(0, import_fs13.existsSync)(regPath)) return {};
5397
5862
  try {
5398
- const stat = (0, import_fs9.lstatSync)(regPath);
5863
+ const stat = (0, import_fs13.lstatSync)(regPath);
5399
5864
  if (stat.isSymbolicLink()) return {};
5400
- return JSON.parse((0, import_fs9.readFileSync)(regPath, "utf-8"));
5865
+ return JSON.parse((0, import_fs13.readFileSync)(regPath, "utf-8"));
5401
5866
  } catch {
5402
5867
  return {};
5403
5868
  }
5404
5869
  }
5405
5870
  function writePageRegistry(cwd, registry) {
5406
- const dir = (0, import_path9.join)(cwd, ".stackwright");
5407
- (0, import_fs9.mkdirSync)(dir, { recursive: true });
5408
- const regPath = (0, import_path9.join)(cwd, PAGE_REGISTRY_FILE);
5409
- if ((0, import_fs9.existsSync)(regPath)) {
5410
- const stat = (0, import_fs9.lstatSync)(regPath);
5871
+ const dir = (0, import_path13.join)(cwd, ".stackwright");
5872
+ (0, import_fs13.mkdirSync)(dir, { recursive: true });
5873
+ const regPath = (0, import_path13.join)(cwd, PAGE_REGISTRY_FILE);
5874
+ if ((0, import_fs13.existsSync)(regPath)) {
5875
+ const stat = (0, import_fs13.lstatSync)(regPath);
5411
5876
  if (stat.isSymbolicLink()) {
5412
5877
  throw new Error("Refusing to write page registry through symlink");
5413
5878
  }
5414
5879
  }
5415
- (0, import_fs9.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
5880
+ (0, import_fs13.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
5416
5881
  }
5417
5882
  function checkPageOwnership(cwd, slug, callerOtter) {
5418
5883
  const registry = readPageRegistry(cwd);
@@ -5431,11 +5896,11 @@ function stripNextjsBracketSegments(path5) {
5431
5896
  return path5.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
5432
5897
  }
5433
5898
  function checkPathAllowed(callerOtter, filePath) {
5434
- const normalized = (0, import_path9.normalize)(filePath);
5899
+ const normalized = (0, import_path13.normalize)(filePath);
5435
5900
  if (stripNextjsBracketSegments(normalized).includes("..")) {
5436
5901
  return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
5437
5902
  }
5438
- if ((0, import_path9.isAbsolute)(normalized)) {
5903
+ if ((0, import_path13.isAbsolute)(normalized)) {
5439
5904
  return {
5440
5905
  allowed: false,
5441
5906
  error: "Absolute paths are not allowed \u2014 use paths relative to project root"
@@ -5577,11 +6042,11 @@ function _safeWriteInner(input) {
5577
6042
  };
5578
6043
  return { text: JSON.stringify(result), isError: true };
5579
6044
  }
5580
- const normalized = (0, import_path9.normalize)(filePath);
5581
- const fullPath = (0, import_path9.join)(cwd, normalized);
5582
- if ((0, import_fs9.existsSync)(fullPath)) {
6045
+ const normalized = (0, import_path13.normalize)(filePath);
6046
+ const fullPath = (0, import_path13.join)(cwd, normalized);
6047
+ if ((0, import_fs13.existsSync)(fullPath)) {
5583
6048
  try {
5584
- const stat = (0, import_fs9.lstatSync)(fullPath);
6049
+ const stat = (0, import_fs13.lstatSync)(fullPath);
5585
6050
  if (stat.isSymbolicLink()) {
5586
6051
  const result = {
5587
6052
  success: false,
@@ -5622,9 +6087,9 @@ function _safeWriteInner(input) {
5622
6087
  }
5623
6088
  try {
5624
6089
  if (createDirectories) {
5625
- (0, import_fs9.mkdirSync)((0, import_path9.dirname)(fullPath), { recursive: true });
6090
+ (0, import_fs13.mkdirSync)((0, import_path13.dirname)(fullPath), { recursive: true });
5626
6091
  }
5627
- (0, import_fs9.writeFileSync)(fullPath, content, { encoding: "utf-8" });
6092
+ (0, import_fs13.writeFileSync)(fullPath, content, { encoding: "utf-8" });
5628
6093
  if (pageSlug) {
5629
6094
  try {
5630
6095
  const registry = readPageRegistry(cwd);
@@ -5709,8 +6174,8 @@ function registerSafeWriteTools(server2) {
5709
6174
 
5710
6175
  // src/tools/auth.ts
5711
6176
  var import_zod16 = require("zod");
5712
- var import_fs10 = require("fs");
5713
- var import_path10 = require("path");
6177
+ var import_fs14 = require("fs");
6178
+ var import_path14 = require("path");
5714
6179
  function normalizeProviderSlug(slug) {
5715
6180
  return slug.replace(/-/g, "_");
5716
6181
  }
@@ -5740,9 +6205,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
5740
6205
  }
5741
6206
  function detectNextMajorVersion(cwd) {
5742
6207
  try {
5743
- const pkgPath = (0, import_path10.join)(cwd, "package.json");
5744
- if (!(0, import_fs10.existsSync)(pkgPath)) return null;
5745
- const pkg = JSON.parse((0, import_fs10.readFileSync)(pkgPath, "utf8"));
6208
+ const pkgPath = (0, import_path14.join)(cwd, "package.json");
6209
+ if (!(0, import_fs14.existsSync)(pkgPath)) return null;
6210
+ const pkg = JSON.parse((0, import_fs14.readFileSync)(pkgPath, "utf8"));
5746
6211
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
5747
6212
  if (!nextVersion) return null;
5748
6213
  const match = nextVersion.match(/(\d+)/);
@@ -6404,7 +6869,7 @@ async function configureAuthHandler(params, cwd) {
6404
6869
  auditRetentionDays,
6405
6870
  normalizedRoutes
6406
6871
  );
6407
- (0, import_fs10.writeFileSync)((0, import_path10.join)(cwd, conventionFile), authFileContent, "utf8");
6872
+ (0, import_fs14.writeFileSync)((0, import_path14.join)(cwd, conventionFile), authFileContent, "utf8");
6408
6873
  filesWritten.push(conventionFile);
6409
6874
  } catch (err) {
6410
6875
  const msg = err instanceof Error ? err.message : String(err);
@@ -6424,12 +6889,12 @@ async function configureAuthHandler(params, cwd) {
6424
6889
  if (!devOnly) {
6425
6890
  try {
6426
6891
  const envBlock = generateEnvBlock(effectiveMethod, params);
6427
- const envPath = (0, import_path10.join)(cwd, ".env.example");
6428
- if ((0, import_fs10.existsSync)(envPath)) {
6429
- const existing = (0, import_fs10.readFileSync)(envPath, "utf8");
6430
- (0, import_fs10.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
6892
+ const envPath = (0, import_path14.join)(cwd, ".env.example");
6893
+ if ((0, import_fs14.existsSync)(envPath)) {
6894
+ const existing = (0, import_fs14.readFileSync)(envPath, "utf8");
6895
+ (0, import_fs14.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
6431
6896
  } else {
6432
- (0, import_fs10.writeFileSync)(envPath, envBlock, "utf8");
6897
+ (0, import_fs14.writeFileSync)(envPath, envBlock, "utf8");
6433
6898
  }
6434
6899
  filesWritten.push(".env.example");
6435
6900
  } catch (err) {
@@ -6447,10 +6912,10 @@ async function configureAuthHandler(params, cwd) {
6447
6912
  }
6448
6913
  if (devOnly) {
6449
6914
  try {
6450
- const mockAuthDir = (0, import_path10.join)(cwd, "lib");
6451
- (0, import_fs10.mkdirSync)(mockAuthDir, { recursive: true });
6915
+ const mockAuthDir = (0, import_path14.join)(cwd, "lib");
6916
+ (0, import_fs14.mkdirSync)(mockAuthDir, { recursive: true });
6452
6917
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
6453
- (0, import_fs10.writeFileSync)((0, import_path10.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
6918
+ (0, import_fs14.writeFileSync)((0, import_path14.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
6454
6919
  filesWritten.push("lib/mock-auth.ts");
6455
6920
  } catch (err) {
6456
6921
  const msg = err instanceof Error ? err.message : String(err);
@@ -6468,11 +6933,11 @@ async function configureAuthHandler(params, cwd) {
6468
6933
  };
6469
6934
  }
6470
6935
  try {
6471
- const pkgPath = (0, import_path10.join)(cwd, "package.json");
6472
- if ((0, import_fs10.existsSync)(pkgPath)) {
6473
- const existingPkg = (0, import_fs10.readFileSync)(pkgPath, "utf8");
6936
+ const pkgPath = (0, import_path14.join)(cwd, "package.json");
6937
+ if ((0, import_fs14.existsSync)(pkgPath)) {
6938
+ const existingPkg = (0, import_fs14.readFileSync)(pkgPath, "utf8");
6474
6939
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
6475
- (0, import_fs10.writeFileSync)(pkgPath, updatedPkg, "utf8");
6940
+ (0, import_fs14.writeFileSync)(pkgPath, updatedPkg, "utf8");
6476
6941
  filesWritten.push("package.json");
6477
6942
  }
6478
6943
  } catch (err) {
@@ -6513,7 +6978,7 @@ async function configureAuthHandler(params, cwd) {
6513
6978
  normalizedRoutes,
6514
6979
  useProxy
6515
6980
  );
6516
- (0, import_fs10.writeFileSync)((0, import_path10.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
6981
+ (0, import_fs14.writeFileSync)((0, import_path14.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
6517
6982
  filesWritten.push("stackwright.auth.yml");
6518
6983
  } catch (err) {
6519
6984
  const msg = err instanceof Error ? err.message : String(err);
@@ -6627,8 +7092,8 @@ function registerAuthTools(server2) {
6627
7092
 
6628
7093
  // src/integrity.ts
6629
7094
  var import_crypto4 = require("crypto");
6630
- var import_fs11 = require("fs");
6631
- var import_path11 = require("path");
7095
+ var import_fs15 = require("fs");
7096
+ var import_path15 = require("path");
6632
7097
  var _checksums = /* @__PURE__ */ new Map([
6633
7098
  [
6634
7099
  "stackwright-pro-api-otter.json",
@@ -6644,7 +7109,7 @@ var _checksums = /* @__PURE__ */ new Map([
6644
7109
  ],
6645
7110
  [
6646
7111
  "stackwright-pro-dashboard-otter.json",
6647
- "f55081bf4d6545e5435128919f6f9233956ba37671fc9f85300f240ad2497ab6"
7112
+ "ac0a049da72ee20bfb711f640bac571263bf2b05f80cd22a0c2eb5778c40989c"
6648
7113
  ],
6649
7114
  [
6650
7115
  "stackwright-pro-data-otter.json",
@@ -6664,15 +7129,15 @@ var _checksums = /* @__PURE__ */ new Map([
6664
7129
  ],
6665
7130
  [
6666
7131
  "stackwright-pro-form-wizard-otter.json",
6667
- "cb350297a0068ead2424e703e8c775d91c0b87249ea6904f43b903baf8a84b79"
7132
+ "941bb11b767530460f96fdd08af638b3ede9f7a1ae6f19de04b7d81c95cc62ad"
6668
7133
  ],
6669
7134
  [
6670
7135
  "stackwright-pro-geo-otter.json",
6671
- "3032fbd27de36c0cdc0e19e46a32d771208d8e86714482f5a368d46342c8317a"
7136
+ "bee276605a3ae3203f5b6a18476316f615cf0ea6f4c73ecf57dcd627417de7f3"
6672
7137
  ],
6673
7138
  [
6674
7139
  "stackwright-pro-page-otter.json",
6675
- "cc0db24b00f0130f8b4ec8d385c73ea1bcf5d57ba45aca6b986bcbd5da328930"
7140
+ "8c04dd8a14c6e1aa9b0b6685a71a657547c20d99c25448e3523f73d670f87a4a"
6676
7141
  ],
6677
7142
  [
6678
7143
  "stackwright-pro-polish-otter.json",
@@ -6688,7 +7153,7 @@ var _checksums = /* @__PURE__ */ new Map([
6688
7153
  ],
6689
7154
  [
6690
7155
  "stackwright-pro-theme-otter.json",
6691
- "afb47f8381907145c0e098bdcaae4dd9f5c4ff825a8e88d00a218d2f6858820b"
7156
+ "e3cb6ef9d9f7cbf662a26f637f3d244ec33cb19cb6a4a55dcd148cbfdaa4d71e"
6692
7157
  ],
6693
7158
  [
6694
7159
  "stackwright-services-otter.json",
@@ -6715,14 +7180,14 @@ function safeEqual(a, b) {
6715
7180
  return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
6716
7181
  }
6717
7182
  function verifyOtterFile(filePath) {
6718
- const filename = (0, import_path11.basename)(filePath);
7183
+ const filename = (0, import_path15.basename)(filePath);
6719
7184
  const expected = CANONICAL_CHECKSUMS.get(filename);
6720
7185
  if (expected === void 0) {
6721
7186
  return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
6722
7187
  }
6723
7188
  let stat;
6724
7189
  try {
6725
- stat = (0, import_fs11.lstatSync)(filePath);
7190
+ stat = (0, import_fs15.lstatSync)(filePath);
6726
7191
  } catch (err) {
6727
7192
  const msg = err instanceof Error ? err.message : String(err);
6728
7193
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -6740,7 +7205,7 @@ function verifyOtterFile(filePath) {
6740
7205
  }
6741
7206
  let raw;
6742
7207
  try {
6743
- raw = (0, import_fs11.readFileSync)(filePath);
7208
+ raw = (0, import_fs15.readFileSync)(filePath);
6744
7209
  } catch (err) {
6745
7210
  const msg = err instanceof Error ? err.message : String(err);
6746
7211
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -6790,7 +7255,7 @@ function verifyAllOtters(otterDir) {
6790
7255
  const unknown = [];
6791
7256
  let entries;
6792
7257
  try {
6793
- entries = (0, import_fs11.readdirSync)(otterDir);
7258
+ entries = (0, import_fs15.readdirSync)(otterDir);
6794
7259
  } catch (err) {
6795
7260
  const msg = err instanceof Error ? err.message : String(err);
6796
7261
  return {
@@ -6801,9 +7266,9 @@ function verifyAllOtters(otterDir) {
6801
7266
  }
6802
7267
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
6803
7268
  for (const filename of otterFiles) {
6804
- const filePath = (0, import_path11.join)(otterDir, filename);
7269
+ const filePath = (0, import_path15.join)(otterDir, filename);
6805
7270
  try {
6806
- if ((0, import_fs11.lstatSync)(filePath).isSymbolicLink()) {
7271
+ if ((0, import_fs15.lstatSync)(filePath).isSymbolicLink()) {
6807
7272
  failed.push({ filename, error: "Skipped: symlink" });
6808
7273
  continue;
6809
7274
  }
@@ -6828,10 +7293,10 @@ function verifyAllOtters(otterDir) {
6828
7293
  var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packages/otters/src/"];
6829
7294
  function resolveOtterDir() {
6830
7295
  const cwd = process.cwd();
6831
- for (const relative2 of DEFAULT_SEARCH_PATHS) {
6832
- const candidate = (0, import_path11.join)(cwd, relative2);
7296
+ for (const relative4 of DEFAULT_SEARCH_PATHS) {
7297
+ const candidate = (0, import_path15.join)(cwd, relative4);
6833
7298
  try {
6834
- (0, import_fs11.lstatSync)(candidate);
7299
+ (0, import_fs15.lstatSync)(candidate);
6835
7300
  return candidate;
6836
7301
  } catch {
6837
7302
  }
@@ -6910,13 +7375,13 @@ function registerIntegrityTools(server2) {
6910
7375
 
6911
7376
  // src/tools/domain.ts
6912
7377
  var import_zod17 = require("zod");
6913
- var import_fs12 = require("fs");
6914
- var import_path12 = require("path");
7378
+ var import_fs16 = require("fs");
7379
+ var import_path16 = require("path");
6915
7380
  function handleListCollections(input) {
6916
7381
  const cwd = input._cwd ?? process.cwd();
6917
7382
  const sources = [
6918
7383
  {
6919
- path: (0, import_path12.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
7384
+ path: (0, import_path16.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
6920
7385
  source: "data-config.json",
6921
7386
  parse: (raw) => {
6922
7387
  const parsed = JSON.parse(raw);
@@ -6927,15 +7392,15 @@ function handleListCollections(input) {
6927
7392
  }
6928
7393
  },
6929
7394
  {
6930
- path: (0, import_path12.join)(cwd, "stackwright.yml"),
7395
+ path: (0, import_path16.join)(cwd, "stackwright.yml"),
6931
7396
  source: "stackwright.yml",
6932
7397
  parse: extractCollectionsFromYaml
6933
7398
  }
6934
7399
  ];
6935
7400
  for (const { path: path5, source, parse } of sources) {
6936
- if (!(0, import_fs12.existsSync)(path5)) continue;
7401
+ if (!(0, import_fs16.existsSync)(path5)) continue;
6937
7402
  try {
6938
- const collections = parse((0, import_fs12.readFileSync)(path5, "utf8"));
7403
+ const collections = parse((0, import_fs16.readFileSync)(path5, "utf8"));
6939
7404
  return {
6940
7405
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
6941
7406
  isError: false
@@ -7086,8 +7551,8 @@ function handleValidateWorkflow(input) {
7086
7551
  if (input.workflow && Object.keys(input.workflow).length > 0) {
7087
7552
  raw = input.workflow;
7088
7553
  } else {
7089
- const artifactPath = (0, import_path12.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
7090
- if (!(0, import_fs12.existsSync)(artifactPath)) {
7554
+ const artifactPath = (0, import_path16.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
7555
+ if (!(0, import_fs16.existsSync)(artifactPath)) {
7091
7556
  return fail([
7092
7557
  {
7093
7558
  code: "NO_WORKFLOW",
@@ -7096,7 +7561,7 @@ function handleValidateWorkflow(input) {
7096
7561
  ]);
7097
7562
  }
7098
7563
  try {
7099
- raw = JSON.parse((0, import_fs12.readFileSync)(artifactPath, "utf8"));
7564
+ raw = JSON.parse((0, import_fs16.readFileSync)(artifactPath, "utf8"));
7100
7565
  } catch (err) {
7101
7566
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
7102
7567
  }
@@ -7403,14 +7868,94 @@ function registerTypeSchemasTool(server2) {
7403
7868
  );
7404
7869
  }
7405
7870
 
7871
+ // src/tools/validate-yaml-fragment.ts
7872
+ var import_zod19 = require("zod");
7873
+ var import_js_yaml5 = require("js-yaml");
7874
+ var FIELD_SYNONYMS = Object.fromEntries(
7875
+ Array.from(SCHEMA_REGISTRY.entries()).map(([name, entry]) => [
7876
+ name,
7877
+ buildReverseSynonyms(entry.synonyms ?? {})
7878
+ ])
7879
+ );
7880
+ function enrichErrors(issues, reverseSynonyms) {
7881
+ return issues.map((issue) => {
7882
+ const pathStr = issue.path.join(".");
7883
+ const lastSegment = issue.path[issue.path.length - 1];
7884
+ const fieldName = typeof lastSegment === "string" ? lastSegment : void 0;
7885
+ const didYouMean = fieldName ? reverseSynonyms[fieldName] : void 0;
7886
+ return {
7887
+ path: pathStr || "(root)",
7888
+ message: issue.message,
7889
+ ...didYouMean ? { didYouMean } : {}
7890
+ };
7891
+ });
7892
+ }
7893
+ function handleValidateYamlFragment(input) {
7894
+ const { schemaName, yaml: yaml4 } = input;
7895
+ const entry = SCHEMA_REGISTRY.get(schemaName);
7896
+ if (!entry) {
7897
+ return {
7898
+ valid: false,
7899
+ parseError: `Unknown schemaName: "${schemaName}". Supported: ${REGISTRY_SCHEMA_NAMES.join(", ")}`
7900
+ };
7901
+ }
7902
+ let parsed;
7903
+ try {
7904
+ parsed = (0, import_js_yaml5.load)(yaml4);
7905
+ } catch (err) {
7906
+ return {
7907
+ valid: false,
7908
+ parseError: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`
7909
+ };
7910
+ }
7911
+ const ctx = input.projectRoot ? { projectRoot: input.projectRoot } : void 0;
7912
+ const resolvedSchema = getSchemaForEntry(entry, ctx);
7913
+ const result = resolvedSchema.safeParse(parsed);
7914
+ if (result.success) {
7915
+ const projectRoot = input.projectRoot ?? process.cwd();
7916
+ const ladder = resolveStatusLadder(projectRoot);
7917
+ if (ladder.length > 0) {
7918
+ const ladderSet = new Set(ladder);
7919
+ const tokenViolations = detectViolationsInYaml(parsed, ladder, ladderSet, "fragment");
7920
+ if (tokenViolations.length > 0) {
7921
+ const tokenErrors = tokenViolations.map((v) => ({
7922
+ path: v.path,
7923
+ message: v.message
7924
+ }));
7925
+ return { valid: false, errors: tokenErrors };
7926
+ }
7927
+ }
7928
+ return { valid: true };
7929
+ }
7930
+ const reverseSynonyms = buildReverseSynonyms(entry.synonyms ?? {});
7931
+ const errors = enrichErrors(result.error.issues, reverseSynonyms);
7932
+ return { valid: false, errors };
7933
+ }
7934
+ function registerValidateYamlFragmentTool(server2) {
7935
+ server2.tool(
7936
+ "stackwright_pro_validate_yaml_fragment",
7937
+ 'Validates a YAML snippet against a canonical Zod schema before writing to disk. Returns actionable errors with "did you mean?" suggestions for common field-name drift (title vs label, id vs name, style vs theme.variant, etc.). Call this before stackwright_pro_safe_write to catch schema violations in-session.',
7938
+ {
7939
+ schemaName: import_zod19.z.string().describe(`Schema to validate against. Available: ${REGISTRY_SCHEMA_NAMES.join(", ")}`),
7940
+ yaml: import_zod19.z.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
7941
+ },
7942
+ async ({ schemaName, yaml: yaml4 }) => {
7943
+ const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
7944
+ return {
7945
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
7946
+ };
7947
+ }
7948
+ );
7949
+ }
7950
+
7406
7951
  // src/tools/pages.ts
7407
7952
  var fs2 = __toESM(require("fs"));
7408
7953
  var os = __toESM(require("os"));
7409
7954
  var path3 = __toESM(require("path"));
7410
- var import_zod19 = require("zod");
7411
- var import_js_yaml3 = require("js-yaml");
7955
+ var import_zod20 = require("zod");
7956
+ var import_js_yaml6 = require("js-yaml");
7412
7957
  var import_cli = require("@stackwright/cli");
7413
- var import_types4 = require("@stackwright-pro/types");
7958
+ var import_types3 = require("@stackwright-pro/types");
7414
7959
  var BCP47_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
7415
7960
  var PRO_EXTENSION_STRIP_KEYS = /* @__PURE__ */ new Set(["theme", "auth", "required_roles"]);
7416
7961
  var PRO_TYPE_STUB = {
@@ -7426,10 +7971,10 @@ function sanitizeNode(node) {
7426
7971
  const obj = node;
7427
7972
  const typeVal = obj["type"];
7428
7973
  if (typeof typeVal === "string") {
7429
- if ((0, import_types4.isProContentType)(typeVal)) {
7974
+ if ((0, import_types3.isProContentType)(typeVal)) {
7430
7975
  return { ...PRO_TYPE_STUB };
7431
7976
  }
7432
- if ((0, import_types4.isOssTypeWithProExtension)(typeVal)) {
7977
+ if ((0, import_types3.isOssTypeWithProExtension)(typeVal)) {
7433
7978
  const sanitized = {};
7434
7979
  for (const [k, v] of Object.entries(obj)) {
7435
7980
  if (!PRO_EXTENSION_STRIP_KEYS.has(k)) {
@@ -7462,7 +8007,7 @@ function handleProWritePage(input) {
7462
8007
  }
7463
8008
  let parsed;
7464
8009
  try {
7465
- parsed = (0, import_js_yaml3.load)(content);
8010
+ parsed = (0, import_js_yaml6.load)(content);
7466
8011
  } catch (err) {
7467
8012
  return {
7468
8013
  isError: true,
@@ -7476,7 +8021,7 @@ function handleProWritePage(input) {
7476
8021
  }
7477
8022
  const clone = JSON.parse(JSON.stringify(parsed));
7478
8023
  const sanitized = sanitizeNode(clone);
7479
- const sanitizedYaml = (0, import_js_yaml3.dump)(sanitized);
8024
+ const sanitizedYaml = (0, import_js_yaml6.dump)(sanitized);
7480
8025
  const tmpDir = fs2.mkdtempSync(path3.join(os.tmpdir(), "sw-pro-validate-"));
7481
8026
  try {
7482
8027
  (0, import_cli.writePage)(path3.join(tmpDir, "pages"), "__validation__", sanitizedYaml);
@@ -7519,10 +8064,10 @@ function registerProPageTools(server2) {
7519
8064
  "stackwright_pro_write_page",
7520
8065
  "Write or update a page's YAML content with Pro content-type awareness. Validates against the Stackwright schema (including Pro types like stats_grid, data_table_pulse, map_pulse, alert_banner). Invalid YAML or schema violations on OSS-type fields are rejected with field-level errors. Use this instead of stackwright_write_page for any page containing Pro content types.",
7521
8066
  {
7522
- projectRoot: import_zod19.z.string().describe("Absolute path to the Stackwright project root"),
7523
- slug: import_zod19.z.string().describe('Page slug (e.g. "dashboard/main" or "/dashboard/main")'),
7524
- content: import_zod19.z.string().describe("Full YAML content for the page (content.yml contents)"),
7525
- locale: import_zod19.z.string().optional().describe(
8067
+ projectRoot: import_zod20.z.string().describe("Absolute path to the Stackwright project root"),
8068
+ slug: import_zod20.z.string().describe('Page slug (e.g. "dashboard/main" or "/dashboard/main")'),
8069
+ content: import_zod20.z.string().describe("Full YAML content for the page (content.yml contents)"),
8070
+ locale: import_zod20.z.string().optional().describe(
7526
8071
  'BCP 47 locale tag (e.g. "fr", "de"). If provided, writes content.<locale>.yml instead of content.yml.'
7527
8072
  )
7528
8073
  },
@@ -7533,8 +8078,8 @@ function registerProPageTools(server2) {
7533
8078
  }
7534
8079
 
7535
8080
  // src/tools/scaffold-cleanup.ts
7536
- var import_fs13 = require("fs");
7537
- var import_path13 = require("path");
8081
+ var import_fs17 = require("fs");
8082
+ var import_path17 = require("path");
7538
8083
  var SCAFFOLD_FILES_TO_DELETE = [
7539
8084
  "content/posts/getting-started.yaml",
7540
8085
  "content/posts/hello-world.yaml"
@@ -7559,12 +8104,12 @@ var FALLBACK_COLORS = {
7559
8104
  mutedForeground: "#737373"
7560
8105
  };
7561
8106
  function readThemeColors(cwd) {
7562
- const tokenPath = (0, import_path13.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
7563
- if (!(0, import_fs13.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
7564
- const stat = (0, import_fs13.lstatSync)(tokenPath);
8107
+ const tokenPath = (0, import_path17.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
8108
+ if (!(0, import_fs17.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
8109
+ const stat = (0, import_fs17.lstatSync)(tokenPath);
7565
8110
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
7566
8111
  try {
7567
- const raw = JSON.parse((0, import_fs13.readFileSync)(tokenPath, "utf-8"));
8112
+ const raw = JSON.parse((0, import_fs17.readFileSync)(tokenPath, "utf-8"));
7568
8113
  const css = raw?.cssVariables ?? {};
7569
8114
  const toHsl = (hslValue) => {
7570
8115
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -7624,15 +8169,15 @@ function generateNotFoundPage(colors) {
7624
8169
  `;
7625
8170
  }
7626
8171
  function isSafePath(fullPath) {
7627
- if (!(0, import_fs13.existsSync)(fullPath)) return true;
7628
- const stat = (0, import_fs13.lstatSync)(fullPath);
8172
+ if (!(0, import_fs17.existsSync)(fullPath)) return true;
8173
+ const stat = (0, import_fs17.lstatSync)(fullPath);
7629
8174
  return !stat.isSymbolicLink();
7630
8175
  }
7631
8176
  function isDirEmpty(dirPath) {
7632
- if (!(0, import_fs13.existsSync)(dirPath)) return false;
7633
- const stat = (0, import_fs13.lstatSync)(dirPath);
8177
+ if (!(0, import_fs17.existsSync)(dirPath)) return false;
8178
+ const stat = (0, import_fs17.lstatSync)(dirPath);
7634
8179
  if (!stat.isDirectory()) return false;
7635
- return (0, import_fs13.readdirSync)(dirPath).length === 0;
8180
+ return (0, import_fs17.readdirSync)(dirPath).length === 0;
7636
8181
  }
7637
8182
  function handleCleanupScaffold(_cwd) {
7638
8183
  const cwd = _cwd ?? process.cwd();
@@ -7645,8 +8190,8 @@ function handleCleanupScaffold(_cwd) {
7645
8190
  cleanupWarnings: []
7646
8191
  };
7647
8192
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
7648
- const fullPath = (0, import_path13.join)(cwd, relPath);
7649
- if (!(0, import_fs13.existsSync)(fullPath)) {
8193
+ const fullPath = (0, import_path17.join)(cwd, relPath);
8194
+ if (!(0, import_fs17.existsSync)(fullPath)) {
7650
8195
  result.skipped.push(relPath);
7651
8196
  continue;
7652
8197
  }
@@ -7655,7 +8200,7 @@ function handleCleanupScaffold(_cwd) {
7655
8200
  continue;
7656
8201
  }
7657
8202
  try {
7658
- (0, import_fs13.unlinkSync)(fullPath);
8203
+ (0, import_fs17.unlinkSync)(fullPath);
7659
8204
  result.deleted.push(relPath);
7660
8205
  } catch (err) {
7661
8206
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -7663,19 +8208,19 @@ function handleCleanupScaffold(_cwd) {
7663
8208
  }
7664
8209
  {
7665
8210
  const relPath = "pages/getting-started/content.yml";
7666
- const fullPath = (0, import_path13.join)(cwd, relPath);
7667
- if (!(0, import_fs13.existsSync)(fullPath)) {
8211
+ const fullPath = (0, import_path17.join)(cwd, relPath);
8212
+ if (!(0, import_fs17.existsSync)(fullPath)) {
7668
8213
  result.skipped.push(relPath);
7669
8214
  } else if (!isSafePath(fullPath)) {
7670
8215
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7671
8216
  } else {
7672
- const content = (0, import_fs13.readFileSync)(fullPath, "utf-8");
8217
+ const content = (0, import_fs17.readFileSync)(fullPath, "utf-8");
7673
8218
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
7674
8219
  (marker) => content.includes(marker)
7675
8220
  );
7676
8221
  if (isScaffoldDefault) {
7677
8222
  try {
7678
- (0, import_fs13.unlinkSync)(fullPath);
8223
+ (0, import_fs17.unlinkSync)(fullPath);
7679
8224
  result.deleted.push(relPath);
7680
8225
  } catch (err) {
7681
8226
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -7689,21 +8234,21 @@ function handleCleanupScaffold(_cwd) {
7689
8234
  }
7690
8235
  {
7691
8236
  const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
7692
- const fullPath = (0, import_path13.join)(cwd, relPath);
7693
- const postsDir = (0, import_path13.join)(cwd, "content/posts");
7694
- if (!(0, import_fs13.existsSync)(fullPath)) {
8237
+ const fullPath = (0, import_path17.join)(cwd, relPath);
8238
+ const postsDir = (0, import_path17.join)(cwd, "content/posts");
8239
+ if (!(0, import_fs17.existsSync)(fullPath)) {
7695
8240
  result.skipped.push(relPath);
7696
8241
  } else if (!isSafePath(fullPath)) {
7697
8242
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
7698
- } else if (!(0, import_fs13.existsSync)(postsDir)) {
8243
+ } else if (!(0, import_fs17.existsSync)(postsDir)) {
7699
8244
  result.skipped.push(relPath);
7700
8245
  } else {
7701
- const userPostFiles = (0, import_fs13.readdirSync)(postsDir).filter(
8246
+ const userPostFiles = (0, import_fs17.readdirSync)(postsDir).filter(
7702
8247
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
7703
8248
  );
7704
8249
  if (userPostFiles.length === 0) {
7705
8250
  try {
7706
- (0, import_fs13.unlinkSync)(fullPath);
8251
+ (0, import_fs17.unlinkSync)(fullPath);
7707
8252
  result.deleted.push(relPath);
7708
8253
  } catch (err) {
7709
8254
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -7716,15 +8261,15 @@ function handleCleanupScaffold(_cwd) {
7716
8261
  }
7717
8262
  }
7718
8263
  for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
7719
- const fullDir = (0, import_path13.join)(cwd, relDir);
7720
- if (!(0, import_fs13.existsSync)(fullDir)) continue;
8264
+ const fullDir = (0, import_path17.join)(cwd, relDir);
8265
+ if (!(0, import_fs17.existsSync)(fullDir)) continue;
7721
8266
  if (!isSafePath(fullDir)) {
7722
8267
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
7723
8268
  continue;
7724
8269
  }
7725
8270
  if (isDirEmpty(fullDir)) {
7726
8271
  try {
7727
- (0, import_fs13.rmdirSync)(fullDir);
8272
+ (0, import_fs17.rmdirSync)(fullDir);
7728
8273
  result.prunedDirs.push(relDir);
7729
8274
  } catch (err) {
7730
8275
  result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
@@ -7732,8 +8277,8 @@ function handleCleanupScaffold(_cwd) {
7732
8277
  }
7733
8278
  }
7734
8279
  {
7735
- const fullPath = (0, import_path13.join)(cwd, NOT_FOUND_PATH);
7736
- if (!(0, import_fs13.existsSync)(fullPath)) {
8280
+ const fullPath = (0, import_path17.join)(cwd, NOT_FOUND_PATH);
8281
+ if (!(0, import_fs17.existsSync)(fullPath)) {
7737
8282
  result.skipped.push(NOT_FOUND_PATH);
7738
8283
  } else if (!isSafePath(fullPath)) {
7739
8284
  result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
@@ -7741,9 +8286,9 @@ function handleCleanupScaffold(_cwd) {
7741
8286
  try {
7742
8287
  const colors = readThemeColors(cwd);
7743
8288
  const content = generateNotFoundPage(colors);
7744
- const dir = (0, import_path13.dirname)(fullPath);
7745
- (0, import_fs13.mkdirSync)(dir, { recursive: true });
7746
- (0, import_fs13.writeFileSync)(fullPath, content, "utf-8");
8289
+ const dir = (0, import_path17.dirname)(fullPath);
8290
+ (0, import_fs17.mkdirSync)(dir, { recursive: true });
8291
+ (0, import_fs17.writeFileSync)(fullPath, content, "utf-8");
7747
8292
  result.rewritten.push(NOT_FOUND_PATH);
7748
8293
  } catch (err) {
7749
8294
  result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
@@ -7771,8 +8316,159 @@ function registerScaffoldCleanupTools(server2) {
7771
8316
  );
7772
8317
  }
7773
8318
 
8319
+ // src/tools/write-workflow-container-page.ts
8320
+ var import_zod21 = require("zod");
8321
+ var import_js_yaml7 = require("js-yaml");
8322
+ var import_types4 = require("@stackwright-pro/types");
8323
+ var WriteWorkflowContainerPageInputSchema = import_zod21.z.object({
8324
+ /**
8325
+ * Test-only: override process.cwd() for deterministic temp-dir testing.
8326
+ * Never set in production — omit entirely in normal MCP tool calls.
8327
+ */
8328
+ _cwd: import_zod21.z.string().optional(),
8329
+ /**
8330
+ * Kebab-case workflow ID — must match `workflow.id` in the referenced YAML.
8331
+ * Example: `patient-evacuation-authorization`
8332
+ */
8333
+ workflowId: import_zod21.z.string().regex(/^[a-z0-9-]+$/, {
8334
+ message: "workflowId must be kebab-case (lowercase alphanumeric + hyphens)"
8335
+ }),
8336
+ /**
8337
+ * Relative path to the workflow YAML file from project root.
8338
+ * Example: `workflows/patient-evacuation-authorization.yml`
8339
+ */
8340
+ workflowFile: import_zod21.z.string().min(1).regex(/^workflows\/[a-z0-9-]+\.(yml|yaml)$/, {
8341
+ message: "workflowFile must be: workflows/<id>.yml"
8342
+ }),
8343
+ /**
8344
+ * URL slug for the container page route.
8345
+ * Convention: `workflows/<workflowId>` → page at `/workflows/<workflowId>`.
8346
+ * Example: `workflows/patient-evacuation`
8347
+ *
8348
+ * The tool writes to `pages/<pageSlug>/content.yml`.
8349
+ */
8350
+ pageSlug: import_zod21.z.string().min(1).regex(/^workflows\/[a-z0-9-]+$/, {
8351
+ message: 'pageSlug must start with "workflows/" and be kebab-case. Example: workflows/patient-evacuation'
8352
+ }),
8353
+ /**
8354
+ * Human-readable label for the workflow (used in page title + breadcrumbs).
8355
+ * Example: `Patient Evacuation Authorization`
8356
+ */
8357
+ label: import_zod21.z.string().min(1).max(120),
8358
+ /**
8359
+ * Union of required roles across all steps in the workflow YAML.
8360
+ * Consumed by auth-reconcile to build protectedRoutes.
8361
+ * Must be non-empty — workflow pages are always auth-protected.
8362
+ */
8363
+ requiredRoles: import_zod21.z.array(import_zod21.z.string().min(1)).min(1, {
8364
+ message: "requiredRoles must have at least one role \u2014 workflow pages are always auth-protected"
8365
+ }),
8366
+ /**
8367
+ * Persistence key for workflow state (localStorage slot).
8368
+ * Defaults to `workflow-<workflowId>` when omitted.
8369
+ */
8370
+ persistenceKey: import_zod21.z.string().optional(),
8371
+ /**
8372
+ * Layout mode for the container page. Defaults to `app-shell` per Pro convention.
8373
+ * Only override when you have a specific reason — `app-shell` is correct for all
8374
+ * workflow container pages in data-dense Pro apps.
8375
+ */
8376
+ layoutMode: import_zod21.z.enum(["app-shell", "page"]).default("app-shell")
8377
+ });
8378
+ function handleWriteWorkflowContainerPage(input) {
8379
+ const parsed = WriteWorkflowContainerPageInputSchema.safeParse(input);
8380
+ if (!parsed.success) {
8381
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
8382
+ return {
8383
+ text: JSON.stringify({ error: `Invalid input: ${issues}` }),
8384
+ isError: true
8385
+ };
8386
+ }
8387
+ const { workflowId, workflowFile, pageSlug, label, requiredRoles, persistenceKey, layoutMode } = parsed.data;
8388
+ const resolvedPersistenceKey = persistenceKey ?? `workflow-${workflowId}`;
8389
+ const wizardItem = {
8390
+ type: "workflow_wizard",
8391
+ label: workflowId,
8392
+ // slug-form label for the content item (page title comes from meta)
8393
+ workflowFile,
8394
+ workflowId,
8395
+ persistenceKey: resolvedPersistenceKey
8396
+ };
8397
+ const schemaCheck = import_types4.WorkflowWizardContentSchema.safeParse(wizardItem);
8398
+ if (!schemaCheck.success) {
8399
+ const issues = schemaCheck.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
8400
+ return {
8401
+ text: JSON.stringify({ error: `Internal schema validation failed: ${issues}` }),
8402
+ isError: true
8403
+ };
8404
+ }
8405
+ const pageContent = {
8406
+ layoutMode,
8407
+ meta: {
8408
+ title: `${label} | Workflow`,
8409
+ description: `${label} \u2014 multi-step workflow. Requires authorization.`,
8410
+ authRequired: true,
8411
+ requiredRoles
8412
+ },
8413
+ content: {
8414
+ content_items: [wizardItem]
8415
+ }
8416
+ };
8417
+ const yamlContent = (0, import_js_yaml7.dump)(pageContent, {
8418
+ indent: 2,
8419
+ lineWidth: 120,
8420
+ quotingType: "'",
8421
+ forceQuotes: false
8422
+ });
8423
+ const filePath = `pages/${pageSlug}/content.yml`;
8424
+ const writeResult = handleSafeWrite({
8425
+ callerOtter: "stackwright-pro-form-wizard-otter",
8426
+ filePath,
8427
+ content: yamlContent,
8428
+ createDirectories: true,
8429
+ _cwd: parsed.data._cwd
8430
+ });
8431
+ if (writeResult.isError) {
8432
+ return writeResult;
8433
+ }
8434
+ const result = {
8435
+ written: true,
8436
+ path: filePath,
8437
+ contentTypesEmitted: ["workflow_wizard"],
8438
+ content: yamlContent
8439
+ };
8440
+ return {
8441
+ text: JSON.stringify(result),
8442
+ isError: false
8443
+ };
8444
+ }
8445
+ function registerWriteWorkflowContainerPageTool(server2) {
8446
+ server2.tool(
8447
+ "stackwright_pro_write_workflow_container_page",
8448
+ [
8449
+ "Create the canonical container page for a workflow at pages/workflows/<workflowId>/content.yml.",
8450
+ "",
8451
+ "ALWAYS call this immediately after writing the workflow YAML via stackwright_pro_safe_write.",
8452
+ "Without it, the route /workflows/<workflowId> 404s at runtime.",
8453
+ "",
8454
+ "The tool deterministically emits:",
8455
+ " - layoutMode: app-shell",
8456
+ " - meta.authRequired: true, meta.requiredRoles: <your input>",
8457
+ " - One workflow_wizard content item binding workflowFile + workflowId",
8458
+ "",
8459
+ "requiredRoles: supply the UNION of required roles from all steps in your workflow YAML.",
8460
+ 'pageSlug: convention is "workflows/<workflowId>" \u2014 must start with "workflows/".'
8461
+ ].join("\n"),
8462
+ WriteWorkflowContainerPageInputSchema.shape,
8463
+ async (args) => {
8464
+ const result = handleWriteWorkflowContainerPage(args);
8465
+ return { content: [{ type: "text", text: result.text }] };
8466
+ }
8467
+ );
8468
+ }
8469
+
7774
8470
  // src/tools/contrast.ts
7775
- var import_zod20 = require("zod");
8471
+ var import_zod22 = require("zod");
7776
8472
  function linearizeChannel(c255) {
7777
8473
  const c = c255 / 255;
7778
8474
  return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
@@ -7944,8 +8640,8 @@ function registerContrastTools(server2) {
7944
8640
  "stackwright_pro_check_contrast",
7945
8641
  'Compute the exact WCAG 2.1 contrast ratio between a foreground and background color. Returns the ratio and AA/AAA pass/fail flags. Use this to verify any foreground/background color pair before writing it to the token set. Accepts #RGB, #RRGGBB, hsl(...), or shadcn HSL format ("H S% L%").',
7946
8642
  {
7947
- fg: import_zod20.z.string().describe("Foreground (text) color \u2014 hex or HSL"),
7948
- bg: import_zod20.z.string().describe("Background color \u2014 hex or HSL")
8643
+ fg: import_zod22.z.string().describe("Foreground (text) color \u2014 hex or HSL"),
8644
+ bg: import_zod22.z.string().describe("Background color \u2014 hex or HSL")
7949
8645
  },
7950
8646
  async ({ fg, bg }) => {
7951
8647
  let result;
@@ -7975,8 +8671,8 @@ function registerContrastTools(server2) {
7975
8671
  "stackwright_pro_derive_accessible_palette",
7976
8672
  'Given a seed color (treated as a background), derive the best accessible foreground (text) color that meets the target contrast ratio. Evaluates #ffffff and #000000 against the seed and picks whichever satisfies the target ratio with the highest contrast. Use this for every {name}-foreground token derivation instead of guessing white vs black. Accepts #RGB, #RRGGBB, hsl(...), or shadcn HSL format ("H S% L%").',
7977
8673
  {
7978
- seed: import_zod20.z.string().describe("Background (seed) color \u2014 hex or HSL"),
7979
- targetRatio: numCoerce(import_zod20.z.number().default(4.5)).describe(
8674
+ seed: import_zod22.z.string().describe("Background (seed) color \u2014 hex or HSL"),
8675
+ targetRatio: numCoerce(import_zod22.z.number().default(4.5)).describe(
7980
8676
  "Minimum required contrast ratio (default 4.5 for WCAG AA)"
7981
8677
  )
7982
8678
  },
@@ -8011,24 +8707,24 @@ function registerContrastTools(server2) {
8011
8707
  }
8012
8708
 
8013
8709
  // src/tools/compile.ts
8014
- var import_zod21 = require("zod");
8015
- var import_fs14 = __toESM(require("fs"));
8016
- var import_path14 = __toESM(require("path"));
8710
+ var import_zod23 = require("zod");
8711
+ var import_fs18 = __toESM(require("fs"));
8712
+ var import_path18 = __toESM(require("path"));
8017
8713
  var import_server = require("@stackwright-pro/pulse/server");
8018
8714
  var import_server2 = require("@stackwright-pro/auth-nextjs/server");
8019
8715
  var import_server3 = require("@stackwright-pro/openapi/server");
8020
8716
  function buildContext(projectRoot) {
8021
8717
  return {
8022
8718
  projectRoot,
8023
- contentOutDir: import_path14.default.join(projectRoot, "public", "stackwright-content"),
8024
- imagesDir: import_path14.default.join(projectRoot, "public", "images"),
8719
+ contentOutDir: import_path18.default.join(projectRoot, "public", "stackwright-content"),
8720
+ imagesDir: import_path18.default.join(projectRoot, "public", "images"),
8025
8721
  siteConfig: loadSiteConfig(projectRoot)
8026
8722
  };
8027
8723
  }
8028
8724
  function loadSiteConfig(projectRoot) {
8029
8725
  try {
8030
- const sitePath = import_path14.default.join(projectRoot, "public", "stackwright-content", "_site.json");
8031
- return JSON.parse(import_fs14.default.readFileSync(sitePath, "utf8"));
8726
+ const sitePath = import_path18.default.join(projectRoot, "public", "stackwright-content", "_site.json");
8727
+ return JSON.parse(import_fs18.default.readFileSync(sitePath, "utf8"));
8032
8728
  } catch {
8033
8729
  return {};
8034
8730
  }
@@ -8039,8 +8735,8 @@ function formatDuration(startMs) {
8039
8735
  }
8040
8736
  async function tryOssCompile(fn, ctx, extra) {
8041
8737
  try {
8042
- const bsPath = import_path14.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
8043
- if (!import_fs14.default.existsSync(bsPath)) {
8738
+ const bsPath = import_path18.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
8739
+ if (!import_fs18.default.existsSync(bsPath)) {
8044
8740
  return {
8045
8741
  ok: false,
8046
8742
  error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
@@ -8057,7 +8753,7 @@ async function tryOssCompile(fn, ctx, extra) {
8057
8753
  }
8058
8754
  }
8059
8755
  function registerCompileTools(server2) {
8060
- const projectRootArg = import_zod21.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
8756
+ const projectRootArg = import_zod23.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
8061
8757
  server2.tool(
8062
8758
  "stackwright_pro_compile_collections",
8063
8759
  "Compile stackwright.collections.yml \u2192 public/stackwright-content/_collections.json. Reads and validates the live-collections sidecar config file and emits the JSON sink consumed at runtime by PulseCollectionProvider.",
@@ -8233,7 +8929,7 @@ ${results.join("\n")}`;
8233
8929
  "stackwright_pro_compile_page",
8234
8930
  "Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
8235
8931
  {
8236
- slug: import_zod21.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
8932
+ slug: import_zod23.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
8237
8933
  projectRoot: projectRootArg
8238
8934
  },
8239
8935
  async ({ slug, projectRoot }) => {
@@ -8253,15 +8949,15 @@ ${results.join("\n")}`;
8253
8949
  }
8254
8950
 
8255
8951
  // src/tools/strip-legacy-integrations.ts
8256
- var import_zod22 = require("zod");
8257
- var import_fs15 = require("fs");
8258
- var import_path15 = require("path");
8952
+ var import_zod24 = require("zod");
8953
+ var import_fs19 = require("fs");
8954
+ var import_path19 = require("path");
8259
8955
  function handleStripLegacyIntegrations(input) {
8260
8956
  const { projectRoot } = input;
8261
- const ymlPath = (0, import_path15.join)(projectRoot, "stackwright.yml");
8262
- const yamlPath = (0, import_path15.join)(projectRoot, "stackwright.yaml");
8263
- if (!(0, import_fs15.existsSync)(ymlPath)) {
8264
- if ((0, import_fs15.existsSync)(yamlPath)) {
8957
+ const ymlPath = (0, import_path19.join)(projectRoot, "stackwright.yml");
8958
+ const yamlPath = (0, import_path19.join)(projectRoot, "stackwright.yaml");
8959
+ if (!(0, import_fs19.existsSync)(ymlPath)) {
8960
+ if ((0, import_fs19.existsSync)(yamlPath)) {
8265
8961
  return {
8266
8962
  changed: false,
8267
8963
  removedEntries: 0,
@@ -8274,7 +8970,7 @@ function handleStripLegacyIntegrations(input) {
8274
8970
  message: "stackwright.yml not found \u2014 no-op."
8275
8971
  };
8276
8972
  }
8277
- const raw = (0, import_fs15.readFileSync)(ymlPath, "utf-8");
8973
+ const raw = (0, import_fs19.readFileSync)(ymlPath, "utf-8");
8278
8974
  const isCRLF = raw.includes("\r\n");
8279
8975
  const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
8280
8976
  const lines = content.split("\n");
@@ -8304,7 +9000,7 @@ function handleStripLegacyIntegrations(input) {
8304
9000
  outputContent += "\n";
8305
9001
  }
8306
9002
  const outputRaw = isCRLF ? outputContent.replace(/\n/g, "\r\n") : outputContent;
8307
- (0, import_fs15.writeFileSync)(ymlPath, outputRaw, "utf-8");
9003
+ (0, import_fs19.writeFileSync)(ymlPath, outputRaw, "utf-8");
8308
9004
  return {
8309
9005
  changed: true,
8310
9006
  removedEntries,
@@ -8316,7 +9012,7 @@ function registerStripLegacyIntegrationsTool(server2) {
8316
9012
  "stackwright_pro_strip_legacy_integrations",
8317
9013
  "Removes the deprecated top-level integrations[] block from stackwright.yml. The authoritative source for integration config is stackwright.integrations.yml \u2014 this tool cleans up legacy entries from older raft runs. Idempotent: no-op if no legacy block present. Preserves all other top-level keys, comments, and formatting.",
8318
9014
  {
8319
- projectRoot: import_zod22.z.string().describe("Absolute path to the project root directory")
9015
+ projectRoot: import_zod24.z.string().describe("Absolute path to the project root directory")
8320
9016
  },
8321
9017
  async ({ projectRoot }) => {
8322
9018
  const result = handleStripLegacyIntegrations({ projectRoot });
@@ -8328,16 +9024,16 @@ function registerStripLegacyIntegrationsTool(server2) {
8328
9024
  }
8329
9025
 
8330
9026
  // src/tools/emitters.ts
8331
- var import_zod23 = require("zod");
8332
- var import_fs16 = require("fs");
8333
- var import_path16 = require("path");
8334
- var import_js_yaml4 = __toESM(require("js-yaml"));
9027
+ var import_zod25 = require("zod");
9028
+ var import_fs20 = require("fs");
9029
+ var import_path20 = require("path");
9030
+ var import_js_yaml8 = __toESM(require("js-yaml"));
8335
9031
  var import_emitters = require("@stackwright-pro/emitters");
8336
9032
  function readIntegrations(cwd) {
8337
- const filePath = (0, import_path16.join)(cwd, "stackwright.integrations.yml");
8338
- if (!(0, import_fs16.existsSync)(filePath)) return [];
9033
+ const filePath = (0, import_path20.join)(cwd, "stackwright.integrations.yml");
9034
+ if (!(0, import_fs20.existsSync)(filePath)) return [];
8339
9035
  try {
8340
- const raw = import_js_yaml4.default.load((0, import_fs16.readFileSync)(filePath, "utf-8"));
9036
+ const raw = import_js_yaml8.default.load((0, import_fs20.readFileSync)(filePath, "utf-8"));
8341
9037
  if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
8342
9038
  return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
8343
9039
  name: i.name,
@@ -8348,15 +9044,15 @@ function readIntegrations(cwd) {
8348
9044
  }
8349
9045
  }
8350
9046
  function readServiceTokenRefs(cwd) {
8351
- const servicesDir = (0, import_path16.join)(cwd, "services");
8352
- if (!(0, import_fs16.existsSync)(servicesDir)) return [];
9047
+ const servicesDir = (0, import_path20.join)(cwd, "services");
9048
+ if (!(0, import_fs20.existsSync)(servicesDir)) return [];
8353
9049
  try {
8354
- const files = (0, import_fs16.readdirSync)(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
9050
+ const files = (0, import_fs20.readdirSync)(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
8355
9051
  const refs = [];
8356
9052
  for (const file of files) {
8357
9053
  try {
8358
- const raw = import_js_yaml4.default.load(
8359
- (0, import_fs16.readFileSync)((0, import_path16.join)(servicesDir, file), "utf-8")
9054
+ const raw = import_js_yaml8.default.load(
9055
+ (0, import_fs20.readFileSync)((0, import_path20.join)(servicesDir, file), "utf-8")
8360
9056
  );
8361
9057
  if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
8362
9058
  refs.push({
@@ -8387,7 +9083,7 @@ Call this tool, then write both files with stackwright_pro_safe_write:
8387
9083
 
8388
9084
  Do NOT compose the contents by hand. This emitter is the source of truth. If an env var is missing, update integrations.yml or the emitter \u2014 not the file. This is the first concrete instance of the swp-zf9y deterministic emitter pattern. ${ENV_LOCAL_DESC}`,
8389
9085
  {
8390
- cwd: import_zod23.z.string().describe(
9086
+ cwd: import_zod25.z.string().describe(
8391
9087
  "Absolute path to the project root containing stackwright.integrations.yml and optionally services/*.yml"
8392
9088
  )
8393
9089
  },
@@ -8434,8 +9130,8 @@ mapProvider options:
8434
9130
 
8435
9131
  This is the second concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-57ku, swp-2m89, swp-zf9y, drawer 957. ${PROVIDERS_DESC}`,
8436
9132
  {
8437
- cwd: import_zod23.z.string().describe("Absolute path to the project root containing stackwright.integrations.yml"),
8438
- mapProvider: import_zod23.z.enum(["maplibre", "cesium", "none"]).optional().describe(
9133
+ cwd: import_zod25.z.string().describe("Absolute path to the project root containing stackwright.integrations.yml"),
9134
+ mapProvider: import_zod25.z.enum(["maplibre", "cesium", "none"]).optional().describe(
8439
9135
  "Map provider to register (default: cesium for Pro \u2014 3D globe + terrain). Use maplibre for OSS-style 2D dashboards (explicit opt-out). Use none if pages have no map content_items."
8440
9136
  )
8441
9137
  },
@@ -8476,7 +9172,7 @@ Workflow:
8476
9172
 
8477
9173
  This is the third concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-phkh, swp-zf9y. ${PORTS_DESC}`,
8478
9174
  {
8479
- cwd: import_zod23.z.string().describe("Absolute path to the project root containing stackwright.integrations.yml")
9175
+ cwd: import_zod25.z.string().describe("Absolute path to the project root containing stackwright.integrations.yml")
8480
9176
  },
8481
9177
  async ({ cwd }) => {
8482
9178
  const integrations = readIntegrations(cwd);
@@ -8505,13 +9201,13 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
8505
9201
  }
8506
9202
 
8507
9203
  // src/tools/list-specs.ts
8508
- var import_zod24 = require("zod");
8509
- var import_fs17 = require("fs");
8510
- var import_path17 = require("path");
9204
+ var import_zod26 = require("zod");
9205
+ var import_fs21 = require("fs");
9206
+ var import_path21 = require("path");
8511
9207
  var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
8512
9208
  var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
8513
9209
  function deriveIntegrationName(fileName) {
8514
- let name = (0, import_path17.basename)(fileName, (0, import_path17.extname)(fileName));
9210
+ let name = (0, import_path21.basename)(fileName, (0, import_path21.extname)(fileName));
8515
9211
  for (const suffix of STRIP_SUFFIXES) {
8516
9212
  if (name.endsWith(suffix)) {
8517
9213
  name = name.slice(0, -suffix.length);
@@ -8529,29 +9225,29 @@ function detectFormat(snippet) {
8529
9225
  }
8530
9226
  function handleListSpecs(input) {
8531
9227
  const { projectRoot } = input;
8532
- const specsDir = (0, import_path17.join)(projectRoot, "specs");
9228
+ const specsDir = (0, import_path21.join)(projectRoot, "specs");
8533
9229
  const empty = { projectRoot, specs: [], totalCount: 0 };
8534
- if (!(0, import_fs17.existsSync)(specsDir)) return empty;
9230
+ if (!(0, import_fs21.existsSync)(specsDir)) return empty;
8535
9231
  let entries;
8536
9232
  try {
8537
- entries = (0, import_fs17.readdirSync)(specsDir);
9233
+ entries = (0, import_fs21.readdirSync)(specsDir);
8538
9234
  } catch {
8539
9235
  return empty;
8540
9236
  }
8541
9237
  const specs = [];
8542
9238
  for (const entry of entries) {
8543
- const fullPath = (0, import_path17.join)(specsDir, entry);
9239
+ const fullPath = (0, import_path21.join)(specsDir, entry);
8544
9240
  let stat;
8545
9241
  try {
8546
- stat = (0, import_fs17.statSync)(fullPath);
9242
+ stat = (0, import_fs21.statSync)(fullPath);
8547
9243
  } catch {
8548
9244
  continue;
8549
9245
  }
8550
9246
  if (!stat.isFile()) continue;
8551
- if (!ALLOWED_EXTS.has((0, import_path17.extname)(entry).toLowerCase())) continue;
9247
+ if (!ALLOWED_EXTS.has((0, import_path21.extname)(entry).toLowerCase())) continue;
8552
9248
  let snippet = "";
8553
9249
  try {
8554
- snippet = (0, import_fs17.readFileSync)(fullPath, "utf-8").slice(0, 2048);
9250
+ snippet = (0, import_fs21.readFileSync)(fullPath, "utf-8").slice(0, 2048);
8555
9251
  } catch {
8556
9252
  }
8557
9253
  specs.push({
@@ -8570,7 +9266,7 @@ function registerListSpecsTool(server2) {
8570
9266
  "stackwright_pro_list_specs",
8571
9267
  "Enumerate OpenAPI/AsyncAPI specs in the project's specs/ directory. Returns one entry per spec with integration name (derived from filename), detected format, and size. Used by the foreman to fan out the api phase with one api-otter invocation per spec.",
8572
9268
  {
8573
- projectRoot: import_zod24.z.string().describe("Absolute path to the project root directory")
9269
+ projectRoot: import_zod26.z.string().describe("Absolute path to the project root directory")
8574
9270
  },
8575
9271
  async ({ projectRoot }) => {
8576
9272
  const result = handleListSpecs({ projectRoot });
@@ -8582,10 +9278,10 @@ function registerListSpecsTool(server2) {
8582
9278
  }
8583
9279
 
8584
9280
  // src/tools/describe-endpoint.ts
8585
- var import_zod25 = require("zod");
8586
- var import_fs18 = require("fs");
8587
- var import_path18 = require("path");
8588
- var import_js_yaml5 = __toESM(require("js-yaml"));
9281
+ var import_zod27 = require("zod");
9282
+ var import_fs22 = require("fs");
9283
+ var import_path22 = require("path");
9284
+ var import_js_yaml9 = __toESM(require("js-yaml"));
8589
9285
  function resolveRef(root, ref) {
8590
9286
  if (!ref.startsWith("#/")) {
8591
9287
  throw new Error(`[describe_endpoint] External $refs not supported: "${ref}"`);
@@ -8616,12 +9312,12 @@ function deref(root, obj) {
8616
9312
  return o;
8617
9313
  }
8618
9314
  function loadSpec(specPath) {
8619
- const raw = (0, import_fs18.readFileSync)(specPath, "utf-8");
8620
- const ext = (0, import_path18.extname)(specPath).toLowerCase();
9315
+ const raw = (0, import_fs22.readFileSync)(specPath, "utf-8");
9316
+ const ext = (0, import_path22.extname)(specPath).toLowerCase();
8621
9317
  if (ext === ".json") {
8622
9318
  return JSON.parse(raw);
8623
9319
  }
8624
- const parsed = import_js_yaml5.default.load(raw);
9320
+ const parsed = import_js_yaml9.default.load(raw);
8625
9321
  if (parsed == null || typeof parsed !== "object") {
8626
9322
  throw new Error(`[describe_endpoint] Spec file "${specPath}" did not parse to an object`);
8627
9323
  }
@@ -8729,9 +9425,9 @@ function registerDescribeEndpointTool(server2) {
8729
9425
  "from required query params and resolved auth query params (swp-8r8i + swp-1l6l)."
8730
9426
  ].join("\n"),
8731
9427
  {
8732
- specPath: import_zod25.z.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
8733
- path: import_zod25.z.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
8734
- method: import_zod25.z.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
9428
+ specPath: import_zod27.z.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
9429
+ path: import_zod27.z.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
9430
+ method: import_zod27.z.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
8735
9431
  },
8736
9432
  async ({ specPath, path: path5, method }) => {
8737
9433
  try {
@@ -8751,15 +9447,15 @@ function registerDescribeEndpointTool(server2) {
8751
9447
  }
8752
9448
 
8753
9449
  // src/tools/consolidate-integrations.ts
8754
- var import_zod26 = require("zod");
8755
- var import_fs19 = require("fs");
9450
+ var import_zod28 = require("zod");
9451
+ var import_fs23 = require("fs");
8756
9452
  var import_proper_lockfile2 = require("proper-lockfile");
8757
- var import_path19 = require("path");
8758
- var import_js_yaml6 = __toESM(require("js-yaml"));
9453
+ var import_path23 = require("path");
9454
+ var import_js_yaml10 = __toESM(require("js-yaml"));
8759
9455
  var BASE_MOCK_PORT = 4011;
8760
9456
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
8761
9457
  function deriveNameFromFilename(filename) {
8762
- const base = (0, import_path19.basename)(filename, ".json").replace(/^api-config-/, "");
9458
+ const base = (0, import_path23.basename)(filename, ".json").replace(/^api-config-/, "");
8763
9459
  for (const suffix of STRIP_SUFFIXES2) {
8764
9460
  if (base.endsWith(suffix)) return base.slice(0, -suffix.length);
8765
9461
  }
@@ -8767,13 +9463,13 @@ function deriveNameFromFilename(filename) {
8767
9463
  }
8768
9464
  function handleConsolidateIntegrations(input) {
8769
9465
  const { projectRoot } = input;
8770
- const artifactsDir = (0, import_path19.join)(projectRoot, ".stackwright", "artifacts");
8771
- if (!(0, import_fs19.existsSync)(artifactsDir)) {
9466
+ const artifactsDir = (0, import_path23.join)(projectRoot, ".stackwright", "artifacts");
9467
+ if (!(0, import_fs23.existsSync)(artifactsDir)) {
8772
9468
  return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
8773
9469
  }
8774
9470
  let entries;
8775
9471
  try {
8776
- entries = (0, import_fs19.readdirSync)(artifactsDir).filter(
9472
+ entries = (0, import_fs23.readdirSync)(artifactsDir).filter(
8777
9473
  (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
8778
9474
  );
8779
9475
  } catch {
@@ -8788,7 +9484,7 @@ function handleConsolidateIntegrations(input) {
8788
9484
  entries.forEach((filename, index) => {
8789
9485
  let artifact = {};
8790
9486
  try {
8791
- const raw = (0, import_fs19.readFileSync)((0, import_path19.join)(artifactsDir, filename), "utf-8");
9487
+ const raw = (0, import_fs23.readFileSync)((0, import_path23.join)(artifactsDir, filename), "utf-8");
8792
9488
  artifact = JSON.parse(raw);
8793
9489
  } catch {
8794
9490
  }
@@ -8821,20 +9517,20 @@ function handleConsolidateIntegrations(input) {
8821
9517
  });
8822
9518
  const integrations = Array.from(integrationsByName.values());
8823
9519
  const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
8824
- const integrationsYmlPath = (0, import_path19.join)(projectRoot, "stackwright.integrations.yml");
9520
+ const integrationsYmlPath = (0, import_path23.join)(projectRoot, "stackwright.integrations.yml");
8825
9521
  let existing = {};
8826
- if ((0, import_fs19.existsSync)(integrationsYmlPath)) {
9522
+ if ((0, import_fs23.existsSync)(integrationsYmlPath)) {
8827
9523
  try {
8828
- const raw = (0, import_fs19.readFileSync)(integrationsYmlPath, "utf-8");
8829
- existing = import_js_yaml6.default.load(raw) ?? {};
9524
+ const raw = (0, import_fs23.readFileSync)(integrationsYmlPath, "utf-8");
9525
+ existing = import_js_yaml10.default.load(raw) ?? {};
8830
9526
  } catch {
8831
9527
  existing = {};
8832
9528
  }
8833
9529
  }
8834
9530
  const merged = { ...existing, integrations };
8835
- (0, import_fs19.mkdirSync)((0, import_path19.join)(projectRoot), { recursive: true });
8836
- if (!(0, import_fs19.existsSync)(integrationsYmlPath)) {
8837
- (0, import_fs19.writeFileSync)(integrationsYmlPath, "", "utf-8");
9531
+ (0, import_fs23.mkdirSync)((0, import_path23.join)(projectRoot), { recursive: true });
9532
+ if (!(0, import_fs23.existsSync)(integrationsYmlPath)) {
9533
+ (0, import_fs23.writeFileSync)(integrationsYmlPath, "", "utf-8");
8838
9534
  }
8839
9535
  const release = (0, import_proper_lockfile2.lockSync)(integrationsYmlPath, {
8840
9536
  realpath: false,
@@ -8843,8 +9539,8 @@ function handleConsolidateIntegrations(input) {
8843
9539
  try {
8844
9540
  const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
8845
9541
  `;
8846
- const body = import_js_yaml6.default.dump(merged, { lineWidth: 120, noRefs: true });
8847
- (0, import_fs19.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
9542
+ const body = import_js_yaml10.default.dump(merged, { lineWidth: 120, noRefs: true });
9543
+ (0, import_fs23.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8848
9544
  } finally {
8849
9545
  release();
8850
9546
  }
@@ -8872,7 +9568,7 @@ function registerConsolidateIntegrationsTool(server2) {
8872
9568
  "no per-spec artifacts are found."
8873
9569
  ].join(" "),
8874
9570
  {
8875
- projectRoot: import_zod26.z.string().describe("Absolute path to the project root directory")
9571
+ projectRoot: import_zod28.z.string().describe("Absolute path to the project root directory")
8876
9572
  },
8877
9573
  async ({ projectRoot }) => {
8878
9574
  try {
@@ -8930,7 +9626,7 @@ var package_default = {
8930
9626
  "test:coverage": "vitest run --coverage"
8931
9627
  },
8932
9628
  name: "@stackwright-pro/mcp",
8933
- version: "0.2.0-alpha.113",
9629
+ version: "0.2.0-alpha.115",
8934
9630
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8935
9631
  license: "SEE LICENSE IN LICENSE",
8936
9632
  main: "./dist/server.js",
@@ -8995,6 +9691,7 @@ registerValidateYamlFragmentTool(server);
8995
9691
  registerProPageTools(server);
8996
9692
  registerGetSchemaTool(server);
8997
9693
  registerScaffoldCleanupTools(server);
9694
+ registerWriteWorkflowContainerPageTool(server);
8998
9695
  registerContrastTools(server);
8999
9696
  registerCompileTools(server);
9000
9697
  registerStripLegacyIntegrationsTool(server);