@stackwright-pro/mcp 0.2.0-alpha.92 → 0.2.0-alpha.97

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
@@ -144,8 +144,8 @@ function registerDataExplorerTools(server2) {
144
144
  lines.push(" endpoints:");
145
145
  if (result.filter.include && result.filter.include.length > 0) {
146
146
  lines.push(" include:");
147
- for (const path3 of result.filter.include) {
148
- lines.push(` - ${path3}`);
147
+ for (const path4 of result.filter.include) {
148
+ lines.push(` - ${path4}`);
149
149
  }
150
150
  }
151
151
  if (result.filter.exclude && result.filter.exclude.length > 0) {
@@ -1894,8 +1894,8 @@ function registerOrchestrationTools(server2) {
1894
1894
  {
1895
1895
  buildContext: import_zod9.z.string().describe("Free-text description of what the user wants to build")
1896
1896
  },
1897
- async ({ buildContext }) => {
1898
- const { text, isError } = handleSaveBuildContext({ buildContext });
1897
+ async ({ buildContext: buildContext2 }) => {
1898
+ const { text, isError } = handleSaveBuildContext({ buildContext: buildContext2 });
1899
1899
  return {
1900
1900
  content: [{ type: "text", text }],
1901
1901
  isError
@@ -2023,8 +2023,8 @@ function registerOrchestrationTools(server2) {
2023
2023
 
2024
2024
  // src/tools/pipeline.ts
2025
2025
  var import_zod13 = require("zod");
2026
- var import_fs5 = require("fs");
2027
- var import_path5 = require("path");
2026
+ var import_fs6 = require("fs");
2027
+ var import_path6 = require("path");
2028
2028
  var import_crypto3 = require("crypto");
2029
2029
 
2030
2030
  // src/artifact-signing.ts
@@ -2686,6 +2686,224 @@ function registerGetSchemaTool(server2) {
2686
2686
  );
2687
2687
  }
2688
2688
 
2689
+ // src/tools/pipeline-graph.ts
2690
+ var import_fs5 = require("fs");
2691
+ var import_path5 = require("path");
2692
+ var MANIFEST_NAME_TO_PHASE = {
2693
+ "stackwright-pro-designer-otter": "designer",
2694
+ "stackwright-pro-theme-otter": "theme",
2695
+ "stackwright-pro-scaffold-otter": "scaffold",
2696
+ "stackwright-pro-api-otter": "api",
2697
+ "stackwright-pro-auth-otter": "auth",
2698
+ "stackwright-pro-data-otter": "data",
2699
+ "stackwright-pro-page-otter": "pages",
2700
+ "stackwright-pro-dashboard-otter": "dashboard",
2701
+ "stackwright-pro-form-wizard-otter": "workflow",
2702
+ "stackwright-pro-geo-otter": "geo",
2703
+ "stackwright-pro-polish-otter": "polish",
2704
+ "stackwright-services-otter": "services",
2705
+ "stackwright-pro-qa-otter": "qa"
2706
+ };
2707
+ function manifestNameToPhase(name) {
2708
+ return MANIFEST_NAME_TO_PHASE[name] ?? null;
2709
+ }
2710
+ var OTTER_SEARCH_PATHS = [
2711
+ "node_modules/@stackwright-pro/otters/src/",
2712
+ // production: installed package
2713
+ "packages/otters/src/",
2714
+ // monorepo: run from repo root
2715
+ "../otters/src/"
2716
+ // monorepo: run from packages/mcp/
2717
+ ];
2718
+ function resolveOtterDirFromCwd() {
2719
+ const cwd = process.cwd();
2720
+ for (const relative of OTTER_SEARCH_PATHS) {
2721
+ const candidate = (0, import_path5.join)(cwd, relative);
2722
+ try {
2723
+ (0, import_fs5.lstatSync)(candidate);
2724
+ return candidate;
2725
+ } catch {
2726
+ }
2727
+ }
2728
+ throw new Error(
2729
+ `Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
2730
+ );
2731
+ }
2732
+ function loadOtterManifestsFromDir(otterDir) {
2733
+ const entries = (0, import_fs5.readdirSync)(otterDir);
2734
+ const manifests = [];
2735
+ for (const filename of entries) {
2736
+ if (!filename.endsWith("-otter.json")) continue;
2737
+ const filePath = (0, import_path5.join)(otterDir, filename);
2738
+ if ((0, import_fs5.lstatSync)(filePath).isSymbolicLink()) continue;
2739
+ try {
2740
+ const raw = (0, import_fs5.readFileSync)(filePath, "utf-8");
2741
+ const manifest = JSON.parse(raw);
2742
+ manifests.push(manifest);
2743
+ } catch (err) {
2744
+ const msg = err instanceof Error ? err.message : String(err);
2745
+ throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
2746
+ }
2747
+ }
2748
+ return manifests;
2749
+ }
2750
+ function topologicalSort(dependencies) {
2751
+ const phases = Object.keys(dependencies);
2752
+ const inDegree = {};
2753
+ const adjList = {};
2754
+ for (const phase of phases) {
2755
+ inDegree[phase] ??= 0;
2756
+ adjList[phase] ??= [];
2757
+ }
2758
+ for (const phase of phases) {
2759
+ for (const dep of dependencies[phase] ?? []) {
2760
+ inDegree[phase] = (inDegree[phase] ?? 0) + 1;
2761
+ adjList[dep] ??= [];
2762
+ adjList[dep].push(phase);
2763
+ }
2764
+ }
2765
+ const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
2766
+ const result = [];
2767
+ while (queue.length > 0) {
2768
+ const node = queue.shift();
2769
+ result.push(node);
2770
+ for (const dependent of adjList[node] ?? []) {
2771
+ inDegree[dependent] -= 1;
2772
+ if (inDegree[dependent] === 0) {
2773
+ queue.push(dependent);
2774
+ }
2775
+ }
2776
+ }
2777
+ if (result.length !== phases.length) {
2778
+ const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
2779
+ throw new Error(
2780
+ `Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
2781
+ );
2782
+ }
2783
+ return result;
2784
+ }
2785
+ function buildPipelineGraph(manifests) {
2786
+ const sinkProducers = {};
2787
+ const artifactProducers = {};
2788
+ for (const m of manifests) {
2789
+ const phase = manifestNameToPhase(m.name);
2790
+ if (!phase) continue;
2791
+ const out = m.pipeline?.outputs;
2792
+ if (!out) continue;
2793
+ for (const sink of out.sinks ?? []) {
2794
+ if (sinkProducers[sink]) {
2795
+ throw new Error(
2796
+ `Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
2797
+ );
2798
+ }
2799
+ sinkProducers[sink] = phase;
2800
+ }
2801
+ if (out.artifact) {
2802
+ if (artifactProducers[out.artifact]) {
2803
+ throw new Error(
2804
+ `Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
2805
+ );
2806
+ }
2807
+ artifactProducers[out.artifact] = phase;
2808
+ }
2809
+ }
2810
+ const dependencies = {};
2811
+ for (const m of manifests) {
2812
+ const phase = manifestNameToPhase(m.name);
2813
+ if (!phase) continue;
2814
+ const deps = /* @__PURE__ */ new Set();
2815
+ const inp = m.pipeline?.inputs;
2816
+ if (inp) {
2817
+ for (const sink of inp.sinks ?? []) {
2818
+ const producer = sinkProducers[sink];
2819
+ if (!producer) {
2820
+ throw new Error(
2821
+ `Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
2822
+ );
2823
+ }
2824
+ if (producer !== phase) deps.add(producer);
2825
+ }
2826
+ for (const artifact of inp.artifacts ?? []) {
2827
+ const producer = artifactProducers[artifact];
2828
+ if (!producer) {
2829
+ throw new Error(
2830
+ `Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
2831
+ );
2832
+ }
2833
+ if (producer !== phase) deps.add(producer);
2834
+ }
2835
+ }
2836
+ dependencies[phase] = Array.from(deps);
2837
+ }
2838
+ const order = topologicalSort(dependencies);
2839
+ return { dependencies, order, sinkProducers, artifactProducers };
2840
+ }
2841
+ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
2842
+ function loadDistributedOtterManifests(cwd) {
2843
+ const results = [];
2844
+ for (const namespace of DISTRIBUTED_NAMESPACES) {
2845
+ const nsDir = (0, import_path5.join)(cwd, "node_modules", namespace);
2846
+ let pkgDirNames;
2847
+ try {
2848
+ pkgDirNames = (0, import_fs5.readdirSync)(nsDir);
2849
+ } catch {
2850
+ continue;
2851
+ }
2852
+ for (const pkgDirName of pkgDirNames) {
2853
+ const pkgDir = (0, import_path5.join)(nsDir, pkgDirName);
2854
+ const packageName = `${namespace}/${pkgDirName}`;
2855
+ let otterRelPaths;
2856
+ try {
2857
+ const raw = (0, import_fs5.readFileSync)((0, import_path5.join)(pkgDir, "package.json"), "utf-8");
2858
+ const pkgJson = JSON.parse(raw);
2859
+ const declared = pkgJson.stackwright?.otters;
2860
+ if (!Array.isArray(declared) || declared.length === 0) continue;
2861
+ otterRelPaths = declared;
2862
+ } catch {
2863
+ continue;
2864
+ }
2865
+ for (const relPath of otterRelPaths) {
2866
+ const otterFilePath = (0, import_path5.join)(pkgDir, relPath);
2867
+ try {
2868
+ const raw = (0, import_fs5.readFileSync)(otterFilePath, "utf-8");
2869
+ const manifest = JSON.parse(raw);
2870
+ results.push({ manifest, packageName });
2871
+ } catch (err) {
2872
+ const msg = err instanceof Error ? err.message : String(err);
2873
+ console.warn(
2874
+ `[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
2875
+ );
2876
+ }
2877
+ }
2878
+ }
2879
+ }
2880
+ return results;
2881
+ }
2882
+ function _mergeManifestPools(central, distributed) {
2883
+ const centralNames = new Set(central.map((m) => m.name));
2884
+ const merged = [...central];
2885
+ for (const { manifest, packageName } of distributed) {
2886
+ if (centralNames.has(manifest.name)) {
2887
+ console.warn(
2888
+ `[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
2889
+ );
2890
+ } else {
2891
+ merged.push(manifest);
2892
+ }
2893
+ }
2894
+ return merged;
2895
+ }
2896
+ var _cachedGraph = null;
2897
+ function loadPipelineGraph() {
2898
+ if (_cachedGraph) return _cachedGraph;
2899
+ const otterDir = resolveOtterDirFromCwd();
2900
+ const central = loadOtterManifestsFromDir(otterDir);
2901
+ const distributed = loadDistributedOtterManifests(process.cwd());
2902
+ const manifests = _mergeManifestPools(central, distributed);
2903
+ _cachedGraph = buildPipelineGraph(manifests);
2904
+ return _cachedGraph;
2905
+ }
2906
+
2689
2907
  // src/tools/pipeline.ts
2690
2908
  var PHASE_ORDER = [
2691
2909
  "designer",
@@ -2694,45 +2912,16 @@ var PHASE_ORDER = [
2694
2912
  // generates app/ directory from config
2695
2913
  "api",
2696
2914
  "data",
2915
+ "auth",
2916
+ // moved earlier — only depends on design-language.json (designer)
2697
2917
  "geo",
2698
2918
  "workflow",
2699
2919
  "services",
2700
2920
  "pages",
2701
2921
  "dashboard",
2702
- "auth",
2703
- "polish"
2922
+ "polish",
2923
+ "qa"
2704
2924
  ];
2705
- var PHASE_DEPENDENCIES = {
2706
- designer: [],
2707
- theme: ["designer"],
2708
- scaffold: ["designer", "theme"],
2709
- // needs stackwright.yml + theme-tokens
2710
- api: [],
2711
- data: ["api"],
2712
- geo: ["data"],
2713
- // workflow: no hard deps — uses service: references with Prism mock fallback
2714
- // when API artifacts aren't available; roles come from user answers
2715
- workflow: [],
2716
- // services: needs API endpoints to know what to wire, and optionally
2717
- // workflow states as trigger sources. Produces OpenAPI specs consumed
2718
- // by pages and dashboard for typed data wiring.
2719
- services: ["api", "workflow"],
2720
- // pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
2721
- // specs) and on geo so the specialist prompt includes geo-manifest.json
2722
- // — page/dashboard otters see which page slugs are already claimed and
2723
- // won't attempt to overwrite them (swp-73c). 'api' is still transitive
2724
- // through 'data'; auth removed — page-otter has graceful fallback.
2725
- pages: ["designer", "theme", "data", "services", "geo", "scaffold"],
2726
- dashboard: ["designer", "theme", "data", "services", "geo", "scaffold"],
2727
- // auth is the penultimate phase — runs after all content-producing phases
2728
- // so it can read pages, dashboard, workflow, and geo artifacts for route
2729
- // protection and RBAC wiring. Skipped upstream phases still satisfy deps
2730
- // (the foreman marks them executed=true), so auth always runs.
2731
- auth: ["pages", "dashboard", "workflow", "geo"],
2732
- // polish is the terminal phase — runs after auth so the landing page and
2733
- // nav reflect the final set of protected routes and all generated pages.
2734
- polish: ["auth"]
2735
- };
2736
2925
  var PHASE_ARTIFACT = {
2737
2926
  designer: "design-language.json",
2738
2927
  theme: "theme-tokens.json",
@@ -2745,7 +2934,8 @@ var PHASE_ARTIFACT = {
2745
2934
  workflow: "workflow-config.json",
2746
2935
  services: "services-config.json",
2747
2936
  polish: "polish-manifest.json",
2748
- geo: "geo-manifest.json"
2937
+ geo: "geo-manifest.json",
2938
+ qa: "qa-findings.json"
2749
2939
  };
2750
2940
  var PHASE_TO_OTTER2 = {
2751
2941
  designer: "stackwright-pro-designer-otter",
@@ -2759,7 +2949,8 @@ var PHASE_TO_OTTER2 = {
2759
2949
  workflow: "stackwright-pro-form-wizard-otter",
2760
2950
  services: "stackwright-services-otter",
2761
2951
  polish: "stackwright-pro-polish-otter",
2762
- geo: "stackwright-pro-geo-otter"
2952
+ geo: "stackwright-pro-geo-otter",
2953
+ qa: "stackwright-pro-qa-otter"
2763
2954
  };
2764
2955
  function isValidPhase2(phase) {
2765
2956
  return PHASE_ORDER.includes(phase);
@@ -2789,11 +2980,11 @@ function createDefaultState() {
2789
2980
  };
2790
2981
  }
2791
2982
  function statePath(cwd) {
2792
- return (0, import_path5.join)(cwd, ".stackwright", "pipeline-state.json");
2983
+ return (0, import_path6.join)(cwd, ".stackwright", "pipeline-state.json");
2793
2984
  }
2794
2985
  function readState(cwd) {
2795
2986
  const p = statePath(cwd);
2796
- if (!(0, import_fs5.existsSync)(p)) return createDefaultState();
2987
+ if (!(0, import_fs6.existsSync)(p)) return createDefaultState();
2797
2988
  const raw = JSON.parse(safeReadSync(p));
2798
2989
  if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
2799
2990
  return createDefaultState();
@@ -2801,26 +2992,26 @@ function readState(cwd) {
2801
2992
  return raw;
2802
2993
  }
2803
2994
  function safeWriteSync(filePath, content) {
2804
- if ((0, import_fs5.existsSync)(filePath)) {
2805
- const stat = (0, import_fs5.lstatSync)(filePath);
2995
+ if ((0, import_fs6.existsSync)(filePath)) {
2996
+ const stat = (0, import_fs6.lstatSync)(filePath);
2806
2997
  if (stat.isSymbolicLink()) {
2807
2998
  throw new Error(`Refusing to write to symlink: ${filePath}`);
2808
2999
  }
2809
3000
  }
2810
- (0, import_fs5.writeFileSync)(filePath, content);
3001
+ (0, import_fs6.writeFileSync)(filePath, content);
2811
3002
  }
2812
3003
  function safeReadSync(filePath) {
2813
- if ((0, import_fs5.existsSync)(filePath)) {
2814
- const stat = (0, import_fs5.lstatSync)(filePath);
3004
+ if ((0, import_fs6.existsSync)(filePath)) {
3005
+ const stat = (0, import_fs6.lstatSync)(filePath);
2815
3006
  if (stat.isSymbolicLink()) {
2816
3007
  throw new Error(`Refusing to read symlink: ${filePath}`);
2817
3008
  }
2818
3009
  }
2819
- return (0, import_fs5.readFileSync)(filePath, "utf-8");
3010
+ return (0, import_fs6.readFileSync)(filePath, "utf-8");
2820
3011
  }
2821
3012
  function writeState(cwd, state) {
2822
- const dir = (0, import_path5.join)(cwd, ".stackwright");
2823
- (0, import_fs5.mkdirSync)(dir, { recursive: true });
3013
+ const dir = (0, import_path6.join)(cwd, ".stackwright");
3014
+ (0, import_fs6.mkdirSync)(dir, { recursive: true });
2824
3015
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2825
3016
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
2826
3017
  }
@@ -2841,8 +3032,8 @@ function handleGetPipelineState(_cwd) {
2841
3032
  const cwd = _cwd ?? process.cwd();
2842
3033
  try {
2843
3034
  const state = readState(cwd);
2844
- const keyPath = (0, import_path5.join)(cwd, ".stackwright", "pipeline-keys.json");
2845
- if (!(0, import_fs5.existsSync)(keyPath)) {
3035
+ const keyPath = (0, import_path6.join)(cwd, ".stackwright", "pipeline-keys.json");
3036
+ if (!(0, import_fs6.existsSync)(keyPath)) {
2846
3037
  try {
2847
3038
  const { fingerprint } = initPipelineKeys(cwd);
2848
3039
  state["signingKeyFingerprint"] = fingerprint;
@@ -2945,8 +3136,8 @@ function handleCheckExecutionReady(_cwd, phase) {
2945
3136
  isError: true
2946
3137
  };
2947
3138
  }
2948
- const answerFile = (0, import_path5.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2949
- if (!(0, import_fs5.existsSync)(answerFile)) {
3139
+ const answerFile = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3140
+ if (!(0, import_fs6.existsSync)(answerFile)) {
2950
3141
  return {
2951
3142
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
2952
3143
  isError: false
@@ -2970,12 +3161,12 @@ function handleCheckExecutionReady(_cwd, phase) {
2970
3161
  }
2971
3162
  }
2972
3163
  try {
2973
- const answersDir = (0, import_path5.join)(cwd, ".stackwright", "answers");
3164
+ const answersDir = (0, import_path6.join)(cwd, ".stackwright", "answers");
2974
3165
  const answeredPhases = [];
2975
3166
  const missingPhases = [];
2976
3167
  for (const phase2 of PHASE_ORDER) {
2977
- const answerFile = (0, import_path5.join)(answersDir, `${phase2}.json`);
2978
- if ((0, import_fs5.existsSync)(answerFile)) {
3168
+ const answerFile = (0, import_path6.join)(answersDir, `${phase2}.json`);
3169
+ if ((0, import_fs6.existsSync)(answerFile)) {
2979
3170
  try {
2980
3171
  const raw = safeReadSync(answerFile);
2981
3172
  const parsed = JSON.parse(raw);
@@ -3008,6 +3199,7 @@ function handleGetReadyPhases(_cwd) {
3008
3199
  const cwd = _cwd ?? process.cwd();
3009
3200
  try {
3010
3201
  const state = readState(cwd);
3202
+ const graph = loadPipelineGraph();
3011
3203
  const completed = [];
3012
3204
  const ready = [];
3013
3205
  const blocked = [];
@@ -3017,7 +3209,7 @@ function handleGetReadyPhases(_cwd) {
3017
3209
  completed.push(phase);
3018
3210
  continue;
3019
3211
  }
3020
- const deps = PHASE_DEPENDENCIES[phase];
3212
+ const deps = graph.dependencies[phase] ?? [];
3021
3213
  const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
3022
3214
  if (unmetDeps.length === 0) {
3023
3215
  ready.push(phase);
@@ -3043,7 +3235,7 @@ function handleGetReadyPhases(_cwd) {
3043
3235
  function handleListArtifacts(_cwd) {
3044
3236
  const cwd = _cwd ?? process.cwd();
3045
3237
  try {
3046
- const artifactsDir = (0, import_path5.join)(cwd, ".stackwright", "artifacts");
3238
+ const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
3047
3239
  let manifest = null;
3048
3240
  try {
3049
3241
  manifest = loadSignatureManifest(cwd);
@@ -3053,8 +3245,8 @@ function handleListArtifacts(_cwd) {
3053
3245
  let completedCount = 0;
3054
3246
  for (const phase of PHASE_ORDER) {
3055
3247
  const expectedFile = PHASE_ARTIFACT[phase];
3056
- const fullPath = (0, import_path5.join)(artifactsDir, expectedFile);
3057
- const exists = (0, import_fs5.existsSync)(fullPath);
3248
+ const fullPath = (0, import_path6.join)(artifactsDir, expectedFile);
3249
+ const exists = (0, import_fs6.existsSync)(fullPath);
3058
3250
  if (exists) completedCount++;
3059
3251
  let signed = false;
3060
3252
  let signatureValid = null;
@@ -3107,9 +3299,9 @@ function handleWritePhaseQuestions(input) {
3107
3299
  }
3108
3300
  } catch {
3109
3301
  }
3110
- const questionsDir = (0, import_path5.join)(cwd, ".stackwright", "questions");
3111
- (0, import_fs5.mkdirSync)(questionsDir, { recursive: true });
3112
- const filePath = (0, import_path5.join)(questionsDir, `${phase}.json`);
3302
+ const questionsDir = (0, import_path6.join)(cwd, ".stackwright", "questions");
3303
+ (0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
3304
+ const filePath = (0, import_path6.join)(questionsDir, `${phase}.json`);
3113
3305
  const payload = {
3114
3306
  version: "1.0",
3115
3307
  phase,
@@ -3149,14 +3341,14 @@ function handleBuildSpecialistPrompt(input) {
3149
3341
  };
3150
3342
  }
3151
3343
  try {
3152
- const answersPath = (0, import_path5.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3344
+ const answersPath = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3153
3345
  let answers = {};
3154
- if ((0, import_fs5.existsSync)(answersPath)) {
3346
+ if ((0, import_fs6.existsSync)(answersPath)) {
3155
3347
  answers = JSON.parse(safeReadSync(answersPath));
3156
3348
  }
3157
3349
  let buildContextText = "";
3158
- const buildContextPath = (0, import_path5.join)(cwd, ".stackwright", "build-context.json");
3159
- if ((0, import_fs5.existsSync)(buildContextPath)) {
3350
+ const buildContextPath = (0, import_path6.join)(cwd, ".stackwright", "build-context.json");
3351
+ if ((0, import_fs6.existsSync)(buildContextPath)) {
3160
3352
  try {
3161
3353
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
3162
3354
  if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
@@ -3165,13 +3357,14 @@ function handleBuildSpecialistPrompt(input) {
3165
3357
  } catch {
3166
3358
  }
3167
3359
  }
3168
- const deps = PHASE_DEPENDENCIES[phase];
3360
+ const graph = loadPipelineGraph();
3361
+ const deps = graph.dependencies[phase] ?? [];
3169
3362
  const artifactSections = [];
3170
3363
  const missingDependencies = [];
3171
3364
  for (const dep of deps) {
3172
3365
  const artifactFile = PHASE_ARTIFACT[dep];
3173
- const artifactPath = (0, import_path5.join)(cwd, ".stackwright", "artifacts", artifactFile);
3174
- if ((0, import_fs5.existsSync)(artifactPath)) {
3366
+ const artifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", artifactFile);
3367
+ if ((0, import_fs6.existsSync)(artifactPath)) {
3175
3368
  const rawContent = safeReadSync(artifactPath);
3176
3369
  const rawBytes = Buffer.from(rawContent, "utf-8");
3177
3370
  const content = JSON.parse(rawContent);
@@ -3236,8 +3429,8 @@ ${JSON.stringify(content, null, 2)}`
3236
3429
  parts.push("BUILD_CONTEXT:", buildContextText, "");
3237
3430
  }
3238
3431
  if (phase === "auth") {
3239
- const initContextPath = (0, import_path5.join)(cwd, ".stackwright", "init-context.json");
3240
- if ((0, import_fs5.existsSync)(initContextPath)) {
3432
+ const initContextPath = (0, import_path6.join)(cwd, ".stackwright", "init-context.json");
3433
+ if ((0, import_fs6.existsSync)(initContextPath)) {
3241
3434
  try {
3242
3435
  const initContext = JSON.parse(safeReadSync(initContextPath));
3243
3436
  if (initContext.devOnly === true || initContext.nonInteractive === true) {
@@ -3301,7 +3494,10 @@ var PHASE_REQUIRED_KEYS = {
3301
3494
  workflow: ["version", "generatedBy"],
3302
3495
  services: ["version", "generatedBy", "flows"],
3303
3496
  polish: ["version", "generatedBy"],
3304
- geo: ["version", "generatedBy", "geoCollections"]
3497
+ geo: ["version", "generatedBy", "geoCollections"],
3498
+ // qa: skipped=true path only needs version+generatedBy+skipped;
3499
+ // skipped=false path also needs summary+findings — Zod validator enforces the conditional.
3500
+ qa: ["version", "generatedBy", "skipped"]
3305
3501
  };
3306
3502
  var PHASE_ARTIFACT_SCHEMA = {
3307
3503
  designer: JSON.stringify(
@@ -3591,6 +3787,41 @@ var PHASE_ARTIFACT_SCHEMA = {
3591
3787
  },
3592
3788
  null,
3593
3789
  2
3790
+ ),
3791
+ qa: JSON.stringify(
3792
+ {
3793
+ version: "1.0",
3794
+ generatedBy: "stackwright-pro-qa-otter",
3795
+ // skipped: true path — when dev server is unreachable at audit time
3796
+ // skipped: false path — full audit completed
3797
+ skipped: false,
3798
+ skipReason: null,
3799
+ wcagLevel: "<AA|AAA>",
3800
+ summary: {
3801
+ routesAudited: 3,
3802
+ serious: 0,
3803
+ moderate: 1,
3804
+ minor: 0
3805
+ },
3806
+ findings: [
3807
+ {
3808
+ id: "qa-001",
3809
+ route: "/dashboard",
3810
+ severity: "<serious|moderate|minor>",
3811
+ category: "<visual|a11y|design-contract|runtime>",
3812
+ finding: "Human-readable description of what was observed",
3813
+ evidence: {
3814
+ screenshot: ".stackwright/qa/screenshots/dashboard-light.png",
3815
+ consoleErrors: [],
3816
+ axeViolations: []
3817
+ },
3818
+ suggested_otters: ["stackwright-pro-theme-otter"],
3819
+ suggested_fix: "Specific, concrete next step the repair otter should take"
3820
+ }
3821
+ ]
3822
+ },
3823
+ null,
3824
+ 2
3594
3825
  )
3595
3826
  };
3596
3827
  function handleValidateArtifact(input) {
@@ -3704,13 +3935,13 @@ function handleValidateArtifact(input) {
3704
3935
  // to a flow or state-machine in this services-config artifact.
3705
3936
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
3706
3937
  services: (artifact2) => {
3707
- const workflowArtifactPath = (0, import_path5.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
3708
- if (!(0, import_fs5.existsSync)(workflowArtifactPath)) {
3938
+ const workflowArtifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
3939
+ if (!(0, import_fs6.existsSync)(workflowArtifactPath)) {
3709
3940
  return { success: true };
3710
3941
  }
3711
3942
  let workflowArtifact;
3712
3943
  try {
3713
- workflowArtifact = JSON.parse((0, import_fs5.readFileSync)(workflowArtifactPath, "utf-8"));
3944
+ workflowArtifact = JSON.parse((0, import_fs6.readFileSync)(workflowArtifactPath, "utf-8"));
3714
3945
  } catch {
3715
3946
  return { success: true };
3716
3947
  }
@@ -3731,6 +3962,40 @@ function handleValidateArtifact(input) {
3731
3962
  };
3732
3963
  }
3733
3964
  return { success: true };
3965
+ },
3966
+ // qa artifact — permissive validator: requires version + generatedBy + skipped.
3967
+ // When skipped=true: skipReason must be a string.
3968
+ // When skipped=false: findings must be an array and summary must be an object.
3969
+ qa: (artifact2) => {
3970
+ const skipped = artifact2["skipped"];
3971
+ if (typeof skipped !== "boolean") {
3972
+ return {
3973
+ success: false,
3974
+ error: { message: '"skipped" must be a boolean (true or false).' }
3975
+ };
3976
+ }
3977
+ if (skipped === true) {
3978
+ if (typeof artifact2["skipReason"] !== "string") {
3979
+ return {
3980
+ success: false,
3981
+ error: { message: 'When skipped=true, "skipReason" must be a string.' }
3982
+ };
3983
+ }
3984
+ return { success: true };
3985
+ }
3986
+ if (!Array.isArray(artifact2["findings"])) {
3987
+ return {
3988
+ success: false,
3989
+ error: { message: 'When skipped=false, "findings" must be an array.' }
3990
+ };
3991
+ }
3992
+ if (typeof artifact2["summary"] !== "object" || artifact2["summary"] === null) {
3993
+ return {
3994
+ success: false,
3995
+ error: { message: 'When skipped=false, "summary" must be an object.' }
3996
+ };
3997
+ }
3998
+ return { success: true };
3734
3999
  }
3735
4000
  };
3736
4001
  const zodValidator = PHASE_ZOD_VALIDATORS[phase];
@@ -3747,10 +4012,10 @@ function handleValidateArtifact(input) {
3747
4012
  }
3748
4013
  }
3749
4014
  try {
3750
- const artifactsDir = (0, import_path5.join)(cwd, ".stackwright", "artifacts");
3751
- (0, import_fs5.mkdirSync)(artifactsDir, { recursive: true });
4015
+ const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
4016
+ (0, import_fs6.mkdirSync)(artifactsDir, { recursive: true });
3752
4017
  const artifactFile = PHASE_ARTIFACT[phase];
3753
- const artifactPath = (0, import_path5.join)(artifactsDir, artifactFile);
4018
+ const artifactPath = (0, import_path6.join)(artifactsDir, artifactFile);
3754
4019
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
3755
4020
  const artifactBytes = Buffer.from(serialized, "utf-8");
3756
4021
  safeWriteSync(artifactPath, serialized);
@@ -3867,10 +4132,10 @@ function registerPipelineTools(server2) {
3867
4132
  isError: true
3868
4133
  };
3869
4134
  }
3870
- const questionsDir = (0, import_path5.join)(process.cwd(), ".stackwright", "questions");
3871
- (0, import_fs5.mkdirSync)(questionsDir, { recursive: true });
3872
- const outPath = (0, import_path5.join)(questionsDir, `${phase}.json`);
3873
- (0, import_fs5.writeFileSync)(
4135
+ const questionsDir = (0, import_path6.join)(process.cwd(), ".stackwright", "questions");
4136
+ (0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
4137
+ const outPath = (0, import_path6.join)(questionsDir, `${phase}.json`);
4138
+ (0, import_fs6.writeFileSync)(
3874
4139
  outPath,
3875
4140
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
3876
4141
  );
@@ -3921,8 +4186,8 @@ function registerPipelineTools(server2) {
3921
4186
 
3922
4187
  // src/tools/safe-write.ts
3923
4188
  var import_zod14 = require("zod");
3924
- var import_fs6 = require("fs");
3925
- var import_path6 = require("path");
4189
+ var import_fs7 = require("fs");
4190
+ var import_path7 = require("path");
3926
4191
  var OTTER_WRITE_ALLOWLISTS = {
3927
4192
  "stackwright-pro-designer-otter": [
3928
4193
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
@@ -3937,8 +4202,11 @@ var OTTER_WRITE_ALLOWLISTS = {
3937
4202
  ],
3938
4203
  "stackwright-pro-auth-otter": [
3939
4204
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
3940
- { prefix: "config/", suffix: ".yml", description: "Auth YAML config" },
3941
- { prefix: "config/", suffix: ".yaml", description: "Auth YAML config" },
4205
+ {
4206
+ prefix: "stackwright.auth.",
4207
+ suffix: ".yml",
4208
+ description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
4209
+ },
3942
4210
  {
3943
4211
  prefix: ".env",
3944
4212
  suffix: "",
@@ -3957,7 +4225,11 @@ var OTTER_WRITE_ALLOWLISTS = {
3957
4225
  ],
3958
4226
  "stackwright-pro-data-otter": [
3959
4227
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
3960
- { prefix: "stackwright.yml", suffix: "", description: "Stackwright config" }
4228
+ {
4229
+ prefix: "stackwright.collections.",
4230
+ suffix: ".yml",
4231
+ description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
4232
+ }
3961
4233
  ],
3962
4234
  "stackwright-pro-page-otter": [
3963
4235
  { prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
@@ -3983,7 +4255,12 @@ var OTTER_WRITE_ALLOWLISTS = {
3983
4255
  }
3984
4256
  ],
3985
4257
  "stackwright-pro-api-otter": [
3986
- { prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
4258
+ { prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
4259
+ {
4260
+ prefix: "stackwright.integrations.",
4261
+ suffix: ".yml",
4262
+ description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
4263
+ }
3987
4264
  ],
3988
4265
  "stackwright-pro-geo-otter": [
3989
4266
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
@@ -4006,6 +4283,25 @@ var OTTER_WRITE_ALLOWLISTS = {
4006
4283
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" },
4007
4284
  { prefix: "README.md", suffix: "", description: "Project README rewrite" }
4008
4285
  ],
4286
+ // QA otter writes ONLY to the qa/ directory and the qa-findings artifact.
4287
+ // It never writes .tsx, .ts, .yml, or .json outside these paths.
4288
+ "stackwright-pro-qa-otter": [
4289
+ {
4290
+ prefix: ".stackwright/artifacts/qa-findings.json",
4291
+ suffix: "",
4292
+ description: "QA findings artifact"
4293
+ },
4294
+ {
4295
+ prefix: ".stackwright/qa/",
4296
+ suffix: ".md",
4297
+ description: "QA report markdown"
4298
+ },
4299
+ {
4300
+ prefix: ".stackwright/qa/screenshots/",
4301
+ suffix: ".png",
4302
+ description: "Route screenshots for evidence"
4303
+ }
4304
+ ],
4009
4305
  // domain-expert-otter is a reader/answerer only — it writes via stackwright_pro_save_phase_answers,
4010
4306
  // not via safe_write. Entry required here because the MCP tool availability guard boilerplate
4011
4307
  // in its system prompt mentions stackwright_pro_safe_write (triggering the coherence test).
@@ -4046,7 +4342,7 @@ function getMaxBytesForPath(filePath) {
4046
4342
  }
4047
4343
  var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
4048
4344
  function extractPageSlug(filePath) {
4049
- const match = (0, import_path6.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
4345
+ const match = (0, import_path7.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
4050
4346
  return match?.[1] ?? null;
4051
4347
  }
4052
4348
  function extractContentTypes(yamlContent) {
@@ -4062,27 +4358,27 @@ function extractContentTypes(yamlContent) {
4062
4358
  return types.length > 0 ? types : ["unknown"];
4063
4359
  }
4064
4360
  function readPageRegistry(cwd) {
4065
- const regPath = (0, import_path6.join)(cwd, PAGE_REGISTRY_FILE);
4066
- if (!(0, import_fs6.existsSync)(regPath)) return {};
4361
+ const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
4362
+ if (!(0, import_fs7.existsSync)(regPath)) return {};
4067
4363
  try {
4068
- const stat = (0, import_fs6.lstatSync)(regPath);
4364
+ const stat = (0, import_fs7.lstatSync)(regPath);
4069
4365
  if (stat.isSymbolicLink()) return {};
4070
- return JSON.parse((0, import_fs6.readFileSync)(regPath, "utf-8"));
4366
+ return JSON.parse((0, import_fs7.readFileSync)(regPath, "utf-8"));
4071
4367
  } catch {
4072
4368
  return {};
4073
4369
  }
4074
4370
  }
4075
4371
  function writePageRegistry(cwd, registry) {
4076
- const dir = (0, import_path6.join)(cwd, ".stackwright");
4077
- (0, import_fs6.mkdirSync)(dir, { recursive: true });
4078
- const regPath = (0, import_path6.join)(cwd, PAGE_REGISTRY_FILE);
4079
- if ((0, import_fs6.existsSync)(regPath)) {
4080
- const stat = (0, import_fs6.lstatSync)(regPath);
4372
+ const dir = (0, import_path7.join)(cwd, ".stackwright");
4373
+ (0, import_fs7.mkdirSync)(dir, { recursive: true });
4374
+ const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
4375
+ if ((0, import_fs7.existsSync)(regPath)) {
4376
+ const stat = (0, import_fs7.lstatSync)(regPath);
4081
4377
  if (stat.isSymbolicLink()) {
4082
4378
  throw new Error("Refusing to write page registry through symlink");
4083
4379
  }
4084
4380
  }
4085
- (0, import_fs6.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
4381
+ (0, import_fs7.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
4086
4382
  }
4087
4383
  function checkPageOwnership(cwd, slug, callerOtter) {
4088
4384
  const registry = readPageRegistry(cwd);
@@ -4097,15 +4393,15 @@ function checkPageOwnership(cwd, slug, callerOtter) {
4097
4393
  };
4098
4394
  }
4099
4395
  var NEXTJS_BRACKET_SEGMENT_RE = /\[\[\.{3}[a-zA-Z_][a-zA-Z0-9_]*\]\]|\[\.{3}[a-zA-Z_][a-zA-Z0-9_]*\]|\[[a-zA-Z_][a-zA-Z0-9_]*\]/g;
4100
- function stripNextjsBracketSegments(path3) {
4101
- return path3.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
4396
+ function stripNextjsBracketSegments(path4) {
4397
+ return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
4102
4398
  }
4103
4399
  function checkPathAllowed(callerOtter, filePath) {
4104
- const normalized = (0, import_path6.normalize)(filePath);
4400
+ const normalized = (0, import_path7.normalize)(filePath);
4105
4401
  if (stripNextjsBracketSegments(normalized).includes("..")) {
4106
4402
  return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
4107
4403
  }
4108
- if ((0, import_path6.isAbsolute)(normalized)) {
4404
+ if ((0, import_path7.isAbsolute)(normalized)) {
4109
4405
  return {
4110
4406
  allowed: false,
4111
4407
  error: "Absolute paths are not allowed \u2014 use paths relative to project root"
@@ -4247,11 +4543,11 @@ function handleSafeWrite(input) {
4247
4543
  };
4248
4544
  return { text: JSON.stringify(result), isError: true };
4249
4545
  }
4250
- const normalized = (0, import_path6.normalize)(filePath);
4251
- const fullPath = (0, import_path6.join)(cwd, normalized);
4252
- if ((0, import_fs6.existsSync)(fullPath)) {
4546
+ const normalized = (0, import_path7.normalize)(filePath);
4547
+ const fullPath = (0, import_path7.join)(cwd, normalized);
4548
+ if ((0, import_fs7.existsSync)(fullPath)) {
4253
4549
  try {
4254
- const stat = (0, import_fs6.lstatSync)(fullPath);
4550
+ const stat = (0, import_fs7.lstatSync)(fullPath);
4255
4551
  if (stat.isSymbolicLink()) {
4256
4552
  const result = {
4257
4553
  success: false,
@@ -4292,9 +4588,9 @@ function handleSafeWrite(input) {
4292
4588
  }
4293
4589
  try {
4294
4590
  if (createDirectories) {
4295
- (0, import_fs6.mkdirSync)((0, import_path6.dirname)(fullPath), { recursive: true });
4591
+ (0, import_fs7.mkdirSync)((0, import_path7.dirname)(fullPath), { recursive: true });
4296
4592
  }
4297
- (0, import_fs6.writeFileSync)(fullPath, content, { encoding: "utf-8" });
4593
+ (0, import_fs7.writeFileSync)(fullPath, content, { encoding: "utf-8" });
4298
4594
  if (pageSlug) {
4299
4595
  try {
4300
4596
  const registry = readPageRegistry(cwd);
@@ -4354,8 +4650,8 @@ function registerSafeWriteTools(server2) {
4354
4650
 
4355
4651
  // src/tools/auth.ts
4356
4652
  var import_zod15 = require("zod");
4357
- var import_fs7 = require("fs");
4358
- var import_path7 = require("path");
4653
+ var import_fs8 = require("fs");
4654
+ var import_path8 = require("path");
4359
4655
  function buildHierarchy(roles) {
4360
4656
  const h = {};
4361
4657
  for (let i = 0; i < roles.length - 1; i++) {
@@ -4369,7 +4665,7 @@ function hierarchyToYaml(hierarchy, indent) {
4369
4665
  return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
4370
4666
  }
4371
4667
  function rolesToYaml(roles, indent) {
4372
- return roles.map((r) => `${indent}- ${r}`).join("\n");
4668
+ return roles.map((r) => `${indent}- name: ${r}`).join("\n");
4373
4669
  }
4374
4670
  function normalizeRoutes(routes, defaultRole) {
4375
4671
  return routes.map(
@@ -4382,9 +4678,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
4382
4678
  }
4383
4679
  function detectNextMajorVersion(cwd) {
4384
4680
  try {
4385
- const pkgPath = (0, import_path7.join)(cwd, "package.json");
4386
- if (!(0, import_fs7.existsSync)(pkgPath)) return null;
4387
- const pkg = JSON.parse((0, import_fs7.readFileSync)(pkgPath, "utf8"));
4681
+ const pkgPath = (0, import_path8.join)(cwd, "package.json");
4682
+ if (!(0, import_fs8.existsSync)(pkgPath)) return null;
4683
+ const pkg = JSON.parse((0, import_fs8.readFileSync)(pkgPath, "utf8"));
4388
4684
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
4389
4685
  if (!nextVersion) return null;
4390
4686
  const match = nextVersion.match(/(\d+)/);
@@ -4393,25 +4689,6 @@ function detectNextMajorVersion(cwd) {
4393
4689
  return null;
4394
4690
  }
4395
4691
  }
4396
- function upsertAuthBlock(existing, authYaml) {
4397
- const lines = existing.split("\n");
4398
- let authStart = -1;
4399
- let authEnd = lines.length;
4400
- for (let i = 0; i < lines.length; i++) {
4401
- if (/^auth:/.test(lines[i])) {
4402
- authStart = i;
4403
- } else if (authStart >= 0 && i > authStart && /^\S/.test(lines[i]) && lines[i].trim() !== "") {
4404
- authEnd = i;
4405
- break;
4406
- }
4407
- }
4408
- if (authStart < 0) {
4409
- return existing.trimEnd() + "\n" + authYaml + "\n";
4410
- }
4411
- const before = lines.slice(0, authStart);
4412
- const after = lines.slice(authEnd);
4413
- return [...before, ...authYaml.trimEnd().split("\n"), ...after.length ? after : []].join("\n");
4414
- }
4415
4692
  function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
4416
4693
  const rbacBlock = ` rbac: {
4417
4694
  roles: ${JSON.stringify(roles)},
@@ -4611,33 +4888,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
4611
4888
  `;
4612
4889
  }
4613
4890
  function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
4614
- const rbacSection = ` rbac:
4615
- roles:
4616
- ${rolesToYaml(roles, " ")}
4617
- defaultRole: ${defaultRole}
4618
- hierarchy:
4619
- ${hierarchyToYaml(hierarchy, " ")}`;
4620
- const auditSection = ` audit:
4621
- enabled: ${auditEnabled}
4622
- retentionDays: ${auditRetentionDays}`;
4623
- const routeLines = routesToYaml(protectedRoutes, " ");
4624
- const providerLine = params.provider ? ` provider: ${params.provider}
4891
+ const rbacSection = `rbac:
4892
+ roles:
4893
+ ${rolesToYaml(roles, " ")}
4894
+ defaultRole: ${defaultRole}
4895
+ hierarchy:
4896
+ ${hierarchyToYaml(hierarchy, " ")}`;
4897
+ const auditSection = `audit:
4898
+ enabled: ${auditEnabled}
4899
+ retentionDays: ${auditRetentionDays}`;
4900
+ const routeLines = routesToYaml(protectedRoutes, " ");
4901
+ const providerLine = params.provider ? `provider: ${params.provider}
4625
4902
  ` : "";
4626
4903
  if (method === "cac") {
4627
4904
  const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4628
4905
  const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4629
4906
  const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4630
4907
  const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
4631
- return `auth:
4632
- method: cac
4633
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4634
- cac:
4635
- caBundle: \${CAC_CA_BUNDLE}
4636
- edipiLookup: ${edipiLookup}
4637
- ocspEndpoint: \${CAC_OCSP_ENDPOINT}
4638
- certHeader: ${certHeader}
4908
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4909
+ method: cac
4910
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4911
+ cac:
4912
+ caBundle: \${CAC_CA_BUNDLE}
4913
+ edipiLookup: ${edipiLookup}
4914
+ ocspEndpoint: \${CAC_OCSP_ENDPOINT}
4915
+ certHeader: ${certHeader}
4639
4916
  ${rbacSection}
4640
- protectedRoutes:
4917
+ protectedRoutes:
4641
4918
  ${routeLines}
4642
4919
  ${auditSection}
4643
4920
  `;
@@ -4645,33 +4922,33 @@ ${auditSection}
4645
4922
  if (method === "oidc") {
4646
4923
  const scopes2 = params.oidcScopes ?? "openid profile email";
4647
4924
  const roleClaim = params.oidcRoleClaim ?? "roles";
4648
- return `auth:
4649
- method: oidc
4650
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4651
- oidc:
4652
- discoveryUrl: \${OIDC_DISCOVERY_URL}
4653
- clientId: \${OIDC_CLIENT_ID}
4654
- clientSecret: \${OIDC_CLIENT_SECRET}
4655
- scopes: ${scopes2}
4656
- roleClaim: ${roleClaim}
4925
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4926
+ method: oidc
4927
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4928
+ oidc:
4929
+ discoveryUrl: \${OIDC_DISCOVERY_URL}
4930
+ clientId: \${OIDC_CLIENT_ID}
4931
+ clientSecret: \${OIDC_CLIENT_SECRET}
4932
+ scopes: ${scopes2}
4933
+ roleClaim: ${roleClaim}
4657
4934
  ${rbacSection}
4658
- protectedRoutes:
4935
+ protectedRoutes:
4659
4936
  ${routeLines}
4660
4937
  ${auditSection}
4661
4938
  `;
4662
4939
  }
4663
4940
  const scopes = params.oauth2Scopes ?? "read write";
4664
- return `auth:
4665
- method: oauth2
4666
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4667
- oauth2:
4668
- authorizationUrl: \${OAUTH2_AUTH_URL}
4669
- tokenUrl: \${OAUTH2_TOKEN_URL}
4670
- clientId: \${OAUTH2_CLIENT_ID}
4671
- clientSecret: \${OAUTH2_CLIENT_SECRET}
4672
- scopes: ${scopes}
4941
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4942
+ method: oauth2
4943
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4944
+ oauth2:
4945
+ authorizationUrl: \${OAUTH2_AUTH_URL}
4946
+ tokenUrl: \${OAUTH2_TOKEN_URL}
4947
+ clientId: \${OAUTH2_CLIENT_ID}
4948
+ clientSecret: \${OAUTH2_CLIENT_SECRET}
4949
+ scopes: ${scopes}
4673
4950
  ${rbacSection}
4674
- protectedRoutes:
4951
+ protectedRoutes:
4675
4952
  ${routeLines}
4676
4953
  ${auditSection}
4677
4954
  `;
@@ -4857,35 +5134,35 @@ ${configBlock}
4857
5134
  `;
4858
5135
  }
4859
5136
  function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
4860
- const rbacSection = ` rbac:
4861
- roles:
4862
- ${rolesToYaml(roles, " ")}
4863
- defaultRole: ${defaultRole}
4864
- hierarchy:
4865
- ${hierarchyToYaml(hierarchy, " ")}`;
4866
- const auditSection = ` audit:
4867
- enabled: ${auditEnabled}
4868
- retentionDays: ${auditRetentionDays}`;
4869
- const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
4870
- requiredRole: ${r.requiredRole}`).join("\n");
4871
- const providerLine = params.provider ? ` provider: ${params.provider}
5137
+ const rbacSection = `rbac:
5138
+ roles:
5139
+ ${rolesToYaml(roles, " ")}
5140
+ defaultRole: ${defaultRole}
5141
+ hierarchy:
5142
+ ${hierarchyToYaml(hierarchy, " ")}`;
5143
+ const auditSection = `audit:
5144
+ enabled: ${auditEnabled}
5145
+ retentionDays: ${auditRetentionDays}`;
5146
+ const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
5147
+ requiredRole: ${r.requiredRole}`).join("\n");
5148
+ const providerLine = params.provider ? `provider: ${params.provider}
4872
5149
  ` : "";
4873
5150
  if (method === "cac") {
4874
5151
  const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4875
5152
  const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4876
5153
  const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4877
5154
  const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
4878
- return `auth:
4879
- method: cac
4880
- devOnly: true
4881
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4882
- cac:
4883
- caBundle: ${caBundle}
4884
- edipiLookup: ${edipiLookup}
4885
- ocspEndpoint: ${ocspEndpoint}
4886
- certHeader: ${certHeader}
5155
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5156
+ method: cac
5157
+ devOnly: true
5158
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5159
+ cac:
5160
+ caBundle: ${caBundle}
5161
+ edipiLookup: ${edipiLookup}
5162
+ ocspEndpoint: ${ocspEndpoint}
5163
+ certHeader: ${certHeader}
4887
5164
  ${rbacSection}
4888
- protectedRoutes:
5165
+ protectedRoutes:
4889
5166
  ${routeLines}
4890
5167
  ${auditSection}
4891
5168
  `;
@@ -4893,35 +5170,35 @@ ${auditSection}
4893
5170
  if (method === "oidc") {
4894
5171
  const scopes2 = params.oidcScopes ?? "openid profile email";
4895
5172
  const roleClaim = params.oidcRoleClaim ?? "roles";
4896
- return `auth:
4897
- method: oidc
4898
- devOnly: true
4899
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4900
- oidc:
4901
- discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
4902
- clientId: dev-mock-client
4903
- clientSecret: dev-mock-secret
4904
- scopes: ${scopes2}
4905
- roleClaim: ${roleClaim}
5173
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5174
+ method: oidc
5175
+ devOnly: true
5176
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5177
+ oidc:
5178
+ discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
5179
+ clientId: dev-mock-client
5180
+ clientSecret: dev-mock-secret
5181
+ scopes: ${scopes2}
5182
+ roleClaim: ${roleClaim}
4906
5183
  ${rbacSection}
4907
- protectedRoutes:
5184
+ protectedRoutes:
4908
5185
  ${routeLines}
4909
5186
  ${auditSection}
4910
5187
  `;
4911
5188
  }
4912
5189
  const scopes = params.oauth2Scopes ?? "read write";
4913
- return `auth:
4914
- method: oauth2
4915
- devOnly: true
4916
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4917
- oauth2:
4918
- authorizationUrl: https://dev-mock-oauth2/authorize
4919
- tokenUrl: https://dev-mock-oauth2/token
4920
- clientId: dev-mock-client
4921
- clientSecret: dev-mock-secret
4922
- scopes: ${scopes}
5190
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5191
+ method: oauth2
5192
+ devOnly: true
5193
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5194
+ oauth2:
5195
+ authorizationUrl: https://dev-mock-oauth2/authorize
5196
+ tokenUrl: https://dev-mock-oauth2/token
5197
+ clientId: dev-mock-client
5198
+ clientSecret: dev-mock-secret
5199
+ scopes: ${scopes}
4923
5200
  ${rbacSection}
4924
- protectedRoutes:
5201
+ protectedRoutes:
4925
5202
  ${routeLines}
4926
5203
  ${auditSection}
4927
5204
  `;
@@ -5066,7 +5343,7 @@ async function configureAuthHandler(params, cwd) {
5066
5343
  auditRetentionDays,
5067
5344
  normalizedRoutes
5068
5345
  );
5069
- (0, import_fs7.writeFileSync)((0, import_path7.join)(cwd, conventionFile), authFileContent, "utf8");
5346
+ (0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, conventionFile), authFileContent, "utf8");
5070
5347
  filesWritten.push(conventionFile);
5071
5348
  } catch (err) {
5072
5349
  const msg = err instanceof Error ? err.message : String(err);
@@ -5086,12 +5363,12 @@ async function configureAuthHandler(params, cwd) {
5086
5363
  if (!devOnly) {
5087
5364
  try {
5088
5365
  const envBlock = generateEnvBlock(effectiveMethod, params);
5089
- const envPath = (0, import_path7.join)(cwd, ".env.example");
5090
- if ((0, import_fs7.existsSync)(envPath)) {
5091
- const existing = (0, import_fs7.readFileSync)(envPath, "utf8");
5092
- (0, import_fs7.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5366
+ const envPath = (0, import_path8.join)(cwd, ".env.example");
5367
+ if ((0, import_fs8.existsSync)(envPath)) {
5368
+ const existing = (0, import_fs8.readFileSync)(envPath, "utf8");
5369
+ (0, import_fs8.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5093
5370
  } else {
5094
- (0, import_fs7.writeFileSync)(envPath, envBlock, "utf8");
5371
+ (0, import_fs8.writeFileSync)(envPath, envBlock, "utf8");
5095
5372
  }
5096
5373
  filesWritten.push(".env.example");
5097
5374
  } catch (err) {
@@ -5109,10 +5386,10 @@ async function configureAuthHandler(params, cwd) {
5109
5386
  }
5110
5387
  if (devOnly) {
5111
5388
  try {
5112
- const mockAuthDir = (0, import_path7.join)(cwd, "lib");
5113
- (0, import_fs7.mkdirSync)(mockAuthDir, { recursive: true });
5389
+ const mockAuthDir = (0, import_path8.join)(cwd, "lib");
5390
+ (0, import_fs8.mkdirSync)(mockAuthDir, { recursive: true });
5114
5391
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
5115
- (0, import_fs7.writeFileSync)((0, import_path7.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5392
+ (0, import_fs8.writeFileSync)((0, import_path8.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5116
5393
  filesWritten.push("lib/mock-auth.ts");
5117
5394
  } catch (err) {
5118
5395
  const msg = err instanceof Error ? err.message : String(err);
@@ -5130,11 +5407,11 @@ async function configureAuthHandler(params, cwd) {
5130
5407
  };
5131
5408
  }
5132
5409
  try {
5133
- const pkgPath = (0, import_path7.join)(cwd, "package.json");
5134
- if ((0, import_fs7.existsSync)(pkgPath)) {
5135
- const existingPkg = (0, import_fs7.readFileSync)(pkgPath, "utf8");
5410
+ const pkgPath = (0, import_path8.join)(cwd, "package.json");
5411
+ if ((0, import_fs8.existsSync)(pkgPath)) {
5412
+ const existingPkg = (0, import_fs8.readFileSync)(pkgPath, "utf8");
5136
5413
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
5137
- (0, import_fs7.writeFileSync)(pkgPath, updatedPkg, "utf8");
5414
+ (0, import_fs8.writeFileSync)(pkgPath, updatedPkg, "utf8");
5138
5415
  filesWritten.push("package.json");
5139
5416
  }
5140
5417
  } catch (err) {
@@ -5175,21 +5452,18 @@ async function configureAuthHandler(params, cwd) {
5175
5452
  normalizedRoutes,
5176
5453
  useProxy
5177
5454
  );
5178
- const ymlPath = (0, import_path7.join)(cwd, "stackwright.yml");
5179
- if (!(0, import_fs7.existsSync)(ymlPath)) {
5180
- (0, import_fs7.writeFileSync)(ymlPath, authYaml, "utf8");
5181
- } else {
5182
- const existing = (0, import_fs7.readFileSync)(ymlPath, "utf8");
5183
- (0, import_fs7.writeFileSync)(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
5184
- }
5185
- filesWritten.push("stackwright.yml");
5455
+ (0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
5456
+ filesWritten.push("stackwright.auth.yml");
5186
5457
  } catch (err) {
5187
5458
  const msg = err instanceof Error ? err.message : String(err);
5188
5459
  return {
5189
5460
  content: [
5190
5461
  {
5191
5462
  type: "text",
5192
- text: JSON.stringify({ success: false, error: `Failed writing stackwright.yml: ${msg}` })
5463
+ text: JSON.stringify({
5464
+ success: false,
5465
+ error: `Failed writing stackwright.auth.yml: ${msg}`
5466
+ })
5193
5467
  }
5194
5468
  ],
5195
5469
  isError: true
@@ -5234,7 +5508,7 @@ async function configureAuthHandler(params, cwd) {
5234
5508
  function registerAuthTools(server2) {
5235
5509
  server2.tool(
5236
5510
  "stackwright_pro_configure_auth",
5237
- "Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template, appends/updates the `auth:` section in `stackwright.yml`, and generates `.env.example` with required environment variables. \u26A0\uFE0F For CAC/PKI: generated `middleware.ts` carries a SECURITY REVIEW REQUIRED comment \u2014 certificate chain validation must be verified by a DoD security officer before production deployment. This is the ONLY approved path to generating `middleware.ts`. Never write TypeScript auth files directly.",
5511
+ "Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` (or `proxy.ts` for Next.js >=16) from a secure template, writes a standalone `stackwright.auth.yml` sibling file, and generates `.env.example` with required environment variables. \u26A0\uFE0F For CAC/PKI: generated `middleware.ts` carries a SECURITY REVIEW REQUIRED comment \u2014 certificate chain validation must be verified by a DoD security officer before production deployment. This is the ONLY approved path to generating `middleware.ts`. Never write TypeScript auth files directly.",
5238
5512
  {
5239
5513
  method: import_zod15.z.enum(["cac", "oidc", "oauth2", "none"]),
5240
5514
  provider: import_zod15.z.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
@@ -5285,28 +5559,28 @@ function registerAuthTools(server2) {
5285
5559
 
5286
5560
  // src/integrity.ts
5287
5561
  var import_crypto4 = require("crypto");
5288
- var import_fs8 = require("fs");
5289
- var import_path8 = require("path");
5562
+ var import_fs9 = require("fs");
5563
+ var import_path9 = require("path");
5290
5564
  var _checksums = /* @__PURE__ */ new Map([
5291
5565
  [
5292
5566
  "stackwright-pro-api-otter.json",
5293
- "df79f4389a576c2885efa07b04f613c60eb8ebf4a8b1d4c7da5e4bb6ee4248dd"
5567
+ "822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
5294
5568
  ],
5295
5569
  [
5296
5570
  "stackwright-pro-auth-otter.json",
5297
- "643344b88e42992e0467845fb0184d3932e115b85130fbc6a47a3175759678c8"
5571
+ "d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
5298
5572
  ],
5299
5573
  [
5300
5574
  "stackwright-pro-dashboard-otter.json",
5301
- "160af221e04200f53709f8c3e249ca6dafb38a325b232fabd478b28f5492ab01"
5575
+ "19268b1ab423bfbb628ebfbc9f6767d5c0bfedd03963c6d445a2724a8bb78f9c"
5302
5576
  ],
5303
5577
  [
5304
5578
  "stackwright-pro-data-otter.json",
5305
- "709c8e49328908549fe87de181a5991e09c918ef24bbfe6948f9888f0c758c25"
5579
+ "bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
5306
5580
  ],
5307
5581
  [
5308
5582
  "stackwright-pro-designer-otter.json",
5309
- "1364b2c235c07b0b798e9aab90a68604f60019a5508d41ba576977f173e20cd9"
5583
+ "b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
5310
5584
  ],
5311
5585
  [
5312
5586
  "stackwright-pro-domain-expert-otter.json",
@@ -5314,35 +5588,39 @@ var _checksums = /* @__PURE__ */ new Map([
5314
5588
  ],
5315
5589
  [
5316
5590
  "stackwright-pro-foreman-otter.json",
5317
- "438b03d2c35f64536055c68b3a9044fe3b5e4f2889acde4500dd801fad724be1"
5591
+ "abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
5318
5592
  ],
5319
5593
  [
5320
5594
  "stackwright-pro-form-wizard-otter.json",
5321
- "3dffa3ef2d2ef057578391f784cbea779d768e87d98321894ea5bcd9e3ab7e60"
5595
+ "975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
5322
5596
  ],
5323
5597
  [
5324
5598
  "stackwright-pro-geo-otter.json",
5325
- "2ec83c26a08c413d9553ff8b8f0f0f643c1a8da043741b356e27106cad78f05f"
5599
+ "eef152e5b58947c8eb1ffec2c37acffc39f84b61be4c6cb827310f052221935b"
5326
5600
  ],
5327
5601
  [
5328
5602
  "stackwright-pro-page-otter.json",
5329
- "0057ea97f7c2e33a8673140f59a0237eb627d82c676af3fae4faa31033598e03"
5603
+ "ddfa497a4d4980080fa2918aec4d6dfd78d5c28ec6426b4b52f4f26fccaddabb"
5330
5604
  ],
5331
5605
  [
5332
5606
  "stackwright-pro-polish-otter.json",
5333
- "bd87327b9a9a62fabaee8837492ce943feae8bfc15e5d43b45ba0e84619daec3"
5607
+ "5e6532c1fe0737ed83b1f46dd55642e1076adb6ef23d4de636523eb3d88d3087"
5608
+ ],
5609
+ [
5610
+ "stackwright-pro-qa-otter.json",
5611
+ "ecb1e76170723fce43f515044e304d9da32253dadecbf29bf08c90bb8f375f77"
5334
5612
  ],
5335
5613
  [
5336
5614
  "stackwright-pro-scaffold-otter.json",
5337
- "91de5861f1406043d1d387388302fb1492211b81e2eea9777dec60e48f317f2a"
5615
+ "96ac7754b54c15732206cd4d8043b558d0c465757a0bc70fae6b498d6f02f22a"
5338
5616
  ],
5339
5617
  [
5340
5618
  "stackwright-pro-theme-otter.json",
5341
- "fe10108e3ba67106751ea662f512bb5f4eb58f7abda26880ef4cf6b0f9feb944"
5619
+ "fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
5342
5620
  ],
5343
5621
  [
5344
5622
  "stackwright-services-otter.json",
5345
- "c013d7fc2475e62d0af54d57bc988182feee7766bac0edf841cbfad5c73e9261"
5623
+ "0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
5346
5624
  ]
5347
5625
  ]);
5348
5626
  Object.freeze(_checksums);
@@ -5355,6 +5633,7 @@ for (const [name, digest] of CANONICAL_CHECKSUMS) {
5355
5633
  );
5356
5634
  }
5357
5635
  }
5636
+ var CANONICAL_OTTERS = Object.freeze([...CANONICAL_CHECKSUMS.keys()]);
5358
5637
  var MAX_OTTER_BYTES = 1 * 1024 * 1024;
5359
5638
  function computeSha256(data) {
5360
5639
  return (0, import_crypto4.createHash)("sha256").update(data).digest("hex");
@@ -5364,14 +5643,14 @@ function safeEqual(a, b) {
5364
5643
  return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
5365
5644
  }
5366
5645
  function verifyOtterFile(filePath) {
5367
- const filename = (0, import_path8.basename)(filePath);
5646
+ const filename = (0, import_path9.basename)(filePath);
5368
5647
  const expected = CANONICAL_CHECKSUMS.get(filename);
5369
5648
  if (expected === void 0) {
5370
5649
  return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
5371
5650
  }
5372
5651
  let stat;
5373
5652
  try {
5374
- stat = (0, import_fs8.lstatSync)(filePath);
5653
+ stat = (0, import_fs9.lstatSync)(filePath);
5375
5654
  } catch (err) {
5376
5655
  const msg = err instanceof Error ? err.message : String(err);
5377
5656
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -5389,7 +5668,7 @@ function verifyOtterFile(filePath) {
5389
5668
  }
5390
5669
  let raw;
5391
5670
  try {
5392
- raw = (0, import_fs8.readFileSync)(filePath);
5671
+ raw = (0, import_fs9.readFileSync)(filePath);
5393
5672
  } catch (err) {
5394
5673
  const msg = err instanceof Error ? err.message : String(err);
5395
5674
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -5439,7 +5718,7 @@ function verifyAllOtters(otterDir) {
5439
5718
  const unknown = [];
5440
5719
  let entries;
5441
5720
  try {
5442
- entries = (0, import_fs8.readdirSync)(otterDir);
5721
+ entries = (0, import_fs9.readdirSync)(otterDir);
5443
5722
  } catch (err) {
5444
5723
  const msg = err instanceof Error ? err.message : String(err);
5445
5724
  return {
@@ -5450,9 +5729,9 @@ function verifyAllOtters(otterDir) {
5450
5729
  }
5451
5730
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
5452
5731
  for (const filename of otterFiles) {
5453
- const filePath = (0, import_path8.join)(otterDir, filename);
5732
+ const filePath = (0, import_path9.join)(otterDir, filename);
5454
5733
  try {
5455
- if ((0, import_fs8.lstatSync)(filePath).isSymbolicLink()) {
5734
+ if ((0, import_fs9.lstatSync)(filePath).isSymbolicLink()) {
5456
5735
  failed.push({ filename, error: "Skipped: symlink" });
5457
5736
  continue;
5458
5737
  }
@@ -5478,9 +5757,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
5478
5757
  function resolveOtterDir() {
5479
5758
  const cwd = process.cwd();
5480
5759
  for (const relative of DEFAULT_SEARCH_PATHS) {
5481
- const candidate = (0, import_path8.join)(cwd, relative);
5760
+ const candidate = (0, import_path9.join)(cwd, relative);
5482
5761
  try {
5483
- (0, import_fs8.lstatSync)(candidate);
5762
+ (0, import_fs9.lstatSync)(candidate);
5484
5763
  return candidate;
5485
5764
  } catch {
5486
5765
  }
@@ -5559,13 +5838,13 @@ function registerIntegrityTools(server2) {
5559
5838
 
5560
5839
  // src/tools/domain.ts
5561
5840
  var import_zod16 = require("zod");
5562
- var import_fs9 = require("fs");
5563
- var import_path9 = require("path");
5841
+ var import_fs10 = require("fs");
5842
+ var import_path10 = require("path");
5564
5843
  function handleListCollections(input) {
5565
5844
  const cwd = input._cwd ?? process.cwd();
5566
5845
  const sources = [
5567
5846
  {
5568
- path: (0, import_path9.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
5847
+ path: (0, import_path10.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
5569
5848
  source: "data-config.json",
5570
5849
  parse: (raw) => {
5571
5850
  const parsed = JSON.parse(raw);
@@ -5576,15 +5855,15 @@ function handleListCollections(input) {
5576
5855
  }
5577
5856
  },
5578
5857
  {
5579
- path: (0, import_path9.join)(cwd, "stackwright.yml"),
5858
+ path: (0, import_path10.join)(cwd, "stackwright.yml"),
5580
5859
  source: "stackwright.yml",
5581
5860
  parse: extractCollectionsFromYaml
5582
5861
  }
5583
5862
  ];
5584
- for (const { path: path3, source, parse } of sources) {
5585
- if (!(0, import_fs9.existsSync)(path3)) continue;
5863
+ for (const { path: path4, source, parse } of sources) {
5864
+ if (!(0, import_fs10.existsSync)(path4)) continue;
5586
5865
  try {
5587
- const collections = parse((0, import_fs9.readFileSync)(path3, "utf8"));
5866
+ const collections = parse((0, import_fs10.readFileSync)(path4, "utf8"));
5588
5867
  return {
5589
5868
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
5590
5869
  isError: false
@@ -5735,8 +6014,8 @@ function handleValidateWorkflow(input) {
5735
6014
  if (input.workflow && Object.keys(input.workflow).length > 0) {
5736
6015
  raw = input.workflow;
5737
6016
  } else {
5738
- const artifactPath = (0, import_path9.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
5739
- if (!(0, import_fs9.existsSync)(artifactPath)) {
6017
+ const artifactPath = (0, import_path10.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
6018
+ if (!(0, import_fs10.existsSync)(artifactPath)) {
5740
6019
  return fail([
5741
6020
  {
5742
6021
  code: "NO_WORKFLOW",
@@ -5745,7 +6024,7 @@ function handleValidateWorkflow(input) {
5745
6024
  ]);
5746
6025
  }
5747
6026
  try {
5748
- raw = JSON.parse((0, import_fs9.readFileSync)(artifactPath, "utf8"));
6027
+ raw = JSON.parse((0, import_fs10.readFileSync)(artifactPath, "utf8"));
5749
6028
  } catch (err) {
5750
6029
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
5751
6030
  }
@@ -5857,12 +6136,12 @@ function handleValidateWorkflow(input) {
5857
6136
  return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
5858
6137
  }
5859
6138
  function validateTransitionTargets(step, stepId, stepIds, errors) {
5860
- const check = (target, path3) => {
6139
+ const check = (target, path4) => {
5861
6140
  if (typeof target === "string" && !stepIds.has(target)) {
5862
6141
  errors.push({
5863
6142
  code: "ORPHANED_TRANSITION",
5864
6143
  message: `Step "${stepId}" transitions to "${target}" which does not exist`,
5865
- path: path3
6144
+ path: path4
5866
6145
  });
5867
6146
  }
5868
6147
  };
@@ -5901,11 +6180,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
5901
6180
  }
5902
6181
  function collectServiceWarnings(step, stepId, warnings) {
5903
6182
  const isService = (val) => typeof val === "string" && val.startsWith("service:");
5904
- const warn = (path3) => {
6183
+ const warn = (path4) => {
5905
6184
  warnings.push({
5906
6185
  code: "WARN_SERVICE_REFERENCE",
5907
- message: `service: reference at "${path3}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
5908
- path: path3
6186
+ message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
6187
+ path: path4
5909
6188
  });
5910
6189
  };
5911
6190
  const onSubmit = step.on_submit;
@@ -6053,8 +6332,8 @@ function registerTypeSchemasTool(server2) {
6053
6332
  }
6054
6333
 
6055
6334
  // src/tools/scaffold-cleanup.ts
6056
- var import_fs10 = require("fs");
6057
- var import_path10 = require("path");
6335
+ var import_fs11 = require("fs");
6336
+ var import_path11 = require("path");
6058
6337
  var SCAFFOLD_FILES_TO_DELETE = [
6059
6338
  "content/posts/getting-started.yaml",
6060
6339
  "content/posts/hello-world.yaml"
@@ -6079,12 +6358,12 @@ var FALLBACK_COLORS = {
6079
6358
  mutedForeground: "#737373"
6080
6359
  };
6081
6360
  function readThemeColors(cwd) {
6082
- const tokenPath = (0, import_path10.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6083
- if (!(0, import_fs10.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
6084
- const stat = (0, import_fs10.lstatSync)(tokenPath);
6361
+ const tokenPath = (0, import_path11.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6362
+ if (!(0, import_fs11.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
6363
+ const stat = (0, import_fs11.lstatSync)(tokenPath);
6085
6364
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
6086
6365
  try {
6087
- const raw = JSON.parse((0, import_fs10.readFileSync)(tokenPath, "utf-8"));
6366
+ const raw = JSON.parse((0, import_fs11.readFileSync)(tokenPath, "utf-8"));
6088
6367
  const css = raw?.cssVariables ?? {};
6089
6368
  const toHsl = (hslValue) => {
6090
6369
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -6144,15 +6423,15 @@ function generateNotFoundPage(colors) {
6144
6423
  `;
6145
6424
  }
6146
6425
  function isSafePath(fullPath) {
6147
- if (!(0, import_fs10.existsSync)(fullPath)) return true;
6148
- const stat = (0, import_fs10.lstatSync)(fullPath);
6426
+ if (!(0, import_fs11.existsSync)(fullPath)) return true;
6427
+ const stat = (0, import_fs11.lstatSync)(fullPath);
6149
6428
  return !stat.isSymbolicLink();
6150
6429
  }
6151
6430
  function isDirEmpty(dirPath) {
6152
- if (!(0, import_fs10.existsSync)(dirPath)) return false;
6153
- const stat = (0, import_fs10.lstatSync)(dirPath);
6431
+ if (!(0, import_fs11.existsSync)(dirPath)) return false;
6432
+ const stat = (0, import_fs11.lstatSync)(dirPath);
6154
6433
  if (!stat.isDirectory()) return false;
6155
- return (0, import_fs10.readdirSync)(dirPath).length === 0;
6434
+ return (0, import_fs11.readdirSync)(dirPath).length === 0;
6156
6435
  }
6157
6436
  function handleCleanupScaffold(_cwd) {
6158
6437
  const cwd = _cwd ?? process.cwd();
@@ -6165,8 +6444,8 @@ function handleCleanupScaffold(_cwd) {
6165
6444
  cleanupWarnings: []
6166
6445
  };
6167
6446
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
6168
- const fullPath = (0, import_path10.join)(cwd, relPath);
6169
- if (!(0, import_fs10.existsSync)(fullPath)) {
6447
+ const fullPath = (0, import_path11.join)(cwd, relPath);
6448
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6170
6449
  result.skipped.push(relPath);
6171
6450
  continue;
6172
6451
  }
@@ -6175,7 +6454,7 @@ function handleCleanupScaffold(_cwd) {
6175
6454
  continue;
6176
6455
  }
6177
6456
  try {
6178
- (0, import_fs10.unlinkSync)(fullPath);
6457
+ (0, import_fs11.unlinkSync)(fullPath);
6179
6458
  result.deleted.push(relPath);
6180
6459
  } catch (err) {
6181
6460
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -6183,19 +6462,19 @@ function handleCleanupScaffold(_cwd) {
6183
6462
  }
6184
6463
  {
6185
6464
  const relPath = "pages/getting-started/content.yml";
6186
- const fullPath = (0, import_path10.join)(cwd, relPath);
6187
- if (!(0, import_fs10.existsSync)(fullPath)) {
6465
+ const fullPath = (0, import_path11.join)(cwd, relPath);
6466
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6188
6467
  result.skipped.push(relPath);
6189
6468
  } else if (!isSafePath(fullPath)) {
6190
6469
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
6191
6470
  } else {
6192
- const content = (0, import_fs10.readFileSync)(fullPath, "utf-8");
6471
+ const content = (0, import_fs11.readFileSync)(fullPath, "utf-8");
6193
6472
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
6194
6473
  (marker) => content.includes(marker)
6195
6474
  );
6196
6475
  if (isScaffoldDefault) {
6197
6476
  try {
6198
- (0, import_fs10.unlinkSync)(fullPath);
6477
+ (0, import_fs11.unlinkSync)(fullPath);
6199
6478
  result.deleted.push(relPath);
6200
6479
  } catch (err) {
6201
6480
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -6209,21 +6488,21 @@ function handleCleanupScaffold(_cwd) {
6209
6488
  }
6210
6489
  {
6211
6490
  const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
6212
- const fullPath = (0, import_path10.join)(cwd, relPath);
6213
- const postsDir = (0, import_path10.join)(cwd, "content/posts");
6214
- if (!(0, import_fs10.existsSync)(fullPath)) {
6491
+ const fullPath = (0, import_path11.join)(cwd, relPath);
6492
+ const postsDir = (0, import_path11.join)(cwd, "content/posts");
6493
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6215
6494
  result.skipped.push(relPath);
6216
6495
  } else if (!isSafePath(fullPath)) {
6217
6496
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
6218
- } else if (!(0, import_fs10.existsSync)(postsDir)) {
6497
+ } else if (!(0, import_fs11.existsSync)(postsDir)) {
6219
6498
  result.skipped.push(relPath);
6220
6499
  } else {
6221
- const userPostFiles = (0, import_fs10.readdirSync)(postsDir).filter(
6500
+ const userPostFiles = (0, import_fs11.readdirSync)(postsDir).filter(
6222
6501
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
6223
6502
  );
6224
6503
  if (userPostFiles.length === 0) {
6225
6504
  try {
6226
- (0, import_fs10.unlinkSync)(fullPath);
6505
+ (0, import_fs11.unlinkSync)(fullPath);
6227
6506
  result.deleted.push(relPath);
6228
6507
  } catch (err) {
6229
6508
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -6236,15 +6515,15 @@ function handleCleanupScaffold(_cwd) {
6236
6515
  }
6237
6516
  }
6238
6517
  for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
6239
- const fullDir = (0, import_path10.join)(cwd, relDir);
6240
- if (!(0, import_fs10.existsSync)(fullDir)) continue;
6518
+ const fullDir = (0, import_path11.join)(cwd, relDir);
6519
+ if (!(0, import_fs11.existsSync)(fullDir)) continue;
6241
6520
  if (!isSafePath(fullDir)) {
6242
6521
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
6243
6522
  continue;
6244
6523
  }
6245
6524
  if (isDirEmpty(fullDir)) {
6246
6525
  try {
6247
- (0, import_fs10.rmdirSync)(fullDir);
6526
+ (0, import_fs11.rmdirSync)(fullDir);
6248
6527
  result.prunedDirs.push(relDir);
6249
6528
  } catch (err) {
6250
6529
  result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
@@ -6252,8 +6531,8 @@ function handleCleanupScaffold(_cwd) {
6252
6531
  }
6253
6532
  }
6254
6533
  {
6255
- const fullPath = (0, import_path10.join)(cwd, NOT_FOUND_PATH);
6256
- if (!(0, import_fs10.existsSync)(fullPath)) {
6534
+ const fullPath = (0, import_path11.join)(cwd, NOT_FOUND_PATH);
6535
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6257
6536
  result.skipped.push(NOT_FOUND_PATH);
6258
6537
  } else if (!isSafePath(fullPath)) {
6259
6538
  result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
@@ -6261,9 +6540,9 @@ function handleCleanupScaffold(_cwd) {
6261
6540
  try {
6262
6541
  const colors = readThemeColors(cwd);
6263
6542
  const content = generateNotFoundPage(colors);
6264
- const dir = (0, import_path10.dirname)(fullPath);
6265
- (0, import_fs10.mkdirSync)(dir, { recursive: true });
6266
- (0, import_fs10.writeFileSync)(fullPath, content, "utf-8");
6543
+ const dir = (0, import_path11.dirname)(fullPath);
6544
+ (0, import_fs11.mkdirSync)(dir, { recursive: true });
6545
+ (0, import_fs11.writeFileSync)(fullPath, content, "utf-8");
6267
6546
  result.rewritten.push(NOT_FOUND_PATH);
6268
6547
  } catch (err) {
6269
6548
  result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
@@ -6530,12 +6809,258 @@ function registerContrastTools(server2) {
6530
6809
  );
6531
6810
  }
6532
6811
 
6812
+ // src/tools/compile.ts
6813
+ var import_zod19 = require("zod");
6814
+ var import_fs12 = __toESM(require("fs"));
6815
+ var import_path12 = __toESM(require("path"));
6816
+ var import_server = require("@stackwright-pro/pulse/server");
6817
+ var import_server2 = require("@stackwright-pro/auth-nextjs/server");
6818
+ var import_server3 = require("@stackwright-pro/openapi/server");
6819
+ function buildContext(projectRoot) {
6820
+ return {
6821
+ projectRoot,
6822
+ contentOutDir: import_path12.default.join(projectRoot, "public", "stackwright-content"),
6823
+ imagesDir: import_path12.default.join(projectRoot, "public", "images"),
6824
+ siteConfig: loadSiteConfig(projectRoot)
6825
+ };
6826
+ }
6827
+ function loadSiteConfig(projectRoot) {
6828
+ try {
6829
+ const sitePath = import_path12.default.join(projectRoot, "public", "stackwright-content", "_site.json");
6830
+ return JSON.parse(import_fs12.default.readFileSync(sitePath, "utf8"));
6831
+ } catch {
6832
+ return {};
6833
+ }
6834
+ }
6835
+ function formatDuration(startMs) {
6836
+ const ms = Date.now() - startMs;
6837
+ return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
6838
+ }
6839
+ async function tryOssCompile(fn, ctx, extra) {
6840
+ try {
6841
+ const bsPath = import_path12.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
6842
+ if (!import_fs12.default.existsSync(bsPath)) {
6843
+ return {
6844
+ ok: false,
6845
+ error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
6846
+ };
6847
+ }
6848
+ const bs = await import(bsPath);
6849
+ if (typeof bs[fn] !== "function") {
6850
+ return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
6851
+ }
6852
+ await bs[fn](ctx, ...extra ? [extra] : []);
6853
+ return { ok: true };
6854
+ } catch (err) {
6855
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
6856
+ }
6857
+ }
6858
+ function registerCompileTools(server2) {
6859
+ const projectRootArg = import_zod19.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
6860
+ server2.tool(
6861
+ "stackwright_pro_compile_collections",
6862
+ "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.",
6863
+ { projectRoot: projectRootArg },
6864
+ async ({ projectRoot }) => {
6865
+ const start = Date.now();
6866
+ const root = projectRoot ?? process.cwd();
6867
+ try {
6868
+ await (0, import_server.compileCollections)(buildContext(root));
6869
+ return {
6870
+ content: [
6871
+ {
6872
+ type: "text",
6873
+ text: ` _collections.json written (${formatDuration(start)})`
6874
+ }
6875
+ ]
6876
+ };
6877
+ } catch (err) {
6878
+ return {
6879
+ content: [
6880
+ {
6881
+ type: "text",
6882
+ text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
6883
+ }
6884
+ ]
6885
+ };
6886
+ }
6887
+ }
6888
+ );
6889
+ server2.tool(
6890
+ "stackwright_pro_compile_auth",
6891
+ "Compile stackwright.auth.yml \u2192 public/stackwright-content/_auth.json. Reads and validates the auth sidecar config file and emits the JSON sink consumed at app startup to configure auth middleware.",
6892
+ { projectRoot: projectRootArg },
6893
+ async ({ projectRoot }) => {
6894
+ const start = Date.now();
6895
+ const root = projectRoot ?? process.cwd();
6896
+ try {
6897
+ await (0, import_server2.compileAuth)(buildContext(root));
6898
+ return {
6899
+ content: [
6900
+ {
6901
+ type: "text",
6902
+ text: ` _auth.json written (${formatDuration(start)})`
6903
+ }
6904
+ ]
6905
+ };
6906
+ } catch (err) {
6907
+ return {
6908
+ content: [
6909
+ {
6910
+ type: "text",
6911
+ text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
6912
+ }
6913
+ ]
6914
+ };
6915
+ }
6916
+ }
6917
+ );
6918
+ server2.tool(
6919
+ "stackwright_pro_compile_integrations",
6920
+ "Compile stackwright.integrations.yml \u2192 public/stackwright-content/_integrations.json. If no yml file exists, falls back to extracting integration metadata from _site.json (collections data is excluded \u2014 it lives in _collections.json).",
6921
+ { projectRoot: projectRootArg },
6922
+ async ({ projectRoot }) => {
6923
+ const start = Date.now();
6924
+ const root = projectRoot ?? process.cwd();
6925
+ try {
6926
+ await (0, import_server3.compileIntegrations)(buildContext(root));
6927
+ return {
6928
+ content: [
6929
+ {
6930
+ type: "text",
6931
+ text: ` _integrations.json written (${formatDuration(start)})`
6932
+ }
6933
+ ]
6934
+ };
6935
+ } catch (err) {
6936
+ return {
6937
+ content: [
6938
+ {
6939
+ type: "text",
6940
+ text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
6941
+ }
6942
+ ]
6943
+ };
6944
+ }
6945
+ }
6946
+ );
6947
+ server2.tool(
6948
+ "stackwright_pro_compile_all",
6949
+ "Run all Pro compile sinks (collections + auth + integrations) in sequence. Equivalent to running compile_collections, compile_auth, and compile_integrations individually. Use this for a full Pro prebuild pass.",
6950
+ { projectRoot: projectRootArg },
6951
+ async ({ projectRoot }) => {
6952
+ const start = Date.now();
6953
+ const root = projectRoot ?? process.cwd();
6954
+ const ctx = buildContext(root);
6955
+ const results = [];
6956
+ const errors = [];
6957
+ for (const [label, fn] of [
6958
+ ["collections", () => (0, import_server.compileCollections)(ctx)],
6959
+ ["auth", () => (0, import_server2.compileAuth)(ctx)],
6960
+ ["integrations", () => (0, import_server3.compileIntegrations)(ctx)]
6961
+ ]) {
6962
+ try {
6963
+ await fn();
6964
+ results.push(` _${label}.json`);
6965
+ } catch (err) {
6966
+ errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
6967
+ }
6968
+ }
6969
+ const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
6970
+ ${results.join("\n")}
6971
+ Errors:
6972
+ ${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
6973
+ ${results.join("\n")}`;
6974
+ return { content: [{ type: "text", text: summary }] };
6975
+ }
6976
+ );
6977
+ server2.tool(
6978
+ "stackwright_pro_compile_theme",
6979
+ "Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
6980
+ { projectRoot: projectRootArg },
6981
+ async ({ projectRoot }) => {
6982
+ const start = Date.now();
6983
+ const root = projectRoot ?? process.cwd();
6984
+ const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
6985
+ return {
6986
+ content: [
6987
+ {
6988
+ type: "text",
6989
+ text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
6990
+ }
6991
+ ]
6992
+ };
6993
+ }
6994
+ );
6995
+ server2.tool(
6996
+ "stackwright_pro_compile_site",
6997
+ "Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
6998
+ { projectRoot: projectRootArg },
6999
+ async ({ projectRoot }) => {
7000
+ const start = Date.now();
7001
+ const root = projectRoot ?? process.cwd();
7002
+ const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
7003
+ return {
7004
+ content: [
7005
+ {
7006
+ type: "text",
7007
+ text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
7008
+ }
7009
+ ]
7010
+ };
7011
+ }
7012
+ );
7013
+ server2.tool(
7014
+ "stackwright_pro_compile_pages",
7015
+ "Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
7016
+ { projectRoot: projectRootArg },
7017
+ async ({ projectRoot }) => {
7018
+ const start = Date.now();
7019
+ const root = projectRoot ?? process.cwd();
7020
+ const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
7021
+ return {
7022
+ content: [
7023
+ {
7024
+ type: "text",
7025
+ text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
7026
+ }
7027
+ ]
7028
+ };
7029
+ }
7030
+ );
7031
+ server2.tool(
7032
+ "stackwright_pro_compile_page",
7033
+ "Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
7034
+ {
7035
+ slug: import_zod19.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
7036
+ projectRoot: projectRootArg
7037
+ },
7038
+ async ({ slug, projectRoot }) => {
7039
+ const start = Date.now();
7040
+ const root = projectRoot ?? process.cwd();
7041
+ const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
7042
+ return {
7043
+ content: [
7044
+ {
7045
+ type: "text",
7046
+ text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
7047
+ }
7048
+ ]
7049
+ };
7050
+ }
7051
+ );
7052
+ }
7053
+
6533
7054
  // package.json
6534
7055
  var package_default = {
6535
7056
  dependencies: {
6536
7057
  "@modelcontextprotocol/sdk": "^1.10.0",
7058
+ "@stackwright-pro/auth-nextjs": "workspace:*",
6537
7059
  "@stackwright-pro/cli-data-explorer": "workspace:*",
7060
+ "@stackwright-pro/openapi": "workspace:*",
7061
+ "@stackwright-pro/pulse": "workspace:*",
6538
7062
  "@stackwright-pro/types": "workspace:*",
7063
+ "@stackwright/mcp": "0.6.0-alpha.1",
6539
7064
  "@types/js-yaml": "^4.0.9",
6540
7065
  "js-yaml": "^4.2.0",
6541
7066
  zod: "^4.4.3"
@@ -6555,7 +7080,7 @@ var package_default = {
6555
7080
  "test:coverage": "vitest run --coverage"
6556
7081
  },
6557
7082
  name: "@stackwright-pro/mcp",
6558
- version: "0.2.0-alpha.92",
7083
+ version: "0.2.0-alpha.97",
6559
7084
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
6560
7085
  license: "SEE LICENSE IN LICENSE",
6561
7086
  main: "./dist/server.js",
@@ -6579,6 +7104,11 @@ var package_default = {
6579
7104
  types: "./dist/tools/type-schemas.d.ts",
6580
7105
  import: "./dist/tools/type-schemas.mjs",
6581
7106
  require: "./dist/tools/type-schemas.js"
7107
+ },
7108
+ "./pipeline-constants": {
7109
+ types: "./dist/pipeline-constants.d.ts",
7110
+ import: "./dist/pipeline-constants.mjs",
7111
+ require: "./dist/pipeline-constants.js"
6582
7112
  }
6583
7113
  },
6584
7114
  files: [
@@ -6590,6 +7120,7 @@ var package_default = {
6590
7120
  };
6591
7121
 
6592
7122
  // src/server.ts
7123
+ var import_register = require("@stackwright/mcp/register");
6593
7124
  var server = new import_mcp.McpServer({
6594
7125
  name: "stackwright-pro",
6595
7126
  version: package_default.version
@@ -6613,7 +7144,45 @@ registerValidateYamlFragmentTool(server);
6613
7144
  registerGetSchemaTool(server);
6614
7145
  registerScaffoldCleanupTools(server);
6615
7146
  registerContrastTools(server);
7147
+ registerCompileTools(server);
7148
+ (0, import_register.registerContentTypeTools)(server);
7149
+ (0, import_register.registerPageTools)(server);
7150
+ (0, import_register.registerSiteTools)(server);
7151
+ (0, import_register.registerProjectTools)(server);
7152
+ (0, import_register.registerGitOpsTools)(server);
7153
+ (0, import_register.registerBoardTools)(server);
7154
+ (0, import_register.registerCollectionTools)(server);
7155
+ (0, import_register.registerIntegrationTools)(server);
7156
+ (0, import_register.registerComposeTools)(server);
7157
+ (0, import_register.registerRenderTools)(server);
7158
+ (0, import_register.registerA11yTools)(server);
7159
+ process.on("SIGINT", async () => {
7160
+ const forceExit = setTimeout(() => process.exit(1), 3e3);
7161
+ forceExit.unref();
7162
+ await (0, import_register.closeBrowser)();
7163
+ process.exit(0);
7164
+ });
7165
+ process.on("SIGTERM", async () => {
7166
+ const forceExit = setTimeout(() => process.exit(1), 3e3);
7167
+ forceExit.unref();
7168
+ await (0, import_register.closeBrowser)();
7169
+ process.exit(0);
7170
+ });
6616
7171
  async function main() {
7172
+ const pipelineGraph = loadPipelineGraph();
7173
+ const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
7174
+ for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
7175
+ const phaseIdx = phasePosition.get(phase);
7176
+ if (phaseIdx === void 0) continue;
7177
+ for (const dep of deps) {
7178
+ const depIdx = phasePosition.get(dep);
7179
+ if (depIdx !== void 0 && depIdx >= phaseIdx) {
7180
+ throw new Error(
7181
+ `PHASE_ORDER sanity check failed: "${dep}" (position ${depIdx}) must precede "${phase}" (position ${phaseIdx}) but it doesn't. Update PHASE_ORDER in pipeline.ts to reflect the new dependency structure.`
7182
+ );
7183
+ }
7184
+ }
7185
+ }
6617
7186
  const transport = new import_stdio.StdioServerTransport();
6618
7187
  try {
6619
7188
  const servicesRegisterPkg = "@stackwright-services/mcp/register";