@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/integrity.js +13 -13
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +13 -13
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +750 -311
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +698 -259
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -3
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
|
|
124
|
-
lines.push(` - ${
|
|
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
|
|
2003
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
3010
|
+
return readFileSync5(filePath, "utf-8");
|
|
2824
3011
|
}
|
|
2825
3012
|
function writeState(cwd, state) {
|
|
2826
|
-
const dir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3302
|
+
const questionsDir = join5(cwd, ".stackwright", "questions");
|
|
3115
3303
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3116
|
-
const filePath =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
3943
|
+
const artifactsDir = join5(cwd, ".stackwright", "artifacts");
|
|
3755
3944
|
mkdirSync4(artifactsDir, { recursive: true });
|
|
3756
3945
|
const artifactFile = PHASE_ARTIFACT[phase];
|
|
3757
|
-
const artifactPath =
|
|
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 =
|
|
4063
|
+
const questionsDir = join5(process.cwd(), ".stackwright", "questions");
|
|
3875
4064
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3876
|
-
const outPath =
|
|
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
|
|
3929
|
-
import { normalize, isAbsolute, dirname, join as
|
|
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
|
-
{
|
|
3945
|
-
|
|
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
|
-
{
|
|
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 =
|
|
4270
|
+
const regPath = join6(cwd, PAGE_REGISTRY_FILE);
|
|
4070
4271
|
if (!existsSync6(regPath)) return {};
|
|
4071
4272
|
try {
|
|
4072
|
-
const stat =
|
|
4273
|
+
const stat = lstatSync7(regPath);
|
|
4073
4274
|
if (stat.isSymbolicLink()) return {};
|
|
4074
|
-
return JSON.parse(
|
|
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 =
|
|
4281
|
+
const dir = join6(cwd, ".stackwright");
|
|
4081
4282
|
mkdirSync5(dir, { recursive: true });
|
|
4082
|
-
const regPath =
|
|
4283
|
+
const regPath = join6(cwd, PAGE_REGISTRY_FILE);
|
|
4083
4284
|
if (existsSync6(regPath)) {
|
|
4084
|
-
const stat =
|
|
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(
|
|
4105
|
-
return
|
|
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 =
|
|
4456
|
+
const fullPath = join6(cwd, normalized);
|
|
4256
4457
|
if (existsSync6(fullPath)) {
|
|
4257
4458
|
try {
|
|
4258
|
-
const stat =
|
|
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
|
|
4362
|
-
import { join as
|
|
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 =
|
|
4590
|
+
const pkgPath = join7(cwd, "package.json");
|
|
4390
4591
|
if (!existsSync7(pkgPath)) return null;
|
|
4391
|
-
const pkg = JSON.parse(
|
|
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 = `
|
|
4619
|
-
|
|
4620
|
-
${rolesToYaml(roles, "
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
${hierarchyToYaml(hierarchy, "
|
|
4624
|
-
const auditSection = `
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
const routeLines = routesToYaml(protectedRoutes, "
|
|
4628
|
-
const providerLine = 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
|
|
4636
|
-
|
|
4637
|
-
${providerLine}
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
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
|
-
|
|
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
|
|
4653
|
-
|
|
4654
|
-
${providerLine}
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
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
|
-
|
|
4844
|
+
protectedRoutes:
|
|
4663
4845
|
${routeLines}
|
|
4664
4846
|
${auditSection}
|
|
4665
4847
|
`;
|
|
4666
4848
|
}
|
|
4667
4849
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4668
|
-
return
|
|
4669
|
-
|
|
4670
|
-
${providerLine}
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
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
|
-
|
|
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 = `
|
|
4865
|
-
|
|
4866
|
-
${rolesToYaml(roles, "
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
${hierarchyToYaml(hierarchy, "
|
|
4870
|
-
const auditSection = `
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
const routeLines = protectedRoutes.map((r) => `
|
|
4874
|
-
|
|
4875
|
-
const providerLine = 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
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
${providerLine}
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
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
|
-
|
|
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
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
${providerLine}
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
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
|
-
|
|
5093
|
+
protectedRoutes:
|
|
4912
5094
|
${routeLines}
|
|
4913
5095
|
${auditSection}
|
|
4914
5096
|
`;
|
|
4915
5097
|
}
|
|
4916
5098
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4917
|
-
return
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
${providerLine}
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
5275
|
+
const envPath = join7(cwd, ".env.example");
|
|
5094
5276
|
if (existsSync7(envPath)) {
|
|
5095
|
-
const existing =
|
|
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 =
|
|
5298
|
+
const mockAuthDir = join7(cwd, "lib");
|
|
5117
5299
|
mkdirSync6(mockAuthDir, { recursive: true });
|
|
5118
5300
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5119
|
-
writeFileSync6(
|
|
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 =
|
|
5319
|
+
const pkgPath = join7(cwd, "package.json");
|
|
5138
5320
|
if (existsSync7(pkgPath)) {
|
|
5139
|
-
const existingPkg =
|
|
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
|
-
|
|
5183
|
-
|
|
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({
|
|
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,
|
|
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
|
|
5293
|
-
import { join as
|
|
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
|
-
"
|
|
5476
|
+
"822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
|
|
5298
5477
|
],
|
|
5299
5478
|
[
|
|
5300
5479
|
"stackwright-pro-auth-otter.json",
|
|
5301
|
-
"
|
|
5480
|
+
"d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
|
|
5302
5481
|
],
|
|
5303
5482
|
[
|
|
5304
5483
|
"stackwright-pro-dashboard-otter.json",
|
|
5305
|
-
"
|
|
5484
|
+
"e3b82555fcffbd77285bd304828814e9f9f7257130797c9de3785de97527668c"
|
|
5306
5485
|
],
|
|
5307
5486
|
[
|
|
5308
5487
|
"stackwright-pro-data-otter.json",
|
|
5309
|
-
"
|
|
5488
|
+
"bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
|
|
5310
5489
|
],
|
|
5311
5490
|
[
|
|
5312
5491
|
"stackwright-pro-designer-otter.json",
|
|
5313
|
-
"
|
|
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
|
-
"
|
|
5500
|
+
"abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
|
|
5322
5501
|
],
|
|
5323
5502
|
[
|
|
5324
5503
|
"stackwright-pro-form-wizard-otter.json",
|
|
5325
|
-
"
|
|
5504
|
+
"975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
|
|
5326
5505
|
],
|
|
5327
5506
|
[
|
|
5328
5507
|
"stackwright-pro-geo-otter.json",
|
|
5329
|
-
"
|
|
5508
|
+
"ac440da786d8c5ab1feddbf861449c3be3a433a04370578e1b2d35bf6ae7a601"
|
|
5330
5509
|
],
|
|
5331
5510
|
[
|
|
5332
5511
|
"stackwright-pro-page-otter.json",
|
|
5333
|
-
"
|
|
5512
|
+
"b70bd6e5d2a40230f7c24ff4a6113585a1614e5b6a215ece2bd47ff053c30f58"
|
|
5334
5513
|
],
|
|
5335
5514
|
[
|
|
5336
5515
|
"stackwright-pro-polish-otter.json",
|
|
5337
|
-
"
|
|
5516
|
+
"48ef3cf7f29ac6b5e8ab2004d4d41e2fb899e781689178d3905993241c84095a"
|
|
5338
5517
|
],
|
|
5339
5518
|
[
|
|
5340
5519
|
"stackwright-pro-scaffold-otter.json",
|
|
5341
|
-
"
|
|
5520
|
+
"e8662b63bbc9ce38851d94f9656bbebefdb7843d038862adb9fde2c23f9e77c2"
|
|
5342
5521
|
],
|
|
5343
5522
|
[
|
|
5344
5523
|
"stackwright-pro-theme-otter.json",
|
|
5345
|
-
"
|
|
5524
|
+
"fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
|
|
5346
5525
|
],
|
|
5347
5526
|
[
|
|
5348
5527
|
"stackwright-services-otter.json",
|
|
5349
|
-
"
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5636
|
+
const filePath = join8(otterDir, filename);
|
|
5458
5637
|
try {
|
|
5459
|
-
if (
|
|
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 =
|
|
5664
|
+
const candidate = join8(cwd, relative);
|
|
5486
5665
|
try {
|
|
5487
|
-
|
|
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
|
|
5567
|
-
import { join as
|
|
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:
|
|
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:
|
|
5762
|
+
path: join9(cwd, "stackwright.yml"),
|
|
5584
5763
|
source: "stackwright.yml",
|
|
5585
5764
|
parse: extractCollectionsFromYaml
|
|
5586
5765
|
}
|
|
5587
5766
|
];
|
|
5588
|
-
for (const { path:
|
|
5589
|
-
if (!existsSync8(
|
|
5767
|
+
for (const { path: path4, source, parse } of sources) {
|
|
5768
|
+
if (!existsSync8(path4)) continue;
|
|
5590
5769
|
try {
|
|
5591
|
-
const collections = parse(
|
|
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 =
|
|
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(
|
|
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,
|
|
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:
|
|
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 = (
|
|
6087
|
+
const warn = (path4) => {
|
|
5909
6088
|
warnings.push({
|
|
5910
6089
|
code: "WARN_SERVICE_REFERENCE",
|
|
5911
|
-
message: `service: reference at "${
|
|
5912
|
-
path:
|
|
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
|
|
6241
|
+
lstatSync as lstatSync9,
|
|
6063
6242
|
unlinkSync as unlinkSync2,
|
|
6064
|
-
readFileSync as
|
|
6243
|
+
readFileSync as readFileSync10,
|
|
6065
6244
|
writeFileSync as writeFileSync7,
|
|
6066
|
-
readdirSync as
|
|
6245
|
+
readdirSync as readdirSync4,
|
|
6067
6246
|
rmdirSync,
|
|
6068
6247
|
mkdirSync as mkdirSync7
|
|
6069
6248
|
} from "fs";
|
|
6070
|
-
import { join as
|
|
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 =
|
|
6274
|
+
const tokenPath = join10(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
6096
6275
|
if (!existsSync9(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6097
|
-
const stat =
|
|
6276
|
+
const stat = lstatSync9(tokenPath);
|
|
6098
6277
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6099
6278
|
try {
|
|
6100
|
-
const raw = JSON.parse(
|
|
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 =
|
|
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 =
|
|
6345
|
+
const stat = lstatSync9(dirPath);
|
|
6167
6346
|
if (!stat.isDirectory()) return false;
|
|
6168
|
-
return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
6226
|
-
const postsDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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";
|