@stackwright-pro/mcp 0.2.0-alpha.113 → 0.2.0-alpha.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrity.js +5 -5
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +5 -5
- package/dist/integrity.mjs.map +1 -1
- package/dist/pipeline-constants.js +229 -41
- package/dist/pipeline-constants.js.map +1 -1
- package/dist/pipeline-constants.mjs +237 -48
- package/dist/pipeline-constants.mjs.map +1 -1
- package/dist/server.js +1173 -476
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1119 -421
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -6
package/dist/server.mjs
CHANGED
|
@@ -2006,9 +2006,9 @@ function registerOrchestrationTools(server2) {
|
|
|
2006
2006
|
|
|
2007
2007
|
// src/tools/pipeline.ts
|
|
2008
2008
|
import { z as z14 } from "zod";
|
|
2009
|
-
import { readFileSync as
|
|
2009
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync4, existsSync as existsSync11, mkdirSync as mkdirSync4, lstatSync as lstatSync6, readdirSync as readdirSync8 } from "fs";
|
|
2010
2010
|
import { lockSync } from "proper-lockfile";
|
|
2011
|
-
import { join as
|
|
2011
|
+
import { join as join11 } from "path";
|
|
2012
2012
|
import { createHash as createHash3 } from "crypto";
|
|
2013
2013
|
|
|
2014
2014
|
// src/artifact-signing.ts
|
|
@@ -2551,6 +2551,598 @@ function formatPulseRetryPrompt(result) {
|
|
|
2551
2551
|
return lines.join("\n");
|
|
2552
2552
|
}
|
|
2553
2553
|
|
|
2554
|
+
// src/tools/validate-theme-tokens.ts
|
|
2555
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
2556
|
+
import { join as join6, relative as relative2 } from "path";
|
|
2557
|
+
import { load as yamlLoad2 } from "js-yaml";
|
|
2558
|
+
|
|
2559
|
+
// src/tools/schema-registry.ts
|
|
2560
|
+
import { z as z11 } from "zod";
|
|
2561
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
2562
|
+
import { join as join5 } from "path";
|
|
2563
|
+
import {
|
|
2564
|
+
WorkflowStepSchema,
|
|
2565
|
+
WorkflowDefinitionSchema,
|
|
2566
|
+
WorkflowFieldSchema,
|
|
2567
|
+
WorkflowActionSchema,
|
|
2568
|
+
WorkflowWizardContentSchema,
|
|
2569
|
+
authConfigSchema,
|
|
2570
|
+
themeTokensFileSchema
|
|
2571
|
+
} from "@stackwright-pro/types";
|
|
2572
|
+
import {
|
|
2573
|
+
MetricCardPulseSchema,
|
|
2574
|
+
DataTablePulseSchema,
|
|
2575
|
+
StatusBadgePulseSchema,
|
|
2576
|
+
MapPulseSchema
|
|
2577
|
+
} from "@stackwright-pro/pulse";
|
|
2578
|
+
function getSchemaForEntry(entry, ctx) {
|
|
2579
|
+
if (entry.schema) return entry.schema;
|
|
2580
|
+
if (entry.schemaFactory) {
|
|
2581
|
+
if (ctx) return entry.schemaFactory(ctx);
|
|
2582
|
+
console.warn(
|
|
2583
|
+
"[schema-registry] schemaFactory entry requires projectRoot context. Pass { projectRoot } to get the project-specific schema. Falling back to z.string() (no enforcement)."
|
|
2584
|
+
);
|
|
2585
|
+
return z11.string();
|
|
2586
|
+
}
|
|
2587
|
+
return z11.unknown();
|
|
2588
|
+
}
|
|
2589
|
+
var NavigationLinkSchema = z11.lazy(
|
|
2590
|
+
() => z11.object({
|
|
2591
|
+
label: z11.string().min(1),
|
|
2592
|
+
href: z11.string().min(1),
|
|
2593
|
+
children: z11.array(NavigationLinkSchema).optional()
|
|
2594
|
+
})
|
|
2595
|
+
);
|
|
2596
|
+
var NavigationSectionSchema = z11.object({
|
|
2597
|
+
section: z11.string().min(1),
|
|
2598
|
+
items: z11.array(NavigationLinkSchema)
|
|
2599
|
+
});
|
|
2600
|
+
var NavigationItemSchema = z11.union([
|
|
2601
|
+
NavigationLinkSchema,
|
|
2602
|
+
NavigationSectionSchema
|
|
2603
|
+
]);
|
|
2604
|
+
var THEME_TOKENS_PATHS = [
|
|
2605
|
+
join5(".stackwright", "artifacts", "theme-tokens.json"),
|
|
2606
|
+
join5("public", "stackwright-content", "_theme.json")
|
|
2607
|
+
];
|
|
2608
|
+
function resolveStatusLadder(projectRoot) {
|
|
2609
|
+
for (const relPath of THEME_TOKENS_PATHS) {
|
|
2610
|
+
const fullPath = join5(projectRoot, relPath);
|
|
2611
|
+
if (!existsSync6(fullPath)) continue;
|
|
2612
|
+
try {
|
|
2613
|
+
const raw = readFileSync5(fullPath, "utf-8");
|
|
2614
|
+
const parsed = JSON.parse(raw);
|
|
2615
|
+
if (Array.isArray(parsed["statusLadder"]) && parsed["statusLadder"].length > 0) {
|
|
2616
|
+
return parsed["statusLadder"].filter(
|
|
2617
|
+
(t) => typeof t === "string" && t.startsWith("status-")
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
const colors = parsed["tokens"]?.["colors"];
|
|
2621
|
+
if (colors && typeof colors === "object") {
|
|
2622
|
+
const statusKeys = Object.keys(colors).filter((k) => k.startsWith("status-"));
|
|
2623
|
+
if (statusKeys.length > 0) return statusKeys;
|
|
2624
|
+
}
|
|
2625
|
+
} catch {
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
return [];
|
|
2629
|
+
}
|
|
2630
|
+
function buildThemeStatusTokenSchema(ctx) {
|
|
2631
|
+
const ladder = resolveStatusLadder(ctx.projectRoot);
|
|
2632
|
+
if (ladder.length === 0) {
|
|
2633
|
+
return z11.string();
|
|
2634
|
+
}
|
|
2635
|
+
return z11.enum(ladder);
|
|
2636
|
+
}
|
|
2637
|
+
var REGISTRY = /* @__PURE__ */ new Map([
|
|
2638
|
+
// ── Workflow schemas ──────────────────────────────────────────────────────
|
|
2639
|
+
[
|
|
2640
|
+
"workflow_step",
|
|
2641
|
+
{
|
|
2642
|
+
schema: WorkflowStepSchema,
|
|
2643
|
+
synonyms: {
|
|
2644
|
+
label: ["title", "name"],
|
|
2645
|
+
message: ["description"],
|
|
2646
|
+
"theme.variant": ["style"],
|
|
2647
|
+
"on_submit.transition": ["transitions"]
|
|
2648
|
+
},
|
|
2649
|
+
phaseAffinity: ["workflow"]
|
|
2650
|
+
}
|
|
2651
|
+
],
|
|
2652
|
+
[
|
|
2653
|
+
"workflow_definition",
|
|
2654
|
+
{
|
|
2655
|
+
schema: WorkflowDefinitionSchema,
|
|
2656
|
+
synonyms: {
|
|
2657
|
+
label: ["title", "name"],
|
|
2658
|
+
id: ["type"]
|
|
2659
|
+
},
|
|
2660
|
+
phaseAffinity: ["workflow"]
|
|
2661
|
+
}
|
|
2662
|
+
],
|
|
2663
|
+
[
|
|
2664
|
+
"workflow_field",
|
|
2665
|
+
{
|
|
2666
|
+
schema: WorkflowFieldSchema,
|
|
2667
|
+
synonyms: {
|
|
2668
|
+
name: ["id"],
|
|
2669
|
+
label: ["title", "description"]
|
|
2670
|
+
},
|
|
2671
|
+
phaseAffinity: ["workflow"]
|
|
2672
|
+
}
|
|
2673
|
+
],
|
|
2674
|
+
[
|
|
2675
|
+
"workflow_action",
|
|
2676
|
+
{
|
|
2677
|
+
schema: WorkflowActionSchema,
|
|
2678
|
+
synonyms: {
|
|
2679
|
+
label: ["title", "name"],
|
|
2680
|
+
transition: ["then"],
|
|
2681
|
+
"theme.variant": ["style"],
|
|
2682
|
+
action: ["on_click"]
|
|
2683
|
+
},
|
|
2684
|
+
phaseAffinity: ["workflow"]
|
|
2685
|
+
}
|
|
2686
|
+
],
|
|
2687
|
+
// ── Navigation ────────────────────────────────────────────────────────────
|
|
2688
|
+
[
|
|
2689
|
+
"navigation_item",
|
|
2690
|
+
{
|
|
2691
|
+
schema: NavigationItemSchema,
|
|
2692
|
+
synonyms: {
|
|
2693
|
+
items: ["children"],
|
|
2694
|
+
section: ["label", "title"]
|
|
2695
|
+
},
|
|
2696
|
+
phaseAffinity: ["pages", "dashboard", "polish"]
|
|
2697
|
+
}
|
|
2698
|
+
],
|
|
2699
|
+
// ── Auth ──────────────────────────────────────────────────────────────────
|
|
2700
|
+
[
|
|
2701
|
+
"auth_config",
|
|
2702
|
+
{
|
|
2703
|
+
schema: authConfigSchema,
|
|
2704
|
+
synonyms: {
|
|
2705
|
+
type: ["method", "provider_type", "strategy"]
|
|
2706
|
+
},
|
|
2707
|
+
phaseAffinity: ["auth"]
|
|
2708
|
+
}
|
|
2709
|
+
],
|
|
2710
|
+
// ── Pulse content types ───────────────────────────────────────────────────
|
|
2711
|
+
[
|
|
2712
|
+
"metric_card_pulse",
|
|
2713
|
+
{
|
|
2714
|
+
schema: MetricCardPulseSchema,
|
|
2715
|
+
synonyms: {
|
|
2716
|
+
field: ["valueField", "valuePath", "dataField", "value"]
|
|
2717
|
+
},
|
|
2718
|
+
phaseAffinity: ["dashboard", "pages"]
|
|
2719
|
+
}
|
|
2720
|
+
],
|
|
2721
|
+
[
|
|
2722
|
+
"data_table_pulse",
|
|
2723
|
+
{
|
|
2724
|
+
schema: DataTablePulseSchema,
|
|
2725
|
+
synonyms: {
|
|
2726
|
+
header: ["label"],
|
|
2727
|
+
type: ["renderAs"],
|
|
2728
|
+
colorMap: ["severityMap"]
|
|
2729
|
+
},
|
|
2730
|
+
phaseAffinity: ["dashboard", "pages"]
|
|
2731
|
+
}
|
|
2732
|
+
],
|
|
2733
|
+
[
|
|
2734
|
+
"status_badge_pulse",
|
|
2735
|
+
{
|
|
2736
|
+
schema: StatusBadgePulseSchema,
|
|
2737
|
+
synonyms: {},
|
|
2738
|
+
phaseAffinity: ["dashboard", "pages"]
|
|
2739
|
+
}
|
|
2740
|
+
],
|
|
2741
|
+
[
|
|
2742
|
+
"map_pulse",
|
|
2743
|
+
{
|
|
2744
|
+
schema: MapPulseSchema,
|
|
2745
|
+
synonyms: {},
|
|
2746
|
+
phaseAffinity: ["geo", "dashboard", "pages"]
|
|
2747
|
+
}
|
|
2748
|
+
],
|
|
2749
|
+
// ── Workflow container page content type (swp-dvde) ───────────────────────
|
|
2750
|
+
[
|
|
2751
|
+
"workflow_wizard",
|
|
2752
|
+
{
|
|
2753
|
+
schema: WorkflowWizardContentSchema,
|
|
2754
|
+
synonyms: {
|
|
2755
|
+
workflowFile: ["workflowPath", "file", "path", "yamlFile"],
|
|
2756
|
+
workflowId: ["id", "workflowName", "name"],
|
|
2757
|
+
persistenceKey: ["stateKey", "storageKey"]
|
|
2758
|
+
},
|
|
2759
|
+
phaseAffinity: ["workflow"]
|
|
2760
|
+
}
|
|
2761
|
+
],
|
|
2762
|
+
// ── Theme schemas (swp-cxwu) ──────────────────────────────────────────────
|
|
2763
|
+
[
|
|
2764
|
+
"theme_tokens",
|
|
2765
|
+
{
|
|
2766
|
+
schema: themeTokensFileSchema,
|
|
2767
|
+
synonyms: {},
|
|
2768
|
+
phaseAffinity: ["theme"]
|
|
2769
|
+
}
|
|
2770
|
+
],
|
|
2771
|
+
[
|
|
2772
|
+
"theme_status_token",
|
|
2773
|
+
{
|
|
2774
|
+
// No static schema — this is a project-specific runtime enum.
|
|
2775
|
+
// Call getSchemaForEntry(entry, { projectRoot }) to get the project's ladder.
|
|
2776
|
+
schemaFactory: buildThemeStatusTokenSchema,
|
|
2777
|
+
synonyms: {},
|
|
2778
|
+
phaseAffinity: ["pages", "dashboard", "geo"]
|
|
2779
|
+
}
|
|
2780
|
+
]
|
|
2781
|
+
]);
|
|
2782
|
+
var SCHEMA_REGISTRY = REGISTRY;
|
|
2783
|
+
var REGISTRY_SCHEMA_NAMES = Array.from(REGISTRY.keys());
|
|
2784
|
+
function buildReverseSynonyms(synonyms) {
|
|
2785
|
+
const reverse = {};
|
|
2786
|
+
for (const [correct, wrongs] of Object.entries(synonyms)) {
|
|
2787
|
+
for (const wrong of wrongs) {
|
|
2788
|
+
reverse[wrong] = correct;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
return reverse;
|
|
2792
|
+
}
|
|
2793
|
+
function buildPhaseSchemaNames() {
|
|
2794
|
+
const phaseMap = {};
|
|
2795
|
+
for (const [name, entry] of REGISTRY.entries()) {
|
|
2796
|
+
for (const phase of entry.phaseAffinity ?? []) {
|
|
2797
|
+
if (!phaseMap[phase]) phaseMap[phase] = [];
|
|
2798
|
+
phaseMap[phase].push(name);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
return phaseMap;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// src/tools/validate-theme-tokens.ts
|
|
2805
|
+
var COLOR_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
2806
|
+
"color",
|
|
2807
|
+
"colorMap",
|
|
2808
|
+
"layerColor",
|
|
2809
|
+
"borderColor",
|
|
2810
|
+
"backgroundColor",
|
|
2811
|
+
"iconColor",
|
|
2812
|
+
"fill",
|
|
2813
|
+
"stroke",
|
|
2814
|
+
"markerColor",
|
|
2815
|
+
"fillColor",
|
|
2816
|
+
"outlineColor",
|
|
2817
|
+
"statusColor",
|
|
2818
|
+
"badgeColor",
|
|
2819
|
+
"tagColor"
|
|
2820
|
+
]);
|
|
2821
|
+
var LEGACY_OSS_TOKENS = /* @__PURE__ */ new Set(["success", "warning", "danger", "info", "muted"]);
|
|
2822
|
+
var LEGACY_MAPPING_HINT = "Suggested mapping: success->status-safe, warning->status-warning, danger->status-critical, info->status-watch, muted->status-at-risk.";
|
|
2823
|
+
var HEX_RE = /^#[0-9a-f]{3,8}$/i;
|
|
2824
|
+
var STATUS_TOKEN_RE = /^status-[a-z0-9-]+$/;
|
|
2825
|
+
function walkYaml(node, fieldName, pathParts, inColorContext, visitor) {
|
|
2826
|
+
if (typeof node === "string") {
|
|
2827
|
+
visitor(node, fieldName, pathParts.join(".") || "(root)", inColorContext);
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
if (Array.isArray(node)) {
|
|
2831
|
+
node.forEach((item, idx) => {
|
|
2832
|
+
walkYaml(item, fieldName, [...pathParts, `[${idx}]`], inColorContext, visitor);
|
|
2833
|
+
});
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
if (node !== null && typeof node === "object") {
|
|
2837
|
+
for (const [key, val] of Object.entries(node)) {
|
|
2838
|
+
const thisIsColorField = COLOR_FIELD_KEYS.has(key);
|
|
2839
|
+
walkYaml(val, key, [...pathParts, key], inColorContext || thisIsColorField, visitor);
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
function detectViolationsInYaml(yamlContent, ladder, ladderSet, relPath) {
|
|
2844
|
+
const violations = [];
|
|
2845
|
+
walkYaml(yamlContent, null, [], false, (value, fieldName, path5, inColorContext) => {
|
|
2846
|
+
const isColorField = inColorContext || fieldName !== null && COLOR_FIELD_KEYS.has(fieldName);
|
|
2847
|
+
if (STATUS_TOKEN_RE.test(value) && !ladderSet.has(value)) {
|
|
2848
|
+
violations.push({
|
|
2849
|
+
type: "undefined_token",
|
|
2850
|
+
message: `Undefined theme token: "${value}" not in declared theme ladder. Declared tokens: ${ladder.join(", ")}`,
|
|
2851
|
+
file: relPath,
|
|
2852
|
+
path: path5,
|
|
2853
|
+
value
|
|
2854
|
+
});
|
|
2855
|
+
return;
|
|
2856
|
+
}
|
|
2857
|
+
if (!isColorField) return;
|
|
2858
|
+
if (HEX_RE.test(value)) {
|
|
2859
|
+
violations.push({
|
|
2860
|
+
type: "raw_hex",
|
|
2861
|
+
message: `Raw hex color "${value}" in color field "${fieldName ?? "<color context>"}". Use declared theme tokens instead. Available: ${ladder.join(", ")}`,
|
|
2862
|
+
file: relPath,
|
|
2863
|
+
path: path5,
|
|
2864
|
+
value
|
|
2865
|
+
});
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
if (LEGACY_OSS_TOKENS.has(value)) {
|
|
2869
|
+
violations.push({
|
|
2870
|
+
type: "legacy_vocab",
|
|
2871
|
+
message: `Legacy status token "${value}" in color field "${fieldName ?? "<color context>"}". This project's ladder is: ${ladder.join(", ")}. ` + LEGACY_MAPPING_HINT,
|
|
2872
|
+
file: relPath,
|
|
2873
|
+
path: path5,
|
|
2874
|
+
value
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
return violations;
|
|
2879
|
+
}
|
|
2880
|
+
function findContentFiles(pagesRoot) {
|
|
2881
|
+
if (!existsSync7(pagesRoot)) return [];
|
|
2882
|
+
const files = [];
|
|
2883
|
+
try {
|
|
2884
|
+
const slugDirs = readdirSync3(pagesRoot, { withFileTypes: true });
|
|
2885
|
+
for (const entry of slugDirs) {
|
|
2886
|
+
if (!entry.isDirectory()) continue;
|
|
2887
|
+
const slugDir = join6(pagesRoot, entry.name);
|
|
2888
|
+
for (const candidate of ["content.yml", "content.yaml"]) {
|
|
2889
|
+
const p = join6(slugDir, candidate);
|
|
2890
|
+
if (existsSync7(p)) files.push(p);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
} catch {
|
|
2894
|
+
}
|
|
2895
|
+
return files;
|
|
2896
|
+
}
|
|
2897
|
+
function validateThemeTokenUsage(projectRoot) {
|
|
2898
|
+
const ladder = resolveStatusLadder(projectRoot);
|
|
2899
|
+
if (ladder.length === 0) {
|
|
2900
|
+
return {
|
|
2901
|
+
valid: true,
|
|
2902
|
+
violations: [],
|
|
2903
|
+
skipped: true
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
const ladderSet = new Set(ladder);
|
|
2907
|
+
const pagesRoot = join6(projectRoot, "pages");
|
|
2908
|
+
const contentFiles = findContentFiles(pagesRoot);
|
|
2909
|
+
const allViolations = [];
|
|
2910
|
+
for (const absPath of contentFiles) {
|
|
2911
|
+
const relPath = relative2(projectRoot, absPath);
|
|
2912
|
+
try {
|
|
2913
|
+
const raw = readFileSync6(absPath, "utf-8");
|
|
2914
|
+
const parsed = yamlLoad2(raw);
|
|
2915
|
+
const fileViolations = detectViolationsInYaml(parsed, ladder, ladderSet, relPath);
|
|
2916
|
+
allViolations.push(...fileViolations);
|
|
2917
|
+
} catch {
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
if (allViolations.length === 0) {
|
|
2921
|
+
return { valid: true, violations: [] };
|
|
2922
|
+
}
|
|
2923
|
+
const lines = [
|
|
2924
|
+
`Theme token enforcement found ${allViolations.length} violation(s):`,
|
|
2925
|
+
"",
|
|
2926
|
+
...allViolations.map((v, i) => {
|
|
2927
|
+
const typeLabel = v.type === "undefined_token" ? "UNDEFINED TOKEN" : v.type === "raw_hex" ? "RAW HEX" : "LEGACY VOCAB";
|
|
2928
|
+
return ` ${i + 1}. [${typeLabel}] ${v.message}
|
|
2929
|
+
Emitted in: ${v.file} at ${v.path}`;
|
|
2930
|
+
}),
|
|
2931
|
+
"",
|
|
2932
|
+
`Fix all violations to use declared theme tokens: ${ladder.join(", ")}`
|
|
2933
|
+
];
|
|
2934
|
+
return {
|
|
2935
|
+
valid: false,
|
|
2936
|
+
violations: allViolations,
|
|
2937
|
+
retryPrompt: lines.join("\n")
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/tools/validate-build-context-globals.ts
|
|
2942
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
2943
|
+
import { join as join7, relative as relative3 } from "path";
|
|
2944
|
+
import { load as yamlLoad3 } from "js-yaml";
|
|
2945
|
+
var DATA_WIDGET_TYPES = /* @__PURE__ */ new Set([
|
|
2946
|
+
"data_table_pulse",
|
|
2947
|
+
"stats_grid",
|
|
2948
|
+
"metric_card_pulse",
|
|
2949
|
+
"map_pulse"
|
|
2950
|
+
]);
|
|
2951
|
+
function readGlobalDefaults(projectRoot) {
|
|
2952
|
+
const bcPath = join7(projectRoot, ".stackwright", "build-context.json");
|
|
2953
|
+
if (!existsSync8(bcPath)) return null;
|
|
2954
|
+
try {
|
|
2955
|
+
const raw = readFileSync7(bcPath, "utf-8");
|
|
2956
|
+
const parsed = JSON.parse(raw);
|
|
2957
|
+
const gd = parsed.globalDefaults;
|
|
2958
|
+
if (!gd || typeof gd !== "object") return null;
|
|
2959
|
+
return gd;
|
|
2960
|
+
} catch {
|
|
2961
|
+
return null;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
function findDataWidgets(node, pathParts, out) {
|
|
2965
|
+
if (node === null || node === void 0 || typeof node !== "object") return;
|
|
2966
|
+
if (Array.isArray(node)) {
|
|
2967
|
+
node.forEach((item, idx) => {
|
|
2968
|
+
findDataWidgets(item, [...pathParts, `[${idx}]`], out);
|
|
2969
|
+
});
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
const obj = node;
|
|
2973
|
+
const contentType = typeof obj["type"] === "string" ? obj["type"] : null;
|
|
2974
|
+
if (contentType && DATA_WIDGET_TYPES.has(contentType)) {
|
|
2975
|
+
out.push({
|
|
2976
|
+
type: contentType,
|
|
2977
|
+
label: typeof obj["label"] === "string" ? obj["label"] : void 0,
|
|
2978
|
+
data: obj,
|
|
2979
|
+
path: pathParts.join(".") || "(root)"
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
2983
|
+
findDataWidgets(val, [...pathParts, key], out);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
function findContentFiles2(pagesRoot) {
|
|
2987
|
+
if (!existsSync8(pagesRoot)) return [];
|
|
2988
|
+
const files = [];
|
|
2989
|
+
try {
|
|
2990
|
+
const slugDirs = readdirSync4(pagesRoot, { withFileTypes: true });
|
|
2991
|
+
for (const entry of slugDirs) {
|
|
2992
|
+
if (!entry.isDirectory()) continue;
|
|
2993
|
+
for (const candidate of ["content.yml", "content.yaml"]) {
|
|
2994
|
+
const p = join7(pagesRoot, entry.name, candidate);
|
|
2995
|
+
if (existsSync8(p)) files.push(p);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
} catch {
|
|
2999
|
+
}
|
|
3000
|
+
return files;
|
|
3001
|
+
}
|
|
3002
|
+
function validateBuildContextGlobals(projectRoot) {
|
|
3003
|
+
const globalDefaults = readGlobalDefaults(projectRoot);
|
|
3004
|
+
if (!globalDefaults) {
|
|
3005
|
+
return { valid: true, violations: [], skipped: true };
|
|
3006
|
+
}
|
|
3007
|
+
const requiredFields = Object.entries(globalDefaults).filter(([, v]) => v === true).map(([k]) => k);
|
|
3008
|
+
if (requiredFields.length === 0) {
|
|
3009
|
+
return { valid: true, violations: [], skipped: true };
|
|
3010
|
+
}
|
|
3011
|
+
const pagesRoot = join7(projectRoot, "pages");
|
|
3012
|
+
const contentFiles = findContentFiles2(pagesRoot);
|
|
3013
|
+
const allViolations = [];
|
|
3014
|
+
for (const absPath of contentFiles) {
|
|
3015
|
+
const relPath = relative3(projectRoot, absPath);
|
|
3016
|
+
try {
|
|
3017
|
+
const raw = readFileSync7(absPath, "utf-8");
|
|
3018
|
+
const parsed = yamlLoad3(raw);
|
|
3019
|
+
const widgets = [];
|
|
3020
|
+
findDataWidgets(parsed, [], widgets);
|
|
3021
|
+
for (const widget of widgets) {
|
|
3022
|
+
for (const field of requiredFields) {
|
|
3023
|
+
if (widget.data[field] !== true) {
|
|
3024
|
+
allViolations.push({
|
|
3025
|
+
field,
|
|
3026
|
+
widgetType: widget.type,
|
|
3027
|
+
widgetLabel: widget.label,
|
|
3028
|
+
file: relPath,
|
|
3029
|
+
path: widget.path
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
} catch {
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
if (allViolations.length === 0) {
|
|
3038
|
+
return { valid: true, violations: [] };
|
|
3039
|
+
}
|
|
3040
|
+
const lines = [
|
|
3041
|
+
`BUILD_CONTEXT global defaults: ${allViolations.length} violation(s) found.`,
|
|
3042
|
+
"",
|
|
3043
|
+
...allViolations.map((v, i) => {
|
|
3044
|
+
const widgetId = v.widgetLabel ? `"${v.widgetLabel}" (${v.widgetType})` : `${v.widgetType}`;
|
|
3045
|
+
return ` ${i + 1}. BUILD_CONTEXT.${v.field} is globally true but widget ${widgetId} omits it.
|
|
3046
|
+
Set ${v.field}: true on this widget.
|
|
3047
|
+
Emitted in: ${v.file} at ${v.path}`;
|
|
3048
|
+
}),
|
|
3049
|
+
"",
|
|
3050
|
+
`Required global defaults: ${requiredFields.map((f) => `${f}: true`).join(", ")}`
|
|
3051
|
+
];
|
|
3052
|
+
return {
|
|
3053
|
+
valid: false,
|
|
3054
|
+
violations: allViolations,
|
|
3055
|
+
retryPrompt: lines.join("\n")
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/tools/validate-workflow-container-pages.ts
|
|
3060
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5 } from "fs";
|
|
3061
|
+
import { join as join8 } from "path";
|
|
3062
|
+
import { load as yamlLoad4 } from "js-yaml";
|
|
3063
|
+
function discoverWorkflowIds(projectRoot) {
|
|
3064
|
+
const artifactsDir = join8(projectRoot, ".stackwright", "artifacts");
|
|
3065
|
+
if (!existsSync9(artifactsDir)) return [];
|
|
3066
|
+
const entries = [];
|
|
3067
|
+
let files;
|
|
3068
|
+
try {
|
|
3069
|
+
files = readdirSync5(artifactsDir);
|
|
3070
|
+
} catch {
|
|
3071
|
+
return [];
|
|
3072
|
+
}
|
|
3073
|
+
for (const filename of files) {
|
|
3074
|
+
if (filename !== "workflow-config.json" && !filename.startsWith("workflow-config-")) continue;
|
|
3075
|
+
if (!filename.endsWith(".json")) continue;
|
|
3076
|
+
const fullPath = join8(artifactsDir, filename);
|
|
3077
|
+
try {
|
|
3078
|
+
const raw = JSON.parse(readFileSync8(fullPath, "utf-8"));
|
|
3079
|
+
const workflowData = raw["workflow"] ?? raw["workflowConfig"];
|
|
3080
|
+
const workflowId = typeof workflowData?.["id"] === "string" ? workflowData["id"] : null;
|
|
3081
|
+
if (workflowId && /^[a-z0-9-]+$/.test(workflowId)) {
|
|
3082
|
+
entries.push({ workflowId, sourceFile: filename });
|
|
3083
|
+
}
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return entries;
|
|
3088
|
+
}
|
|
3089
|
+
function hasWorkflowWizardType(filePath) {
|
|
3090
|
+
try {
|
|
3091
|
+
const raw = readFileSync8(filePath, "utf-8");
|
|
3092
|
+
const parsed = yamlLoad4(raw);
|
|
3093
|
+
if (!parsed) return false;
|
|
3094
|
+
const content = parsed["content"];
|
|
3095
|
+
if (!content) return false;
|
|
3096
|
+
const items = content["content_items"];
|
|
3097
|
+
if (!Array.isArray(items)) return false;
|
|
3098
|
+
return items.some((item) => item["type"] === "workflow_wizard");
|
|
3099
|
+
} catch {
|
|
3100
|
+
return false;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
function validateWorkflowContainerPages(projectRoot) {
|
|
3104
|
+
const workflows = discoverWorkflowIds(projectRoot);
|
|
3105
|
+
if (workflows.length === 0) {
|
|
3106
|
+
return { valid: true, violations: [], skipped: true };
|
|
3107
|
+
}
|
|
3108
|
+
const violations = [];
|
|
3109
|
+
for (const { workflowId, sourceFile: _sourceFile } of workflows) {
|
|
3110
|
+
const expectedPath = `pages/workflows/${workflowId}/content.yml`;
|
|
3111
|
+
const fullPath = join8(projectRoot, expectedPath);
|
|
3112
|
+
if (!existsSync9(fullPath)) {
|
|
3113
|
+
violations.push({ workflowId, expectedPath, reason: "missing-file" });
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
if (!hasWorkflowWizardType(fullPath)) {
|
|
3117
|
+
violations.push({ workflowId, expectedPath, reason: "missing-workflow-wizard-type" });
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
if (violations.length === 0) {
|
|
3121
|
+
return { valid: true, violations: [] };
|
|
3122
|
+
}
|
|
3123
|
+
const lines = [
|
|
3124
|
+
`Workflow container page cross-check failed: ${violations.length} workflow(s) missing container pages.`,
|
|
3125
|
+
"",
|
|
3126
|
+
...violations.map((v, i) => {
|
|
3127
|
+
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.`;
|
|
3128
|
+
return ` ${i + 1}. workflowId "${v.workflowId}"
|
|
3129
|
+
Expected: ${v.expectedPath}
|
|
3130
|
+
Issue: ${reason}`;
|
|
3131
|
+
}),
|
|
3132
|
+
"",
|
|
3133
|
+
"Fix: workflow-otter must call stackwright_pro_write_workflow_container_page immediately",
|
|
3134
|
+
"after writing each workflow YAML. Required args:",
|
|
3135
|
+
' { workflowId, workflowFile, pageSlug: "workflows/<workflowId>", label, requiredRoles }',
|
|
3136
|
+
"",
|
|
3137
|
+
"Without the container page, the route 404s and auth-reconcile cannot protect it."
|
|
3138
|
+
];
|
|
3139
|
+
return {
|
|
3140
|
+
valid: false,
|
|
3141
|
+
violations,
|
|
3142
|
+
retryPrompt: lines.join("\n")
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
|
|
2554
3146
|
// src/tools/validate-api-integration.ts
|
|
2555
3147
|
var CANONICAL_API_AUTH_TYPES = ["apiKey", "bearer", "oauth2", "oidc", "none"];
|
|
2556
3148
|
var CANONICAL_SET = new Set(CANONICAL_API_AUTH_TYPES);
|
|
@@ -2743,9 +3335,9 @@ function validateAuthConfig(artifact, artifactPath) {
|
|
|
2743
3335
|
}
|
|
2744
3336
|
|
|
2745
3337
|
// src/tools/auth-manifest-aggregator.ts
|
|
2746
|
-
import { existsSync as
|
|
2747
|
-
import { join as
|
|
2748
|
-
import { z as
|
|
3338
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, readdirSync as readdirSync6 } from "fs";
|
|
3339
|
+
import { join as join9 } from "path";
|
|
3340
|
+
import { z as z12 } from "zod";
|
|
2749
3341
|
function computeLowestRole(requiredRoles, rbacRoles) {
|
|
2750
3342
|
let lowestIndex = -1;
|
|
2751
3343
|
let lowestRole = requiredRoles[0];
|
|
@@ -2765,17 +3357,17 @@ function extractPatternBaseSlug(pattern) {
|
|
|
2765
3357
|
}
|
|
2766
3358
|
function extractManifestSlugs(artifactsDir) {
|
|
2767
3359
|
const slugs = /* @__PURE__ */ new Set();
|
|
2768
|
-
if (!
|
|
3360
|
+
if (!existsSync10(artifactsDir)) return slugs;
|
|
2769
3361
|
let entries;
|
|
2770
3362
|
try {
|
|
2771
|
-
entries =
|
|
3363
|
+
entries = readdirSync6(artifactsDir);
|
|
2772
3364
|
} catch {
|
|
2773
3365
|
return slugs;
|
|
2774
3366
|
}
|
|
2775
3367
|
for (const entry of entries) {
|
|
2776
3368
|
if (!entry.endsWith("-manifest.json")) continue;
|
|
2777
3369
|
try {
|
|
2778
|
-
const content = JSON.parse(
|
|
3370
|
+
const content = JSON.parse(readFileSync9(join9(artifactsDir, entry), "utf-8"));
|
|
2779
3371
|
const pages = content.pages;
|
|
2780
3372
|
if (!Array.isArray(pages)) continue;
|
|
2781
3373
|
for (const page of pages) {
|
|
@@ -2818,18 +3410,18 @@ function deriveProtectedRoutes(pages, rbacRoles) {
|
|
|
2818
3410
|
}
|
|
2819
3411
|
function readAllManifestPages(artifactsDir) {
|
|
2820
3412
|
const allPages = [];
|
|
2821
|
-
if (!
|
|
3413
|
+
if (!existsSync10(artifactsDir)) return allPages;
|
|
2822
3414
|
let entries;
|
|
2823
3415
|
try {
|
|
2824
|
-
entries =
|
|
3416
|
+
entries = readdirSync6(artifactsDir);
|
|
2825
3417
|
} catch {
|
|
2826
3418
|
return allPages;
|
|
2827
3419
|
}
|
|
2828
3420
|
for (const entry of entries) {
|
|
2829
3421
|
if (entry.endsWith("-manifest.json")) {
|
|
2830
|
-
const filePath =
|
|
3422
|
+
const filePath = join9(artifactsDir, entry);
|
|
2831
3423
|
try {
|
|
2832
|
-
const raw = JSON.parse(
|
|
3424
|
+
const raw = JSON.parse(readFileSync9(filePath, "utf-8"));
|
|
2833
3425
|
if (!Array.isArray(raw.pages)) continue;
|
|
2834
3426
|
for (const page of raw.pages) {
|
|
2835
3427
|
if (typeof page.slug !== "string" || !page.slug) continue;
|
|
@@ -2845,9 +3437,9 @@ function readAllManifestPages(artifactsDir) {
|
|
|
2845
3437
|
continue;
|
|
2846
3438
|
}
|
|
2847
3439
|
if (entry === "workflow-config.json" || entry.startsWith("workflow-config-")) {
|
|
2848
|
-
const filePath =
|
|
3440
|
+
const filePath = join9(artifactsDir, entry);
|
|
2849
3441
|
try {
|
|
2850
|
-
const raw = JSON.parse(
|
|
3442
|
+
const raw = JSON.parse(readFileSync9(filePath, "utf-8"));
|
|
2851
3443
|
const route = raw.workflow?.route;
|
|
2852
3444
|
if (typeof route !== "string" || !route) continue;
|
|
2853
3445
|
const slug = route.startsWith("/") ? route.slice(1) : route;
|
|
@@ -2869,16 +3461,16 @@ function registerReadAuthManifestsTool(server2) {
|
|
|
2869
3461
|
"stackwright_pro_read_auth_manifests",
|
|
2870
3462
|
"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).",
|
|
2871
3463
|
{
|
|
2872
|
-
rbacRoles:
|
|
3464
|
+
rbacRoles: z12.array(z12.string()).describe(
|
|
2873
3465
|
'RBAC role hierarchy in descending privilege order, e.g. ["ESF8_COORDINATOR", "HOSPITAL_EM", "OBSERVER"]. Used to compute the lowestRole for each authRequired page.'
|
|
2874
3466
|
),
|
|
2875
|
-
artifactsDir:
|
|
3467
|
+
artifactsDir: z12.string().optional().describe(
|
|
2876
3468
|
"Absolute or relative path to the pipeline artifacts directory. Defaults to .stackwright/artifacts relative to cwd."
|
|
2877
3469
|
)
|
|
2878
3470
|
},
|
|
2879
3471
|
(params) => {
|
|
2880
3472
|
const cwd = process.cwd();
|
|
2881
|
-
const artifactsDir = params.artifactsDir ? params.artifactsDir :
|
|
3473
|
+
const artifactsDir = params.artifactsDir ? params.artifactsDir : join9(cwd, ".stackwright", "artifacts");
|
|
2882
3474
|
const allPages = readAllManifestPages(artifactsDir);
|
|
2883
3475
|
const { protectedRoutes, uncoveredSlugs } = deriveProtectedRoutes(allPages, params.rbacRoles);
|
|
2884
3476
|
const manifestsRead = [...new Set(allPages.map((p) => p.source))].sort();
|
|
@@ -2901,195 +3493,11 @@ function registerReadAuthManifestsTool(server2) {
|
|
|
2901
3493
|
}
|
|
2902
3494
|
|
|
2903
3495
|
// src/tools/pipeline.ts
|
|
2904
|
-
import { WorkflowFileSchema, authConfigSchema as
|
|
3496
|
+
import { WorkflowFileSchema, authConfigSchema as authConfigSchema2 } from "@stackwright-pro/types";
|
|
2905
3497
|
|
|
2906
3498
|
// src/tools/get-schema.ts
|
|
2907
3499
|
import { z as z13 } from "zod";
|
|
2908
|
-
|
|
2909
|
-
WorkflowStepSchema as WorkflowStepSchema2,
|
|
2910
|
-
WorkflowDefinitionSchema as WorkflowDefinitionSchema2,
|
|
2911
|
-
WorkflowFieldSchema as WorkflowFieldSchema2,
|
|
2912
|
-
WorkflowActionSchema as WorkflowActionSchema2,
|
|
2913
|
-
authConfigSchema as authConfigSchema2
|
|
2914
|
-
} from "@stackwright-pro/types";
|
|
2915
|
-
|
|
2916
|
-
// src/tools/validate-yaml-fragment.ts
|
|
2917
|
-
import { z as z12 } from "zod";
|
|
2918
|
-
import { load as yamlLoad2 } from "js-yaml";
|
|
2919
|
-
import {
|
|
2920
|
-
WorkflowStepSchema,
|
|
2921
|
-
WorkflowDefinitionSchema,
|
|
2922
|
-
WorkflowFieldSchema,
|
|
2923
|
-
WorkflowActionSchema,
|
|
2924
|
-
authConfigSchema
|
|
2925
|
-
} from "@stackwright-pro/types";
|
|
2926
|
-
var SUPPORTED_SCHEMA_NAMES = [
|
|
2927
|
-
"workflow_step",
|
|
2928
|
-
"workflow_definition",
|
|
2929
|
-
"workflow_field",
|
|
2930
|
-
"workflow_action",
|
|
2931
|
-
"navigation_item",
|
|
2932
|
-
"auth_config"
|
|
2933
|
-
];
|
|
2934
|
-
var NavigationLinkSchema = z12.lazy(
|
|
2935
|
-
() => z12.object({
|
|
2936
|
-
label: z12.string().min(1),
|
|
2937
|
-
href: z12.string().min(1),
|
|
2938
|
-
children: z12.array(NavigationLinkSchema).optional()
|
|
2939
|
-
})
|
|
2940
|
-
);
|
|
2941
|
-
var NavigationSectionSchema = z12.object({
|
|
2942
|
-
section: z12.string().min(1),
|
|
2943
|
-
items: z12.array(NavigationLinkSchema)
|
|
2944
|
-
});
|
|
2945
|
-
var NavigationItemSchema = z12.union([
|
|
2946
|
-
NavigationLinkSchema,
|
|
2947
|
-
NavigationSectionSchema
|
|
2948
|
-
]);
|
|
2949
|
-
var FIELD_SYNONYMS = {
|
|
2950
|
-
workflow_step: {
|
|
2951
|
-
title: "label",
|
|
2952
|
-
name: "label (at step level, use label: not name:)",
|
|
2953
|
-
description: "message (use message: for step-level descriptive text)",
|
|
2954
|
-
style: 'theme.variant \u2014 use theme: { variant: "primary" } not style: "primary"',
|
|
2955
|
-
transitions: 'on_submit.transition \u2014 use on_submit: { transition: "next_step" } not transitions: [{then:...}]'
|
|
2956
|
-
},
|
|
2957
|
-
workflow_definition: {
|
|
2958
|
-
title: "label",
|
|
2959
|
-
name: "label (workflow-level label, not name:)",
|
|
2960
|
-
description: "description is valid here \u2014 but message: is not (that is a step field)",
|
|
2961
|
-
type: "id (workflows are identified by id:, not type:)"
|
|
2962
|
-
},
|
|
2963
|
-
workflow_field: {
|
|
2964
|
-
id: "name (field-level identifier is name:, NOT id:)",
|
|
2965
|
-
title: "label (field display text is label:, NOT title:)",
|
|
2966
|
-
description: "placeholder or label (no description field on WorkflowField)"
|
|
2967
|
-
},
|
|
2968
|
-
workflow_action: {
|
|
2969
|
-
style: 'theme.variant \u2014 use theme: { variant: "primary" } not style: "primary"',
|
|
2970
|
-
then: "transition (action-level next-step is transition:, NOT then:)",
|
|
2971
|
-
on_click: 'action: "service:..." (extract the service reference from on_click.action)',
|
|
2972
|
-
title: "label (action display text is label:, NOT title:)",
|
|
2973
|
-
name: "label (action display text is label:, NOT name:)"
|
|
2974
|
-
},
|
|
2975
|
-
navigation_item: {
|
|
2976
|
-
children: "items (NavigationSection uses items: not children: \u2014 also use section: instead of label: for section headers)",
|
|
2977
|
-
label: 'section (if this is a nav group/section, use section: "Title" and items: [...], not label:)',
|
|
2978
|
-
title: "section (NavigationSection header field is section:, NOT title:)"
|
|
2979
|
-
},
|
|
2980
|
-
auth_config: {
|
|
2981
|
-
method: 'type \u2014 auth config discriminates on type: "oidc" | "pki", NOT method:',
|
|
2982
|
-
provider_type: 'type \u2014 use type: "oidc" or type: "pki"',
|
|
2983
|
-
strategy: "type \u2014 top-level discriminator is type:, not strategy:"
|
|
2984
|
-
}
|
|
2985
|
-
};
|
|
2986
|
-
function getSchema(name) {
|
|
2987
|
-
switch (name) {
|
|
2988
|
-
case "workflow_step":
|
|
2989
|
-
return WorkflowStepSchema;
|
|
2990
|
-
case "workflow_definition":
|
|
2991
|
-
return WorkflowDefinitionSchema;
|
|
2992
|
-
case "workflow_field":
|
|
2993
|
-
return WorkflowFieldSchema;
|
|
2994
|
-
case "workflow_action":
|
|
2995
|
-
return WorkflowActionSchema;
|
|
2996
|
-
case "navigation_item":
|
|
2997
|
-
return NavigationItemSchema;
|
|
2998
|
-
case "auth_config":
|
|
2999
|
-
return authConfigSchema;
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
function enrichErrors(issues, synonyms) {
|
|
3003
|
-
return issues.map((issue) => {
|
|
3004
|
-
const pathStr = issue.path.join(".");
|
|
3005
|
-
const lastSegment = issue.path[issue.path.length - 1];
|
|
3006
|
-
const fieldName = typeof lastSegment === "string" ? lastSegment : void 0;
|
|
3007
|
-
const didYouMean = fieldName ? synonyms[fieldName] : void 0;
|
|
3008
|
-
return {
|
|
3009
|
-
path: pathStr || "(root)",
|
|
3010
|
-
message: issue.message,
|
|
3011
|
-
...didYouMean ? { didYouMean } : {}
|
|
3012
|
-
};
|
|
3013
|
-
});
|
|
3014
|
-
}
|
|
3015
|
-
function handleValidateYamlFragment(input) {
|
|
3016
|
-
const { schemaName, yaml: yaml4 } = input;
|
|
3017
|
-
if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
|
|
3018
|
-
return {
|
|
3019
|
-
valid: false,
|
|
3020
|
-
parseError: `Unknown schemaName: "${schemaName}". Supported: ${SUPPORTED_SCHEMA_NAMES.join(", ")}`
|
|
3021
|
-
};
|
|
3022
|
-
}
|
|
3023
|
-
const name = schemaName;
|
|
3024
|
-
let parsed;
|
|
3025
|
-
try {
|
|
3026
|
-
parsed = yamlLoad2(yaml4);
|
|
3027
|
-
} catch (err) {
|
|
3028
|
-
return {
|
|
3029
|
-
valid: false,
|
|
3030
|
-
parseError: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
3031
|
-
};
|
|
3032
|
-
}
|
|
3033
|
-
const schema = getSchema(name);
|
|
3034
|
-
const result = schema.safeParse(parsed);
|
|
3035
|
-
if (result.success) {
|
|
3036
|
-
return { valid: true };
|
|
3037
|
-
}
|
|
3038
|
-
const synonyms = FIELD_SYNONYMS[name];
|
|
3039
|
-
const errors = enrichErrors(result.error.issues, synonyms);
|
|
3040
|
-
return { valid: false, errors };
|
|
3041
|
-
}
|
|
3042
|
-
function registerValidateYamlFragmentTool(server2) {
|
|
3043
|
-
server2.tool(
|
|
3044
|
-
"stackwright_pro_validate_yaml_fragment",
|
|
3045
|
-
'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.',
|
|
3046
|
-
{
|
|
3047
|
-
schemaName: z12.enum(SUPPORTED_SCHEMA_NAMES).describe(
|
|
3048
|
-
"Schema to validate against. One of: workflow_step, workflow_definition, workflow_field, workflow_action, navigation_item, auth_config"
|
|
3049
|
-
),
|
|
3050
|
-
yaml: z12.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
|
|
3051
|
-
},
|
|
3052
|
-
async ({ schemaName, yaml: yaml4 }) => {
|
|
3053
|
-
const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
|
|
3054
|
-
return {
|
|
3055
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3056
|
-
};
|
|
3057
|
-
}
|
|
3058
|
-
);
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
// src/tools/get-schema.ts
|
|
3062
|
-
var NavigationLinkSchema2 = z13.lazy(
|
|
3063
|
-
() => z13.object({
|
|
3064
|
-
label: z13.string().min(1),
|
|
3065
|
-
href: z13.string().min(1),
|
|
3066
|
-
children: z13.array(NavigationLinkSchema2).optional()
|
|
3067
|
-
})
|
|
3068
|
-
);
|
|
3069
|
-
var NavigationSectionSchema2 = z13.object({
|
|
3070
|
-
section: z13.string().min(1),
|
|
3071
|
-
items: z13.array(NavigationLinkSchema2)
|
|
3072
|
-
});
|
|
3073
|
-
var NavigationItemSchema2 = z13.union([
|
|
3074
|
-
NavigationLinkSchema2,
|
|
3075
|
-
NavigationSectionSchema2
|
|
3076
|
-
]);
|
|
3077
|
-
function getZodSchema(name) {
|
|
3078
|
-
switch (name) {
|
|
3079
|
-
case "workflow_step":
|
|
3080
|
-
return WorkflowStepSchema2;
|
|
3081
|
-
case "workflow_definition":
|
|
3082
|
-
return WorkflowDefinitionSchema2;
|
|
3083
|
-
case "workflow_field":
|
|
3084
|
-
return WorkflowFieldSchema2;
|
|
3085
|
-
case "workflow_action":
|
|
3086
|
-
return WorkflowActionSchema2;
|
|
3087
|
-
case "navigation_item":
|
|
3088
|
-
return NavigationItemSchema2;
|
|
3089
|
-
case "auth_config":
|
|
3090
|
-
return authConfigSchema2;
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3500
|
+
var PHASE_SCHEMA_NAMES = buildPhaseSchemaNames();
|
|
3093
3501
|
function formatType(node, depth = 0) {
|
|
3094
3502
|
if (node.const !== void 0) return JSON.stringify(node.const);
|
|
3095
3503
|
if (node.enum) return node.enum.map((v) => JSON.stringify(v)).join(" | ");
|
|
@@ -3116,7 +3524,7 @@ function formatType(node, depth = 0) {
|
|
|
3116
3524
|
if (node.maximum !== void 0) constraints.push(`max ${node.maximum}`);
|
|
3117
3525
|
return constraints.length > 0 ? `${base} (${constraints.join(", ")})` : base;
|
|
3118
3526
|
}
|
|
3119
|
-
function formatObjectSchema(node, schemaName,
|
|
3527
|
+
function formatObjectSchema(node, schemaName, reverseSynonyms) {
|
|
3120
3528
|
const lines = [];
|
|
3121
3529
|
const displayName = schemaName.split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
3122
3530
|
lines.push(`${displayName}:`);
|
|
@@ -3130,7 +3538,8 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3130
3538
|
for (const [field, fieldNode] of Object.entries(branch.properties ?? {})) {
|
|
3131
3539
|
if (fieldNode.const !== void 0) continue;
|
|
3132
3540
|
const isRequired = req.has(field);
|
|
3133
|
-
const
|
|
3541
|
+
const correctFor = reverseSynonyms[field];
|
|
3542
|
+
const hint = correctFor ? ` -- WARNING: use ${correctFor}: not ${field}:` : "";
|
|
3134
3543
|
lines.push(
|
|
3135
3544
|
` ${isRequired ? "REQUIRED" : "optional"}: ${field}: ${formatType(fieldNode)}${hint}`
|
|
3136
3545
|
);
|
|
@@ -3138,6 +3547,12 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3138
3547
|
}
|
|
3139
3548
|
return lines.join("\n");
|
|
3140
3549
|
}
|
|
3550
|
+
if (!node.properties && !node.oneOf && !node.anyOf) {
|
|
3551
|
+
const typeStr = formatType(node);
|
|
3552
|
+
lines.push(` type: ${typeStr}`);
|
|
3553
|
+
if (node.description) lines.push(` description: ${node.description}`);
|
|
3554
|
+
return lines.join("\n");
|
|
3555
|
+
}
|
|
3141
3556
|
const required = new Set(node.required ?? []);
|
|
3142
3557
|
const properties = node.properties ?? {};
|
|
3143
3558
|
const requiredFields = Object.entries(properties).filter(([k]) => required.has(k));
|
|
@@ -3146,8 +3561,7 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3146
3561
|
lines.push(" REQUIRED:");
|
|
3147
3562
|
for (const [field, fieldNode] of requiredFields) {
|
|
3148
3563
|
const typeStr = formatType(fieldNode);
|
|
3149
|
-
|
|
3150
|
-
lines.push(` ${field}: ${typeStr}${warn}`);
|
|
3564
|
+
lines.push(` ${field}: ${typeStr}`);
|
|
3151
3565
|
}
|
|
3152
3566
|
}
|
|
3153
3567
|
if (optionalFields.length > 0) {
|
|
@@ -3161,24 +3575,30 @@ function formatObjectSchema(node, schemaName, synonyms) {
|
|
|
3161
3575
|
}
|
|
3162
3576
|
function handleGetSchema(input) {
|
|
3163
3577
|
const { schemaName } = input;
|
|
3164
|
-
|
|
3578
|
+
const entry = SCHEMA_REGISTRY.get(schemaName);
|
|
3579
|
+
if (!entry) {
|
|
3165
3580
|
return {
|
|
3166
|
-
error: `Unknown schemaName: "${schemaName}". Supported: ${
|
|
3581
|
+
error: `Unknown schemaName: "${schemaName}". Supported: ${REGISTRY_SCHEMA_NAMES.join(", ")}`
|
|
3167
3582
|
};
|
|
3168
3583
|
}
|
|
3169
|
-
const
|
|
3170
|
-
const
|
|
3171
|
-
const synonyms
|
|
3584
|
+
const synonyms = entry.synonyms ?? {};
|
|
3585
|
+
const reverseSynonyms = {};
|
|
3586
|
+
for (const [correct, wrongs] of Object.entries(synonyms)) {
|
|
3587
|
+
for (const wrong of wrongs) {
|
|
3588
|
+
reverseSynonyms[wrong] = correct;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
const ctx = input.projectRoot ? { projectRoot: input.projectRoot } : void 0;
|
|
3592
|
+
const resolvedSchema = getSchemaForEntry(entry, ctx);
|
|
3172
3593
|
let jsonSchema;
|
|
3173
3594
|
try {
|
|
3174
|
-
|
|
3175
|
-
jsonSchema = raw;
|
|
3595
|
+
jsonSchema = z13.toJSONSchema(resolvedSchema, { unrepresentable: "any" });
|
|
3176
3596
|
} catch {
|
|
3177
3597
|
jsonSchema = { type: "object", description: "Schema introspection unavailable for this type" };
|
|
3178
3598
|
}
|
|
3179
|
-
const summary = formatObjectSchema(jsonSchema,
|
|
3599
|
+
const summary = formatObjectSchema(jsonSchema, schemaName, reverseSynonyms);
|
|
3180
3600
|
const synonymWarnings = Object.entries(synonyms).map(
|
|
3181
|
-
([
|
|
3601
|
+
([correct, wrongs]) => ` WARNING: use "${correct}" \u2014 NOT ${wrongs.map((w) => `"${w}"`).join(" or ")}`
|
|
3182
3602
|
);
|
|
3183
3603
|
const fullText = [
|
|
3184
3604
|
summary,
|
|
@@ -3188,19 +3608,12 @@ function handleGetSchema(input) {
|
|
|
3188
3608
|
].join("\n");
|
|
3189
3609
|
const tokenEstimate = Math.ceil(fullText.length / 4);
|
|
3190
3610
|
return {
|
|
3191
|
-
schemaName
|
|
3611
|
+
schemaName,
|
|
3192
3612
|
summary,
|
|
3193
3613
|
synonymWarnings,
|
|
3194
3614
|
tokenEstimate
|
|
3195
3615
|
};
|
|
3196
3616
|
}
|
|
3197
|
-
var PHASE_SCHEMA_NAMES = {
|
|
3198
|
-
workflow: ["workflow_step", "workflow_definition", "workflow_field", "workflow_action"],
|
|
3199
|
-
pages: ["navigation_item"],
|
|
3200
|
-
dashboard: ["navigation_item"],
|
|
3201
|
-
polish: ["navigation_item"],
|
|
3202
|
-
auth: ["auth_config"]
|
|
3203
|
-
};
|
|
3204
3617
|
function buildSchemaReferenceBlock(phase) {
|
|
3205
3618
|
const schemaNames = PHASE_SCHEMA_NAMES[phase];
|
|
3206
3619
|
if (!schemaNames || schemaNames.length === 0) return null;
|
|
@@ -3225,12 +3638,13 @@ function registerGetSchemaTool(server2) {
|
|
|
3225
3638
|
"stackwright_pro_get_schema",
|
|
3226
3639
|
'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.',
|
|
3227
3640
|
{
|
|
3228
|
-
schemaName: z13.
|
|
3229
|
-
|
|
3641
|
+
schemaName: z13.string().describe(`Schema to summarize. Available: ${REGISTRY_SCHEMA_NAMES.join(", ")}`),
|
|
3642
|
+
projectRoot: z13.string().optional().describe(
|
|
3643
|
+
"Absolute path to the project root. Required for factory schemas like theme_status_token. Defaults to cwd."
|
|
3230
3644
|
)
|
|
3231
3645
|
},
|
|
3232
|
-
async ({ schemaName }) => {
|
|
3233
|
-
const result = handleGetSchema({ schemaName });
|
|
3646
|
+
async ({ schemaName, projectRoot }) => {
|
|
3647
|
+
const result = handleGetSchema({ schemaName, projectRoot: projectRoot ?? process.cwd() });
|
|
3234
3648
|
if ("error" in result) {
|
|
3235
3649
|
return {
|
|
3236
3650
|
content: [{ type: "text", text: JSON.stringify({ error: result.error }) }],
|
|
@@ -3250,8 +3664,8 @@ function registerGetSchemaTool(server2) {
|
|
|
3250
3664
|
}
|
|
3251
3665
|
|
|
3252
3666
|
// src/tools/pipeline-graph.ts
|
|
3253
|
-
import { readFileSync as
|
|
3254
|
-
import { join as
|
|
3667
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync7, lstatSync as lstatSync5 } from "fs";
|
|
3668
|
+
import { join as join10 } from "path";
|
|
3255
3669
|
var MANIFEST_NAME_TO_PHASE = {
|
|
3256
3670
|
"stackwright-pro-designer-otter": "designer",
|
|
3257
3671
|
"stackwright-pro-theme-otter": "theme",
|
|
@@ -3283,8 +3697,8 @@ var OTTER_SEARCH_PATHS = [
|
|
|
3283
3697
|
];
|
|
3284
3698
|
function resolveOtterDirFromCwd() {
|
|
3285
3699
|
const cwd = process.cwd();
|
|
3286
|
-
for (const
|
|
3287
|
-
const candidate =
|
|
3700
|
+
for (const relative4 of OTTER_SEARCH_PATHS) {
|
|
3701
|
+
const candidate = join10(cwd, relative4);
|
|
3288
3702
|
try {
|
|
3289
3703
|
lstatSync5(candidate);
|
|
3290
3704
|
return candidate;
|
|
@@ -3296,14 +3710,14 @@ function resolveOtterDirFromCwd() {
|
|
|
3296
3710
|
);
|
|
3297
3711
|
}
|
|
3298
3712
|
function loadOtterManifestsFromDir(otterDir) {
|
|
3299
|
-
const entries =
|
|
3713
|
+
const entries = readdirSync7(otterDir);
|
|
3300
3714
|
const manifests = [];
|
|
3301
3715
|
for (const filename of entries) {
|
|
3302
3716
|
if (!filename.endsWith("-otter.json")) continue;
|
|
3303
|
-
const filePath =
|
|
3717
|
+
const filePath = join10(otterDir, filename);
|
|
3304
3718
|
if (lstatSync5(filePath).isSymbolicLink()) continue;
|
|
3305
3719
|
try {
|
|
3306
|
-
const raw =
|
|
3720
|
+
const raw = readFileSync10(filePath, "utf-8");
|
|
3307
3721
|
const manifest = JSON.parse(raw);
|
|
3308
3722
|
manifests.push(manifest);
|
|
3309
3723
|
} catch (err) {
|
|
@@ -3408,19 +3822,19 @@ var DISTRIBUTED_NAMESPACES = ["@stackwright-pro", "@stackwright"];
|
|
|
3408
3822
|
function loadDistributedOtterManifests(cwd) {
|
|
3409
3823
|
const results = [];
|
|
3410
3824
|
for (const namespace of DISTRIBUTED_NAMESPACES) {
|
|
3411
|
-
const nsDir =
|
|
3825
|
+
const nsDir = join10(cwd, "node_modules", namespace);
|
|
3412
3826
|
let pkgDirNames;
|
|
3413
3827
|
try {
|
|
3414
|
-
pkgDirNames =
|
|
3828
|
+
pkgDirNames = readdirSync7(nsDir);
|
|
3415
3829
|
} catch {
|
|
3416
3830
|
continue;
|
|
3417
3831
|
}
|
|
3418
3832
|
for (const pkgDirName of pkgDirNames) {
|
|
3419
|
-
const pkgDir =
|
|
3833
|
+
const pkgDir = join10(nsDir, pkgDirName);
|
|
3420
3834
|
const packageName = `${namespace}/${pkgDirName}`;
|
|
3421
3835
|
let otterRelPaths;
|
|
3422
3836
|
try {
|
|
3423
|
-
const raw =
|
|
3837
|
+
const raw = readFileSync10(join10(pkgDir, "package.json"), "utf-8");
|
|
3424
3838
|
const pkgJson = JSON.parse(raw);
|
|
3425
3839
|
const declared = pkgJson.stackwright?.otters;
|
|
3426
3840
|
if (!Array.isArray(declared) || declared.length === 0) continue;
|
|
@@ -3429,9 +3843,9 @@ function loadDistributedOtterManifests(cwd) {
|
|
|
3429
3843
|
continue;
|
|
3430
3844
|
}
|
|
3431
3845
|
for (const relPath of otterRelPaths) {
|
|
3432
|
-
const otterFilePath =
|
|
3846
|
+
const otterFilePath = join10(pkgDir, relPath);
|
|
3433
3847
|
try {
|
|
3434
|
-
const raw =
|
|
3848
|
+
const raw = readFileSync10(otterFilePath, "utf-8");
|
|
3435
3849
|
const manifest = JSON.parse(raw);
|
|
3436
3850
|
results.push({ manifest, packageName });
|
|
3437
3851
|
} catch (err) {
|
|
@@ -3576,18 +3990,18 @@ function createDefaultState() {
|
|
|
3576
3990
|
};
|
|
3577
3991
|
}
|
|
3578
3992
|
function statePath(cwd) {
|
|
3579
|
-
return
|
|
3993
|
+
return join11(cwd, ".stackwright", "pipeline-state.json");
|
|
3580
3994
|
}
|
|
3581
3995
|
function readState(cwd) {
|
|
3582
3996
|
const p = statePath(cwd);
|
|
3583
|
-
if (!
|
|
3997
|
+
if (!existsSync11(p)) return createDefaultState();
|
|
3584
3998
|
const raw = JSON.parse(safeReadSync(p));
|
|
3585
3999
|
const result = PipelineStateSchema.safeParse(raw);
|
|
3586
4000
|
if (!result.success) return createDefaultState();
|
|
3587
4001
|
return result.data;
|
|
3588
4002
|
}
|
|
3589
4003
|
function safeWriteSync(filePath, content) {
|
|
3590
|
-
if (
|
|
4004
|
+
if (existsSync11(filePath)) {
|
|
3591
4005
|
const stat = lstatSync6(filePath);
|
|
3592
4006
|
if (stat.isSymbolicLink()) {
|
|
3593
4007
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
@@ -3596,25 +4010,25 @@ function safeWriteSync(filePath, content) {
|
|
|
3596
4010
|
writeFileSync4(filePath, content);
|
|
3597
4011
|
}
|
|
3598
4012
|
function safeReadSync(filePath) {
|
|
3599
|
-
if (
|
|
4013
|
+
if (existsSync11(filePath)) {
|
|
3600
4014
|
const stat = lstatSync6(filePath);
|
|
3601
4015
|
if (stat.isSymbolicLink()) {
|
|
3602
4016
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
3603
4017
|
}
|
|
3604
4018
|
}
|
|
3605
|
-
return
|
|
4019
|
+
return readFileSync11(filePath, "utf-8");
|
|
3606
4020
|
}
|
|
3607
4021
|
function writeState(cwd, state) {
|
|
3608
|
-
const dir =
|
|
4022
|
+
const dir = join11(cwd, ".stackwright");
|
|
3609
4023
|
mkdirSync4(dir, { recursive: true });
|
|
3610
4024
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3611
4025
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
3612
4026
|
}
|
|
3613
4027
|
function updateStateAtomic(cwd, mutator) {
|
|
3614
|
-
const dir =
|
|
4028
|
+
const dir = join11(cwd, ".stackwright");
|
|
3615
4029
|
mkdirSync4(dir, { recursive: true });
|
|
3616
4030
|
const path5 = statePath(cwd);
|
|
3617
|
-
if (!
|
|
4031
|
+
if (!existsSync11(path5)) {
|
|
3618
4032
|
safeWriteSync(path5, JSON.stringify(createDefaultState(), null, 2) + "\n");
|
|
3619
4033
|
}
|
|
3620
4034
|
const release = lockSync(path5, {
|
|
@@ -3648,8 +4062,8 @@ function handleGetPipelineState(_cwd) {
|
|
|
3648
4062
|
const cwd = _cwd ?? process.cwd();
|
|
3649
4063
|
try {
|
|
3650
4064
|
const state = readState(cwd);
|
|
3651
|
-
const keyPath =
|
|
3652
|
-
if (!
|
|
4065
|
+
const keyPath = join11(cwd, ".stackwright", "pipeline-keys.json");
|
|
4066
|
+
if (!existsSync11(keyPath)) {
|
|
3653
4067
|
try {
|
|
3654
4068
|
const { fingerprint } = initPipelineKeys(cwd);
|
|
3655
4069
|
state["signingKeyFingerprint"] = fingerprint;
|
|
@@ -3788,8 +4202,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3788
4202
|
if (AUTO_EXECUTE_PHASES.has(phase)) {
|
|
3789
4203
|
return { text: JSON.stringify({ ready: true, phase, autoExecute: true }), isError: false };
|
|
3790
4204
|
}
|
|
3791
|
-
const answerFile =
|
|
3792
|
-
if (!
|
|
4205
|
+
const answerFile = join11(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
4206
|
+
if (!existsSync11(answerFile)) {
|
|
3793
4207
|
return {
|
|
3794
4208
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
3795
4209
|
isError: false
|
|
@@ -3813,7 +4227,7 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3813
4227
|
}
|
|
3814
4228
|
}
|
|
3815
4229
|
try {
|
|
3816
|
-
const answersDir =
|
|
4230
|
+
const answersDir = join11(cwd, ".stackwright", "answers");
|
|
3817
4231
|
const answeredPhases = [];
|
|
3818
4232
|
const missingPhases = [];
|
|
3819
4233
|
for (const phase2 of PHASE_ORDER) {
|
|
@@ -3821,8 +4235,8 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
3821
4235
|
answeredPhases.push(phase2);
|
|
3822
4236
|
continue;
|
|
3823
4237
|
}
|
|
3824
|
-
const answerFile =
|
|
3825
|
-
if (
|
|
4238
|
+
const answerFile = join11(answersDir, `${phase2}.json`);
|
|
4239
|
+
if (existsSync11(answerFile)) {
|
|
3826
4240
|
try {
|
|
3827
4241
|
const raw = safeReadSync(answerFile);
|
|
3828
4242
|
const parsed = JSON.parse(raw);
|
|
@@ -3891,7 +4305,7 @@ function handleGetReadyPhases(_cwd) {
|
|
|
3891
4305
|
function handleListArtifacts(_cwd) {
|
|
3892
4306
|
const cwd = _cwd ?? process.cwd();
|
|
3893
4307
|
try {
|
|
3894
|
-
const artifactsDir =
|
|
4308
|
+
const artifactsDir = join11(cwd, ".stackwright", "artifacts");
|
|
3895
4309
|
let manifest = null;
|
|
3896
4310
|
try {
|
|
3897
4311
|
manifest = loadSignatureManifest(cwd);
|
|
@@ -3901,8 +4315,8 @@ function handleListArtifacts(_cwd) {
|
|
|
3901
4315
|
let completedCount = 0;
|
|
3902
4316
|
for (const phase of PHASE_ORDER) {
|
|
3903
4317
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
3904
|
-
const fullPath =
|
|
3905
|
-
const exists =
|
|
4318
|
+
const fullPath = join11(artifactsDir, expectedFile);
|
|
4319
|
+
const exists = existsSync11(fullPath);
|
|
3906
4320
|
if (exists) completedCount++;
|
|
3907
4321
|
let signed = false;
|
|
3908
4322
|
let signatureValid = null;
|
|
@@ -3955,9 +4369,9 @@ function handleWritePhaseQuestions(input) {
|
|
|
3955
4369
|
}
|
|
3956
4370
|
} catch {
|
|
3957
4371
|
}
|
|
3958
|
-
const questionsDir =
|
|
4372
|
+
const questionsDir = join11(cwd, ".stackwright", "questions");
|
|
3959
4373
|
mkdirSync4(questionsDir, { recursive: true });
|
|
3960
|
-
const filePath =
|
|
4374
|
+
const filePath = join11(questionsDir, `${phase}.json`);
|
|
3961
4375
|
const payload = {
|
|
3962
4376
|
version: "1.0",
|
|
3963
4377
|
phase,
|
|
@@ -3997,14 +4411,14 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
3997
4411
|
};
|
|
3998
4412
|
}
|
|
3999
4413
|
try {
|
|
4000
|
-
const answersPath =
|
|
4414
|
+
const answersPath = join11(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
4001
4415
|
let answers = {};
|
|
4002
|
-
if (
|
|
4416
|
+
if (existsSync11(answersPath)) {
|
|
4003
4417
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
4004
4418
|
}
|
|
4005
4419
|
let buildContextText = "";
|
|
4006
|
-
const buildContextPath =
|
|
4007
|
-
if (
|
|
4420
|
+
const buildContextPath = join11(cwd, ".stackwright", "build-context.json");
|
|
4421
|
+
if (existsSync11(buildContextPath)) {
|
|
4008
4422
|
try {
|
|
4009
4423
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
4010
4424
|
if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
|
|
@@ -4019,8 +4433,8 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
4019
4433
|
const missingDependencies = [];
|
|
4020
4434
|
for (const dep of deps) {
|
|
4021
4435
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
4022
|
-
const artifactPath =
|
|
4023
|
-
if (
|
|
4436
|
+
const artifactPath = join11(cwd, ".stackwright", "artifacts", artifactFile);
|
|
4437
|
+
if (existsSync11(artifactPath)) {
|
|
4024
4438
|
const rawContent = safeReadSync(artifactPath);
|
|
4025
4439
|
const rawBytes = Buffer.from(rawContent, "utf-8");
|
|
4026
4440
|
const content = JSON.parse(rawContent);
|
|
@@ -4085,8 +4499,8 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
4085
4499
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
4086
4500
|
}
|
|
4087
4501
|
if (phase === "auth") {
|
|
4088
|
-
const initContextPath =
|
|
4089
|
-
if (
|
|
4502
|
+
const initContextPath = join11(cwd, ".stackwright", "init-context.json");
|
|
4503
|
+
if (existsSync11(initContextPath)) {
|
|
4090
4504
|
try {
|
|
4091
4505
|
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
4092
4506
|
if (initContext.devOnly === true || initContext.nonInteractive === true) {
|
|
@@ -4227,7 +4641,12 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
4227
4641
|
"--surface": "210 40% 98%",
|
|
4228
4642
|
"--border": "214.3 31.8% 91.4%"
|
|
4229
4643
|
},
|
|
4230
|
-
dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" }
|
|
4644
|
+
dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" },
|
|
4645
|
+
// Optional — include when design language has status semantics (swp-cxwu).
|
|
4646
|
+
// Array of status-* token names emitted in tokens.colors.
|
|
4647
|
+
// Consumed by validate_artifact theme enforcement to reject unknown status tokens.
|
|
4648
|
+
// OMIT if project has no status-coded data.
|
|
4649
|
+
statusLadder: ["status-ok", "status-warning", "status-error", "status-info"]
|
|
4231
4650
|
},
|
|
4232
4651
|
null,
|
|
4233
4652
|
2
|
|
@@ -4665,7 +5084,7 @@ function _validateArtifactInner(input) {
|
|
|
4665
5084
|
auth: (artifact2) => {
|
|
4666
5085
|
const authConfig = artifact2["authConfig"];
|
|
4667
5086
|
if (!authConfig) return { success: true };
|
|
4668
|
-
const result =
|
|
5087
|
+
const result = authConfigSchema2.safeParse(authConfig);
|
|
4669
5088
|
if (!result.success) {
|
|
4670
5089
|
const issues = result.error.issues.slice(0, 3).map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
4671
5090
|
return { success: false, error: { message: issues } };
|
|
@@ -4697,21 +5116,21 @@ function _validateArtifactInner(input) {
|
|
|
4697
5116
|
// state-machine in this services-config artifact.
|
|
4698
5117
|
// Skips gracefully when wizard-otter didn't run (Pro-only install) or declared no hooks.
|
|
4699
5118
|
services: (artifact2) => {
|
|
4700
|
-
const artifactsDir =
|
|
5119
|
+
const artifactsDir = join11(cwd, ".stackwright", "artifacts");
|
|
4701
5120
|
const workflowArtifactFiles = [];
|
|
4702
|
-
if (
|
|
5121
|
+
if (existsSync11(artifactsDir)) {
|
|
4703
5122
|
try {
|
|
4704
|
-
const entries =
|
|
5123
|
+
const entries = readdirSync8(artifactsDir);
|
|
4705
5124
|
for (const entry of entries) {
|
|
4706
5125
|
if (entry.startsWith("workflow-config-") && entry.endsWith(".json")) {
|
|
4707
|
-
workflowArtifactFiles.push(
|
|
5126
|
+
workflowArtifactFiles.push(join11(artifactsDir, entry));
|
|
4708
5127
|
}
|
|
4709
5128
|
}
|
|
4710
5129
|
} catch {
|
|
4711
5130
|
}
|
|
4712
5131
|
}
|
|
4713
|
-
const legacyPath =
|
|
4714
|
-
if (
|
|
5132
|
+
const legacyPath = join11(artifactsDir, "workflow-config.json");
|
|
5133
|
+
if (existsSync11(legacyPath)) {
|
|
4715
5134
|
workflowArtifactFiles.push(legacyPath);
|
|
4716
5135
|
}
|
|
4717
5136
|
if (workflowArtifactFiles.length === 0) {
|
|
@@ -4720,7 +5139,7 @@ function _validateArtifactInner(input) {
|
|
|
4720
5139
|
const declaredHooks = [];
|
|
4721
5140
|
for (const filePath of workflowArtifactFiles) {
|
|
4722
5141
|
try {
|
|
4723
|
-
const parsed = JSON.parse(
|
|
5142
|
+
const parsed = JSON.parse(readFileSync11(filePath, "utf-8"));
|
|
4724
5143
|
if (Array.isArray(parsed.serviceHooks)) {
|
|
4725
5144
|
declaredHooks.push(...parsed.serviceHooks);
|
|
4726
5145
|
}
|
|
@@ -4810,6 +5229,42 @@ function _validateArtifactInner(input) {
|
|
|
4810
5229
|
return { text: JSON.stringify(result), isError: false };
|
|
4811
5230
|
}
|
|
4812
5231
|
}
|
|
5232
|
+
if (phase === "pages" || phase === "dashboard" || phase === "geo") {
|
|
5233
|
+
const themeResult = validateThemeTokenUsage(cwd);
|
|
5234
|
+
if (!themeResult.valid && !themeResult.skipped) {
|
|
5235
|
+
const result = {
|
|
5236
|
+
valid: false,
|
|
5237
|
+
phase,
|
|
5238
|
+
violation: "schema-mismatch",
|
|
5239
|
+
retryPrompt: themeResult.retryPrompt
|
|
5240
|
+
};
|
|
5241
|
+
return { text: JSON.stringify(result), isError: false };
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
if (phase === "pages" || phase === "dashboard" || phase === "geo") {
|
|
5245
|
+
const globalsResult = validateBuildContextGlobals(cwd);
|
|
5246
|
+
if (!globalsResult.valid && !globalsResult.skipped) {
|
|
5247
|
+
const result = {
|
|
5248
|
+
valid: false,
|
|
5249
|
+
phase,
|
|
5250
|
+
violation: "schema-mismatch",
|
|
5251
|
+
retryPrompt: globalsResult.retryPrompt
|
|
5252
|
+
};
|
|
5253
|
+
return { text: JSON.stringify(result), isError: false };
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
if (phase === "pages") {
|
|
5257
|
+
const workflowCrossCheck = validateWorkflowContainerPages(cwd);
|
|
5258
|
+
if (!workflowCrossCheck.valid) {
|
|
5259
|
+
const result = {
|
|
5260
|
+
valid: false,
|
|
5261
|
+
phase,
|
|
5262
|
+
violation: "schema-mismatch",
|
|
5263
|
+
retryPrompt: workflowCrossCheck.retryPrompt
|
|
5264
|
+
};
|
|
5265
|
+
return { text: JSON.stringify(result), isError: false };
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
4813
5268
|
if (phase === "api") {
|
|
4814
5269
|
const apiAuthResult = validateApiIntegration(artifact, input.integrationName);
|
|
4815
5270
|
if (!apiAuthResult.valid) {
|
|
@@ -4838,7 +5293,7 @@ function _validateArtifactInner(input) {
|
|
|
4838
5293
|
const authCfg = artifact["authConfig"];
|
|
4839
5294
|
const routes = authCfg?.["protectedRoutes"];
|
|
4840
5295
|
if (Array.isArray(routes) && routes.length > 0) {
|
|
4841
|
-
const artifactsDir =
|
|
5296
|
+
const artifactsDir = join11(cwd, ".stackwright", "artifacts");
|
|
4842
5297
|
const knownSlugs = extractManifestSlugs(artifactsDir);
|
|
4843
5298
|
const orphanPatterns = [];
|
|
4844
5299
|
for (const route of routes) {
|
|
@@ -4855,10 +5310,10 @@ function _validateArtifactInner(input) {
|
|
|
4855
5310
|
}
|
|
4856
5311
|
}
|
|
4857
5312
|
try {
|
|
4858
|
-
const artifactsDir =
|
|
5313
|
+
const artifactsDir = join11(cwd, ".stackwright", "artifacts");
|
|
4859
5314
|
mkdirSync4(artifactsDir, { recursive: true });
|
|
4860
5315
|
const artifactFile = phase === "api" && input.integrationName ? `api-config-${input.integrationName}.json` : phase === "workflow" && input.workflowName ? `workflow-config-${input.workflowName}.json` : PHASE_ARTIFACT[phase];
|
|
4861
|
-
const artifactPath =
|
|
5316
|
+
const artifactPath = join11(artifactsDir, artifactFile);
|
|
4862
5317
|
const serialized = JSON.stringify(artifact, null, 2) + "\n";
|
|
4863
5318
|
const artifactBytes = Buffer.from(serialized, "utf-8");
|
|
4864
5319
|
safeWriteSync(artifactPath, serialized);
|
|
@@ -5088,9 +5543,9 @@ function registerPipelineTools(server2) {
|
|
|
5088
5543
|
isError: true
|
|
5089
5544
|
};
|
|
5090
5545
|
}
|
|
5091
|
-
const questionsDir =
|
|
5546
|
+
const questionsDir = join11(process.cwd(), ".stackwright", "questions");
|
|
5092
5547
|
mkdirSync4(questionsDir, { recursive: true });
|
|
5093
|
-
const outPath =
|
|
5548
|
+
const outPath = join11(questionsDir, `${phase}.json`);
|
|
5094
5549
|
writeFileSync4(
|
|
5095
5550
|
outPath,
|
|
5096
5551
|
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
@@ -5183,8 +5638,8 @@ function registerPipelineTools(server2) {
|
|
|
5183
5638
|
|
|
5184
5639
|
// src/tools/safe-write.ts
|
|
5185
5640
|
import { z as z15 } from "zod";
|
|
5186
|
-
import { writeFileSync as writeFileSync5, readFileSync as
|
|
5187
|
-
import { normalize, isAbsolute, dirname, join as
|
|
5641
|
+
import { writeFileSync as writeFileSync5, readFileSync as readFileSync12, existsSync as existsSync12, mkdirSync as mkdirSync5, lstatSync as lstatSync7 } from "fs";
|
|
5642
|
+
import { normalize, isAbsolute, dirname, join as join12 } from "path";
|
|
5188
5643
|
import { emit as emit2 } from "@stackwright-pro/telemetry";
|
|
5189
5644
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
5190
5645
|
"stackwright-pro-designer-otter": [
|
|
@@ -5261,7 +5716,18 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
5261
5716
|
"stackwright-pro-form-wizard-otter": [
|
|
5262
5717
|
{ prefix: "workflows/", suffix: ".yml", description: "Workflow definition" },
|
|
5263
5718
|
{ prefix: "workflows/", suffix: ".yaml", description: "Workflow definition" },
|
|
5264
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" }
|
|
5719
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Workflow config" },
|
|
5720
|
+
// swp-dvde: workflow-otter owns the container page to prevent the 404 gap
|
|
5721
|
+
{
|
|
5722
|
+
prefix: "pages/workflows/",
|
|
5723
|
+
suffix: "/content.yml",
|
|
5724
|
+
description: "Workflow container page content (swp-dvde)"
|
|
5725
|
+
},
|
|
5726
|
+
{
|
|
5727
|
+
prefix: "pages/workflows/",
|
|
5728
|
+
suffix: "/content.yaml",
|
|
5729
|
+
description: "Workflow container page content (swp-dvde)"
|
|
5730
|
+
}
|
|
5265
5731
|
],
|
|
5266
5732
|
"stackwright-pro-scaffold-otter": [
|
|
5267
5733
|
{ prefix: "app/", suffix: ".tsx", description: "Next.js App Router shell files" },
|
|
@@ -5396,21 +5862,21 @@ function extractContentTypes(yamlContent) {
|
|
|
5396
5862
|
return types.length > 0 ? types : ["unknown"];
|
|
5397
5863
|
}
|
|
5398
5864
|
function readPageRegistry(cwd) {
|
|
5399
|
-
const regPath =
|
|
5400
|
-
if (!
|
|
5865
|
+
const regPath = join12(cwd, PAGE_REGISTRY_FILE);
|
|
5866
|
+
if (!existsSync12(regPath)) return {};
|
|
5401
5867
|
try {
|
|
5402
5868
|
const stat = lstatSync7(regPath);
|
|
5403
5869
|
if (stat.isSymbolicLink()) return {};
|
|
5404
|
-
return JSON.parse(
|
|
5870
|
+
return JSON.parse(readFileSync12(regPath, "utf-8"));
|
|
5405
5871
|
} catch {
|
|
5406
5872
|
return {};
|
|
5407
5873
|
}
|
|
5408
5874
|
}
|
|
5409
5875
|
function writePageRegistry(cwd, registry) {
|
|
5410
|
-
const dir =
|
|
5876
|
+
const dir = join12(cwd, ".stackwright");
|
|
5411
5877
|
mkdirSync5(dir, { recursive: true });
|
|
5412
|
-
const regPath =
|
|
5413
|
-
if (
|
|
5878
|
+
const regPath = join12(cwd, PAGE_REGISTRY_FILE);
|
|
5879
|
+
if (existsSync12(regPath)) {
|
|
5414
5880
|
const stat = lstatSync7(regPath);
|
|
5415
5881
|
if (stat.isSymbolicLink()) {
|
|
5416
5882
|
throw new Error("Refusing to write page registry through symlink");
|
|
@@ -5582,8 +6048,8 @@ function _safeWriteInner(input) {
|
|
|
5582
6048
|
return { text: JSON.stringify(result), isError: true };
|
|
5583
6049
|
}
|
|
5584
6050
|
const normalized = normalize(filePath);
|
|
5585
|
-
const fullPath =
|
|
5586
|
-
if (
|
|
6051
|
+
const fullPath = join12(cwd, normalized);
|
|
6052
|
+
if (existsSync12(fullPath)) {
|
|
5587
6053
|
try {
|
|
5588
6054
|
const stat = lstatSync7(fullPath);
|
|
5589
6055
|
if (stat.isSymbolicLink()) {
|
|
@@ -5713,8 +6179,8 @@ function registerSafeWriteTools(server2) {
|
|
|
5713
6179
|
|
|
5714
6180
|
// src/tools/auth.ts
|
|
5715
6181
|
import { z as z16 } from "zod";
|
|
5716
|
-
import { readFileSync as
|
|
5717
|
-
import { join as
|
|
6182
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "fs";
|
|
6183
|
+
import { join as join13 } from "path";
|
|
5718
6184
|
function normalizeProviderSlug(slug) {
|
|
5719
6185
|
return slug.replace(/-/g, "_");
|
|
5720
6186
|
}
|
|
@@ -5744,9 +6210,9 @@ ${indent} requiredRole: ${r.requiredRole}`).join("\n");
|
|
|
5744
6210
|
}
|
|
5745
6211
|
function detectNextMajorVersion(cwd) {
|
|
5746
6212
|
try {
|
|
5747
|
-
const pkgPath =
|
|
5748
|
-
if (!
|
|
5749
|
-
const pkg = JSON.parse(
|
|
6213
|
+
const pkgPath = join13(cwd, "package.json");
|
|
6214
|
+
if (!existsSync13(pkgPath)) return null;
|
|
6215
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
5750
6216
|
const nextVersion = pkg.dependencies?.next ?? pkg.devDependencies?.next;
|
|
5751
6217
|
if (!nextVersion) return null;
|
|
5752
6218
|
const match = nextVersion.match(/(\d+)/);
|
|
@@ -6408,7 +6874,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6408
6874
|
auditRetentionDays,
|
|
6409
6875
|
normalizedRoutes
|
|
6410
6876
|
);
|
|
6411
|
-
writeFileSync6(
|
|
6877
|
+
writeFileSync6(join13(cwd, conventionFile), authFileContent, "utf8");
|
|
6412
6878
|
filesWritten.push(conventionFile);
|
|
6413
6879
|
} catch (err) {
|
|
6414
6880
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6428,9 +6894,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6428
6894
|
if (!devOnly) {
|
|
6429
6895
|
try {
|
|
6430
6896
|
const envBlock = generateEnvBlock(effectiveMethod, params);
|
|
6431
|
-
const envPath =
|
|
6432
|
-
if (
|
|
6433
|
-
const existing =
|
|
6897
|
+
const envPath = join13(cwd, ".env.example");
|
|
6898
|
+
if (existsSync13(envPath)) {
|
|
6899
|
+
const existing = readFileSync13(envPath, "utf8");
|
|
6434
6900
|
writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
6435
6901
|
} else {
|
|
6436
6902
|
writeFileSync6(envPath, envBlock, "utf8");
|
|
@@ -6451,10 +6917,10 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6451
6917
|
}
|
|
6452
6918
|
if (devOnly) {
|
|
6453
6919
|
try {
|
|
6454
|
-
const mockAuthDir =
|
|
6920
|
+
const mockAuthDir = join13(cwd, "lib");
|
|
6455
6921
|
mkdirSync6(mockAuthDir, { recursive: true });
|
|
6456
6922
|
const mockAuthContent = generateMockAuthContent(roles, mockUsers);
|
|
6457
|
-
writeFileSync6(
|
|
6923
|
+
writeFileSync6(join13(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
|
|
6458
6924
|
filesWritten.push("lib/mock-auth.ts");
|
|
6459
6925
|
} catch (err) {
|
|
6460
6926
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6472,9 +6938,9 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6472
6938
|
};
|
|
6473
6939
|
}
|
|
6474
6940
|
try {
|
|
6475
|
-
const pkgPath =
|
|
6476
|
-
if (
|
|
6477
|
-
const existingPkg =
|
|
6941
|
+
const pkgPath = join13(cwd, "package.json");
|
|
6942
|
+
if (existsSync13(pkgPath)) {
|
|
6943
|
+
const existingPkg = readFileSync13(pkgPath, "utf8");
|
|
6478
6944
|
const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
|
|
6479
6945
|
writeFileSync6(pkgPath, updatedPkg, "utf8");
|
|
6480
6946
|
filesWritten.push("package.json");
|
|
@@ -6517,7 +6983,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
6517
6983
|
normalizedRoutes,
|
|
6518
6984
|
useProxy
|
|
6519
6985
|
);
|
|
6520
|
-
writeFileSync6(
|
|
6986
|
+
writeFileSync6(join13(cwd, "stackwright.auth.yml"), authYaml, "utf8");
|
|
6521
6987
|
filesWritten.push("stackwright.auth.yml");
|
|
6522
6988
|
} catch (err) {
|
|
6523
6989
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -6631,8 +7097,8 @@ function registerAuthTools(server2) {
|
|
|
6631
7097
|
|
|
6632
7098
|
// src/integrity.ts
|
|
6633
7099
|
import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
6634
|
-
import { readFileSync as
|
|
6635
|
-
import { join as
|
|
7100
|
+
import { readFileSync as readFileSync14, readdirSync as readdirSync9, lstatSync as lstatSync8 } from "fs";
|
|
7101
|
+
import { join as join14, basename } from "path";
|
|
6636
7102
|
var _checksums = /* @__PURE__ */ new Map([
|
|
6637
7103
|
[
|
|
6638
7104
|
"stackwright-pro-api-otter.json",
|
|
@@ -6648,7 +7114,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6648
7114
|
],
|
|
6649
7115
|
[
|
|
6650
7116
|
"stackwright-pro-dashboard-otter.json",
|
|
6651
|
-
"
|
|
7117
|
+
"ac0a049da72ee20bfb711f640bac571263bf2b05f80cd22a0c2eb5778c40989c"
|
|
6652
7118
|
],
|
|
6653
7119
|
[
|
|
6654
7120
|
"stackwright-pro-data-otter.json",
|
|
@@ -6668,15 +7134,15 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6668
7134
|
],
|
|
6669
7135
|
[
|
|
6670
7136
|
"stackwright-pro-form-wizard-otter.json",
|
|
6671
|
-
"
|
|
7137
|
+
"941bb11b767530460f96fdd08af638b3ede9f7a1ae6f19de04b7d81c95cc62ad"
|
|
6672
7138
|
],
|
|
6673
7139
|
[
|
|
6674
7140
|
"stackwright-pro-geo-otter.json",
|
|
6675
|
-
"
|
|
7141
|
+
"bee276605a3ae3203f5b6a18476316f615cf0ea6f4c73ecf57dcd627417de7f3"
|
|
6676
7142
|
],
|
|
6677
7143
|
[
|
|
6678
7144
|
"stackwright-pro-page-otter.json",
|
|
6679
|
-
"
|
|
7145
|
+
"8c04dd8a14c6e1aa9b0b6685a71a657547c20d99c25448e3523f73d670f87a4a"
|
|
6680
7146
|
],
|
|
6681
7147
|
[
|
|
6682
7148
|
"stackwright-pro-polish-otter.json",
|
|
@@ -6692,7 +7158,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
6692
7158
|
],
|
|
6693
7159
|
[
|
|
6694
7160
|
"stackwright-pro-theme-otter.json",
|
|
6695
|
-
"
|
|
7161
|
+
"e3cb6ef9d9f7cbf662a26f637f3d244ec33cb19cb6a4a55dcd148cbfdaa4d71e"
|
|
6696
7162
|
],
|
|
6697
7163
|
[
|
|
6698
7164
|
"stackwright-services-otter.json",
|
|
@@ -6744,7 +7210,7 @@ function verifyOtterFile(filePath) {
|
|
|
6744
7210
|
}
|
|
6745
7211
|
let raw;
|
|
6746
7212
|
try {
|
|
6747
|
-
raw =
|
|
7213
|
+
raw = readFileSync14(filePath);
|
|
6748
7214
|
} catch (err) {
|
|
6749
7215
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6750
7216
|
return { verified: false, filename, error: `Cannot read file: ${msg}` };
|
|
@@ -6794,7 +7260,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6794
7260
|
const unknown = [];
|
|
6795
7261
|
let entries;
|
|
6796
7262
|
try {
|
|
6797
|
-
entries =
|
|
7263
|
+
entries = readdirSync9(otterDir);
|
|
6798
7264
|
} catch (err) {
|
|
6799
7265
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6800
7266
|
return {
|
|
@@ -6805,7 +7271,7 @@ function verifyAllOtters(otterDir) {
|
|
|
6805
7271
|
}
|
|
6806
7272
|
const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
|
|
6807
7273
|
for (const filename of otterFiles) {
|
|
6808
|
-
const filePath =
|
|
7274
|
+
const filePath = join14(otterDir, filename);
|
|
6809
7275
|
try {
|
|
6810
7276
|
if (lstatSync8(filePath).isSymbolicLink()) {
|
|
6811
7277
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
@@ -6832,8 +7298,8 @@ function verifyAllOtters(otterDir) {
|
|
|
6832
7298
|
var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packages/otters/src/"];
|
|
6833
7299
|
function resolveOtterDir() {
|
|
6834
7300
|
const cwd = process.cwd();
|
|
6835
|
-
for (const
|
|
6836
|
-
const candidate =
|
|
7301
|
+
for (const relative4 of DEFAULT_SEARCH_PATHS) {
|
|
7302
|
+
const candidate = join14(cwd, relative4);
|
|
6837
7303
|
try {
|
|
6838
7304
|
lstatSync8(candidate);
|
|
6839
7305
|
return candidate;
|
|
@@ -6914,13 +7380,13 @@ function registerIntegrityTools(server2) {
|
|
|
6914
7380
|
|
|
6915
7381
|
// src/tools/domain.ts
|
|
6916
7382
|
import { z as z17 } from "zod";
|
|
6917
|
-
import { readFileSync as
|
|
6918
|
-
import { join as
|
|
7383
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
7384
|
+
import { join as join15 } from "path";
|
|
6919
7385
|
function handleListCollections(input) {
|
|
6920
7386
|
const cwd = input._cwd ?? process.cwd();
|
|
6921
7387
|
const sources = [
|
|
6922
7388
|
{
|
|
6923
|
-
path:
|
|
7389
|
+
path: join15(cwd, ".stackwright", "artifacts", "data-config.json"),
|
|
6924
7390
|
source: "data-config.json",
|
|
6925
7391
|
parse: (raw) => {
|
|
6926
7392
|
const parsed = JSON.parse(raw);
|
|
@@ -6931,15 +7397,15 @@ function handleListCollections(input) {
|
|
|
6931
7397
|
}
|
|
6932
7398
|
},
|
|
6933
7399
|
{
|
|
6934
|
-
path:
|
|
7400
|
+
path: join15(cwd, "stackwright.yml"),
|
|
6935
7401
|
source: "stackwright.yml",
|
|
6936
7402
|
parse: extractCollectionsFromYaml
|
|
6937
7403
|
}
|
|
6938
7404
|
];
|
|
6939
7405
|
for (const { path: path5, source, parse } of sources) {
|
|
6940
|
-
if (!
|
|
7406
|
+
if (!existsSync14(path5)) continue;
|
|
6941
7407
|
try {
|
|
6942
|
-
const collections = parse(
|
|
7408
|
+
const collections = parse(readFileSync15(path5, "utf8"));
|
|
6943
7409
|
return {
|
|
6944
7410
|
text: JSON.stringify({ collections, source, collectionCount: collections.length }),
|
|
6945
7411
|
isError: false
|
|
@@ -7090,8 +7556,8 @@ function handleValidateWorkflow(input) {
|
|
|
7090
7556
|
if (input.workflow && Object.keys(input.workflow).length > 0) {
|
|
7091
7557
|
raw = input.workflow;
|
|
7092
7558
|
} else {
|
|
7093
|
-
const artifactPath =
|
|
7094
|
-
if (!
|
|
7559
|
+
const artifactPath = join15(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
7560
|
+
if (!existsSync14(artifactPath)) {
|
|
7095
7561
|
return fail([
|
|
7096
7562
|
{
|
|
7097
7563
|
code: "NO_WORKFLOW",
|
|
@@ -7100,7 +7566,7 @@ function handleValidateWorkflow(input) {
|
|
|
7100
7566
|
]);
|
|
7101
7567
|
}
|
|
7102
7568
|
try {
|
|
7103
|
-
raw = JSON.parse(
|
|
7569
|
+
raw = JSON.parse(readFileSync15(artifactPath, "utf8"));
|
|
7104
7570
|
} catch (err) {
|
|
7105
7571
|
return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
|
|
7106
7572
|
}
|
|
@@ -7407,12 +7873,92 @@ function registerTypeSchemasTool(server2) {
|
|
|
7407
7873
|
);
|
|
7408
7874
|
}
|
|
7409
7875
|
|
|
7876
|
+
// src/tools/validate-yaml-fragment.ts
|
|
7877
|
+
import { z as z19 } from "zod";
|
|
7878
|
+
import { load as yamlLoad5 } from "js-yaml";
|
|
7879
|
+
var FIELD_SYNONYMS = Object.fromEntries(
|
|
7880
|
+
Array.from(SCHEMA_REGISTRY.entries()).map(([name, entry]) => [
|
|
7881
|
+
name,
|
|
7882
|
+
buildReverseSynonyms(entry.synonyms ?? {})
|
|
7883
|
+
])
|
|
7884
|
+
);
|
|
7885
|
+
function enrichErrors(issues, reverseSynonyms) {
|
|
7886
|
+
return issues.map((issue) => {
|
|
7887
|
+
const pathStr = issue.path.join(".");
|
|
7888
|
+
const lastSegment = issue.path[issue.path.length - 1];
|
|
7889
|
+
const fieldName = typeof lastSegment === "string" ? lastSegment : void 0;
|
|
7890
|
+
const didYouMean = fieldName ? reverseSynonyms[fieldName] : void 0;
|
|
7891
|
+
return {
|
|
7892
|
+
path: pathStr || "(root)",
|
|
7893
|
+
message: issue.message,
|
|
7894
|
+
...didYouMean ? { didYouMean } : {}
|
|
7895
|
+
};
|
|
7896
|
+
});
|
|
7897
|
+
}
|
|
7898
|
+
function handleValidateYamlFragment(input) {
|
|
7899
|
+
const { schemaName, yaml: yaml4 } = input;
|
|
7900
|
+
const entry = SCHEMA_REGISTRY.get(schemaName);
|
|
7901
|
+
if (!entry) {
|
|
7902
|
+
return {
|
|
7903
|
+
valid: false,
|
|
7904
|
+
parseError: `Unknown schemaName: "${schemaName}". Supported: ${REGISTRY_SCHEMA_NAMES.join(", ")}`
|
|
7905
|
+
};
|
|
7906
|
+
}
|
|
7907
|
+
let parsed;
|
|
7908
|
+
try {
|
|
7909
|
+
parsed = yamlLoad5(yaml4);
|
|
7910
|
+
} catch (err) {
|
|
7911
|
+
return {
|
|
7912
|
+
valid: false,
|
|
7913
|
+
parseError: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`
|
|
7914
|
+
};
|
|
7915
|
+
}
|
|
7916
|
+
const ctx = input.projectRoot ? { projectRoot: input.projectRoot } : void 0;
|
|
7917
|
+
const resolvedSchema = getSchemaForEntry(entry, ctx);
|
|
7918
|
+
const result = resolvedSchema.safeParse(parsed);
|
|
7919
|
+
if (result.success) {
|
|
7920
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
7921
|
+
const ladder = resolveStatusLadder(projectRoot);
|
|
7922
|
+
if (ladder.length > 0) {
|
|
7923
|
+
const ladderSet = new Set(ladder);
|
|
7924
|
+
const tokenViolations = detectViolationsInYaml(parsed, ladder, ladderSet, "fragment");
|
|
7925
|
+
if (tokenViolations.length > 0) {
|
|
7926
|
+
const tokenErrors = tokenViolations.map((v) => ({
|
|
7927
|
+
path: v.path,
|
|
7928
|
+
message: v.message
|
|
7929
|
+
}));
|
|
7930
|
+
return { valid: false, errors: tokenErrors };
|
|
7931
|
+
}
|
|
7932
|
+
}
|
|
7933
|
+
return { valid: true };
|
|
7934
|
+
}
|
|
7935
|
+
const reverseSynonyms = buildReverseSynonyms(entry.synonyms ?? {});
|
|
7936
|
+
const errors = enrichErrors(result.error.issues, reverseSynonyms);
|
|
7937
|
+
return { valid: false, errors };
|
|
7938
|
+
}
|
|
7939
|
+
function registerValidateYamlFragmentTool(server2) {
|
|
7940
|
+
server2.tool(
|
|
7941
|
+
"stackwright_pro_validate_yaml_fragment",
|
|
7942
|
+
'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.',
|
|
7943
|
+
{
|
|
7944
|
+
schemaName: z19.string().describe(`Schema to validate against. Available: ${REGISTRY_SCHEMA_NAMES.join(", ")}`),
|
|
7945
|
+
yaml: z19.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
|
|
7946
|
+
},
|
|
7947
|
+
async ({ schemaName, yaml: yaml4 }) => {
|
|
7948
|
+
const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
|
|
7949
|
+
return {
|
|
7950
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
7951
|
+
};
|
|
7952
|
+
}
|
|
7953
|
+
);
|
|
7954
|
+
}
|
|
7955
|
+
|
|
7410
7956
|
// src/tools/pages.ts
|
|
7411
7957
|
import * as fs2 from "fs";
|
|
7412
7958
|
import * as os from "os";
|
|
7413
7959
|
import * as path3 from "path";
|
|
7414
|
-
import { z as
|
|
7415
|
-
import { load as
|
|
7960
|
+
import { z as z20 } from "zod";
|
|
7961
|
+
import { load as yamlLoad6, dump as yamlDump } from "js-yaml";
|
|
7416
7962
|
import { writePage, resolvePagesDir } from "@stackwright/cli";
|
|
7417
7963
|
import { isProContentType, isOssTypeWithProExtension } from "@stackwright-pro/types";
|
|
7418
7964
|
var BCP47_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
|
|
@@ -7466,7 +8012,7 @@ function handleProWritePage(input) {
|
|
|
7466
8012
|
}
|
|
7467
8013
|
let parsed;
|
|
7468
8014
|
try {
|
|
7469
|
-
parsed =
|
|
8015
|
+
parsed = yamlLoad6(content);
|
|
7470
8016
|
} catch (err) {
|
|
7471
8017
|
return {
|
|
7472
8018
|
isError: true,
|
|
@@ -7523,10 +8069,10 @@ function registerProPageTools(server2) {
|
|
|
7523
8069
|
"stackwright_pro_write_page",
|
|
7524
8070
|
"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.",
|
|
7525
8071
|
{
|
|
7526
|
-
projectRoot:
|
|
7527
|
-
slug:
|
|
7528
|
-
content:
|
|
7529
|
-
locale:
|
|
8072
|
+
projectRoot: z20.string().describe("Absolute path to the Stackwright project root"),
|
|
8073
|
+
slug: z20.string().describe('Page slug (e.g. "dashboard/main" or "/dashboard/main")'),
|
|
8074
|
+
content: z20.string().describe("Full YAML content for the page (content.yml contents)"),
|
|
8075
|
+
locale: z20.string().optional().describe(
|
|
7530
8076
|
'BCP 47 locale tag (e.g. "fr", "de"). If provided, writes content.<locale>.yml instead of content.yml.'
|
|
7531
8077
|
)
|
|
7532
8078
|
},
|
|
@@ -7538,16 +8084,16 @@ function registerProPageTools(server2) {
|
|
|
7538
8084
|
|
|
7539
8085
|
// src/tools/scaffold-cleanup.ts
|
|
7540
8086
|
import {
|
|
7541
|
-
existsSync as
|
|
8087
|
+
existsSync as existsSync16,
|
|
7542
8088
|
lstatSync as lstatSync9,
|
|
7543
8089
|
unlinkSync as unlinkSync2,
|
|
7544
|
-
readFileSync as
|
|
8090
|
+
readFileSync as readFileSync16,
|
|
7545
8091
|
writeFileSync as writeFileSync8,
|
|
7546
|
-
readdirSync as
|
|
8092
|
+
readdirSync as readdirSync10,
|
|
7547
8093
|
rmdirSync,
|
|
7548
8094
|
mkdirSync as mkdirSync8
|
|
7549
8095
|
} from "fs";
|
|
7550
|
-
import { join as
|
|
8096
|
+
import { join as join17, dirname as dirname3 } from "path";
|
|
7551
8097
|
var SCAFFOLD_FILES_TO_DELETE = [
|
|
7552
8098
|
"content/posts/getting-started.yaml",
|
|
7553
8099
|
"content/posts/hello-world.yaml"
|
|
@@ -7572,12 +8118,12 @@ var FALLBACK_COLORS = {
|
|
|
7572
8118
|
mutedForeground: "#737373"
|
|
7573
8119
|
};
|
|
7574
8120
|
function readThemeColors(cwd) {
|
|
7575
|
-
const tokenPath =
|
|
7576
|
-
if (!
|
|
8121
|
+
const tokenPath = join17(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
8122
|
+
if (!existsSync16(tokenPath)) return { ...FALLBACK_COLORS };
|
|
7577
8123
|
const stat = lstatSync9(tokenPath);
|
|
7578
8124
|
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
7579
8125
|
try {
|
|
7580
|
-
const raw = JSON.parse(
|
|
8126
|
+
const raw = JSON.parse(readFileSync16(tokenPath, "utf-8"));
|
|
7581
8127
|
const css = raw?.cssVariables ?? {};
|
|
7582
8128
|
const toHsl = (hslValue) => {
|
|
7583
8129
|
if (!hslValue || typeof hslValue !== "string") return null;
|
|
@@ -7637,15 +8183,15 @@ function generateNotFoundPage(colors) {
|
|
|
7637
8183
|
`;
|
|
7638
8184
|
}
|
|
7639
8185
|
function isSafePath(fullPath) {
|
|
7640
|
-
if (!
|
|
8186
|
+
if (!existsSync16(fullPath)) return true;
|
|
7641
8187
|
const stat = lstatSync9(fullPath);
|
|
7642
8188
|
return !stat.isSymbolicLink();
|
|
7643
8189
|
}
|
|
7644
8190
|
function isDirEmpty(dirPath) {
|
|
7645
|
-
if (!
|
|
8191
|
+
if (!existsSync16(dirPath)) return false;
|
|
7646
8192
|
const stat = lstatSync9(dirPath);
|
|
7647
8193
|
if (!stat.isDirectory()) return false;
|
|
7648
|
-
return
|
|
8194
|
+
return readdirSync10(dirPath).length === 0;
|
|
7649
8195
|
}
|
|
7650
8196
|
function handleCleanupScaffold(_cwd) {
|
|
7651
8197
|
const cwd = _cwd ?? process.cwd();
|
|
@@ -7658,8 +8204,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7658
8204
|
cleanupWarnings: []
|
|
7659
8205
|
};
|
|
7660
8206
|
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
7661
|
-
const fullPath =
|
|
7662
|
-
if (!
|
|
8207
|
+
const fullPath = join17(cwd, relPath);
|
|
8208
|
+
if (!existsSync16(fullPath)) {
|
|
7663
8209
|
result.skipped.push(relPath);
|
|
7664
8210
|
continue;
|
|
7665
8211
|
}
|
|
@@ -7676,13 +8222,13 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7676
8222
|
}
|
|
7677
8223
|
{
|
|
7678
8224
|
const relPath = "pages/getting-started/content.yml";
|
|
7679
|
-
const fullPath =
|
|
7680
|
-
if (!
|
|
8225
|
+
const fullPath = join17(cwd, relPath);
|
|
8226
|
+
if (!existsSync16(fullPath)) {
|
|
7681
8227
|
result.skipped.push(relPath);
|
|
7682
8228
|
} else if (!isSafePath(fullPath)) {
|
|
7683
8229
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7684
8230
|
} else {
|
|
7685
|
-
const content =
|
|
8231
|
+
const content = readFileSync16(fullPath, "utf-8");
|
|
7686
8232
|
const isScaffoldDefault = GETTING_STARTED_SCAFFOLD_FINGERPRINTS.some(
|
|
7687
8233
|
(marker) => content.includes(marker)
|
|
7688
8234
|
);
|
|
@@ -7702,16 +8248,16 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7702
8248
|
}
|
|
7703
8249
|
{
|
|
7704
8250
|
const relPath = `content/posts/${POSTS_COLLECTION_FILE}`;
|
|
7705
|
-
const fullPath =
|
|
7706
|
-
const postsDir =
|
|
7707
|
-
if (!
|
|
8251
|
+
const fullPath = join17(cwd, relPath);
|
|
8252
|
+
const postsDir = join17(cwd, "content/posts");
|
|
8253
|
+
if (!existsSync16(fullPath)) {
|
|
7708
8254
|
result.skipped.push(relPath);
|
|
7709
8255
|
} else if (!isSafePath(fullPath)) {
|
|
7710
8256
|
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
7711
|
-
} else if (!
|
|
8257
|
+
} else if (!existsSync16(postsDir)) {
|
|
7712
8258
|
result.skipped.push(relPath);
|
|
7713
8259
|
} else {
|
|
7714
|
-
const userPostFiles =
|
|
8260
|
+
const userPostFiles = readdirSync10(postsDir).filter(
|
|
7715
8261
|
(f) => f !== POSTS_COLLECTION_FILE && POST_FILE_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
7716
8262
|
);
|
|
7717
8263
|
if (userPostFiles.length === 0) {
|
|
@@ -7729,8 +8275,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7729
8275
|
}
|
|
7730
8276
|
}
|
|
7731
8277
|
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
7732
|
-
const fullDir =
|
|
7733
|
-
if (!
|
|
8278
|
+
const fullDir = join17(cwd, relDir);
|
|
8279
|
+
if (!existsSync16(fullDir)) continue;
|
|
7734
8280
|
if (!isSafePath(fullDir)) {
|
|
7735
8281
|
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
7736
8282
|
continue;
|
|
@@ -7745,8 +8291,8 @@ function handleCleanupScaffold(_cwd) {
|
|
|
7745
8291
|
}
|
|
7746
8292
|
}
|
|
7747
8293
|
{
|
|
7748
|
-
const fullPath =
|
|
7749
|
-
if (!
|
|
8294
|
+
const fullPath = join17(cwd, NOT_FOUND_PATH);
|
|
8295
|
+
if (!existsSync16(fullPath)) {
|
|
7750
8296
|
result.skipped.push(NOT_FOUND_PATH);
|
|
7751
8297
|
} else if (!isSafePath(fullPath)) {
|
|
7752
8298
|
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
@@ -7784,8 +8330,159 @@ function registerScaffoldCleanupTools(server2) {
|
|
|
7784
8330
|
);
|
|
7785
8331
|
}
|
|
7786
8332
|
|
|
8333
|
+
// src/tools/write-workflow-container-page.ts
|
|
8334
|
+
import { z as z21 } from "zod";
|
|
8335
|
+
import { dump as yamlDump2 } from "js-yaml";
|
|
8336
|
+
import { WorkflowWizardContentSchema as WorkflowWizardContentSchema2 } from "@stackwright-pro/types";
|
|
8337
|
+
var WriteWorkflowContainerPageInputSchema = z21.object({
|
|
8338
|
+
/**
|
|
8339
|
+
* Test-only: override process.cwd() for deterministic temp-dir testing.
|
|
8340
|
+
* Never set in production — omit entirely in normal MCP tool calls.
|
|
8341
|
+
*/
|
|
8342
|
+
_cwd: z21.string().optional(),
|
|
8343
|
+
/**
|
|
8344
|
+
* Kebab-case workflow ID — must match `workflow.id` in the referenced YAML.
|
|
8345
|
+
* Example: `patient-evacuation-authorization`
|
|
8346
|
+
*/
|
|
8347
|
+
workflowId: z21.string().regex(/^[a-z0-9-]+$/, {
|
|
8348
|
+
message: "workflowId must be kebab-case (lowercase alphanumeric + hyphens)"
|
|
8349
|
+
}),
|
|
8350
|
+
/**
|
|
8351
|
+
* Relative path to the workflow YAML file from project root.
|
|
8352
|
+
* Example: `workflows/patient-evacuation-authorization.yml`
|
|
8353
|
+
*/
|
|
8354
|
+
workflowFile: z21.string().min(1).regex(/^workflows\/[a-z0-9-]+\.(yml|yaml)$/, {
|
|
8355
|
+
message: "workflowFile must be: workflows/<id>.yml"
|
|
8356
|
+
}),
|
|
8357
|
+
/**
|
|
8358
|
+
* URL slug for the container page route.
|
|
8359
|
+
* Convention: `workflows/<workflowId>` → page at `/workflows/<workflowId>`.
|
|
8360
|
+
* Example: `workflows/patient-evacuation`
|
|
8361
|
+
*
|
|
8362
|
+
* The tool writes to `pages/<pageSlug>/content.yml`.
|
|
8363
|
+
*/
|
|
8364
|
+
pageSlug: z21.string().min(1).regex(/^workflows\/[a-z0-9-]+$/, {
|
|
8365
|
+
message: 'pageSlug must start with "workflows/" and be kebab-case. Example: workflows/patient-evacuation'
|
|
8366
|
+
}),
|
|
8367
|
+
/**
|
|
8368
|
+
* Human-readable label for the workflow (used in page title + breadcrumbs).
|
|
8369
|
+
* Example: `Patient Evacuation Authorization`
|
|
8370
|
+
*/
|
|
8371
|
+
label: z21.string().min(1).max(120),
|
|
8372
|
+
/**
|
|
8373
|
+
* Union of required roles across all steps in the workflow YAML.
|
|
8374
|
+
* Consumed by auth-reconcile to build protectedRoutes.
|
|
8375
|
+
* Must be non-empty — workflow pages are always auth-protected.
|
|
8376
|
+
*/
|
|
8377
|
+
requiredRoles: z21.array(z21.string().min(1)).min(1, {
|
|
8378
|
+
message: "requiredRoles must have at least one role \u2014 workflow pages are always auth-protected"
|
|
8379
|
+
}),
|
|
8380
|
+
/**
|
|
8381
|
+
* Persistence key for workflow state (localStorage slot).
|
|
8382
|
+
* Defaults to `workflow-<workflowId>` when omitted.
|
|
8383
|
+
*/
|
|
8384
|
+
persistenceKey: z21.string().optional(),
|
|
8385
|
+
/**
|
|
8386
|
+
* Layout mode for the container page. Defaults to `app-shell` per Pro convention.
|
|
8387
|
+
* Only override when you have a specific reason — `app-shell` is correct for all
|
|
8388
|
+
* workflow container pages in data-dense Pro apps.
|
|
8389
|
+
*/
|
|
8390
|
+
layoutMode: z21.enum(["app-shell", "page"]).default("app-shell")
|
|
8391
|
+
});
|
|
8392
|
+
function handleWriteWorkflowContainerPage(input) {
|
|
8393
|
+
const parsed = WriteWorkflowContainerPageInputSchema.safeParse(input);
|
|
8394
|
+
if (!parsed.success) {
|
|
8395
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
8396
|
+
return {
|
|
8397
|
+
text: JSON.stringify({ error: `Invalid input: ${issues}` }),
|
|
8398
|
+
isError: true
|
|
8399
|
+
};
|
|
8400
|
+
}
|
|
8401
|
+
const { workflowId, workflowFile, pageSlug, label, requiredRoles, persistenceKey, layoutMode } = parsed.data;
|
|
8402
|
+
const resolvedPersistenceKey = persistenceKey ?? `workflow-${workflowId}`;
|
|
8403
|
+
const wizardItem = {
|
|
8404
|
+
type: "workflow_wizard",
|
|
8405
|
+
label: workflowId,
|
|
8406
|
+
// slug-form label for the content item (page title comes from meta)
|
|
8407
|
+
workflowFile,
|
|
8408
|
+
workflowId,
|
|
8409
|
+
persistenceKey: resolvedPersistenceKey
|
|
8410
|
+
};
|
|
8411
|
+
const schemaCheck = WorkflowWizardContentSchema2.safeParse(wizardItem);
|
|
8412
|
+
if (!schemaCheck.success) {
|
|
8413
|
+
const issues = schemaCheck.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
8414
|
+
return {
|
|
8415
|
+
text: JSON.stringify({ error: `Internal schema validation failed: ${issues}` }),
|
|
8416
|
+
isError: true
|
|
8417
|
+
};
|
|
8418
|
+
}
|
|
8419
|
+
const pageContent = {
|
|
8420
|
+
layoutMode,
|
|
8421
|
+
meta: {
|
|
8422
|
+
title: `${label} | Workflow`,
|
|
8423
|
+
description: `${label} \u2014 multi-step workflow. Requires authorization.`,
|
|
8424
|
+
authRequired: true,
|
|
8425
|
+
requiredRoles
|
|
8426
|
+
},
|
|
8427
|
+
content: {
|
|
8428
|
+
content_items: [wizardItem]
|
|
8429
|
+
}
|
|
8430
|
+
};
|
|
8431
|
+
const yamlContent = yamlDump2(pageContent, {
|
|
8432
|
+
indent: 2,
|
|
8433
|
+
lineWidth: 120,
|
|
8434
|
+
quotingType: "'",
|
|
8435
|
+
forceQuotes: false
|
|
8436
|
+
});
|
|
8437
|
+
const filePath = `pages/${pageSlug}/content.yml`;
|
|
8438
|
+
const writeResult = handleSafeWrite({
|
|
8439
|
+
callerOtter: "stackwright-pro-form-wizard-otter",
|
|
8440
|
+
filePath,
|
|
8441
|
+
content: yamlContent,
|
|
8442
|
+
createDirectories: true,
|
|
8443
|
+
_cwd: parsed.data._cwd
|
|
8444
|
+
});
|
|
8445
|
+
if (writeResult.isError) {
|
|
8446
|
+
return writeResult;
|
|
8447
|
+
}
|
|
8448
|
+
const result = {
|
|
8449
|
+
written: true,
|
|
8450
|
+
path: filePath,
|
|
8451
|
+
contentTypesEmitted: ["workflow_wizard"],
|
|
8452
|
+
content: yamlContent
|
|
8453
|
+
};
|
|
8454
|
+
return {
|
|
8455
|
+
text: JSON.stringify(result),
|
|
8456
|
+
isError: false
|
|
8457
|
+
};
|
|
8458
|
+
}
|
|
8459
|
+
function registerWriteWorkflowContainerPageTool(server2) {
|
|
8460
|
+
server2.tool(
|
|
8461
|
+
"stackwright_pro_write_workflow_container_page",
|
|
8462
|
+
[
|
|
8463
|
+
"Create the canonical container page for a workflow at pages/workflows/<workflowId>/content.yml.",
|
|
8464
|
+
"",
|
|
8465
|
+
"ALWAYS call this immediately after writing the workflow YAML via stackwright_pro_safe_write.",
|
|
8466
|
+
"Without it, the route /workflows/<workflowId> 404s at runtime.",
|
|
8467
|
+
"",
|
|
8468
|
+
"The tool deterministically emits:",
|
|
8469
|
+
" - layoutMode: app-shell",
|
|
8470
|
+
" - meta.authRequired: true, meta.requiredRoles: <your input>",
|
|
8471
|
+
" - One workflow_wizard content item binding workflowFile + workflowId",
|
|
8472
|
+
"",
|
|
8473
|
+
"requiredRoles: supply the UNION of required roles from all steps in your workflow YAML.",
|
|
8474
|
+
'pageSlug: convention is "workflows/<workflowId>" \u2014 must start with "workflows/".'
|
|
8475
|
+
].join("\n"),
|
|
8476
|
+
WriteWorkflowContainerPageInputSchema.shape,
|
|
8477
|
+
async (args) => {
|
|
8478
|
+
const result = handleWriteWorkflowContainerPage(args);
|
|
8479
|
+
return { content: [{ type: "text", text: result.text }] };
|
|
8480
|
+
}
|
|
8481
|
+
);
|
|
8482
|
+
}
|
|
8483
|
+
|
|
7787
8484
|
// src/tools/contrast.ts
|
|
7788
|
-
import { z as
|
|
8485
|
+
import { z as z22 } from "zod";
|
|
7789
8486
|
function linearizeChannel(c255) {
|
|
7790
8487
|
const c = c255 / 255;
|
|
7791
8488
|
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
@@ -7957,8 +8654,8 @@ function registerContrastTools(server2) {
|
|
|
7957
8654
|
"stackwright_pro_check_contrast",
|
|
7958
8655
|
'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%").',
|
|
7959
8656
|
{
|
|
7960
|
-
fg:
|
|
7961
|
-
bg:
|
|
8657
|
+
fg: z22.string().describe("Foreground (text) color \u2014 hex or HSL"),
|
|
8658
|
+
bg: z22.string().describe("Background color \u2014 hex or HSL")
|
|
7962
8659
|
},
|
|
7963
8660
|
async ({ fg, bg }) => {
|
|
7964
8661
|
let result;
|
|
@@ -7988,8 +8685,8 @@ function registerContrastTools(server2) {
|
|
|
7988
8685
|
"stackwright_pro_derive_accessible_palette",
|
|
7989
8686
|
'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%").',
|
|
7990
8687
|
{
|
|
7991
|
-
seed:
|
|
7992
|
-
targetRatio: numCoerce(
|
|
8688
|
+
seed: z22.string().describe("Background (seed) color \u2014 hex or HSL"),
|
|
8689
|
+
targetRatio: numCoerce(z22.number().default(4.5)).describe(
|
|
7993
8690
|
"Minimum required contrast ratio (default 4.5 for WCAG AA)"
|
|
7994
8691
|
)
|
|
7995
8692
|
},
|
|
@@ -8024,7 +8721,7 @@ function registerContrastTools(server2) {
|
|
|
8024
8721
|
}
|
|
8025
8722
|
|
|
8026
8723
|
// src/tools/compile.ts
|
|
8027
|
-
import { z as
|
|
8724
|
+
import { z as z23 } from "zod";
|
|
8028
8725
|
import fs3 from "fs";
|
|
8029
8726
|
import path4 from "path";
|
|
8030
8727
|
import { compileCollections } from "@stackwright-pro/pulse/server";
|
|
@@ -8070,7 +8767,7 @@ async function tryOssCompile(fn, ctx, extra) {
|
|
|
8070
8767
|
}
|
|
8071
8768
|
}
|
|
8072
8769
|
function registerCompileTools(server2) {
|
|
8073
|
-
const projectRootArg =
|
|
8770
|
+
const projectRootArg = z23.string().optional().describe("Absolute path to project root (defaults to cwd)");
|
|
8074
8771
|
server2.tool(
|
|
8075
8772
|
"stackwright_pro_compile_collections",
|
|
8076
8773
|
"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.",
|
|
@@ -8246,7 +8943,7 @@ ${results.join("\n")}`;
|
|
|
8246
8943
|
"stackwright_pro_compile_page",
|
|
8247
8944
|
"Compile a single page by slug. Delegates to compilePage from @stackwright/build-scripts installed in the project.",
|
|
8248
8945
|
{
|
|
8249
|
-
slug:
|
|
8946
|
+
slug: z23.string().describe('Page slug (relative path under pages/, e.g. "dashboard")'),
|
|
8250
8947
|
projectRoot: projectRootArg
|
|
8251
8948
|
},
|
|
8252
8949
|
async ({ slug, projectRoot }) => {
|
|
@@ -8266,15 +8963,15 @@ ${results.join("\n")}`;
|
|
|
8266
8963
|
}
|
|
8267
8964
|
|
|
8268
8965
|
// src/tools/strip-legacy-integrations.ts
|
|
8269
|
-
import { z as
|
|
8270
|
-
import { existsSync as
|
|
8271
|
-
import { join as
|
|
8966
|
+
import { z as z24 } from "zod";
|
|
8967
|
+
import { existsSync as existsSync17, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
|
|
8968
|
+
import { join as join18 } from "path";
|
|
8272
8969
|
function handleStripLegacyIntegrations(input) {
|
|
8273
8970
|
const { projectRoot } = input;
|
|
8274
|
-
const ymlPath =
|
|
8275
|
-
const yamlPath =
|
|
8276
|
-
if (!
|
|
8277
|
-
if (
|
|
8971
|
+
const ymlPath = join18(projectRoot, "stackwright.yml");
|
|
8972
|
+
const yamlPath = join18(projectRoot, "stackwright.yaml");
|
|
8973
|
+
if (!existsSync17(ymlPath)) {
|
|
8974
|
+
if (existsSync17(yamlPath)) {
|
|
8278
8975
|
return {
|
|
8279
8976
|
changed: false,
|
|
8280
8977
|
removedEntries: 0,
|
|
@@ -8287,7 +8984,7 @@ function handleStripLegacyIntegrations(input) {
|
|
|
8287
8984
|
message: "stackwright.yml not found \u2014 no-op."
|
|
8288
8985
|
};
|
|
8289
8986
|
}
|
|
8290
|
-
const raw =
|
|
8987
|
+
const raw = readFileSync17(ymlPath, "utf-8");
|
|
8291
8988
|
const isCRLF = raw.includes("\r\n");
|
|
8292
8989
|
const content = isCRLF ? raw.replace(/\r\n/g, "\n") : raw;
|
|
8293
8990
|
const lines = content.split("\n");
|
|
@@ -8329,7 +9026,7 @@ function registerStripLegacyIntegrationsTool(server2) {
|
|
|
8329
9026
|
"stackwright_pro_strip_legacy_integrations",
|
|
8330
9027
|
"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.",
|
|
8331
9028
|
{
|
|
8332
|
-
projectRoot:
|
|
9029
|
+
projectRoot: z24.string().describe("Absolute path to the project root directory")
|
|
8333
9030
|
},
|
|
8334
9031
|
async ({ projectRoot }) => {
|
|
8335
9032
|
const result = handleStripLegacyIntegrations({ projectRoot });
|
|
@@ -8341,16 +9038,16 @@ function registerStripLegacyIntegrationsTool(server2) {
|
|
|
8341
9038
|
}
|
|
8342
9039
|
|
|
8343
9040
|
// src/tools/emitters.ts
|
|
8344
|
-
import { z as
|
|
8345
|
-
import { readFileSync as
|
|
8346
|
-
import { join as
|
|
9041
|
+
import { z as z25 } from "zod";
|
|
9042
|
+
import { readFileSync as readFileSync18, existsSync as existsSync18, readdirSync as readdirSync11 } from "fs";
|
|
9043
|
+
import { join as join19 } from "path";
|
|
8347
9044
|
import yaml from "js-yaml";
|
|
8348
9045
|
import { emitEnvLocal, emitProvidersFile, emitMockPorts } from "@stackwright-pro/emitters";
|
|
8349
9046
|
function readIntegrations(cwd) {
|
|
8350
|
-
const filePath =
|
|
8351
|
-
if (!
|
|
9047
|
+
const filePath = join19(cwd, "stackwright.integrations.yml");
|
|
9048
|
+
if (!existsSync18(filePath)) return [];
|
|
8352
9049
|
try {
|
|
8353
|
-
const raw = yaml.load(
|
|
9050
|
+
const raw = yaml.load(readFileSync18(filePath, "utf-8"));
|
|
8354
9051
|
if (!raw?.integrations || !Array.isArray(raw.integrations)) return [];
|
|
8355
9052
|
return raw.integrations.filter((i) => typeof i.name === "string").map((i) => ({
|
|
8356
9053
|
name: i.name,
|
|
@@ -8361,15 +9058,15 @@ function readIntegrations(cwd) {
|
|
|
8361
9058
|
}
|
|
8362
9059
|
}
|
|
8363
9060
|
function readServiceTokenRefs(cwd) {
|
|
8364
|
-
const servicesDir =
|
|
8365
|
-
if (!
|
|
9061
|
+
const servicesDir = join19(cwd, "services");
|
|
9062
|
+
if (!existsSync18(servicesDir)) return [];
|
|
8366
9063
|
try {
|
|
8367
|
-
const files =
|
|
9064
|
+
const files = readdirSync11(servicesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
8368
9065
|
const refs = [];
|
|
8369
9066
|
for (const file of files) {
|
|
8370
9067
|
try {
|
|
8371
9068
|
const raw = yaml.load(
|
|
8372
|
-
|
|
9069
|
+
readFileSync18(join19(servicesDir, file), "utf-8")
|
|
8373
9070
|
);
|
|
8374
9071
|
if (raw?.tokenEnv && typeof raw.tokenEnv === "string") {
|
|
8375
9072
|
refs.push({
|
|
@@ -8400,7 +9097,7 @@ Call this tool, then write both files with stackwright_pro_safe_write:
|
|
|
8400
9097
|
|
|
8401
9098
|
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}`,
|
|
8402
9099
|
{
|
|
8403
|
-
cwd:
|
|
9100
|
+
cwd: z25.string().describe(
|
|
8404
9101
|
"Absolute path to the project root containing stackwright.integrations.yml and optionally services/*.yml"
|
|
8405
9102
|
)
|
|
8406
9103
|
},
|
|
@@ -8447,8 +9144,8 @@ mapProvider options:
|
|
|
8447
9144
|
|
|
8448
9145
|
This is the second concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-57ku, swp-2m89, swp-zf9y, drawer 957. ${PROVIDERS_DESC}`,
|
|
8449
9146
|
{
|
|
8450
|
-
cwd:
|
|
8451
|
-
mapProvider:
|
|
9147
|
+
cwd: z25.string().describe("Absolute path to the project root containing stackwright.integrations.yml"),
|
|
9148
|
+
mapProvider: z25.enum(["maplibre", "cesium", "none"]).optional().describe(
|
|
8452
9149
|
"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."
|
|
8453
9150
|
)
|
|
8454
9151
|
},
|
|
@@ -8489,7 +9186,7 @@ Workflow:
|
|
|
8489
9186
|
|
|
8490
9187
|
This is the third concrete instance of the swp-zf9y deterministic emitter pattern. Refs: swp-phkh, swp-zf9y. ${PORTS_DESC}`,
|
|
8491
9188
|
{
|
|
8492
|
-
cwd:
|
|
9189
|
+
cwd: z25.string().describe("Absolute path to the project root containing stackwright.integrations.yml")
|
|
8493
9190
|
},
|
|
8494
9191
|
async ({ cwd }) => {
|
|
8495
9192
|
const integrations = readIntegrations(cwd);
|
|
@@ -8518,9 +9215,9 @@ This is the third concrete instance of the swp-zf9y deterministic emitter patter
|
|
|
8518
9215
|
}
|
|
8519
9216
|
|
|
8520
9217
|
// src/tools/list-specs.ts
|
|
8521
|
-
import { z as
|
|
8522
|
-
import { readdirSync as
|
|
8523
|
-
import { join as
|
|
9218
|
+
import { z as z26 } from "zod";
|
|
9219
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync19, statSync as statSync2, existsSync as existsSync19 } from "fs";
|
|
9220
|
+
import { join as join20, extname, basename as basename2 } from "path";
|
|
8524
9221
|
var STRIP_SUFFIXES = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
8525
9222
|
var ALLOWED_EXTS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
8526
9223
|
function deriveIntegrationName(fileName) {
|
|
@@ -8542,18 +9239,18 @@ function detectFormat(snippet) {
|
|
|
8542
9239
|
}
|
|
8543
9240
|
function handleListSpecs(input) {
|
|
8544
9241
|
const { projectRoot } = input;
|
|
8545
|
-
const specsDir =
|
|
9242
|
+
const specsDir = join20(projectRoot, "specs");
|
|
8546
9243
|
const empty = { projectRoot, specs: [], totalCount: 0 };
|
|
8547
|
-
if (!
|
|
9244
|
+
if (!existsSync19(specsDir)) return empty;
|
|
8548
9245
|
let entries;
|
|
8549
9246
|
try {
|
|
8550
|
-
entries =
|
|
9247
|
+
entries = readdirSync12(specsDir);
|
|
8551
9248
|
} catch {
|
|
8552
9249
|
return empty;
|
|
8553
9250
|
}
|
|
8554
9251
|
const specs = [];
|
|
8555
9252
|
for (const entry of entries) {
|
|
8556
|
-
const fullPath =
|
|
9253
|
+
const fullPath = join20(specsDir, entry);
|
|
8557
9254
|
let stat;
|
|
8558
9255
|
try {
|
|
8559
9256
|
stat = statSync2(fullPath);
|
|
@@ -8564,7 +9261,7 @@ function handleListSpecs(input) {
|
|
|
8564
9261
|
if (!ALLOWED_EXTS.has(extname(entry).toLowerCase())) continue;
|
|
8565
9262
|
let snippet = "";
|
|
8566
9263
|
try {
|
|
8567
|
-
snippet =
|
|
9264
|
+
snippet = readFileSync19(fullPath, "utf-8").slice(0, 2048);
|
|
8568
9265
|
} catch {
|
|
8569
9266
|
}
|
|
8570
9267
|
specs.push({
|
|
@@ -8583,7 +9280,7 @@ function registerListSpecsTool(server2) {
|
|
|
8583
9280
|
"stackwright_pro_list_specs",
|
|
8584
9281
|
"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.",
|
|
8585
9282
|
{
|
|
8586
|
-
projectRoot:
|
|
9283
|
+
projectRoot: z26.string().describe("Absolute path to the project root directory")
|
|
8587
9284
|
},
|
|
8588
9285
|
async ({ projectRoot }) => {
|
|
8589
9286
|
const result = handleListSpecs({ projectRoot });
|
|
@@ -8595,8 +9292,8 @@ function registerListSpecsTool(server2) {
|
|
|
8595
9292
|
}
|
|
8596
9293
|
|
|
8597
9294
|
// src/tools/describe-endpoint.ts
|
|
8598
|
-
import { z as
|
|
8599
|
-
import { readFileSync as
|
|
9295
|
+
import { z as z27 } from "zod";
|
|
9296
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
8600
9297
|
import { extname as extname2 } from "path";
|
|
8601
9298
|
import yaml2 from "js-yaml";
|
|
8602
9299
|
function resolveRef(root, ref) {
|
|
@@ -8629,7 +9326,7 @@ function deref(root, obj) {
|
|
|
8629
9326
|
return o;
|
|
8630
9327
|
}
|
|
8631
9328
|
function loadSpec(specPath) {
|
|
8632
|
-
const raw =
|
|
9329
|
+
const raw = readFileSync20(specPath, "utf-8");
|
|
8633
9330
|
const ext = extname2(specPath).toLowerCase();
|
|
8634
9331
|
if (ext === ".json") {
|
|
8635
9332
|
return JSON.parse(raw);
|
|
@@ -8742,9 +9439,9 @@ function registerDescribeEndpointTool(server2) {
|
|
|
8742
9439
|
"from required query params and resolved auth query params (swp-8r8i + swp-1l6l)."
|
|
8743
9440
|
].join("\n"),
|
|
8744
9441
|
{
|
|
8745
|
-
specPath:
|
|
8746
|
-
path:
|
|
8747
|
-
method:
|
|
9442
|
+
specPath: z27.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
|
|
9443
|
+
path: z27.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
|
|
9444
|
+
method: z27.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
|
|
8748
9445
|
},
|
|
8749
9446
|
async ({ specPath, path: path5, method }) => {
|
|
8750
9447
|
try {
|
|
@@ -8764,10 +9461,10 @@ function registerDescribeEndpointTool(server2) {
|
|
|
8764
9461
|
}
|
|
8765
9462
|
|
|
8766
9463
|
// src/tools/consolidate-integrations.ts
|
|
8767
|
-
import { z as
|
|
8768
|
-
import { readdirSync as
|
|
9464
|
+
import { z as z28 } from "zod";
|
|
9465
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync10, existsSync as existsSync20, mkdirSync as mkdirSync9 } from "fs";
|
|
8769
9466
|
import { lockSync as lockSync2 } from "proper-lockfile";
|
|
8770
|
-
import { join as
|
|
9467
|
+
import { join as join21, basename as basename3 } from "path";
|
|
8771
9468
|
import yaml3 from "js-yaml";
|
|
8772
9469
|
var BASE_MOCK_PORT = 4011;
|
|
8773
9470
|
var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
|
|
@@ -8780,13 +9477,13 @@ function deriveNameFromFilename(filename) {
|
|
|
8780
9477
|
}
|
|
8781
9478
|
function handleConsolidateIntegrations(input) {
|
|
8782
9479
|
const { projectRoot } = input;
|
|
8783
|
-
const artifactsDir =
|
|
8784
|
-
if (!
|
|
9480
|
+
const artifactsDir = join21(projectRoot, ".stackwright", "artifacts");
|
|
9481
|
+
if (!existsSync20(artifactsDir)) {
|
|
8785
9482
|
return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
|
|
8786
9483
|
}
|
|
8787
9484
|
let entries;
|
|
8788
9485
|
try {
|
|
8789
|
-
entries =
|
|
9486
|
+
entries = readdirSync13(artifactsDir).filter(
|
|
8790
9487
|
(f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
|
|
8791
9488
|
);
|
|
8792
9489
|
} catch {
|
|
@@ -8801,7 +9498,7 @@ function handleConsolidateIntegrations(input) {
|
|
|
8801
9498
|
entries.forEach((filename, index) => {
|
|
8802
9499
|
let artifact = {};
|
|
8803
9500
|
try {
|
|
8804
|
-
const raw =
|
|
9501
|
+
const raw = readFileSync21(join21(artifactsDir, filename), "utf-8");
|
|
8805
9502
|
artifact = JSON.parse(raw);
|
|
8806
9503
|
} catch {
|
|
8807
9504
|
}
|
|
@@ -8834,19 +9531,19 @@ function handleConsolidateIntegrations(input) {
|
|
|
8834
9531
|
});
|
|
8835
9532
|
const integrations = Array.from(integrationsByName.values());
|
|
8836
9533
|
const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
|
|
8837
|
-
const integrationsYmlPath =
|
|
9534
|
+
const integrationsYmlPath = join21(projectRoot, "stackwright.integrations.yml");
|
|
8838
9535
|
let existing = {};
|
|
8839
|
-
if (
|
|
9536
|
+
if (existsSync20(integrationsYmlPath)) {
|
|
8840
9537
|
try {
|
|
8841
|
-
const raw =
|
|
9538
|
+
const raw = readFileSync21(integrationsYmlPath, "utf-8");
|
|
8842
9539
|
existing = yaml3.load(raw) ?? {};
|
|
8843
9540
|
} catch {
|
|
8844
9541
|
existing = {};
|
|
8845
9542
|
}
|
|
8846
9543
|
}
|
|
8847
9544
|
const merged = { ...existing, integrations };
|
|
8848
|
-
mkdirSync9(
|
|
8849
|
-
if (!
|
|
9545
|
+
mkdirSync9(join21(projectRoot), { recursive: true });
|
|
9546
|
+
if (!existsSync20(integrationsYmlPath)) {
|
|
8850
9547
|
writeFileSync10(integrationsYmlPath, "", "utf-8");
|
|
8851
9548
|
}
|
|
8852
9549
|
const release = lockSync2(integrationsYmlPath, {
|
|
@@ -8885,7 +9582,7 @@ function registerConsolidateIntegrationsTool(server2) {
|
|
|
8885
9582
|
"no per-spec artifacts are found."
|
|
8886
9583
|
].join(" "),
|
|
8887
9584
|
{
|
|
8888
|
-
projectRoot:
|
|
9585
|
+
projectRoot: z28.string().describe("Absolute path to the project root directory")
|
|
8889
9586
|
},
|
|
8890
9587
|
async ({ projectRoot }) => {
|
|
8891
9588
|
try {
|
|
@@ -8943,7 +9640,7 @@ var package_default = {
|
|
|
8943
9640
|
"test:coverage": "vitest run --coverage"
|
|
8944
9641
|
},
|
|
8945
9642
|
name: "@stackwright-pro/mcp",
|
|
8946
|
-
version: "0.2.0-alpha.
|
|
9643
|
+
version: "0.2.0-alpha.115",
|
|
8947
9644
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
8948
9645
|
license: "SEE LICENSE IN LICENSE",
|
|
8949
9646
|
main: "./dist/server.js",
|
|
@@ -9021,6 +9718,7 @@ registerValidateYamlFragmentTool(server);
|
|
|
9021
9718
|
registerProPageTools(server);
|
|
9022
9719
|
registerGetSchemaTool(server);
|
|
9023
9720
|
registerScaffoldCleanupTools(server);
|
|
9721
|
+
registerWriteWorkflowContainerPageTool(server);
|
|
9024
9722
|
registerContrastTools(server);
|
|
9025
9723
|
registerCompileTools(server);
|
|
9026
9724
|
registerStripLegacyIntegrationsTool(server);
|