@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.mjs CHANGED
@@ -120,8 +120,8 @@ function registerDataExplorerTools(server2) {
120
120
  lines.push(" endpoints:");
121
121
  if (result.filter.include && result.filter.include.length > 0) {
122
122
  lines.push(" include:");
123
- for (const path3 of result.filter.include) {
124
- lines.push(` - ${path3}`);
123
+ for (const path4 of result.filter.include) {
124
+ lines.push(` - ${path4}`);
125
125
  }
126
126
  }
127
127
  if (result.filter.exclude && result.filter.exclude.length > 0) {
@@ -1870,8 +1870,8 @@ function registerOrchestrationTools(server2) {
1870
1870
  {
1871
1871
  buildContext: z9.string().describe("Free-text description of what the user wants to build")
1872
1872
  },
1873
- async ({ buildContext }) => {
1874
- const { text, isError } = handleSaveBuildContext({ buildContext });
1873
+ async ({ buildContext: buildContext2 }) => {
1874
+ const { text, isError } = handleSaveBuildContext({ buildContext: buildContext2 });
1875
1875
  return {
1876
1876
  content: [{ type: "text", text }],
1877
1877
  isError
@@ -1999,8 +1999,8 @@ function registerOrchestrationTools(server2) {
1999
1999
 
2000
2000
  // src/tools/pipeline.ts
2001
2001
  import { z as z13 } from "zod";
2002
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, lstatSync as lstatSync5 } from "fs";
2003
- import { join as join4 } from "path";
2002
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, lstatSync as lstatSync6 } from "fs";
2003
+ import { join as join5 } from "path";
2004
2004
  import { createHash as createHash3 } from "crypto";
2005
2005
 
2006
2006
  // src/artifact-signing.ts
@@ -2690,6 +2690,223 @@ function registerGetSchemaTool(server2) {
2690
2690
  );
2691
2691
  }
2692
2692
 
2693
+ // src/tools/pipeline-graph.ts
2694
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, lstatSync as lstatSync5 } from "fs";
2695
+ import { join as join4 } from "path";
2696
+ var MANIFEST_NAME_TO_PHASE = {
2697
+ "stackwright-pro-designer-otter": "designer",
2698
+ "stackwright-pro-theme-otter": "theme",
2699
+ "stackwright-pro-scaffold-otter": "scaffold",
2700
+ "stackwright-pro-api-otter": "api",
2701
+ "stackwright-pro-auth-otter": "auth",
2702
+ "stackwright-pro-data-otter": "data",
2703
+ "stackwright-pro-page-otter": "pages",
2704
+ "stackwright-pro-dashboard-otter": "dashboard",
2705
+ "stackwright-pro-form-wizard-otter": "workflow",
2706
+ "stackwright-pro-geo-otter": "geo",
2707
+ "stackwright-pro-polish-otter": "polish",
2708
+ "stackwright-services-otter": "services"
2709
+ };
2710
+ function manifestNameToPhase(name) {
2711
+ return MANIFEST_NAME_TO_PHASE[name] ?? null;
2712
+ }
2713
+ var OTTER_SEARCH_PATHS = [
2714
+ "node_modules/@stackwright-pro/otters/src/",
2715
+ // production: installed package
2716
+ "packages/otters/src/",
2717
+ // monorepo: run from repo root
2718
+ "../otters/src/"
2719
+ // monorepo: run from packages/mcp/
2720
+ ];
2721
+ function resolveOtterDirFromCwd() {
2722
+ const cwd = process.cwd();
2723
+ for (const relative of OTTER_SEARCH_PATHS) {
2724
+ const candidate = join4(cwd, relative);
2725
+ try {
2726
+ lstatSync5(candidate);
2727
+ return candidate;
2728
+ } catch {
2729
+ }
2730
+ }
2731
+ throw new Error(
2732
+ `Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
2733
+ );
2734
+ }
2735
+ function loadOtterManifestsFromDir(otterDir) {
2736
+ const entries = readdirSync2(otterDir);
2737
+ const manifests = [];
2738
+ for (const filename of entries) {
2739
+ if (!filename.endsWith("-otter.json")) continue;
2740
+ const filePath = join4(otterDir, filename);
2741
+ if (lstatSync5(filePath).isSymbolicLink()) continue;
2742
+ try {
2743
+ const raw = readFileSync4(filePath, "utf-8");
2744
+ const manifest = JSON.parse(raw);
2745
+ manifests.push(manifest);
2746
+ } catch (err) {
2747
+ const msg = err instanceof Error ? err.message : String(err);
2748
+ throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
2749
+ }
2750
+ }
2751
+ return manifests;
2752
+ }
2753
+ function topologicalSort(dependencies) {
2754
+ const phases = Object.keys(dependencies);
2755
+ const inDegree = {};
2756
+ const adjList = {};
2757
+ for (const phase of phases) {
2758
+ inDegree[phase] ??= 0;
2759
+ adjList[phase] ??= [];
2760
+ }
2761
+ for (const phase of phases) {
2762
+ for (const dep of dependencies[phase] ?? []) {
2763
+ inDegree[phase] = (inDegree[phase] ?? 0) + 1;
2764
+ adjList[dep] ??= [];
2765
+ adjList[dep].push(phase);
2766
+ }
2767
+ }
2768
+ const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
2769
+ const result = [];
2770
+ while (queue.length > 0) {
2771
+ const node = queue.shift();
2772
+ result.push(node);
2773
+ for (const dependent of adjList[node] ?? []) {
2774
+ inDegree[dependent] -= 1;
2775
+ if (inDegree[dependent] === 0) {
2776
+ queue.push(dependent);
2777
+ }
2778
+ }
2779
+ }
2780
+ if (result.length !== phases.length) {
2781
+ const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
2782
+ throw new Error(
2783
+ `Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
2784
+ );
2785
+ }
2786
+ return result;
2787
+ }
2788
+ function buildPipelineGraph(manifests) {
2789
+ const sinkProducers = {};
2790
+ const artifactProducers = {};
2791
+ for (const m of manifests) {
2792
+ const phase = manifestNameToPhase(m.name);
2793
+ if (!phase) continue;
2794
+ const out = m.pipeline?.outputs;
2795
+ if (!out) continue;
2796
+ for (const sink of out.sinks ?? []) {
2797
+ if (sinkProducers[sink]) {
2798
+ throw new Error(
2799
+ `Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
2800
+ );
2801
+ }
2802
+ sinkProducers[sink] = phase;
2803
+ }
2804
+ if (out.artifact) {
2805
+ if (artifactProducers[out.artifact]) {
2806
+ throw new Error(
2807
+ `Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
2808
+ );
2809
+ }
2810
+ artifactProducers[out.artifact] = phase;
2811
+ }
2812
+ }
2813
+ const dependencies = {};
2814
+ for (const m of manifests) {
2815
+ const phase = manifestNameToPhase(m.name);
2816
+ if (!phase) continue;
2817
+ const deps = /* @__PURE__ */ new Set();
2818
+ const inp = m.pipeline?.inputs;
2819
+ if (inp) {
2820
+ for (const sink of inp.sinks ?? []) {
2821
+ const producer = sinkProducers[sink];
2822
+ if (!producer) {
2823
+ throw new Error(
2824
+ `Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
2825
+ );
2826
+ }
2827
+ if (producer !== phase) deps.add(producer);
2828
+ }
2829
+ for (const artifact of inp.artifacts ?? []) {
2830
+ const producer = artifactProducers[artifact];
2831
+ if (!producer) {
2832
+ throw new Error(
2833
+ `Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
2834
+ );
2835
+ }
2836
+ if (producer !== phase) deps.add(producer);
2837
+ }
2838
+ }
2839
+ dependencies[phase] = Array.from(deps);
2840
+ }
2841
+ const order = topologicalSort(dependencies);
2842
+ return { dependencies, order, sinkProducers, artifactProducers };
2843
+ }
2844
+ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
2845
+ function loadDistributedOtterManifests(cwd) {
2846
+ const results = [];
2847
+ for (const namespace of DISTRIBUTED_NAMESPACES) {
2848
+ const nsDir = join4(cwd, "node_modules", namespace);
2849
+ let pkgDirNames;
2850
+ try {
2851
+ pkgDirNames = readdirSync2(nsDir);
2852
+ } catch {
2853
+ continue;
2854
+ }
2855
+ for (const pkgDirName of pkgDirNames) {
2856
+ const pkgDir = join4(nsDir, pkgDirName);
2857
+ const packageName = `${namespace}/${pkgDirName}`;
2858
+ let otterRelPaths;
2859
+ try {
2860
+ const raw = readFileSync4(join4(pkgDir, "package.json"), "utf-8");
2861
+ const pkgJson = JSON.parse(raw);
2862
+ const declared = pkgJson.stackwright?.otters;
2863
+ if (!Array.isArray(declared) || declared.length === 0) continue;
2864
+ otterRelPaths = declared;
2865
+ } catch {
2866
+ continue;
2867
+ }
2868
+ for (const relPath of otterRelPaths) {
2869
+ const otterFilePath = join4(pkgDir, relPath);
2870
+ try {
2871
+ const raw = readFileSync4(otterFilePath, "utf-8");
2872
+ const manifest = JSON.parse(raw);
2873
+ results.push({ manifest, packageName });
2874
+ } catch (err) {
2875
+ const msg = err instanceof Error ? err.message : String(err);
2876
+ console.warn(
2877
+ `[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
2878
+ );
2879
+ }
2880
+ }
2881
+ }
2882
+ }
2883
+ return results;
2884
+ }
2885
+ function _mergeManifestPools(central, distributed) {
2886
+ const centralNames = new Set(central.map((m) => m.name));
2887
+ const merged = [...central];
2888
+ for (const { manifest, packageName } of distributed) {
2889
+ if (centralNames.has(manifest.name)) {
2890
+ console.warn(
2891
+ `[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
2892
+ );
2893
+ } else {
2894
+ merged.push(manifest);
2895
+ }
2896
+ }
2897
+ return merged;
2898
+ }
2899
+ var _cachedGraph = null;
2900
+ function loadPipelineGraph() {
2901
+ if (_cachedGraph) return _cachedGraph;
2902
+ const otterDir = resolveOtterDirFromCwd();
2903
+ const central = loadOtterManifestsFromDir(otterDir);
2904
+ const distributed = loadDistributedOtterManifests(process.cwd());
2905
+ const manifests = _mergeManifestPools(central, distributed);
2906
+ _cachedGraph = buildPipelineGraph(manifests);
2907
+ return _cachedGraph;
2908
+ }
2909
+
2693
2910
  // src/tools/pipeline.ts
2694
2911
  var PHASE_ORDER = [
2695
2912
  "designer",
@@ -2698,45 +2915,15 @@ var PHASE_ORDER = [
2698
2915
  // generates app/ directory from config
2699
2916
  "api",
2700
2917
  "data",
2918
+ "auth",
2919
+ // moved earlier — only depends on design-language.json (designer)
2701
2920
  "geo",
2702
2921
  "workflow",
2703
2922
  "services",
2704
2923
  "pages",
2705
2924
  "dashboard",
2706
- "auth",
2707
2925
  "polish"
2708
2926
  ];
2709
- var PHASE_DEPENDENCIES = {
2710
- designer: [],
2711
- theme: ["designer"],
2712
- scaffold: ["designer", "theme"],
2713
- // needs stackwright.yml + theme-tokens
2714
- api: [],
2715
- data: ["api"],
2716
- geo: ["data"],
2717
- // workflow: no hard deps — uses service: references with Prism mock fallback
2718
- // when API artifacts aren't available; roles come from user answers
2719
- workflow: [],
2720
- // services: needs API endpoints to know what to wire, and optionally
2721
- // workflow states as trigger sources. Produces OpenAPI specs consumed
2722
- // by pages and dashboard for typed data wiring.
2723
- services: ["api", "workflow"],
2724
- // pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
2725
- // specs) and on geo so the specialist prompt includes geo-manifest.json
2726
- // — page/dashboard otters see which page slugs are already claimed and
2727
- // won't attempt to overwrite them (swp-73c). 'api' is still transitive
2728
- // through 'data'; auth removed — page-otter has graceful fallback.
2729
- pages: ["designer", "theme", "data", "services", "geo", "scaffold"],
2730
- dashboard: ["designer", "theme", "data", "services", "geo", "scaffold"],
2731
- // auth is the penultimate phase — runs after all content-producing phases
2732
- // so it can read pages, dashboard, workflow, and geo artifacts for route
2733
- // protection and RBAC wiring. Skipped upstream phases still satisfy deps
2734
- // (the foreman marks them executed=true), so auth always runs.
2735
- auth: ["pages", "dashboard", "workflow", "geo"],
2736
- // polish is the terminal phase — runs after auth so the landing page and
2737
- // nav reflect the final set of protected routes and all generated pages.
2738
- polish: ["auth"]
2739
- };
2740
2927
  var PHASE_ARTIFACT = {
2741
2928
  designer: "design-language.json",
2742
2929
  theme: "theme-tokens.json",
@@ -2793,7 +2980,7 @@ function createDefaultState() {
2793
2980
  };
2794
2981
  }
2795
2982
  function statePath(cwd) {
2796
- return join4(cwd, ".stackwright", "pipeline-state.json");
2983
+ return join5(cwd, ".stackwright", "pipeline-state.json");
2797
2984
  }
2798
2985
  function readState(cwd) {
2799
2986
  const p = statePath(cwd);
@@ -2806,7 +2993,7 @@ function readState(cwd) {
2806
2993
  }
2807
2994
  function safeWriteSync(filePath, content) {
2808
2995
  if (existsSync5(filePath)) {
2809
- const stat = lstatSync5(filePath);
2996
+ const stat = lstatSync6(filePath);
2810
2997
  if (stat.isSymbolicLink()) {
2811
2998
  throw new Error(`Refusing to write to symlink: ${filePath}`);
2812
2999
  }
@@ -2815,15 +3002,15 @@ function safeWriteSync(filePath, content) {
2815
3002
  }
2816
3003
  function safeReadSync(filePath) {
2817
3004
  if (existsSync5(filePath)) {
2818
- const stat = lstatSync5(filePath);
3005
+ const stat = lstatSync6(filePath);
2819
3006
  if (stat.isSymbolicLink()) {
2820
3007
  throw new Error(`Refusing to read symlink: ${filePath}`);
2821
3008
  }
2822
3009
  }
2823
- return readFileSync4(filePath, "utf-8");
3010
+ return readFileSync5(filePath, "utf-8");
2824
3011
  }
2825
3012
  function writeState(cwd, state) {
2826
- const dir = join4(cwd, ".stackwright");
3013
+ const dir = join5(cwd, ".stackwright");
2827
3014
  mkdirSync4(dir, { recursive: true });
2828
3015
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2829
3016
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
@@ -2845,7 +3032,7 @@ function handleGetPipelineState(_cwd) {
2845
3032
  const cwd = _cwd ?? process.cwd();
2846
3033
  try {
2847
3034
  const state = readState(cwd);
2848
- const keyPath = join4(cwd, ".stackwright", "pipeline-keys.json");
3035
+ const keyPath = join5(cwd, ".stackwright", "pipeline-keys.json");
2849
3036
  if (!existsSync5(keyPath)) {
2850
3037
  try {
2851
3038
  const { fingerprint } = initPipelineKeys(cwd);
@@ -2949,7 +3136,7 @@ function handleCheckExecutionReady(_cwd, phase) {
2949
3136
  isError: true
2950
3137
  };
2951
3138
  }
2952
- const answerFile = join4(cwd, ".stackwright", "answers", `${phase}.json`);
3139
+ const answerFile = join5(cwd, ".stackwright", "answers", `${phase}.json`);
2953
3140
  if (!existsSync5(answerFile)) {
2954
3141
  return {
2955
3142
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
@@ -2974,11 +3161,11 @@ function handleCheckExecutionReady(_cwd, phase) {
2974
3161
  }
2975
3162
  }
2976
3163
  try {
2977
- const answersDir = join4(cwd, ".stackwright", "answers");
3164
+ const answersDir = join5(cwd, ".stackwright", "answers");
2978
3165
  const answeredPhases = [];
2979
3166
  const missingPhases = [];
2980
3167
  for (const phase2 of PHASE_ORDER) {
2981
- const answerFile = join4(answersDir, `${phase2}.json`);
3168
+ const answerFile = join5(answersDir, `${phase2}.json`);
2982
3169
  if (existsSync5(answerFile)) {
2983
3170
  try {
2984
3171
  const raw = safeReadSync(answerFile);
@@ -3012,6 +3199,7 @@ function handleGetReadyPhases(_cwd) {
3012
3199
  const cwd = _cwd ?? process.cwd();
3013
3200
  try {
3014
3201
  const state = readState(cwd);
3202
+ const graph = loadPipelineGraph();
3015
3203
  const completed = [];
3016
3204
  const ready = [];
3017
3205
  const blocked = [];
@@ -3021,7 +3209,7 @@ function handleGetReadyPhases(_cwd) {
3021
3209
  completed.push(phase);
3022
3210
  continue;
3023
3211
  }
3024
- const deps = PHASE_DEPENDENCIES[phase];
3212
+ const deps = graph.dependencies[phase] ?? [];
3025
3213
  const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
3026
3214
  if (unmetDeps.length === 0) {
3027
3215
  ready.push(phase);
@@ -3047,7 +3235,7 @@ function handleGetReadyPhases(_cwd) {
3047
3235
  function handleListArtifacts(_cwd) {
3048
3236
  const cwd = _cwd ?? process.cwd();
3049
3237
  try {
3050
- const artifactsDir = join4(cwd, ".stackwright", "artifacts");
3238
+ const artifactsDir = join5(cwd, ".stackwright", "artifacts");
3051
3239
  let manifest = null;
3052
3240
  try {
3053
3241
  manifest = loadSignatureManifest(cwd);
@@ -3057,7 +3245,7 @@ function handleListArtifacts(_cwd) {
3057
3245
  let completedCount = 0;
3058
3246
  for (const phase of PHASE_ORDER) {
3059
3247
  const expectedFile = PHASE_ARTIFACT[phase];
3060
- const fullPath = join4(artifactsDir, expectedFile);
3248
+ const fullPath = join5(artifactsDir, expectedFile);
3061
3249
  const exists = existsSync5(fullPath);
3062
3250
  if (exists) completedCount++;
3063
3251
  let signed = false;
@@ -3111,9 +3299,9 @@ function handleWritePhaseQuestions(input) {
3111
3299
  }
3112
3300
  } catch {
3113
3301
  }
3114
- const questionsDir = join4(cwd, ".stackwright", "questions");
3302
+ const questionsDir = join5(cwd, ".stackwright", "questions");
3115
3303
  mkdirSync4(questionsDir, { recursive: true });
3116
- const filePath = join4(questionsDir, `${phase}.json`);
3304
+ const filePath = join5(questionsDir, `${phase}.json`);
3117
3305
  const payload = {
3118
3306
  version: "1.0",
3119
3307
  phase,
@@ -3153,13 +3341,13 @@ function handleBuildSpecialistPrompt(input) {
3153
3341
  };
3154
3342
  }
3155
3343
  try {
3156
- const answersPath = join4(cwd, ".stackwright", "answers", `${phase}.json`);
3344
+ const answersPath = join5(cwd, ".stackwright", "answers", `${phase}.json`);
3157
3345
  let answers = {};
3158
3346
  if (existsSync5(answersPath)) {
3159
3347
  answers = JSON.parse(safeReadSync(answersPath));
3160
3348
  }
3161
3349
  let buildContextText = "";
3162
- const buildContextPath = join4(cwd, ".stackwright", "build-context.json");
3350
+ const buildContextPath = join5(cwd, ".stackwright", "build-context.json");
3163
3351
  if (existsSync5(buildContextPath)) {
3164
3352
  try {
3165
3353
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
@@ -3169,12 +3357,13 @@ function handleBuildSpecialistPrompt(input) {
3169
3357
  } catch {
3170
3358
  }
3171
3359
  }
3172
- const deps = PHASE_DEPENDENCIES[phase];
3360
+ const graph = loadPipelineGraph();
3361
+ const deps = graph.dependencies[phase] ?? [];
3173
3362
  const artifactSections = [];
3174
3363
  const missingDependencies = [];
3175
3364
  for (const dep of deps) {
3176
3365
  const artifactFile = PHASE_ARTIFACT[dep];
3177
- const artifactPath = join4(cwd, ".stackwright", "artifacts", artifactFile);
3366
+ const artifactPath = join5(cwd, ".stackwright", "artifacts", artifactFile);
3178
3367
  if (existsSync5(artifactPath)) {
3179
3368
  const rawContent = safeReadSync(artifactPath);
3180
3369
  const rawBytes = Buffer.from(rawContent, "utf-8");
@@ -3240,7 +3429,7 @@ ${JSON.stringify(content, null, 2)}`
3240
3429
  parts.push("BUILD_CONTEXT:", buildContextText, "");
3241
3430
  }
3242
3431
  if (phase === "auth") {
3243
- const initContextPath = join4(cwd, ".stackwright", "init-context.json");
3432
+ const initContextPath = join5(cwd, ".stackwright", "init-context.json");
3244
3433
  if (existsSync5(initContextPath)) {
3245
3434
  try {
3246
3435
  const initContext = JSON.parse(safeReadSync(initContextPath));
@@ -3708,13 +3897,13 @@ function handleValidateArtifact(input) {
3708
3897
  // to a flow or state-machine in this services-config artifact.
3709
3898
  // Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
3710
3899
  services: (artifact2) => {
3711
- const workflowArtifactPath = join4(cwd, ".stackwright", "artifacts", "workflow-config.json");
3900
+ const workflowArtifactPath = join5(cwd, ".stackwright", "artifacts", "workflow-config.json");
3712
3901
  if (!existsSync5(workflowArtifactPath)) {
3713
3902
  return { success: true };
3714
3903
  }
3715
3904
  let workflowArtifact;
3716
3905
  try {
3717
- workflowArtifact = JSON.parse(readFileSync4(workflowArtifactPath, "utf-8"));
3906
+ workflowArtifact = JSON.parse(readFileSync5(workflowArtifactPath, "utf-8"));
3718
3907
  } catch {
3719
3908
  return { success: true };
3720
3909
  }
@@ -3751,10 +3940,10 @@ function handleValidateArtifact(input) {
3751
3940
  }
3752
3941
  }
3753
3942
  try {
3754
- const artifactsDir = join4(cwd, ".stackwright", "artifacts");
3943
+ const artifactsDir = join5(cwd, ".stackwright", "artifacts");
3755
3944
  mkdirSync4(artifactsDir, { recursive: true });
3756
3945
  const artifactFile = PHASE_ARTIFACT[phase];
3757
- const artifactPath = join4(artifactsDir, artifactFile);
3946
+ const artifactPath = join5(artifactsDir, artifactFile);
3758
3947
  const serialized = JSON.stringify(artifact, null, 2) + "\n";
3759
3948
  const artifactBytes = Buffer.from(serialized, "utf-8");
3760
3949
  safeWriteSync(artifactPath, serialized);
@@ -3871,9 +4060,9 @@ function registerPipelineTools(server2) {
3871
4060
  isError: true
3872
4061
  };
3873
4062
  }
3874
- const questionsDir = join4(process.cwd(), ".stackwright", "questions");
4063
+ const questionsDir = join5(process.cwd(), ".stackwright", "questions");
3875
4064
  mkdirSync4(questionsDir, { recursive: true });
3876
- const outPath = join4(questionsDir, `${phase}.json`);
4065
+ const outPath = join5(questionsDir, `${phase}.json`);
3877
4066
  writeFileSync4(
3878
4067
  outPath,
3879
4068
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
@@ -3925,8 +4114,8 @@ function registerPipelineTools(server2) {
3925
4114
 
3926
4115
  // src/tools/safe-write.ts
3927
4116
  import { z as z14 } from "zod";
3928
- import { writeFileSync as writeFileSync5, readFileSync as readFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
3929
- import { normalize, isAbsolute, dirname, join as join5 } from "path";
4117
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
4118
+ import { normalize, isAbsolute, dirname, join as join6 } from "path";
3930
4119
  var OTTER_WRITE_ALLOWLISTS = {
3931
4120
  "stackwright-pro-designer-otter": [
3932
4121
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
@@ -3941,8 +4130,11 @@ var OTTER_WRITE_ALLOWLISTS = {
3941
4130
  ],
3942
4131
  "stackwright-pro-auth-otter": [
3943
4132
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
3944
- { prefix: "config/", suffix: ".yml", description: "Auth YAML config" },
3945
- { prefix: "config/", suffix: ".yaml", description: "Auth YAML config" },
4133
+ {
4134
+ prefix: "stackwright.auth.",
4135
+ suffix: ".yml",
4136
+ description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
4137
+ },
3946
4138
  {
3947
4139
  prefix: ".env",
3948
4140
  suffix: "",
@@ -3961,7 +4153,11 @@ var OTTER_WRITE_ALLOWLISTS = {
3961
4153
  ],
3962
4154
  "stackwright-pro-data-otter": [
3963
4155
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
3964
- { prefix: "stackwright.yml", suffix: "", description: "Stackwright config" }
4156
+ {
4157
+ prefix: "stackwright.collections.",
4158
+ suffix: ".yml",
4159
+ description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
4160
+ }
3965
4161
  ],
3966
4162
  "stackwright-pro-page-otter": [
3967
4163
  { prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
@@ -3987,7 +4183,12 @@ var OTTER_WRITE_ALLOWLISTS = {
3987
4183
  }
3988
4184
  ],
3989
4185
  "stackwright-pro-api-otter": [
3990
- { prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
4186
+ { prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
4187
+ {
4188
+ prefix: "stackwright.integrations.",
4189
+ suffix: ".yml",
4190
+ description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
4191
+ }
3991
4192
  ],
3992
4193
  "stackwright-pro-geo-otter": [
3993
4194
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
@@ -4066,22 +4267,22 @@ function extractContentTypes(yamlContent) {
4066
4267
  return types.length > 0 ? types : ["unknown"];
4067
4268
  }
4068
4269
  function readPageRegistry(cwd) {
4069
- const regPath = join5(cwd, PAGE_REGISTRY_FILE);
4270
+ const regPath = join6(cwd, PAGE_REGISTRY_FILE);
4070
4271
  if (!existsSync6(regPath)) return {};
4071
4272
  try {
4072
- const stat = lstatSync6(regPath);
4273
+ const stat = lstatSync7(regPath);
4073
4274
  if (stat.isSymbolicLink()) return {};
4074
- return JSON.parse(readFileSync5(regPath, "utf-8"));
4275
+ return JSON.parse(readFileSync6(regPath, "utf-8"));
4075
4276
  } catch {
4076
4277
  return {};
4077
4278
  }
4078
4279
  }
4079
4280
  function writePageRegistry(cwd, registry) {
4080
- const dir = join5(cwd, ".stackwright");
4281
+ const dir = join6(cwd, ".stackwright");
4081
4282
  mkdirSync5(dir, { recursive: true });
4082
- const regPath = join5(cwd, PAGE_REGISTRY_FILE);
4283
+ const regPath = join6(cwd, PAGE_REGISTRY_FILE);
4083
4284
  if (existsSync6(regPath)) {
4084
- const stat = lstatSync6(regPath);
4285
+ const stat = lstatSync7(regPath);
4085
4286
  if (stat.isSymbolicLink()) {
4086
4287
  throw new Error("Refusing to write page registry through symlink");
4087
4288
  }
@@ -4101,8 +4302,8 @@ function checkPageOwnership(cwd, slug, callerOtter) {
4101
4302
  };
4102
4303
  }
4103
4304
  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;
4104
- function stripNextjsBracketSegments(path3) {
4105
- return path3.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
4305
+ function stripNextjsBracketSegments(path4) {
4306
+ return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
4106
4307
  }
4107
4308
  function checkPathAllowed(callerOtter, filePath) {
4108
4309
  const normalized = normalize(filePath);
@@ -4252,10 +4453,10 @@ function handleSafeWrite(input) {
4252
4453
  return { text: JSON.stringify(result), isError: true };
4253
4454
  }
4254
4455
  const normalized = normalize(filePath);
4255
- const fullPath = join5(cwd, normalized);
4456
+ const fullPath = join6(cwd, normalized);
4256
4457
  if (existsSync6(fullPath)) {
4257
4458
  try {
4258
- const stat = lstatSync6(fullPath);
4459
+ const stat = lstatSync7(fullPath);
4259
4460
  if (stat.isSymbolicLink()) {
4260
4461
  const result = {
4261
4462
  success: false,
@@ -4358,8 +4559,8 @@ function registerSafeWriteTools(server2) {
4358
4559
 
4359
4560
  // src/tools/auth.ts
4360
4561
  import { z as z15 } from "zod";
4361
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
4362
- import { join as join6 } from "path";
4562
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
4563
+ import { join as join7 } from "path";
4363
4564
  function buildHierarchy(roles) {
4364
4565
  const h = {};
4365
4566
  for (let i = 0; i < roles.length - 1; i++) {
@@ -4373,7 +4574,7 @@ function hierarchyToYaml(hierarchy, indent) {
4373
4574
  return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
4374
4575
  }
4375
4576
  function rolesToYaml(roles, indent) {
4376
- return roles.map((r) => `${indent}- ${r}`).join("\n");
4577
+ return roles.map((r) => `${indent}- name: ${r}`).join("\n");
4377
4578
  }
4378
4579
  function normalizeRoutes(routes, defaultRole) {
4379
4580
  return routes.map(
@@ -4386,9 +4587,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
4386
4587
  }
4387
4588
  function detectNextMajorVersion(cwd) {
4388
4589
  try {
4389
- const pkgPath = join6(cwd, "package.json");
4590
+ const pkgPath = join7(cwd, "package.json");
4390
4591
  if (!existsSync7(pkgPath)) return null;
4391
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
4592
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
4392
4593
  const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
4393
4594
  if (!nextVersion) return null;
4394
4595
  const match = nextVersion.match(/(\d+)/);
@@ -4397,25 +4598,6 @@ function detectNextMajorVersion(cwd) {
4397
4598
  return null;
4398
4599
  }
4399
4600
  }
4400
- function upsertAuthBlock(existing, authYaml) {
4401
- const lines = existing.split("\n");
4402
- let authStart = -1;
4403
- let authEnd = lines.length;
4404
- for (let i = 0; i < lines.length; i++) {
4405
- if (/^auth:/.test(lines[i])) {
4406
- authStart = i;
4407
- } else if (authStart >= 0 && i > authStart && /^\S/.test(lines[i]) && lines[i].trim() !== "") {
4408
- authEnd = i;
4409
- break;
4410
- }
4411
- }
4412
- if (authStart < 0) {
4413
- return existing.trimEnd() + "\n" + authYaml + "\n";
4414
- }
4415
- const before = lines.slice(0, authStart);
4416
- const after = lines.slice(authEnd);
4417
- return [...before, ...authYaml.trimEnd().split("\n"), ...after.length ? after : []].join("\n");
4418
- }
4419
4601
  function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
4420
4602
  const rbacBlock = ` rbac: {
4421
4603
  roles: ${JSON.stringify(roles)},
@@ -4615,33 +4797,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
4615
4797
  `;
4616
4798
  }
4617
4799
  function generateYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
4618
- const rbacSection = ` rbac:
4619
- roles:
4620
- ${rolesToYaml(roles, " ")}
4621
- defaultRole: ${defaultRole}
4622
- hierarchy:
4623
- ${hierarchyToYaml(hierarchy, " ")}`;
4624
- const auditSection = ` audit:
4625
- enabled: ${auditEnabled}
4626
- retentionDays: ${auditRetentionDays}`;
4627
- const routeLines = routesToYaml(protectedRoutes, " ");
4628
- const providerLine = params.provider ? ` provider: ${params.provider}
4800
+ const rbacSection = `rbac:
4801
+ roles:
4802
+ ${rolesToYaml(roles, " ")}
4803
+ defaultRole: ${defaultRole}
4804
+ hierarchy:
4805
+ ${hierarchyToYaml(hierarchy, " ")}`;
4806
+ const auditSection = `audit:
4807
+ enabled: ${auditEnabled}
4808
+ retentionDays: ${auditRetentionDays}`;
4809
+ const routeLines = routesToYaml(protectedRoutes, " ");
4810
+ const providerLine = params.provider ? `provider: ${params.provider}
4629
4811
  ` : "";
4630
4812
  if (method === "cac") {
4631
4813
  const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4632
4814
  const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4633
4815
  const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4634
4816
  const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
4635
- return `auth:
4636
- method: cac
4637
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4638
- cac:
4639
- caBundle: \${CAC_CA_BUNDLE}
4640
- edipiLookup: ${edipiLookup}
4641
- ocspEndpoint: \${CAC_OCSP_ENDPOINT}
4642
- certHeader: ${certHeader}
4817
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4818
+ method: cac
4819
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4820
+ cac:
4821
+ caBundle: \${CAC_CA_BUNDLE}
4822
+ edipiLookup: ${edipiLookup}
4823
+ ocspEndpoint: \${CAC_OCSP_ENDPOINT}
4824
+ certHeader: ${certHeader}
4643
4825
  ${rbacSection}
4644
- protectedRoutes:
4826
+ protectedRoutes:
4645
4827
  ${routeLines}
4646
4828
  ${auditSection}
4647
4829
  `;
@@ -4649,33 +4831,33 @@ ${auditSection}
4649
4831
  if (method === "oidc") {
4650
4832
  const scopes2 = params.oidcScopes ?? "openid profile email";
4651
4833
  const roleClaim = params.oidcRoleClaim ?? "roles";
4652
- return `auth:
4653
- method: oidc
4654
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4655
- oidc:
4656
- discoveryUrl: \${OIDC_DISCOVERY_URL}
4657
- clientId: \${OIDC_CLIENT_ID}
4658
- clientSecret: \${OIDC_CLIENT_SECRET}
4659
- scopes: ${scopes2}
4660
- roleClaim: ${roleClaim}
4834
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4835
+ method: oidc
4836
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4837
+ oidc:
4838
+ discoveryUrl: \${OIDC_DISCOVERY_URL}
4839
+ clientId: \${OIDC_CLIENT_ID}
4840
+ clientSecret: \${OIDC_CLIENT_SECRET}
4841
+ scopes: ${scopes2}
4842
+ roleClaim: ${roleClaim}
4661
4843
  ${rbacSection}
4662
- protectedRoutes:
4844
+ protectedRoutes:
4663
4845
  ${routeLines}
4664
4846
  ${auditSection}
4665
4847
  `;
4666
4848
  }
4667
4849
  const scopes = params.oauth2Scopes ?? "read write";
4668
- return `auth:
4669
- method: oauth2
4670
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4671
- oauth2:
4672
- authorizationUrl: \${OAUTH2_AUTH_URL}
4673
- tokenUrl: \${OAUTH2_TOKEN_URL}
4674
- clientId: \${OAUTH2_CLIENT_ID}
4675
- clientSecret: \${OAUTH2_CLIENT_SECRET}
4676
- scopes: ${scopes}
4850
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
4851
+ method: oauth2
4852
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4853
+ oauth2:
4854
+ authorizationUrl: \${OAUTH2_AUTH_URL}
4855
+ tokenUrl: \${OAUTH2_TOKEN_URL}
4856
+ clientId: \${OAUTH2_CLIENT_ID}
4857
+ clientSecret: \${OAUTH2_CLIENT_SECRET}
4858
+ scopes: ${scopes}
4677
4859
  ${rbacSection}
4678
- protectedRoutes:
4860
+ protectedRoutes:
4679
4861
  ${routeLines}
4680
4862
  ${auditSection}
4681
4863
  `;
@@ -4861,35 +5043,35 @@ ${configBlock}
4861
5043
  `;
4862
5044
  }
4863
5045
  function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes, useProxy) {
4864
- const rbacSection = ` rbac:
4865
- roles:
4866
- ${rolesToYaml(roles, " ")}
4867
- defaultRole: ${defaultRole}
4868
- hierarchy:
4869
- ${hierarchyToYaml(hierarchy, " ")}`;
4870
- const auditSection = ` audit:
4871
- enabled: ${auditEnabled}
4872
- retentionDays: ${auditRetentionDays}`;
4873
- const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
4874
- requiredRole: ${r.requiredRole}`).join("\n");
4875
- const providerLine = params.provider ? ` provider: ${params.provider}
5046
+ const rbacSection = `rbac:
5047
+ roles:
5048
+ ${rolesToYaml(roles, " ")}
5049
+ defaultRole: ${defaultRole}
5050
+ hierarchy:
5051
+ ${hierarchyToYaml(hierarchy, " ")}`;
5052
+ const auditSection = `audit:
5053
+ enabled: ${auditEnabled}
5054
+ retentionDays: ${auditRetentionDays}`;
5055
+ const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
5056
+ requiredRole: ${r.requiredRole}`).join("\n");
5057
+ const providerLine = params.provider ? `provider: ${params.provider}
4876
5058
  ` : "";
4877
5059
  if (method === "cac") {
4878
5060
  const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4879
5061
  const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4880
5062
  const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4881
5063
  const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
4882
- return `auth:
4883
- method: cac
4884
- devOnly: true
4885
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4886
- cac:
4887
- caBundle: ${caBundle}
4888
- edipiLookup: ${edipiLookup}
4889
- ocspEndpoint: ${ocspEndpoint}
4890
- certHeader: ${certHeader}
5064
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5065
+ method: cac
5066
+ devOnly: true
5067
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5068
+ cac:
5069
+ caBundle: ${caBundle}
5070
+ edipiLookup: ${edipiLookup}
5071
+ ocspEndpoint: ${ocspEndpoint}
5072
+ certHeader: ${certHeader}
4891
5073
  ${rbacSection}
4892
- protectedRoutes:
5074
+ protectedRoutes:
4893
5075
  ${routeLines}
4894
5076
  ${auditSection}
4895
5077
  `;
@@ -4897,35 +5079,35 @@ ${auditSection}
4897
5079
  if (method === "oidc") {
4898
5080
  const scopes2 = params.oidcScopes ?? "openid profile email";
4899
5081
  const roleClaim = params.oidcRoleClaim ?? "roles";
4900
- return `auth:
4901
- method: oidc
4902
- devOnly: true
4903
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4904
- oidc:
4905
- discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
4906
- clientId: dev-mock-client
4907
- clientSecret: dev-mock-secret
4908
- scopes: ${scopes2}
4909
- roleClaim: ${roleClaim}
5082
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5083
+ method: oidc
5084
+ devOnly: true
5085
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5086
+ oidc:
5087
+ discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
5088
+ clientId: dev-mock-client
5089
+ clientSecret: dev-mock-secret
5090
+ scopes: ${scopes2}
5091
+ roleClaim: ${roleClaim}
4910
5092
  ${rbacSection}
4911
- protectedRoutes:
5093
+ protectedRoutes:
4912
5094
  ${routeLines}
4913
5095
  ${auditSection}
4914
5096
  `;
4915
5097
  }
4916
5098
  const scopes = params.oauth2Scopes ?? "read write";
4917
- return `auth:
4918
- method: oauth2
4919
- devOnly: true
4920
- ${providerLine} ${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
4921
- oauth2:
4922
- authorizationUrl: https://dev-mock-oauth2/authorize
4923
- tokenUrl: https://dev-mock-oauth2/token
4924
- clientId: dev-mock-client
4925
- clientSecret: dev-mock-secret
4926
- scopes: ${scopes}
5099
+ return `# stackwright.auth.yml \u2014 generated by Auth Otter
5100
+ method: oauth2
5101
+ devOnly: true
5102
+ ${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
5103
+ oauth2:
5104
+ authorizationUrl: https://dev-mock-oauth2/authorize
5105
+ tokenUrl: https://dev-mock-oauth2/token
5106
+ clientId: dev-mock-client
5107
+ clientSecret: dev-mock-secret
5108
+ scopes: ${scopes}
4927
5109
  ${rbacSection}
4928
- protectedRoutes:
5110
+ protectedRoutes:
4929
5111
  ${routeLines}
4930
5112
  ${auditSection}
4931
5113
  `;
@@ -5070,7 +5252,7 @@ async function configureAuthHandler(params, cwd) {
5070
5252
  auditRetentionDays,
5071
5253
  normalizedRoutes
5072
5254
  );
5073
- writeFileSync6(join6(cwd, conventionFile), authFileContent, "utf8");
5255
+ writeFileSync6(join7(cwd, conventionFile), authFileContent, "utf8");
5074
5256
  filesWritten.push(conventionFile);
5075
5257
  } catch (err) {
5076
5258
  const msg = err instanceof Error ? err.message : String(err);
@@ -5090,9 +5272,9 @@ async function configureAuthHandler(params, cwd) {
5090
5272
  if (!devOnly) {
5091
5273
  try {
5092
5274
  const envBlock = generateEnvBlock(effectiveMethod, params);
5093
- const envPath = join6(cwd, ".env.example");
5275
+ const envPath = join7(cwd, ".env.example");
5094
5276
  if (existsSync7(envPath)) {
5095
- const existing = readFileSync6(envPath, "utf8");
5277
+ const existing = readFileSync7(envPath, "utf8");
5096
5278
  writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
5097
5279
  } else {
5098
5280
  writeFileSync6(envPath, envBlock, "utf8");
@@ -5113,10 +5295,10 @@ async function configureAuthHandler(params, cwd) {
5113
5295
  }
5114
5296
  if (devOnly) {
5115
5297
  try {
5116
- const mockAuthDir = join6(cwd, "lib");
5298
+ const mockAuthDir = join7(cwd, "lib");
5117
5299
  mkdirSync6(mockAuthDir, { recursive: true });
5118
5300
  const mockAuthContent = generateMockAuthContent(roles, mockUsers);
5119
- writeFileSync6(join6(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5301
+ writeFileSync6(join7(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
5120
5302
  filesWritten.push("lib/mock-auth.ts");
5121
5303
  } catch (err) {
5122
5304
  const msg = err instanceof Error ? err.message : String(err);
@@ -5134,9 +5316,9 @@ async function configureAuthHandler(params, cwd) {
5134
5316
  };
5135
5317
  }
5136
5318
  try {
5137
- const pkgPath = join6(cwd, "package.json");
5319
+ const pkgPath = join7(cwd, "package.json");
5138
5320
  if (existsSync7(pkgPath)) {
5139
- const existingPkg = readFileSync6(pkgPath, "utf8");
5321
+ const existingPkg = readFileSync7(pkgPath, "utf8");
5140
5322
  const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
5141
5323
  writeFileSync6(pkgPath, updatedPkg, "utf8");
5142
5324
  filesWritten.push("package.json");
@@ -5179,21 +5361,18 @@ async function configureAuthHandler(params, cwd) {
5179
5361
  normalizedRoutes,
5180
5362
  useProxy
5181
5363
  );
5182
- const ymlPath = join6(cwd, "stackwright.yml");
5183
- if (!existsSync7(ymlPath)) {
5184
- writeFileSync6(ymlPath, authYaml, "utf8");
5185
- } else {
5186
- const existing = readFileSync6(ymlPath, "utf8");
5187
- writeFileSync6(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
5188
- }
5189
- filesWritten.push("stackwright.yml");
5364
+ writeFileSync6(join7(cwd, "stackwright.auth.yml"), authYaml, "utf8");
5365
+ filesWritten.push("stackwright.auth.yml");
5190
5366
  } catch (err) {
5191
5367
  const msg = err instanceof Error ? err.message : String(err);
5192
5368
  return {
5193
5369
  content: [
5194
5370
  {
5195
5371
  type: "text",
5196
- text: JSON.stringify({ success: false, error: `Failed writing stackwright.yml: ${msg}` })
5372
+ text: JSON.stringify({
5373
+ success: false,
5374
+ error: `Failed writing stackwright.auth.yml: ${msg}`
5375
+ })
5197
5376
  }
5198
5377
  ],
5199
5378
  isError: true
@@ -5238,7 +5417,7 @@ async function configureAuthHandler(params, cwd) {
5238
5417
  function registerAuthTools(server2) {
5239
5418
  server2.tool(
5240
5419
  "stackwright_pro_configure_auth",
5241
- "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.",
5420
+ "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.",
5242
5421
  {
5243
5422
  method: z15.enum(["cac", "oidc", "oauth2", "none"]),
5244
5423
  provider: z15.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
@@ -5289,28 +5468,28 @@ function registerAuthTools(server2) {
5289
5468
 
5290
5469
  // src/integrity.ts
5291
5470
  import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
5292
- import { readFileSync as readFileSync7, readdirSync as readdirSync2, lstatSync as lstatSync7 } from "fs";
5293
- import { join as join7, basename } from "path";
5471
+ import { readFileSync as readFileSync8, readdirSync as readdirSync3, lstatSync as lstatSync8 } from "fs";
5472
+ import { join as join8, basename } from "path";
5294
5473
  var _checksums = /* @__PURE__ */ new Map([
5295
5474
  [
5296
5475
  "stackwright-pro-api-otter.json",
5297
- "df79f4389a576c2885efa07b04f613c60eb8ebf4a8b1d4c7da5e4bb6ee4248dd"
5476
+ "822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
5298
5477
  ],
5299
5478
  [
5300
5479
  "stackwright-pro-auth-otter.json",
5301
- "643344b88e42992e0467845fb0184d3932e115b85130fbc6a47a3175759678c8"
5480
+ "d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
5302
5481
  ],
5303
5482
  [
5304
5483
  "stackwright-pro-dashboard-otter.json",
5305
- "160af221e04200f53709f8c3e249ca6dafb38a325b232fabd478b28f5492ab01"
5484
+ "e3b82555fcffbd77285bd304828814e9f9f7257130797c9de3785de97527668c"
5306
5485
  ],
5307
5486
  [
5308
5487
  "stackwright-pro-data-otter.json",
5309
- "709c8e49328908549fe87de181a5991e09c918ef24bbfe6948f9888f0c758c25"
5488
+ "bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
5310
5489
  ],
5311
5490
  [
5312
5491
  "stackwright-pro-designer-otter.json",
5313
- "1364b2c235c07b0b798e9aab90a68604f60019a5508d41ba576977f173e20cd9"
5492
+ "b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
5314
5493
  ],
5315
5494
  [
5316
5495
  "stackwright-pro-domain-expert-otter.json",
@@ -5318,35 +5497,35 @@ var _checksums = /* @__PURE__ */ new Map([
5318
5497
  ],
5319
5498
  [
5320
5499
  "stackwright-pro-foreman-otter.json",
5321
- "438b03d2c35f64536055c68b3a9044fe3b5e4f2889acde4500dd801fad724be1"
5500
+ "abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
5322
5501
  ],
5323
5502
  [
5324
5503
  "stackwright-pro-form-wizard-otter.json",
5325
- "3dffa3ef2d2ef057578391f784cbea779d768e87d98321894ea5bcd9e3ab7e60"
5504
+ "975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
5326
5505
  ],
5327
5506
  [
5328
5507
  "stackwright-pro-geo-otter.json",
5329
- "2ec83c26a08c413d9553ff8b8f0f0f643c1a8da043741b356e27106cad78f05f"
5508
+ "ac440da786d8c5ab1feddbf861449c3be3a433a04370578e1b2d35bf6ae7a601"
5330
5509
  ],
5331
5510
  [
5332
5511
  "stackwright-pro-page-otter.json",
5333
- "0057ea97f7c2e33a8673140f59a0237eb627d82c676af3fae4faa31033598e03"
5512
+ "b70bd6e5d2a40230f7c24ff4a6113585a1614e5b6a215ece2bd47ff053c30f58"
5334
5513
  ],
5335
5514
  [
5336
5515
  "stackwright-pro-polish-otter.json",
5337
- "bd87327b9a9a62fabaee8837492ce943feae8bfc15e5d43b45ba0e84619daec3"
5516
+ "48ef3cf7f29ac6b5e8ab2004d4d41e2fb899e781689178d3905993241c84095a"
5338
5517
  ],
5339
5518
  [
5340
5519
  "stackwright-pro-scaffold-otter.json",
5341
- "91de5861f1406043d1d387388302fb1492211b81e2eea9777dec60e48f317f2a"
5520
+ "e8662b63bbc9ce38851d94f9656bbebefdb7843d038862adb9fde2c23f9e77c2"
5342
5521
  ],
5343
5522
  [
5344
5523
  "stackwright-pro-theme-otter.json",
5345
- "fe10108e3ba67106751ea662f512bb5f4eb58f7abda26880ef4cf6b0f9feb944"
5524
+ "fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
5346
5525
  ],
5347
5526
  [
5348
5527
  "stackwright-services-otter.json",
5349
- "c013d7fc2475e62d0af54d57bc988182feee7766bac0edf841cbfad5c73e9261"
5528
+ "0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
5350
5529
  ]
5351
5530
  ]);
5352
5531
  Object.freeze(_checksums);
@@ -5375,7 +5554,7 @@ function verifyOtterFile(filePath) {
5375
5554
  }
5376
5555
  let stat;
5377
5556
  try {
5378
- stat = lstatSync7(filePath);
5557
+ stat = lstatSync8(filePath);
5379
5558
  } catch (err) {
5380
5559
  const msg = err instanceof Error ? err.message : String(err);
5381
5560
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -5393,7 +5572,7 @@ function verifyOtterFile(filePath) {
5393
5572
  }
5394
5573
  let raw;
5395
5574
  try {
5396
- raw = readFileSync7(filePath);
5575
+ raw = readFileSync8(filePath);
5397
5576
  } catch (err) {
5398
5577
  const msg = err instanceof Error ? err.message : String(err);
5399
5578
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -5443,7 +5622,7 @@ function verifyAllOtters(otterDir) {
5443
5622
  const unknown = [];
5444
5623
  let entries;
5445
5624
  try {
5446
- entries = readdirSync2(otterDir);
5625
+ entries = readdirSync3(otterDir);
5447
5626
  } catch (err) {
5448
5627
  const msg = err instanceof Error ? err.message : String(err);
5449
5628
  return {
@@ -5454,9 +5633,9 @@ function verifyAllOtters(otterDir) {
5454
5633
  }
5455
5634
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
5456
5635
  for (const filename of otterFiles) {
5457
- const filePath = join7(otterDir, filename);
5636
+ const filePath = join8(otterDir, filename);
5458
5637
  try {
5459
- if (lstatSync7(filePath).isSymbolicLink()) {
5638
+ if (lstatSync8(filePath).isSymbolicLink()) {
5460
5639
  failed.push({ filename, error: "Skipped: symlink" });
5461
5640
  continue;
5462
5641
  }
@@ -5482,9 +5661,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
5482
5661
  function resolveOtterDir() {
5483
5662
  const cwd = process.cwd();
5484
5663
  for (const relative of DEFAULT_SEARCH_PATHS) {
5485
- const candidate = join7(cwd, relative);
5664
+ const candidate = join8(cwd, relative);
5486
5665
  try {
5487
- lstatSync7(candidate);
5666
+ lstatSync8(candidate);
5488
5667
  return candidate;
5489
5668
  } catch {
5490
5669
  }
@@ -5563,13 +5742,13 @@ function registerIntegrityTools(server2) {
5563
5742
 
5564
5743
  // src/tools/domain.ts
5565
5744
  import { z as z16 } from "zod";
5566
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
5567
- import { join as join8 } from "path";
5745
+ import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
5746
+ import { join as join9 } from "path";
5568
5747
  function handleListCollections(input) {
5569
5748
  const cwd = input._cwd ?? process.cwd();
5570
5749
  const sources = [
5571
5750
  {
5572
- path: join8(cwd, ".stackwright", "artifacts", "data-config.json"),
5751
+ path: join9(cwd, ".stackwright", "artifacts", "data-config.json"),
5573
5752
  source: "data-config.json",
5574
5753
  parse: (raw) => {
5575
5754
  const parsed = JSON.parse(raw);
@@ -5580,15 +5759,15 @@ function handleListCollections(input) {
5580
5759
  }
5581
5760
  },
5582
5761
  {
5583
- path: join8(cwd, "stackwright.yml"),
5762
+ path: join9(cwd, "stackwright.yml"),
5584
5763
  source: "stackwright.yml",
5585
5764
  parse: extractCollectionsFromYaml
5586
5765
  }
5587
5766
  ];
5588
- for (const { path: path3, source, parse } of sources) {
5589
- if (!existsSync8(path3)) continue;
5767
+ for (const { path: path4, source, parse } of sources) {
5768
+ if (!existsSync8(path4)) continue;
5590
5769
  try {
5591
- const collections = parse(readFileSync8(path3, "utf8"));
5770
+ const collections = parse(readFileSync9(path4, "utf8"));
5592
5771
  return {
5593
5772
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
5594
5773
  isError: false
@@ -5739,7 +5918,7 @@ function handleValidateWorkflow(input) {
5739
5918
  if (input.workflow && Object.keys(input.workflow).length > 0) {
5740
5919
  raw = input.workflow;
5741
5920
  } else {
5742
- const artifactPath = join8(cwd, ".stackwright", "artifacts", "workflow-config.json");
5921
+ const artifactPath = join9(cwd, ".stackwright", "artifacts", "workflow-config.json");
5743
5922
  if (!existsSync8(artifactPath)) {
5744
5923
  return fail([
5745
5924
  {
@@ -5749,7 +5928,7 @@ function handleValidateWorkflow(input) {
5749
5928
  ]);
5750
5929
  }
5751
5930
  try {
5752
- raw = JSON.parse(readFileSync8(artifactPath, "utf8"));
5931
+ raw = JSON.parse(readFileSync9(artifactPath, "utf8"));
5753
5932
  } catch (err) {
5754
5933
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
5755
5934
  }
@@ -5861,12 +6040,12 @@ function handleValidateWorkflow(input) {
5861
6040
  return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
5862
6041
  }
5863
6042
  function validateTransitionTargets(step, stepId, stepIds, errors) {
5864
- const check = (target, path3) => {
6043
+ const check = (target, path4) => {
5865
6044
  if (typeof target === "string" && !stepIds.has(target)) {
5866
6045
  errors.push({
5867
6046
  code: "ORPHANED_TRANSITION",
5868
6047
  message: `Step "${stepId}" transitions to "${target}" which does not exist`,
5869
- path: path3
6048
+ path: path4
5870
6049
  });
5871
6050
  }
5872
6051
  };
@@ -5905,11 +6084,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
5905
6084
  }
5906
6085
  function collectServiceWarnings(step, stepId, warnings) {
5907
6086
  const isService = (val) => typeof val === "string" && val.startsWith("service:");
5908
- const warn = (path3) => {
6087
+ const warn = (path4) => {
5909
6088
  warnings.push({
5910
6089
  code: "WARN_SERVICE_REFERENCE",
5911
- message: `service: reference at "${path3}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
5912
- path: path3
6090
+ message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
6091
+ path: path4
5913
6092
  });
5914
6093
  };
5915
6094
  const onSubmit = step.on_submit;
@@ -6059,15 +6238,15 @@ function registerTypeSchemasTool(server2) {
6059
6238
  // src/tools/scaffold-cleanup.ts
6060
6239
  import {
6061
6240
  existsSync as existsSync9,
6062
- lstatSync as lstatSync8,
6241
+ lstatSync as lstatSync9,
6063
6242
  unlinkSync as unlinkSync2,
6064
- readFileSync as readFileSync9,
6243
+ readFileSync as readFileSync10,
6065
6244
  writeFileSync as writeFileSync7,
6066
- readdirSync as readdirSync3,
6245
+ readdirSync as readdirSync4,
6067
6246
  rmdirSync,
6068
6247
  mkdirSync as mkdirSync7
6069
6248
  } from "fs";
6070
- import { join as join9, dirname as dirname2 } from "path";
6249
+ import { join as join10, dirname as dirname2 } from "path";
6071
6250
  var SCAFFOLD_FILES_TO_DELETE = [
6072
6251
  "content/posts/getting-started.yaml",
6073
6252
  "content/posts/hello-world.yaml"
@@ -6092,12 +6271,12 @@ var FALLBACK_COLORS = {
6092
6271
  mutedForeground: "#737373"
6093
6272
  };
6094
6273
  function readThemeColors(cwd) {
6095
- const tokenPath = join9(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6274
+ const tokenPath = join10(cwd, ".stackwright", "artifacts", "theme-tokens.json");
6096
6275
  if (!existsSync9(tokenPath)) return { ...FALLBACK_COLORS };
6097
- const stat = lstatSync8(tokenPath);
6276
+ const stat = lstatSync9(tokenPath);
6098
6277
  if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
6099
6278
  try {
6100
- const raw = JSON.parse(readFileSync9(tokenPath, "utf-8"));
6279
+ const raw = JSON.parse(readFileSync10(tokenPath, "utf-8"));
6101
6280
  const css = raw?.cssVariables ?? {};
6102
6281
  const toHsl = (hslValue) => {
6103
6282
  if (!hslValue || typeof hslValue !== "string") return null;
@@ -6158,14 +6337,14 @@ function generateNotFoundPage(colors) {
6158
6337
  }
6159
6338
  function isSafePath(fullPath) {
6160
6339
  if (!existsSync9(fullPath)) return true;
6161
- const stat = lstatSync8(fullPath);
6340
+ const stat = lstatSync9(fullPath);
6162
6341
  return !stat.isSymbolicLink();
6163
6342
  }
6164
6343
  function isDirEmpty(dirPath) {
6165
6344
  if (!existsSync9(dirPath)) return false;
6166
- const stat = lstatSync8(dirPath);
6345
+ const stat = lstatSync9(dirPath);
6167
6346
  if (!stat.isDirectory()) return false;
6168
- return readdirSync3(dirPath).length === 0;
6347
+ return readdirSync4(dirPath).length === 0;
6169
6348
  }
6170
6349
  function handleCleanupScaffold(_cwd) {
6171
6350
  const cwd = _cwd ?? process.cwd();
@@ -6178,7 +6357,7 @@ function handleCleanupScaffold(_cwd) {
6178
6357
  cleanupWarnings: []
6179
6358
  };
6180
6359
  for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
6181
- const fullPath = join9(cwd, relPath);
6360
+ const fullPath = join10(cwd, relPath);
6182
6361
  if (!existsSync9(fullPath)) {
6183
6362
  result.skipped.push(relPath);
6184
6363
  continue;
@@ -6196,13 +6375,13 @@ function handleCleanupScaffold(_cwd) {
6196
6375
  }
6197
6376
  {
6198
6377
  const relPath = "pages/getting-started/content.yml";
6199
- const fullPath = join9(cwd, relPath);
6378
+ const fullPath = join10(cwd, relPath);
6200
6379
  if (!existsSync9(fullPath)) {
6201
6380
  result.skipped.push(relPath);
6202
6381
  } else if (!isSafePath(fullPath)) {
6203
6382
  result.errors.push(`Refusing to delete symlink: ${relPath}`);
6204
6383
  } else {
6205
- const content = readFileSync9(fullPath, "utf-8");
6384
+ const content = readFileSync10(fullPath, "utf-8");
6206
6385
  const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
6207
6386
  (marker) => content.includes(marker)
6208
6387
  );
@@ -6222,8 +6401,8 @@ function handleCleanupScaffold(_cwd) {
6222
6401
  }
6223
6402
  {
6224
6403
  const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
6225
- const fullPath = join9(cwd, relPath);
6226
- const postsDir = join9(cwd, "content/posts");
6404
+ const fullPath = join10(cwd, relPath);
6405
+ const postsDir = join10(cwd, "content/posts");
6227
6406
  if (!existsSync9(fullPath)) {
6228
6407
  result.skipped.push(relPath);
6229
6408
  } else if (!isSafePath(fullPath)) {
@@ -6231,7 +6410,7 @@ function handleCleanupScaffold(_cwd) {
6231
6410
  } else if (!existsSync9(postsDir)) {
6232
6411
  result.skipped.push(relPath);
6233
6412
  } else {
6234
- const userPostFiles = readdirSync3(postsDir).filter(
6413
+ const userPostFiles = readdirSync4(postsDir).filter(
6235
6414
  (f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
6236
6415
  );
6237
6416
  if (userPostFiles.length === 0) {
@@ -6249,7 +6428,7 @@ function handleCleanupScaffold(_cwd) {
6249
6428
  }
6250
6429
  }
6251
6430
  for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
6252
- const fullDir = join9(cwd, relDir);
6431
+ const fullDir = join10(cwd, relDir);
6253
6432
  if (!existsSync9(fullDir)) continue;
6254
6433
  if (!isSafePath(fullDir)) {
6255
6434
  result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
@@ -6265,7 +6444,7 @@ function handleCleanupScaffold(_cwd) {
6265
6444
  }
6266
6445
  }
6267
6446
  {
6268
- const fullPath = join9(cwd, NOT_FOUND_PATH);
6447
+ const fullPath = join10(cwd, NOT_FOUND_PATH);
6269
6448
  if (!existsSync9(fullPath)) {
6270
6449
  result.skipped.push(NOT_FOUND_PATH);
6271
6450
  } else if (!isSafePath(fullPath)) {
@@ -6543,11 +6722,256 @@ function registerContrastTools(server2) {
6543
6722
  );
6544
6723
  }
6545
6724
 
6725
+ // src/tools/compile.ts
6726
+ import { z as z19 } from "zod";
6727
+ import fs2 from "fs";
6728
+ import path3 from "path";
6729
+ import { compileCollections } from "@stackwright-pro/pulse/server";
6730
+ import { compileAuth } from "@stackwright-pro/auth-nextjs/server";
6731
+ import { compileIntegrations } from "@stackwright-pro/openapi/server";
6732
+ function buildContext(projectRoot) {
6733
+ return {
6734
+ projectRoot,
6735
+ contentOutDir: path3.join(projectRoot, "public", "stackwright-content"),
6736
+ imagesDir: path3.join(projectRoot, "public", "images"),
6737
+ siteConfig: loadSiteConfig(projectRoot)
6738
+ };
6739
+ }
6740
+ function loadSiteConfig(projectRoot) {
6741
+ try {
6742
+ const sitePath = path3.join(projectRoot, "public", "stackwright-content", "_site.json");
6743
+ return JSON.parse(fs2.readFileSync(sitePath, "utf8"));
6744
+ } catch {
6745
+ return {};
6746
+ }
6747
+ }
6748
+ function formatDuration(startMs) {
6749
+ const ms = Date.now() - startMs;
6750
+ return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
6751
+ }
6752
+ async function tryOssCompile(fn, ctx, extra) {
6753
+ try {
6754
+ const bsPath = path3.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
6755
+ if (!fs2.existsSync(bsPath)) {
6756
+ return {
6757
+ ok: false,
6758
+ error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
6759
+ };
6760
+ }
6761
+ const bs = await import(bsPath);
6762
+ if (typeof bs[fn] !== "function") {
6763
+ return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
6764
+ }
6765
+ await bs[fn](ctx, ...extra ? [extra] : []);
6766
+ return { ok: true };
6767
+ } catch (err) {
6768
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
6769
+ }
6770
+ }
6771
+ function registerCompileTools(server2) {
6772
+ const projectRootArg = z19.string().optional().describe("Absolute path to project root (defaults to cwd)");
6773
+ server2.tool(
6774
+ "stackwright_pro_compile_collections",
6775
+ "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.",
6776
+ { projectRoot: projectRootArg },
6777
+ async ({ projectRoot }) => {
6778
+ const start = Date.now();
6779
+ const root = projectRoot ?? process.cwd();
6780
+ try {
6781
+ await compileCollections(buildContext(root));
6782
+ return {
6783
+ content: [
6784
+ {
6785
+ type: "text",
6786
+ text: ` _collections.json written (${formatDuration(start)})`
6787
+ }
6788
+ ]
6789
+ };
6790
+ } catch (err) {
6791
+ return {
6792
+ content: [
6793
+ {
6794
+ type: "text",
6795
+ text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
6796
+ }
6797
+ ]
6798
+ };
6799
+ }
6800
+ }
6801
+ );
6802
+ server2.tool(
6803
+ "stackwright_pro_compile_auth",
6804
+ "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.",
6805
+ { projectRoot: projectRootArg },
6806
+ async ({ projectRoot }) => {
6807
+ const start = Date.now();
6808
+ const root = projectRoot ?? process.cwd();
6809
+ try {
6810
+ await compileAuth(buildContext(root));
6811
+ return {
6812
+ content: [
6813
+ {
6814
+ type: "text",
6815
+ text: ` _auth.json written (${formatDuration(start)})`
6816
+ }
6817
+ ]
6818
+ };
6819
+ } catch (err) {
6820
+ return {
6821
+ content: [
6822
+ {
6823
+ type: "text",
6824
+ text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
6825
+ }
6826
+ ]
6827
+ };
6828
+ }
6829
+ }
6830
+ );
6831
+ server2.tool(
6832
+ "stackwright_pro_compile_integrations",
6833
+ "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).",
6834
+ { projectRoot: projectRootArg },
6835
+ async ({ projectRoot }) => {
6836
+ const start = Date.now();
6837
+ const root = projectRoot ?? process.cwd();
6838
+ try {
6839
+ await compileIntegrations(buildContext(root));
6840
+ return {
6841
+ content: [
6842
+ {
6843
+ type: "text",
6844
+ text: ` _integrations.json written (${formatDuration(start)})`
6845
+ }
6846
+ ]
6847
+ };
6848
+ } catch (err) {
6849
+ return {
6850
+ content: [
6851
+ {
6852
+ type: "text",
6853
+ text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
6854
+ }
6855
+ ]
6856
+ };
6857
+ }
6858
+ }
6859
+ );
6860
+ server2.tool(
6861
+ "stackwright_pro_compile_all",
6862
+ "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.",
6863
+ { projectRoot: projectRootArg },
6864
+ async ({ projectRoot }) => {
6865
+ const start = Date.now();
6866
+ const root = projectRoot ?? process.cwd();
6867
+ const ctx = buildContext(root);
6868
+ const results = [];
6869
+ const errors = [];
6870
+ for (const [label, fn] of [
6871
+ ["collections", () => compileCollections(ctx)],
6872
+ ["auth", () => compileAuth(ctx)],
6873
+ ["integrations", () => compileIntegrations(ctx)]
6874
+ ]) {
6875
+ try {
6876
+ await fn();
6877
+ results.push(` _${label}.json`);
6878
+ } catch (err) {
6879
+ errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
6880
+ }
6881
+ }
6882
+ const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
6883
+ ${results.join("\n")}
6884
+ Errors:
6885
+ ${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
6886
+ ${results.join("\n")}`;
6887
+ return { content: [{ type: "text", text: summary }] };
6888
+ }
6889
+ );
6890
+ server2.tool(
6891
+ "stackwright_pro_compile_theme",
6892
+ "Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
6893
+ { projectRoot: projectRootArg },
6894
+ async ({ projectRoot }) => {
6895
+ const start = Date.now();
6896
+ const root = projectRoot ?? process.cwd();
6897
+ const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
6898
+ return {
6899
+ content: [
6900
+ {
6901
+ type: "text",
6902
+ text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
6903
+ }
6904
+ ]
6905
+ };
6906
+ }
6907
+ );
6908
+ server2.tool(
6909
+ "stackwright_pro_compile_site",
6910
+ "Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
6911
+ { projectRoot: projectRootArg },
6912
+ async ({ projectRoot }) => {
6913
+ const start = Date.now();
6914
+ const root = projectRoot ?? process.cwd();
6915
+ const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
6916
+ return {
6917
+ content: [
6918
+ {
6919
+ type: "text",
6920
+ text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
6921
+ }
6922
+ ]
6923
+ };
6924
+ }
6925
+ );
6926
+ server2.tool(
6927
+ "stackwright_pro_compile_pages",
6928
+ "Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
6929
+ { projectRoot: projectRootArg },
6930
+ async ({ projectRoot }) => {
6931
+ const start = Date.now();
6932
+ const root = projectRoot ?? process.cwd();
6933
+ const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
6934
+ return {
6935
+ content: [
6936
+ {
6937
+ type: "text",
6938
+ text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
6939
+ }
6940
+ ]
6941
+ };
6942
+ }
6943
+ );
6944
+ server2.tool(
6945
+ "stackwright_pro_compile_page",
6946
+ "Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
6947
+ {
6948
+ slug: z19.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
6949
+ projectRoot: projectRootArg
6950
+ },
6951
+ async ({ slug, projectRoot }) => {
6952
+ const start = Date.now();
6953
+ const root = projectRoot ?? process.cwd();
6954
+ const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
6955
+ return {
6956
+ content: [
6957
+ {
6958
+ type: "text",
6959
+ text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
6960
+ }
6961
+ ]
6962
+ };
6963
+ }
6964
+ );
6965
+ }
6966
+
6546
6967
  // package.json
6547
6968
  var package_default = {
6548
6969
  dependencies: {
6549
6970
  "@modelcontextprotocol/sdk": "^1.10.0",
6971
+ "@stackwright-pro/auth-nextjs": "workspace:*",
6550
6972
  "@stackwright-pro/cli-data-explorer": "workspace:*",
6973
+ "@stackwright-pro/openapi": "workspace:*",
6974
+ "@stackwright-pro/pulse": "workspace:*",
6551
6975
  "@stackwright-pro/types": "workspace:*",
6552
6976
  "@types/js-yaml": "^4.0.9",
6553
6977
  "js-yaml": "^4.2.0",
@@ -6568,7 +6992,7 @@ var package_default = {
6568
6992
  "test:coverage": "vitest run --coverage"
6569
6993
  },
6570
6994
  name: "@stackwright-pro/mcp",
6571
- version: "0.2.0-alpha.92",
6995
+ version: "0.2.0-alpha.95",
6572
6996
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
6573
6997
  license: "SEE LICENSE IN LICENSE",
6574
6998
  main: "./dist/server.js",
@@ -6626,7 +7050,22 @@ registerValidateYamlFragmentTool(server);
6626
7050
  registerGetSchemaTool(server);
6627
7051
  registerScaffoldCleanupTools(server);
6628
7052
  registerContrastTools(server);
7053
+ registerCompileTools(server);
6629
7054
  async function main() {
7055
+ const pipelineGraph = loadPipelineGraph();
7056
+ const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
7057
+ for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
7058
+ const phaseIdx = phasePosition.get(phase);
7059
+ if (phaseIdx === void 0) continue;
7060
+ for (const dep of deps) {
7061
+ const depIdx = phasePosition.get(dep);
7062
+ if (depIdx !== void 0 && depIdx >= phaseIdx) {
7063
+ throw new Error(
7064
+ `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.`
7065
+ );
7066
+ }
7067
+ }
7068
+ }
6630
7069
  const transport = new StdioServerTransport();
6631
7070
  try {
6632
7071
  const servicesRegisterPkg = "@stackwright-services/mcp/register";