@stackwright-pro/mcp 0.2.0-alpha.92 → 0.2.0-alpha.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrity.d.mts +2 -1
- package/dist/integrity.d.ts +2 -1
- package/dist/integrity.js +20 -13
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +19 -13
- package/dist/integrity.mjs.map +1 -1
- package/dist/pipeline-constants.d.mts +30 -0
- package/dist/pipeline-constants.d.ts +30 -0
- package/dist/pipeline-constants.js +444 -0
- package/dist/pipeline-constants.js.map +1 -0
- package/dist/pipeline-constants.mjs +428 -0
- package/dist/pipeline-constants.mjs.map +1 -0
- package/dist/server.js +884 -315
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +845 -263
- package/dist/server.mjs.map +1 -1
- package/package.json +12 -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,224 @@ 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
|
+
"stackwright-pro-qa-otter": "qa"
|
|
2710
|
+
};
|
|
2711
|
+
function manifestNameToPhase(name) {
|
|
2712
|
+
return MANIFEST_NAME_TO_PHASE[name] ?? null;
|
|
2713
|
+
}
|
|
2714
|
+
var OTTER_SEARCH_PATHS = [
|
|
2715
|
+
"node_modules/@stackwright-pro/otters/src/",
|
|
2716
|
+
// production: installed package
|
|
2717
|
+
"packages/otters/src/",
|
|
2718
|
+
// monorepo: run from repo root
|
|
2719
|
+
"../otters/src/"
|
|
2720
|
+
// monorepo: run from packages/mcp/
|
|
2721
|
+
];
|
|
2722
|
+
function resolveOtterDirFromCwd() {
|
|
2723
|
+
const cwd = process.cwd();
|
|
2724
|
+
for (const relative of OTTER_SEARCH_PATHS) {
|
|
2725
|
+
const candidate = join4(cwd, relative);
|
|
2726
|
+
try {
|
|
2727
|
+
lstatSync5(candidate);
|
|
2728
|
+
return candidate;
|
|
2729
|
+
} catch {
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
throw new Error(
|
|
2733
|
+
`Cannot locate otter directory. Searched: ${OTTER_SEARCH_PATHS.join(", ")} (relative to ${cwd}). Make sure @stackwright-pro/otters is installed.`
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
function loadOtterManifestsFromDir(otterDir) {
|
|
2737
|
+
const entries = readdirSync2(otterDir);
|
|
2738
|
+
const manifests = [];
|
|
2739
|
+
for (const filename of entries) {
|
|
2740
|
+
if (!filename.endsWith("-otter.json")) continue;
|
|
2741
|
+
const filePath = join4(otterDir, filename);
|
|
2742
|
+
if (lstatSync5(filePath).isSymbolicLink()) continue;
|
|
2743
|
+
try {
|
|
2744
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
2745
|
+
const manifest = JSON.parse(raw);
|
|
2746
|
+
manifests.push(manifest);
|
|
2747
|
+
} catch (err) {
|
|
2748
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2749
|
+
throw new Error(`Failed to load otter manifest "${filename}": ${msg}`, { cause: err });
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
return manifests;
|
|
2753
|
+
}
|
|
2754
|
+
function topologicalSort(dependencies) {
|
|
2755
|
+
const phases = Object.keys(dependencies);
|
|
2756
|
+
const inDegree = {};
|
|
2757
|
+
const adjList = {};
|
|
2758
|
+
for (const phase of phases) {
|
|
2759
|
+
inDegree[phase] ??= 0;
|
|
2760
|
+
adjList[phase] ??= [];
|
|
2761
|
+
}
|
|
2762
|
+
for (const phase of phases) {
|
|
2763
|
+
for (const dep of dependencies[phase] ?? []) {
|
|
2764
|
+
inDegree[phase] = (inDegree[phase] ?? 0) + 1;
|
|
2765
|
+
adjList[dep] ??= [];
|
|
2766
|
+
adjList[dep].push(phase);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
const queue = phases.filter((p) => (inDegree[p] ?? 0) === 0);
|
|
2770
|
+
const result = [];
|
|
2771
|
+
while (queue.length > 0) {
|
|
2772
|
+
const node = queue.shift();
|
|
2773
|
+
result.push(node);
|
|
2774
|
+
for (const dependent of adjList[node] ?? []) {
|
|
2775
|
+
inDegree[dependent] -= 1;
|
|
2776
|
+
if (inDegree[dependent] === 0) {
|
|
2777
|
+
queue.push(dependent);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
if (result.length !== phases.length) {
|
|
2782
|
+
const cycleNodes = phases.filter((p) => (inDegree[p] ?? 0) > 0);
|
|
2783
|
+
throw new Error(
|
|
2784
|
+
`Pipeline dependency cycle detected involving: [${cycleNodes.join(", ")}]. Check otter pipeline declarations for circular dependencies.`
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
return result;
|
|
2788
|
+
}
|
|
2789
|
+
function buildPipelineGraph(manifests) {
|
|
2790
|
+
const sinkProducers = {};
|
|
2791
|
+
const artifactProducers = {};
|
|
2792
|
+
for (const m of manifests) {
|
|
2793
|
+
const phase = manifestNameToPhase(m.name);
|
|
2794
|
+
if (!phase) continue;
|
|
2795
|
+
const out = m.pipeline?.outputs;
|
|
2796
|
+
if (!out) continue;
|
|
2797
|
+
for (const sink of out.sinks ?? []) {
|
|
2798
|
+
if (sinkProducers[sink]) {
|
|
2799
|
+
throw new Error(
|
|
2800
|
+
`Sink "${sink}" is produced by both "${sinkProducers[sink]}" and "${phase}". Each sink must have exactly one producer.`
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
sinkProducers[sink] = phase;
|
|
2804
|
+
}
|
|
2805
|
+
if (out.artifact) {
|
|
2806
|
+
if (artifactProducers[out.artifact]) {
|
|
2807
|
+
throw new Error(
|
|
2808
|
+
`Artifact "${out.artifact}" is produced by both "${artifactProducers[out.artifact]}" and "${phase}". Each artifact must have exactly one producer.`
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
artifactProducers[out.artifact] = phase;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
const dependencies = {};
|
|
2815
|
+
for (const m of manifests) {
|
|
2816
|
+
const phase = manifestNameToPhase(m.name);
|
|
2817
|
+
if (!phase) continue;
|
|
2818
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2819
|
+
const inp = m.pipeline?.inputs;
|
|
2820
|
+
if (inp) {
|
|
2821
|
+
for (const sink of inp.sinks ?? []) {
|
|
2822
|
+
const producer = sinkProducers[sink];
|
|
2823
|
+
if (!producer) {
|
|
2824
|
+
throw new Error(
|
|
2825
|
+
`Phase "${phase}" declares sink input "${sink}" but no otter produces it. Add an otter with outputs.sinks including "${sink}".`
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
if (producer !== phase) deps.add(producer);
|
|
2829
|
+
}
|
|
2830
|
+
for (const artifact of inp.artifacts ?? []) {
|
|
2831
|
+
const producer = artifactProducers[artifact];
|
|
2832
|
+
if (!producer) {
|
|
2833
|
+
throw new Error(
|
|
2834
|
+
`Phase "${phase}" declares artifact input "${artifact}" but no otter produces it. Add an otter with outputs.artifact = "${artifact}".`
|
|
2835
|
+
);
|
|
2836
|
+
}
|
|
2837
|
+
if (producer !== phase) deps.add(producer);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
dependencies[phase] = Array.from(deps);
|
|
2841
|
+
}
|
|
2842
|
+
const order = topologicalSort(dependencies);
|
|
2843
|
+
return { dependencies, order, sinkProducers, artifactProducers };
|
|
2844
|
+
}
|
|
2845
|
+
var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
2846
|
+
function loadDistributedOtterManifests(cwd) {
|
|
2847
|
+
const results = [];
|
|
2848
|
+
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
2849
|
+
const nsDir = join4(cwd, "node_modules", namespace);
|
|
2850
|
+
let pkgDirNames;
|
|
2851
|
+
try {
|
|
2852
|
+
pkgDirNames = readdirSync2(nsDir);
|
|
2853
|
+
} catch {
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
for (const pkgDirName of pkgDirNames) {
|
|
2857
|
+
const pkgDir = join4(nsDir, pkgDirName);
|
|
2858
|
+
const packageName = `${namespace}/${pkgDirName}`;
|
|
2859
|
+
let otterRelPaths;
|
|
2860
|
+
try {
|
|
2861
|
+
const raw = readFileSync4(join4(pkgDir, "package.json"), "utf-8");
|
|
2862
|
+
const pkgJson = JSON.parse(raw);
|
|
2863
|
+
const declared = pkgJson.stackwright?.otters;
|
|
2864
|
+
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
2865
|
+
otterRelPaths = declared;
|
|
2866
|
+
} catch {
|
|
2867
|
+
continue;
|
|
2868
|
+
}
|
|
2869
|
+
for (const relPath of otterRelPaths) {
|
|
2870
|
+
const otterFilePath = join4(pkgDir, relPath);
|
|
2871
|
+
try {
|
|
2872
|
+
const raw = readFileSync4(otterFilePath, "utf-8");
|
|
2873
|
+
const manifest = JSON.parse(raw);
|
|
2874
|
+
results.push({ manifest, packageName });
|
|
2875
|
+
} catch (err) {
|
|
2876
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2877
|
+
console.warn(
|
|
2878
|
+
`[pipeline-graph] Failed to load distributed otter at "${otterFilePath}" from "${packageName}": ${msg}`
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
return results;
|
|
2885
|
+
}
|
|
2886
|
+
function _mergeManifestPools(central, distributed) {
|
|
2887
|
+
const centralNames = new Set(central.map((m) => m.name));
|
|
2888
|
+
const merged = [...central];
|
|
2889
|
+
for (const { manifest, packageName } of distributed) {
|
|
2890
|
+
if (centralNames.has(manifest.name)) {
|
|
2891
|
+
console.warn(
|
|
2892
|
+
`[pipeline-graph] Otter "${manifest.name}" declared by both @stackwright-pro/otters and ${packageName} \u2014 using @stackwright-pro/otters version.`
|
|
2893
|
+
);
|
|
2894
|
+
} else {
|
|
2895
|
+
merged.push(manifest);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return merged;
|
|
2899
|
+
}
|
|
2900
|
+
var _cachedGraph = null;
|
|
2901
|
+
function loadPipelineGraph() {
|
|
2902
|
+
if (_cachedGraph) return _cachedGraph;
|
|
2903
|
+
const otterDir = resolveOtterDirFromCwd();
|
|
2904
|
+
const central = loadOtterManifestsFromDir(otterDir);
|
|
2905
|
+
const distributed = loadDistributedOtterManifests(process.cwd());
|
|
2906
|
+
const manifests = _mergeManifestPools(central, distributed);
|
|
2907
|
+
_cachedGraph = buildPipelineGraph(manifests);
|
|
2908
|
+
return _cachedGraph;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2693
2911
|
// src/tools/pipeline.ts
|
|
2694
2912
|
var PHASE_ORDER = [
|
|
2695
2913
|
"designer",
|
|
@@ -2698,45 +2916,16 @@ var PHASE_ORDER = [
|
|
|
2698
2916
|
// generates app/ directory from config
|
|
2699
2917
|
"api",
|
|
2700
2918
|
"data",
|
|
2919
|
+
"auth",
|
|
2920
|
+
// moved earlier — only depends on design-language.json (designer)
|
|
2701
2921
|
"geo",
|
|
2702
2922
|
"workflow",
|
|
2703
2923
|
"services",
|
|
2704
2924
|
"pages",
|
|
2705
2925
|
"dashboard",
|
|
2706
|
-
"
|
|
2707
|
-
"
|
|
2926
|
+
"polish",
|
|
2927
|
+
"qa"
|
|
2708
2928
|
];
|
|
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
2929
|
var PHASE_ARTIFACT = {
|
|
2741
2930
|
designer: "design-language.json",
|
|
2742
2931
|
theme: "theme-tokens.json",
|
|
@@ -2749,7 +2938,8 @@ var PHASE_ARTIFACT = {
|
|
|
2749
2938
|
workflow: "workflow-config.json",
|
|
2750
2939
|
services: "services-config.json",
|
|
2751
2940
|
polish: "polish-manifest.json",
|
|
2752
|
-
geo: "geo-manifest.json"
|
|
2941
|
+
geo: "geo-manifest.json",
|
|
2942
|
+
qa: "qa-findings.json"
|
|
2753
2943
|
};
|
|
2754
2944
|
var PHASE_TO_OTTER2 = {
|
|
2755
2945
|
designer: "stackwright-pro-designer-otter",
|
|
@@ -2763,7 +2953,8 @@ var PHASE_TO_OTTER2 = {
|
|
|
2763
2953
|
workflow: "stackwright-pro-form-wizard-otter",
|
|
2764
2954
|
services: "stackwright-services-otter",
|
|
2765
2955
|
polish: "stackwright-pro-polish-otter",
|
|
2766
|
-
geo: "stackwright-pro-geo-otter"
|
|
2956
|
+
geo: "stackwright-pro-geo-otter",
|
|
2957
|
+
qa: "stackwright-pro-qa-otter"
|
|
2767
2958
|
};
|
|
2768
2959
|
function isValidPhase2(phase) {
|
|
2769
2960
|
return PHASE_ORDER.includes(phase);
|
|
@@ -2793,7 +2984,7 @@ function createDefaultState() {
|
|
|
2793
2984
|
};
|
|
2794
2985
|
}
|
|
2795
2986
|
function statePath(cwd) {
|
|
2796
|
-
return
|
|
2987
|
+
return join5(cwd, ".stackwright", "pipeline-state.json");
|
|
2797
2988
|
}
|
|
2798
2989
|
function readState(cwd) {
|
|
2799
2990
|
const p = statePath(cwd);
|
|
@@ -2806,7 +2997,7 @@ function readState(cwd) {
|
|
|
2806
2997
|
}
|
|
2807
2998
|
function safeWriteSync(filePath, content) {
|
|
2808
2999
|
if (existsSync5(filePath)) {
|
|
2809
|
-
const stat =
|
|
3000
|
+
const stat = lstatSync6(filePath);
|
|
2810
3001
|
if (stat.isSymbolicLink()) {
|
|
2811
3002
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
2812
3003
|
}
|
|
@@ -2815,15 +3006,15 @@ function safeWriteSync(filePath, content) {
|
|
|
2815
3006
|
}
|
|
2816
3007
|
function safeReadSync(filePath) {
|
|
2817
3008
|
if (existsSync5(filePath)) {
|
|
2818
|
-
const stat =
|
|
3009
|
+
const stat = lstatSync6(filePath);
|
|
2819
3010
|
if (stat.isSymbolicLink()) {
|
|
2820
3011
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
2821
3012
|
}
|
|
2822
3013
|
}
|
|
2823
|
-
return
|
|
3014
|
+
return readFileSync5(filePath, "utf-8");
|
|
2824
3015
|
}
|
|
2825
3016
|
function writeState(cwd, state) {
|
|
2826
|
-
const dir =
|
|
3017
|
+
const dir = join5(cwd, ".stackwright");
|
|
2827
3018
|
mkdirSync4(dir, { recursive: true });
|
|
2828
3019
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2829
3020
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
@@ -2845,7 +3036,7 @@ function handleGetPipelineState(_cwd) {
|
|
|
2845
3036
|
const cwd = _cwd ?? process.cwd();
|
|
2846
3037
|
try {
|
|
2847
3038
|
const state = readState(cwd);
|
|
2848
|
-
const keyPath =
|
|
3039
|
+
const keyPath = join5(cwd, ".stackwright", "pipeline-keys.json");
|
|
2849
3040
|
if (!existsSync5(keyPath)) {
|
|
2850
3041
|
try {
|
|
2851
3042
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
@@ -2949,7 +3140,7 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2949
3140
|
isError: true
|
|
2950
3141
|
};
|
|
2951
3142
|
}
|
|
2952
|
-
const answerFile =
|
|
3143
|
+
const answerFile = join5(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
2953
3144
|
if (!existsSync5(answerFile)) {
|
|
2954
3145
|
return {
|
|
2955
3146
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
@@ -2974,11 +3165,11 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
2974
3165
|
}
|
|
2975
3166
|
}
|
|
2976
3167
|
try {
|
|
2977
|
-
const answersDir =
|
|
3168
|
+
const answersDir = join5(cwd, ".stackwright", "answers");
|
|
2978
3169
|
const answeredPhases = [];
|
|
2979
3170
|
const missingPhases = [];
|
|
2980
3171
|
for (const phase2 of PHASE_ORDER) {
|
|
2981
|
-
const answerFile =
|
|
3172
|
+
const answerFile = join5(answersDir, `${phase2}.json`);
|
|
2982
3173
|
if (existsSync5(answerFile)) {
|
|
2983
3174
|
try {
|
|
2984
3175
|
const raw = safeReadSync(answerFile);
|
|
@@ -3012,6 +3203,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3012
3203
|
const cwd = _cwd ?? process.cwd();
|
|
3013
3204
|
try {
|
|
3014
3205
|
const state = readState(cwd);
|
|
3206
|
+
const graph = loadPipelineGraph();
|
|
3015
3207
|
const completed = [];
|
|
3016
3208
|
const ready = [];
|
|
3017
3209
|
const blocked = [];
|
|
@@ -3021,7 +3213,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3021
3213
|
completed.push(phase);
|
|
3022
3214
|
continue;
|
|
3023
3215
|
}
|
|
3024
|
-
const deps =
|
|
3216
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3025
3217
|
const unmetDeps = deps.filter((dep) => !state.phases[dep]?.executed);
|
|
3026
3218
|
if (unmetDeps.length === 0) {
|
|
3027
3219
|
ready.push(phase);
|
|
@@ -3047,7 +3239,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3047
3239
|
function handleListArtifacts(_cwd) {
|
|
3048
3240
|
const cwd = _cwd ?? process.cwd();
|
|
3049
3241
|
try {
|
|
3050
|
-
const artifactsDir =
|
|
3242
|
+
const artifactsDir = join5(cwd, ".stackwright", "artifacts");
|
|
3051
3243
|
let manifest = null;
|
|
3052
3244
|
try {
|
|
3053
3245
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3057,7 +3249,7 @@ function handleListArtifacts(_cwd) {
|
|
|
3057
3249
|
let completedCount = 0;
|
|
3058
3250
|
for (const phase of PHASE_ORDER) {
|
|
3059
3251
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3060
|
-
const fullPath =
|
|
3252
|
+
const fullPath = join5(artifactsDir, expectedFile);
|
|
3061
3253
|
const exists = existsSync5(fullPath);
|
|
3062
3254
|
if (exists) completedCount++;
|
|
3063
3255
|
let signed = false;
|
|
@@ -3111,9 +3303,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3111
3303
|
}
|
|
3112
3304
|
} catch {
|
|
3113
3305
|
}
|
|
3114
|
-
const questionsDir =
|
|
3306
|
+
const questionsDir = join5(cwd, ".stackwright", "questions");
|
|
3115
3307
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3116
|
-
const filePath =
|
|
3308
|
+
const filePath = join5(questionsDir, `${phase}.json`);
|
|
3117
3309
|
const payload = {
|
|
3118
3310
|
version: "1.0",
|
|
3119
3311
|
phase,
|
|
@@ -3153,13 +3345,13 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3153
3345
|
};
|
|
3154
3346
|
}
|
|
3155
3347
|
try {
|
|
3156
|
-
const answersPath =
|
|
3348
|
+
const answersPath = join5(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3157
3349
|
let answers = {};
|
|
3158
3350
|
if (existsSync5(answersPath)) {
|
|
3159
3351
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
3160
3352
|
}
|
|
3161
3353
|
let buildContextText = "";
|
|
3162
|
-
const buildContextPath =
|
|
3354
|
+
const buildContextPath = join5(cwd, ".stackwright", "build-context.json");
|
|
3163
3355
|
if (existsSync5(buildContextPath)) {
|
|
3164
3356
|
try {
|
|
3165
3357
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
@@ -3169,12 +3361,13 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3169
3361
|
} catch {
|
|
3170
3362
|
}
|
|
3171
3363
|
}
|
|
3172
|
-
const
|
|
3364
|
+
const graph = loadPipelineGraph();
|
|
3365
|
+
const deps = graph.dependencies[phase] ?? [];
|
|
3173
3366
|
const artifactSections = [];
|
|
3174
3367
|
const missingDependencies = [];
|
|
3175
3368
|
for (const dep of deps) {
|
|
3176
3369
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
3177
|
-
const artifactPath =
|
|
3370
|
+
const artifactPath = join5(cwd, ".stackwright", "artifacts", artifactFile);
|
|
3178
3371
|
if (existsSync5(artifactPath)) {
|
|
3179
3372
|
const rawContent = safeReadSync(artifactPath);
|
|
3180
3373
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
@@ -3240,7 +3433,7 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
3240
3433
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
3241
3434
|
}
|
|
3242
3435
|
if (phase === "auth") {
|
|
3243
|
-
const initContextPath =
|
|
3436
|
+
const initContextPath = join5(cwd, ".stackwright", "init-context.json");
|
|
3244
3437
|
if (existsSync5(initContextPath)) {
|
|
3245
3438
|
try {
|
|
3246
3439
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
@@ -3305,7 +3498,10 @@ var PHASE_REQUIRED_KEYS = {
|
|
|
3305
3498
|
workflow: ["version", "generatedBy"],
|
|
3306
3499
|
services: ["version", "generatedBy", "flows"],
|
|
3307
3500
|
polish: ["version", "generatedBy"],
|
|
3308
|
-
geo: ["version", "generatedBy", "geoCollections"]
|
|
3501
|
+
geo: ["version", "generatedBy", "geoCollections"],
|
|
3502
|
+
// qa: skipped=true path only needs version+generatedBy+skipped;
|
|
3503
|
+
// skipped=false path also needs summary+findings — Zod validator enforces the conditional.
|
|
3504
|
+
qa: ["version", "generatedBy", "skipped"]
|
|
3309
3505
|
};
|
|
3310
3506
|
var PHASE_ARTIFACT_SCHEMA = {
|
|
3311
3507
|
designer: JSON.stringify(
|
|
@@ -3595,6 +3791,41 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3595
3791
|
},
|
|
3596
3792
|
null,
|
|
3597
3793
|
2
|
|
3794
|
+
),
|
|
3795
|
+
qa: JSON.stringify(
|
|
3796
|
+
{
|
|
3797
|
+
version: "1.0",
|
|
3798
|
+
generatedBy: "stackwright-pro-qa-otter",
|
|
3799
|
+
// skipped: true path — when dev server is unreachable at audit time
|
|
3800
|
+
// skipped: false path — full audit completed
|
|
3801
|
+
skipped: false,
|
|
3802
|
+
skipReason: null,
|
|
3803
|
+
wcagLevel: "<AA|AAA>",
|
|
3804
|
+
summary: {
|
|
3805
|
+
routesAudited: 3,
|
|
3806
|
+
serious: 0,
|
|
3807
|
+
moderate: 1,
|
|
3808
|
+
minor: 0
|
|
3809
|
+
},
|
|
3810
|
+
findings: [
|
|
3811
|
+
{
|
|
3812
|
+
id: "qa-001",
|
|
3813
|
+
route: "/dashboard",
|
|
3814
|
+
severity: "<serious|moderate|minor>",
|
|
3815
|
+
category: "<visual|a11y|design-contract|runtime>",
|
|
3816
|
+
finding: "Human-readable description of what was observed",
|
|
3817
|
+
evidence: {
|
|
3818
|
+
screenshot: ".stackwright/qa/screenshots/dashboard-light.png",
|
|
3819
|
+
consoleErrors: [],
|
|
3820
|
+
axeViolations: []
|
|
3821
|
+
},
|
|
3822
|
+
suggested_otters: ["stackwright-pro-theme-otter"],
|
|
3823
|
+
suggested_fix: "Specific, concrete next step the repair otter should take"
|
|
3824
|
+
}
|
|
3825
|
+
]
|
|
3826
|
+
},
|
|
3827
|
+
null,
|
|
3828
|
+
2
|
|
3598
3829
|
)
|
|
3599
3830
|
};
|
|
3600
3831
|
function handleValidateArtifact(input) {
|
|
@@ -3708,13 +3939,13 @@ function handleValidateArtifact(input) {
|
|
|
3708
3939
|
// to a flow or state-machine in this services-config artifact.
|
|
3709
3940
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
3710
3941
|
services: (artifact2) => {
|
|
3711
|
-
const workflowArtifactPath =
|
|
3942
|
+
const workflowArtifactPath = join5(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
3712
3943
|
if (!existsSync5(workflowArtifactPath)) {
|
|
3713
3944
|
return { success: true };
|
|
3714
3945
|
}
|
|
3715
3946
|
let workflowArtifact;
|
|
3716
3947
|
try {
|
|
3717
|
-
workflowArtifact = JSON.parse(
|
|
3948
|
+
workflowArtifact = JSON.parse(readFileSync5(workflowArtifactPath, "utf-8"));
|
|
3718
3949
|
} catch {
|
|
3719
3950
|
return { success: true };
|
|
3720
3951
|
}
|
|
@@ -3735,6 +3966,40 @@ function handleValidateArtifact(input) {
|
|
|
3735
3966
|
};
|
|
3736
3967
|
}
|
|
3737
3968
|
return { success: true };
|
|
3969
|
+
},
|
|
3970
|
+
// qa artifact — permissive validator: requires version + generatedBy + skipped.
|
|
3971
|
+
// When skipped=true: skipReason must be a string.
|
|
3972
|
+
// When skipped=false: findings must be an array and summary must be an object.
|
|
3973
|
+
qa: (artifact2) => {
|
|
3974
|
+
const skipped = artifact2["skipped"];
|
|
3975
|
+
if (typeof skipped !== "boolean") {
|
|
3976
|
+
return {
|
|
3977
|
+
success: false,
|
|
3978
|
+
error: { message: '"skipped" must be a boolean (true or false).' }
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
if (skipped === true) {
|
|
3982
|
+
if (typeof artifact2["skipReason"] !== "string") {
|
|
3983
|
+
return {
|
|
3984
|
+
success: false,
|
|
3985
|
+
error: { message: 'When skipped=true, "skipReason" must be a string.' }
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
return { success: true };
|
|
3989
|
+
}
|
|
3990
|
+
if (!Array.isArray(artifact2["findings"])) {
|
|
3991
|
+
return {
|
|
3992
|
+
success: false,
|
|
3993
|
+
error: { message: 'When skipped=false, "findings" must be an array.' }
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
if (typeof artifact2["summary"] !== "object" || artifact2["summary"] === null) {
|
|
3997
|
+
return {
|
|
3998
|
+
success: false,
|
|
3999
|
+
error: { message: 'When skipped=false, "summary" must be an object.' }
|
|
4000
|
+
};
|
|
4001
|
+
}
|
|
4002
|
+
return { success: true };
|
|
3738
4003
|
}
|
|
3739
4004
|
};
|
|
3740
4005
|
const zodValidator = PHASE_ZOD_VALIDATORS[phase];
|
|
@@ -3751,10 +4016,10 @@ function handleValidateArtifact(input) {
|
|
|
3751
4016
|
}
|
|
3752
4017
|
}
|
|
3753
4018
|
try {
|
|
3754
|
-
const artifactsDir =
|
|
4019
|
+
const artifactsDir = join5(cwd, ".stackwright", "artifacts");
|
|
3755
4020
|
mkdirSync4(artifactsDir, { recursive: true });
|
|
3756
4021
|
const artifactFile = PHASE_ARTIFACT[phase];
|
|
3757
|
-
const artifactPath =
|
|
4022
|
+
const artifactPath = join5(artifactsDir, artifactFile);
|
|
3758
4023
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
3759
4024
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
3760
4025
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -3871,9 +4136,9 @@ function registerPipelineTools(server2) {
|
|
|
3871
4136
|
isError: true
|
|
3872
4137
|
};
|
|
3873
4138
|
}
|
|
3874
|
-
const questionsDir =
|
|
4139
|
+
const questionsDir = join5(process.cwd(), ".stackwright", "questions");
|
|
3875
4140
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3876
|
-
const outPath =
|
|
4141
|
+
const outPath = join5(questionsDir, `${phase}.json`);
|
|
3877
4142
|
writeFileSync4(
|
|
3878
4143
|
outPath,
|
|
3879
4144
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
@@ -3925,8 +4190,8 @@ function registerPipelineTools(server2) {
|
|
|
3925
4190
|
|
|
3926
4191
|
// src/tools/safe-write.ts
|
|
3927
4192
|
import { z as z14 } from "zod";
|
|
3928
|
-
import { writeFileSync as writeFileSync5, readFileSync as
|
|
3929
|
-
import { normalize, isAbsolute, dirname, join as
|
|
4193
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
|
|
4194
|
+
import { normalize, isAbsolute, dirname, join as join6 } from "path";
|
|
3930
4195
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
3931
4196
|
"stackwright-pro-designer-otter": [
|
|
3932
4197
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
|
|
@@ -3941,8 +4206,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3941
4206
|
],
|
|
3942
4207
|
"stackwright-pro-auth-otter": [
|
|
3943
4208
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
|
|
3944
|
-
{
|
|
3945
|
-
|
|
4209
|
+
{
|
|
4210
|
+
prefix: "stackwright.auth.",
|
|
4211
|
+
suffix: ".yml",
|
|
4212
|
+
description: "Auth config YAML (stackwright.auth.yml \u2014 compiled to _auth.json)"
|
|
4213
|
+
},
|
|
3946
4214
|
{
|
|
3947
4215
|
prefix: ".env",
|
|
3948
4216
|
suffix: "",
|
|
@@ -3961,7 +4229,11 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3961
4229
|
],
|
|
3962
4230
|
"stackwright-pro-data-otter": [
|
|
3963
4231
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Data config artifact" },
|
|
3964
|
-
{
|
|
4232
|
+
{
|
|
4233
|
+
prefix: "stackwright.collections.",
|
|
4234
|
+
suffix: ".yml",
|
|
4235
|
+
description: "Collections config YAML (stackwright.collections.yml \u2014 compiled to _collections.json)"
|
|
4236
|
+
}
|
|
3965
4237
|
],
|
|
3966
4238
|
"stackwright-pro-page-otter": [
|
|
3967
4239
|
{ prefix: "pages/", suffix: "/content.yml", description: "Page content YAML" },
|
|
@@ -3987,7 +4259,12 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3987
4259
|
}
|
|
3988
4260
|
],
|
|
3989
4261
|
"stackwright-pro-api-otter": [
|
|
3990
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" }
|
|
4262
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "API config artifact" },
|
|
4263
|
+
{
|
|
4264
|
+
prefix: "stackwright.integrations.",
|
|
4265
|
+
suffix: ".yml",
|
|
4266
|
+
description: "Integrations config YAML (stackwright.integrations.yml \u2014 compiled to _integrations.json)"
|
|
4267
|
+
}
|
|
3991
4268
|
],
|
|
3992
4269
|
"stackwright-pro-geo-otter": [
|
|
3993
4270
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Geo manifest artifact" },
|
|
@@ -4010,6 +4287,25 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
4010
4287
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" },
|
|
4011
4288
|
{ prefix: "README.md", suffix: "", description: "Project README rewrite" }
|
|
4012
4289
|
],
|
|
4290
|
+
// QA otter writes ONLY to the qa/ directory and the qa-findings artifact.
|
|
4291
|
+
// It never writes .tsx, .ts, .yml, or .json outside these paths.
|
|
4292
|
+
"stackwright-pro-qa-otter": [
|
|
4293
|
+
{
|
|
4294
|
+
prefix: ".stackwright/artifacts/qa-findings.json",
|
|
4295
|
+
suffix: "",
|
|
4296
|
+
description: "QA findings artifact"
|
|
4297
|
+
},
|
|
4298
|
+
{
|
|
4299
|
+
prefix: ".stackwright/qa/",
|
|
4300
|
+
suffix: ".md",
|
|
4301
|
+
description: "QA report markdown"
|
|
4302
|
+
},
|
|
4303
|
+
{
|
|
4304
|
+
prefix: ".stackwright/qa/screenshots/",
|
|
4305
|
+
suffix: ".png",
|
|
4306
|
+
description: "Route screenshots for evidence"
|
|
4307
|
+
}
|
|
4308
|
+
],
|
|
4013
4309
|
// domain-expert-otter is a reader/answerer only — it writes via stackwright_pro_save_phase_answers,
|
|
4014
4310
|
// not via safe_write. Entry required here because the MCP tool availability guard boilerplate
|
|
4015
4311
|
// in its system prompt mentions stackwright_pro_safe_write (triggering the coherence test).
|
|
@@ -4066,22 +4362,22 @@ function extractContentTypes(yamlContent) {
|
|
|
4066
4362
|
return types.length > 0 ? types : ["unknown"];
|
|
4067
4363
|
}
|
|
4068
4364
|
function readPageRegistry(cwd) {
|
|
4069
|
-
const regPath =
|
|
4365
|
+
const regPath = join6(cwd, PAGE_REGISTRY_FILE);
|
|
4070
4366
|
if (!existsSync6(regPath)) return {};
|
|
4071
4367
|
try {
|
|
4072
|
-
const stat =
|
|
4368
|
+
const stat = lstatSync7(regPath);
|
|
4073
4369
|
if (stat.isSymbolicLink()) return {};
|
|
4074
|
-
return JSON.parse(
|
|
4370
|
+
return JSON.parse(readFileSync6(regPath, "utf-8"));
|
|
4075
4371
|
} catch {
|
|
4076
4372
|
return {};
|
|
4077
4373
|
}
|
|
4078
4374
|
}
|
|
4079
4375
|
function writePageRegistry(cwd, registry) {
|
|
4080
|
-
const dir =
|
|
4376
|
+
const dir = join6(cwd, ".stackwright");
|
|
4081
4377
|
mkdirSync5(dir, { recursive: true });
|
|
4082
|
-
const regPath =
|
|
4378
|
+
const regPath = join6(cwd, PAGE_REGISTRY_FILE);
|
|
4083
4379
|
if (existsSync6(regPath)) {
|
|
4084
|
-
const stat =
|
|
4380
|
+
const stat = lstatSync7(regPath);
|
|
4085
4381
|
if (stat.isSymbolicLink()) {
|
|
4086
4382
|
throw new Error("Refusing to write page registry through symlink");
|
|
4087
4383
|
}
|
|
@@ -4101,8 +4397,8 @@ function checkPageOwnership(cwd, slug, callerOtter) {
|
|
|
4101
4397
|
};
|
|
4102
4398
|
}
|
|
4103
4399
|
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
|
|
4400
|
+
function stripNextjsBracketSegments(path4) {
|
|
4401
|
+
return path4.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
|
|
4106
4402
|
}
|
|
4107
4403
|
function checkPathAllowed(callerOtter, filePath) {
|
|
4108
4404
|
const normalized = normalize(filePath);
|
|
@@ -4252,10 +4548,10 @@ function handleSafeWrite(input) {
|
|
|
4252
4548
|
return { text: JSON.stringify(result), isError: true };
|
|
4253
4549
|
}
|
|
4254
4550
|
const normalized = normalize(filePath);
|
|
4255
|
-
const fullPath =
|
|
4551
|
+
const fullPath = join6(cwd, normalized);
|
|
4256
4552
|
if (existsSync6(fullPath)) {
|
|
4257
4553
|
try {
|
|
4258
|
-
const stat =
|
|
4554
|
+
const stat = lstatSync7(fullPath);
|
|
4259
4555
|
if (stat.isSymbolicLink()) {
|
|
4260
4556
|
const result = {
|
|
4261
4557
|
success: false,
|
|
@@ -4358,8 +4654,8 @@ function registerSafeWriteTools(server2) {
|
|
|
4358
4654
|
|
|
4359
4655
|
// src/tools/auth.ts
|
|
4360
4656
|
import { z as z15 } from "zod";
|
|
4361
|
-
import { readFileSync as
|
|
4362
|
-
import { join as
|
|
4657
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
4658
|
+
import { join as join7 } from "path";
|
|
4363
4659
|
function buildHierarchy(roles) {
|
|
4364
4660
|
const h = {};
|
|
4365
4661
|
for (let i = 0; i < roles.length - 1; i++) {
|
|
@@ -4373,7 +4669,7 @@ function hierarchyToYaml(hierarchy, indent) {
|
|
|
4373
4669
|
return entries.map(([role, subs]) => `${indent}${role}: [${subs.join(", ")}]`).join("\n");
|
|
4374
4670
|
}
|
|
4375
4671
|
function rolesToYaml(roles, indent) {
|
|
4376
|
-
return roles.map((r) => `${indent}- ${r}`).join("\n");
|
|
4672
|
+
return roles.map((r) => `${indent}- name: ${r}`).join("\n");
|
|
4377
4673
|
}
|
|
4378
4674
|
function normalizeRoutes(routes, defaultRole) {
|
|
4379
4675
|
return routes.map(
|
|
@@ -4386,9 +4682,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
4386
4682
|
}
|
|
4387
4683
|
function detectNextMajorVersion(cwd) {
|
|
4388
4684
|
try {
|
|
4389
|
-
const pkgPath =
|
|
4685
|
+
const pkgPath = join7(cwd, "package.json");
|
|
4390
4686
|
if (!existsSync7(pkgPath)) return null;
|
|
4391
|
-
const pkg = JSON.parse(
|
|
4687
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
4392
4688
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
4393
4689
|
if (!nextVersion) return null;
|
|
4394
4690
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -4397,25 +4693,6 @@ function detectNextMajorVersion(cwd) {
|
|
|
4397
4693
|
return null;
|
|
4398
4694
|
}
|
|
4399
4695
|
}
|
|
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
4696
|
function generateMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
|
|
4420
4697
|
const rbacBlock = ` rbac: {
|
|
4421
4698
|
roles: ${JSON.stringify(roles)},
|
|
@@ -4615,33 +4892,33 @@ OAUTH2_CLIENT_SECRET=your-client-secret
|
|
|
4615
4892
|
`;
|
|
4616
4893
|
}
|
|
4617
4894
|
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 ? `
|
|
4895
|
+
const rbacSection = `rbac:
|
|
4896
|
+
roles:
|
|
4897
|
+
${rolesToYaml(roles, " ")}
|
|
4898
|
+
defaultRole: ${defaultRole}
|
|
4899
|
+
hierarchy:
|
|
4900
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
4901
|
+
const auditSection = `audit:
|
|
4902
|
+
enabled: ${auditEnabled}
|
|
4903
|
+
retentionDays: ${auditRetentionDays}`;
|
|
4904
|
+
const routeLines = routesToYaml(protectedRoutes, " ");
|
|
4905
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4629
4906
|
` : "";
|
|
4630
4907
|
if (method === "cac") {
|
|
4631
4908
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4632
4909
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4633
4910
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4634
4911
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4635
|
-
return
|
|
4636
|
-
|
|
4637
|
-
${providerLine}
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4912
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4913
|
+
method: cac
|
|
4914
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4915
|
+
cac:
|
|
4916
|
+
caBundle: \${CAC_CA_BUNDLE}
|
|
4917
|
+
edipiLookup: ${edipiLookup}
|
|
4918
|
+
ocspEndpoint: \${CAC_OCSP_ENDPOINT}
|
|
4919
|
+
certHeader: ${certHeader}
|
|
4643
4920
|
${rbacSection}
|
|
4644
|
-
|
|
4921
|
+
protectedRoutes:
|
|
4645
4922
|
${routeLines}
|
|
4646
4923
|
${auditSection}
|
|
4647
4924
|
`;
|
|
@@ -4649,33 +4926,33 @@ ${auditSection}
|
|
|
4649
4926
|
if (method === "oidc") {
|
|
4650
4927
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4651
4928
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4652
|
-
return
|
|
4653
|
-
|
|
4654
|
-
${providerLine}
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4929
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4930
|
+
method: oidc
|
|
4931
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4932
|
+
oidc:
|
|
4933
|
+
discoveryUrl: \${OIDC_DISCOVERY_URL}
|
|
4934
|
+
clientId: \${OIDC_CLIENT_ID}
|
|
4935
|
+
clientSecret: \${OIDC_CLIENT_SECRET}
|
|
4936
|
+
scopes: ${scopes2}
|
|
4937
|
+
roleClaim: ${roleClaim}
|
|
4661
4938
|
${rbacSection}
|
|
4662
|
-
|
|
4939
|
+
protectedRoutes:
|
|
4663
4940
|
${routeLines}
|
|
4664
4941
|
${auditSection}
|
|
4665
4942
|
`;
|
|
4666
4943
|
}
|
|
4667
4944
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4668
|
-
return
|
|
4669
|
-
|
|
4670
|
-
${providerLine}
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4945
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
4946
|
+
method: oauth2
|
|
4947
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
4948
|
+
oauth2:
|
|
4949
|
+
authorizationUrl: \${OAUTH2_AUTH_URL}
|
|
4950
|
+
tokenUrl: \${OAUTH2_TOKEN_URL}
|
|
4951
|
+
clientId: \${OAUTH2_CLIENT_ID}
|
|
4952
|
+
clientSecret: \${OAUTH2_CLIENT_SECRET}
|
|
4953
|
+
scopes: ${scopes}
|
|
4677
4954
|
${rbacSection}
|
|
4678
|
-
|
|
4955
|
+
protectedRoutes:
|
|
4679
4956
|
${routeLines}
|
|
4680
4957
|
${auditSection}
|
|
4681
4958
|
`;
|
|
@@ -4861,35 +5138,35 @@ ${configBlock}
|
|
|
4861
5138
|
`;
|
|
4862
5139
|
}
|
|
4863
5140
|
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 ? `
|
|
5141
|
+
const rbacSection = `rbac:
|
|
5142
|
+
roles:
|
|
5143
|
+
${rolesToYaml(roles, " ")}
|
|
5144
|
+
defaultRole: ${defaultRole}
|
|
5145
|
+
hierarchy:
|
|
5146
|
+
${hierarchyToYaml(hierarchy, " ")}`;
|
|
5147
|
+
const auditSection = `audit:
|
|
5148
|
+
enabled: ${auditEnabled}
|
|
5149
|
+
retentionDays: ${auditRetentionDays}`;
|
|
5150
|
+
const routeLines = protectedRoutes.map((r) => ` - pattern: ${r.pattern}
|
|
5151
|
+
requiredRole: ${r.requiredRole}`).join("\n");
|
|
5152
|
+
const providerLine = params.provider ? `provider: ${params.provider}
|
|
4876
5153
|
` : "";
|
|
4877
5154
|
if (method === "cac") {
|
|
4878
5155
|
const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
|
|
4879
5156
|
const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
|
|
4880
5157
|
const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
|
|
4881
5158
|
const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
|
|
4882
|
-
return
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
${providerLine}
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
5159
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5160
|
+
method: cac
|
|
5161
|
+
devOnly: true
|
|
5162
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5163
|
+
cac:
|
|
5164
|
+
caBundle: ${caBundle}
|
|
5165
|
+
edipiLookup: ${edipiLookup}
|
|
5166
|
+
ocspEndpoint: ${ocspEndpoint}
|
|
5167
|
+
certHeader: ${certHeader}
|
|
4891
5168
|
${rbacSection}
|
|
4892
|
-
|
|
5169
|
+
protectedRoutes:
|
|
4893
5170
|
${routeLines}
|
|
4894
5171
|
${auditSection}
|
|
4895
5172
|
`;
|
|
@@ -4897,35 +5174,35 @@ ${auditSection}
|
|
|
4897
5174
|
if (method === "oidc") {
|
|
4898
5175
|
const scopes2 = params.oidcScopes ?? "openid profile email";
|
|
4899
5176
|
const roleClaim = params.oidcRoleClaim ?? "roles";
|
|
4900
|
-
return
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
${providerLine}
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
5177
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5178
|
+
method: oidc
|
|
5179
|
+
devOnly: true
|
|
5180
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5181
|
+
oidc:
|
|
5182
|
+
discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
|
|
5183
|
+
clientId: dev-mock-client
|
|
5184
|
+
clientSecret: dev-mock-secret
|
|
5185
|
+
scopes: ${scopes2}
|
|
5186
|
+
roleClaim: ${roleClaim}
|
|
4910
5187
|
${rbacSection}
|
|
4911
|
-
|
|
5188
|
+
protectedRoutes:
|
|
4912
5189
|
${routeLines}
|
|
4913
5190
|
${auditSection}
|
|
4914
5191
|
`;
|
|
4915
5192
|
}
|
|
4916
5193
|
const scopes = params.oauth2Scopes ?? "read write";
|
|
4917
|
-
return
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
${providerLine}
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
5194
|
+
return `# stackwright.auth.yml \u2014 generated by Auth Otter
|
|
5195
|
+
method: oauth2
|
|
5196
|
+
devOnly: true
|
|
5197
|
+
${providerLine}${useProxy ? "proxy" : "middleware"}: ./${useProxy ? "proxy" : "middleware"}.ts
|
|
5198
|
+
oauth2:
|
|
5199
|
+
authorizationUrl: https://dev-mock-oauth2/authorize
|
|
5200
|
+
tokenUrl: https://dev-mock-oauth2/token
|
|
5201
|
+
clientId: dev-mock-client
|
|
5202
|
+
clientSecret: dev-mock-secret
|
|
5203
|
+
scopes: ${scopes}
|
|
4927
5204
|
${rbacSection}
|
|
4928
|
-
|
|
5205
|
+
protectedRoutes:
|
|
4929
5206
|
${routeLines}
|
|
4930
5207
|
${auditSection}
|
|
4931
5208
|
`;
|
|
@@ -5070,7 +5347,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5070
5347
|
auditRetentionDays,
|
|
5071
5348
|
normalizedRoutes
|
|
5072
5349
|
);
|
|
5073
|
-
writeFileSync6(
|
|
5350
|
+
writeFileSync6(join7(cwd, conventionFile), authFileContent, "utf8");
|
|
5074
5351
|
filesWritten.push(conventionFile);
|
|
5075
5352
|
} catch (err) {
|
|
5076
5353
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5090,9 +5367,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5090
5367
|
if (!devOnly) {
|
|
5091
5368
|
try {
|
|
5092
5369
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
5093
|
-
const envPath =
|
|
5370
|
+
const envPath = join7(cwd, ".env.example");
|
|
5094
5371
|
if (existsSync7(envPath)) {
|
|
5095
|
-
const existing =
|
|
5372
|
+
const existing = readFileSync7(envPath, "utf8");
|
|
5096
5373
|
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
5097
5374
|
} else {
|
|
5098
5375
|
writeFileSync6(envPath, envBlock, "utf8");
|
|
@@ -5113,10 +5390,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5113
5390
|
}
|
|
5114
5391
|
if (devOnly) {
|
|
5115
5392
|
try {
|
|
5116
|
-
const mockAuthDir =
|
|
5393
|
+
const mockAuthDir = join7(cwd, "lib");
|
|
5117
5394
|
mkdirSync6(mockAuthDir, { recursive: true });
|
|
5118
5395
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
5119
|
-
writeFileSync6(
|
|
5396
|
+
writeFileSync6(join7(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
5120
5397
|
filesWritten.push("lib/mock-auth.ts");
|
|
5121
5398
|
} catch (err) {
|
|
5122
5399
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5134,9 +5411,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5134
5411
|
};
|
|
5135
5412
|
}
|
|
5136
5413
|
try {
|
|
5137
|
-
const pkgPath =
|
|
5414
|
+
const pkgPath = join7(cwd, "package.json");
|
|
5138
5415
|
if (existsSync7(pkgPath)) {
|
|
5139
|
-
const existingPkg =
|
|
5416
|
+
const existingPkg = readFileSync7(pkgPath, "utf8");
|
|
5140
5417
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
5141
5418
|
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
5142
5419
|
filesWritten.push("package.json");
|
|
@@ -5179,21 +5456,18 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5179
5456
|
normalizedRoutes,
|
|
5180
5457
|
useProxy
|
|
5181
5458
|
);
|
|
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");
|
|
5459
|
+
writeFileSync6(join7(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
5460
|
+
filesWritten.push("stackwright.auth.yml");
|
|
5190
5461
|
} catch (err) {
|
|
5191
5462
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5192
5463
|
return {
|
|
5193
5464
|
content: [
|
|
5194
5465
|
{
|
|
5195
5466
|
type: "text",
|
|
5196
|
-
text: JSON.stringify({
|
|
5467
|
+
text: JSON.stringify({
|
|
5468
|
+
success: false,
|
|
5469
|
+
error: `Failed writing stackwright.auth.yml: ${msg}`
|
|
5470
|
+
})
|
|
5197
5471
|
}
|
|
5198
5472
|
],
|
|
5199
5473
|
isError: true
|
|
@@ -5238,7 +5512,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
5238
5512
|
function registerAuthTools(server2) {
|
|
5239
5513
|
server2.tool(
|
|
5240
5514
|
"stackwright_pro_configure_auth",
|
|
5241
|
-
"Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template,
|
|
5515
|
+
"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
5516
|
{
|
|
5243
5517
|
method: z15.enum(["cac", "oidc", "oauth2", "none"]),
|
|
5244
5518
|
provider: z15.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
|
|
@@ -5289,28 +5563,28 @@ function registerAuthTools(server2) {
|
|
|
5289
5563
|
|
|
5290
5564
|
// src/integrity.ts
|
|
5291
5565
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
5292
|
-
import { readFileSync as
|
|
5293
|
-
import { join as
|
|
5566
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync3, lstatSync as lstatSync8 } from "fs";
|
|
5567
|
+
import { join as join8, basename } from "path";
|
|
5294
5568
|
var _checksums = /* @__PURE__ */ new Map([
|
|
5295
5569
|
[
|
|
5296
5570
|
"stackwright-pro-api-otter.json",
|
|
5297
|
-
"
|
|
5571
|
+
"822b35d7a330ed8ac0b42a63b0f70a41885aa9b5ea23cc5b8b998b549da4c05c"
|
|
5298
5572
|
],
|
|
5299
5573
|
[
|
|
5300
5574
|
"stackwright-pro-auth-otter.json",
|
|
5301
|
-
"
|
|
5575
|
+
"d6cd5732667018d99456be77d5955e454da7a0999a0330b5f03a483aa1b9b33f"
|
|
5302
5576
|
],
|
|
5303
5577
|
[
|
|
5304
5578
|
"stackwright-pro-dashboard-otter.json",
|
|
5305
|
-
"
|
|
5579
|
+
"19268b1ab423bfbb628ebfbc9f6767d5c0bfedd03963c6d445a2724a8bb78f9c"
|
|
5306
5580
|
],
|
|
5307
5581
|
[
|
|
5308
5582
|
"stackwright-pro-data-otter.json",
|
|
5309
|
-
"
|
|
5583
|
+
"bb66eaab31c6872536dca4d3ff145724a46356946dd0665cc4f0346714d3bacb"
|
|
5310
5584
|
],
|
|
5311
5585
|
[
|
|
5312
5586
|
"stackwright-pro-designer-otter.json",
|
|
5313
|
-
"
|
|
5587
|
+
"b54121c6a2ab5b1ad68c1262c58b2e04e6315feacd6bb8de8e78eb36f2fa6be6"
|
|
5314
5588
|
],
|
|
5315
5589
|
[
|
|
5316
5590
|
"stackwright-pro-domain-expert-otter.json",
|
|
@@ -5318,35 +5592,39 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
5318
5592
|
],
|
|
5319
5593
|
[
|
|
5320
5594
|
"stackwright-pro-foreman-otter.json",
|
|
5321
|
-
"
|
|
5595
|
+
"abb1cc5e40a8c5ff98057273f43a9e90e2791a15951090cd4d98407c0c3618e3"
|
|
5322
5596
|
],
|
|
5323
5597
|
[
|
|
5324
5598
|
"stackwright-pro-form-wizard-otter.json",
|
|
5325
|
-
"
|
|
5599
|
+
"975ad68e8fb6fe5a10373be278946fa4d9d7ec2238a0b2bd9e4a412e5b1fdd6d"
|
|
5326
5600
|
],
|
|
5327
5601
|
[
|
|
5328
5602
|
"stackwright-pro-geo-otter.json",
|
|
5329
|
-
"
|
|
5603
|
+
"eef152e5b58947c8eb1ffec2c37acffc39f84b61be4c6cb827310f052221935b"
|
|
5330
5604
|
],
|
|
5331
5605
|
[
|
|
5332
5606
|
"stackwright-pro-page-otter.json",
|
|
5333
|
-
"
|
|
5607
|
+
"ddfa497a4d4980080fa2918aec4d6dfd78d5c28ec6426b4b52f4f26fccaddabb"
|
|
5334
5608
|
],
|
|
5335
5609
|
[
|
|
5336
5610
|
"stackwright-pro-polish-otter.json",
|
|
5337
|
-
"
|
|
5611
|
+
"5e6532c1fe0737ed83b1f46dd55642e1076adb6ef23d4de636523eb3d88d3087"
|
|
5612
|
+
],
|
|
5613
|
+
[
|
|
5614
|
+
"stackwright-pro-qa-otter.json",
|
|
5615
|
+
"ecb1e76170723fce43f515044e304d9da32253dadecbf29bf08c90bb8f375f77"
|
|
5338
5616
|
],
|
|
5339
5617
|
[
|
|
5340
5618
|
"stackwright-pro-scaffold-otter.json",
|
|
5341
|
-
"
|
|
5619
|
+
"96ac7754b54c15732206cd4d8043b558d0c465757a0bc70fae6b498d6f02f22a"
|
|
5342
5620
|
],
|
|
5343
5621
|
[
|
|
5344
5622
|
"stackwright-pro-theme-otter.json",
|
|
5345
|
-
"
|
|
5623
|
+
"fb62e56642f7f94c50ce173f3e1ce978b3f20ab1567e87403b901d6dd991a7db"
|
|
5346
5624
|
],
|
|
5347
5625
|
[
|
|
5348
5626
|
"stackwright-services-otter.json",
|
|
5349
|
-
"
|
|
5627
|
+
"0a5dac670482871c718099767b30bf23d8ec57e942bd40a00ce295926d82c6bd"
|
|
5350
5628
|
]
|
|
5351
5629
|
]);
|
|
5352
5630
|
Object.freeze(_checksums);
|
|
@@ -5359,6 +5637,7 @@ for (const [name, digest] of CANONICAL_CHECKSUMS) {
|
|
|
5359
5637
|
);
|
|
5360
5638
|
}
|
|
5361
5639
|
}
|
|
5640
|
+
var CANONICAL_OTTERS = Object.freeze([...CANONICAL_CHECKSUMS.keys()]);
|
|
5362
5641
|
var MAX_OTTER_BYTES = 1 * 1024 * 1024;
|
|
5363
5642
|
function computeSha256(data) {
|
|
5364
5643
|
return createHash4("sha256").update(data).digest("hex");
|
|
@@ -5375,7 +5654,7 @@ function verifyOtterFile(filePath) {
|
|
|
5375
5654
|
}
|
|
5376
5655
|
let stat;
|
|
5377
5656
|
try {
|
|
5378
|
-
stat =
|
|
5657
|
+
stat = lstatSync8(filePath);
|
|
5379
5658
|
} catch (err) {
|
|
5380
5659
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5381
5660
|
return { verified: false, filename, error: `Cannot stat file: ${msg}` };
|
|
@@ -5393,7 +5672,7 @@ function verifyOtterFile(filePath) {
|
|
|
5393
5672
|
}
|
|
5394
5673
|
let raw;
|
|
5395
5674
|
try {
|
|
5396
|
-
raw =
|
|
5675
|
+
raw = readFileSync8(filePath);
|
|
5397
5676
|
} catch (err) {
|
|
5398
5677
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5399
5678
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -5443,7 +5722,7 @@ function verifyAllOtters(otterDir) {
|
|
|
5443
5722
|
const unknown = [];
|
|
5444
5723
|
let entries;
|
|
5445
5724
|
try {
|
|
5446
|
-
entries =
|
|
5725
|
+
entries = readdirSync3(otterDir);
|
|
5447
5726
|
} catch (err) {
|
|
5448
5727
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5449
5728
|
return {
|
|
@@ -5454,9 +5733,9 @@ function verifyAllOtters(otterDir) {
|
|
|
5454
5733
|
}
|
|
5455
5734
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
5456
5735
|
for (const filename of otterFiles) {
|
|
5457
|
-
const filePath =
|
|
5736
|
+
const filePath = join8(otterDir, filename);
|
|
5458
5737
|
try {
|
|
5459
|
-
if (
|
|
5738
|
+
if (lstatSync8(filePath).isSymbolicLink()) {
|
|
5460
5739
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
5461
5740
|
continue;
|
|
5462
5741
|
}
|
|
@@ -5482,9 +5761,9 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
|
|
|
5482
5761
|
function resolveOtterDir() {
|
|
5483
5762
|
const cwd = process.cwd();
|
|
5484
5763
|
for (const relative of DEFAULT_SEARCH_PATHS) {
|
|
5485
|
-
const candidate =
|
|
5764
|
+
const candidate = join8(cwd, relative);
|
|
5486
5765
|
try {
|
|
5487
|
-
|
|
5766
|
+
lstatSync8(candidate);
|
|
5488
5767
|
return candidate;
|
|
5489
5768
|
} catch {
|
|
5490
5769
|
}
|
|
@@ -5563,13 +5842,13 @@ function registerIntegrityTools(server2) {
|
|
|
5563
5842
|
|
|
5564
5843
|
// src/tools/domain.ts
|
|
5565
5844
|
import { z as z16 } from "zod";
|
|
5566
|
-
import { readFileSync as
|
|
5567
|
-
import { join as
|
|
5845
|
+
import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
|
|
5846
|
+
import { join as join9 } from "path";
|
|
5568
5847
|
function handleListCollections(input) {
|
|
5569
5848
|
const cwd = input._cwd ?? process.cwd();
|
|
5570
5849
|
const sources = [
|
|
5571
5850
|
{
|
|
5572
|
-
path:
|
|
5851
|
+
path: join9(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
5573
5852
|
source: "data-config.json",
|
|
5574
5853
|
parse: (raw) => {
|
|
5575
5854
|
const parsed = JSON.parse(raw);
|
|
@@ -5580,15 +5859,15 @@ function handleListCollections(input) {
|
|
|
5580
5859
|
}
|
|
5581
5860
|
},
|
|
5582
5861
|
{
|
|
5583
|
-
path:
|
|
5862
|
+
path: join9(cwd, "stackwright.yml"),
|
|
5584
5863
|
source: "stackwright.yml",
|
|
5585
5864
|
parse: extractCollectionsFromYaml
|
|
5586
5865
|
}
|
|
5587
5866
|
];
|
|
5588
|
-
for (const { path:
|
|
5589
|
-
if (!existsSync8(
|
|
5867
|
+
for (const { path: path4, source, parse } of sources) {
|
|
5868
|
+
if (!existsSync8(path4)) continue;
|
|
5590
5869
|
try {
|
|
5591
|
-
const collections = parse(
|
|
5870
|
+
const collections = parse(readFileSync9(path4, "utf8"));
|
|
5592
5871
|
return {
|
|
5593
5872
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
5594
5873
|
isError: false
|
|
@@ -5739,7 +6018,7 @@ function handleValidateWorkflow(input) {
|
|
|
5739
6018
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
5740
6019
|
raw = input.workflow;
|
|
5741
6020
|
} else {
|
|
5742
|
-
const artifactPath =
|
|
6021
|
+
const artifactPath = join9(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
5743
6022
|
if (!existsSync8(artifactPath)) {
|
|
5744
6023
|
return fail([
|
|
5745
6024
|
{
|
|
@@ -5749,7 +6028,7 @@ function handleValidateWorkflow(input) {
|
|
|
5749
6028
|
]);
|
|
5750
6029
|
}
|
|
5751
6030
|
try {
|
|
5752
|
-
raw = JSON.parse(
|
|
6031
|
+
raw = JSON.parse(readFileSync9(artifactPath, "utf8"));
|
|
5753
6032
|
} catch (err) {
|
|
5754
6033
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
5755
6034
|
}
|
|
@@ -5861,12 +6140,12 @@ function handleValidateWorkflow(input) {
|
|
|
5861
6140
|
return { text: JSON.stringify({ valid: errors.length === 0, errors, warnings }), isError: false };
|
|
5862
6141
|
}
|
|
5863
6142
|
function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
5864
|
-
const check = (target,
|
|
6143
|
+
const check = (target, path4) => {
|
|
5865
6144
|
if (typeof target === "string" && !stepIds.has(target)) {
|
|
5866
6145
|
errors.push({
|
|
5867
6146
|
code: "ORPHANED_TRANSITION",
|
|
5868
6147
|
message: `Step "${stepId}" transitions to "${target}" which does not exist`,
|
|
5869
|
-
path:
|
|
6148
|
+
path: path4
|
|
5870
6149
|
});
|
|
5871
6150
|
}
|
|
5872
6151
|
};
|
|
@@ -5905,11 +6184,11 @@ function validateTransitionTargets(step, stepId, stepIds, errors) {
|
|
|
5905
6184
|
}
|
|
5906
6185
|
function collectServiceWarnings(step, stepId, warnings) {
|
|
5907
6186
|
const isService = (val) => typeof val === "string" && val.startsWith("service:");
|
|
5908
|
-
const warn = (
|
|
6187
|
+
const warn = (path4) => {
|
|
5909
6188
|
warnings.push({
|
|
5910
6189
|
code: "WARN_SERVICE_REFERENCE",
|
|
5911
|
-
message: `service: reference at "${
|
|
5912
|
-
path:
|
|
6190
|
+
message: `service: reference at "${path4}" requires @stackwright-pro/services. Prism mock fallback will be used until services layer is configured.`,
|
|
6191
|
+
path: path4
|
|
5913
6192
|
});
|
|
5914
6193
|
};
|
|
5915
6194
|
const onSubmit = step.on_submit;
|
|
@@ -6059,15 +6338,15 @@ function registerTypeSchemasTool(server2) {
|
|
|
6059
6338
|
// src/tools/scaffold-cleanup.ts
|
|
6060
6339
|
import {
|
|
6061
6340
|
existsSync as existsSync9,
|
|
6062
|
-
lstatSync as
|
|
6341
|
+
lstatSync as lstatSync9,
|
|
6063
6342
|
unlinkSync as unlinkSync2,
|
|
6064
|
-
readFileSync as
|
|
6343
|
+
readFileSync as readFileSync10,
|
|
6065
6344
|
writeFileSync as writeFileSync7,
|
|
6066
|
-
readdirSync as
|
|
6345
|
+
readdirSync as readdirSync4,
|
|
6067
6346
|
rmdirSync,
|
|
6068
6347
|
mkdirSync as mkdirSync7
|
|
6069
6348
|
} from "fs";
|
|
6070
|
-
import { join as
|
|
6349
|
+
import { join as join10, dirname as dirname2 } from "path";
|
|
6071
6350
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
6072
6351
|
"content/posts/getting-started.yaml",
|
|
6073
6352
|
"content/posts/hello-world.yaml"
|
|
@@ -6092,12 +6371,12 @@ var FALLBACK_COLORS = {
|
|
|
6092
6371
|
mutedForeground: "#737373"
|
|
6093
6372
|
};
|
|
6094
6373
|
function readThemeColors(cwd) {
|
|
6095
|
-
const tokenPath =
|
|
6374
|
+
const tokenPath = join10(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
6096
6375
|
if (!existsSync9(tokenPath)) return { ...FALLBACK_COLORS };
|
|
6097
|
-
const stat =
|
|
6376
|
+
const stat = lstatSync9(tokenPath);
|
|
6098
6377
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
6099
6378
|
try {
|
|
6100
|
-
const raw = JSON.parse(
|
|
6379
|
+
const raw = JSON.parse(readFileSync10(tokenPath, "utf-8"));
|
|
6101
6380
|
const css = raw?.cssVariables ?? {};
|
|
6102
6381
|
const toHsl = (hslValue) => {
|
|
6103
6382
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -6158,14 +6437,14 @@ function generateNotFoundPage(colors) {
|
|
|
6158
6437
|
}
|
|
6159
6438
|
function isSafePath(fullPath) {
|
|
6160
6439
|
if (!existsSync9(fullPath)) return true;
|
|
6161
|
-
const stat =
|
|
6440
|
+
const stat = lstatSync9(fullPath);
|
|
6162
6441
|
return !stat.isSymbolicLink();
|
|
6163
6442
|
}
|
|
6164
6443
|
function isDirEmpty(dirPath) {
|
|
6165
6444
|
if (!existsSync9(dirPath)) return false;
|
|
6166
|
-
const stat =
|
|
6445
|
+
const stat = lstatSync9(dirPath);
|
|
6167
6446
|
if (!stat.isDirectory()) return false;
|
|
6168
|
-
return
|
|
6447
|
+
return readdirSync4(dirPath).length === 0;
|
|
6169
6448
|
}
|
|
6170
6449
|
function handleCleanupScaffold(_cwd) {
|
|
6171
6450
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -6178,7 +6457,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6178
6457
|
cleanupWarnings: []
|
|
6179
6458
|
};
|
|
6180
6459
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
6181
|
-
const fullPath =
|
|
6460
|
+
const fullPath = join10(cwd, relPath);
|
|
6182
6461
|
if (!existsSync9(fullPath)) {
|
|
6183
6462
|
result.skipped.push(relPath);
|
|
6184
6463
|
continue;
|
|
@@ -6196,13 +6475,13 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6196
6475
|
}
|
|
6197
6476
|
{
|
|
6198
6477
|
const relPath = "pages/getting-started/content.yml";
|
|
6199
|
-
const fullPath =
|
|
6478
|
+
const fullPath = join10(cwd, relPath);
|
|
6200
6479
|
if (!existsSync9(fullPath)) {
|
|
6201
6480
|
result.skipped.push(relPath);
|
|
6202
6481
|
} else if (!isSafePath(fullPath)) {
|
|
6203
6482
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
6204
6483
|
} else {
|
|
6205
|
-
const content =
|
|
6484
|
+
const content = readFileSync10(fullPath, "utf-8");
|
|
6206
6485
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
6207
6486
|
(marker) => content.includes(marker)
|
|
6208
6487
|
);
|
|
@@ -6222,8 +6501,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6222
6501
|
}
|
|
6223
6502
|
{
|
|
6224
6503
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
6225
|
-
const fullPath =
|
|
6226
|
-
const postsDir =
|
|
6504
|
+
const fullPath = join10(cwd, relPath);
|
|
6505
|
+
const postsDir = join10(cwd, "content/posts");
|
|
6227
6506
|
if (!existsSync9(fullPath)) {
|
|
6228
6507
|
result.skipped.push(relPath);
|
|
6229
6508
|
} else if (!isSafePath(fullPath)) {
|
|
@@ -6231,7 +6510,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6231
6510
|
} else if (!existsSync9(postsDir)) {
|
|
6232
6511
|
result.skipped.push(relPath);
|
|
6233
6512
|
} else {
|
|
6234
|
-
const userPostFiles =
|
|
6513
|
+
const userPostFiles = readdirSync4(postsDir).filter(
|
|
6235
6514
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
6236
6515
|
);
|
|
6237
6516
|
if (userPostFiles.length === 0) {
|
|
@@ -6249,7 +6528,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6249
6528
|
}
|
|
6250
6529
|
}
|
|
6251
6530
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
6252
|
-
const fullDir =
|
|
6531
|
+
const fullDir = join10(cwd, relDir);
|
|
6253
6532
|
if (!existsSync9(fullDir)) continue;
|
|
6254
6533
|
if (!isSafePath(fullDir)) {
|
|
6255
6534
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
@@ -6265,7 +6544,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
6265
6544
|
}
|
|
6266
6545
|
}
|
|
6267
6546
|
{
|
|
6268
|
-
const fullPath =
|
|
6547
|
+
const fullPath = join10(cwd, NOT_FOUND_PATH);
|
|
6269
6548
|
if (!existsSync9(fullPath)) {
|
|
6270
6549
|
result.skipped.push(NOT_FOUND_PATH);
|
|
6271
6550
|
} else if (!isSafePath(fullPath)) {
|
|
@@ -6543,12 +6822,258 @@ function registerContrastTools(server2) {
|
|
|
6543
6822
|
);
|
|
6544
6823
|
}
|
|
6545
6824
|
|
|
6825
|
+
// src/tools/compile.ts
|
|
6826
|
+
import { z as z19 } from "zod";
|
|
6827
|
+
import fs2 from "fs";
|
|
6828
|
+
import path3 from "path";
|
|
6829
|
+
import { compileCollections } from "@stackwright-pro/pulse/server";
|
|
6830
|
+
import { compileAuth } from "@stackwright-pro/auth-nextjs/server";
|
|
6831
|
+
import { compileIntegrations } from "@stackwright-pro/openapi/server";
|
|
6832
|
+
function buildContext(projectRoot) {
|
|
6833
|
+
return {
|
|
6834
|
+
projectRoot,
|
|
6835
|
+
contentOutDir: path3.join(projectRoot, "public", "stackwright-content"),
|
|
6836
|
+
imagesDir: path3.join(projectRoot, "public", "images"),
|
|
6837
|
+
siteConfig: loadSiteConfig(projectRoot)
|
|
6838
|
+
};
|
|
6839
|
+
}
|
|
6840
|
+
function loadSiteConfig(projectRoot) {
|
|
6841
|
+
try {
|
|
6842
|
+
const sitePath = path3.join(projectRoot, "public", "stackwright-content", "_site.json");
|
|
6843
|
+
return JSON.parse(fs2.readFileSync(sitePath, "utf8"));
|
|
6844
|
+
} catch {
|
|
6845
|
+
return {};
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
function formatDuration(startMs) {
|
|
6849
|
+
const ms = Date.now() - startMs;
|
|
6850
|
+
return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(2)}s`;
|
|
6851
|
+
}
|
|
6852
|
+
async function tryOssCompile(fn, ctx, extra) {
|
|
6853
|
+
try {
|
|
6854
|
+
const bsPath = path3.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
|
|
6855
|
+
if (!fs2.existsSync(bsPath)) {
|
|
6856
|
+
return {
|
|
6857
|
+
ok: false,
|
|
6858
|
+
error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
|
|
6859
|
+
};
|
|
6860
|
+
}
|
|
6861
|
+
const bs = await import(bsPath);
|
|
6862
|
+
if (typeof bs[fn] !== "function") {
|
|
6863
|
+
return { ok: false, error: `${fn} not exported from @stackwright/build-scripts` };
|
|
6864
|
+
}
|
|
6865
|
+
await bs[fn](ctx, ...extra ? [extra] : []);
|
|
6866
|
+
return { ok: true };
|
|
6867
|
+
} catch (err) {
|
|
6868
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
6869
|
+
}
|
|
6870
|
+
}
|
|
6871
|
+
function registerCompileTools(server2) {
|
|
6872
|
+
const projectRootArg = z19.string().optional().describe("Absolute path to project root (defaults to cwd)");
|
|
6873
|
+
server2.tool(
|
|
6874
|
+
"stackwright_pro_compile_collections",
|
|
6875
|
+
"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.",
|
|
6876
|
+
{ projectRoot: projectRootArg },
|
|
6877
|
+
async ({ projectRoot }) => {
|
|
6878
|
+
const start = Date.now();
|
|
6879
|
+
const root = projectRoot ?? process.cwd();
|
|
6880
|
+
try {
|
|
6881
|
+
await compileCollections(buildContext(root));
|
|
6882
|
+
return {
|
|
6883
|
+
content: [
|
|
6884
|
+
{
|
|
6885
|
+
type: "text",
|
|
6886
|
+
text: ` _collections.json written (${formatDuration(start)})`
|
|
6887
|
+
}
|
|
6888
|
+
]
|
|
6889
|
+
};
|
|
6890
|
+
} catch (err) {
|
|
6891
|
+
return {
|
|
6892
|
+
content: [
|
|
6893
|
+
{
|
|
6894
|
+
type: "text",
|
|
6895
|
+
text: ` compile_collections failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6896
|
+
}
|
|
6897
|
+
]
|
|
6898
|
+
};
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
);
|
|
6902
|
+
server2.tool(
|
|
6903
|
+
"stackwright_pro_compile_auth",
|
|
6904
|
+
"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.",
|
|
6905
|
+
{ projectRoot: projectRootArg },
|
|
6906
|
+
async ({ projectRoot }) => {
|
|
6907
|
+
const start = Date.now();
|
|
6908
|
+
const root = projectRoot ?? process.cwd();
|
|
6909
|
+
try {
|
|
6910
|
+
await compileAuth(buildContext(root));
|
|
6911
|
+
return {
|
|
6912
|
+
content: [
|
|
6913
|
+
{
|
|
6914
|
+
type: "text",
|
|
6915
|
+
text: ` _auth.json written (${formatDuration(start)})`
|
|
6916
|
+
}
|
|
6917
|
+
]
|
|
6918
|
+
};
|
|
6919
|
+
} catch (err) {
|
|
6920
|
+
return {
|
|
6921
|
+
content: [
|
|
6922
|
+
{
|
|
6923
|
+
type: "text",
|
|
6924
|
+
text: ` compile_auth failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6925
|
+
}
|
|
6926
|
+
]
|
|
6927
|
+
};
|
|
6928
|
+
}
|
|
6929
|
+
}
|
|
6930
|
+
);
|
|
6931
|
+
server2.tool(
|
|
6932
|
+
"stackwright_pro_compile_integrations",
|
|
6933
|
+
"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).",
|
|
6934
|
+
{ projectRoot: projectRootArg },
|
|
6935
|
+
async ({ projectRoot }) => {
|
|
6936
|
+
const start = Date.now();
|
|
6937
|
+
const root = projectRoot ?? process.cwd();
|
|
6938
|
+
try {
|
|
6939
|
+
await compileIntegrations(buildContext(root));
|
|
6940
|
+
return {
|
|
6941
|
+
content: [
|
|
6942
|
+
{
|
|
6943
|
+
type: "text",
|
|
6944
|
+
text: ` _integrations.json written (${formatDuration(start)})`
|
|
6945
|
+
}
|
|
6946
|
+
]
|
|
6947
|
+
};
|
|
6948
|
+
} catch (err) {
|
|
6949
|
+
return {
|
|
6950
|
+
content: [
|
|
6951
|
+
{
|
|
6952
|
+
type: "text",
|
|
6953
|
+
text: ` compile_integrations failed: ${err instanceof Error ? err.message : String(err)}`
|
|
6954
|
+
}
|
|
6955
|
+
]
|
|
6956
|
+
};
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6959
|
+
);
|
|
6960
|
+
server2.tool(
|
|
6961
|
+
"stackwright_pro_compile_all",
|
|
6962
|
+
"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.",
|
|
6963
|
+
{ projectRoot: projectRootArg },
|
|
6964
|
+
async ({ projectRoot }) => {
|
|
6965
|
+
const start = Date.now();
|
|
6966
|
+
const root = projectRoot ?? process.cwd();
|
|
6967
|
+
const ctx = buildContext(root);
|
|
6968
|
+
const results = [];
|
|
6969
|
+
const errors = [];
|
|
6970
|
+
for (const [label, fn] of [
|
|
6971
|
+
["collections", () => compileCollections(ctx)],
|
|
6972
|
+
["auth", () => compileAuth(ctx)],
|
|
6973
|
+
["integrations", () => compileIntegrations(ctx)]
|
|
6974
|
+
]) {
|
|
6975
|
+
try {
|
|
6976
|
+
await fn();
|
|
6977
|
+
results.push(` _${label}.json`);
|
|
6978
|
+
} catch (err) {
|
|
6979
|
+
errors.push(` ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6982
|
+
const summary = errors.length > 0 ? ` compile_all finished with errors (${formatDuration(start)}):
|
|
6983
|
+
${results.join("\n")}
|
|
6984
|
+
Errors:
|
|
6985
|
+
${errors.join("\n")}` : ` compile_all complete (${formatDuration(start)}):
|
|
6986
|
+
${results.join("\n")}`;
|
|
6987
|
+
return { content: [{ type: "text", text: summary }] };
|
|
6988
|
+
}
|
|
6989
|
+
);
|
|
6990
|
+
server2.tool(
|
|
6991
|
+
"stackwright_pro_compile_theme",
|
|
6992
|
+
"Compile stackwright.yml theme config \u2192 _theme.json. Delegates to compileTheme from @stackwright/build-scripts installed in the project.",
|
|
6993
|
+
{ projectRoot: projectRootArg },
|
|
6994
|
+
async ({ projectRoot }) => {
|
|
6995
|
+
const start = Date.now();
|
|
6996
|
+
const root = projectRoot ?? process.cwd();
|
|
6997
|
+
const { ok, error } = await tryOssCompile("compileTheme", buildContext(root));
|
|
6998
|
+
return {
|
|
6999
|
+
content: [
|
|
7000
|
+
{
|
|
7001
|
+
type: "text",
|
|
7002
|
+
text: ok ? ` _theme.json written (${formatDuration(start)})` : ` compile_theme failed: ${error}`
|
|
7003
|
+
}
|
|
7004
|
+
]
|
|
7005
|
+
};
|
|
7006
|
+
}
|
|
7007
|
+
);
|
|
7008
|
+
server2.tool(
|
|
7009
|
+
"stackwright_pro_compile_site",
|
|
7010
|
+
"Compile stackwright.yml \u2192 _site.json. Delegates to compileSite from @stackwright/build-scripts installed in the project.",
|
|
7011
|
+
{ projectRoot: projectRootArg },
|
|
7012
|
+
async ({ projectRoot }) => {
|
|
7013
|
+
const start = Date.now();
|
|
7014
|
+
const root = projectRoot ?? process.cwd();
|
|
7015
|
+
const { ok, error } = await tryOssCompile("compileSite", buildContext(root));
|
|
7016
|
+
return {
|
|
7017
|
+
content: [
|
|
7018
|
+
{
|
|
7019
|
+
type: "text",
|
|
7020
|
+
text: ok ? ` _site.json written (${formatDuration(start)})` : ` compile_site failed: ${error}`
|
|
7021
|
+
}
|
|
7022
|
+
]
|
|
7023
|
+
};
|
|
7024
|
+
}
|
|
7025
|
+
);
|
|
7026
|
+
server2.tool(
|
|
7027
|
+
"stackwright_pro_compile_pages",
|
|
7028
|
+
"Compile all pages content.yml files \u2192 page JSON files. Delegates to compilePages from @stackwright/build-scripts installed in the project.",
|
|
7029
|
+
{ projectRoot: projectRootArg },
|
|
7030
|
+
async ({ projectRoot }) => {
|
|
7031
|
+
const start = Date.now();
|
|
7032
|
+
const root = projectRoot ?? process.cwd();
|
|
7033
|
+
const { ok, error } = await tryOssCompile("compilePages", buildContext(root));
|
|
7034
|
+
return {
|
|
7035
|
+
content: [
|
|
7036
|
+
{
|
|
7037
|
+
type: "text",
|
|
7038
|
+
text: ok ? ` pages compiled (${formatDuration(start)})` : ` compile_pages failed: ${error}`
|
|
7039
|
+
}
|
|
7040
|
+
]
|
|
7041
|
+
};
|
|
7042
|
+
}
|
|
7043
|
+
);
|
|
7044
|
+
server2.tool(
|
|
7045
|
+
"stackwright_pro_compile_page",
|
|
7046
|
+
"Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
|
|
7047
|
+
{
|
|
7048
|
+
slug: z19.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
|
|
7049
|
+
projectRoot: projectRootArg
|
|
7050
|
+
},
|
|
7051
|
+
async ({ slug, projectRoot }) => {
|
|
7052
|
+
const start = Date.now();
|
|
7053
|
+
const root = projectRoot ?? process.cwd();
|
|
7054
|
+
const { ok, error } = await tryOssCompile("compilePage", buildContext(root), { slug });
|
|
7055
|
+
return {
|
|
7056
|
+
content: [
|
|
7057
|
+
{
|
|
7058
|
+
type: "text",
|
|
7059
|
+
text: ok ? ` page "${slug}" compiled (${formatDuration(start)})` : ` compile_page failed: ${error}`
|
|
7060
|
+
}
|
|
7061
|
+
]
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
);
|
|
7065
|
+
}
|
|
7066
|
+
|
|
6546
7067
|
// package.json
|
|
6547
7068
|
var package_default = {
|
|
6548
7069
|
dependencies: {
|
|
6549
7070
|
"@modelcontextprotocol/sdk": "^1.10.0",
|
|
7071
|
+
"@stackwright-pro/auth-nextjs": "workspace:*",
|
|
6550
7072
|
"@stackwright-pro/cli-data-explorer": "workspace:*",
|
|
7073
|
+
"@stackwright-pro/openapi": "workspace:*",
|
|
7074
|
+
"@stackwright-pro/pulse": "workspace:*",
|
|
6551
7075
|
"@stackwright-pro/types": "workspace:*",
|
|
7076
|
+
"@stackwright/mcp": "0.6.0-alpha.1",
|
|
6552
7077
|
"@types/js-yaml": "^4.0.9",
|
|
6553
7078
|
"js-yaml": "^4.2.0",
|
|
6554
7079
|
zod: "^4.4.3"
|
|
@@ -6568,7 +7093,7 @@ var package_default = {
|
|
|
6568
7093
|
"test:coverage": "vitest run --coverage"
|
|
6569
7094
|
},
|
|
6570
7095
|
name: "@stackwright-pro/mcp",
|
|
6571
|
-
version: "0.2.0-alpha.
|
|
7096
|
+
version: "0.2.0-alpha.97",
|
|
6572
7097
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
6573
7098
|
license: "SEE LICENSE IN LICENSE",
|
|
6574
7099
|
main: "./dist/server.js",
|
|
@@ -6592,6 +7117,11 @@ var package_default = {
|
|
|
6592
7117
|
types: "./dist/tools/type-schemas.d.ts",
|
|
6593
7118
|
import: "./dist/tools/type-schemas.mjs",
|
|
6594
7119
|
require: "./dist/tools/type-schemas.js"
|
|
7120
|
+
},
|
|
7121
|
+
"./pipeline-constants": {
|
|
7122
|
+
types: "./dist/pipeline-constants.d.ts",
|
|
7123
|
+
import: "./dist/pipeline-constants.mjs",
|
|
7124
|
+
require: "./dist/pipeline-constants.js"
|
|
6595
7125
|
}
|
|
6596
7126
|
},
|
|
6597
7127
|
files: [
|
|
@@ -6603,6 +7133,20 @@ var package_default = {
|
|
|
6603
7133
|
};
|
|
6604
7134
|
|
|
6605
7135
|
// src/server.ts
|
|
7136
|
+
import {
|
|
7137
|
+
registerContentTypeTools,
|
|
7138
|
+
registerPageTools,
|
|
7139
|
+
registerSiteTools,
|
|
7140
|
+
registerProjectTools,
|
|
7141
|
+
registerGitOpsTools,
|
|
7142
|
+
registerBoardTools,
|
|
7143
|
+
registerCollectionTools,
|
|
7144
|
+
registerIntegrationTools,
|
|
7145
|
+
registerComposeTools,
|
|
7146
|
+
registerRenderTools,
|
|
7147
|
+
registerA11yTools,
|
|
7148
|
+
closeBrowser
|
|
7149
|
+
} from "@stackwright/mcp/register";
|
|
6606
7150
|
var server = new McpServer({
|
|
6607
7151
|
name: "stackwright-pro",
|
|
6608
7152
|
version: package_default.version
|
|
@@ -6626,7 +7170,45 @@ registerValidateYamlFragmentTool(server);
|
|
|
6626
7170
|
registerGetSchemaTool(server);
|
|
6627
7171
|
registerScaffoldCleanupTools(server);
|
|
6628
7172
|
registerContrastTools(server);
|
|
7173
|
+
registerCompileTools(server);
|
|
7174
|
+
registerContentTypeTools(server);
|
|
7175
|
+
registerPageTools(server);
|
|
7176
|
+
registerSiteTools(server);
|
|
7177
|
+
registerProjectTools(server);
|
|
7178
|
+
registerGitOpsTools(server);
|
|
7179
|
+
registerBoardTools(server);
|
|
7180
|
+
registerCollectionTools(server);
|
|
7181
|
+
registerIntegrationTools(server);
|
|
7182
|
+
registerComposeTools(server);
|
|
7183
|
+
registerRenderTools(server);
|
|
7184
|
+
registerA11yTools(server);
|
|
7185
|
+
process.on("SIGINT", async () => {
|
|
7186
|
+
const forceExit = setTimeout(() => process.exit(1), 3e3);
|
|
7187
|
+
forceExit.unref();
|
|
7188
|
+
await closeBrowser();
|
|
7189
|
+
process.exit(0);
|
|
7190
|
+
});
|
|
7191
|
+
process.on("SIGTERM", async () => {
|
|
7192
|
+
const forceExit = setTimeout(() => process.exit(1), 3e3);
|
|
7193
|
+
forceExit.unref();
|
|
7194
|
+
await closeBrowser();
|
|
7195
|
+
process.exit(0);
|
|
7196
|
+
});
|
|
6629
7197
|
async function main() {
|
|
7198
|
+
const pipelineGraph = loadPipelineGraph();
|
|
7199
|
+
const phasePosition = new Map(PHASE_ORDER.map((p, i) => [p, i]));
|
|
7200
|
+
for (const [phase, deps] of Object.entries(pipelineGraph.dependencies)) {
|
|
7201
|
+
const phaseIdx = phasePosition.get(phase);
|
|
7202
|
+
if (phaseIdx === void 0) continue;
|
|
7203
|
+
for (const dep of deps) {
|
|
7204
|
+
const depIdx = phasePosition.get(dep);
|
|
7205
|
+
if (depIdx !== void 0 && depIdx >= phaseIdx) {
|
|
7206
|
+
throw new Error(
|
|
7207
|
+
`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.`
|
|
7208
|
+
);
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
}
|
|
6630
7212
|
const transport = new StdioServerTransport();
|
|
6631
7213
|
try {
|
|
6632
7214
|
const servicesRegisterPkg = "@stackwright-services/mcp/register";
|