@stackwright-pro/mcp 0.2.0-alpha.113 → 0.2.0-alpha.115
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 +5 -5
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +5 -5
- package/dist/integrity.mjs.map +1 -1
- package/dist/pipeline-constants.js +229 -41
- package/dist/pipeline-constants.js.map +1 -1
- package/dist/pipeline-constants.mjs +237 -48
- package/dist/pipeline-constants.mjs.map +1 -1
- package/dist/server.js +1173 -476
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1119 -421
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -6
package/dist/server.js
CHANGED
|
@@ -2030,9 +2030,9 @@ function registerOrchestrationTools(server2) {
|
|
|
2030
2030
|
|
|
2031
2031
|
// src/tools/pipeline.ts
|
|
2032
2032
|
var import_zod14 = require("zod");
|
|
2033
|
-
var
|
|
2033
|
+
var import_fs12 = require("fs");
|
|
2034
2034
|
var import_proper_lockfile = require("proper-lockfile");
|
|
2035
|
-
var
|
|
2035
|
+
var import_path12 = require("path");
|
|
2036
2036
|
var import_crypto3 = require("crypto");
|
|
2037
2037
|
|
|
2038
2038
|
// src/artifact-signing.ts
|
|
@@ -2559,6 +2559,585 @@ function formatPulseRetryPrompt(result) {
|
|
|
2559
2559
|
return lines.join("\n");
|
|
2560
2560
|
}
|
|
2561
2561
|
|
|
2562
|
+
// src/tools/validate-theme-tokens.ts
|
|
2563
|
+
var import_fs7 = require("fs");
|
|
2564
|
+
var import_path7 = require("path");
|
|
2565
|
+
var import_js_yaml2 = require("js-yaml");
|
|
2566
|
+
|
|
2567
|
+
// src/tools/schema-registry.ts
|
|
2568
|
+
var import_zod11 = require("zod");
|
|
2569
|
+
var import_fs6 = require("fs");
|
|
2570
|
+
var import_path6 = require("path");
|
|
2571
|
+
var import_types = require("@stackwright-pro/types");
|
|
2572
|
+
var import_pulse2 = require("@stackwright-pro/pulse");
|
|
2573
|
+
function getSchemaForEntry(entry, ctx) {
|
|
2574
|
+
if (entry.schema) return entry.schema;
|
|
2575
|
+
if (entry.schemaFactory) {
|
|
2576
|
+
if (ctx) return entry.schemaFactory(ctx);
|
|
2577
|
+
console.warn(
|
|
2578
|
+
"[schema-registry] schemaFactory entry requires projectRoot context. Pass { projectRoot } to get the project-specific schema. Falling back to z.string() (no enforcement)."
|
|
2579
|
+
);
|
|
2580
|
+
return import_zod11.z.string();
|
|
2581
|
+
}
|
|
2582
|
+
return import_zod11.z.unknown();
|
|
2583
|
+
}
|
|
2584
|
+
var NavigationLinkSchema = import_zod11.z.lazy(
|
|
2585
|
+
() => import_zod11.z.object({
|
|
2586
|
+
label: import_zod11.z.string().min(1),
|
|
2587
|
+
href: import_zod11.z.string().min(1),
|
|
2588
|
+
children: import_zod11.z.array(NavigationLinkSchema).optional()
|
|
2589
|
+
})
|
|
2590
|
+
);
|
|
2591
|
+
var NavigationSectionSchema = import_zod11.z.object({
|
|
2592
|
+
section: import_zod11.z.string().min(1),
|
|
2593
|
+
items: import_zod11.z.array(NavigationLinkSchema)
|
|
2594
|
+
});
|
|
2595
|
+
var NavigationItemSchema = import_zod11.z.union([
|
|
2596
|
+
NavigationLinkSchema,
|
|
2597
|
+
NavigationSectionSchema
|
|
2598
|
+
]);
|
|
2599
|
+
var THEME_TOKENS_PATHS = [
|
|
2600
|
+
(0, import_path6.join)(".stackwright", "artifacts", "theme-tokens.json"),
|
|
2601
|
+
(0, import_path6.join)("public", "stackwright-content", "_theme.json")
|
|
2602
|
+
];
|
|
2603
|
+
function resolveStatusLadder(projectRoot) {
|
|
2604
|
+
for (const relPath of THEME_TOKENS_PATHS) {
|
|
2605
|
+
const fullPath = (0, import_path6.join)(projectRoot, relPath);
|
|
2606
|
+
if (!(0, import_fs6.existsSync)(fullPath)) continue;
|
|
2607
|
+
try {
|
|
2608
|
+
const raw = (0, import_fs6.readFileSync)(fullPath, "utf-8");
|
|
2609
|
+
const parsed = JSON.parse(raw);
|
|
2610
|
+
if (Array.isArray(parsed["statusLadder"]) && parsed["statusLadder"].length > 0) {
|
|
2611
|
+
return parsed["statusLadder"].filter(
|
|
2612
|
+
(t) => typeof t === "string" && t.startsWith("status-")
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
const colors = parsed["tokens"]?.["colors"];
|
|
2616
|
+
if (colors && typeof colors === "object") {
|
|
2617
|
+
const statusKeys = Object.keys(colors).filter((k) => k.startsWith("status-"));
|
|
2618
|
+
if (statusKeys.length > 0) return statusKeys;
|
|
2619
|
+
}
|
|
2620
|
+
} catch {
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return [];
|
|
2624
|
+
}
|
|
2625
|
+
function buildThemeStatusTokenSchema(ctx) {
|
|
2626
|
+
const ladder = resolveStatusLadder(ctx.projectRoot);
|
|
2627
|
+
if (ladder.length === 0) {
|
|
2628
|
+
return import_zod11.z.string();
|
|
2629
|
+
}
|
|
2630
|
+
return import_zod11.z.enum(ladder);
|
|
2631
|
+
}
|
|
2632
|
+
var REGISTRY = /* @__PURE__ */ new Map([
|
|
2633
|
+
// ── Workflow schemas ──────────────────────────────────────────────────────
|
|
2634
|
+
[
|
|
2635
|
+
"workflow_step",
|
|
2636
|
+
{
|
|
2637
|
+
schema: import_types.WorkflowStepSchema,
|
|
2638
|
+
synonyms: {
|
|
2639
|
+
label: ["title", "name"],
|
|
2640
|
+
message: ["description"],
|
|
2641
|
+
"theme.variant": ["style"],
|
|
2642
|
+
"on_submit.transition": ["transitions"]
|
|
2643
|
+
},
|
|
2644
|
+
phaseAffinity: ["workflow"]
|
|
2645
|
+
}
|
|
2646
|
+
],
|
|
2647
|
+
[
|
|
2648
|
+
"workflow_definition",
|
|
2649
|
+
{
|
|
2650
|
+
schema: import_types.WorkflowDefinitionSchema,
|
|
2651
|
+
synonyms: {
|
|
2652
|
+
label: ["title", "name"],
|
|
2653
|
+
id: ["type"]
|
|
2654
|
+
},
|
|
2655
|
+
phaseAffinity: ["workflow"]
|
|
2656
|
+
}
|
|
2657
|
+
],
|
|
2658
|
+
[
|
|
2659
|
+
"workflow_field",
|
|
2660
|
+
{
|
|
2661
|
+
schema: import_types.WorkflowFieldSchema,
|
|
2662
|
+
synonyms: {
|
|
2663
|
+
name: ["id"],
|
|
2664
|
+
label: ["title", "description"]
|
|
2665
|
+
},
|
|
2666
|
+
phaseAffinity: ["workflow"]
|
|
2667
|
+
}
|
|
2668
|
+
],
|
|
2669
|
+
[
|
|
2670
|
+
"workflow_action",
|
|
2671
|
+
{
|
|
2672
|
+
schema: import_types.WorkflowActionSchema,
|
|
2673
|
+
synonyms: {
|
|
2674
|
+
label: ["title", "name"],
|
|
2675
|
+
transition: ["then"],
|
|
2676
|
+
"theme.variant": ["style"],
|
|
2677
|
+
action: ["on_click"]
|
|
2678
|
+
},
|
|
2679
|
+
phaseAffinity: ["workflow"]
|
|
2680
|
+
}
|
|
2681
|
+
],
|
|
2682
|
+
// ── Navigation ────────────────────────────────────────────────────────────
|
|
2683
|
+
[
|
|
2684
|
+
"navigation_item",
|
|
2685
|
+
{
|
|
2686
|
+
schema: NavigationItemSchema,
|
|
2687
|
+
synonyms: {
|
|
2688
|
+
items: ["children"],
|
|
2689
|
+
section: ["label", "title"]
|
|
2690
|
+
},
|
|
2691
|
+
phaseAffinity: ["pages", "dashboard", "polish"]
|
|
2692
|
+
}
|
|
2693
|
+
],
|
|
2694
|
+
// ── Auth ──────────────────────────────────────────────────────────────────
|
|
2695
|
+
[
|
|
2696
|
+
"auth_config",
|
|
2697
|
+
{
|
|
2698
|
+
schema: import_types.authConfigSchema,
|
|
2699
|
+
synonyms: {
|
|
2700
|
+
type: ["method", "provider_type", "strategy"]
|
|
2701
|
+
},
|
|
2702
|
+
phaseAffinity: ["auth"]
|
|
2703
|
+
}
|
|
2704
|
+
],
|
|
2705
|
+
// ── Pulse content types ───────────────────────────────────────────────────
|
|
2706
|
+
[
|
|
2707
|
+
"metric_card_pulse",
|
|
2708
|
+
{
|
|
2709
|
+
schema: import_pulse2.MetricCardPulseSchema,
|
|
2710
|
+
synonyms: {
|
|
2711
|
+
field: ["valueField", "valuePath", "dataField", "value"]
|
|
2712
|
+
},
|
|
2713
|
+
phaseAffinity: ["dashboard", "pages"]
|
|
2714
|
+
}
|
|
2715
|
+
],
|
|
2716
|
+
[
|
|
2717
|
+
"data_table_pulse",
|
|
2718
|
+
{
|
|
2719
|
+
schema: import_pulse2.DataTablePulseSchema,
|
|
2720
|
+
synonyms: {
|
|
2721
|
+
header: ["label"],
|
|
2722
|
+
type: ["renderAs"],
|
|
2723
|
+
colorMap: ["severityMap"]
|
|
2724
|
+
},
|
|
2725
|
+
phaseAffinity: ["dashboard", "pages"]
|
|
2726
|
+
}
|
|
2727
|
+
],
|
|
2728
|
+
[
|
|
2729
|
+
"status_badge_pulse",
|
|
2730
|
+
{
|
|
2731
|
+
schema: import_pulse2.StatusBadgePulseSchema,
|
|
2732
|
+
synonyms: {},
|
|
2733
|
+
phaseAffinity: ["dashboard", "pages"]
|
|
2734
|
+
}
|
|
2735
|
+
],
|
|
2736
|
+
[
|
|
2737
|
+
"map_pulse",
|
|
2738
|
+
{
|
|
2739
|
+
schema: import_pulse2.MapPulseSchema,
|
|
2740
|
+
synonyms: {},
|
|
2741
|
+
phaseAffinity: ["geo", "dashboard", "pages"]
|
|
2742
|
+
}
|
|
2743
|
+
],
|
|
2744
|
+
// ── Workflow container page content type (swp-dvde) ───────────────────────
|
|
2745
|
+
[
|
|
2746
|
+
"workflow_wizard",
|
|
2747
|
+
{
|
|
2748
|
+
schema: import_types.WorkflowWizardContentSchema,
|
|
2749
|
+
synonyms: {
|
|
2750
|
+
workflowFile: ["workflowPath", "file", "path", "yamlFile"],
|
|
2751
|
+
workflowId: ["id", "workflowName", "name"],
|
|
2752
|
+
persistenceKey: ["stateKey", "storageKey"]
|
|
2753
|
+
},
|
|
2754
|
+
phaseAffinity: ["workflow"]
|
|
2755
|
+
}
|
|
2756
|
+
],
|
|
2757
|
+
// ── Theme schemas (swp-cxwu) ──────────────────────────────────────────────
|
|
2758
|
+
[
|
|
2759
|
+
"theme_tokens",
|
|
2760
|
+
{
|
|
2761
|
+
schema: import_types.themeTokensFileSchema,
|
|
2762
|
+
synonyms: {},
|
|
2763
|
+
phaseAffinity: ["theme"]
|
|
2764
|
+
}
|
|
2765
|
+
],
|
|
2766
|
+
[
|
|
2767
|
+
"theme_status_token",
|
|
2768
|
+
{
|
|
2769
|
+
// No static schema — this is a project-specific runtime enum.
|
|
2770
|
+
// Call getSchemaForEntry(entry, { projectRoot }) to get the project's ladder.
|
|
2771
|
+
schemaFactory: buildThemeStatusTokenSchema,
|
|
2772
|
+
synonyms: {},
|
|
2773
|
+
phaseAffinity: ["pages", "dashboard", "geo"]
|
|
2774
|
+
}
|
|
2775
|
+
]
|
|
2776
|
+
]);
|
|
2777
|
+
var SCHEMA_REGISTRY = REGISTRY;
|
|
2778
|
+
var REGISTRY_SCHEMA_NAMES = Array.from(REGISTRY.keys());
|
|
2779
|
+
function buildReverseSynonyms(synonyms) {
|
|
2780
|
+
const reverse = {};
|
|
2781
|
+
for (const [correct, wrongs] of Object.entries(synonyms)) {
|
|
2782
|
+
for (const wrong of wrongs) {
|
|
2783
|
+
reverse[wrong] = correct;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
return reverse;
|
|
2787
|
+
}
|
|
2788
|
+
function buildPhaseSchemaNames() {
|
|
2789
|
+
const phaseMap = {};
|
|
2790
|
+
for (const [name, entry] of REGISTRY.entries()) {
|
|
2791
|
+
for (const phase of entry.phaseAffinity ?? []) {
|
|
2792
|
+
if (!phaseMap[phase]) phaseMap[phase] = [];
|
|
2793
|
+
phaseMap[phase].push(name);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
return phaseMap;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// src/tools/validate-theme-tokens.ts
|
|
2800
|
+
var COLOR_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
2801
|
+
"color",
|
|
2802
|
+
"colorMap",
|
|
2803
|
+
"layerColor",
|
|
2804
|
+
"borderColor",
|
|
2805
|
+
"backgroundColor",
|
|
2806
|
+
"iconColor",
|
|
2807
|
+
"fill",
|
|
2808
|
+
"stroke",
|
|
2809
|
+
"markerColor",
|
|
2810
|
+
"fillColor",
|
|
2811
|
+
"outlineColor",
|
|
2812
|
+
"statusColor",
|
|
2813
|
+
"badgeColor",
|
|
2814
|
+
"tagColor"
|
|
2815
|
+
]);
|
|
2816
|
+
var LEGACY_OSS_TOKENS = /* @__PURE__ */ new Set(["success", "warning", "danger", "info", "muted"]);
|
|
2817
|
+
var LEGACY_MAPPING_HINT = "Suggested mapping: success->status-safe, warning->status-warning, danger->status-critical, info->status-watch, muted->status-at-risk.";
|
|
2818
|
+
var HEX_RE = /^#[0-9a-f]{3,8}$/i;
|
|
2819
|
+
var STATUS_TOKEN_RE = /^status-[a-z0-9-]+$/;
|
|
2820
|
+
function walkYaml(node, fieldName, pathParts, inColorContext, visitor) {
|
|
2821
|
+
if (typeof node === "string") {
|
|
2822
|
+
visitor(node, fieldName, pathParts.join(".") || "(root)", inColorContext);
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
if (Array.isArray(node)) {
|
|
2826
|
+
node.forEach((item, idx) => {
|
|
2827
|
+
walkYaml(item, fieldName, [...pathParts, `[${idx}]`], inColorContext, visitor);
|
|
2828
|
+
});
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
if (node !== null && typeof node === "object") {
|
|
2832
|
+
for (const [key, val] of Object.entries(node)) {
|
|
2833
|
+
const thisIsColorField = COLOR_FIELD_KEYS.has(key);
|
|
2834
|
+
walkYaml(val, key, [...pathParts, key], inColorContext || thisIsColorField, visitor);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function detectViolationsInYaml(yamlContent, ladder, ladderSet, relPath) {
|
|
2839
|
+
const violations = [];
|
|
2840
|
+
walkYaml(yamlContent, null, [], false, (value, fieldName, path5, inColorContext) => {
|
|
2841
|
+
const isColorField = inColorContext || fieldName !== null && COLOR_FIELD_KEYS.has(fieldName);
|
|
2842
|
+
if (STATUS_TOKEN_RE.test(value) && !ladderSet.has(value)) {
|
|
2843
|
+
violations.push({
|
|
2844
|
+
type: "undefined_token",
|
|
2845
|
+
message: `Undefined theme token: "${value}" not in declared theme ladder. Declared tokens: ${ladder.join(", ")}`,
|
|
2846
|
+
file: relPath,
|
|
2847
|
+
path: path5,
|
|
2848
|
+
value
|
|
2849
|
+
});
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
if (!isColorField) return;
|
|
2853
|
+
if (HEX_RE.test(value)) {
|
|
2854
|
+
violations.push({
|
|
2855
|
+
type: "raw_hex",
|
|
2856
|
+
message: `Raw hex color "${value}" in color field "${fieldName ?? "<color context>"}". Use declared theme tokens instead. Available: ${ladder.join(", ")}`,
|
|
2857
|
+
file: relPath,
|
|
2858
|
+
path: path5,
|
|
2859
|
+
value
|
|
2860
|
+
});
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
if (LEGACY_OSS_TOKENS.has(value)) {
|
|
2864
|
+
violations.push({
|
|
2865
|
+
type: "legacy_vocab",
|
|
2866
|
+
message: `Legacy status token "${value}" in color field "${fieldName ?? "<color context>"}". This project's ladder is: ${ladder.join(", ")}. ` + LEGACY_MAPPING_HINT,
|
|
2867
|
+
file: relPath,
|
|
2868
|
+
path: path5,
|
|
2869
|
+
value
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
});
|
|
2873
|
+
return violations;
|
|
2874
|
+
}
|
|
2875
|
+
function findContentFiles(pagesRoot) {
|
|
2876
|
+
if (!(0, import_fs7.existsSync)(pagesRoot)) return [];
|
|
2877
|
+
const files = [];
|
|
2878
|
+
try {
|
|
2879
|
+
const slugDirs = (0, import_fs7.readdirSync)(pagesRoot, { withFileTypes: true });
|
|
2880
|
+
for (const entry of slugDirs) {
|
|
2881
|
+
if (!entry.isDirectory()) continue;
|
|
2882
|
+
const slugDir = (0, import_path7.join)(pagesRoot, entry.name);
|
|
2883
|
+
for (const candidate of ["content.yml", "content.yaml"]) {
|
|
2884
|
+
const p = (0, import_path7.join)(slugDir, candidate);
|
|
2885
|
+
if ((0, import_fs7.existsSync)(p)) files.push(p);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
} catch {
|
|
2889
|
+
}
|
|
2890
|
+
return files;
|
|
2891
|
+
}
|
|
2892
|
+
function validateThemeTokenUsage(projectRoot) {
|
|
2893
|
+
const ladder = resolveStatusLadder(projectRoot);
|
|
2894
|
+
if (ladder.length === 0) {
|
|
2895
|
+
return {
|
|
2896
|
+
valid: true,
|
|
2897
|
+
violations: [],
|
|
2898
|
+
skipped: true
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
const ladderSet = new Set(ladder);
|
|
2902
|
+
const pagesRoot = (0, import_path7.join)(projectRoot, "pages");
|
|
2903
|
+
const contentFiles = findContentFiles(pagesRoot);
|
|
2904
|
+
const allViolations = [];
|
|
2905
|
+
for (const absPath of contentFiles) {
|
|
2906
|
+
const relPath = (0, import_path7.relative)(projectRoot, absPath);
|
|
2907
|
+
try {
|
|
2908
|
+
const raw = (0, import_fs7.readFileSync)(absPath, "utf-8");
|
|
2909
|
+
const parsed = (0, import_js_yaml2.load)(raw);
|
|
2910
|
+
const fileViolations = detectViolationsInYaml(parsed, ladder, ladderSet, relPath);
|
|
2911
|
+
allViolations.push(...fileViolations);
|
|
2912
|
+
} catch {
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
if (allViolations.length === 0) {
|
|
2916
|
+
return { valid: true, violations: [] };
|
|
2917
|
+
}
|
|
2918
|
+
const lines = [
|
|
2919
|
+
`Theme token enforcement found ${allViolations.length} violation(s):`,
|
|
2920
|
+
"",
|
|
2921
|
+
...allViolations.map((v, i) => {
|
|
2922
|
+
const typeLabel = v.type === "undefined_token" ? "UNDEFINED TOKEN" : v.type === "raw_hex" ? "RAW HEX" : "LEGACY VOCAB";
|
|
2923
|
+
return ` ${i + 1}. [${typeLabel}] ${v.message}
|
|
2924
|
+
Emitted in: ${v.file} at ${v.path}`;
|
|
2925
|
+
}),
|
|
2926
|
+
"",
|
|
2927
|
+
`Fix all violations to use declared theme tokens: ${ladder.join(", ")}`
|
|
2928
|
+
];
|
|
2929
|
+
return {
|
|
2930
|
+
valid: false,
|
|
2931
|
+
violations: allViolations,
|
|
2932
|
+
retryPrompt: lines.join("\n")
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// src/tools/validate-build-context-globals.ts
|
|
2937
|
+
var import_fs8 = require("fs");
|
|
2938
|
+
var import_path8 = require("path");
|
|
2939
|
+
var import_js_yaml3 = require("js-yaml");
|
|
2940
|
+
var DATA_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
2941
|
+
"data_table_pulse",
|
|
2942
|
+
"stats_grid",
|
|
2943
|
+
"metric_card_pulse",
|
|
2944
|
+
"map_pulse"
|
|
2945
|
+
]);
|
|
2946
|
+
function readGlobalDefaults(projectRoot) {
|
|
2947
|
+
const bcPath = (0, import_path8.join)(projectRoot, ".stackwright", "build-context.json");
|
|
2948
|
+
if (!(0, import_fs8.existsSync)(bcPath)) return null;
|
|
2949
|
+
try {
|
|
2950
|
+
const raw = (0, import_fs8.readFileSync)(bcPath, "utf-8");
|
|
2951
|
+
const parsed = JSON.parse(raw);
|
|
2952
|
+
const gd = parsed.globalDefaults;
|
|
2953
|
+
if (!gd || typeof gd !== "object") return null;
|
|
2954
|
+
return gd;
|
|
2955
|
+
} catch {
|
|
2956
|
+
return null;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
function findDataWidgets(node, pathParts, out) {
|
|
2960
|
+
if (node === null || node === void 0 || typeof node !== "object") return;
|
|
2961
|
+
if (Array.isArray(node)) {
|
|
2962
|
+
node.forEach((item, idx) => {
|
|
2963
|
+
findDataWidgets(item, [...pathParts, `[${idx}]`], out);
|
|
2964
|
+
});
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
const obj = node;
|
|
2968
|
+
const contentType = typeof obj["type"] === "string" ? obj["type"] : null;
|
|
2969
|
+
if (contentType && DATA_WIDGET_TYPES.has(contentType)) {
|
|
2970
|
+
out.push({
|
|
2971
|
+
type: contentType,
|
|
2972
|
+
label: typeof obj["label"] === "string" ? obj["label"] : void 0,
|
|
2973
|
+
data: obj,
|
|
2974
|
+
path: pathParts.join(".") || "(root)"
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
2978
|
+
findDataWidgets(val, [...pathParts, key], out);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
function findContentFiles2(pagesRoot) {
|
|
2982
|
+
if (!(0, import_fs8.existsSync)(pagesRoot)) return [];
|
|
2983
|
+
const files = [];
|
|
2984
|
+
try {
|
|
2985
|
+
const slugDirs = (0, import_fs8.readdirSync)(pagesRoot, { withFileTypes: true });
|
|
2986
|
+
for (const entry of slugDirs) {
|
|
2987
|
+
if (!entry.isDirectory()) continue;
|
|
2988
|
+
for (const candidate of ["content.yml", "content.yaml"]) {
|
|
2989
|
+
const p = (0, import_path8.join)(pagesRoot, entry.name, candidate);
|
|
2990
|
+
if ((0, import_fs8.existsSync)(p)) files.push(p);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
} catch {
|
|
2994
|
+
}
|
|
2995
|
+
return files;
|
|
2996
|
+
}
|
|
2997
|
+
function validateBuildContextGlobals(projectRoot) {
|
|
2998
|
+
const globalDefaults = readGlobalDefaults(projectRoot);
|
|
2999
|
+
if (!globalDefaults) {
|
|
3000
|
+
return { valid: true, violations: [], skipped: true };
|
|
3001
|
+
}
|
|
3002
|
+
const requiredFields = Object.entries(globalDefaults).filter(([, v]) => v === true).map(([k]) => k);
|
|
3003
|
+
if (requiredFields.length === 0) {
|
|
3004
|
+
return { valid: true, violations: [], skipped: true };
|
|
3005
|
+
}
|
|
3006
|
+
const pagesRoot = (0, import_path8.join)(projectRoot, "pages");
|
|
3007
|
+
const contentFiles = findContentFiles2(pagesRoot);
|
|
3008
|
+
const allViolations = [];
|
|
3009
|
+
for (const absPath of contentFiles) {
|
|
3010
|
+
const relPath = (0, import_path8.relative)(projectRoot, absPath);
|
|
3011
|
+
try {
|
|
3012
|
+
const raw = (0, import_fs8.readFileSync)(absPath, "utf-8");
|
|
3013
|
+
const parsed = (0, import_js_yaml3.load)(raw);
|
|
3014
|
+
const widgets = [];
|
|
3015
|
+
findDataWidgets(parsed, [], widgets);
|
|
3016
|
+
for (const widget of widgets) {
|
|
3017
|
+
for (const field of requiredFields) {
|
|
3018
|
+
if (widget.data[field] !== true) {
|
|
3019
|
+
allViolations.push({
|
|
3020
|
+
field,
|
|
3021
|
+
widgetType: widget.type,
|
|
3022
|
+
widgetLabel: widget.label,
|
|
3023
|
+
file: relPath,
|
|
3024
|
+
path: widget.path
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
} catch {
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
if (allViolations.length === 0) {
|
|
3033
|
+
return { valid: true, violations: [] };
|
|
3034
|
+
}
|
|
3035
|
+
const lines = [
|
|
3036
|
+
`BUILD_CONTEXT global defaults: ${allViolations.length} violation(s) found.`,
|
|
3037
|
+
"",
|
|
3038
|
+
...allViolations.map((v, i) => {
|
|
3039
|
+
const widgetId = v.widgetLabel ? `"${v.widgetLabel}" (${v.widgetType})` : `${v.widgetType}`;
|
|
3040
|
+
return ` ${i + 1}. BUILD_CONTEXT.${v.field} is globally true but widget ${widgetId} omits it.
|
|
3041
|
+
Set ${v.field}: true on this widget.
|
|
3042
|
+
Emitted in: ${v.file} at ${v.path}`;
|
|
3043
|
+
}),
|
|
3044
|
+
"",
|
|
3045
|
+
`Required global defaults: ${requiredFields.map((f) => `${f}: true`).join(", ")}`
|
|
3046
|
+
];
|
|
3047
|
+
return {
|
|
3048
|
+
valid: false,
|
|
3049
|
+
violations: allViolations,
|
|
3050
|
+
retryPrompt: lines.join("\n")
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
// src/tools/validate-workflow-container-pages.ts
|
|
3055
|
+
var import_fs9 = require("fs");
|
|
3056
|
+
var import_path9 = require("path");
|
|
3057
|
+
var import_js_yaml4 = require("js-yaml");
|
|
3058
|
+
function discoverWorkflowIds(projectRoot) {
|
|
3059
|
+
const artifactsDir = (0, import_path9.join)(projectRoot, ".stackwright", "artifacts");
|
|
3060
|
+
if (!(0, import_fs9.existsSync)(artifactsDir)) return [];
|
|
3061
|
+
const entries = [];
|
|
3062
|
+
let files;
|
|
3063
|
+
try {
|
|
3064
|
+
files = (0, import_fs9.readdirSync)(artifactsDir);
|
|
3065
|
+
} catch {
|
|
3066
|
+
return [];
|
|
3067
|
+
}
|
|
3068
|
+
for (const filename of files) {
|
|
3069
|
+
if (filename !== "workflow-config.json" && !filename.startsWith("workflow-config-")) continue;
|
|
3070
|
+
if (!filename.endsWith(".json")) continue;
|
|
3071
|
+
const fullPath = (0, import_path9.join)(artifactsDir, filename);
|
|
3072
|
+
try {
|
|
3073
|
+
const raw = JSON.parse((0, import_fs9.readFileSync)(fullPath, "utf-8"));
|
|
3074
|
+
const workflowData = raw["workflow"] ?? raw["workflowConfig"];
|
|
3075
|
+
const workflowId = typeof workflowData?.["id"] === "string" ? workflowData["id"] : null;
|
|
3076
|
+
if (workflowId && /^[a-z0-9-]+$/.test(workflowId)) {
|
|
3077
|
+
entries.push({ workflowId, sourceFile: filename });
|
|
3078
|
+
}
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
return entries;
|
|
3083
|
+
}
|
|
3084
|
+
function hasWorkflowWizardType(filePath) {
|
|
3085
|
+
try {
|
|
3086
|
+
const raw = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
3087
|
+
const parsed = (0, import_js_yaml4.load)(raw);
|
|
3088
|
+
if (!parsed) return false;
|
|
3089
|
+
const content = parsed["content"];
|
|
3090
|
+
if (!content) return false;
|
|
3091
|
+
const items = content["content_items"];
|
|
3092
|
+
if (!Array.isArray(items)) return false;
|
|
3093
|
+
return items.some((item) => item["type"] === "workflow_wizard");
|
|
3094
|
+
} catch {
|
|
3095
|
+
return false;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
function validateWorkflowContainerPages(projectRoot) {
|
|
3099
|
+
const workflows = discoverWorkflowIds(projectRoot);
|
|
3100
|
+
if (workflows.length === 0) {
|
|
3101
|
+
return { valid: true, violations: [], skipped: true };
|
|
3102
|
+
}
|
|
3103
|
+
const violations = [];
|
|
3104
|
+
for (const { workflowId, sourceFile: _sourceFile } of workflows) {
|
|
3105
|
+
const expectedPath = `pages/workflows/${workflowId}/content.yml`;
|
|
3106
|
+
const fullPath = (0, import_path9.join)(projectRoot, expectedPath);
|
|
3107
|
+
if (!(0, import_fs9.existsSync)(fullPath)) {
|
|
3108
|
+
violations.push({ workflowId, expectedPath, reason: "missing-file" });
|
|
3109
|
+
continue;
|
|
3110
|
+
}
|
|
3111
|
+
if (!hasWorkflowWizardType(fullPath)) {
|
|
3112
|
+
violations.push({ workflowId, expectedPath, reason: "missing-workflow-wizard-type" });
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
if (violations.length === 0) {
|
|
3116
|
+
return { valid: true, violations: [] };
|
|
3117
|
+
}
|
|
3118
|
+
const lines = [
|
|
3119
|
+
`Workflow container page cross-check failed: ${violations.length} workflow(s) missing container pages.`,
|
|
3120
|
+
"",
|
|
3121
|
+
...violations.map((v, i) => {
|
|
3122
|
+
const reason = v.reason === "missing-file" ? `File does not exist. Create it via stackwright_pro_write_workflow_container_page.` : `File exists but has no workflow_wizard content item. Must contain type: workflow_wizard.`;
|
|
3123
|
+
return ` ${i + 1}. workflowId "${v.workflowId}"
|
|
3124
|
+
Expected: ${v.expectedPath}
|
|
3125
|
+
Issue: ${reason}`;
|
|
3126
|
+
}),
|
|
3127
|
+
"",
|
|
3128
|
+
"Fix: workflow-otter must call stackwright_pro_write_workflow_container_page immediately",
|
|
3129
|
+
"after writing each workflow YAML. Required args:",
|
|
3130
|
+
' { workflowId, workflowFile, pageSlug: "workflows/<workflowId>", label, requiredRoles }',
|
|
3131
|
+
"",
|
|
3132
|
+
"Without the container page, the route 404s and auth-reconcile cannot protect it."
|
|
3133
|
+
];
|
|
3134
|
+
return {
|
|
3135
|
+
valid: false,
|
|
3136
|
+
violations,
|
|
3137
|
+
retryPrompt: lines.join("\n")
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
|
|
2562
3141
|
// src/tools/validate-api-integration.ts
|
|
2563
3142
|
var CANONICAL_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2", "oidc", "none"];
|
|
2564
3143
|
var CANONICAL_SET = new Set(CANONICAL_API_AUTH_TYPES);
|
|
@@ -2751,9 +3330,9 @@ function validateAuthConfig(artifact, artifactPath) {
|
|
|
2751
3330
|
}
|
|
2752
3331
|
|
|
2753
3332
|
// src/tools/auth-manifest-aggregator.ts
|
|
2754
|
-
var
|
|
2755
|
-
var
|
|
2756
|
-
var
|
|
3333
|
+
var import_fs10 = require("fs");
|
|
3334
|
+
var import_path10 = require("path");
|
|
3335
|
+
var import_zod12 = require("zod");
|
|
2757
3336
|
function computeLowestRole(requiredRoles, rbacRoles) {
|
|
2758
3337
|
let lowestIndex = -1;
|
|
2759
3338
|
let lowestRole = requiredRoles[0];
|
|
@@ -2773,17 +3352,17 @@ function extractPatternBaseSlug(pattern) {
|
|
|
2773
3352
|
}
|
|
2774
3353
|
function extractManifestSlugs(artifactsDir) {
|
|
2775
3354
|
const slugs = /* @__PURE__ */ new Set();
|
|
2776
|
-
if (!(0,
|
|
3355
|
+
if (!(0, import_fs10.existsSync)(artifactsDir)) return slugs;
|
|
2777
3356
|
let entries;
|
|
2778
3357
|
try {
|
|
2779
|
-
entries = (0,
|
|
3358
|
+
entries = (0, import_fs10.readdirSync)(artifactsDir);
|
|
2780
3359
|
} catch {
|
|
2781
3360
|
return slugs;
|
|
2782
3361
|
}
|
|
2783
3362
|
for (const entry of entries) {
|
|
2784
3363
|
if (!entry.endsWith("-manifest.json")) continue;
|
|
2785
3364
|
try {
|
|
2786
|
-
const content = JSON.parse((0,
|
|
3365
|
+
const content = JSON.parse((0, import_fs10.readFileSync)((0, import_path10.join)(artifactsDir, entry), "utf-8"));
|
|
2787
3366
|
const pages = content.pages;
|
|
2788
3367
|
if (!Array.isArray(pages)) continue;
|
|
2789
3368
|
for (const page of pages) {
|
|
@@ -2826,18 +3405,18 @@ function deriveProtectedRoutes(pages, rbacRoles) {
|
|
|
2826
3405
|
}
|
|
2827
3406
|
function readAllManifestPages(artifactsDir) {
|
|
2828
3407
|
const allPages = [];
|
|
2829
|
-
if (!(0,
|
|
3408
|
+
if (!(0, import_fs10.existsSync)(artifactsDir)) return allPages;
|
|
2830
3409
|
let entries;
|
|
2831
3410
|
try {
|
|
2832
|
-
entries = (0,
|
|
3411
|
+
entries = (0, import_fs10.readdirSync)(artifactsDir);
|
|
2833
3412
|
} catch {
|
|
2834
3413
|
return allPages;
|
|
2835
3414
|
}
|
|
2836
3415
|
for (const entry of entries) {
|
|
2837
3416
|
if (entry.endsWith("-manifest.json")) {
|
|
2838
|
-
const filePath = (0,
|
|
3417
|
+
const filePath = (0, import_path10.join)(artifactsDir, entry);
|
|
2839
3418
|
try {
|
|
2840
|
-
const raw = JSON.parse((0,
|
|
3419
|
+
const raw = JSON.parse((0, import_fs10.readFileSync)(filePath, "utf-8"));
|
|
2841
3420
|
if (!Array.isArray(raw.pages)) continue;
|
|
2842
3421
|
for (const page of raw.pages) {
|
|
2843
3422
|
if (typeof page.slug !== "string" || !page.slug) continue;
|
|
@@ -2853,9 +3432,9 @@ function readAllManifestPages(artifactsDir) {
|
|
|
2853
3432
|
continue;
|
|
2854
3433
|
}
|
|
2855
3434
|
if (entry === "workflow-config.json" || entry.startsWith("workflow-config-")) {
|
|
2856
|
-
const filePath = (0,
|
|
3435
|
+
const filePath = (0, import_path10.join)(artifactsDir, entry);
|
|
2857
3436
|
try {
|
|
2858
|
-
const raw = JSON.parse((0,
|
|
3437
|
+
const raw = JSON.parse((0, import_fs10.readFileSync)(filePath, "utf-8"));
|
|
2859
3438
|
const route = raw.workflow?.route;
|
|
2860
3439
|
if (typeof route !== "string" || !route) continue;
|
|
2861
3440
|
const slug = route.startsWith("/") ? route.slice(1) : route;
|
|
@@ -2877,16 +3456,16 @@ function registerReadAuthManifestsTool(server2) {
|
|
|
2877
3456
|
"stackwright_pro_read_auth_manifests",
|
|
2878
3457
|
"Read ALL route-producing artifacts from the pipeline artifacts directory: *-manifest.json files (pages, dashboard, geo, form-wizard) AND workflow-config.json / workflow-config-*.json (different shape: workflow.route). Deterministically derives protectedRoutes and uncoveredSlugs. Uses the both-forms invariant (swp-n7kk): every authRequired route registers BOTH /{slug} (exact) AND /{slug}/:path* (sub-path). Workflow routes appear in uncoveredSlugs (no requiredRoles declared) for auth-reconcile to resolve via cross-cutting rules from auth-config.json. Extended by swp-hng0 to cover all four manifest types (was: pages+dashboard only).",
|
|
2879
3458
|
{
|
|
2880
|
-
rbacRoles:
|
|
3459
|
+
rbacRoles: import_zod12.z.array(import_zod12.z.string()).describe(
|
|
2881
3460
|
'RBAC role hierarchy in descending privilege order, e.g. ["ESF8_COORDINATOR", "HOSPITAL_EM", "OBSERVER"]. Used to compute the lowestRole for each authRequired page.'
|
|
2882
3461
|
),
|
|
2883
|
-
artifactsDir:
|
|
3462
|
+
artifactsDir: import_zod12.z.string().optional().describe(
|
|
2884
3463
|
"Absolute or relative path to the pipeline artifacts directory. Defaults to .stackwright/artifacts relative to cwd."
|
|
2885
3464
|
)
|
|
2886
3465
|
},
|
|
2887
3466
|
(params) => {
|
|
2888
3467
|
const cwd = process.cwd();
|
|
2889
|
-
const artifactsDir = params.artifactsDir ? params.artifactsDir : (0,
|
|
3468
|
+
const artifactsDir = params.artifactsDir ? params.artifactsDir : (0, import_path10.join)(cwd, ".stackwright", "artifacts");
|
|
2890
3469
|
const allPages = readAllManifestPages(artifactsDir);
|
|
2891
3470
|
const { protectedRoutes, uncoveredSlugs } = deriveProtectedRoutes(allPages, params.rbacRoles);
|
|
2892
3471
|
const manifestsRead = [...new Set(allPages.map((p) => p.source))].sort();
|
|
@@ -2909,183 +3488,11 @@ function registerReadAuthManifestsTool(server2) {
|
|
|
2909
3488
|
}
|
|
2910
3489
|
|
|
2911
3490
|
// src/tools/pipeline.ts
|
|
2912
|
-
var import_types3 = require("@stackwright-pro/types");
|
|
2913
|
-
|
|
2914
|
-
// src/tools/get-schema.ts
|
|
2915
|
-
var import_zod13 = require("zod");
|
|
2916
3491
|
var import_types2 = require("@stackwright-pro/types");
|
|
2917
3492
|
|
|
2918
|
-
// src/tools/validate-yaml-fragment.ts
|
|
2919
|
-
var import_zod12 = require("zod");
|
|
2920
|
-
var import_js_yaml2 = require("js-yaml");
|
|
2921
|
-
var import_types = require("@stackwright-pro/types");
|
|
2922
|
-
var SUPPORTED_SCHEMA_NAMES = [
|
|
2923
|
-
"workflow_step",
|
|
2924
|
-
"workflow_definition",
|
|
2925
|
-
"workflow_field",
|
|
2926
|
-
"workflow_action",
|
|
2927
|
-
"navigation_item",
|
|
2928
|
-
"auth_config"
|
|
2929
|
-
];
|
|
2930
|
-
var NavigationLinkSchema = import_zod12.z.lazy(
|
|
2931
|
-
() => import_zod12.z.object({
|
|
2932
|
-
label: import_zod12.z.string().min(1),
|
|
2933
|
-
href: import_zod12.z.string().min(1),
|
|
2934
|
-
children: import_zod12.z.array(NavigationLinkSchema).optional()
|
|
2935
|
-
})
|
|
2936
|
-
);
|
|
2937
|
-
var NavigationSectionSchema = import_zod12.z.object({
|
|
2938
|
-
section: import_zod12.z.string().min(1),
|
|
2939
|
-
items: import_zod12.z.array(NavigationLinkSchema)
|
|
2940
|
-
});
|
|
2941
|
-
var NavigationItemSchema = import_zod12.z.union([
|
|
2942
|
-
NavigationLinkSchema,
|
|
2943
|
-
NavigationSectionSchema
|
|
2944
|
-
]);
|
|
2945
|
-
var FIELD_SYNONYMS = {
|
|
2946
|
-
workflow_step: {
|
|
2947
|
-
title: "label",
|
|
2948
|
-
name: "label (at step level, use label: not name:)",
|
|
2949
|
-
description: "message (use message: for step-level descriptive text)",
|
|
2950
|
-
style: 'theme.variant \u2014 use theme: { variant: "primary" } not style: "primary"',
|
|
2951
|
-
transitions: 'on_submit.transition \u2014 use on_submit: { transition: "next_step" } not transitions: [{then:...}]'
|
|
2952
|
-
},
|
|
2953
|
-
workflow_definition: {
|
|
2954
|
-
title: "label",
|
|
2955
|
-
name: "label (workflow-level label, not name:)",
|
|
2956
|
-
description: "description is valid here \u2014 but message: is not (that is a step field)",
|
|
2957
|
-
type: "id (workflows are identified by id:, not type:)"
|
|
2958
|
-
},
|
|
2959
|
-
workflow_field: {
|
|
2960
|
-
id: "name (field-level identifier is name:, NOT id:)",
|
|
2961
|
-
title: "label (field display text is label:, NOT title:)",
|
|
2962
|
-
description: "placeholder or label (no description field on WorkflowField)"
|
|
2963
|
-
},
|
|
2964
|
-
workflow_action: {
|
|
2965
|
-
style: 'theme.variant \u2014 use theme: { variant: "primary" } not style: "primary"',
|
|
2966
|
-
then: "transition (action-level next-step is transition:, NOT then:)",
|
|
2967
|
-
on_click: 'action: "service:..." (extract the service reference from on_click.action)',
|
|
2968
|
-
title: "label (action display text is label:, NOT title:)",
|
|
2969
|
-
name: "label (action display text is label:, NOT name:)"
|
|
2970
|
-
},
|
|
2971
|
-
navigation_item: {
|
|
2972
|
-
children: "items (NavigationSection uses items: not children: \u2014 also use section: instead of label: for section headers)",
|
|
2973
|
-
label: 'section (if this is a nav group/section, use section: "Title" and items: [...], not label:)',
|
|
2974
|
-
title: "section (NavigationSection header field is section:, NOT title:)"
|
|
2975
|
-
},
|
|
2976
|
-
auth_config: {
|
|
2977
|
-
method: 'type \u2014 auth config discriminates on type: "oidc" | "pki", NOT method:',
|
|
2978
|
-
provider_type: 'type \u2014 use type: "oidc" or type: "pki"',
|
|
2979
|
-
strategy: "type \u2014 top-level discriminator is type:, not strategy:"
|
|
2980
|
-
}
|
|
2981
|
-
};
|
|
2982
|
-
function getSchema(name) {
|
|
2983
|
-
switch (name) {
|
|
2984
|
-
case "workflow_step":
|
|
2985
|
-
return import_types.WorkflowStepSchema;
|
|
2986
|
-
case "workflow_definition":
|
|
2987
|
-
return import_types.WorkflowDefinitionSchema;
|
|
2988
|
-
case "workflow_field":
|
|
2989
|
-
return import_types.WorkflowFieldSchema;
|
|
2990
|
-
case "workflow_action":
|
|
2991
|
-
return import_types.WorkflowActionSchema;
|
|
2992
|
-
case "navigation_item":
|
|
2993
|
-
return NavigationItemSchema;
|
|
2994
|
-
case "auth_config":
|
|
2995
|
-
return import_types.authConfigSchema;
|
|
2996
|
-
}
|
|
2997
|
-
}
|
|
2998
|
-
function enrichErrors(issues, synonyms) {
|
|
2999
|
-
return issues.map((issue) => {
|
|
3000
|
-
const pathStr = issue.path.join(".");
|
|
3001
|
-
const lastSegment = issue.path[issue.path.length - 1];
|
|
3002
|
-
const fieldName = typeof lastSegment === "string" ? lastSegment : void 0;
|
|
3003
|
-
const didYouMean = fieldName ? synonyms[fieldName] : void 0;
|
|
3004
|
-
return {
|
|
3005
|
-
path: pathStr || "(root)",
|
|
3006
|
-
message: issue.message,
|
|
3007
|
-
...didYouMean ? { didYouMean } : {}
|
|
3008
|
-
};
|
|
3009
|
-
});
|
|
3010
|
-
}
|
|
3011
|
-
function handleValidateYamlFragment(input) {
|
|
3012
|
-
const { schemaName, yaml: yaml4 } = input;
|
|
3013
|
-
if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
|
|
3014
|
-
return {
|
|
3015
|
-
valid: false,
|
|
3016
|
-
parseError: `Unknown schemaName: "${schemaName}". Supported: ${SUPPORTED_SCHEMA_NAMES.join(", ")}`
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
const name = schemaName;
|
|
3020
|
-
let parsed;
|
|
3021
|
-
try {
|
|
3022
|
-
parsed = (0, import_js_yaml2.load)(yaml4);
|
|
3023
|
-
} catch (err) {
|
|
3024
|
-
return {
|
|
3025
|
-
valid: false,
|
|
3026
|
-
parseError: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
|
-
const schema = getSchema(name);
|
|
3030
|
-
const result = schema.safeParse(parsed);
|
|
3031
|
-
if (result.success) {
|
|
3032
|
-
return { valid: true };
|
|
3033
|
-
}
|
|
3034
|
-
const synonyms = FIELD_SYNONYMS[name];
|
|
3035
|
-
const errors = enrichErrors(result.error.issues, synonyms);
|
|
3036
|
-
return { valid: false, errors };
|
|
3037
|
-
}
|
|
3038
|
-
function registerValidateYamlFragmentTool(server2) {
|
|
3039
|
-
server2.tool(
|
|
3040
|
-
"stackwright_pro_validate_yaml_fragment",
|
|
3041
|
-
'Validates a YAML snippet against a canonical Zod schema before writing to disk. Returns actionable errors with "did you mean?" suggestions for common field-name drift (title vs label, id vs name, style vs theme.variant, etc.). Call this before stackwright_pro_safe_write to catch schema violations in-session.',
|
|
3042
|
-
{
|
|
3043
|
-
schemaName: import_zod12.z.enum(SUPPORTED_SCHEMA_NAMES).describe(
|
|
3044
|
-
"Schema to validate against. One of: workflow_step, workflow_definition, workflow_field, workflow_action, navigation_item, auth_config"
|
|
3045
|
-
),
|
|
3046
|
-
yaml: import_zod12.z.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
|
|
3047
|
-
},
|
|
3048
|
-
async ({ schemaName, yaml: yaml4 }) => {
|
|
3049
|
-
const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
|
|
3050
|
-
return {
|
|
3051
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3052
|
-
};
|
|
3053
|
-
}
|
|
3054
|
-
);
|
|
3055
|
-
}
|
|
3056
|
-
|
|
3057
3493
|
// src/tools/get-schema.ts
|
|
3058
|
-
var
|
|
3059
|
-
|
|
3060
|
-
label: import_zod13.z.string().min(1),
|
|
3061
|
-
href: import_zod13.z.string().min(1),
|
|
3062
|
-
children: import_zod13.z.array(NavigationLinkSchema2).optional()
|
|
3063
|
-
})
|
|
3064
|
-
);
|
|
3065
|
-
var NavigationSectionSchema2 = import_zod13.z.object({
|
|
3066
|
-
section: import_zod13.z.string().min(1),
|
|
3067
|
-
items: import_zod13.z.array(NavigationLinkSchema2)
|
|
3068
|
-
});
|
|
3069
|
-
var NavigationItemSchema2 = import_zod13.z.union([
|
|
3070
|
-
NavigationLinkSchema2,
|
|
3071
|
-
NavigationSectionSchema2
|
|
3072
|
-
]);
|
|
3073
|
-
function getZodSchema(name) {
|
|
3074
|
-
switch (name) {
|
|
3075
|
-
case "workflow_step":
|
|
3076
|
-
return import_types2.WorkflowStepSchema;
|
|
3077
|
-
case "workflow_definition":
|
|
3078
|
-
return import_types2.WorkflowDefinitionSchema;
|
|
3079
|
-
case "workflow_field":
|
|
3080
|
-
return import_types2.WorkflowFieldSchema;
|
|
3081
|
-
case "workflow_action":
|
|
3082
|
-
return import_types2.WorkflowActionSchema;
|
|
3083
|
-
case "navigation_item":
|
|
3084
|
-
return NavigationItemSchema2;
|
|
3085
|
-
case "auth_config":
|
|
3086
|
-
return import_types2.authConfigSchema;
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3494
|
+
var import_zod13 = require("zod");
|
|
3495
|
+
var PHASE_SCHEMA_NAMES = buildPhaseSchemaNames();
|
|
3089
3496
|
function formatType(node, depth = 0) {
|
|
3090
3497
|
if (node.const !== void 0) return JSON.stringify(node.const);
|
|
3091
3498
|
if (node.enum) return node.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
@@ -3112,7 +3519,7 @@ function formatType(node, depth = 0) {
|
|
|
3112
3519
|
if (node.maximum !== void 0) constraints.push(`max ${node.maximum}`);
|
|
3113
3520
|
return constraints.length > 0 ? `${base} (${constraints.join(", ")})` : base;
|
|
3114
3521
|
}
|
|
3115
|
-
function formatObjectSchema(node, schemaName,
|
|
3522
|
+
function formatObjectSchema(node, schemaName, reverseSynonyms) {
|
|
3116
3523
|
const lines = [];
|
|
3117
3524
|
const displayName = schemaName.split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
3118
3525
|
lines.push(`${displayName}:`);
|
|
@@ -3126,7 +3533,8 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3126
3533
|
for (const [field, fieldNode] of Object.entries(branch.properties ?? {})) {
|
|
3127
3534
|
if (fieldNode.const !== void 0) continue;
|
|
3128
3535
|
const isRequired = req.has(field);
|
|
3129
|
-
const
|
|
3536
|
+
const correctFor = reverseSynonyms[field];
|
|
3537
|
+
const hint = correctFor ? ` -- WARNING: use ${correctFor}: not ${field}:` : "";
|
|
3130
3538
|
lines.push(
|
|
3131
3539
|
` ${isRequired ? "REQUIRED" : "optional"}: ${field}: ${formatType(fieldNode)}${hint}`
|
|
3132
3540
|
);
|
|
@@ -3134,6 +3542,12 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3134
3542
|
}
|
|
3135
3543
|
return lines.join("\n");
|
|
3136
3544
|
}
|
|
3545
|
+
if (!node.properties && !node.oneOf && !node.anyOf) {
|
|
3546
|
+
const typeStr = formatType(node);
|
|
3547
|
+
lines.push(` type: ${typeStr}`);
|
|
3548
|
+
if (node.description) lines.push(` description: ${node.description}`);
|
|
3549
|
+
return lines.join("\n");
|
|
3550
|
+
}
|
|
3137
3551
|
const required = new Set(node.required ?? []);
|
|
3138
3552
|
const properties = node.properties ?? {};
|
|
3139
3553
|
const requiredFields = Object.entries(properties).filter(([k]) => required.has(k));
|
|
@@ -3142,8 +3556,7 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3142
3556
|
lines.push(" REQUIRED:");
|
|
3143
3557
|
for (const [field, fieldNode] of requiredFields) {
|
|
3144
3558
|
const typeStr = formatType(fieldNode);
|
|
3145
|
-
|
|
3146
|
-
lines.push(` ${field}: ${typeStr}${warn}`);
|
|
3559
|
+
lines.push(` ${field}: ${typeStr}`);
|
|
3147
3560
|
}
|
|
3148
3561
|
}
|
|
3149
3562
|
if (optionalFields.length > 0) {
|
|
@@ -3157,24 +3570,30 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3157
3570
|
}
|
|
3158
3571
|
function handleGetSchema(input) {
|
|
3159
3572
|
const { schemaName } = input;
|
|
3160
|
-
|
|
3573
|
+
const entry = SCHEMA_REGISTRY.get(schemaName);
|
|
3574
|
+
if (!entry) {
|
|
3161
3575
|
return {
|
|
3162
|
-
error: `Unknown schemaName: "${schemaName}". Supported: ${
|
|
3576
|
+
error: `Unknown schemaName: "${schemaName}". Supported: ${REGISTRY_SCHEMA_NAMES.join(", ")}`
|
|
3163
3577
|
};
|
|
3164
3578
|
}
|
|
3165
|
-
const
|
|
3166
|
-
const
|
|
3167
|
-
const synonyms
|
|
3579
|
+
const synonyms = entry.synonyms ?? {};
|
|
3580
|
+
const reverseSynonyms = {};
|
|
3581
|
+
for (const [correct, wrongs] of Object.entries(synonyms)) {
|
|
3582
|
+
for (const wrong of wrongs) {
|
|
3583
|
+
reverseSynonyms[wrong] = correct;
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
const ctx = input.projectRoot ? { projectRoot: input.projectRoot } : void 0;
|
|
3587
|
+
const resolvedSchema = getSchemaForEntry(entry, ctx);
|
|
3168
3588
|
let jsonSchema;
|
|
3169
3589
|
try {
|
|
3170
|
-
|
|
3171
|
-
jsonSchema = raw;
|
|
3590
|
+
jsonSchema = import_zod13.z.toJSONSchema(resolvedSchema, { unrepresentable: "any" });
|
|
3172
3591
|
} catch {
|
|
3173
3592
|
jsonSchema = { type: "object", description: "Schema introspection unavailable for this type" };
|
|
3174
3593
|
}
|
|
3175
|
-
const summary = formatObjectSchema(jsonSchema,
|
|
3594
|
+
const summary = formatObjectSchema(jsonSchema, schemaName, reverseSynonyms);
|
|
3176
3595
|
const synonymWarnings = Object.entries(synonyms).map(
|
|
3177
|
-
([
|
|
3596
|
+
([correct, wrongs]) => ` WARNING: use "${correct}" \u2014 NOT ${wrongs.map((w) => `"${w}"`).join(" or ")}`
|
|
3178
3597
|
);
|
|
3179
3598
|
const fullText = [
|
|
3180
3599
|
summary,
|
|
@@ -3184,19 +3603,12 @@ function handleGetSchema(input) {
|
|
|
3184
3603
|
].join("\n");
|
|
3185
3604
|
const tokenEstimate = Math.ceil(fullText.length / 4);
|
|
3186
3605
|
return {
|
|
3187
|
-
schemaName
|
|
3606
|
+
schemaName,
|
|
3188
3607
|
summary,
|
|
3189
3608
|
synonymWarnings,
|
|
3190
3609
|
tokenEstimate
|
|
3191
3610
|
};
|
|
3192
3611
|
}
|
|
3193
|
-
var PHASE_SCHEMA_NAMES = {
|
|
3194
|
-
workflow: ["workflow_step", "workflow_definition", "workflow_field", "workflow_action"],
|
|
3195
|
-
pages: ["navigation_item"],
|
|
3196
|
-
dashboard: ["navigation_item"],
|
|
3197
|
-
polish: ["navigation_item"],
|
|
3198
|
-
auth: ["auth_config"]
|
|
3199
|
-
};
|
|
3200
3612
|
function buildSchemaReferenceBlock(phase) {
|
|
3201
3613
|
const schemaNames = PHASE_SCHEMA_NAMES[phase];
|
|
3202
3614
|
if (!schemaNames || schemaNames.length === 0) return null;
|
|
@@ -3221,12 +3633,13 @@ function registerGetSchemaTool(server2) {
|
|
|
3221
3633
|
"stackwright_pro_get_schema",
|
|
3222
3634
|
'Returns a compact, LLM-friendly summary of a canonical schema derived programmatically from Zod. Includes required/optional fields, types, constraints, and "did you mean?" warnings for common field-name drift. Use before authoring YAML to confirm exact field names.',
|
|
3223
3635
|
{
|
|
3224
|
-
schemaName: import_zod13.z.
|
|
3225
|
-
|
|
3636
|
+
schemaName: import_zod13.z.string().describe(`Schema to summarize. Available: ${REGISTRY_SCHEMA_NAMES.join(", ")}`),
|
|
3637
|
+
projectRoot: import_zod13.z.string().optional().describe(
|
|
3638
|
+
"Absolute path to the project root. Required for factory schemas like theme_status_token. Defaults to cwd."
|
|
3226
3639
|
)
|
|
3227
3640
|
},
|
|
3228
|
-
async ({ schemaName }) => {
|
|
3229
|
-
const result = handleGetSchema({ schemaName });
|
|
3641
|
+
async ({ schemaName, projectRoot }) => {
|
|
3642
|
+
const result = handleGetSchema({ schemaName, projectRoot: projectRoot ?? process.cwd() });
|
|
3230
3643
|
if ("error" in result) {
|
|
3231
3644
|
return {
|
|
3232
3645
|
content: [{ type: "text", text: JSON.stringify({ error: result.error }) }],
|
|
@@ -3246,8 +3659,8 @@ function registerGetSchemaTool(server2) {
|
|
|
3246
3659
|
}
|
|
3247
3660
|
|
|
3248
3661
|
// src/tools/pipeline-graph.ts
|
|
3249
|
-
var
|
|
3250
|
-
var
|
|
3662
|
+
var import_fs11 = require("fs");
|
|
3663
|
+
var import_path11 = require("path");
|
|
3251
3664
|
var MANIFEST_NAME_TO_PHASE = {
|
|
3252
3665
|
"stackwright-pro-designer-otter": "designer",
|
|
3253
3666
|
"stackwright-pro-theme-otter": "theme",
|
|
@@ -3279,10 +3692,10 @@ var OTTER_SEARCH_PATHS = [
|
|
|
3279
3692
|
];
|
|
3280
3693
|
function resolveOtterDirFromCwd() {
|
|
3281
3694
|
const cwd = process.cwd();
|
|
3282
|
-
for (const
|
|
3283
|
-
const candidate = (0,
|
|
3695
|
+
for (const relative4 of OTTER_SEARCH_PATHS) {
|
|
3696
|
+
const candidate = (0, import_path11.join)(cwd, relative4);
|
|
3284
3697
|
try {
|
|
3285
|
-
(0,
|
|
3698
|
+
(0, import_fs11.lstatSync)(candidate);
|
|
3286
3699
|
return candidate;
|
|
3287
3700
|
} catch {
|
|
3288
3701
|
}
|
|
@@ -3292,14 +3705,14 @@ function resolveOtterDirFromCwd() {
|
|
|
3292
3705
|
);
|
|
3293
3706
|
}
|
|
3294
3707
|
function loadOtterManifestsFromDir(otterDir) {
|
|
3295
|
-
const entries = (0,
|
|
3708
|
+
const entries = (0, import_fs11.readdirSync)(otterDir);
|
|
3296
3709
|
const manifests = [];
|
|
3297
3710
|
for (const filename of entries) {
|
|
3298
3711
|
if (!filename.endsWith("-otter.json")) continue;
|
|
3299
|
-
const filePath = (0,
|
|
3300
|
-
if ((0,
|
|
3712
|
+
const filePath = (0, import_path11.join)(otterDir, filename);
|
|
3713
|
+
if ((0, import_fs11.lstatSync)(filePath).isSymbolicLink()) continue;
|
|
3301
3714
|
try {
|
|
3302
|
-
const raw = (0,
|
|
3715
|
+
const raw = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
3303
3716
|
const manifest = JSON.parse(raw);
|
|
3304
3717
|
manifests.push(manifest);
|
|
3305
3718
|
} catch (err) {
|
|
@@ -3404,19 +3817,19 @@ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
|
3404
3817
|
function loadDistributedOtterManifests(cwd) {
|
|
3405
3818
|
const results = [];
|
|
3406
3819
|
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
3407
|
-
const nsDir = (0,
|
|
3820
|
+
const nsDir = (0, import_path11.join)(cwd, "node_modules", namespace);
|
|
3408
3821
|
let pkgDirNames;
|
|
3409
3822
|
try {
|
|
3410
|
-
pkgDirNames = (0,
|
|
3823
|
+
pkgDirNames = (0, import_fs11.readdirSync)(nsDir);
|
|
3411
3824
|
} catch {
|
|
3412
3825
|
continue;
|
|
3413
3826
|
}
|
|
3414
3827
|
for (const pkgDirName of pkgDirNames) {
|
|
3415
|
-
const pkgDir = (0,
|
|
3828
|
+
const pkgDir = (0, import_path11.join)(nsDir, pkgDirName);
|
|
3416
3829
|
const packageName = `${namespace}/${pkgDirName}`;
|
|
3417
3830
|
let otterRelPaths;
|
|
3418
3831
|
try {
|
|
3419
|
-
const raw = (0,
|
|
3832
|
+
const raw = (0, import_fs11.readFileSync)((0, import_path11.join)(pkgDir, "package.json"), "utf-8");
|
|
3420
3833
|
const pkgJson = JSON.parse(raw);
|
|
3421
3834
|
const declared = pkgJson.stackwright?.otters;
|
|
3422
3835
|
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
@@ -3425,9 +3838,9 @@ function loadDistributedOtterManifests(cwd) {
|
|
|
3425
3838
|
continue;
|
|
3426
3839
|
}
|
|
3427
3840
|
for (const relPath of otterRelPaths) {
|
|
3428
|
-
const otterFilePath = (0,
|
|
3841
|
+
const otterFilePath = (0, import_path11.join)(pkgDir, relPath);
|
|
3429
3842
|
try {
|
|
3430
|
-
const raw = (0,
|
|
3843
|
+
const raw = (0, import_fs11.readFileSync)(otterFilePath, "utf-8");
|
|
3431
3844
|
const manifest = JSON.parse(raw);
|
|
3432
3845
|
results.push({ manifest, packageName });
|
|
3433
3846
|
} catch (err) {
|
|
@@ -3572,45 +3985,45 @@ function createDefaultState() {
|
|
|
3572
3985
|
};
|
|
3573
3986
|
}
|
|
3574
3987
|
function statePath(cwd) {
|
|
3575
|
-
return (0,
|
|
3988
|
+
return (0, import_path12.join)(cwd, ".stackwright", "pipeline-state.json");
|
|
3576
3989
|
}
|
|
3577
3990
|
function readState(cwd) {
|
|
3578
3991
|
const p = statePath(cwd);
|
|
3579
|
-
if (!(0,
|
|
3992
|
+
if (!(0, import_fs12.existsSync)(p)) return createDefaultState();
|
|
3580
3993
|
const raw = JSON.parse(safeReadSync(p));
|
|
3581
3994
|
const result = PipelineStateSchema.safeParse(raw);
|
|
3582
3995
|
if (!result.success) return createDefaultState();
|
|
3583
3996
|
return result.data;
|
|
3584
3997
|
}
|
|
3585
3998
|
function safeWriteSync(filePath, content) {
|
|
3586
|
-
if ((0,
|
|
3587
|
-
const stat = (0,
|
|
3999
|
+
if ((0, import_fs12.existsSync)(filePath)) {
|
|
4000
|
+
const stat = (0, import_fs12.lstatSync)(filePath);
|
|
3588
4001
|
if (stat.isSymbolicLink()) {
|
|
3589
4002
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
3590
4003
|
}
|
|
3591
4004
|
}
|
|
3592
|
-
(0,
|
|
4005
|
+
(0, import_fs12.writeFileSync)(filePath, content);
|
|
3593
4006
|
}
|
|
3594
4007
|
function safeReadSync(filePath) {
|
|
3595
|
-
if ((0,
|
|
3596
|
-
const stat = (0,
|
|
4008
|
+
if ((0, import_fs12.existsSync)(filePath)) {
|
|
4009
|
+
const stat = (0, import_fs12.lstatSync)(filePath);
|
|
3597
4010
|
if (stat.isSymbolicLink()) {
|
|
3598
4011
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
3599
4012
|
}
|
|
3600
4013
|
}
|
|
3601
|
-
return (0,
|
|
4014
|
+
return (0, import_fs12.readFileSync)(filePath, "utf-8");
|
|
3602
4015
|
}
|
|
3603
4016
|
function writeState(cwd, state) {
|
|
3604
|
-
const dir = (0,
|
|
3605
|
-
(0,
|
|
4017
|
+
const dir = (0, import_path12.join)(cwd, ".stackwright");
|
|
4018
|
+
(0, import_fs12.mkdirSync)(dir, { recursive: true });
|
|
3606
4019
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3607
4020
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
3608
4021
|
}
|
|
3609
4022
|
function updateStateAtomic(cwd, mutator) {
|
|
3610
|
-
const dir = (0,
|
|
3611
|
-
(0,
|
|
4023
|
+
const dir = (0, import_path12.join)(cwd, ".stackwright");
|
|
4024
|
+
(0, import_fs12.mkdirSync)(dir, { recursive: true });
|
|
3612
4025
|
const path5 = statePath(cwd);
|
|
3613
|
-
if (!(0,
|
|
4026
|
+
if (!(0, import_fs12.existsSync)(path5)) {
|
|
3614
4027
|
safeWriteSync(path5, JSON.stringify(createDefaultState(), null, 2) + "\n");
|
|
3615
4028
|
}
|
|
3616
4029
|
const release = (0, import_proper_lockfile.lockSync)(path5, {
|
|
@@ -3644,8 +4057,8 @@ function handleGetPipelineState(_cwd) {
|
|
|
3644
4057
|
const cwd = _cwd ?? process.cwd();
|
|
3645
4058
|
try {
|
|
3646
4059
|
const state = readState(cwd);
|
|
3647
|
-
const keyPath = (0,
|
|
3648
|
-
if (!(0,
|
|
4060
|
+
const keyPath = (0, import_path12.join)(cwd, ".stackwright", "pipeline-keys.json");
|
|
4061
|
+
if (!(0, import_fs12.existsSync)(keyPath)) {
|
|
3649
4062
|
try {
|
|
3650
4063
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
3651
4064
|
state["signingKeyFingerprint"] = fingerprint;
|
|
@@ -3784,8 +4197,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3784
4197
|
if (AUTO_EXECUTE_PHASES.has(phase)) {
|
|
3785
4198
|
return { text: JSON.stringify({ ready: true, phase, autoExecute: true }), isError: false };
|
|
3786
4199
|
}
|
|
3787
|
-
const answerFile = (0,
|
|
3788
|
-
if (!(0,
|
|
4200
|
+
const answerFile = (0, import_path12.join)(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
4201
|
+
if (!(0, import_fs12.existsSync)(answerFile)) {
|
|
3789
4202
|
return {
|
|
3790
4203
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
3791
4204
|
isError: false
|
|
@@ -3809,7 +4222,7 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3809
4222
|
}
|
|
3810
4223
|
}
|
|
3811
4224
|
try {
|
|
3812
|
-
const answersDir = (0,
|
|
4225
|
+
const answersDir = (0, import_path12.join)(cwd, ".stackwright", "answers");
|
|
3813
4226
|
const answeredPhases = [];
|
|
3814
4227
|
const missingPhases = [];
|
|
3815
4228
|
for (const phase2 of PHASE_ORDER) {
|
|
@@ -3817,8 +4230,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3817
4230
|
answeredPhases.push(phase2);
|
|
3818
4231
|
continue;
|
|
3819
4232
|
}
|
|
3820
|
-
const answerFile = (0,
|
|
3821
|
-
if ((0,
|
|
4233
|
+
const answerFile = (0, import_path12.join)(answersDir, `${phase2}.json`);
|
|
4234
|
+
if ((0, import_fs12.existsSync)(answerFile)) {
|
|
3822
4235
|
try {
|
|
3823
4236
|
const raw = safeReadSync(answerFile);
|
|
3824
4237
|
const parsed = JSON.parse(raw);
|
|
@@ -3887,7 +4300,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3887
4300
|
function handleListArtifacts(_cwd) {
|
|
3888
4301
|
const cwd = _cwd ?? process.cwd();
|
|
3889
4302
|
try {
|
|
3890
|
-
const artifactsDir = (0,
|
|
4303
|
+
const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
|
|
3891
4304
|
let manifest = null;
|
|
3892
4305
|
try {
|
|
3893
4306
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3897,8 +4310,8 @@ function handleListArtifacts(_cwd) {
|
|
|
3897
4310
|
let completedCount = 0;
|
|
3898
4311
|
for (const phase of PHASE_ORDER) {
|
|
3899
4312
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3900
|
-
const fullPath = (0,
|
|
3901
|
-
const exists = (0,
|
|
4313
|
+
const fullPath = (0, import_path12.join)(artifactsDir, expectedFile);
|
|
4314
|
+
const exists = (0, import_fs12.existsSync)(fullPath);
|
|
3902
4315
|
if (exists) completedCount++;
|
|
3903
4316
|
let signed = false;
|
|
3904
4317
|
let signatureValid = null;
|
|
@@ -3951,9 +4364,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3951
4364
|
}
|
|
3952
4365
|
} catch {
|
|
3953
4366
|
}
|
|
3954
|
-
const questionsDir = (0,
|
|
3955
|
-
(0,
|
|
3956
|
-
const filePath = (0,
|
|
4367
|
+
const questionsDir = (0, import_path12.join)(cwd, ".stackwright", "questions");
|
|
4368
|
+
(0, import_fs12.mkdirSync)(questionsDir, { recursive: true });
|
|
4369
|
+
const filePath = (0, import_path12.join)(questionsDir, `${phase}.json`);
|
|
3957
4370
|
const payload = {
|
|
3958
4371
|
version: "1.0",
|
|
3959
4372
|
phase,
|
|
@@ -3993,14 +4406,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3993
4406
|
};
|
|
3994
4407
|
}
|
|
3995
4408
|
try {
|
|
3996
|
-
const answersPath = (0,
|
|
4409
|
+
const answersPath = (0, import_path12.join)(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
3997
4410
|
let answers = {};
|
|
3998
|
-
if ((0,
|
|
4411
|
+
if ((0, import_fs12.existsSync)(answersPath)) {
|
|
3999
4412
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
4000
4413
|
}
|
|
4001
4414
|
let buildContextText = "";
|
|
4002
|
-
const buildContextPath = (0,
|
|
4003
|
-
if ((0,
|
|
4415
|
+
const buildContextPath = (0, import_path12.join)(cwd, ".stackwright", "build-context.json");
|
|
4416
|
+
if ((0, import_fs12.existsSync)(buildContextPath)) {
|
|
4004
4417
|
try {
|
|
4005
4418
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
4006
4419
|
if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
|
|
@@ -4015,8 +4428,8 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
4015
4428
|
const missingDependencies = [];
|
|
4016
4429
|
for (const dep of deps) {
|
|
4017
4430
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
4018
|
-
const artifactPath = (0,
|
|
4019
|
-
if ((0,
|
|
4431
|
+
const artifactPath = (0, import_path12.join)(cwd, ".stackwright", "artifacts", artifactFile);
|
|
4432
|
+
if ((0, import_fs12.existsSync)(artifactPath)) {
|
|
4020
4433
|
const rawContent = safeReadSync(artifactPath);
|
|
4021
4434
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
4022
4435
|
const content = JSON.parse(rawContent);
|
|
@@ -4081,8 +4494,8 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
4081
4494
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
4082
4495
|
}
|
|
4083
4496
|
if (phase === "auth") {
|
|
4084
|
-
const initContextPath = (0,
|
|
4085
|
-
if ((0,
|
|
4497
|
+
const initContextPath = (0, import_path12.join)(cwd, ".stackwright", "init-context.json");
|
|
4498
|
+
if ((0, import_fs12.existsSync)(initContextPath)) {
|
|
4086
4499
|
try {
|
|
4087
4500
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
4088
4501
|
if (initContext.devOnly === true || initContext.nonInteractive === true) {
|
|
@@ -4223,7 +4636,12 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
4223
4636
|
"--surface": "210 40% 98%",
|
|
4224
4637
|
"--border": "214.3 31.8% 91.4%"
|
|
4225
4638
|
},
|
|
4226
|
-
dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" }
|
|
4639
|
+
dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" },
|
|
4640
|
+
// Optional — include when design language has status semantics (swp-cxwu).
|
|
4641
|
+
// Array of status-* token names emitted in tokens.colors.
|
|
4642
|
+
// Consumed by validate_artifact theme enforcement to reject unknown status tokens.
|
|
4643
|
+
// OMIT if project has no status-coded data.
|
|
4644
|
+
statusLadder: ["status-ok", "status-warning", "status-error", "status-info"]
|
|
4227
4645
|
},
|
|
4228
4646
|
null,
|
|
4229
4647
|
2
|
|
@@ -4650,7 +5068,7 @@ function _validateArtifactInner(input) {
|
|
|
4650
5068
|
return { success: true };
|
|
4651
5069
|
}
|
|
4652
5070
|
if (typeof workflowData === "object" && workflowData !== null && "workflow" in workflowData) {
|
|
4653
|
-
const result =
|
|
5071
|
+
const result = import_types2.WorkflowFileSchema.safeParse(workflowData);
|
|
4654
5072
|
if (!result.success) {
|
|
4655
5073
|
const issues = result.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
4656
5074
|
return { success: false, error: { message: issues } };
|
|
@@ -4661,7 +5079,7 @@ function _validateArtifactInner(input) {
|
|
|
4661
5079
|
auth: (artifact2) => {
|
|
4662
5080
|
const authConfig = artifact2["authConfig"];
|
|
4663
5081
|
if (!authConfig) return { success: true };
|
|
4664
|
-
const result =
|
|
5082
|
+
const result = import_types2.authConfigSchema.safeParse(authConfig);
|
|
4665
5083
|
if (!result.success) {
|
|
4666
5084
|
const issues = result.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
4667
5085
|
return { success: false, error: { message: issues } };
|
|
@@ -4693,21 +5111,21 @@ function _validateArtifactInner(input) {
|
|
|
4693
5111
|
// state-machine in this services-config artifact.
|
|
4694
5112
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
4695
5113
|
services: (artifact2) => {
|
|
4696
|
-
const artifactsDir = (0,
|
|
5114
|
+
const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
|
|
4697
5115
|
const workflowArtifactFiles = [];
|
|
4698
|
-
if ((0,
|
|
5116
|
+
if ((0, import_fs12.existsSync)(artifactsDir)) {
|
|
4699
5117
|
try {
|
|
4700
|
-
const entries = (0,
|
|
5118
|
+
const entries = (0, import_fs12.readdirSync)(artifactsDir);
|
|
4701
5119
|
for (const entry of entries) {
|
|
4702
5120
|
if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
|
|
4703
|
-
workflowArtifactFiles.push((0,
|
|
5121
|
+
workflowArtifactFiles.push((0, import_path12.join)(artifactsDir, entry));
|
|
4704
5122
|
}
|
|
4705
5123
|
}
|
|
4706
5124
|
} catch {
|
|
4707
5125
|
}
|
|
4708
5126
|
}
|
|
4709
|
-
const legacyPath = (0,
|
|
4710
|
-
if ((0,
|
|
5127
|
+
const legacyPath = (0, import_path12.join)(artifactsDir, "workflow-config.json");
|
|
5128
|
+
if ((0, import_fs12.existsSync)(legacyPath)) {
|
|
4711
5129
|
workflowArtifactFiles.push(legacyPath);
|
|
4712
5130
|
}
|
|
4713
5131
|
if (workflowArtifactFiles.length === 0) {
|
|
@@ -4716,7 +5134,7 @@ function _validateArtifactInner(input) {
|
|
|
4716
5134
|
const declaredHooks = [];
|
|
4717
5135
|
for (const filePath of workflowArtifactFiles) {
|
|
4718
5136
|
try {
|
|
4719
|
-
const parsed = JSON.parse((0,
|
|
5137
|
+
const parsed = JSON.parse((0, import_fs12.readFileSync)(filePath, "utf-8"));
|
|
4720
5138
|
if (Array.isArray(parsed.serviceHooks)) {
|
|
4721
5139
|
declaredHooks.push(...parsed.serviceHooks);
|
|
4722
5140
|
}
|
|
@@ -4806,6 +5224,42 @@ function _validateArtifactInner(input) {
|
|
|
4806
5224
|
return { text: JSON.stringify(result), isError: false };
|
|
4807
5225
|
}
|
|
4808
5226
|
}
|
|
5227
|
+
if (phase === "pages" || phase === "dashboard" || phase === "geo") {
|
|
5228
|
+
const themeResult = validateThemeTokenUsage(cwd);
|
|
5229
|
+
if (!themeResult.valid && !themeResult.skipped) {
|
|
5230
|
+
const result = {
|
|
5231
|
+
valid: false,
|
|
5232
|
+
phase,
|
|
5233
|
+
violation: "schema-mismatch",
|
|
5234
|
+
retryPrompt: themeResult.retryPrompt
|
|
5235
|
+
};
|
|
5236
|
+
return { text: JSON.stringify(result), isError: false };
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
if (phase === "pages" || phase === "dashboard" || phase === "geo") {
|
|
5240
|
+
const globalsResult = validateBuildContextGlobals(cwd);
|
|
5241
|
+
if (!globalsResult.valid && !globalsResult.skipped) {
|
|
5242
|
+
const result = {
|
|
5243
|
+
valid: false,
|
|
5244
|
+
phase,
|
|
5245
|
+
violation: "schema-mismatch",
|
|
5246
|
+
retryPrompt: globalsResult.retryPrompt
|
|
5247
|
+
};
|
|
5248
|
+
return { text: JSON.stringify(result), isError: false };
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
if (phase === "pages") {
|
|
5252
|
+
const workflowCrossCheck = validateWorkflowContainerPages(cwd);
|
|
5253
|
+
if (!workflowCrossCheck.valid) {
|
|
5254
|
+
const result = {
|
|
5255
|
+
valid: false,
|
|
5256
|
+
phase,
|
|
5257
|
+
violation: "schema-mismatch",
|
|
5258
|
+
retryPrompt: workflowCrossCheck.retryPrompt
|
|
5259
|
+
};
|
|
5260
|
+
return { text: JSON.stringify(result), isError: false };
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
4809
5263
|
if (phase === "api") {
|
|
4810
5264
|
const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
|
|
4811
5265
|
if (!apiAuthResult.valid) {
|
|
@@ -4834,7 +5288,7 @@ function _validateArtifactInner(input) {
|
|
|
4834
5288
|
const authCfg = artifact["authConfig"];
|
|
4835
5289
|
const routes = authCfg?.["protectedRoutes"];
|
|
4836
5290
|
if (Array.isArray(routes) && routes.length > 0) {
|
|
4837
|
-
const artifactsDir = (0,
|
|
5291
|
+
const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
|
|
4838
5292
|
const knownSlugs = extractManifestSlugs(artifactsDir);
|
|
4839
5293
|
const orphanPatterns = [];
|
|
4840
5294
|
for (const route of routes) {
|
|
@@ -4851,10 +5305,10 @@ function _validateArtifactInner(input) {
|
|
|
4851
5305
|
}
|
|
4852
5306
|
}
|
|
4853
5307
|
try {
|
|
4854
|
-
const artifactsDir = (0,
|
|
4855
|
-
(0,
|
|
5308
|
+
const artifactsDir = (0, import_path12.join)(cwd, ".stackwright", "artifacts");
|
|
5309
|
+
(0, import_fs12.mkdirSync)(artifactsDir, { recursive: true });
|
|
4856
5310
|
const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
|
|
4857
|
-
const artifactPath = (0,
|
|
5311
|
+
const artifactPath = (0, import_path12.join)(artifactsDir, artifactFile);
|
|
4858
5312
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
4859
5313
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
4860
5314
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -5084,10 +5538,10 @@ function registerPipelineTools(server2) {
|
|
|
5084
5538
|
isError: true
|
|
5085
5539
|
};
|
|
5086
5540
|
}
|
|
5087
|
-
const questionsDir = (0,
|
|
5088
|
-
(0,
|
|
5089
|
-
const outPath = (0,
|
|
5090
|
-
(0,
|
|
5541
|
+
const questionsDir = (0, import_path12.join)(process.cwd(), ".stackwright", "questions");
|
|
5542
|
+
(0, import_fs12.mkdirSync)(questionsDir, { recursive: true });
|
|
5543
|
+
const outPath = (0, import_path12.join)(questionsDir, `${phase}.json`);
|
|
5544
|
+
(0, import_fs12.writeFileSync)(
|
|
5091
5545
|
outPath,
|
|
5092
5546
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
5093
5547
|
);
|
|
@@ -5179,8 +5633,8 @@ function registerPipelineTools(server2) {
|
|
|
5179
5633
|
|
|
5180
5634
|
// src/tools/safe-write.ts
|
|
5181
5635
|
var import_zod15 = require("zod");
|
|
5182
|
-
var
|
|
5183
|
-
var
|
|
5636
|
+
var import_fs13 = require("fs");
|
|
5637
|
+
var import_path13 = require("path");
|
|
5184
5638
|
var import_telemetry2 = require("@stackwright-pro/telemetry");
|
|
5185
5639
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
5186
5640
|
"stackwright-pro-designer-otter": [
|
|
@@ -5257,7 +5711,18 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
5257
5711
|
"stackwright-pro-form-wizard-otter": [
|
|
5258
5712
|
{ prefix: "workflows/", suffix: ".yml", description: "Workflow definition" },
|
|
5259
5713
|
{ prefix: "workflows/", suffix: ".yaml", description: "Workflow definition" },
|
|
5260
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" }
|
|
5714
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" },
|
|
5715
|
+
// swp-dvde: workflow-otter owns the container page to prevent the 404 gap
|
|
5716
|
+
{
|
|
5717
|
+
prefix: "pages/workflows/",
|
|
5718
|
+
suffix: "/content.yml",
|
|
5719
|
+
description: "Workflow container page content (swp-dvde)"
|
|
5720
|
+
},
|
|
5721
|
+
{
|
|
5722
|
+
prefix: "pages/workflows/",
|
|
5723
|
+
suffix: "/content.yaml",
|
|
5724
|
+
description: "Workflow container page content (swp-dvde)"
|
|
5725
|
+
}
|
|
5261
5726
|
],
|
|
5262
5727
|
"stackwright-pro-scaffold-otter": [
|
|
5263
5728
|
{ prefix: "app/", suffix: ".tsx", description: "Next.js App Router shell files" },
|
|
@@ -5376,7 +5841,7 @@ function getMaxBytesForPath(filePath) {
|
|
|
5376
5841
|
}
|
|
5377
5842
|
var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
|
|
5378
5843
|
function extractPageSlug(filePath) {
|
|
5379
|
-
const match = (0,
|
|
5844
|
+
const match = (0, import_path13.normalize)(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
|
|
5380
5845
|
return match?.[1] ?? null;
|
|
5381
5846
|
}
|
|
5382
5847
|
function extractContentTypes(yamlContent) {
|
|
@@ -5392,27 +5857,27 @@ function extractContentTypes(yamlContent) {
|
|
|
5392
5857
|
return types.length > 0 ? types : ["unknown"];
|
|
5393
5858
|
}
|
|
5394
5859
|
function readPageRegistry(cwd) {
|
|
5395
|
-
const regPath = (0,
|
|
5396
|
-
if (!(0,
|
|
5860
|
+
const regPath = (0, import_path13.join)(cwd, PAGE_REGISTRY_FILE);
|
|
5861
|
+
if (!(0, import_fs13.existsSync)(regPath)) return {};
|
|
5397
5862
|
try {
|
|
5398
|
-
const stat = (0,
|
|
5863
|
+
const stat = (0, import_fs13.lstatSync)(regPath);
|
|
5399
5864
|
if (stat.isSymbolicLink()) return {};
|
|
5400
|
-
return JSON.parse((0,
|
|
5865
|
+
return JSON.parse((0, import_fs13.readFileSync)(regPath, "utf-8"));
|
|
5401
5866
|
} catch {
|
|
5402
5867
|
return {};
|
|
5403
5868
|
}
|
|
5404
5869
|
}
|
|
5405
5870
|
function writePageRegistry(cwd, registry) {
|
|
5406
|
-
const dir = (0,
|
|
5407
|
-
(0,
|
|
5408
|
-
const regPath = (0,
|
|
5409
|
-
if ((0,
|
|
5410
|
-
const stat = (0,
|
|
5871
|
+
const dir = (0, import_path13.join)(cwd, ".stackwright");
|
|
5872
|
+
(0, import_fs13.mkdirSync)(dir, { recursive: true });
|
|
5873
|
+
const regPath = (0, import_path13.join)(cwd, PAGE_REGISTRY_FILE);
|
|
5874
|
+
if ((0, import_fs13.existsSync)(regPath)) {
|
|
5875
|
+
const stat = (0, import_fs13.lstatSync)(regPath);
|
|
5411
5876
|
if (stat.isSymbolicLink()) {
|
|
5412
5877
|
throw new Error("Refusing to write page registry through symlink");
|
|
5413
5878
|
}
|
|
5414
5879
|
}
|
|
5415
|
-
(0,
|
|
5880
|
+
(0, import_fs13.writeFileSync)(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
|
|
5416
5881
|
}
|
|
5417
5882
|
function checkPageOwnership(cwd, slug, callerOtter) {
|
|
5418
5883
|
const registry = readPageRegistry(cwd);
|
|
@@ -5431,11 +5896,11 @@ function stripNextjsBracketSegments(path5) {
|
|
|
5431
5896
|
return path5.replace(NEXTJS_BRACKET_SEGMENT_RE, "");
|
|
5432
5897
|
}
|
|
5433
5898
|
function checkPathAllowed(callerOtter, filePath) {
|
|
5434
|
-
const normalized = (0,
|
|
5899
|
+
const normalized = (0, import_path13.normalize)(filePath);
|
|
5435
5900
|
if (stripNextjsBracketSegments(normalized).includes("..")) {
|
|
5436
5901
|
return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
|
|
5437
5902
|
}
|
|
5438
|
-
if ((0,
|
|
5903
|
+
if ((0, import_path13.isAbsolute)(normalized)) {
|
|
5439
5904
|
return {
|
|
5440
5905
|
allowed: false,
|
|
5441
5906
|
error: "Absolute paths are not allowed \u2014 use paths relative to project root"
|
|
@@ -5577,11 +6042,11 @@ function _safeWriteInner(input) {
|
|
|
5577
6042
|
};
|
|
5578
6043
|
return { text: JSON.stringify(result), isError: true };
|
|
5579
6044
|
}
|
|
5580
|
-
const normalized = (0,
|
|
5581
|
-
const fullPath = (0,
|
|
5582
|
-
if ((0,
|
|
6045
|
+
const normalized = (0, import_path13.normalize)(filePath);
|
|
6046
|
+
const fullPath = (0, import_path13.join)(cwd, normalized);
|
|
6047
|
+
if ((0, import_fs13.existsSync)(fullPath)) {
|
|
5583
6048
|
try {
|
|
5584
|
-
const stat = (0,
|
|
6049
|
+
const stat = (0, import_fs13.lstatSync)(fullPath);
|
|
5585
6050
|
if (stat.isSymbolicLink()) {
|
|
5586
6051
|
const result = {
|
|
5587
6052
|
success: false,
|
|
@@ -5622,9 +6087,9 @@ function _safeWriteInner(input) {
|
|
|
5622
6087
|
}
|
|
5623
6088
|
try {
|
|
5624
6089
|
if (createDirectories) {
|
|
5625
|
-
(0,
|
|
6090
|
+
(0, import_fs13.mkdirSync)((0, import_path13.dirname)(fullPath), { recursive: true });
|
|
5626
6091
|
}
|
|
5627
|
-
(0,
|
|
6092
|
+
(0, import_fs13.writeFileSync)(fullPath, content, { encoding: "utf-8" });
|
|
5628
6093
|
if (pageSlug) {
|
|
5629
6094
|
try {
|
|
5630
6095
|
const registry = readPageRegistry(cwd);
|
|
@@ -5709,8 +6174,8 @@ function registerSafeWriteTools(server2) {
|
|
|
5709
6174
|
|
|
5710
6175
|
// src/tools/auth.ts
|
|
5711
6176
|
var import_zod16 = require("zod");
|
|
5712
|
-
var
|
|
5713
|
-
var
|
|
6177
|
+
var import_fs14 = require("fs");
|
|
6178
|
+
var import_path14 = require("path");
|
|
5714
6179
|
function normalizeProviderSlug(slug) {
|
|
5715
6180
|
return slug.replace(/-/g, "_");
|
|
5716
6181
|
}
|
|
@@ -5740,9 +6205,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
5740
6205
|
}
|
|
5741
6206
|
function detectNextMajorVersion(cwd) {
|
|
5742
6207
|
try {
|
|
5743
|
-
const pkgPath = (0,
|
|
5744
|
-
if (!(0,
|
|
5745
|
-
const pkg = JSON.parse((0,
|
|
6208
|
+
const pkgPath = (0, import_path14.join)(cwd, "package.json");
|
|
6209
|
+
if (!(0, import_fs14.existsSync)(pkgPath)) return null;
|
|
6210
|
+
const pkg = JSON.parse((0, import_fs14.readFileSync)(pkgPath, "utf8"));
|
|
5746
6211
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
5747
6212
|
if (!nextVersion) return null;
|
|
5748
6213
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -6404,7 +6869,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6404
6869
|
auditRetentionDays,
|
|
6405
6870
|
normalizedRoutes
|
|
6406
6871
|
);
|
|
6407
|
-
(0,
|
|
6872
|
+
(0, import_fs14.writeFileSync)((0, import_path14.join)(cwd, conventionFile), authFileContent, "utf8");
|
|
6408
6873
|
filesWritten.push(conventionFile);
|
|
6409
6874
|
} catch (err) {
|
|
6410
6875
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6424,12 +6889,12 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6424
6889
|
if (!devOnly) {
|
|
6425
6890
|
try {
|
|
6426
6891
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
6427
|
-
const envPath = (0,
|
|
6428
|
-
if ((0,
|
|
6429
|
-
const existing = (0,
|
|
6430
|
-
(0,
|
|
6892
|
+
const envPath = (0, import_path14.join)(cwd, ".env.example");
|
|
6893
|
+
if ((0, import_fs14.existsSync)(envPath)) {
|
|
6894
|
+
const existing = (0, import_fs14.readFileSync)(envPath, "utf8");
|
|
6895
|
+
(0, import_fs14.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
6431
6896
|
} else {
|
|
6432
|
-
(0,
|
|
6897
|
+
(0, import_fs14.writeFileSync)(envPath, envBlock, "utf8");
|
|
6433
6898
|
}
|
|
6434
6899
|
filesWritten.push(".env.example");
|
|
6435
6900
|
} catch (err) {
|
|
@@ -6447,10 +6912,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6447
6912
|
}
|
|
6448
6913
|
if (devOnly) {
|
|
6449
6914
|
try {
|
|
6450
|
-
const mockAuthDir = (0,
|
|
6451
|
-
(0,
|
|
6915
|
+
const mockAuthDir = (0, import_path14.join)(cwd, "lib");
|
|
6916
|
+
(0, import_fs14.mkdirSync)(mockAuthDir, { recursive: true });
|
|
6452
6917
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
6453
|
-
(0,
|
|
6918
|
+
(0, import_fs14.writeFileSync)((0, import_path14.join)(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
6454
6919
|
filesWritten.push("lib/mock-auth.ts");
|
|
6455
6920
|
} catch (err) {
|
|
6456
6921
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6468,11 +6933,11 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6468
6933
|
};
|
|
6469
6934
|
}
|
|
6470
6935
|
try {
|
|
6471
|
-
const pkgPath = (0,
|
|
6472
|
-
if ((0,
|
|
6473
|
-
const existingPkg = (0,
|
|
6936
|
+
const pkgPath = (0, import_path14.join)(cwd, "package.json");
|
|
6937
|
+
if ((0, import_fs14.existsSync)(pkgPath)) {
|
|
6938
|
+
const existingPkg = (0, import_fs14.readFileSync)(pkgPath, "utf8");
|
|
6474
6939
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
6475
|
-
(0,
|
|
6940
|
+
(0, import_fs14.writeFileSync)(pkgPath, updatedPkg, "utf8");
|
|
6476
6941
|
filesWritten.push("package.json");
|
|
6477
6942
|
}
|
|
6478
6943
|
} catch (err) {
|
|
@@ -6513,7 +6978,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6513
6978
|
normalizedRoutes,
|
|
6514
6979
|
useProxy
|
|
6515
6980
|
);
|
|
6516
|
-
(0,
|
|
6981
|
+
(0, import_fs14.writeFileSync)((0, import_path14.join)(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
6517
6982
|
filesWritten.push("stackwright.auth.yml");
|
|
6518
6983
|
} catch (err) {
|
|
6519
6984
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6627,8 +7092,8 @@ function registerAuthTools(server2) {
|
|
|
6627
7092
|
|
|
6628
7093
|
// src/integrity.ts
|
|
6629
7094
|
var import_crypto4 = require("crypto");
|
|
6630
|
-
var
|
|
6631
|
-
var
|
|
7095
|
+
var import_fs15 = require("fs");
|
|
7096
|
+
var import_path15 = require("path");
|
|
6632
7097
|
var _checksums = /* @__PURE__ */ new Map([
|
|
6633
7098
|
[
|
|
6634
7099
|
"stackwright-pro-api-otter.json",
|
|
@@ -6644,7 +7109,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6644
7109
|
],
|
|
6645
7110
|
[
|
|
6646
7111
|
"stackwright-pro-dashboard-otter.json",
|
|
6647
|
-
"
|
|
7112
|
+
"ac0a049da72ee20bfb711f640bac571263bf2b05f80cd22a0c2eb5778c40989c"
|
|
6648
7113
|
],
|
|
6649
7114
|
[
|
|
6650
7115
|
"stackwright-pro-data-otter.json",
|
|
@@ -6664,15 +7129,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6664
7129
|
],
|
|
6665
7130
|
[
|
|
6666
7131
|
"stackwright-pro-form-wizard-otter.json",
|
|
6667
|
-
"
|
|
7132
|
+
"941bb11b767530460f96fdd08af638b3ede9f7a1ae6f19de04b7d81c95cc62ad"
|
|
6668
7133
|
],
|
|
6669
7134
|
[
|
|
6670
7135
|
"stackwright-pro-geo-otter.json",
|
|
6671
|
-
"
|
|
7136
|
+
"bee276605a3ae3203f5b6a18476316f615cf0ea6f4c73ecf57dcd627417de7f3"
|
|
6672
7137
|
],
|
|
6673
7138
|
[
|
|
6674
7139
|
"stackwright-pro-page-otter.json",
|
|
6675
|
-
"
|
|
7140
|
+
"8c04dd8a14c6e1aa9b0b6685a71a657547c20d99c25448e3523f73d670f87a4a"
|
|
6676
7141
|
],
|
|
6677
7142
|
[
|
|
6678
7143
|
"stackwright-pro-polish-otter.json",
|
|
@@ -6688,7 +7153,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6688
7153
|
],
|
|
6689
7154
|
[
|
|
6690
7155
|
"stackwright-pro-theme-otter.json",
|
|
6691
|
-
"
|
|
7156
|
+
"e3cb6ef9d9f7cbf662a26f637f3d244ec33cb19cb6a4a55dcd148cbfdaa4d71e"
|
|
6692
7157
|
],
|
|
6693
7158
|
[
|
|
6694
7159
|
"stackwright-services-otter.json",
|
|
@@ -6715,14 +7180,14 @@ function safeEqual(a, b) {
|
|
|
6715
7180
|
return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
6716
7181
|
}
|
|
6717
7182
|
function verifyOtterFile(filePath) {
|
|
6718
|
-
const filename = (0,
|
|
7183
|
+
const filename = (0, import_path15.basename)(filePath);
|
|
6719
7184
|
const expected = CANONICAL_CHECKSUMS.get(filename);
|
|
6720
7185
|
if (expected === void 0) {
|
|
6721
7186
|
return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
|
|
6722
7187
|
}
|
|
6723
7188
|
let stat;
|
|
6724
7189
|
try {
|
|
6725
|
-
stat = (0,
|
|
7190
|
+
stat = (0, import_fs15.lstatSync)(filePath);
|
|
6726
7191
|
} catch (err) {
|
|
6727
7192
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6728
7193
|
return { verified: false, filename, error: `Cannot stat file: ${msg}` };
|
|
@@ -6740,7 +7205,7 @@ function verifyOtterFile(filePath) {
|
|
|
6740
7205
|
}
|
|
6741
7206
|
let raw;
|
|
6742
7207
|
try {
|
|
6743
|
-
raw = (0,
|
|
7208
|
+
raw = (0, import_fs15.readFileSync)(filePath);
|
|
6744
7209
|
} catch (err) {
|
|
6745
7210
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6746
7211
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -6790,7 +7255,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6790
7255
|
const unknown = [];
|
|
6791
7256
|
let entries;
|
|
6792
7257
|
try {
|
|
6793
|
-
entries = (0,
|
|
7258
|
+
entries = (0, import_fs15.readdirSync)(otterDir);
|
|
6794
7259
|
} catch (err) {
|
|
6795
7260
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6796
7261
|
return {
|
|
@@ -6801,9 +7266,9 @@ function verifyAllOtters(otterDir) {
|
|
|
6801
7266
|
}
|
|
6802
7267
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
6803
7268
|
for (const filename of otterFiles) {
|
|
6804
|
-
const filePath = (0,
|
|
7269
|
+
const filePath = (0, import_path15.join)(otterDir, filename);
|
|
6805
7270
|
try {
|
|
6806
|
-
if ((0,
|
|
7271
|
+
if ((0, import_fs15.lstatSync)(filePath).isSymbolicLink()) {
|
|
6807
7272
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
6808
7273
|
continue;
|
|
6809
7274
|
}
|
|
@@ -6828,10 +7293,10 @@ function verifyAllOtters(otterDir) {
|
|
|
6828
7293
|
var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packages/otters/src/"];
|
|
6829
7294
|
function resolveOtterDir() {
|
|
6830
7295
|
const cwd = process.cwd();
|
|
6831
|
-
for (const
|
|
6832
|
-
const candidate = (0,
|
|
7296
|
+
for (const relative4 of DEFAULT_SEARCH_PATHS) {
|
|
7297
|
+
const candidate = (0, import_path15.join)(cwd, relative4);
|
|
6833
7298
|
try {
|
|
6834
|
-
(0,
|
|
7299
|
+
(0, import_fs15.lstatSync)(candidate);
|
|
6835
7300
|
return candidate;
|
|
6836
7301
|
} catch {
|
|
6837
7302
|
}
|
|
@@ -6910,13 +7375,13 @@ function registerIntegrityTools(server2) {
|
|
|
6910
7375
|
|
|
6911
7376
|
// src/tools/domain.ts
|
|
6912
7377
|
var import_zod17 = require("zod");
|
|
6913
|
-
var
|
|
6914
|
-
var
|
|
7378
|
+
var import_fs16 = require("fs");
|
|
7379
|
+
var import_path16 = require("path");
|
|
6915
7380
|
function handleListCollections(input) {
|
|
6916
7381
|
const cwd = input._cwd ?? process.cwd();
|
|
6917
7382
|
const sources = [
|
|
6918
7383
|
{
|
|
6919
|
-
path: (0,
|
|
7384
|
+
path: (0, import_path16.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
6920
7385
|
source: "data-config.json",
|
|
6921
7386
|
parse: (raw) => {
|
|
6922
7387
|
const parsed = JSON.parse(raw);
|
|
@@ -6927,15 +7392,15 @@ function handleListCollections(input) {
|
|
|
6927
7392
|
}
|
|
6928
7393
|
},
|
|
6929
7394
|
{
|
|
6930
|
-
path: (0,
|
|
7395
|
+
path: (0, import_path16.join)(cwd, "stackwright.yml"),
|
|
6931
7396
|
source: "stackwright.yml",
|
|
6932
7397
|
parse: extractCollectionsFromYaml
|
|
6933
7398
|
}
|
|
6934
7399
|
];
|
|
6935
7400
|
for (const { path: path5, source, parse } of sources) {
|
|
6936
|
-
if (!(0,
|
|
7401
|
+
if (!(0, import_fs16.existsSync)(path5)) continue;
|
|
6937
7402
|
try {
|
|
6938
|
-
const collections = parse((0,
|
|
7403
|
+
const collections = parse((0, import_fs16.readFileSync)(path5, "utf8"));
|
|
6939
7404
|
return {
|
|
6940
7405
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
6941
7406
|
isError: false
|
|
@@ -7086,8 +7551,8 @@ function handleValidateWorkflow(input) {
|
|
|
7086
7551
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
7087
7552
|
raw = input.workflow;
|
|
7088
7553
|
} else {
|
|
7089
|
-
const artifactPath = (0,
|
|
7090
|
-
if (!(0,
|
|
7554
|
+
const artifactPath = (0, import_path16.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
7555
|
+
if (!(0, import_fs16.existsSync)(artifactPath)) {
|
|
7091
7556
|
return fail([
|
|
7092
7557
|
{
|
|
7093
7558
|
code: "NO_WORKFLOW",
|
|
@@ -7096,7 +7561,7 @@ function handleValidateWorkflow(input) {
|
|
|
7096
7561
|
]);
|
|
7097
7562
|
}
|
|
7098
7563
|
try {
|
|
7099
|
-
raw = JSON.parse((0,
|
|
7564
|
+
raw = JSON.parse((0, import_fs16.readFileSync)(artifactPath, "utf8"));
|
|
7100
7565
|
} catch (err) {
|
|
7101
7566
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
7102
7567
|
}
|
|
@@ -7403,14 +7868,94 @@ function registerTypeSchemasTool(server2) {
|
|
|
7403
7868
|
);
|
|
7404
7869
|
}
|
|
7405
7870
|
|
|
7871
|
+
// src/tools/validate-yaml-fragment.ts
|
|
7872
|
+
var import_zod19 = require("zod");
|
|
7873
|
+
var import_js_yaml5 = require("js-yaml");
|
|
7874
|
+
var FIELD_SYNONYMS = Object.fromEntries(
|
|
7875
|
+
Array.from(SCHEMA_REGISTRY.entries()).map(([name, entry]) => [
|
|
7876
|
+
name,
|
|
7877
|
+
buildReverseSynonyms(entry.synonyms ?? {})
|
|
7878
|
+
])
|
|
7879
|
+
);
|
|
7880
|
+
function enrichErrors(issues, reverseSynonyms) {
|
|
7881
|
+
return issues.map((issue) => {
|
|
7882
|
+
const pathStr = issue.path.join(".");
|
|
7883
|
+
const lastSegment = issue.path[issue.path.length - 1];
|
|
7884
|
+
const fieldName = typeof lastSegment === "string" ? lastSegment : void 0;
|
|
7885
|
+
const didYouMean = fieldName ? reverseSynonyms[fieldName] : void 0;
|
|
7886
|
+
return {
|
|
7887
|
+
path: pathStr || "(root)",
|
|
7888
|
+
message: issue.message,
|
|
7889
|
+
...didYouMean ? { didYouMean } : {}
|
|
7890
|
+
};
|
|
7891
|
+
});
|
|
7892
|
+
}
|
|
7893
|
+
function handleValidateYamlFragment(input) {
|
|
7894
|
+
const { schemaName, yaml: yaml4 } = input;
|
|
7895
|
+
const entry = SCHEMA_REGISTRY.get(schemaName);
|
|
7896
|
+
if (!entry) {
|
|
7897
|
+
return {
|
|
7898
|
+
valid: false,
|
|
7899
|
+
parseError: `Unknown schemaName: "${schemaName}". Supported: ${REGISTRY_SCHEMA_NAMES.join(", ")}`
|
|
7900
|
+
};
|
|
7901
|
+
}
|
|
7902
|
+
let parsed;
|
|
7903
|
+
try {
|
|
7904
|
+
parsed = (0, import_js_yaml5.load)(yaml4);
|
|
7905
|
+
} catch (err) {
|
|
7906
|
+
return {
|
|
7907
|
+
valid: false,
|
|
7908
|
+
parseError: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
7909
|
+
};
|
|
7910
|
+
}
|
|
7911
|
+
const ctx = input.projectRoot ? { projectRoot: input.projectRoot } : void 0;
|
|
7912
|
+
const resolvedSchema = getSchemaForEntry(entry, ctx);
|
|
7913
|
+
const result = resolvedSchema.safeParse(parsed);
|
|
7914
|
+
if (result.success) {
|
|
7915
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
7916
|
+
const ladder = resolveStatusLadder(projectRoot);
|
|
7917
|
+
if (ladder.length > 0) {
|
|
7918
|
+
const ladderSet = new Set(ladder);
|
|
7919
|
+
const tokenViolations = detectViolationsInYaml(parsed, ladder, ladderSet, "fragment");
|
|
7920
|
+
if (tokenViolations.length > 0) {
|
|
7921
|
+
const tokenErrors = tokenViolations.map((v) => ({
|
|
7922
|
+
path: v.path,
|
|
7923
|
+
message: v.message
|
|
7924
|
+
}));
|
|
7925
|
+
return { valid: false, errors: tokenErrors };
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
return { valid: true };
|
|
7929
|
+
}
|
|
7930
|
+
const reverseSynonyms = buildReverseSynonyms(entry.synonyms ?? {});
|
|
7931
|
+
const errors = enrichErrors(result.error.issues, reverseSynonyms);
|
|
7932
|
+
return { valid: false, errors };
|
|
7933
|
+
}
|
|
7934
|
+
function registerValidateYamlFragmentTool(server2) {
|
|
7935
|
+
server2.tool(
|
|
7936
|
+
"stackwright_pro_validate_yaml_fragment",
|
|
7937
|
+
'Validates a YAML snippet against a canonical Zod schema before writing to disk. Returns actionable errors with "did you mean?" suggestions for common field-name drift (title vs label, id vs name, style vs theme.variant, etc.). Call this before stackwright_pro_safe_write to catch schema violations in-session.',
|
|
7938
|
+
{
|
|
7939
|
+
schemaName: import_zod19.z.string().describe(`Schema to validate against. Available: ${REGISTRY_SCHEMA_NAMES.join(", ")}`),
|
|
7940
|
+
yaml: import_zod19.z.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
|
|
7941
|
+
},
|
|
7942
|
+
async ({ schemaName, yaml: yaml4 }) => {
|
|
7943
|
+
const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
|
|
7944
|
+
return {
|
|
7945
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
7946
|
+
};
|
|
7947
|
+
}
|
|
7948
|
+
);
|
|
7949
|
+
}
|
|
7950
|
+
|
|
7406
7951
|
// src/tools/pages.ts
|
|
7407
7952
|
var fs2 = __toESM(require("fs"));
|
|
7408
7953
|
var os = __toESM(require("os"));
|
|
7409
7954
|
var path3 = __toESM(require("path"));
|
|
7410
|
-
var
|
|
7411
|
-
var
|
|
7955
|
+
var import_zod20 = require("zod");
|
|
7956
|
+
var import_js_yaml6 = require("js-yaml");
|
|
7412
7957
|
var import_cli = require("@stackwright/cli");
|
|
7413
|
-
var
|
|
7958
|
+
var import_types3 = require("@stackwright-pro/types");
|
|
7414
7959
|
var BCP47_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
|
|
7415
7960
|
var PRO_EXTENSION_STRIP_KEYS = /* @__PURE__ */ new Set(["theme", "auth", "required_roles"]);
|
|
7416
7961
|
var PRO_TYPE_STUB = {
|
|
@@ -7426,10 +7971,10 @@ function sanitizeNode(node) {
|
|
|
7426
7971
|
const obj = node;
|
|
7427
7972
|
const typeVal = obj["type"];
|
|
7428
7973
|
if (typeof typeVal === "string") {
|
|
7429
|
-
if ((0,
|
|
7974
|
+
if ((0, import_types3.isProContentType)(typeVal)) {
|
|
7430
7975
|
return { ...PRO_TYPE_STUB };
|
|
7431
7976
|
}
|
|
7432
|
-
if ((0,
|
|
7977
|
+
if ((0, import_types3.isOssTypeWithProExtension)(typeVal)) {
|
|
7433
7978
|
const sanitized = {};
|
|
7434
7979
|
for (const [k, v] of Object.entries(obj)) {
|
|
7435
7980
|
if (!PRO_EXTENSION_STRIP_KEYS.has(k)) {
|
|
@@ -7462,7 +8007,7 @@ function handleProWritePage(input) {
|
|
|
7462
8007
|
}
|
|
7463
8008
|
let parsed;
|
|
7464
8009
|
try {
|
|
7465
|
-
parsed = (0,
|
|
8010
|
+
parsed = (0, import_js_yaml6.load)(content);
|
|
7466
8011
|
} catch (err) {
|
|
7467
8012
|
return {
|
|
7468
8013
|
isError: true,
|
|
@@ -7476,7 +8021,7 @@ function handleProWritePage(input) {
|
|
|
7476
8021
|
}
|
|
7477
8022
|
const clone = JSON.parse(JSON.stringify(parsed));
|
|
7478
8023
|
const sanitized = sanitizeNode(clone);
|
|
7479
|
-
const sanitizedYaml = (0,
|
|
8024
|
+
const sanitizedYaml = (0, import_js_yaml6.dump)(sanitized);
|
|
7480
8025
|
const tmpDir = fs2.mkdtempSync(path3.join(os.tmpdir(), "sw-pro-validate-"));
|
|
7481
8026
|
try {
|
|
7482
8027
|
(0, import_cli.writePage)(path3.join(tmpDir, "pages"), "__validation__", sanitizedYaml);
|
|
@@ -7519,10 +8064,10 @@ function registerProPageTools(server2) {
|
|
|
7519
8064
|
"stackwright_pro_write_page",
|
|
7520
8065
|
"Write or update a page's YAML content with Pro content-type awareness. Validates against the Stackwright schema (including Pro types like stats_grid, data_table_pulse, map_pulse, alert_banner). Invalid YAML or schema violations on OSS-type fields are rejected with field-level errors. Use this instead of stackwright_write_page for any page containing Pro content types.",
|
|
7521
8066
|
{
|
|
7522
|
-
projectRoot:
|
|
7523
|
-
slug:
|
|
7524
|
-
content:
|
|
7525
|
-
locale:
|
|
8067
|
+
projectRoot: import_zod20.z.string().describe("Absolute path to the Stackwright project root"),
|
|
8068
|
+
slug: import_zod20.z.string().describe('Page slug (e.g. "dashboard/main" or "/dashboard/main")'),
|
|
8069
|
+
content: import_zod20.z.string().describe("Full YAML content for the page (content.yml contents)"),
|
|
8070
|
+
locale: import_zod20.z.string().optional().describe(
|
|
7526
8071
|
'BCP 47 locale tag (e.g. "fr", "de"). If provided, writes content.<locale>.yml instead of content.yml.'
|
|
7527
8072
|
)
|
|
7528
8073
|
},
|
|
@@ -7533,8 +8078,8 @@ function registerProPageTools(server2) {
|
|
|
7533
8078
|
}
|
|
7534
8079
|
|
|
7535
8080
|
// src/tools/scaffold-cleanup.ts
|
|
7536
|
-
var
|
|
7537
|
-
var
|
|
8081
|
+
var import_fs17 = require("fs");
|
|
8082
|
+
var import_path17 = require("path");
|
|
7538
8083
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
7539
8084
|
"content/posts/getting-started.yaml",
|
|
7540
8085
|
"content/posts/hello-world.yaml"
|
|
@@ -7559,12 +8104,12 @@ var FALLBACK_COLORS = {
|
|
|
7559
8104
|
mutedForeground: "#737373"
|
|
7560
8105
|
};
|
|
7561
8106
|
function readThemeColors(cwd) {
|
|
7562
|
-
const tokenPath = (0,
|
|
7563
|
-
if (!(0,
|
|
7564
|
-
const stat = (0,
|
|
8107
|
+
const tokenPath = (0, import_path17.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
8108
|
+
if (!(0, import_fs17.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
|
|
8109
|
+
const stat = (0, import_fs17.lstatSync)(tokenPath);
|
|
7565
8110
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
7566
8111
|
try {
|
|
7567
|
-
const raw = JSON.parse((0,
|
|
8112
|
+
const raw = JSON.parse((0, import_fs17.readFileSync)(tokenPath, "utf-8"));
|
|
7568
8113
|
const css = raw?.cssVariables ?? {};
|
|
7569
8114
|
const toHsl = (hslValue) => {
|
|
7570
8115
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -7624,15 +8169,15 @@ function generateNotFoundPage(colors) {
|
|
|
7624
8169
|
`;
|
|
7625
8170
|
}
|
|
7626
8171
|
function isSafePath(fullPath) {
|
|
7627
|
-
if (!(0,
|
|
7628
|
-
const stat = (0,
|
|
8172
|
+
if (!(0, import_fs17.existsSync)(fullPath)) return true;
|
|
8173
|
+
const stat = (0, import_fs17.lstatSync)(fullPath);
|
|
7629
8174
|
return !stat.isSymbolicLink();
|
|
7630
8175
|
}
|
|
7631
8176
|
function isDirEmpty(dirPath) {
|
|
7632
|
-
if (!(0,
|
|
7633
|
-
const stat = (0,
|
|
8177
|
+
if (!(0, import_fs17.existsSync)(dirPath)) return false;
|
|
8178
|
+
const stat = (0, import_fs17.lstatSync)(dirPath);
|
|
7634
8179
|
if (!stat.isDirectory()) return false;
|
|
7635
|
-
return (0,
|
|
8180
|
+
return (0, import_fs17.readdirSync)(dirPath).length === 0;
|
|
7636
8181
|
}
|
|
7637
8182
|
function handleCleanupScaffold(_cwd) {
|
|
7638
8183
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -7645,8 +8190,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7645
8190
|
cleanupWarnings: []
|
|
7646
8191
|
};
|
|
7647
8192
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
7648
|
-
const fullPath = (0,
|
|
7649
|
-
if (!(0,
|
|
8193
|
+
const fullPath = (0, import_path17.join)(cwd, relPath);
|
|
8194
|
+
if (!(0, import_fs17.existsSync)(fullPath)) {
|
|
7650
8195
|
result.skipped.push(relPath);
|
|
7651
8196
|
continue;
|
|
7652
8197
|
}
|
|
@@ -7655,7 +8200,7 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7655
8200
|
continue;
|
|
7656
8201
|
}
|
|
7657
8202
|
try {
|
|
7658
|
-
(0,
|
|
8203
|
+
(0, import_fs17.unlinkSync)(fullPath);
|
|
7659
8204
|
result.deleted.push(relPath);
|
|
7660
8205
|
} catch (err) {
|
|
7661
8206
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -7663,19 +8208,19 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7663
8208
|
}
|
|
7664
8209
|
{
|
|
7665
8210
|
const relPath = "pages/getting-started/content.yml";
|
|
7666
|
-
const fullPath = (0,
|
|
7667
|
-
if (!(0,
|
|
8211
|
+
const fullPath = (0, import_path17.join)(cwd, relPath);
|
|
8212
|
+
if (!(0, import_fs17.existsSync)(fullPath)) {
|
|
7668
8213
|
result.skipped.push(relPath);
|
|
7669
8214
|
} else if (!isSafePath(fullPath)) {
|
|
7670
8215
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7671
8216
|
} else {
|
|
7672
|
-
const content = (0,
|
|
8217
|
+
const content = (0, import_fs17.readFileSync)(fullPath, "utf-8");
|
|
7673
8218
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
7674
8219
|
(marker) => content.includes(marker)
|
|
7675
8220
|
);
|
|
7676
8221
|
if (isScaffoldDefault) {
|
|
7677
8222
|
try {
|
|
7678
|
-
(0,
|
|
8223
|
+
(0, import_fs17.unlinkSync)(fullPath);
|
|
7679
8224
|
result.deleted.push(relPath);
|
|
7680
8225
|
} catch (err) {
|
|
7681
8226
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -7689,21 +8234,21 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7689
8234
|
}
|
|
7690
8235
|
{
|
|
7691
8236
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
7692
|
-
const fullPath = (0,
|
|
7693
|
-
const postsDir = (0,
|
|
7694
|
-
if (!(0,
|
|
8237
|
+
const fullPath = (0, import_path17.join)(cwd, relPath);
|
|
8238
|
+
const postsDir = (0, import_path17.join)(cwd, "content/posts");
|
|
8239
|
+
if (!(0, import_fs17.existsSync)(fullPath)) {
|
|
7695
8240
|
result.skipped.push(relPath);
|
|
7696
8241
|
} else if (!isSafePath(fullPath)) {
|
|
7697
8242
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7698
|
-
} else if (!(0,
|
|
8243
|
+
} else if (!(0, import_fs17.existsSync)(postsDir)) {
|
|
7699
8244
|
result.skipped.push(relPath);
|
|
7700
8245
|
} else {
|
|
7701
|
-
const userPostFiles = (0,
|
|
8246
|
+
const userPostFiles = (0, import_fs17.readdirSync)(postsDir).filter(
|
|
7702
8247
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
7703
8248
|
);
|
|
7704
8249
|
if (userPostFiles.length === 0) {
|
|
7705
8250
|
try {
|
|
7706
|
-
(0,
|
|
8251
|
+
(0, import_fs17.unlinkSync)(fullPath);
|
|
7707
8252
|
result.deleted.push(relPath);
|
|
7708
8253
|
} catch (err) {
|
|
7709
8254
|
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
@@ -7716,15 +8261,15 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7716
8261
|
}
|
|
7717
8262
|
}
|
|
7718
8263
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
7719
|
-
const fullDir = (0,
|
|
7720
|
-
if (!(0,
|
|
8264
|
+
const fullDir = (0, import_path17.join)(cwd, relDir);
|
|
8265
|
+
if (!(0, import_fs17.existsSync)(fullDir)) continue;
|
|
7721
8266
|
if (!isSafePath(fullDir)) {
|
|
7722
8267
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
7723
8268
|
continue;
|
|
7724
8269
|
}
|
|
7725
8270
|
if (isDirEmpty(fullDir)) {
|
|
7726
8271
|
try {
|
|
7727
|
-
(0,
|
|
8272
|
+
(0, import_fs17.rmdirSync)(fullDir);
|
|
7728
8273
|
result.prunedDirs.push(relDir);
|
|
7729
8274
|
} catch (err) {
|
|
7730
8275
|
result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
|
|
@@ -7732,8 +8277,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7732
8277
|
}
|
|
7733
8278
|
}
|
|
7734
8279
|
{
|
|
7735
|
-
const fullPath = (0,
|
|
7736
|
-
if (!(0,
|
|
8280
|
+
const fullPath = (0, import_path17.join)(cwd, NOT_FOUND_PATH);
|
|
8281
|
+
if (!(0, import_fs17.existsSync)(fullPath)) {
|
|
7737
8282
|
result.skipped.push(NOT_FOUND_PATH);
|
|
7738
8283
|
} else if (!isSafePath(fullPath)) {
|
|
7739
8284
|
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
@@ -7741,9 +8286,9 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7741
8286
|
try {
|
|
7742
8287
|
const colors = readThemeColors(cwd);
|
|
7743
8288
|
const content = generateNotFoundPage(colors);
|
|
7744
|
-
const dir = (0,
|
|
7745
|
-
(0,
|
|
7746
|
-
(0,
|
|
8289
|
+
const dir = (0, import_path17.dirname)(fullPath);
|
|
8290
|
+
(0, import_fs17.mkdirSync)(dir, { recursive: true });
|
|
8291
|
+
(0, import_fs17.writeFileSync)(fullPath, content, "utf-8");
|
|
7747
8292
|
result.rewritten.push(NOT_FOUND_PATH);
|
|
7748
8293
|
} catch (err) {
|
|
7749
8294
|
result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
|
|
@@ -7771,8 +8316,159 @@ function registerScaffoldCleanupTools(server2) {
|
|
|
7771
8316
|
);
|
|
7772
8317
|
}
|
|
7773
8318
|
|
|
8319
|
+
// src/tools/write-workflow-container-page.ts
|
|
8320
|
+
var import_zod21 = require("zod");
|
|
8321
|
+
var import_js_yaml7 = require("js-yaml");
|
|
8322
|
+
var import_types4 = require("@stackwright-pro/types");
|
|
8323
|
+
var WriteWorkflowContainerPageInputSchema = import_zod21.z.object({
|
|
8324
|
+
/**
|
|
8325
|
+
* Test-only: override process.cwd() for deterministic temp-dir testing.
|
|
8326
|
+
* Never set in production — omit entirely in normal MCP tool calls.
|
|
8327
|
+
*/
|
|
8328
|
+
_cwd: import_zod21.z.string().optional(),
|
|
8329
|
+
/**
|
|
8330
|
+
* Kebab-case workflow ID — must match `workflow.id` in the referenced YAML.
|
|
8331
|
+
* Example: `patient-evacuation-authorization`
|
|
8332
|
+
*/
|
|
8333
|
+
workflowId: import_zod21.z.string().regex(/^[a-z0-9-]+$/, {
|
|
8334
|
+
message: "workflowId must be kebab-case (lowercase alphanumeric + hyphens)"
|
|
8335
|
+
}),
|
|
8336
|
+
/**
|
|
8337
|
+
* Relative path to the workflow YAML file from project root.
|
|
8338
|
+
* Example: `workflows/patient-evacuation-authorization.yml`
|
|
8339
|
+
*/
|
|
8340
|
+
workflowFile: import_zod21.z.string().min(1).regex(/^workflows\/[a-z0-9-]+\.(yml|yaml)$/, {
|
|
8341
|
+
message: "workflowFile must be: workflows/<id>.yml"
|
|
8342
|
+
}),
|
|
8343
|
+
/**
|
|
8344
|
+
* URL slug for the container page route.
|
|
8345
|
+
* Convention: `workflows/<workflowId>` → page at `/workflows/<workflowId>`.
|
|
8346
|
+
* Example: `workflows/patient-evacuation`
|
|
8347
|
+
*
|
|
8348
|
+
* The tool writes to `pages/<pageSlug>/content.yml`.
|
|
8349
|
+
*/
|
|
8350
|
+
pageSlug: import_zod21.z.string().min(1).regex(/^workflows\/[a-z0-9-]+$/, {
|
|
8351
|
+
message: 'pageSlug must start with "workflows/" and be kebab-case. Example: workflows/patient-evacuation'
|
|
8352
|
+
}),
|
|
8353
|
+
/**
|
|
8354
|
+
* Human-readable label for the workflow (used in page title + breadcrumbs).
|
|
8355
|
+
* Example: `Patient Evacuation Authorization`
|
|
8356
|
+
*/
|
|
8357
|
+
label: import_zod21.z.string().min(1).max(120),
|
|
8358
|
+
/**
|
|
8359
|
+
* Union of required roles across all steps in the workflow YAML.
|
|
8360
|
+
* Consumed by auth-reconcile to build protectedRoutes.
|
|
8361
|
+
* Must be non-empty — workflow pages are always auth-protected.
|
|
8362
|
+
*/
|
|
8363
|
+
requiredRoles: import_zod21.z.array(import_zod21.z.string().min(1)).min(1, {
|
|
8364
|
+
message: "requiredRoles must have at least one role \u2014 workflow pages are always auth-protected"
|
|
8365
|
+
}),
|
|
8366
|
+
/**
|
|
8367
|
+
* Persistence key for workflow state (localStorage slot).
|
|
8368
|
+
* Defaults to `workflow-<workflowId>` when omitted.
|
|
8369
|
+
*/
|
|
8370
|
+
persistenceKey: import_zod21.z.string().optional(),
|
|
8371
|
+
/**
|
|
8372
|
+
* Layout mode for the container page. Defaults to `app-shell` per Pro convention.
|
|
8373
|
+
* Only override when you have a specific reason — `app-shell` is correct for all
|
|
8374
|
+
* workflow container pages in data-dense Pro apps.
|
|
8375
|
+
*/
|
|
8376
|
+
layoutMode: import_zod21.z.enum(["app-shell", "page"]).default("app-shell")
|
|
8377
|
+
});
|
|
8378
|
+
function handleWriteWorkflowContainerPage(input) {
|
|
8379
|
+
const parsed = WriteWorkflowContainerPageInputSchema.safeParse(input);
|
|
8380
|
+
if (!parsed.success) {
|
|
8381
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
8382
|
+
return {
|
|
8383
|
+
text: JSON.stringify({ error: `Invalid input: ${issues}` }),
|
|
8384
|
+
isError: true
|
|
8385
|
+
};
|
|
8386
|
+
}
|
|
8387
|
+
const { workflowId, workflowFile, pageSlug, label, requiredRoles, persistenceKey, layoutMode } = parsed.data;
|
|
8388
|
+
const resolvedPersistenceKey = persistenceKey ?? `workflow-${workflowId}`;
|
|
8389
|
+
const wizardItem = {
|
|
8390
|
+
type: "workflow_wizard",
|
|
8391
|
+
label: workflowId,
|
|
8392
|
+
// slug-form label for the content item (page title comes from meta)
|
|
8393
|
+
workflowFile,
|
|
8394
|
+
workflowId,
|
|
8395
|
+
persistenceKey: resolvedPersistenceKey
|
|
8396
|
+
};
|
|
8397
|
+
const schemaCheck = import_types4.WorkflowWizardContentSchema.safeParse(wizardItem);
|
|
8398
|
+
if (!schemaCheck.success) {
|
|
8399
|
+
const issues = schemaCheck.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
8400
|
+
return {
|
|
8401
|
+
text: JSON.stringify({ error: `Internal schema validation failed: ${issues}` }),
|
|
8402
|
+
isError: true
|
|
8403
|
+
};
|
|
8404
|
+
}
|
|
8405
|
+
const pageContent = {
|
|
8406
|
+
layoutMode,
|
|
8407
|
+
meta: {
|
|
8408
|
+
title: `${label} | Workflow`,
|
|
8409
|
+
description: `${label} \u2014 multi-step workflow. Requires authorization.`,
|
|
8410
|
+
authRequired: true,
|
|
8411
|
+
requiredRoles
|
|
8412
|
+
},
|
|
8413
|
+
content: {
|
|
8414
|
+
content_items: [wizardItem]
|
|
8415
|
+
}
|
|
8416
|
+
};
|
|
8417
|
+
const yamlContent = (0, import_js_yaml7.dump)(pageContent, {
|
|
8418
|
+
indent: 2,
|
|
8419
|
+
lineWidth: 120,
|
|
8420
|
+
quotingType: "'",
|
|
8421
|
+
forceQuotes: false
|
|
8422
|
+
});
|
|
8423
|
+
const filePath = `pages/${pageSlug}/content.yml`;
|
|
8424
|
+
const writeResult = handleSafeWrite({
|
|
8425
|
+
callerOtter: "stackwright-pro-form-wizard-otter",
|
|
8426
|
+
filePath,
|
|
8427
|
+
content: yamlContent,
|
|
8428
|
+
createDirectories: true,
|
|
8429
|
+
_cwd: parsed.data._cwd
|
|
8430
|
+
});
|
|
8431
|
+
if (writeResult.isError) {
|
|
8432
|
+
return writeResult;
|
|
8433
|
+
}
|
|
8434
|
+
const result = {
|
|
8435
|
+
written: true,
|
|
8436
|
+
path: filePath,
|
|
8437
|
+
contentTypesEmitted: ["workflow_wizard"],
|
|
8438
|
+
content: yamlContent
|
|
8439
|
+
};
|
|
8440
|
+
return {
|
|
8441
|
+
text: JSON.stringify(result),
|
|
8442
|
+
isError: false
|
|
8443
|
+
};
|
|
8444
|
+
}
|
|
8445
|
+
function registerWriteWorkflowContainerPageTool(server2) {
|
|
8446
|
+
server2.tool(
|
|
8447
|
+
"stackwright_pro_write_workflow_container_page",
|
|
8448
|
+
[
|
|
8449
|
+
"Create the canonical container page for a workflow at pages/workflows/<workflowId>/content.yml.",
|
|
8450
|
+
"",
|
|
8451
|
+
"ALWAYS call this immediately after writing the workflow YAML via stackwright_pro_safe_write.",
|
|
8452
|
+
"Without it, the route /workflows/<workflowId> 404s at runtime.",
|
|
8453
|
+
"",
|
|
8454
|
+
"The tool deterministically emits:",
|
|
8455
|
+
" - layoutMode: app-shell",
|
|
8456
|
+
" - meta.authRequired: true, meta.requiredRoles: <your input>",
|
|
8457
|
+
" - One workflow_wizard content item binding workflowFile + workflowId",
|
|
8458
|
+
"",
|
|
8459
|
+
"requiredRoles: supply the UNION of required roles from all steps in your workflow YAML.",
|
|
8460
|
+
'pageSlug: convention is "workflows/<workflowId>" \u2014 must start with "workflows/".'
|
|
8461
|
+
].join("\n"),
|
|
8462
|
+
WriteWorkflowContainerPageInputSchema.shape,
|
|
8463
|
+
async (args) => {
|
|
8464
|
+
const result = handleWriteWorkflowContainerPage(args);
|
|
8465
|
+
return { content: [{ type: "text", text: result.text }] };
|
|
8466
|
+
}
|
|
8467
|
+
);
|
|
8468
|
+
}
|
|
8469
|
+
|
|
7774
8470
|
// src/tools/contrast.ts
|
|
7775
|
-
var
|
|
8471
|
+
var import_zod22 = require("zod");
|
|
7776
8472
|
function linearizeChannel(c255) {
|
|
7777
8473
|
const c = c255 / 255;
|
|
7778
8474
|
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
@@ -7944,8 +8640,8 @@ function registerContrastTools(server2) {
|
|
|
7944
8640
|
"stackwright_pro_check_contrast",
|
|
7945
8641
|
'Compute the exact WCAG 2.1 contrast ratio between a foreground and background color. Returns the ratio and AA/AAA pass/fail flags. Use this to verify any foreground/background color pair before writing it to the token set. Accepts #RGB, #RRGGBB, hsl(...), or shadcn HSL format ("H S% L%").',
|
|
7946
8642
|
{
|
|
7947
|
-
fg:
|
|
7948
|
-
bg:
|
|
8643
|
+
fg: import_zod22.z.string().describe("Foreground (text) color \u2014 hex or HSL"),
|
|
8644
|
+
bg: import_zod22.z.string().describe("Background color \u2014 hex or HSL")
|
|
7949
8645
|
},
|
|
7950
8646
|
async ({ fg, bg }) => {
|
|
7951
8647
|
let result;
|
|
@@ -7975,8 +8671,8 @@ function registerContrastTools(server2) {
|
|
|
7975
8671
|
"stackwright_pro_derive_accessible_palette",
|
|
7976
8672
|
'Given a seed color (treated as a background), derive the best accessible foreground (text) color that meets the target contrast ratio. Evaluates #ffffff and #000000 against the seed and picks whichever satisfies the target ratio with the highest contrast. Use this for every {name}-foreground token derivation instead of guessing white vs black. Accepts #RGB, #RRGGBB, hsl(...), or shadcn HSL format ("H S% L%").',
|
|
7977
8673
|
{
|
|
7978
|
-
seed:
|
|
7979
|
-
targetRatio: numCoerce(
|
|
8674
|
+
seed: import_zod22.z.string().describe("Background (seed) color \u2014 hex or HSL"),
|
|
8675
|
+
targetRatio: numCoerce(import_zod22.z.number().default(4.5)).describe(
|
|
7980
8676
|
"Minimum required contrast ratio (default 4.5 for WCAG AA)"
|
|
7981
8677
|
)
|
|
7982
8678
|
},
|
|
@@ -8011,24 +8707,24 @@ function registerContrastTools(server2) {
|
|
|
8011
8707
|
}
|
|
8012
8708
|
|
|
8013
8709
|
// src/tools/compile.ts
|
|
8014
|
-
var
|
|
8015
|
-
var
|
|
8016
|
-
var
|
|
8710
|
+
var import_zod23 = require("zod");
|
|
8711
|
+
var import_fs18 = __toESM(require("fs"));
|
|
8712
|
+
var import_path18 = __toESM(require("path"));
|
|
8017
8713
|
var import_server = require("@stackwright-pro/pulse/server");
|
|
8018
8714
|
var import_server2 = require("@stackwright-pro/auth-nextjs/server");
|
|
8019
8715
|
var import_server3 = require("@stackwright-pro/openapi/server");
|
|
8020
8716
|
function buildContext(projectRoot) {
|
|
8021
8717
|
return {
|
|
8022
8718
|
projectRoot,
|
|
8023
|
-
contentOutDir:
|
|
8024
|
-
imagesDir:
|
|
8719
|
+
contentOutDir: import_path18.default.join(projectRoot, "public", "stackwright-content"),
|
|
8720
|
+
imagesDir: import_path18.default.join(projectRoot, "public", "images"),
|
|
8025
8721
|
siteConfig: loadSiteConfig(projectRoot)
|
|
8026
8722
|
};
|
|
8027
8723
|
}
|
|
8028
8724
|
function loadSiteConfig(projectRoot) {
|
|
8029
8725
|
try {
|
|
8030
|
-
const sitePath =
|
|
8031
|
-
return JSON.parse(
|
|
8726
|
+
const sitePath = import_path18.default.join(projectRoot, "public", "stackwright-content", "_site.json");
|
|
8727
|
+
return JSON.parse(import_fs18.default.readFileSync(sitePath, "utf8"));
|
|
8032
8728
|
} catch {
|
|
8033
8729
|
return {};
|
|
8034
8730
|
}
|
|
@@ -8039,8 +8735,8 @@ function formatDuration(startMs) {
|
|
|
8039
8735
|
}
|
|
8040
8736
|
async function tryOssCompile(fn, ctx, extra) {
|
|
8041
8737
|
try {
|
|
8042
|
-
const bsPath =
|
|
8043
|
-
if (!
|
|
8738
|
+
const bsPath = import_path18.default.join(ctx.projectRoot, "node_modules", "@stackwright", "build-scripts");
|
|
8739
|
+
if (!import_fs18.default.existsSync(bsPath)) {
|
|
8044
8740
|
return {
|
|
8045
8741
|
ok: false,
|
|
8046
8742
|
error: `@stackwright/build-scripts not found at ${bsPath}. Is it installed in your project?`
|
|
@@ -8057,7 +8753,7 @@ async function tryOssCompile(fn, ctx, extra) {
|
|
|
8057
8753
|
}
|
|
8058
8754
|
}
|
|
8059
8755
|
function registerCompileTools(server2) {
|
|
8060
|
-
const projectRootArg =
|
|
8756
|
+
const projectRootArg = import_zod23.z.string().optional().describe("Absolute path to project root (defaults to cwd)");
|
|
8061
8757
|
server2.tool(
|
|
8062
8758
|
"stackwright_pro_compile_collections",
|
|
8063
8759
|
"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.",
|
|
@@ -8233,7 +8929,7 @@ ${results.join("\n")}`;
|
|
|
8233
8929
|
"stackwright_pro_compile_page",
|
|
8234
8930
|
"Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
|
|
8235
8931
|
{
|
|
8236
|
-
slug:
|
|
8932
|
+
slug: import_zod23.z.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
|
|
8237
8933
|
projectRoot: projectRootArg
|
|
8238
8934
|
},
|
|
8239
8935
|
async ({ slug, projectRoot }) => {
|
|
@@ -8253,15 +8949,15 @@ ${results.join("\n")}`;
|
|
|
8253
8949
|
}
|
|
8254
8950
|
|
|
8255
8951
|
// src/tools/strip-legacy-integrations.ts
|
|
8256
|
-
var
|
|
8257
|
-
var
|
|
8258
|
-
var
|
|
8952
|
+
var import_zod24 = require("zod");
|
|
8953
|
+
var import_fs19 = require("fs");
|
|
8954
|
+
var import_path19 = require("path");
|
|
8259
8955
|
function handleStripLegacyIntegrations(input) {
|
|
8260
8956
|
const { projectRoot } = input;
|
|
8261
|
-
const ymlPath = (0,
|
|
8262
|
-
const yamlPath = (0,
|
|
8263
|
-
if (!(0,
|
|
8264
|
-
if ((0,
|
|
8957
|
+
const ymlPath = (0, import_path19.join)(projectRoot, "stackwright.yml");
|
|
8958
|
+
const yamlPath = (0, import_path19.join)(projectRoot, "stackwright.yaml");
|
|
8959
|
+
if (!(0, import_fs19.existsSync)(ymlPath)) {
|
|
8960
|
+
if ((0, import_fs19.existsSync)(yamlPath)) {
|
|
8265
8961
|
return {
|
|
8266
8962
|
changed: false,
|
|
8267
8963
|
removedEntries: 0,
|
|
@@ -8274,7 +8970,7 @@ function handleStripLegacyIntegrations(input) {
|
|
|
8274
8970
|
message: "stackwright.yml not found \u2014 no-op."
|
|
8275
8971
|
};
|
|
8276
8972
|
}
|
|
8277
|
-
const raw = (0,
|
|
8973
|
+
const raw = (0, import_fs19.readFileSync)(ymlPath, "utf-8");
|
|
8278
8974
|
const isCRLF = raw.includes("\r\n");
|
|
8279
8975
|
const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
|
|
8280
8976
|
const lines = content.split("\n");
|
|
@@ -8304,7 +9000,7 @@ function handleStripLegacyIntegrations(input) {
|
|
|
8304
9000
|
outputContent += "\n";
|
|
8305
9001
|
}
|
|
8306
9002
|
const outputRaw = isCRLF ? outputContent.replace(/\n/g, "\r\n") : outputContent;
|
|
8307
|
-
(0,
|
|
9003
|
+
(0, import_fs19.writeFileSync)(ymlPath, outputRaw, "utf-8");
|
|
8308
9004
|
return {
|
|
8309
9005
|
changed: true,
|
|
8310
9006
|
removedEntries,
|
|
@@ -8316,7 +9012,7 @@ function registerStripLegacyIntegrationsTool(server2) {
|
|
|
8316
9012
|
"stackwright_pro_strip_legacy_integrations",
|
|
8317
9013
|
"Removes the deprecated top-level integrations[] block from stackwright.yml. The authoritative source for integration config is stackwright.integrations.yml \u2014 this tool cleans up legacy entries from older raft runs. Idempotent: no-op if no legacy block present. Preserves all other top-level keys, comments, and formatting.",
|
|
8318
9014
|
{
|
|
8319
|
-
projectRoot:
|
|
9015
|
+
projectRoot: import_zod24.z.string().describe("Absolute path to the project root directory")
|
|
8320
9016
|
},
|
|
8321
9017
|
async ({ projectRoot }) => {
|
|
8322
9018
|
const result = handleStripLegacyIntegrations({ projectRoot });
|
|
@@ -8328,16 +9024,16 @@ function registerStripLegacyIntegrationsTool(server2) {
|
|
|
8328
9024
|
}
|
|
8329
9025
|
|
|
8330
9026
|
// src/tools/emitters.ts
|
|
8331
|
-
var
|
|
8332
|
-
var
|
|
8333
|
-
var
|
|
8334
|
-
var
|
|
9027
|
+
var import_zod25 = require("zod");
|
|
9028
|
+
var import_fs20 = require("fs");
|
|
9029
|
+
var import_path20 = require("path");
|
|
9030
|
+
var import_js_yaml8 = __toESM(require("js-yaml"));
|
|
8335
9031
|
var import_emitters = require("@stackwright-pro/emitters");
|
|
8336
9032
|
function readIntegrations(cwd) {
|
|
8337
|
-
const filePath = (0,
|
|
8338
|
-
if (!(0,
|
|
9033
|
+
const filePath = (0, import_path20.join)(cwd, "stackwright.integrations.yml");
|
|
9034
|
+
if (!(0, import_fs20.existsSync)(filePath)) return [];
|
|
8339
9035
|
try {
|
|
8340
|
-
const raw =
|
|
9036
|
+
const raw = import_js_yaml8.default.load((0, import_fs20.readFileSync)(filePath, "utf-8"));
|
|
8341
9037
|
if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
|
|
8342
9038
|
return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
|
|
8343
9039
|
name: i.name,
|
|
@@ -8348,15 +9044,15 @@ function readIntegrations(cwd) {
|
|
|
8348
9044
|
}
|
|
8349
9045
|
}
|
|
8350
9046
|
function readServiceTokenRefs(cwd) {
|
|
8351
|
-
const servicesDir = (0,
|
|
8352
|
-
if (!(0,
|
|
9047
|
+
const servicesDir = (0, import_path20.join)(cwd, "services");
|
|
9048
|
+
if (!(0, import_fs20.existsSync)(servicesDir)) return [];
|
|
8353
9049
|
try {
|
|
8354
|
-
const files = (0,
|
|
9050
|
+
const files = (0, import_fs20.readdirSync)(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
8355
9051
|
const refs = [];
|
|
8356
9052
|
for (const file of files) {
|
|
8357
9053
|
try {
|
|
8358
|
-
const raw =
|
|
8359
|
-
(0,
|
|
9054
|
+
const raw = import_js_yaml8.default.load(
|
|
9055
|
+
(0, import_fs20.readFileSync)((0, import_path20.join)(servicesDir, file), "utf-8")
|
|
8360
9056
|
);
|
|
8361
9057
|
if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
|
|
8362
9058
|
refs.push({
|
|
@@ -8387,7 +9083,7 @@ Call this tool, then write both files with stackwright_pro_safe_write:
|
|
|
8387
9083
|
|
|
8388
9084
|
Do NOT compose the contents by hand. This emitter is the source of truth. If an env var is missing, update integrations.yml or the emitter \u2014 not the file. This is the first concrete instance of the swp-zf9y deterministic emitter pattern. ${ENV_LOCAL_DESC}`,
|
|
8389
9085
|
{
|
|
8390
|
-
cwd:
|
|
9086
|
+
cwd: import_zod25.z.string().describe(
|
|
8391
9087
|
"Absolute path to the project root containing stackwright.integrations.yml and optionally services/*.yml"
|
|
8392
9088
|
)
|
|
8393
9089
|
},
|
|
@@ -8434,8 +9130,8 @@ mapProvider options:
|
|
|
8434
9130
|
|
|
8435
9131
|
This is the second concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-57ku, swp-2m89, swp-zf9y, drawer 957. ${PROVIDERS_DESC}`,
|
|
8436
9132
|
{
|
|
8437
|
-
cwd:
|
|
8438
|
-
mapProvider:
|
|
9133
|
+
cwd: import_zod25.z.string().describe("Absolute path to the project root containing stackwright.integrations.yml"),
|
|
9134
|
+
mapProvider: import_zod25.z.enum(["maplibre", "cesium", "none"]).optional().describe(
|
|
8439
9135
|
"Map provider to register (default: cesium for Pro \u2014 3D globe + terrain). Use maplibre for OSS-style 2D dashboards (explicit opt-out). Use none if pages have no map content_items."
|
|
8440
9136
|
)
|
|
8441
9137
|
},
|
|
@@ -8476,7 +9172,7 @@ Workflow:
|
|
|
8476
9172
|
|
|
8477
9173
|
This is the third concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-phkh, swp-zf9y. ${PORTS_DESC}`,
|
|
8478
9174
|
{
|
|
8479
|
-
cwd:
|
|
9175
|
+
cwd: import_zod25.z.string().describe("Absolute path to the project root containing stackwright.integrations.yml")
|
|
8480
9176
|
},
|
|
8481
9177
|
async ({ cwd }) => {
|
|
8482
9178
|
const integrations = readIntegrations(cwd);
|
|
@@ -8505,13 +9201,13 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
|
|
|
8505
9201
|
}
|
|
8506
9202
|
|
|
8507
9203
|
// src/tools/list-specs.ts
|
|
8508
|
-
var
|
|
8509
|
-
var
|
|
8510
|
-
var
|
|
9204
|
+
var import_zod26 = require("zod");
|
|
9205
|
+
var import_fs21 = require("fs");
|
|
9206
|
+
var import_path21 = require("path");
|
|
8511
9207
|
var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
8512
9208
|
var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
8513
9209
|
function deriveIntegrationName(fileName) {
|
|
8514
|
-
let name = (0,
|
|
9210
|
+
let name = (0, import_path21.basename)(fileName, (0, import_path21.extname)(fileName));
|
|
8515
9211
|
for (const suffix of STRIP_SUFFIXES) {
|
|
8516
9212
|
if (name.endsWith(suffix)) {
|
|
8517
9213
|
name = name.slice(0, -suffix.length);
|
|
@@ -8529,29 +9225,29 @@ function detectFormat(snippet) {
|
|
|
8529
9225
|
}
|
|
8530
9226
|
function handleListSpecs(input) {
|
|
8531
9227
|
const { projectRoot } = input;
|
|
8532
|
-
const specsDir = (0,
|
|
9228
|
+
const specsDir = (0, import_path21.join)(projectRoot, "specs");
|
|
8533
9229
|
const empty = { projectRoot, specs: [], totalCount: 0 };
|
|
8534
|
-
if (!(0,
|
|
9230
|
+
if (!(0, import_fs21.existsSync)(specsDir)) return empty;
|
|
8535
9231
|
let entries;
|
|
8536
9232
|
try {
|
|
8537
|
-
entries = (0,
|
|
9233
|
+
entries = (0, import_fs21.readdirSync)(specsDir);
|
|
8538
9234
|
} catch {
|
|
8539
9235
|
return empty;
|
|
8540
9236
|
}
|
|
8541
9237
|
const specs = [];
|
|
8542
9238
|
for (const entry of entries) {
|
|
8543
|
-
const fullPath = (0,
|
|
9239
|
+
const fullPath = (0, import_path21.join)(specsDir, entry);
|
|
8544
9240
|
let stat;
|
|
8545
9241
|
try {
|
|
8546
|
-
stat = (0,
|
|
9242
|
+
stat = (0, import_fs21.statSync)(fullPath);
|
|
8547
9243
|
} catch {
|
|
8548
9244
|
continue;
|
|
8549
9245
|
}
|
|
8550
9246
|
if (!stat.isFile()) continue;
|
|
8551
|
-
if (!ALLOWED_EXTS.has((0,
|
|
9247
|
+
if (!ALLOWED_EXTS.has((0, import_path21.extname)(entry).toLowerCase())) continue;
|
|
8552
9248
|
let snippet = "";
|
|
8553
9249
|
try {
|
|
8554
|
-
snippet = (0,
|
|
9250
|
+
snippet = (0, import_fs21.readFileSync)(fullPath, "utf-8").slice(0, 2048);
|
|
8555
9251
|
} catch {
|
|
8556
9252
|
}
|
|
8557
9253
|
specs.push({
|
|
@@ -8570,7 +9266,7 @@ function registerListSpecsTool(server2) {
|
|
|
8570
9266
|
"stackwright_pro_list_specs",
|
|
8571
9267
|
"Enumerate OpenAPI/AsyncAPI specs in the project's specs/ directory. Returns one entry per spec with integration name (derived from filename), detected format, and size. Used by the foreman to fan out the api phase with one api-otter invocation per spec.",
|
|
8572
9268
|
{
|
|
8573
|
-
projectRoot:
|
|
9269
|
+
projectRoot: import_zod26.z.string().describe("Absolute path to the project root directory")
|
|
8574
9270
|
},
|
|
8575
9271
|
async ({ projectRoot }) => {
|
|
8576
9272
|
const result = handleListSpecs({ projectRoot });
|
|
@@ -8582,10 +9278,10 @@ function registerListSpecsTool(server2) {
|
|
|
8582
9278
|
}
|
|
8583
9279
|
|
|
8584
9280
|
// src/tools/describe-endpoint.ts
|
|
8585
|
-
var
|
|
8586
|
-
var
|
|
8587
|
-
var
|
|
8588
|
-
var
|
|
9281
|
+
var import_zod27 = require("zod");
|
|
9282
|
+
var import_fs22 = require("fs");
|
|
9283
|
+
var import_path22 = require("path");
|
|
9284
|
+
var import_js_yaml9 = __toESM(require("js-yaml"));
|
|
8589
9285
|
function resolveRef(root, ref) {
|
|
8590
9286
|
if (!ref.startsWith("#/")) {
|
|
8591
9287
|
throw new Error(`[describe_endpoint] External $refs not supported: "${ref}"`);
|
|
@@ -8616,12 +9312,12 @@ function deref(root, obj) {
|
|
|
8616
9312
|
return o;
|
|
8617
9313
|
}
|
|
8618
9314
|
function loadSpec(specPath) {
|
|
8619
|
-
const raw = (0,
|
|
8620
|
-
const ext = (0,
|
|
9315
|
+
const raw = (0, import_fs22.readFileSync)(specPath, "utf-8");
|
|
9316
|
+
const ext = (0, import_path22.extname)(specPath).toLowerCase();
|
|
8621
9317
|
if (ext === ".json") {
|
|
8622
9318
|
return JSON.parse(raw);
|
|
8623
9319
|
}
|
|
8624
|
-
const parsed =
|
|
9320
|
+
const parsed = import_js_yaml9.default.load(raw);
|
|
8625
9321
|
if (parsed == null || typeof parsed !== "object") {
|
|
8626
9322
|
throw new Error(`[describe_endpoint] Spec file "${specPath}" did not parse to an object`);
|
|
8627
9323
|
}
|
|
@@ -8729,9 +9425,9 @@ function registerDescribeEndpointTool(server2) {
|
|
|
8729
9425
|
"from required query params and resolved auth query params (swp-8r8i + swp-1l6l)."
|
|
8730
9426
|
].join("\n"),
|
|
8731
9427
|
{
|
|
8732
|
-
specPath:
|
|
8733
|
-
path:
|
|
8734
|
-
method:
|
|
9428
|
+
specPath: import_zod27.z.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
|
|
9429
|
+
path: import_zod27.z.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
|
|
9430
|
+
method: import_zod27.z.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
|
|
8735
9431
|
},
|
|
8736
9432
|
async ({ specPath, path: path5, method }) => {
|
|
8737
9433
|
try {
|
|
@@ -8751,15 +9447,15 @@ function registerDescribeEndpointTool(server2) {
|
|
|
8751
9447
|
}
|
|
8752
9448
|
|
|
8753
9449
|
// src/tools/consolidate-integrations.ts
|
|
8754
|
-
var
|
|
8755
|
-
var
|
|
9450
|
+
var import_zod28 = require("zod");
|
|
9451
|
+
var import_fs23 = require("fs");
|
|
8756
9452
|
var import_proper_lockfile2 = require("proper-lockfile");
|
|
8757
|
-
var
|
|
8758
|
-
var
|
|
9453
|
+
var import_path23 = require("path");
|
|
9454
|
+
var import_js_yaml10 = __toESM(require("js-yaml"));
|
|
8759
9455
|
var BASE_MOCK_PORT = 4011;
|
|
8760
9456
|
var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
8761
9457
|
function deriveNameFromFilename(filename) {
|
|
8762
|
-
const base = (0,
|
|
9458
|
+
const base = (0, import_path23.basename)(filename, ".json").replace(/^api-config-/, "");
|
|
8763
9459
|
for (const suffix of STRIP_SUFFIXES2) {
|
|
8764
9460
|
if (base.endsWith(suffix)) return base.slice(0, -suffix.length);
|
|
8765
9461
|
}
|
|
@@ -8767,13 +9463,13 @@ function deriveNameFromFilename(filename) {
|
|
|
8767
9463
|
}
|
|
8768
9464
|
function handleConsolidateIntegrations(input) {
|
|
8769
9465
|
const { projectRoot } = input;
|
|
8770
|
-
const artifactsDir = (0,
|
|
8771
|
-
if (!(0,
|
|
9466
|
+
const artifactsDir = (0, import_path23.join)(projectRoot, ".stackwright", "artifacts");
|
|
9467
|
+
if (!(0, import_fs23.existsSync)(artifactsDir)) {
|
|
8772
9468
|
return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
|
|
8773
9469
|
}
|
|
8774
9470
|
let entries;
|
|
8775
9471
|
try {
|
|
8776
|
-
entries = (0,
|
|
9472
|
+
entries = (0, import_fs23.readdirSync)(artifactsDir).filter(
|
|
8777
9473
|
(f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
|
|
8778
9474
|
);
|
|
8779
9475
|
} catch {
|
|
@@ -8788,7 +9484,7 @@ function handleConsolidateIntegrations(input) {
|
|
|
8788
9484
|
entries.forEach((filename, index) => {
|
|
8789
9485
|
let artifact = {};
|
|
8790
9486
|
try {
|
|
8791
|
-
const raw = (0,
|
|
9487
|
+
const raw = (0, import_fs23.readFileSync)((0, import_path23.join)(artifactsDir, filename), "utf-8");
|
|
8792
9488
|
artifact = JSON.parse(raw);
|
|
8793
9489
|
} catch {
|
|
8794
9490
|
}
|
|
@@ -8821,20 +9517,20 @@ function handleConsolidateIntegrations(input) {
|
|
|
8821
9517
|
});
|
|
8822
9518
|
const integrations = Array.from(integrationsByName.values());
|
|
8823
9519
|
const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
|
|
8824
|
-
const integrationsYmlPath = (0,
|
|
9520
|
+
const integrationsYmlPath = (0, import_path23.join)(projectRoot, "stackwright.integrations.yml");
|
|
8825
9521
|
let existing = {};
|
|
8826
|
-
if ((0,
|
|
9522
|
+
if ((0, import_fs23.existsSync)(integrationsYmlPath)) {
|
|
8827
9523
|
try {
|
|
8828
|
-
const raw = (0,
|
|
8829
|
-
existing =
|
|
9524
|
+
const raw = (0, import_fs23.readFileSync)(integrationsYmlPath, "utf-8");
|
|
9525
|
+
existing = import_js_yaml10.default.load(raw) ?? {};
|
|
8830
9526
|
} catch {
|
|
8831
9527
|
existing = {};
|
|
8832
9528
|
}
|
|
8833
9529
|
}
|
|
8834
9530
|
const merged = { ...existing, integrations };
|
|
8835
|
-
(0,
|
|
8836
|
-
if (!(0,
|
|
8837
|
-
(0,
|
|
9531
|
+
(0, import_fs23.mkdirSync)((0, import_path23.join)(projectRoot), { recursive: true });
|
|
9532
|
+
if (!(0, import_fs23.existsSync)(integrationsYmlPath)) {
|
|
9533
|
+
(0, import_fs23.writeFileSync)(integrationsYmlPath, "", "utf-8");
|
|
8838
9534
|
}
|
|
8839
9535
|
const release = (0, import_proper_lockfile2.lockSync)(integrationsYmlPath, {
|
|
8840
9536
|
realpath: false,
|
|
@@ -8843,8 +9539,8 @@ function handleConsolidateIntegrations(input) {
|
|
|
8843
9539
|
try {
|
|
8844
9540
|
const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
|
|
8845
9541
|
`;
|
|
8846
|
-
const body =
|
|
8847
|
-
(0,
|
|
9542
|
+
const body = import_js_yaml10.default.dump(merged, { lineWidth: 120, noRefs: true });
|
|
9543
|
+
(0, import_fs23.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
|
|
8848
9544
|
} finally {
|
|
8849
9545
|
release();
|
|
8850
9546
|
}
|
|
@@ -8872,7 +9568,7 @@ function registerConsolidateIntegrationsTool(server2) {
|
|
|
8872
9568
|
"no per-spec artifacts are found."
|
|
8873
9569
|
].join(" "),
|
|
8874
9570
|
{
|
|
8875
|
-
projectRoot:
|
|
9571
|
+
projectRoot: import_zod28.z.string().describe("Absolute path to the project root directory")
|
|
8876
9572
|
},
|
|
8877
9573
|
async ({ projectRoot }) => {
|
|
8878
9574
|
try {
|
|
@@ -8930,7 +9626,7 @@ var package_default = {
|
|
|
8930
9626
|
"test:coverage": "vitest run --coverage"
|
|
8931
9627
|
},
|
|
8932
9628
|
name: "@stackwright-pro/mcp",
|
|
8933
|
-
version: "0.2.0-alpha.
|
|
9629
|
+
version: "0.2.0-alpha.115",
|
|
8934
9630
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
8935
9631
|
license: "SEE LICENSE IN LICENSE",
|
|
8936
9632
|
main: "./dist/server.js",
|
|
@@ -8995,6 +9691,7 @@ registerValidateYamlFragmentTool(server);
|
|
|
8995
9691
|
registerProPageTools(server);
|
|
8996
9692
|
registerGetSchemaTool(server);
|
|
8997
9693
|
registerScaffoldCleanupTools(server);
|
|
9694
|
+
registerWriteWorkflowContainerPageTool(server);
|
|
8998
9695
|
registerContrastTools(server);
|
|
8999
9696
|
registerCompileTools(server);
|
|
9000
9697
|
registerStripLegacyIntegrationsTool(server);
|