@salesforce/b2c-dx-mcp 0.4.4 → 0.4.6
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/README.md +82 -370
- package/content/pwav3/components.md +400 -0
- package/content/pwav3/config.md +124 -0
- package/content/pwav3/data-fetching.md +213 -0
- package/content/pwav3/extensibility.md +167 -0
- package/content/pwav3/i18n.md +214 -0
- package/content/pwav3/quick-reference.md +169 -0
- package/content/pwav3/routing.md +107 -0
- package/content/pwav3/state-management.md +193 -0
- package/content/pwav3/styling.md +248 -0
- package/content/pwav3/testing.md +124 -0
- package/content/site-theming/theming-accessibility.md +126 -0
- package/content/site-theming/theming-questions.md +208 -0
- package/content/site-theming/theming-validation.md +174 -0
- package/dist/commands/mcp.d.ts +3 -3
- package/dist/commands/mcp.js +7 -7
- package/dist/registry.js +1 -1
- package/dist/services.d.ts +15 -15
- package/dist/services.js +21 -14
- package/dist/tools/adapter.d.ts +2 -2
- package/dist/tools/adapter.js +2 -2
- package/dist/tools/cartridges/index.js +1 -6
- package/dist/tools/index.d.ts +1 -4
- package/dist/tools/index.js +1 -4
- package/dist/tools/mrt/index.js +4 -9
- package/dist/tools/pwav3/index.d.ts +12 -3
- package/dist/tools/pwav3/index.js +5 -63
- package/dist/tools/pwav3/pwa-kit-development-guidelines.d.ts +9 -0
- package/dist/tools/pwav3/pwa-kit-development-guidelines.js +151 -0
- package/dist/tools/scapi/index.d.ts +1 -1
- package/dist/tools/scapi/index.js +6 -1
- package/dist/tools/scapi/scapi-custom-api-scaffold.d.ts +60 -0
- package/dist/tools/scapi/scapi-custom-api-scaffold.js +175 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.d.ts +24 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/figma-url-parser.js +53 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.d.ts +42 -0
- package/dist/tools/storefrontnext/figma/figma-to-component/index.js +325 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.d.ts +40 -0
- package/dist/tools/storefrontnext/figma/generate-component/decision.js +312 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.d.ts +9 -0
- package/dist/tools/storefrontnext/figma/generate-component/formatter.js +92 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.d.ts +114 -0
- package/dist/tools/storefrontnext/figma/generate-component/index.js +98 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.d.ts +71 -0
- package/dist/tools/storefrontnext/figma/map-tokens/css-parser.js +260 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.d.ts +61 -0
- package/dist/tools/storefrontnext/figma/map-tokens/index.js +234 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.d.ts +65 -0
- package/dist/tools/storefrontnext/figma/map-tokens/token-matcher.js +268 -0
- package/dist/tools/storefrontnext/index.d.ts +17 -0
- package/dist/tools/storefrontnext/index.js +10 -60
- package/dist/tools/storefrontnext/page-designer-decorator/analyzer.js +15 -0
- package/dist/tools/storefrontnext/page-designer-decorator/index.js +3 -3
- package/dist/tools/storefrontnext/{developer-guidelines.js → sfnext-development-guidelines.js} +3 -3
- package/dist/tools/storefrontnext/site-theming/color-contrast.d.ts +92 -0
- package/dist/tools/storefrontnext/site-theming/color-contrast.js +186 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/color-mapping.js +131 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.d.ts +11 -0
- package/dist/tools/storefrontnext/site-theming/guidance-merger.js +78 -0
- package/dist/tools/storefrontnext/site-theming/index.d.ts +14 -0
- package/dist/tools/storefrontnext/site-theming/index.js +122 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.d.ts +16 -0
- package/dist/tools/storefrontnext/site-theming/response-builder.js +316 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.d.ts +62 -0
- package/dist/tools/storefrontnext/site-theming/theming-store.js +410 -0
- package/dist/tools/storefrontnext/site-theming/types.d.ts +35 -0
- package/dist/tools/storefrontnext/site-theming/types.js +7 -0
- package/oclif.manifest.json +8 -5
- package/package.json +9 -6
- /package/content/{auth.md → sfnext/auth.md} +0 -0
- /package/content/{components.md → sfnext/components.md} +0 -0
- /package/content/{config.md → sfnext/config.md} +0 -0
- /package/content/{data-fetching.md → sfnext/data-fetching.md} +0 -0
- /package/content/{extensions.md → sfnext/extensions.md} +0 -0
- /package/content/{i18n.md → sfnext/i18n.md} +0 -0
- /package/content/{page-designer.md → sfnext/page-designer.md} +0 -0
- /package/content/{performance.md → sfnext/performance.md} +0 -0
- /package/content/{pitfalls.md → sfnext/pitfalls.md} +0 -0
- /package/content/{quick-reference.md → sfnext/quick-reference.md} +0 -0
- /package/content/{state-management.md → sfnext/state-management.md} +0 -0
- /package/content/{styling.md → sfnext/styling.md} +0 -0
- /package/content/{testing.md → sfnext/testing.md} +0 -0
- /package/dist/tools/storefrontnext/{developer-guidelines.d.ts → sfnext-development-guidelines.d.ts} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
function mergeWorkflows(guidanceArray) {
|
|
7
|
+
const workflows = guidanceArray.filter((g) => g.workflow);
|
|
8
|
+
if (workflows.length === 0)
|
|
9
|
+
return undefined;
|
|
10
|
+
const merged = {
|
|
11
|
+
steps: [],
|
|
12
|
+
extractionInstructions: workflows[0].workflow?.extractionInstructions,
|
|
13
|
+
preImplementationChecklist: workflows[0].workflow?.preImplementationChecklist,
|
|
14
|
+
};
|
|
15
|
+
for (const g of workflows) {
|
|
16
|
+
if (g.workflow?.steps)
|
|
17
|
+
merged.steps.push(...g.workflow.steps);
|
|
18
|
+
if (!merged.extractionInstructions && g.workflow?.extractionInstructions) {
|
|
19
|
+
merged.extractionInstructions = g.workflow.extractionInstructions;
|
|
20
|
+
}
|
|
21
|
+
if (!merged.preImplementationChecklist && g.workflow?.preImplementationChecklist) {
|
|
22
|
+
merged.preImplementationChecklist = g.workflow.preImplementationChecklist;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return merged;
|
|
26
|
+
}
|
|
27
|
+
function mergeValidations(guidanceArray) {
|
|
28
|
+
const validations = guidanceArray.filter((g) => g.validation);
|
|
29
|
+
if (validations.length === 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
const joinField = (field) => validations
|
|
32
|
+
.map((g) => g.validation?.[field])
|
|
33
|
+
.filter((x) => typeof x === 'string')
|
|
34
|
+
.join('\n\n');
|
|
35
|
+
return {
|
|
36
|
+
colorValidation: joinField('colorValidation'),
|
|
37
|
+
fontValidation: joinField('fontValidation'),
|
|
38
|
+
generalValidation: joinField('generalValidation'),
|
|
39
|
+
requirements: joinField('requirements'),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function buildQuestionMap(guidanceArray) {
|
|
43
|
+
const questionMap = new Map();
|
|
44
|
+
for (const guidance of guidanceArray) {
|
|
45
|
+
for (const q of guidance.questions) {
|
|
46
|
+
if (!questionMap.has(q.id))
|
|
47
|
+
questionMap.set(q.id, q);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return questionMap;
|
|
51
|
+
}
|
|
52
|
+
function buildMergedMetadata(guidanceArray) {
|
|
53
|
+
return {
|
|
54
|
+
filePath: guidanceArray.map((g) => g.metadata.filePath).join(', '),
|
|
55
|
+
fileName: guidanceArray.map((g) => g.metadata.fileName).join(', '),
|
|
56
|
+
loadedAt: new Date(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Merges multiple ThemingGuidance objects into one.
|
|
61
|
+
* Questions are deduplicated by ID; guidelines, rules, workflows, and validations are combined.
|
|
62
|
+
*/
|
|
63
|
+
export function mergeGuidance(guidanceArray) {
|
|
64
|
+
if (guidanceArray.length === 0)
|
|
65
|
+
throw new Error('Cannot merge empty guidance array');
|
|
66
|
+
if (guidanceArray.length === 1)
|
|
67
|
+
return guidanceArray[0];
|
|
68
|
+
const questionMap = buildQuestionMap(guidanceArray);
|
|
69
|
+
return {
|
|
70
|
+
questions: [...questionMap.values()],
|
|
71
|
+
guidelines: guidanceArray.flatMap((g) => g.guidelines),
|
|
72
|
+
rules: guidanceArray.flatMap((g) => g.rules),
|
|
73
|
+
metadata: buildMergedMetadata(guidanceArray),
|
|
74
|
+
workflow: mergeWorkflows(guidanceArray),
|
|
75
|
+
validation: mergeValidations(guidanceArray),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=guidance-merger.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { McpTool } from '../../../utils/index.js';
|
|
2
|
+
import type { Services } from '../../../services.js';
|
|
3
|
+
export type { ColorEntry, ColorMapping, CollectedAnswers, ConversationContext, FontEntry, SiteThemingInput, } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates the site theming MCP tool for Storefront Next.
|
|
6
|
+
*
|
|
7
|
+
* The tool guides theming changes (colors, fonts, visual styling) and validates color
|
|
8
|
+
* combinations for WCAG accessibility. It must be called before implementing any
|
|
9
|
+
* theming changes.
|
|
10
|
+
*
|
|
11
|
+
* @param loadServices - Function that loads configuration and returns Services instance
|
|
12
|
+
* @returns The configured MCP tool
|
|
13
|
+
*/
|
|
14
|
+
export declare function createSiteThemingTool(loadServices: () => Services): McpTool;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Site Theming tool for Storefront Next.
|
|
8
|
+
*
|
|
9
|
+
* Provides theming guidelines, guided questions, and automatic WCAG color contrast
|
|
10
|
+
* validation. Call this tool first when users request brand colors or theme changes.
|
|
11
|
+
*
|
|
12
|
+
* @module tools/storefrontnext/site-theming
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { createToolAdapter, textResult, errorResult } from '../../adapter.js';
|
|
16
|
+
import { siteThemingStore } from './theming-store.js';
|
|
17
|
+
import { mergeGuidance } from './guidance-merger.js';
|
|
18
|
+
import { generateResponse } from './response-builder.js';
|
|
19
|
+
/**
|
|
20
|
+
* Creates the site theming MCP tool for Storefront Next.
|
|
21
|
+
*
|
|
22
|
+
* The tool guides theming changes (colors, fonts, visual styling) and validates color
|
|
23
|
+
* combinations for WCAG accessibility. It must be called before implementing any
|
|
24
|
+
* theming changes.
|
|
25
|
+
*
|
|
26
|
+
* @param loadServices - Function that loads configuration and returns Services instance
|
|
27
|
+
* @returns The configured MCP tool
|
|
28
|
+
*/
|
|
29
|
+
export function createSiteThemingTool(loadServices) {
|
|
30
|
+
return createToolAdapter({
|
|
31
|
+
name: 'storefront_next_site_theming',
|
|
32
|
+
description: '⚠️ MANDATORY: Call this tool FIRST before implementing any theming changes. ' +
|
|
33
|
+
'Provides theming guidelines, questions, and automatic validation. ' +
|
|
34
|
+
'CRITICAL RULES: Call immediately when user requests theming (even if colors/fonts provided). ' +
|
|
35
|
+
'NEVER implement without calling this tool first. NEVER skip question-answer workflow. ' +
|
|
36
|
+
'MUST ask questions and WAIT for responses. ' +
|
|
37
|
+
'VALIDATION GATE: After collecting answers, call tool again with colorMapping to trigger validation. ' +
|
|
38
|
+
'DEFAULT FILES: theming-questions, theming-validation, theming-accessibility. ' +
|
|
39
|
+
'Use fileKeys to add custom files. ' +
|
|
40
|
+
'WORKFLOW: Call tool → Ask questions → Call with colorMapping (validation) → Present findings → Wait confirmation → Implement',
|
|
41
|
+
toolsets: ['STOREFRONTNEXT'],
|
|
42
|
+
isGA: false,
|
|
43
|
+
requiresInstance: false,
|
|
44
|
+
inputSchema: {
|
|
45
|
+
fileKeys: z
|
|
46
|
+
.array(z.string())
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Array of file keys to add to the default set. If provided, guidance from all specified files will be merged with defaults: theming-questions, theming-validation, theming-accessibility. Available keys can be listed by calling the tool without parameters.'),
|
|
49
|
+
conversationContext: z
|
|
50
|
+
.object({
|
|
51
|
+
currentStep: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Current step in the theming conversation (e.g., "collecting-colors", "collecting-fonts")'),
|
|
55
|
+
collectedAnswers: z
|
|
56
|
+
.record(z.string(), z.any())
|
|
57
|
+
.optional()
|
|
58
|
+
.describe('Previously collected answers from the user'),
|
|
59
|
+
questionsAsked: z.array(z.string()).optional().describe('List of questions that have already been asked'),
|
|
60
|
+
})
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Context from previous conversation rounds'),
|
|
63
|
+
},
|
|
64
|
+
async execute(args, context) {
|
|
65
|
+
siteThemingStore.initialize(context.services.resolveWithProjectDirectory());
|
|
66
|
+
const defaultFileKeys = ['theming-questions', 'theming-validation', 'theming-accessibility'];
|
|
67
|
+
let fileKeys;
|
|
68
|
+
if (args.fileKeys && args.fileKeys.length > 0) {
|
|
69
|
+
const allKeys = [...defaultFileKeys, ...args.fileKeys];
|
|
70
|
+
fileKeys = [...new Set(allKeys)];
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
fileKeys = defaultFileKeys;
|
|
74
|
+
}
|
|
75
|
+
const hasContext = args.conversationContext &&
|
|
76
|
+
(args.conversationContext.collectedAnswers ||
|
|
77
|
+
args.conversationContext.questionsAsked ||
|
|
78
|
+
args.conversationContext.currentStep);
|
|
79
|
+
if (!args.fileKeys && !hasContext) {
|
|
80
|
+
const availableKeys = siteThemingStore.getKeys();
|
|
81
|
+
if (availableKeys.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
text: 'No theming files have been loaded. Please ensure theming files are configured at server startup.',
|
|
84
|
+
isError: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
text: `Available theming files:\n\n${availableKeys.map((key) => `- ${key}`).join('\n')}\n\nDefault files (always used): theming-questions, theming-validation, theming-accessibility\n\nUse the \`fileKeys\` parameter to add additional files. User-provided files are merged with the defaults.`,
|
|
89
|
+
isError: false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const guidanceArray = [];
|
|
93
|
+
const missingKeys = [];
|
|
94
|
+
for (const key of fileKeys) {
|
|
95
|
+
const guidance = siteThemingStore.get(key);
|
|
96
|
+
if (guidance) {
|
|
97
|
+
guidanceArray.push(guidance);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
missingKeys.push(key);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (guidanceArray.length === 0 || missingKeys.length > 0) {
|
|
104
|
+
const availableKeys = siteThemingStore.getKeys();
|
|
105
|
+
const keysList = fileKeys.length === 1 ? `key "${fileKeys[0]}"` : `keys: ${fileKeys.join(', ')}`;
|
|
106
|
+
const missingList = missingKeys.length === 1 ? `"${missingKeys[0]}"` : missingKeys.join(', ');
|
|
107
|
+
return {
|
|
108
|
+
text: `Theming file(s) with ${keysList} not found.\n\nMissing: ${missingList}\nAvailable keys: ${availableKeys.join(', ')}\n\nFiles are loaded at server startup. To add more files, configure them via the THEMING_FILES environment variable or update the server initialization.`,
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const guidance = fileKeys.length > 1 ? mergeGuidance(guidanceArray) : guidanceArray[0];
|
|
113
|
+
const response = generateResponse(guidance, args.conversationContext);
|
|
114
|
+
return {
|
|
115
|
+
text: response,
|
|
116
|
+
isError: false,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
formatOutput: (output) => output.isError ? errorResult(output.text) : textResult(output.text),
|
|
120
|
+
}, loadServices);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the theming tool response from guidance and conversation context.
|
|
3
|
+
*
|
|
4
|
+
* @module tools/storefrontnext/site-theming/response-builder
|
|
5
|
+
*/
|
|
6
|
+
import type { ThemingGuidance } from './theming-store.js';
|
|
7
|
+
import type { ConversationContext } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Returns questions relevant to the current conversation state, filtered and sorted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getRelevantQuestions(guidance: ThemingGuidance, context?: ConversationContext): ThemingGuidance['questions'];
|
|
12
|
+
export declare function hasProvidedThemingInfo(context?: ConversationContext): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Generates the full theming tool response from guidance and conversation context.
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateResponse(guidance: ThemingGuidance, context?: ConversationContext): string;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025, Salesforce, Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2
|
|
4
|
+
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { buildColorCombinations, appendValidationSection } from './color-mapping.js';
|
|
7
|
+
function isComponentScopeQuestion(question) {
|
|
8
|
+
const questionLower = question.question.toLowerCase();
|
|
9
|
+
return (questionLower.includes('which components') ||
|
|
10
|
+
questionLower.includes('component scope') ||
|
|
11
|
+
questionLower.includes('component group'));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns questions relevant to the current conversation state, filtered and sorted.
|
|
15
|
+
*/
|
|
16
|
+
export function getRelevantQuestions(guidance, context) {
|
|
17
|
+
if (!context || !context.questionsAsked || context.questionsAsked.length === 0) {
|
|
18
|
+
return guidance.questions
|
|
19
|
+
.filter((q) => !isComponentScopeQuestion(q))
|
|
20
|
+
.sort((a, b) => {
|
|
21
|
+
if (a.required !== b.required) {
|
|
22
|
+
return a.required ? -1 : 1;
|
|
23
|
+
}
|
|
24
|
+
const categoryOrder = { colors: 0, typography: 1, general: 2 };
|
|
25
|
+
return ((categoryOrder[a.category] || 2) -
|
|
26
|
+
(categoryOrder[b.category] || 2));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const askedIds = new Set(context.questionsAsked);
|
|
30
|
+
const remaining = guidance.questions.filter((q) => !askedIds.has(q.id) && !isComponentScopeQuestion(q));
|
|
31
|
+
if (context.collectedAnswers) {
|
|
32
|
+
const followUps = [];
|
|
33
|
+
for (const q of remaining) {
|
|
34
|
+
if (q.followUpQuestions && context.collectedAnswers?.[q.id]) {
|
|
35
|
+
for (const [index, followUp] of q.followUpQuestions.entries()) {
|
|
36
|
+
followUps.push({
|
|
37
|
+
id: `${q.id}-followup-${index}`,
|
|
38
|
+
question: followUp,
|
|
39
|
+
category: q.category,
|
|
40
|
+
required: false,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [...remaining, ...followUps];
|
|
46
|
+
}
|
|
47
|
+
return remaining;
|
|
48
|
+
}
|
|
49
|
+
export function hasProvidedThemingInfo(context) {
|
|
50
|
+
if (!context?.collectedAnswers) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const collectedAnswers = context.collectedAnswers;
|
|
54
|
+
const hasColors = Boolean(collectedAnswers.colors && Array.isArray(collectedAnswers.colors));
|
|
55
|
+
const hasFonts = Boolean(collectedAnswers.fonts && Array.isArray(collectedAnswers.fonts));
|
|
56
|
+
return hasColors || hasFonts;
|
|
57
|
+
}
|
|
58
|
+
function buildExtractionResponse(extractionInstructions) {
|
|
59
|
+
const internal = '# ⚠️ MANDATORY: Extract User-Provided Theming Information\n\n## 🚨 CRITICAL: Information Extraction Required\n\n' +
|
|
60
|
+
extractionInstructions;
|
|
61
|
+
const user = "I need to extract the theming information from your input first.\n\nLet me review what you've shared and structure it properly, then I'll proceed with clarifying questions.\n\n";
|
|
62
|
+
return `${internal}\n\n---\n\n# USER-FACING RESPONSE (What to say to the user):\n\n${user}`;
|
|
63
|
+
}
|
|
64
|
+
function appendValidationInstructions(out, validation) {
|
|
65
|
+
let s = out +
|
|
66
|
+
'## ⚠️ MANDATORY: Input Validation\n\n**BEFORE implementing, you MUST validate ALL user-provided inputs:**\n\n';
|
|
67
|
+
if (validation.colorValidation)
|
|
68
|
+
s +=
|
|
69
|
+
'**A. Color Combination Validation (MANDATORY if colors provided):**\n\n' + validation.colorValidation + '\n\n';
|
|
70
|
+
if (validation.fontValidation)
|
|
71
|
+
s += '**B. Font Validation (MANDATORY if fonts provided):**\n\n' + validation.fontValidation + '\n\n';
|
|
72
|
+
if (validation.generalValidation)
|
|
73
|
+
s += '**C. General Input Validation:**\n\n' + validation.generalValidation + '\n\n';
|
|
74
|
+
if (validation.requirements)
|
|
75
|
+
s += '**IMPORTANT:**\n\n' + validation.requirements + '\n\n';
|
|
76
|
+
return s;
|
|
77
|
+
}
|
|
78
|
+
function appendCriticalAndRules(out, guidance) {
|
|
79
|
+
let s = out;
|
|
80
|
+
const critical = guidance.guidelines.filter((g) => g.critical);
|
|
81
|
+
if (critical.length > 0) {
|
|
82
|
+
s += '## ⚠️ Critical Guidelines (INTERNAL - Follow these rules)\n\n';
|
|
83
|
+
for (const g of critical)
|
|
84
|
+
s += `### ${g.title}\n\n${g.content}\n\n`;
|
|
85
|
+
}
|
|
86
|
+
if (guidance.rules.length > 0) {
|
|
87
|
+
s += '## Rules to Follow (INTERNAL)\n\n';
|
|
88
|
+
const doRules = guidance.rules.filter((r) => r.type === 'do');
|
|
89
|
+
const dontRules = guidance.rules.filter((r) => r.type === 'dont');
|
|
90
|
+
if (doRules.length > 0) {
|
|
91
|
+
s += '### ✅ What TO Do:\n\n';
|
|
92
|
+
for (const r of doRules)
|
|
93
|
+
s += `- ${r.description}\n`;
|
|
94
|
+
s += '\n';
|
|
95
|
+
}
|
|
96
|
+
if (dontRules.length > 0) {
|
|
97
|
+
s += '### ❌ What NOT to Do:\n\n';
|
|
98
|
+
for (const r of dontRules)
|
|
99
|
+
s += `- ${r.description}\n`;
|
|
100
|
+
s += '\n';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return s;
|
|
104
|
+
}
|
|
105
|
+
function extractColorsFromArray(colors) {
|
|
106
|
+
if (!colors || !Array.isArray(colors))
|
|
107
|
+
return [];
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const color of colors) {
|
|
110
|
+
if (color.hex && color.type)
|
|
111
|
+
out.push(`${color.hex} (${color.type})`);
|
|
112
|
+
else if (color.hex)
|
|
113
|
+
out.push(color.hex);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
function extractFontsFromArray(fonts) {
|
|
118
|
+
if (!fonts || !Array.isArray(fonts))
|
|
119
|
+
return [];
|
|
120
|
+
const out = [];
|
|
121
|
+
for (const font of fonts) {
|
|
122
|
+
if (font.name)
|
|
123
|
+
out.push(font.type ? `${font.name} (${font.type})` : font.name);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function extractColorFromValue(value) {
|
|
128
|
+
if (typeof value === 'string')
|
|
129
|
+
return value;
|
|
130
|
+
if (typeof value === 'object' && value && 'hex' in value) {
|
|
131
|
+
const v = value;
|
|
132
|
+
if (v.hex === undefined)
|
|
133
|
+
return null;
|
|
134
|
+
return v.type ? `${v.hex} (${v.type})` : v.hex;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function extractFontFromValue(value) {
|
|
139
|
+
if (typeof value === 'string')
|
|
140
|
+
return value;
|
|
141
|
+
if (typeof value === 'object' && value && 'name' in value) {
|
|
142
|
+
const v = value;
|
|
143
|
+
if (v.name === undefined)
|
|
144
|
+
return null;
|
|
145
|
+
return v.type ? `${v.name} (${v.type})` : v.name;
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function shouldSkipKeyForOtherInfo(key, lowerKey) {
|
|
150
|
+
if (key === 'colors' || key === 'fonts')
|
|
151
|
+
return true;
|
|
152
|
+
if (lowerKey.includes('question') || lowerKey.includes('step'))
|
|
153
|
+
return true;
|
|
154
|
+
if (lowerKey.includes('color') || lowerKey.includes('font'))
|
|
155
|
+
return true;
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
function extractOtherInfoFromEntries(collectedAnswers) {
|
|
159
|
+
const otherInfo = [];
|
|
160
|
+
for (const key of Object.keys(collectedAnswers)) {
|
|
161
|
+
const lowerKey = key.toLowerCase();
|
|
162
|
+
if (shouldSkipKeyForOtherInfo(key, lowerKey))
|
|
163
|
+
continue;
|
|
164
|
+
const value = collectedAnswers[key];
|
|
165
|
+
if (value === null || value === undefined)
|
|
166
|
+
continue;
|
|
167
|
+
otherInfo.push(`${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
|
|
168
|
+
}
|
|
169
|
+
return otherInfo;
|
|
170
|
+
}
|
|
171
|
+
function collectUserInfo(collectedAnswers) {
|
|
172
|
+
const colorsInfo = [...extractColorsFromArray(collectedAnswers.colors)];
|
|
173
|
+
const fontsInfo = [...extractFontsFromArray(collectedAnswers.fonts)];
|
|
174
|
+
for (const key of Object.keys(collectedAnswers)) {
|
|
175
|
+
const lowerKey = key.toLowerCase();
|
|
176
|
+
const value = collectedAnswers[key];
|
|
177
|
+
if (key === 'colors' || key === 'fonts' || lowerKey.includes('question') || lowerKey.includes('step'))
|
|
178
|
+
continue;
|
|
179
|
+
if (lowerKey.includes('color') && !key.includes('colors')) {
|
|
180
|
+
const c = extractColorFromValue(value);
|
|
181
|
+
if (c)
|
|
182
|
+
colorsInfo.push(c);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (lowerKey.includes('font') && !key.includes('fonts')) {
|
|
186
|
+
const f = extractFontFromValue(value);
|
|
187
|
+
if (f)
|
|
188
|
+
fontsInfo.push(f);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const otherInfo = extractOtherInfoFromEntries(collectedAnswers);
|
|
193
|
+
return { colorsInfo, fontsInfo, otherInfo };
|
|
194
|
+
}
|
|
195
|
+
function buildUserInfoSection(info) {
|
|
196
|
+
const { colorsInfo, fontsInfo, otherInfo } = info;
|
|
197
|
+
if (colorsInfo.length === 0 && fontsInfo.length === 0 && otherInfo.length === 0) {
|
|
198
|
+
return 'Following the theming workflow. I need a few clarifications before implementing.\n\n';
|
|
199
|
+
}
|
|
200
|
+
let s = "## Information You've Provided\n\n";
|
|
201
|
+
if (colorsInfo.length > 0) {
|
|
202
|
+
s += '### Colors:\n';
|
|
203
|
+
for (const c of colorsInfo)
|
|
204
|
+
s += `- ${c}\n`;
|
|
205
|
+
s += '\n';
|
|
206
|
+
}
|
|
207
|
+
if (fontsInfo.length > 0) {
|
|
208
|
+
s += '### Fonts:\n';
|
|
209
|
+
for (const f of fontsInfo)
|
|
210
|
+
s += `- ${f}\n`;
|
|
211
|
+
s += '\n';
|
|
212
|
+
}
|
|
213
|
+
if (otherInfo.length > 0) {
|
|
214
|
+
s += '### Other Information:\n';
|
|
215
|
+
for (const o of otherInfo)
|
|
216
|
+
s += `- ${o}\n`;
|
|
217
|
+
s += '\n';
|
|
218
|
+
}
|
|
219
|
+
return (s +
|
|
220
|
+
"I've noted the information above. Before implementing, I need a few clarifications to ensure everything is set up correctly.\n\n");
|
|
221
|
+
}
|
|
222
|
+
function buildInternalInstructionsBase(guidance, context) {
|
|
223
|
+
let s = '# ⚠️ MANDATORY: Site Theming Guidelines and Questions\n\n## 🚨 CRITICAL: Read This First\n\n';
|
|
224
|
+
if (guidance.workflow?.steps && guidance.workflow.steps.length > 0) {
|
|
225
|
+
s += '**YOU MUST FOLLOW THIS WORKFLOW - NO EXCEPTIONS:**\n\n';
|
|
226
|
+
for (const [i, step] of guidance.workflow.steps.entries())
|
|
227
|
+
s += `${i + 1}. ${step}\n`;
|
|
228
|
+
s += '\n**VIOLATION OF THIS WORKFLOW IS A CRITICAL ERROR.**\n\n';
|
|
229
|
+
}
|
|
230
|
+
if (guidance.validation)
|
|
231
|
+
s = appendValidationInstructions(s, guidance.validation);
|
|
232
|
+
const colorMapping = context?.collectedAnswers?.colorMapping;
|
|
233
|
+
if (colorMapping && Object.keys(colorMapping).length > 0) {
|
|
234
|
+
s +=
|
|
235
|
+
'## 🎨 AUTOMATED COLOR VALIDATION RESULTS\n\n**The following validation has been automatically performed using built-in contrast calculation:**\n\n';
|
|
236
|
+
s = appendValidationSection(s, buildColorCombinations(colorMapping));
|
|
237
|
+
}
|
|
238
|
+
return appendCriticalAndRules(s, guidance);
|
|
239
|
+
}
|
|
240
|
+
function appendQuestionsToResponse(internal, user, nextQuestions, relevantQuestions) {
|
|
241
|
+
let userOut = user + '## Questions\n\n';
|
|
242
|
+
const categories = [
|
|
243
|
+
{ category: 'colors', title: 'Color Questions' },
|
|
244
|
+
{ category: 'typography', title: 'Font Questions' },
|
|
245
|
+
{ category: 'general', title: 'General Questions' },
|
|
246
|
+
];
|
|
247
|
+
for (const { category, title } of categories) {
|
|
248
|
+
const qs = nextQuestions.filter((q) => q.category === category);
|
|
249
|
+
if (qs.length > 0) {
|
|
250
|
+
userOut += `### ${title}\n\n`;
|
|
251
|
+
for (const [i, q] of qs.entries())
|
|
252
|
+
userOut += `**Question ${i + 1}**: ${q.question}\n\n`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const remaining = relevantQuestions.length - nextQuestions.length;
|
|
256
|
+
if (remaining > 0)
|
|
257
|
+
userOut += `\n_Note: I have ${remaining} more question${remaining > 1 ? 's' : ''} to ask after you answer these._\n\n`;
|
|
258
|
+
userOut += 'Please answer these questions so I can proceed with the implementation.\n\n';
|
|
259
|
+
let internalOut = internal +
|
|
260
|
+
"## Questions to Ask the User\n\n**IMPORTANT**: Ask these questions ONE AT A TIME and WAIT for the user's response before proceeding.\n\n**CRITICAL RULE**: NEVER implement changes after asking questions without waiting for the user's response.\n\n";
|
|
261
|
+
internalOut += `**You have ${relevantQuestions.length} total questions to ask. Show ${nextQuestions.length} now, then continue with the rest after user responds.**\n\n`;
|
|
262
|
+
for (const [i, q] of nextQuestions.entries()) {
|
|
263
|
+
internalOut += `### Question ${i + 1} (${q.category}): ${q.id}\n\n${q.question}\n\n`;
|
|
264
|
+
if (q.required)
|
|
265
|
+
internalOut += '**Required**: Yes\n\n';
|
|
266
|
+
}
|
|
267
|
+
return { internal: internalOut, user: userOut };
|
|
268
|
+
}
|
|
269
|
+
function appendReadyOrWarningToResponse(internal, user, guidance, context) {
|
|
270
|
+
const required = guidance.questions.filter((q) => q.required);
|
|
271
|
+
const answered = required.filter((q) => context.collectedAnswers?.[q.id] !== undefined);
|
|
272
|
+
if (answered.length < required.length) {
|
|
273
|
+
return {
|
|
274
|
+
internal: internal +
|
|
275
|
+
'## ⚠️ WARNING: Not all required questions have been answered!\n\n**DO NOT implement yet. Continue asking questions.**\n\n',
|
|
276
|
+
user: user + 'I still need answers to some required questions before I can proceed.\n\n',
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
let internalOut = internal;
|
|
280
|
+
if (guidance.workflow?.preImplementationChecklist) {
|
|
281
|
+
internalOut +=
|
|
282
|
+
'## ⚠️ MANDATORY PRE-IMPLEMENTATION CHECKLIST\n\n' + guidance.workflow.preImplementationChecklist + '\n\n';
|
|
283
|
+
}
|
|
284
|
+
const userOut = user +
|
|
285
|
+
'## Ready to Implement\n\nI have collected all necessary information. Before implementing, I will validate all provided inputs (colors, fonts, etc.) for accessibility, availability, and best practices.\n\n';
|
|
286
|
+
return { internal: internalOut, user: userOut };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Generates the full theming tool response from guidance and conversation context.
|
|
290
|
+
*/
|
|
291
|
+
export function generateResponse(guidance, context) {
|
|
292
|
+
const isFirstCall = !context || !context.questionsAsked || context.questionsAsked.length === 0;
|
|
293
|
+
if (isFirstCall && !hasProvidedThemingInfo(context) && guidance.workflow?.extractionInstructions) {
|
|
294
|
+
return buildExtractionResponse(guidance.workflow.extractionInstructions);
|
|
295
|
+
}
|
|
296
|
+
const relevantQuestions = getRelevantQuestions(guidance, context);
|
|
297
|
+
const questionLimit = !context || context.questionsAsked?.length === 0 ? 5 : 3;
|
|
298
|
+
const nextQuestions = relevantQuestions.slice(0, questionLimit);
|
|
299
|
+
let internalInstructions = buildInternalInstructionsBase(guidance, context);
|
|
300
|
+
const info = context?.collectedAnswers
|
|
301
|
+
? collectUserInfo(context.collectedAnswers)
|
|
302
|
+
: { colorsInfo: [], fontsInfo: [], otherInfo: [] };
|
|
303
|
+
let userResponse = buildUserInfoSection(info);
|
|
304
|
+
if (nextQuestions.length > 0) {
|
|
305
|
+
const appended = appendQuestionsToResponse(internalInstructions, userResponse, nextQuestions, relevantQuestions);
|
|
306
|
+
internalInstructions = appended.internal;
|
|
307
|
+
userResponse = appended.user;
|
|
308
|
+
}
|
|
309
|
+
else if (context?.collectedAnswers && Object.keys(context.collectedAnswers).length > 0) {
|
|
310
|
+
const appended = appendReadyOrWarningToResponse(internalInstructions, userResponse, guidance, context);
|
|
311
|
+
internalInstructions = appended.internal;
|
|
312
|
+
userResponse = appended.user;
|
|
313
|
+
}
|
|
314
|
+
return `${internalInstructions}\n\n---\n\n# USER-FACING RESPONSE (What to say to the user):\n\n${userResponse}`;
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=response-builder.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface ThemingGuidance {
|
|
2
|
+
questions: Array<{
|
|
3
|
+
id: string;
|
|
4
|
+
question: string;
|
|
5
|
+
category: string;
|
|
6
|
+
required: boolean;
|
|
7
|
+
followUpQuestions?: string[];
|
|
8
|
+
}>;
|
|
9
|
+
guidelines: Array<{
|
|
10
|
+
category: string;
|
|
11
|
+
title: string;
|
|
12
|
+
content: string;
|
|
13
|
+
critical: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
rules: Array<{
|
|
16
|
+
type: 'do' | 'dont';
|
|
17
|
+
description: string;
|
|
18
|
+
examples?: string[];
|
|
19
|
+
}>;
|
|
20
|
+
workflow?: {
|
|
21
|
+
steps: string[];
|
|
22
|
+
extractionInstructions?: string;
|
|
23
|
+
preImplementationChecklist?: string;
|
|
24
|
+
};
|
|
25
|
+
validation?: {
|
|
26
|
+
colorValidation?: string;
|
|
27
|
+
fontValidation?: string;
|
|
28
|
+
generalValidation?: string;
|
|
29
|
+
requirements?: string;
|
|
30
|
+
};
|
|
31
|
+
metadata: {
|
|
32
|
+
filePath: string;
|
|
33
|
+
fileName: string;
|
|
34
|
+
loadedAt: Date;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Theming Data Store
|
|
39
|
+
* Loads and caches theming guidance from .md/.mdc files
|
|
40
|
+
*/
|
|
41
|
+
export interface InitializeOptions {
|
|
42
|
+
/** Override content directory for default files (used in tests). */
|
|
43
|
+
contentDirOverride?: string;
|
|
44
|
+
}
|
|
45
|
+
declare class ThemingStore {
|
|
46
|
+
private initializedForRoot;
|
|
47
|
+
private store;
|
|
48
|
+
get(fileKey: string): ThemingGuidance | undefined;
|
|
49
|
+
getKeys(): string[];
|
|
50
|
+
has(fileKey: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Initialize store with default files from content/site-theming.
|
|
53
|
+
* Uses workspaceRoot for THEMING_FILES env paths (relative to project).
|
|
54
|
+
* Skips re-loading when already initialized for the same root.
|
|
55
|
+
*/
|
|
56
|
+
initialize(workspaceRoot?: string, options?: InitializeOptions): void;
|
|
57
|
+
loadFile(fileKey: string, filePath: string): void;
|
|
58
|
+
private loadThemingFilesFromEnv;
|
|
59
|
+
private tryLoadEnvFile;
|
|
60
|
+
}
|
|
61
|
+
export declare const siteThemingStore: ThemingStore;
|
|
62
|
+
export {};
|