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