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

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,223 @@ 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
+ };
2706
+ function manifestNameToPhase(name) {
2707
+ return MANIFEST_NAME_TO_PHASE[name] ?? null;
2708
+ }
2709
+ var OTTER_SEARCH_PATHS = [
2710
+ "node_modules/@stackwright-pro/otters/src/",
2711
+ // production: installed package
2712
+ "packages/otters/src/",
2713
+ // monorepo: run from repo root
2714
+ "../otters/src/"
2715
+ // monorepo: run from packages/mcp/
2716
+ ];
2717
+ function resolveOtterDirFromCwd() {
2718
+ const cwd = process.cwd();
2719
+ for (const relative of OTTER_SEARCH_PATHS) {
2720
+ const candidate = (0, import_path5.join)(cwd, relative);
2721
+ try {
2722
+ (0, import_fs5.lstatSync)(candidate);
2723
+ return candidate;
2724
+ } catch {
2725
+ }
2726
+ }
2727
+ throw new Error(
2728
+ `Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
2729
+ );
2730
+ }
2731
+ function loadOtterManifestsFromDir(otterDir) {
2732
+ const entries = (0, import_fs5.readdirSync)(otterDir);
2733
+ const manifests = [];
2734
+ for (const filename of entries) {
2735
+ if (!filename.endsWith("-otter.json")) continue;
2736
+ const filePath = (0, import_path5.join)(otterDir, filename);
2737
+ if ((0, import_fs5.lstatSync)(filePath).isSymbolicLink()) continue;
2738
+ try {
2739
+ const raw = (0, import_fs5.readFileSync)(filePath, "utf-8");
2740
+ const manifest = JSON.parse(raw);
2741
+ manifests.push(manifest);
2742
+ } catch (err) {
2743
+ const msg = err instanceof Error ? err.message : String(err);
2744
+ throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
2745
+ }
2746
+ }
2747
+ return manifests;
2748
+ }
2749
+ function topologicalSort(dependencies) {
2750
+ const phases = Object.keys(dependencies);
2751
+ const inDegree = {};
2752
+ const adjList = {};
2753
+ for (const phase of phases) {
2754
+ inDegree[phase] ??= 0;
2755
+ adjList[phase] ??= [];
2756
+ }
2757
+ for (const phase of phases) {
2758
+ for (const dep of dependencies[phase] ?? []) {
2759
+ inDegree[phase] = (inDegree[phase] ?? 0) + 1;
2760
+ adjList[dep] ??= [];
2761
+ adjList[dep].push(phase);
2762
+ }
2763
+ }
2764
+ const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
2765
+ const result = [];
2766
+ while (queue.length > 0) {
2767
+ const node = queue.shift();
2768
+ result.push(node);
2769
+ for (const dependent of adjList[node] ?? []) {
2770
+ inDegree[dependent] -= 1;
2771
+ if (inDegree[dependent] === 0) {
2772
+ queue.push(dependent);
2773
+ }
2774
+ }
2775
+ }
2776
+ if (result.length !== phases.length) {
2777
+ const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
2778
+ throw new Error(
2779
+ `Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
2780
+ );
2781
+ }
2782
+ return result;
2783
+ }
2784
+ function buildPipelineGraph(manifests) {
2785
+ const sinkProducers = {};
2786
+ const artifactProducers = {};
2787
+ for (const m of manifests) {
2788
+ const phase = manifestNameToPhase(m.name);
2789
+ if (!phase) continue;
2790
+ const out = m.pipeline?.outputs;
2791
+ if (!out) continue;
2792
+ for (const sink of out.sinks ?? []) {
2793
+ if (sinkProducers[sink]) {
2794
+ throw new Error(
2795
+ `Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
2796
+ );
2797
+ }
2798
+ sinkProducers[sink] = phase;
2799
+ }
2800
+ if (out.artifact) {
2801
+ if (artifactProducers[out.artifact]) {
2802
+ throw new Error(
2803
+ `Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
2804
+ );
2805
+ }
2806
+ artifactProducers[out.artifact] = phase;
2807
+ }
2808
+ }
2809
+ const dependencies = {};
2810
+ for (const m of manifests) {
2811
+ const phase = manifestNameToPhase(m.name);
2812
+ if (!phase) continue;
2813
+ const deps = /* @__PURE__ */ new Set();
2814
+ const inp = m.pipeline?.inputs;
2815
+ if (inp) {
2816
+ for (const sink of inp.sinks ?? []) {
2817
+ const producer = sinkProducers[sink];
2818
+ if (!producer) {
2819
+ throw new Error(
2820
+ `Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
2821
+ );
2822
+ }
2823
+ if (producer !== phase) deps.add(producer);
2824
+ }
2825
+ for (const artifact of inp.artifacts ?? []) {
2826
+ const producer = artifactProducers[artifact];
2827
+ if (!producer) {
2828
+ throw new Error(
2829
+ `Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
2830
+ );
2831
+ }
2832
+ if (producer !== phase) deps.add(producer);
2833
+ }
2834
+ }
2835
+ dependencies[phase] = Array.from(deps);
2836
+ }
2837
+ const order = topologicalSort(dependencies);
2838
+ return { dependencies, order, sinkProducers, artifactProducers };
2839
+ }
2840
+ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
2841
+ function loadDistributedOtterManifests(cwd) {
2842
+ const results = [];
2843
+ for (const namespace of DISTRIBUTED_NAMESPACES) {
2844
+ const nsDir = (0, import_path5.join)(cwd, "node_modules", namespace);
2845
+ let pkgDirNames;
2846
+ try {
2847
+ pkgDirNames = (0, import_fs5.readdirSync)(nsDir);
2848
+ } catch {
2849
+ continue;
2850
+ }
2851
+ for (const pkgDirName of pkgDirNames) {
2852
+ const pkgDir = (0, import_path5.join)(nsDir, pkgDirName);
2853
+ const packageName = `${namespace}/${pkgDirName}`;
2854
+ let otterRelPaths;
2855
+ try {
2856
+ const raw = (0, import_fs5.readFileSync)((0, import_path5.join)(pkgDir, "package.json"), "utf-8");
2857
+ const pkgJson = JSON.parse(raw);
2858
+ const declared = pkgJson.stackwright?.otters;
2859
+ if (!Array.isArray(declared) || declared.length === 0) continue;
2860
+ otterRelPaths = declared;
2861
+ } catch {
2862
+ continue;
2863
+ }
2864
+ for (const relPath of otterRelPaths) {
2865
+ const otterFilePath = (0, import_path5.join)(pkgDir, relPath);
2866
+ try {
2867
+ const raw = (0, import_fs5.readFileSync)(otterFilePath, "utf-8");
2868
+ const manifest = JSON.parse(raw);
2869
+ results.push({ manifest, packageName });
2870
+ } catch (err) {
2871
+ const msg = err instanceof Error ? err.message : String(err);
2872
+ console.warn(
2873
+ `[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
2874
+ );
2875
+ }
2876
+ }
2877
+ }
2878
+ }
2879
+ return results;
2880
+ }
2881
+ function _mergeManifestPools(central, distributed) {
2882
+ const centralNames = new Set(central.map((m) => m.name));
2883
+ const merged = [...central];
2884
+ for (const { manifest, packageName } of distributed) {
2885
+ if (centralNames.has(manifest.name)) {
2886
+ console.warn(
2887
+ `[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
2888
+ );
2889
+ } else {
2890
+ merged.push(manifest);
2891
+ }
2892
+ }
2893
+ return merged;
2894
+ }
2895
+ var _cachedGraph = null;
2896
+ function loadPipelineGraph() {
2897
+ if (_cachedGraph) return _cachedGraph;
2898
+ const otterDir = resolveOtterDirFromCwd();
2899
+ const central = loadOtterManifestsFromDir(otterDir);
2900
+ const distributed = loadDistributedOtterManifests(process.cwd());
2901
+ const manifests = _mergeManifestPools(central, distributed);
2902
+ _cachedGraph = buildPipelineGraph(manifests);
2903
+ return _cachedGraph;
2904
+ }
2905
+
2689
2906
  // src/tools/pipeline.ts
2690
2907
  var PHASE_ORDER = [
2691
2908
  "designer",
@@ -2694,45 +2911,15 @@ var PHASE_ORDER = [
2694
2911
  // generates app/ directory from config
2695
2912
  "api",
2696
2913
  "data",
2914
+ "auth",
2915
+ // moved earlier — only depends on design-language.json (designer)
2697
2916
  "geo",
2698
2917
  "workflow",
2699
2918
  "services",
2700
2919
  "pages",
2701
2920
  "dashboard",
2702
- "auth",
2703
2921
  "polish"
2704
2922
  ];
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
2923
  var PHASE_ARTIFACT = {
2737
2924
  designer: "design-language.json",
2738
2925
  theme: "theme-tokens.json",
@@ -2789,11 +2976,11 @@ function createDefaultState() {
2789
2976
  };
2790
2977
  }
2791
2978
  function statePath(cwd) {
2792
- return (0, import_path5.join)(cwd, ".stackwright", "pipeline-state.json");
2979
+ return (0, import_path6.join)(cwd, ".stackwright", "pipeline-state.json");
2793
2980
  }
2794
2981
  function readState(cwd) {
2795
2982
  const p = statePath(cwd);
2796
- if (!(0, import_fs5.existsSync)(p)) return createDefaultState();
2983
+ if (!(0, import_fs6.existsSync)(p)) return createDefaultState();
2797
2984
  const raw = JSON.parse(safeReadSync(p));
2798
2985
  if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
2799
2986
  return createDefaultState();
@@ -2801,26 +2988,26 @@ function readState(cwd) {
2801
2988
  return raw;
2802
2989
  }
2803
2990
  function safeWriteSync(filePath, content) {
2804
- if ((0, import_fs5.existsSync)(filePath)) {
2805
- const stat = (0, import_fs5.lstatSync)(filePath);
2991
+ if ((0, import_fs6.existsSync)(filePath)) {
2992
+ const stat = (0, import_fs6.lstatSync)(filePath);
2806
2993
  if (stat.isSymbolicLink()) {
2807
2994
  throw new Error(`Refusing to write to symlink: ${filePath}`);
2808
2995
  }
2809
2996
  }
2810
- (0, import_fs5.writeFileSync)(filePath, content);
2997
+ (0, import_fs6.writeFileSync)(filePath, content);
2811
2998
  }
2812
2999
  function safeReadSync(filePath) {
2813
- if ((0, import_fs5.existsSync)(filePath)) {
2814
- const stat = (0, import_fs5.lstatSync)(filePath);
3000
+ if ((0, import_fs6.existsSync)(filePath)) {
3001
+ const stat = (0, import_fs6.lstatSync)(filePath);
2815
3002
  if (stat.isSymbolicLink()) {
2816
3003
  throw new Error(`Refusing to read symlink: ${filePath}`);
2817
3004
  }
2818
3005
  }
2819
- return (0, import_fs5.readFileSync)(filePath, "utf-8");
3006
+ return (0, import_fs6.readFileSync)(filePath, "utf-8");
2820
3007
  }
2821
3008
  function writeState(cwd, state) {
2822
- const dir = (0, import_path5.join)(cwd, ".stackwright");
2823
- (0, import_fs5.mkdirSync)(dir, { recursive: true });
3009
+ const dir = (0, import_path6.join)(cwd, ".stackwright");
3010
+ (0, import_fs6.mkdirSync)(dir, { recursive: true });
2824
3011
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2825
3012
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
2826
3013
  }
@@ -2841,8 +3028,8 @@ function handleGetPipelineState(_cwd) {
2841
3028
  const cwd = _cwd ?? process.cwd();
2842
3029
  try {
2843
3030
  const state = readState(cwd);
2844
- const keyPath = (0, import_path5.join)(cwd, ".stackwright", "pipeline-keys.json");
2845
- if (!(0, import_fs5.existsSync)(keyPath)) {
3031
+ const keyPath = (0, import_path6.join)(cwd, ".stackwright", "pipeline-keys.json");
3032
+ if (!(0, import_fs6.existsSync)(keyPath)) {
2846
3033
  try {
2847
3034
  const { fingerprint } = initPipelineKeys(cwd);
2848
3035
  state["signingKeyFingerprint"] = fingerprint;
@@ -2945,8 +3132,8 @@ function handleCheckExecutionReady(_cwd, phase) {
2945
3132
  isError: true
2946
3133
  };
2947
3134
  }
2948
- const answerFile = (0, import_path5.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2949
- if (!(0, import_fs5.existsSync)(answerFile)) {
3135
+ const answerFile = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3136
+ if (!(0, import_fs6.existsSync)(answerFile)) {
2950
3137
  return {
2951
3138
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
2952
3139
  isError: false
@@ -2970,12 +3157,12 @@ function handleCheckExecutionReady(_cwd, phase) {
2970
3157
  }
2971
3158
  }
2972
3159
  try {
2973
- const answersDir = (0, import_path5.join)(cwd, ".stackwright", "answers");
3160
+ const answersDir = (0, import_path6.join)(cwd, ".stackwright", "answers");
2974
3161
  const answeredPhases = [];
2975
3162
  const missingPhases = [];
2976
3163
  for (const phase2 of PHASE_ORDER) {
2977
- const answerFile = (0, import_path5.join)(answersDir, `${phase2}.json`);
2978
- if ((0, import_fs5.existsSync)(answerFile)) {
3164
+ const answerFile = (0, import_path6.join)(answersDir, `${phase2}.json`);
3165
+ if ((0, import_fs6.existsSync)(answerFile)) {
2979
3166
  try {
2980
3167
  const raw = safeReadSync(answerFile);
2981
3168
  const parsed = JSON.parse(raw);
@@ -3008,6 +3195,7 @@ function handleGetReadyPhases(_cwd) {
3008
3195
  const cwd = _cwd ?? process.cwd();
3009
3196
  try {
3010
3197
  const state = readState(cwd);
3198
+ const graph = loadPipelineGraph();
3011
3199
  const completed = [];
3012
3200
  const ready = [];
3013
3201
  const blocked = [];
@@ -3017,7 +3205,7 @@ function handleGetReadyPhases(_cwd) {
3017
3205
  completed.push(phase);
3018
3206
  continue;
3019
3207
  }
3020
- const deps = PHASE_DEPENDENCIES[phase];
3208
+ const deps = graph.dependencies[phase] ?? [];
3021
3209
  const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
3022
3210
  if (unmetDeps.length === 0) {
3023
3211
  ready.push(phase);
@@ -3043,7 +3231,7 @@ function handleGetReadyPhases(_cwd) {
3043
3231
  function handleListArtifacts(_cwd) {
3044
3232
  const cwd = _cwd ?? process.cwd();
3045
3233
  try {
3046
- const artifactsDir = (0, import_path5.join)(cwd, ".stackwright", "artifacts");
3234
+ const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
3047
3235
  let manifest = null;
3048
3236
  try {
3049
3237
  manifest = loadSignatureManifest(cwd);
@@ -3053,8 +3241,8 @@ function handleListArtifacts(_cwd) {
3053
3241
  let completedCount = 0;
3054
3242
  for (const phase of PHASE_ORDER) {
3055
3243
  const expectedFile = PHASE_ARTIFACT[phase];
3056
- const fullPath = (0, import_path5.join)(artifactsDir, expectedFile);
3057
- const exists = (0, import_fs5.existsSync)(fullPath);
3244
+ const fullPath = (0, import_path6.join)(artifactsDir, expectedFile);
3245
+ const exists = (0, import_fs6.existsSync)(fullPath);
3058
3246
  if (exists) completedCount++;
3059
3247
  let signed = false;
3060
3248
  let signatureValid = null;
@@ -3107,9 +3295,9 @@ function handleWritePhaseQuestions(input) {
3107
3295
  }
3108
3296
  } catch {
3109
3297
  }
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`);
3298
+ const questionsDir = (0, import_path6.join)(cwd, ".stackwright", "questions");
3299
+ (0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
3300
+ const filePath = (0, import_path6.join)(questionsDir, `${phase}.json`);
3113
3301
  const payload = {
3114
3302
  version: "1.0",
3115
3303
  phase,
@@ -3149,14 +3337,14 @@ function handleBuildSpecialistPrompt(input) {
3149
3337
  };
3150
3338
  }
3151
3339
  try {
3152
- const answersPath = (0, import_path5.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3340
+ const answersPath = (0, import_path6.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3153
3341
  let answers = {};
3154
- if ((0, import_fs5.existsSync)(answersPath)) {
3342
+ if ((0, import_fs6.existsSync)(answersPath)) {
3155
3343
  answers = JSON.parse(safeReadSync(answersPath));
3156
3344
  }
3157
3345
  let buildContextText = "";
3158
- const buildContextPath = (0, import_path5.join)(cwd, ".stackwright", "build-context.json");
3159
- if ((0, import_fs5.existsSync)(buildContextPath)) {
3346
+ const buildContextPath = (0, import_path6.join)(cwd, ".stackwright", "build-context.json");
3347
+ if ((0, import_fs6.existsSync)(buildContextPath)) {
3160
3348
  try {
3161
3349
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
3162
3350
  if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
@@ -3165,13 +3353,14 @@ function handleBuildSpecialistPrompt(input) {
3165
3353
  } catch {
3166
3354
  }
3167
3355
  }
3168
- const deps = PHASE_DEPENDENCIES[phase];
3356
+ const graph = loadPipelineGraph();
3357
+ const deps = graph.dependencies[phase] ?? [];
3169
3358
  const artifactSections = [];
3170
3359
  const missingDependencies = [];
3171
3360
  for (const dep of deps) {
3172
3361
  const artifactFile = PHASE_ARTIFACT[dep];
3173
- const artifactPath = (0, import_path5.join)(cwd, ".stackwright", "artifacts", artifactFile);
3174
- if ((0, import_fs5.existsSync)(artifactPath)) {
3362
+ const artifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", artifactFile);
3363
+ if ((0, import_fs6.existsSync)(artifactPath)) {
3175
3364
  const rawContent = safeReadSync(artifactPath);
3176
3365
  const rawBytes = Buffer.from(rawContent, "utf-8");
3177
3366
  const content = JSON.parse(rawContent);
@@ -3236,8 +3425,8 @@ ${JSON.stringify(content, null, 2)}`
3236
3425
  parts.push("BUILD_CONTEXT:", buildContextText, "");
3237
3426
  }
3238
3427
  if (phase === "auth") {
3239
- const initContextPath = (0, import_path5.join)(cwd, ".stackwright", "init-context.json");
3240
- if ((0, import_fs5.existsSync)(initContextPath)) {
3428
+ const initContextPath = (0, import_path6.join)(cwd, ".stackwright", "init-context.json");
3429
+ if ((0, import_fs6.existsSync)(initContextPath)) {
3241
3430
  try {
3242
3431
  const initContext = JSON.parse(safeReadSync(initContextPath));
3243
3432
  if (initContext.devOnly === true || initContext.nonInteractive === true) {
@@ -3704,13 +3893,13 @@ function handleValidateArtifact(input) {
3704
3893
  // to a flow or state-machine in this services-config artifact.
3705
3894
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
3706
3895
  services: (artifact2) => {
3707
- const workflowArtifactPath = (0, import_path5.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
3708
- if (!(0, import_fs5.existsSync)(workflowArtifactPath)) {
3896
+ const workflowArtifactPath = (0, import_path6.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
3897
+ if (!(0, import_fs6.existsSync)(workflowArtifactPath)) {
3709
3898
  return { success: true };
3710
3899
  }
3711
3900
  let workflowArtifact;
3712
3901
  try {
3713
- workflowArtifact = JSON.parse((0, import_fs5.readFileSync)(workflowArtifactPath, "utf-8"));
3902
+ workflowArtifact = JSON.parse((0, import_fs6.readFileSync)(workflowArtifactPath, "utf-8"));
3714
3903
  } catch {
3715
3904
  return { success: true };
3716
3905
  }
@@ -3747,10 +3936,10 @@ function handleValidateArtifact(input) {
3747
3936
  }
3748
3937
  }
3749
3938
  try {
3750
- const artifactsDir = (0, import_path5.join)(cwd, ".stackwright", "artifacts");
3751
- (0, import_fs5.mkdirSync)(artifactsDir, { recursive: true });
3939
+ const artifactsDir = (0, import_path6.join)(cwd, ".stackwright", "artifacts");
3940
+ (0, import_fs6.mkdirSync)(artifactsDir, { recursive: true });
3752
3941
  const artifactFile = PHASE_ARTIFACT[phase];
3753
- const artifactPath = (0, import_path5.join)(artifactsDir, artifactFile);
3942
+ const artifactPath = (0, import_path6.join)(artifactsDir, artifactFile);
3754
3943
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
3755
3944
  const artifactBytes = Buffer.from(serialized, "utf-8");
3756
3945
  safeWriteSync(artifactPath, serialized);
@@ -3867,10 +4056,10 @@ function registerPipelineTools(server2) {
3867
4056
  isError: true
3868
4057
  };
3869
4058
  }
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)(
4059
+ const questionsDir = (0, import_path6.join)(process.cwd(), ".stackwright", "questions");
4060
+ (0, import_fs6.mkdirSync)(questionsDir, { recursive: true });
4061
+ const outPath = (0, import_path6.join)(questionsDir, `${phase}.json`);
4062
+ (0, import_fs6.writeFileSync)(
3874
4063
  outPath,
3875
4064
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
3876
4065
  );
@@ -3921,8 +4110,8 @@ function registerPipelineTools(server2) {
3921
4110
 
3922
4111
  // src/tools/safe-write.ts
3923
4112
  var import_zod14 = require("zod");
3924
- var import_fs6 = require("fs");
3925
- var import_path6 = require("path");
4113
+ var import_fs7 = require("fs");
4114
+ var import_path7 = require("path");
3926
4115
  var OTTER_WRITE_ALLOWLISTS = {
3927
4116
  "stackwright-pro-designer-otter": [
3928
4117
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
@@ -3937,8 +4126,11 @@ var OTTER_WRITE_ALLOWLISTS = {
3937
4126
  ],
3938
4127
  "stackwright-pro-auth-otter": [
3939
4128
  { 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" },
4129
+ {
4130
+ prefix: "stackwright.auth.",
4131
+ suffix: ".yml",
4132
+ description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
4133
+ },
3942
4134
  {
3943
4135
  prefix: ".env",
3944
4136
  suffix: "",
@@ -3957,7 +4149,11 @@ var OTTER_WRITE_ALLOWLISTS = {
3957
4149
  ],
3958
4150
  "stackwright-pro-data-otter": [
3959
4151
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
3960
- { prefix: "stackwright.yml", suffix: "", description: "Stackwright config" }
4152
+ {
4153
+ prefix: "stackwright.collections.",
4154
+ suffix: ".yml",
4155
+ description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
4156
+ }
3961
4157
  ],
3962
4158
  "stackwright-pro-page-otter": [
3963
4159
  { prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
@@ -3983,7 +4179,12 @@ var OTTER_WRITE_ALLOWLISTS = {
3983
4179
  }
3984
4180
  ],
3985
4181
  "stackwright-pro-api-otter": [
3986
- { prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
4182
+ { prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
4183
+ {
4184
+ prefix: "stackwright.integrations.",
4185
+ suffix: ".yml",
4186
+ description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
4187
+ }
3987
4188
  ],
3988
4189
  "stackwright-pro-geo-otter": [
3989
4190
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
@@ -4046,7 +4247,7 @@ function getMaxBytesForPath(filePath) {
4046
4247
  }
4047
4248
  var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
4048
4249
  function extractPageSlug(filePath) {
4049
- const match = (0, import_path6.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
4250
+ const match = (0, import_path7.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
4050
4251
  return match?.[1] ?? null;
4051
4252
  }
4052
4253
  function extractContentTypes(yamlContent) {
@@ -4062,27 +4263,27 @@ function extractContentTypes(yamlContent) {
4062
4263
  return types.length > 0 ? types : ["unknown"];
4063
4264
  }
4064
4265
  function readPageRegistry(cwd) {
4065
- const regPath = (0, import_path6.join)(cwd, PAGE_REGISTRY_FILE);
4066
- if (!(0, import_fs6.existsSync)(regPath)) return {};
4266
+ const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
4267
+ if (!(0, import_fs7.existsSync)(regPath)) return {};
4067
4268
  try {
4068
- const stat = (0, import_fs6.lstatSync)(regPath);
4269
+ const stat = (0, import_fs7.lstatSync)(regPath);
4069
4270
  if (stat.isSymbolicLink()) return {};
4070
- return JSON.parse((0, import_fs6.readFileSync)(regPath, "utf-8"));
4271
+ return JSON.parse((0, import_fs7.readFileSync)(regPath, "utf-8"));
4071
4272
  } catch {
4072
4273
  return {};
4073
4274
  }
4074
4275
  }
4075
4276
  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);
4277
+ const dir = (0, import_path7.join)(cwd, ".stackwright");
4278
+ (0, import_fs7.mkdirSync)(dir, { recursive: true });
4279
+ const regPath = (0, import_path7.join)(cwd, PAGE_REGISTRY_FILE);
4280
+ if ((0, import_fs7.existsSync)(regPath)) {
4281
+ const stat = (0, import_fs7.lstatSync)(regPath);
4081
4282
  if (stat.isSymbolicLink()) {
4082
4283
  throw new Error("Refusing to write page registry through symlink");
4083
4284
  }
4084
4285
  }
4085
- (0, import_fs6.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
4286
+ (0, import_fs7.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
4086
4287
  }
4087
4288
  function checkPageOwnership(cwd, slug, callerOtter) {
4088
4289
  const registry = readPageRegistry(cwd);
@@ -4097,15 +4298,15 @@ function checkPageOwnership(cwd, slug, callerOtter) {
4097
4298
  };
4098
4299
  }
4099
4300
  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, "");
4301
+ function stripNextjsBracketSegments(path4) {
4302
+ return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
4102
4303
  }
4103
4304
  function checkPathAllowed(callerOtter, filePath) {
4104
- const normalized = (0, import_path6.normalize)(filePath);
4305
+ const normalized = (0, import_path7.normalize)(filePath);
4105
4306
  if (stripNextjsBracketSegments(normalized).includes("..")) {
4106
4307
  return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
4107
4308
  }
4108
- if ((0, import_path6.isAbsolute)(normalized)) {
4309
+ if ((0, import_path7.isAbsolute)(normalized)) {
4109
4310
  return {
4110
4311
  allowed: false,
4111
4312
  error: "Absolute paths are not allowed \u2014 use paths relative to project root"
@@ -4247,11 +4448,11 @@ function handleSafeWrite(input) {
4247
4448
  };
4248
4449
  return { text: JSON.stringify(result), isError: true };
4249
4450
  }
4250
- const normalized = (0, import_path6.normalize)(filePath);
4251
- const fullPath = (0, import_path6.join)(cwd, normalized);
4252
- if ((0, import_fs6.existsSync)(fullPath)) {
4451
+ const normalized = (0, import_path7.normalize)(filePath);
4452
+ const fullPath = (0, import_path7.join)(cwd, normalized);
4453
+ if ((0, import_fs7.existsSync)(fullPath)) {
4253
4454
  try {
4254
- const stat = (0, import_fs6.lstatSync)(fullPath);
4455
+ const stat = (0, import_fs7.lstatSync)(fullPath);
4255
4456
  if (stat.isSymbolicLink()) {
4256
4457
  const result = {
4257
4458
  success: false,
@@ -4292,9 +4493,9 @@ function handleSafeWrite(input) {
4292
4493
  }
4293
4494
  try {
4294
4495
  if (createDirectories) {
4295
- (0, import_fs6.mkdirSync)((0, import_path6.dirname)(fullPath), { recursive: true });
4496
+ (0, import_fs7.mkdirSync)((0, import_path7.dirname)(fullPath), { recursive: true });
4296
4497
  }
4297
- (0, import_fs6.writeFileSync)(fullPath, content, { encoding: "utf-8" });
4498
+ (0, import_fs7.writeFileSync)(fullPath, content, { encoding: "utf-8" });
4298
4499
  if (pageSlug) {
4299
4500
  try {
4300
4501
  const registry = readPageRegistry(cwd);
@@ -4354,8 +4555,8 @@ function registerSafeWriteTools(server2) {
4354
4555
 
4355
4556
  // src/tools/auth.ts
4356
4557
  var import_zod15 = require("zod");
4357
- var import_fs7 = require("fs");
4358
- var import_path7 = require("path");
4558
+ var import_fs8 = require("fs");
4559
+ var import_path8 = require("path");
4359
4560
  function buildHierarchy(roles) {
4360
4561
  const h = {};
4361
4562
  for (let i = 0; i < roles.length - 1; i++) {
@@ -4369,7 +4570,7 @@ function hierarchyToYaml(hierarchy, indent) {
4369
4570
  return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
4370
4571
  }
4371
4572
  function rolesToYaml(roles, indent) {
4372
- return roles.map((r) => `${indent}- ${r}`).join("\n");
4573
+ return roles.map((r) => `${indent}- name: ${r}`).join("\n");
4373
4574
  }
4374
4575
  function normalizeRoutes(routes, defaultRole) {
4375
4576
  return routes.map(
@@ -4382,9 +4583,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
4382
4583
  }
4383
4584
  function detectNextMajorVersion(cwd) {
4384
4585
  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"));
4586
+ const pkgPath = (0, import_path8.join)(cwd, "package.json");
4587
+ if (!(0, import_fs8.existsSync)(pkgPath)) return null;
4588
+ const pkg = JSON.parse((0, import_fs8.readFileSync)(pkgPath, "utf8"));
4388
4589
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
4389
4590
  if (!nextVersion) return null;
4390
4591
  const match = nextVersion.match(/(\d+)/);
@@ -4393,25 +4594,6 @@ function detectNextMajorVersion(cwd) {
4393
4594
  return null;
4394
4595
  }
4395
4596
  }
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
4597
  function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
4416
4598
  const rbacBlock = ` rbac: {
4417
4599
  roles: ${JSON.stringify(roles)},
@@ -4611,33 +4793,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
4611
4793
  `;
4612
4794
  }
4613
4795
  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}
4796
+ const rbacSection = `rbac:
4797
+ roles:
4798
+ ${rolesToYaml(roles, " ")}
4799
+ defaultRole: ${defaultRole}
4800
+ hierarchy:
4801
+ ${hierarchyToYaml(hierarchy, " ")}`;
4802
+ const auditSection = `audit:
4803
+ enabled: ${auditEnabled}
4804
+ retentionDays: ${auditRetentionDays}`;
4805
+ const routeLines = routesToYaml(protectedRoutes, " ");
4806
+ const providerLine = params.provider ? `provider: ${params.provider}
4625
4807
  ` : "";
4626
4808
  if (method === "cac") {
4627
4809
  const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4628
4810
  const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4629
4811
  const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4630
4812
  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}
4813
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4814
+ method: cac
4815
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4816
+ cac:
4817
+ caBundle: \${CAC_CA_BUNDLE}
4818
+ edipiLookup: ${edipiLookup}
4819
+ ocspEndpoint: \${CAC_OCSP_ENDPOINT}
4820
+ certHeader: ${certHeader}
4639
4821
  ${rbacSection}
4640
- protectedRoutes:
4822
+ protectedRoutes:
4641
4823
  ${routeLines}
4642
4824
  ${auditSection}
4643
4825
  `;
@@ -4645,33 +4827,33 @@ ${auditSection}
4645
4827
  if (method === "oidc") {
4646
4828
  const scopes2 = params.oidcScopes ?? "openid profile email";
4647
4829
  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}
4830
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4831
+ method: oidc
4832
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4833
+ oidc:
4834
+ discoveryUrl: \${OIDC_DISCOVERY_URL}
4835
+ clientId: \${OIDC_CLIENT_ID}
4836
+ clientSecret: \${OIDC_CLIENT_SECRET}
4837
+ scopes: ${scopes2}
4838
+ roleClaim: ${roleClaim}
4657
4839
  ${rbacSection}
4658
- protectedRoutes:
4840
+ protectedRoutes:
4659
4841
  ${routeLines}
4660
4842
  ${auditSection}
4661
4843
  `;
4662
4844
  }
4663
4845
  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}
4846
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4847
+ method: oauth2
4848
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4849
+ oauth2:
4850
+ authorizationUrl: \${OAUTH2_AUTH_URL}
4851
+ tokenUrl: \${OAUTH2_TOKEN_URL}
4852
+ clientId: \${OAUTH2_CLIENT_ID}
4853
+ clientSecret: \${OAUTH2_CLIENT_SECRET}
4854
+ scopes: ${scopes}
4673
4855
  ${rbacSection}
4674
- protectedRoutes:
4856
+ protectedRoutes:
4675
4857
  ${routeLines}
4676
4858
  ${auditSection}
4677
4859
  `;
@@ -4857,35 +5039,35 @@ ${configBlock}
4857
5039
  `;
4858
5040
  }
4859
5041
  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}
5042
+ const rbacSection = `rbac:
5043
+ roles:
5044
+ ${rolesToYaml(roles, " ")}
5045
+ defaultRole: ${defaultRole}
5046
+ hierarchy:
5047
+ ${hierarchyToYaml(hierarchy, " ")}`;
5048
+ const auditSection = `audit:
5049
+ enabled: ${auditEnabled}
5050
+ retentionDays: ${auditRetentionDays}`;
5051
+ const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
5052
+ requiredRole: ${r.requiredRole}`).join("\n");
5053
+ const providerLine = params.provider ? `provider: ${params.provider}
4872
5054
  ` : "";
4873
5055
  if (method === "cac") {
4874
5056
  const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4875
5057
  const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4876
5058
  const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4877
5059
  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}
5060
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5061
+ method: cac
5062
+ devOnly: true
5063
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5064
+ cac:
5065
+ caBundle: ${caBundle}
5066
+ edipiLookup: ${edipiLookup}
5067
+ ocspEndpoint: ${ocspEndpoint}
5068
+ certHeader: ${certHeader}
4887
5069
  ${rbacSection}
4888
- protectedRoutes:
5070
+ protectedRoutes:
4889
5071
  ${routeLines}
4890
5072
  ${auditSection}
4891
5073
  `;
@@ -4893,35 +5075,35 @@ ${auditSection}
4893
5075
  if (method === "oidc") {
4894
5076
  const scopes2 = params.oidcScopes ?? "openid profile email";
4895
5077
  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}
5078
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5079
+ method: oidc
5080
+ devOnly: true
5081
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5082
+ oidc:
5083
+ discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
5084
+ clientId: dev-mock-client
5085
+ clientSecret: dev-mock-secret
5086
+ scopes: ${scopes2}
5087
+ roleClaim: ${roleClaim}
4906
5088
  ${rbacSection}
4907
- protectedRoutes:
5089
+ protectedRoutes:
4908
5090
  ${routeLines}
4909
5091
  ${auditSection}
4910
5092
  `;
4911
5093
  }
4912
5094
  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}
5095
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5096
+ method: oauth2
5097
+ devOnly: true
5098
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5099
+ oauth2:
5100
+ authorizationUrl: https://dev-mock-oauth2/authorize
5101
+ tokenUrl: https://dev-mock-oauth2/token
5102
+ clientId: dev-mock-client
5103
+ clientSecret: dev-mock-secret
5104
+ scopes: ${scopes}
4923
5105
  ${rbacSection}
4924
- protectedRoutes:
5106
+ protectedRoutes:
4925
5107
  ${routeLines}
4926
5108
  ${auditSection}
4927
5109
  `;
@@ -5066,7 +5248,7 @@ async function configureAuthHandler(params, cwd) {
5066
5248
  auditRetentionDays,
5067
5249
  normalizedRoutes
5068
5250
  );
5069
- (0, import_fs7.writeFileSync)((0, import_path7.join)(cwd, conventionFile), authFileContent, "utf8");
5251
+ (0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, conventionFile), authFileContent, "utf8");
5070
5252
  filesWritten.push(conventionFile);
5071
5253
  } catch (err) {
5072
5254
  const msg = err instanceof Error ? err.message : String(err);
@@ -5086,12 +5268,12 @@ async function configureAuthHandler(params, cwd) {
5086
5268
  if (!devOnly) {
5087
5269
  try {
5088
5270
  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");
5271
+ const envPath = (0, import_path8.join)(cwd, ".env.example");
5272
+ if ((0, import_fs8.existsSync)(envPath)) {
5273
+ const existing = (0, import_fs8.readFileSync)(envPath, "utf8");
5274
+ (0, import_fs8.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5093
5275
  } else {
5094
- (0, import_fs7.writeFileSync)(envPath, envBlock, "utf8");
5276
+ (0, import_fs8.writeFileSync)(envPath, envBlock, "utf8");
5095
5277
  }
5096
5278
  filesWritten.push(".env.example");
5097
5279
  } catch (err) {
@@ -5109,10 +5291,10 @@ async function configureAuthHandler(params, cwd) {
5109
5291
  }
5110
5292
  if (devOnly) {
5111
5293
  try {
5112
- const mockAuthDir = (0, import_path7.join)(cwd, "lib");
5113
- (0, import_fs7.mkdirSync)(mockAuthDir, { recursive: true });
5294
+ const mockAuthDir = (0, import_path8.join)(cwd, "lib");
5295
+ (0, import_fs8.mkdirSync)(mockAuthDir, { recursive: true });
5114
5296
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
5115
- (0, import_fs7.writeFileSync)((0, import_path7.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5297
+ (0, import_fs8.writeFileSync)((0, import_path8.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5116
5298
  filesWritten.push("lib/mock-auth.ts");
5117
5299
  } catch (err) {
5118
5300
  const msg = err instanceof Error ? err.message : String(err);
@@ -5130,11 +5312,11 @@ async function configureAuthHandler(params, cwd) {
5130
5312
  };
5131
5313
  }
5132
5314
  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");
5315
+ const pkgPath = (0, import_path8.join)(cwd, "package.json");
5316
+ if ((0, import_fs8.existsSync)(pkgPath)) {
5317
+ const existingPkg = (0, import_fs8.readFileSync)(pkgPath, "utf8");
5136
5318
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
5137
- (0, import_fs7.writeFileSync)(pkgPath, updatedPkg, "utf8");
5319
+ (0, import_fs8.writeFileSync)(pkgPath, updatedPkg, "utf8");
5138
5320
  filesWritten.push("package.json");
5139
5321
  }
5140
5322
  } catch (err) {
@@ -5175,21 +5357,18 @@ async function configureAuthHandler(params, cwd) {
5175
5357
  normalizedRoutes,
5176
5358
  useProxy
5177
5359
  );
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");
5360
+ (0, import_fs8.writeFileSync)((0, import_path8.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
5361
+ filesWritten.push("stackwright.auth.yml");
5186
5362
  } catch (err) {
5187
5363
  const msg = err instanceof Error ? err.message : String(err);
5188
5364
  return {
5189
5365
  content: [
5190
5366
  {
5191
5367
  type: "text",
5192
- text: JSON.stringify({ success: false, error: `Failed writing stackwright.yml: ${msg}` })
5368
+ text: JSON.stringify({
5369
+ success: false,
5370
+ error: `Failed writing stackwright.auth.yml: ${msg}`
5371
+ })
5193
5372
  }
5194
5373
  ],
5195
5374
  isError: true
@@ -5234,7 +5413,7 @@ async function configureAuthHandler(params, cwd) {
5234
5413
  function registerAuthTools(server2) {
5235
5414
  server2.tool(
5236
5415
  "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.",
5416
+ "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
5417
  {
5239
5418
  method: import_zod15.z.enum(["cac", "oidc", "oauth2", "none"]),
5240
5419
  provider: import_zod15.z.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
@@ -5285,28 +5464,28 @@ function registerAuthTools(server2) {
5285
5464
 
5286
5465
  // src/integrity.ts
5287
5466
  var import_crypto4 = require("crypto");
5288
- var import_fs8 = require("fs");
5289
- var import_path8 = require("path");
5467
+ var import_fs9 = require("fs");
5468
+ var import_path9 = require("path");
5290
5469
  var _checksums = /* @__PURE__ */ new Map([
5291
5470
  [
5292
5471
  "stackwright-pro-api-otter.json",
5293
- "df79f4389a576c2885efa07b04f613c60eb8ebf4a8b1d4c7da5e4bb6ee4248dd"
5472
+ "822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
5294
5473
  ],
5295
5474
  [
5296
5475
  "stackwright-pro-auth-otter.json",
5297
- "643344b88e42992e0467845fb0184d3932e115b85130fbc6a47a3175759678c8"
5476
+ "d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
5298
5477
  ],
5299
5478
  [
5300
5479
  "stackwright-pro-dashboard-otter.json",
5301
- "160af221e04200f53709f8c3e249ca6dafb38a325b232fabd478b28f5492ab01"
5480
+ "e3b82555fcffbd77285bd304828814e9f9f7257130797c9de3785de97527668c"
5302
5481
  ],
5303
5482
  [
5304
5483
  "stackwright-pro-data-otter.json",
5305
- "709c8e49328908549fe87de181a5991e09c918ef24bbfe6948f9888f0c758c25"
5484
+ "bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
5306
5485
  ],
5307
5486
  [
5308
5487
  "stackwright-pro-designer-otter.json",
5309
- "1364b2c235c07b0b798e9aab90a68604f60019a5508d41ba576977f173e20cd9"
5488
+ "b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
5310
5489
  ],
5311
5490
  [
5312
5491
  "stackwright-pro-domain-expert-otter.json",
@@ -5314,35 +5493,35 @@ var _checksums = /* @__PURE__ */ new Map([
5314
5493
  ],
5315
5494
  [
5316
5495
  "stackwright-pro-foreman-otter.json",
5317
- "438b03d2c35f64536055c68b3a9044fe3b5e4f2889acde4500dd801fad724be1"
5496
+ "abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
5318
5497
  ],
5319
5498
  [
5320
5499
  "stackwright-pro-form-wizard-otter.json",
5321
- "3dffa3ef2d2ef057578391f784cbea779d768e87d98321894ea5bcd9e3ab7e60"
5500
+ "975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
5322
5501
  ],
5323
5502
  [
5324
5503
  "stackwright-pro-geo-otter.json",
5325
- "2ec83c26a08c413d9553ff8b8f0f0f643c1a8da043741b356e27106cad78f05f"
5504
+ "ac440da786d8c5ab1feddbf861449c3be3a433a04370578e1b2d35bf6ae7a601"
5326
5505
  ],
5327
5506
  [
5328
5507
  "stackwright-pro-page-otter.json",
5329
- "0057ea97f7c2e33a8673140f59a0237eb627d82c676af3fae4faa31033598e03"
5508
+ "b70bd6e5d2a40230f7c24ff4a6113585a1614e5b6a215ece2bd47ff053c30f58"
5330
5509
  ],
5331
5510
  [
5332
5511
  "stackwright-pro-polish-otter.json",
5333
- "bd87327b9a9a62fabaee8837492ce943feae8bfc15e5d43b45ba0e84619daec3"
5512
+ "48ef3cf7f29ac6b5e8ab2004d4d41e2fb899e781689178d3905993241c84095a"
5334
5513
  ],
5335
5514
  [
5336
5515
  "stackwright-pro-scaffold-otter.json",
5337
- "91de5861f1406043d1d387388302fb1492211b81e2eea9777dec60e48f317f2a"
5516
+ "e8662b63bbc9ce38851d94f9656bbebefdb7843d038862adb9fde2c23f9e77c2"
5338
5517
  ],
5339
5518
  [
5340
5519
  "stackwright-pro-theme-otter.json",
5341
- "fe10108e3ba67106751ea662f512bb5f4eb58f7abda26880ef4cf6b0f9feb944"
5520
+ "fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
5342
5521
  ],
5343
5522
  [
5344
5523
  "stackwright-services-otter.json",
5345
- "c013d7fc2475e62d0af54d57bc988182feee7766bac0edf841cbfad5c73e9261"
5524
+ "0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
5346
5525
  ]
5347
5526
  ]);
5348
5527
  Object.freeze(_checksums);
@@ -5364,14 +5543,14 @@ function safeEqual(a, b) {
5364
5543
  return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
5365
5544
  }
5366
5545
  function verifyOtterFile(filePath) {
5367
- const filename = (0, import_path8.basename)(filePath);
5546
+ const filename = (0, import_path9.basename)(filePath);
5368
5547
  const expected = CANONICAL_CHECKSUMS.get(filename);
5369
5548
  if (expected === void 0) {
5370
5549
  return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
5371
5550
  }
5372
5551
  let stat;
5373
5552
  try {
5374
- stat = (0, import_fs8.lstatSync)(filePath);
5553
+ stat = (0, import_fs9.lstatSync)(filePath);
5375
5554
  } catch (err) {
5376
5555
  const msg = err instanceof Error ? err.message : String(err);
5377
5556
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -5389,7 +5568,7 @@ function verifyOtterFile(filePath) {
5389
5568
  }
5390
5569
  let raw;
5391
5570
  try {
5392
- raw = (0, import_fs8.readFileSync)(filePath);
5571
+ raw = (0, import_fs9.readFileSync)(filePath);
5393
5572
  } catch (err) {
5394
5573
  const msg = err instanceof Error ? err.message : String(err);
5395
5574
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -5439,7 +5618,7 @@ function verifyAllOtters(otterDir) {
5439
5618
  const unknown = [];
5440
5619
  let entries;
5441
5620
  try {
5442
- entries = (0, import_fs8.readdirSync)(otterDir);
5621
+ entries = (0, import_fs9.readdirSync)(otterDir);
5443
5622
  } catch (err) {
5444
5623
  const msg = err instanceof Error ? err.message : String(err);
5445
5624
  return {
@@ -5450,9 +5629,9 @@ function verifyAllOtters(otterDir) {
5450
5629
  }
5451
5630
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
5452
5631
  for (const filename of otterFiles) {
5453
- const filePath = (0, import_path8.join)(otterDir, filename);
5632
+ const filePath = (0, import_path9.join)(otterDir, filename);
5454
5633
  try {
5455
- if ((0, import_fs8.lstatSync)(filePath).isSymbolicLink()) {
5634
+ if ((0, import_fs9.lstatSync)(filePath).isSymbolicLink()) {
5456
5635
  failed.push({ filename, error: "Skipped: symlink" });
5457
5636
  continue;
5458
5637
  }
@@ -5478,9 +5657,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
5478
5657
  function resolveOtterDir() {
5479
5658
  const cwd = process.cwd();
5480
5659
  for (const relative of DEFAULT_SEARCH_PATHS) {
5481
- const candidate = (0, import_path8.join)(cwd, relative);
5660
+ const candidate = (0, import_path9.join)(cwd, relative);
5482
5661
  try {
5483
- (0, import_fs8.lstatSync)(candidate);
5662
+ (0, import_fs9.lstatSync)(candidate);
5484
5663
  return candidate;
5485
5664
  } catch {
5486
5665
  }
@@ -5559,13 +5738,13 @@ function registerIntegrityTools(server2) {
5559
5738
 
5560
5739
  // src/tools/domain.ts
5561
5740
  var import_zod16 = require("zod");
5562
- var import_fs9 = require("fs");
5563
- var import_path9 = require("path");
5741
+ var import_fs10 = require("fs");
5742
+ var import_path10 = require("path");
5564
5743
  function handleListCollections(input) {
5565
5744
  const cwd = input._cwd ?? process.cwd();
5566
5745
  const sources = [
5567
5746
  {
5568
- path: (0, import_path9.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
5747
+ path: (0, import_path10.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
5569
5748
  source: "data-config.json",
5570
5749
  parse: (raw) => {
5571
5750
  const parsed = JSON.parse(raw);
@@ -5576,15 +5755,15 @@ function handleListCollections(input) {
5576
5755
  }
5577
5756
  },
5578
5757
  {
5579
- path: (0, import_path9.join)(cwd, "stackwright.yml"),
5758
+ path: (0, import_path10.join)(cwd, "stackwright.yml"),
5580
5759
  source: "stackwright.yml",
5581
5760
  parse: extractCollectionsFromYaml
5582
5761
  }
5583
5762
  ];
5584
- for (const { path: path3, source, parse } of sources) {
5585
- if (!(0, import_fs9.existsSync)(path3)) continue;
5763
+ for (const { path: path4, source, parse } of sources) {
5764
+ if (!(0, import_fs10.existsSync)(path4)) continue;
5586
5765
  try {
5587
- const collections = parse((0, import_fs9.readFileSync)(path3, "utf8"));
5766
+ const collections = parse((0, import_fs10.readFileSync)(path4, "utf8"));
5588
5767
  return {
5589
5768
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
5590
5769
  isError: false
@@ -5735,8 +5914,8 @@ function handleValidateWorkflow(input) {
5735
5914
  if (input.workflow && Object.keys(input.workflow).length > 0) {
5736
5915
  raw = input.workflow;
5737
5916
  } else {
5738
- const artifactPath = (0, import_path9.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
5739
- if (!(0, import_fs9.existsSync)(artifactPath)) {
5917
+ const artifactPath = (0, import_path10.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
5918
+ if (!(0, import_fs10.existsSync)(artifactPath)) {
5740
5919
  return fail([
5741
5920
  {
5742
5921
  code: "NO_WORKFLOW",
@@ -5745,7 +5924,7 @@ function handleValidateWorkflow(input) {
5745
5924
  ]);
5746
5925
  }
5747
5926
  try {
5748
- raw = JSON.parse((0, import_fs9.readFileSync)(artifactPath, "utf8"));
5927
+ raw = JSON.parse((0, import_fs10.readFileSync)(artifactPath, "utf8"));
5749
5928
  } catch (err) {
5750
5929
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
5751
5930
  }
@@ -5857,12 +6036,12 @@ function handleValidateWorkflow(input) {
5857
6036
  return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
5858
6037
  }
5859
6038
  function validateTransitionTargets(step, stepId, stepIds, errors) {
5860
- const check = (target, path3) => {
6039
+ const check = (target, path4) => {
5861
6040
  if (typeof target === "string" && !stepIds.has(target)) {
5862
6041
  errors.push({
5863
6042
  code: "ORPHANED_TRANSITION",
5864
6043
  message: `Step "${stepId}" transitions to "${target}" which does not exist`,
5865
- path: path3
6044
+ path: path4
5866
6045
  });
5867
6046
  }
5868
6047
  };
@@ -5901,11 +6080,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
5901
6080
  }
5902
6081
  function collectServiceWarnings(step, stepId, warnings) {
5903
6082
  const isService = (val) => typeof val === "string" && val.startsWith("service:");
5904
- const warn = (path3) => {
6083
+ const warn = (path4) => {
5905
6084
  warnings.push({
5906
6085
  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
6086
+ message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
6087
+ path: path4
5909
6088
  });
5910
6089
  };
5911
6090
  const onSubmit = step.on_submit;
@@ -6053,8 +6232,8 @@ function registerTypeSchemasTool(server2) {
6053
6232
  }
6054
6233
 
6055
6234
  // src/tools/scaffold-cleanup.ts
6056
- var import_fs10 = require("fs");
6057
- var import_path10 = require("path");
6235
+ var import_fs11 = require("fs");
6236
+ var import_path11 = require("path");
6058
6237
  var SCAFFOLD_FILES_TO_DELETE = [
6059
6238
  "content/posts/getting-started.yaml",
6060
6239
  "content/posts/hello-world.yaml"
@@ -6079,12 +6258,12 @@ var FALLBACK_COLORS = {
6079
6258
  mutedForeground: "#737373"
6080
6259
  };
6081
6260
  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);
6261
+ const tokenPath = (0, import_path11.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6262
+ if (!(0, import_fs11.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
6263
+ const stat = (0, import_fs11.lstatSync)(tokenPath);
6085
6264
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
6086
6265
  try {
6087
- const raw = JSON.parse((0, import_fs10.readFileSync)(tokenPath, "utf-8"));
6266
+ const raw = JSON.parse((0, import_fs11.readFileSync)(tokenPath, "utf-8"));
6088
6267
  const css = raw?.cssVariables ?? {};
6089
6268
  const toHsl = (hslValue) => {
6090
6269
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -6144,15 +6323,15 @@ function generateNotFoundPage(colors) {
6144
6323
  `;
6145
6324
  }
6146
6325
  function isSafePath(fullPath) {
6147
- if (!(0, import_fs10.existsSync)(fullPath)) return true;
6148
- const stat = (0, import_fs10.lstatSync)(fullPath);
6326
+ if (!(0, import_fs11.existsSync)(fullPath)) return true;
6327
+ const stat = (0, import_fs11.lstatSync)(fullPath);
6149
6328
  return !stat.isSymbolicLink();
6150
6329
  }
6151
6330
  function isDirEmpty(dirPath) {
6152
- if (!(0, import_fs10.existsSync)(dirPath)) return false;
6153
- const stat = (0, import_fs10.lstatSync)(dirPath);
6331
+ if (!(0, import_fs11.existsSync)(dirPath)) return false;
6332
+ const stat = (0, import_fs11.lstatSync)(dirPath);
6154
6333
  if (!stat.isDirectory()) return false;
6155
- return (0, import_fs10.readdirSync)(dirPath).length === 0;
6334
+ return (0, import_fs11.readdirSync)(dirPath).length === 0;
6156
6335
  }
6157
6336
  function handleCleanupScaffold(_cwd) {
6158
6337
  const cwd = _cwd ?? process.cwd();
@@ -6165,8 +6344,8 @@ function handleCleanupScaffold(_cwd) {
6165
6344
  cleanupWarnings: []
6166
6345
  };
6167
6346
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
6168
- const fullPath = (0, import_path10.join)(cwd, relPath);
6169
- if (!(0, import_fs10.existsSync)(fullPath)) {
6347
+ const fullPath = (0, import_path11.join)(cwd, relPath);
6348
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6170
6349
  result.skipped.push(relPath);
6171
6350
  continue;
6172
6351
  }
@@ -6175,7 +6354,7 @@ function handleCleanupScaffold(_cwd) {
6175
6354
  continue;
6176
6355
  }
6177
6356
  try {
6178
- (0, import_fs10.unlinkSync)(fullPath);
6357
+ (0, import_fs11.unlinkSync)(fullPath);
6179
6358
  result.deleted.push(relPath);
6180
6359
  } catch (err) {
6181
6360
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -6183,19 +6362,19 @@ function handleCleanupScaffold(_cwd) {
6183
6362
  }
6184
6363
  {
6185
6364
  const relPath = "pages/getting-started/content.yml";
6186
- const fullPath = (0, import_path10.join)(cwd, relPath);
6187
- if (!(0, import_fs10.existsSync)(fullPath)) {
6365
+ const fullPath = (0, import_path11.join)(cwd, relPath);
6366
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6188
6367
  result.skipped.push(relPath);
6189
6368
  } else if (!isSafePath(fullPath)) {
6190
6369
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
6191
6370
  } else {
6192
- const content = (0, import_fs10.readFileSync)(fullPath, "utf-8");
6371
+ const content = (0, import_fs11.readFileSync)(fullPath, "utf-8");
6193
6372
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
6194
6373
  (marker) => content.includes(marker)
6195
6374
  );
6196
6375
  if (isScaffoldDefault) {
6197
6376
  try {
6198
- (0, import_fs10.unlinkSync)(fullPath);
6377
+ (0, import_fs11.unlinkSync)(fullPath);
6199
6378
  result.deleted.push(relPath);
6200
6379
  } catch (err) {
6201
6380
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -6209,21 +6388,21 @@ function handleCleanupScaffold(_cwd) {
6209
6388
  }
6210
6389
  {
6211
6390
  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)) {
6391
+ const fullPath = (0, import_path11.join)(cwd, relPath);
6392
+ const postsDir = (0, import_path11.join)(cwd, "content/posts");
6393
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6215
6394
  result.skipped.push(relPath);
6216
6395
  } else if (!isSafePath(fullPath)) {
6217
6396
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
6218
- } else if (!(0, import_fs10.existsSync)(postsDir)) {
6397
+ } else if (!(0, import_fs11.existsSync)(postsDir)) {
6219
6398
  result.skipped.push(relPath);
6220
6399
  } else {
6221
- const userPostFiles = (0, import_fs10.readdirSync)(postsDir).filter(
6400
+ const userPostFiles = (0, import_fs11.readdirSync)(postsDir).filter(
6222
6401
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
6223
6402
  );
6224
6403
  if (userPostFiles.length === 0) {
6225
6404
  try {
6226
- (0, import_fs10.unlinkSync)(fullPath);
6405
+ (0, import_fs11.unlinkSync)(fullPath);
6227
6406
  result.deleted.push(relPath);
6228
6407
  } catch (err) {
6229
6408
  result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
@@ -6236,15 +6415,15 @@ function handleCleanupScaffold(_cwd) {
6236
6415
  }
6237
6416
  }
6238
6417
  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;
6418
+ const fullDir = (0, import_path11.join)(cwd, relDir);
6419
+ if (!(0, import_fs11.existsSync)(fullDir)) continue;
6241
6420
  if (!isSafePath(fullDir)) {
6242
6421
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
6243
6422
  continue;
6244
6423
  }
6245
6424
  if (isDirEmpty(fullDir)) {
6246
6425
  try {
6247
- (0, import_fs10.rmdirSync)(fullDir);
6426
+ (0, import_fs11.rmdirSync)(fullDir);
6248
6427
  result.prunedDirs.push(relDir);
6249
6428
  } catch (err) {
6250
6429
  result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
@@ -6252,8 +6431,8 @@ function handleCleanupScaffold(_cwd) {
6252
6431
  }
6253
6432
  }
6254
6433
  {
6255
- const fullPath = (0, import_path10.join)(cwd, NOT_FOUND_PATH);
6256
- if (!(0, import_fs10.existsSync)(fullPath)) {
6434
+ const fullPath = (0, import_path11.join)(cwd, NOT_FOUND_PATH);
6435
+ if (!(0, import_fs11.existsSync)(fullPath)) {
6257
6436
  result.skipped.push(NOT_FOUND_PATH);
6258
6437
  } else if (!isSafePath(fullPath)) {
6259
6438
  result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
@@ -6261,9 +6440,9 @@ function handleCleanupScaffold(_cwd) {
6261
6440
  try {
6262
6441
  const colors = readThemeColors(cwd);
6263
6442
  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");
6443
+ const dir = (0, import_path11.dirname)(fullPath);
6444
+ (0, import_fs11.mkdirSync)(dir, { recursive: true });
6445
+ (0, import_fs11.writeFileSync)(fullPath, content, "utf-8");
6267
6446
  result.rewritten.push(NOT_FOUND_PATH);
6268
6447
  } catch (err) {
6269
6448
  result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
@@ -6530,11 +6709,256 @@ function registerContrastTools(server2) {
6530
6709
  );
6531
6710
  }
6532
6711
 
6712
+ // src/tools/compile.ts
6713
+ var import_zod19 = require("zod");
6714
+ var import_fs12 = __toESM(require("fs"));
6715
+ var import_path12 = __toESM(require("path"));
6716
+ var import_server = require("@stackwright-pro/pulse/server");
6717
+ var import_server2 = require("@stackwright-pro/auth-nextjs/server");
6718
+ var import_server3 = require("@stackwright-pro/openapi/server");
6719
+ function buildContext(projectRoot) {
6720
+ return {
6721
+ projectRoot,
6722
+ contentOutDir: import_path12.default.join(projectRoot, "public", "stackwright-content"),
6723
+ imagesDir: import_path12.default.join(projectRoot, "public", "images"),
6724
+ siteConfig: loadSiteConfig(projectRoot)
6725
+ };
6726
+ }
6727
+ function loadSiteConfig(projectRoot) {
6728
+ try {
6729
+ const sitePath = import_path12.default.join(projectRoot, "public", "stackwright-content", "_site.json");
6730
+ return JSON.parse(import_fs12.default.readFileSync(sitePath, "utf8"));
6731
+ } catch {
6732
+ return {};
6733
+ }
6734
+ }
6735
+ function formatDuration(startMs) {
6736
+ const ms = Date.now() - startMs;
6737
+ return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
6738
+ }
6739
+ async function tryOssCompile(fn, ctx, extra) {
6740
+ try {
6741
+ const bsPath = import_path12.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
6742
+ if (!import_fs12.default.existsSync(bsPath)) {
6743
+ return {
6744
+ ok: false,
6745
+ error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
6746
+ };
6747
+ }
6748
+ const bs = await import(bsPath);
6749
+ if (typeof bs[fn] !== "function") {
6750
+ return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
6751
+ }
6752
+ await bs[fn](ctx, ...extra ? [extra] : []);
6753
+ return { ok: true };
6754
+ } catch (err) {
6755
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
6756
+ }
6757
+ }
6758
+ function registerCompileTools(server2) {
6759
+ const projectRootArg = import_zod19.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
6760
+ server2.tool(
6761
+ "stackwright_pro_compile_collections",
6762
+ "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.",
6763
+ { projectRoot: projectRootArg },
6764
+ async ({ projectRoot }) => {
6765
+ const start = Date.now();
6766
+ const root = projectRoot ?? process.cwd();
6767
+ try {
6768
+ await (0, import_server.compileCollections)(buildContext(root));
6769
+ return {
6770
+ content: [
6771
+ {
6772
+ type: "text",
6773
+ text: ` _collections.json written (${formatDuration(start)})`
6774
+ }
6775
+ ]
6776
+ };
6777
+ } catch (err) {
6778
+ return {
6779
+ content: [
6780
+ {
6781
+ type: "text",
6782
+ text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
6783
+ }
6784
+ ]
6785
+ };
6786
+ }
6787
+ }
6788
+ );
6789
+ server2.tool(
6790
+ "stackwright_pro_compile_auth",
6791
+ "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.",
6792
+ { projectRoot: projectRootArg },
6793
+ async ({ projectRoot }) => {
6794
+ const start = Date.now();
6795
+ const root = projectRoot ?? process.cwd();
6796
+ try {
6797
+ await (0, import_server2.compileAuth)(buildContext(root));
6798
+ return {
6799
+ content: [
6800
+ {
6801
+ type: "text",
6802
+ text: ` _auth.json written (${formatDuration(start)})`
6803
+ }
6804
+ ]
6805
+ };
6806
+ } catch (err) {
6807
+ return {
6808
+ content: [
6809
+ {
6810
+ type: "text",
6811
+ text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
6812
+ }
6813
+ ]
6814
+ };
6815
+ }
6816
+ }
6817
+ );
6818
+ server2.tool(
6819
+ "stackwright_pro_compile_integrations",
6820
+ "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).",
6821
+ { projectRoot: projectRootArg },
6822
+ async ({ projectRoot }) => {
6823
+ const start = Date.now();
6824
+ const root = projectRoot ?? process.cwd();
6825
+ try {
6826
+ await (0, import_server3.compileIntegrations)(buildContext(root));
6827
+ return {
6828
+ content: [
6829
+ {
6830
+ type: "text",
6831
+ text: ` _integrations.json written (${formatDuration(start)})`
6832
+ }
6833
+ ]
6834
+ };
6835
+ } catch (err) {
6836
+ return {
6837
+ content: [
6838
+ {
6839
+ type: "text",
6840
+ text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
6841
+ }
6842
+ ]
6843
+ };
6844
+ }
6845
+ }
6846
+ );
6847
+ server2.tool(
6848
+ "stackwright_pro_compile_all",
6849
+ "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.",
6850
+ { projectRoot: projectRootArg },
6851
+ async ({ projectRoot }) => {
6852
+ const start = Date.now();
6853
+ const root = projectRoot ?? process.cwd();
6854
+ const ctx = buildContext(root);
6855
+ const results = [];
6856
+ const errors = [];
6857
+ for (const [label, fn] of [
6858
+ ["collections", () => (0, import_server.compileCollections)(ctx)],
6859
+ ["auth", () => (0, import_server2.compileAuth)(ctx)],
6860
+ ["integrations", () => (0, import_server3.compileIntegrations)(ctx)]
6861
+ ]) {
6862
+ try {
6863
+ await fn();
6864
+ results.push(` _${label}.json`);
6865
+ } catch (err) {
6866
+ errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
6867
+ }
6868
+ }
6869
+ const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
6870
+ ${results.join("\n")}
6871
+ Errors:
6872
+ ${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
6873
+ ${results.join("\n")}`;
6874
+ return { content: [{ type: "text", text: summary }] };
6875
+ }
6876
+ );
6877
+ server2.tool(
6878
+ "stackwright_pro_compile_theme",
6879
+ "Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
6880
+ { projectRoot: projectRootArg },
6881
+ async ({ projectRoot }) => {
6882
+ const start = Date.now();
6883
+ const root = projectRoot ?? process.cwd();
6884
+ const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
6885
+ return {
6886
+ content: [
6887
+ {
6888
+ type: "text",
6889
+ text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
6890
+ }
6891
+ ]
6892
+ };
6893
+ }
6894
+ );
6895
+ server2.tool(
6896
+ "stackwright_pro_compile_site",
6897
+ "Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
6898
+ { projectRoot: projectRootArg },
6899
+ async ({ projectRoot }) => {
6900
+ const start = Date.now();
6901
+ const root = projectRoot ?? process.cwd();
6902
+ const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
6903
+ return {
6904
+ content: [
6905
+ {
6906
+ type: "text",
6907
+ text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
6908
+ }
6909
+ ]
6910
+ };
6911
+ }
6912
+ );
6913
+ server2.tool(
6914
+ "stackwright_pro_compile_pages",
6915
+ "Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
6916
+ { projectRoot: projectRootArg },
6917
+ async ({ projectRoot }) => {
6918
+ const start = Date.now();
6919
+ const root = projectRoot ?? process.cwd();
6920
+ const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
6921
+ return {
6922
+ content: [
6923
+ {
6924
+ type: "text",
6925
+ text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
6926
+ }
6927
+ ]
6928
+ };
6929
+ }
6930
+ );
6931
+ server2.tool(
6932
+ "stackwright_pro_compile_page",
6933
+ "Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
6934
+ {
6935
+ slug: import_zod19.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
6936
+ projectRoot: projectRootArg
6937
+ },
6938
+ async ({ slug, projectRoot }) => {
6939
+ const start = Date.now();
6940
+ const root = projectRoot ?? process.cwd();
6941
+ const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
6942
+ return {
6943
+ content: [
6944
+ {
6945
+ type: "text",
6946
+ text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
6947
+ }
6948
+ ]
6949
+ };
6950
+ }
6951
+ );
6952
+ }
6953
+
6533
6954
  // package.json
6534
6955
  var package_default = {
6535
6956
  dependencies: {
6536
6957
  "@modelcontextprotocol/sdk": "^1.10.0",
6958
+ "@stackwright-pro/auth-nextjs": "workspace:*",
6537
6959
  "@stackwright-pro/cli-data-explorer": "workspace:*",
6960
+ "@stackwright-pro/openapi": "workspace:*",
6961
+ "@stackwright-pro/pulse": "workspace:*",
6538
6962
  "@stackwright-pro/types": "workspace:*",
6539
6963
  "@types/js-yaml": "^4.0.9",
6540
6964
  "js-yaml": "^4.2.0",
@@ -6555,7 +6979,7 @@ var package_default = {
6555
6979
  "test:coverage": "vitest run --coverage"
6556
6980
  },
6557
6981
  name: "@stackwright-pro/mcp",
6558
- version: "0.2.0-alpha.92",
6982
+ version: "0.2.0-alpha.95",
6559
6983
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
6560
6984
  license: "SEE LICENSE IN LICENSE",
6561
6985
  main: "./dist/server.js",
@@ -6613,7 +7037,22 @@ registerValidateYamlFragmentTool(server);
6613
7037
  registerGetSchemaTool(server);
6614
7038
  registerScaffoldCleanupTools(server);
6615
7039
  registerContrastTools(server);
7040
+ registerCompileTools(server);
6616
7041
  async function main() {
7042
+ const pipelineGraph = loadPipelineGraph();
7043
+ const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
7044
+ for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
7045
+ const phaseIdx = phasePosition.get(phase);
7046
+ if (phaseIdx === void 0) continue;
7047
+ for (const dep of deps) {
7048
+ const depIdx = phasePosition.get(dep);
7049
+ if (depIdx !== void 0 && depIdx >= phaseIdx) {
7050
+ throw new Error(
7051
+ `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.`
7052
+ );
7053
+ }
7054
+ }
7055
+ }
6617
7056
  const transport = new import_stdio.StdioServerTransport();
6618
7057
  try {
6619
7058
  const servicesRegisterPkg = "@stackwright-services/mcp/register";