mycontext-cli 2.0.27 → 2.0.28
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 +35 -369
- package/dist/agents/implementations/CodeGenSubAgent.d.ts.map +1 -1
- package/dist/agents/implementations/CodeGenSubAgent.js +67 -14
- package/dist/agents/implementations/CodeGenSubAgent.js.map +1 -1
- package/dist/agents/implementations/DesignPipelineAgent.d.ts +80 -0
- package/dist/agents/implementations/DesignPipelineAgent.d.ts.map +1 -0
- package/dist/agents/implementations/DesignPipelineAgent.js +1406 -0
- package/dist/agents/implementations/DesignPipelineAgent.js.map +1 -0
- package/dist/agents/implementations/FeatureAssemblyAgent.d.ts +67 -0
- package/dist/agents/implementations/FeatureAssemblyAgent.d.ts.map +1 -0
- package/dist/agents/implementations/FeatureAssemblyAgent.js +564 -0
- package/dist/agents/implementations/FeatureAssemblyAgent.js.map +1 -0
- package/dist/agents/implementations/PromptConstructorAgent.d.ts +10 -0
- package/dist/agents/implementations/PromptConstructorAgent.d.ts.map +1 -1
- package/dist/agents/implementations/PromptConstructorAgent.js +172 -12
- package/dist/agents/implementations/PromptConstructorAgent.js.map +1 -1
- package/dist/agents/implementations/RoleBasedGenerator.d.ts +52 -0
- package/dist/agents/implementations/RoleBasedGenerator.d.ts.map +1 -0
- package/dist/agents/implementations/RoleBasedGenerator.js +370 -0
- package/dist/agents/implementations/RoleBasedGenerator.js.map +1 -0
- package/dist/cli.js +65 -0
- package/dist/cli.js.map +1 -1
- package/dist/clients/ClaudeSDKClient.d.ts +48 -0
- package/dist/clients/ClaudeSDKClient.d.ts.map +1 -0
- package/dist/clients/ClaudeSDKClient.js +158 -0
- package/dist/clients/ClaudeSDKClient.js.map +1 -0
- package/dist/clients/MyContextAIClient.d.ts +73 -0
- package/dist/clients/MyContextAIClient.d.ts.map +1 -0
- package/dist/clients/MyContextAIClient.js +280 -0
- package/dist/clients/MyContextAIClient.js.map +1 -0
- package/dist/clients/ProviderChain.d.ts +87 -0
- package/dist/clients/ProviderChain.d.ts.map +1 -0
- package/dist/clients/ProviderChain.js +246 -0
- package/dist/clients/ProviderChain.js.map +1 -0
- package/dist/clients/XAIClient.d.ts +48 -0
- package/dist/clients/XAIClient.d.ts.map +1 -0
- package/dist/clients/XAIClient.js +152 -0
- package/dist/clients/XAIClient.js.map +1 -0
- package/dist/commands/assemble-features.d.ts +40 -0
- package/dist/commands/assemble-features.d.ts.map +1 -0
- package/dist/commands/assemble-features.js +383 -0
- package/dist/commands/assemble-features.js.map +1 -0
- package/dist/commands/clone-starter.d.ts +32 -0
- package/dist/commands/clone-starter.d.ts.map +1 -0
- package/dist/commands/clone-starter.js +218 -0
- package/dist/commands/clone-starter.js.map +1 -0
- package/dist/commands/design-analyze.d.ts +46 -0
- package/dist/commands/design-analyze.d.ts.map +1 -0
- package/dist/commands/design-analyze.js +232 -0
- package/dist/commands/design-analyze.js.map +1 -0
- package/dist/commands/generate-components.d.ts +1 -0
- package/dist/commands/generate-components.d.ts.map +1 -1
- package/dist/commands/generate-components.js +42 -9
- package/dist/commands/generate-components.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +34 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/setup-complete.d.ts.map +1 -1
- package/dist/commands/setup-complete.js +38 -2
- package/dist/commands/setup-complete.js.map +1 -1
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +86 -7
- package/dist/commands/workflow.js.map +1 -1
- package/dist/config/intent-dictionary.json +3700 -0
- package/dist/package.json +3 -1
- package/dist/services/IntentEnricher.d.ts +61 -0
- package/dist/services/IntentEnricher.d.ts.map +1 -0
- package/dist/services/IntentEnricher.js +318 -0
- package/dist/services/IntentEnricher.js.map +1 -0
- package/dist/services/IntentValidator.d.ts +114 -0
- package/dist/services/IntentValidator.d.ts.map +1 -0
- package/dist/services/IntentValidator.js +680 -0
- package/dist/services/IntentValidator.js.map +1 -0
- package/dist/types/design-pipeline.d.ts +300 -0
- package/dist/types/design-pipeline.d.ts.map +1 -0
- package/dist/types/design-pipeline.js +9 -0
- package/dist/types/design-pipeline.js.map +1 -0
- package/dist/types/feature-bundle.d.ts +239 -0
- package/dist/types/feature-bundle.d.ts.map +1 -0
- package/dist/types/feature-bundle.js +9 -0
- package/dist/types/feature-bundle.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/intent-dictionary.d.ts +525 -0
- package/dist/types/intent-dictionary.d.ts.map +1 -0
- package/dist/types/intent-dictionary.js +11 -0
- package/dist/types/intent-dictionary.js.map +1 -0
- package/dist/types/role-permissions.d.ts +167 -0
- package/dist/types/role-permissions.d.ts.map +1 -0
- package/dist/types/role-permissions.js +9 -0
- package/dist/types/role-permissions.js.map +1 -0
- package/dist/utils/contextEnricher.d.ts +41 -0
- package/dist/utils/contextEnricher.d.ts.map +1 -0
- package/dist/utils/contextEnricher.js +327 -0
- package/dist/utils/contextEnricher.js.map +1 -0
- package/dist/utils/designFallbacks.d.ts +48 -0
- package/dist/utils/designFallbacks.d.ts.map +1 -0
- package/dist/utils/designFallbacks.js +885 -0
- package/dist/utils/designFallbacks.js.map +1 -0
- package/dist/utils/designManifestManager.d.ts +89 -0
- package/dist/utils/designManifestManager.d.ts.map +1 -0
- package/dist/utils/designManifestManager.js +533 -0
- package/dist/utils/designManifestManager.js.map +1 -0
- package/dist/utils/designPipelineStateManager.d.ts +63 -0
- package/dist/utils/designPipelineStateManager.d.ts.map +1 -0
- package/dist/utils/designPipelineStateManager.js +174 -0
- package/dist/utils/designPipelineStateManager.js.map +1 -0
- package/dist/utils/envExampleGenerator.d.ts.map +1 -1
- package/dist/utils/envExampleGenerator.js +35 -171
- package/dist/utils/envExampleGenerator.js.map +1 -1
- package/dist/utils/featureBundleManager.d.ts +90 -0
- package/dist/utils/featureBundleManager.d.ts.map +1 -0
- package/dist/utils/featureBundleManager.js +340 -0
- package/dist/utils/featureBundleManager.js.map +1 -0
- package/dist/utils/githubCloner.d.ts +93 -0
- package/dist/utils/githubCloner.d.ts.map +1 -0
- package/dist/utils/githubCloner.js +305 -0
- package/dist/utils/githubCloner.js.map +1 -0
- package/dist/utils/rolePermissionMapper.d.ts +89 -0
- package/dist/utils/rolePermissionMapper.d.ts.map +1 -0
- package/dist/utils/rolePermissionMapper.js +337 -0
- package/dist/utils/rolePermissionMapper.js.map +1 -0
- package/dist/utils/unifiedDesignContextLoader.d.ts +76 -0
- package/dist/utils/unifiedDesignContextLoader.d.ts.map +1 -0
- package/dist/utils/unifiedDesignContextLoader.js +344 -0
- package/dist/utils/unifiedDesignContextLoader.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Intent Validator Service
|
|
4
|
+
*
|
|
5
|
+
* Validates natural language UI descriptions against the intent dictionary,
|
|
6
|
+
* extracting UI intents and matching them to shadcn/ui component patterns.
|
|
7
|
+
*
|
|
8
|
+
* @module services/IntentValidator
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.IntentValidator = void 0;
|
|
48
|
+
const fs = __importStar(require("fs-extra"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
51
|
+
const fuse_js_1 = __importDefault(require("fuse.js"));
|
|
52
|
+
/**
|
|
53
|
+
* Main service for validating UI intents from natural language
|
|
54
|
+
*/
|
|
55
|
+
class IntentValidator {
|
|
56
|
+
/**
|
|
57
|
+
* @param dictionaryPath - Optional path to custom dictionary (defaults to built-in)
|
|
58
|
+
*/
|
|
59
|
+
constructor(dictionaryPath) {
|
|
60
|
+
this.dictionaryPath =
|
|
61
|
+
dictionaryPath ||
|
|
62
|
+
path.join(__dirname, "..", "config", "intent-dictionary.json");
|
|
63
|
+
this.dictionary = this.loadDictionary();
|
|
64
|
+
this.fuse = this.initializeFuse();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Load intent dictionary from JSON file
|
|
68
|
+
*/
|
|
69
|
+
loadDictionary() {
|
|
70
|
+
try {
|
|
71
|
+
if (!fs.existsSync(this.dictionaryPath)) {
|
|
72
|
+
console.warn(chalk_1.default.yellow(`⚠️ Intent dictionary not found at ${this.dictionaryPath}, using default`));
|
|
73
|
+
return this.getDefaultDictionary();
|
|
74
|
+
}
|
|
75
|
+
const raw = fs.readFileSync(this.dictionaryPath, "utf-8");
|
|
76
|
+
const dictionary = JSON.parse(raw);
|
|
77
|
+
// Validate dictionary structure
|
|
78
|
+
if (!dictionary.version || !dictionary.mappings) {
|
|
79
|
+
throw new Error("Invalid dictionary structure");
|
|
80
|
+
}
|
|
81
|
+
return dictionary;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(chalk_1.default.red("❌ Failed to load intent dictionary:"), error);
|
|
85
|
+
return this.getDefaultDictionary();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Initialize Fuse.js for fuzzy matching
|
|
90
|
+
*/
|
|
91
|
+
initializeFuse() {
|
|
92
|
+
const searchableItems = [];
|
|
93
|
+
// Build searchable items from dictionary mappings
|
|
94
|
+
for (const [canonicalName, mapping] of Object.entries(this.dictionary.mappings)) {
|
|
95
|
+
// Add canonical name
|
|
96
|
+
searchableItems.push({
|
|
97
|
+
canonical_name: canonicalName,
|
|
98
|
+
search_text: canonicalName,
|
|
99
|
+
type: "canonical",
|
|
100
|
+
mapping: mapping,
|
|
101
|
+
});
|
|
102
|
+
// Add intent phrases
|
|
103
|
+
for (const phrase of mapping.intent_phrases) {
|
|
104
|
+
searchableItems.push({
|
|
105
|
+
canonical_name: canonicalName,
|
|
106
|
+
search_text: phrase.phrase,
|
|
107
|
+
aliases: phrase.aliases || [],
|
|
108
|
+
context_keywords: phrase.context_keywords || [],
|
|
109
|
+
type: "phrase",
|
|
110
|
+
mapping: mapping,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Add synonyms
|
|
115
|
+
for (const [synonym, canonicalName] of Object.entries(this.dictionary.synonyms)) {
|
|
116
|
+
searchableItems.push({
|
|
117
|
+
canonical_name: canonicalName,
|
|
118
|
+
search_text: synonym,
|
|
119
|
+
type: "synonym",
|
|
120
|
+
mapping: this.dictionary.mappings[canonicalName],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return new fuse_js_1.default(searchableItems, {
|
|
124
|
+
keys: [
|
|
125
|
+
{ name: "search_text", weight: 0.7 },
|
|
126
|
+
{ name: "aliases", weight: 0.3 },
|
|
127
|
+
{ name: "context_keywords", weight: 0.2 },
|
|
128
|
+
],
|
|
129
|
+
threshold: 0.4, // Lower threshold = more strict matching
|
|
130
|
+
includeScore: true,
|
|
131
|
+
includeMatches: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get minimal default dictionary as fallback
|
|
136
|
+
*/
|
|
137
|
+
getDefaultDictionary() {
|
|
138
|
+
return {
|
|
139
|
+
version: "1.0.0",
|
|
140
|
+
schema_version: "1.0.0",
|
|
141
|
+
last_updated: new Date().toISOString(),
|
|
142
|
+
mappings: {},
|
|
143
|
+
synonyms: {},
|
|
144
|
+
categories: [],
|
|
145
|
+
validation_config: {
|
|
146
|
+
min_confidence_threshold: 0.7,
|
|
147
|
+
ambiguity_threshold: 0.3,
|
|
148
|
+
require_design_tokens: false,
|
|
149
|
+
strict_mode: false,
|
|
150
|
+
},
|
|
151
|
+
usage_analytics: {
|
|
152
|
+
total_validations: 0,
|
|
153
|
+
intent_usage_counts: {},
|
|
154
|
+
validation_failures: [],
|
|
155
|
+
average_confidence: 0,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Main validation method - analyzes context files and extracts UI intents
|
|
161
|
+
*/
|
|
162
|
+
async validateContextFiles(prd, types, brand) {
|
|
163
|
+
console.log(chalk_1.default.blue("🔍 Validating UI intents from context..."));
|
|
164
|
+
try {
|
|
165
|
+
// 1. Extract intent phrases from text
|
|
166
|
+
const extractedIntents = this.extractIntentsFromText(prd, types, brand);
|
|
167
|
+
console.log(chalk_1.default.gray(` Extracted ${extractedIntents.length} potential intents`));
|
|
168
|
+
// 2. Match against dictionary
|
|
169
|
+
const matches = await this.matchIntents(extractedIntents);
|
|
170
|
+
console.log(chalk_1.default.gray(` Matched ${matches.length} intents`));
|
|
171
|
+
// 3. Detect ambiguities
|
|
172
|
+
const ambiguities = this.detectAmbiguities(matches, extractedIntents);
|
|
173
|
+
// 4. Find unknown intents
|
|
174
|
+
const unknowns = this.findUnknownIntents(extractedIntents, matches);
|
|
175
|
+
// 5. Calculate overall confidence
|
|
176
|
+
const confidence = this.calculateConfidence(matches);
|
|
177
|
+
// 6. Generate clarifications if needed
|
|
178
|
+
const clarifications = this.generateClarifications(ambiguities);
|
|
179
|
+
// 7. Generate warnings and suggestions
|
|
180
|
+
const warnings = this.generateWarnings(matches, ambiguities, unknowns);
|
|
181
|
+
const suggestions = this.generateSuggestions(unknowns);
|
|
182
|
+
// 8. Update analytics
|
|
183
|
+
this.updateAnalytics(matches.length, confidence);
|
|
184
|
+
const report = {
|
|
185
|
+
validated_intents: matches,
|
|
186
|
+
ambiguous_intents: ambiguities,
|
|
187
|
+
unknown_intents: unknowns,
|
|
188
|
+
confidence_score: confidence,
|
|
189
|
+
clarifications_needed: clarifications,
|
|
190
|
+
warnings,
|
|
191
|
+
suggestions,
|
|
192
|
+
};
|
|
193
|
+
console.log(chalk_1.default.green(`✅ Validation complete: ${matches.length} validated, ${ambiguities.length} ambiguous, ${unknowns.length} unknown`));
|
|
194
|
+
return report;
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error(chalk_1.default.red("❌ Intent validation failed:"), error);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Extract UI intent phrases from text using pattern matching
|
|
203
|
+
*/
|
|
204
|
+
extractIntentsFromText(prd, types, brand) {
|
|
205
|
+
const extracted = [];
|
|
206
|
+
const allText = `${prd}\n${types || ""}\n${brand || ""}`;
|
|
207
|
+
// Define extraction patterns for common UI descriptions
|
|
208
|
+
const patterns = [
|
|
209
|
+
// Buttons
|
|
210
|
+
{
|
|
211
|
+
regex: /\b(button|btn)\s+(to|for|that)\s+([^.!?\n]+)/gi,
|
|
212
|
+
confidence: 0.8,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
regex: /\b(submit|save|cancel|delete|edit|create)\s+(button|btn)/gi,
|
|
216
|
+
confidence: 0.85,
|
|
217
|
+
},
|
|
218
|
+
{ regex: /\b(cta|call to action)\b/gi, confidence: 0.9 },
|
|
219
|
+
// Forms
|
|
220
|
+
{
|
|
221
|
+
regex: /\b(login|signin|sign in|authentication|auth)\s+(form|page)/gi,
|
|
222
|
+
confidence: 0.95,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
regex: /\b(registration|signup|sign up|register)\s+(form|page)/gi,
|
|
226
|
+
confidence: 0.9,
|
|
227
|
+
},
|
|
228
|
+
{ regex: /\b(form)\s+(to|for|with)\s+([^.!?\n]+)/gi, confidence: 0.75 },
|
|
229
|
+
// Inputs
|
|
230
|
+
{
|
|
231
|
+
regex: /\b(input|field|text box)\s+(for|to enter)\s+([^.!?\n]+)/gi,
|
|
232
|
+
confidence: 0.8,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
regex: /\b(email|password|text|number)\s+(input|field)/gi,
|
|
236
|
+
confidence: 0.85,
|
|
237
|
+
},
|
|
238
|
+
// Dialogs & Modals
|
|
239
|
+
{
|
|
240
|
+
regex: /\b(confirmation|confirm)\s+(dialog|modal|popup)/gi,
|
|
241
|
+
confidence: 0.9,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
regex: /\b(alert|dialog|modal|popup)\s+(for|to|that)\s+([^.!?\n]+)/gi,
|
|
245
|
+
confidence: 0.75,
|
|
246
|
+
},
|
|
247
|
+
{ regex: /\b(are you sure|confirm|confirmation)\b/gi, confidence: 0.8 },
|
|
248
|
+
// Navigation
|
|
249
|
+
{ regex: /\b(navigation|nav)\s+(menu|bar)/gi, confidence: 0.9 },
|
|
250
|
+
{
|
|
251
|
+
regex: /\b(menu|navbar|sidebar)\s+(with|showing|for)\s+([^.!?\n]+)/gi,
|
|
252
|
+
confidence: 0.8,
|
|
253
|
+
},
|
|
254
|
+
// Data Display
|
|
255
|
+
{
|
|
256
|
+
regex: /\b(table|data table|grid)\s+(of|showing|displaying|with)\s+([^.!?\n]+)/gi,
|
|
257
|
+
confidence: 0.85,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
regex: /\b(list|grid)\s+(of|showing)\s+([^.!?\n]+)/gi,
|
|
261
|
+
confidence: 0.7,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
regex: /\b(card|panel)\s+(for|showing|displaying)\s+([^.!?\n]+)/gi,
|
|
265
|
+
confidence: 0.75,
|
|
266
|
+
},
|
|
267
|
+
// Feedback
|
|
268
|
+
{
|
|
269
|
+
regex: /\b(error|warning|success)\s+(message|alert|notification)/gi,
|
|
270
|
+
confidence: 0.85,
|
|
271
|
+
},
|
|
272
|
+
{ regex: /\b(loading|spinner|loader)\b/gi, confidence: 0.9 },
|
|
273
|
+
{
|
|
274
|
+
regex: /\b(toast|notification|alert)\s+(for|to|showing)\s+([^.!?\n]+)/gi,
|
|
275
|
+
confidence: 0.8,
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
for (const { regex, confidence } of patterns) {
|
|
279
|
+
let match;
|
|
280
|
+
const regexCopy = new RegExp(regex.source, regex.flags);
|
|
281
|
+
while ((match = regexCopy.exec(allText)) !== null) {
|
|
282
|
+
const matchedText = match[0];
|
|
283
|
+
const matchIndex = match.index;
|
|
284
|
+
// Extract context around the match
|
|
285
|
+
const context = this.extractContext(allText, matchIndex);
|
|
286
|
+
extracted.push({
|
|
287
|
+
original_text: matchedText.trim(),
|
|
288
|
+
pattern_matched: regex.source,
|
|
289
|
+
context,
|
|
290
|
+
confidence,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Remove duplicates (same text)
|
|
295
|
+
const unique = extracted.filter((item, index, self) => index === self.findIndex((t) => t.original_text === item.original_text));
|
|
296
|
+
return unique;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Extract surrounding context for an intent match
|
|
300
|
+
*/
|
|
301
|
+
extractContext(text, matchIndex) {
|
|
302
|
+
const contextRadius = 100;
|
|
303
|
+
const before = text
|
|
304
|
+
.slice(Math.max(0, matchIndex - contextRadius), matchIndex)
|
|
305
|
+
.trim();
|
|
306
|
+
const after = text
|
|
307
|
+
.slice(matchIndex, Math.min(text.length, matchIndex + contextRadius))
|
|
308
|
+
.trim();
|
|
309
|
+
// Extract keywords from context
|
|
310
|
+
const contextText = `${before} ${after}`.toLowerCase();
|
|
311
|
+
const keywords = [];
|
|
312
|
+
// Look for category keywords
|
|
313
|
+
const categoryKeywords = {
|
|
314
|
+
forms: ["form", "input", "field", "submit", "validate", "entry"],
|
|
315
|
+
navigation: ["navigation", "menu", "nav", "link", "route", "page"],
|
|
316
|
+
feedback: ["alert", "error", "success", "warning", "notify", "message"],
|
|
317
|
+
"data-display": [
|
|
318
|
+
"table",
|
|
319
|
+
"list",
|
|
320
|
+
"grid",
|
|
321
|
+
"data",
|
|
322
|
+
"row",
|
|
323
|
+
"column",
|
|
324
|
+
"display",
|
|
325
|
+
],
|
|
326
|
+
layout: ["card", "panel", "container", "layout", "section"],
|
|
327
|
+
};
|
|
328
|
+
for (const [category, words] of Object.entries(categoryKeywords)) {
|
|
329
|
+
for (const word of words) {
|
|
330
|
+
if (contextText.includes(word)) {
|
|
331
|
+
keywords.push(word);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Determine section (rough estimate)
|
|
336
|
+
const lines = text.slice(0, matchIndex).split("\n");
|
|
337
|
+
const lineNumber = lines.length;
|
|
338
|
+
const section = lineNumber < 50
|
|
339
|
+
? "Introduction"
|
|
340
|
+
: lineNumber < 100
|
|
341
|
+
? "Features"
|
|
342
|
+
: lineNumber < 200
|
|
343
|
+
? "Requirements"
|
|
344
|
+
: "Implementation";
|
|
345
|
+
return {
|
|
346
|
+
before,
|
|
347
|
+
after,
|
|
348
|
+
keywords: [...new Set(keywords)],
|
|
349
|
+
section,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Match extracted intents against dictionary
|
|
354
|
+
*/
|
|
355
|
+
async matchIntents(extracted) {
|
|
356
|
+
const matches = [];
|
|
357
|
+
for (const intent of extracted) {
|
|
358
|
+
// 1. Try exact phrase matching first
|
|
359
|
+
const exactMatch = this.findExactMatch(intent.original_text);
|
|
360
|
+
if (exactMatch) {
|
|
361
|
+
matches.push({
|
|
362
|
+
original_text: intent.original_text,
|
|
363
|
+
canonical_name: exactMatch.canonical_name,
|
|
364
|
+
intent_mapping: exactMatch,
|
|
365
|
+
confidence: 1.0,
|
|
366
|
+
extracted_context: intent.context,
|
|
367
|
+
resolved_placeholders: this.resolvePlaceholders(exactMatch, intent),
|
|
368
|
+
});
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
// 2. Try synonym matching
|
|
372
|
+
const synonymMatch = this.findSynonymMatch(intent.original_text);
|
|
373
|
+
if (synonymMatch) {
|
|
374
|
+
matches.push({
|
|
375
|
+
original_text: intent.original_text,
|
|
376
|
+
canonical_name: synonymMatch.canonical_name,
|
|
377
|
+
intent_mapping: synonymMatch,
|
|
378
|
+
confidence: 0.95,
|
|
379
|
+
extracted_context: intent.context,
|
|
380
|
+
resolved_placeholders: this.resolvePlaceholders(synonymMatch, intent),
|
|
381
|
+
});
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
// 3. Try fuzzy matching
|
|
385
|
+
const fuzzyMatches = this.fuzzyMatch(intent.original_text, intent);
|
|
386
|
+
if (fuzzyMatches.length > 0) {
|
|
387
|
+
const bestMatch = fuzzyMatches[0];
|
|
388
|
+
if (bestMatch &&
|
|
389
|
+
bestMatch.confidence >=
|
|
390
|
+
this.dictionary.validation_config.min_confidence_threshold) {
|
|
391
|
+
matches.push({
|
|
392
|
+
original_text: intent.original_text,
|
|
393
|
+
canonical_name: bestMatch.canonical_name,
|
|
394
|
+
intent_mapping: bestMatch.mapping,
|
|
395
|
+
confidence: bestMatch.confidence,
|
|
396
|
+
extracted_context: intent.context,
|
|
397
|
+
resolved_placeholders: this.resolvePlaceholders(bestMatch.mapping, intent),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return matches;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Find exact phrase match in dictionary
|
|
406
|
+
*/
|
|
407
|
+
findExactMatch(text) {
|
|
408
|
+
const lowerText = text.toLowerCase().trim();
|
|
409
|
+
for (const [canonicalName, mapping] of Object.entries(this.dictionary.mappings)) {
|
|
410
|
+
for (const intentPhrase of mapping.intent_phrases) {
|
|
411
|
+
if (lowerText.includes(intentPhrase.phrase.toLowerCase())) {
|
|
412
|
+
return mapping;
|
|
413
|
+
}
|
|
414
|
+
for (const alias of intentPhrase.aliases) {
|
|
415
|
+
if (lowerText.includes(alias.toLowerCase())) {
|
|
416
|
+
return mapping;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Find match via synonym lookup
|
|
425
|
+
*/
|
|
426
|
+
findSynonymMatch(text) {
|
|
427
|
+
const lowerText = text.toLowerCase().trim();
|
|
428
|
+
for (const [synonym, canonicalName] of Object.entries(this.dictionary.synonyms)) {
|
|
429
|
+
if (lowerText.includes(synonym.toLowerCase())) {
|
|
430
|
+
return this.dictionary.mappings[canonicalName] || null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Fuzzy match using Fuse.js for better accuracy
|
|
437
|
+
*/
|
|
438
|
+
fuzzyMatch(text, extracted) {
|
|
439
|
+
const results = [];
|
|
440
|
+
// Use Fuse.js to search
|
|
441
|
+
const fuseResults = this.fuse.search(text);
|
|
442
|
+
for (const result of fuseResults) {
|
|
443
|
+
const item = result.item;
|
|
444
|
+
const score = result.score || 0;
|
|
445
|
+
// Convert Fuse.js score (0-1, lower is better) to confidence (0-1, higher is better)
|
|
446
|
+
const confidence = 1 - score;
|
|
447
|
+
// Apply context keyword bonus
|
|
448
|
+
let contextBonus = 0;
|
|
449
|
+
if (item.context_keywords && extracted.context.keywords) {
|
|
450
|
+
const contextMatches = item.context_keywords.filter((kw) => extracted.context.keywords.includes(kw.toLowerCase()));
|
|
451
|
+
contextBonus =
|
|
452
|
+
(contextMatches.length / item.context_keywords.length) * 0.2;
|
|
453
|
+
}
|
|
454
|
+
const finalConfidence = Math.min(confidence + contextBonus, 1.0);
|
|
455
|
+
// Only include if above threshold
|
|
456
|
+
if (finalConfidence >= this.dictionary.validation_config.ambiguity_threshold) {
|
|
457
|
+
results.push({
|
|
458
|
+
canonical_name: item.canonical_name,
|
|
459
|
+
confidence: finalConfidence,
|
|
460
|
+
mapping: item.mapping,
|
|
461
|
+
matched_phrase: item.search_text,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Sort by confidence (highest first) and remove duplicates
|
|
466
|
+
const uniqueResults = results.reduce((acc, current) => {
|
|
467
|
+
const existing = acc.find((item) => item.canonical_name === current.canonical_name);
|
|
468
|
+
if (!existing || current.confidence > existing.confidence) {
|
|
469
|
+
return acc
|
|
470
|
+
.filter((item) => item.canonical_name !== current.canonical_name)
|
|
471
|
+
.concat(current);
|
|
472
|
+
}
|
|
473
|
+
return acc;
|
|
474
|
+
}, []);
|
|
475
|
+
return uniqueResults.sort((a, b) => b.confidence - a.confidence);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Resolve placeholder values from context
|
|
479
|
+
*/
|
|
480
|
+
resolvePlaceholders(mapping, intent) {
|
|
481
|
+
const placeholders = {};
|
|
482
|
+
// Extract common placeholders from text
|
|
483
|
+
const text = intent.original_text;
|
|
484
|
+
// label: try to extract from "button to X" or "button for X"
|
|
485
|
+
const labelMatch = text.match(/(?:to|for|that)\s+(.+)$/i);
|
|
486
|
+
if (labelMatch && labelMatch[1]) {
|
|
487
|
+
placeholders.label = this.capitalize(labelMatch[1].trim());
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
placeholders.label = "Submit";
|
|
491
|
+
}
|
|
492
|
+
// size: default to medium
|
|
493
|
+
placeholders.size = "default";
|
|
494
|
+
// handler: generate name
|
|
495
|
+
placeholders.handler =
|
|
496
|
+
"handle" + this.capitalize(mapping.canonical_name.replace(/-/g, ""));
|
|
497
|
+
// aria_label: use label or description
|
|
498
|
+
placeholders.aria_label = placeholders.label;
|
|
499
|
+
// disabled: default to false
|
|
500
|
+
placeholders.disabled = "false";
|
|
501
|
+
// loading: default to loading state variable
|
|
502
|
+
placeholders.loading = "loading";
|
|
503
|
+
return placeholders;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Detect ambiguous intents (multiple high-confidence matches)
|
|
507
|
+
*/
|
|
508
|
+
detectAmbiguities(matches, extracted) {
|
|
509
|
+
const ambiguities = [];
|
|
510
|
+
const threshold = this.dictionary.validation_config.ambiguity_threshold;
|
|
511
|
+
// Check each extracted intent
|
|
512
|
+
for (const intent of extracted) {
|
|
513
|
+
// Find all fuzzy matches for this intent
|
|
514
|
+
const fuzzyMatches = this.fuzzyMatch(intent.original_text, intent);
|
|
515
|
+
// If multiple matches with similar confidence
|
|
516
|
+
const highConfidenceMatches = fuzzyMatches.filter((m) => m.confidence >= threshold);
|
|
517
|
+
if (highConfidenceMatches.length > 1) {
|
|
518
|
+
// Check if top 2 are close in confidence (within 0.15)
|
|
519
|
+
const top = highConfidenceMatches[0];
|
|
520
|
+
const second = highConfidenceMatches[1];
|
|
521
|
+
if (top &&
|
|
522
|
+
second &&
|
|
523
|
+
Math.abs(top.confidence - second.confidence) < 0.15) {
|
|
524
|
+
ambiguities.push({
|
|
525
|
+
original_text: intent.original_text,
|
|
526
|
+
possible_intents: highConfidenceMatches
|
|
527
|
+
.slice(0, 3)
|
|
528
|
+
.map((match) => ({
|
|
529
|
+
canonical_name: match.canonical_name,
|
|
530
|
+
confidence: match.confidence,
|
|
531
|
+
reasoning: this.generateReasoning(match.mapping, intent),
|
|
532
|
+
})),
|
|
533
|
+
context_needed: [
|
|
534
|
+
"Component type",
|
|
535
|
+
"User interaction",
|
|
536
|
+
"Visual style",
|
|
537
|
+
],
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return ambiguities;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Generate reasoning for why an intent matched
|
|
546
|
+
*/
|
|
547
|
+
generateReasoning(mapping, intent) {
|
|
548
|
+
const keywords = mapping.intent_phrases[0]?.context_keywords || [];
|
|
549
|
+
const matchedKeywords = keywords.filter((kw) => intent.original_text.toLowerCase().includes(kw.toLowerCase()));
|
|
550
|
+
if (matchedKeywords.length > 0) {
|
|
551
|
+
return `Matched keywords: ${matchedKeywords.join(", ")}`;
|
|
552
|
+
}
|
|
553
|
+
return `Pattern match in ${intent.context.section}`;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Find unknown intents that couldn't be matched
|
|
557
|
+
*/
|
|
558
|
+
findUnknownIntents(extracted, matches) {
|
|
559
|
+
const unknowns = [];
|
|
560
|
+
const matchedTexts = new Set(matches.map((m) => m.original_text));
|
|
561
|
+
for (const intent of extracted) {
|
|
562
|
+
if (!matchedTexts.has(intent.original_text)) {
|
|
563
|
+
// Find similar intents
|
|
564
|
+
const fuzzyMatches = this.fuzzyMatch(intent.original_text, intent);
|
|
565
|
+
const similar = fuzzyMatches.slice(0, 3).map((m) => ({
|
|
566
|
+
canonical_name: m.canonical_name,
|
|
567
|
+
similarity_score: m.confidence,
|
|
568
|
+
}));
|
|
569
|
+
unknowns.push({
|
|
570
|
+
original_text: intent.original_text,
|
|
571
|
+
similar_intents: similar,
|
|
572
|
+
suggested_fallback: similar.length > 0 ? similar[0]?.canonical_name || "" : "",
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return unknowns;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Calculate overall confidence score
|
|
580
|
+
*/
|
|
581
|
+
calculateConfidence(matches) {
|
|
582
|
+
if (matches.length === 0)
|
|
583
|
+
return 0;
|
|
584
|
+
const sum = matches.reduce((acc, m) => acc + m.confidence, 0);
|
|
585
|
+
return sum / matches.length;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Generate clarification requests for ambiguous intents
|
|
589
|
+
*/
|
|
590
|
+
generateClarifications(ambiguities) {
|
|
591
|
+
return ambiguities.map((ambig) => ({
|
|
592
|
+
question: `What type of component did you mean?`,
|
|
593
|
+
original_text: ambig.original_text,
|
|
594
|
+
suggested_intents: ambig.possible_intents.map((pi) => {
|
|
595
|
+
const mapping = this.dictionary.mappings[pi.canonical_name];
|
|
596
|
+
return {
|
|
597
|
+
canonical_name: pi.canonical_name,
|
|
598
|
+
description: mapping?.component_pattern.shadcn_components
|
|
599
|
+
?.map((c) => c.name)
|
|
600
|
+
.join(", ") || "",
|
|
601
|
+
components: mapping?.component_pattern.shadcn_components?.map((c) => c.name) ||
|
|
602
|
+
[],
|
|
603
|
+
};
|
|
604
|
+
}),
|
|
605
|
+
}));
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Generate warnings about validation results
|
|
609
|
+
*/
|
|
610
|
+
generateWarnings(matches, ambiguities, unknowns) {
|
|
611
|
+
const warnings = [];
|
|
612
|
+
if (matches.length === 0) {
|
|
613
|
+
warnings.push("No UI intents detected in context files");
|
|
614
|
+
}
|
|
615
|
+
if (ambiguities.length > 0) {
|
|
616
|
+
warnings.push(`${ambiguities.length} ambiguous intent(s) require clarification`);
|
|
617
|
+
}
|
|
618
|
+
if (unknowns.length > 0) {
|
|
619
|
+
warnings.push(`${unknowns.length} unknown intent(s) could not be matched`);
|
|
620
|
+
}
|
|
621
|
+
const lowConfidence = matches.filter((m) => m.confidence < 0.7);
|
|
622
|
+
if (lowConfidence.length > 0) {
|
|
623
|
+
warnings.push(`${lowConfidence.length} intent(s) have low confidence`);
|
|
624
|
+
}
|
|
625
|
+
return warnings;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Generate suggestions for improving intent detection
|
|
629
|
+
*/
|
|
630
|
+
generateSuggestions(unknowns) {
|
|
631
|
+
const suggestions = [];
|
|
632
|
+
if (unknowns.length > 0) {
|
|
633
|
+
suggestions.push("Consider adding more specific UI component descriptions to your PRD");
|
|
634
|
+
const uniqueUnknowns = unknowns.slice(0, 3);
|
|
635
|
+
for (const unknown of uniqueUnknowns) {
|
|
636
|
+
if (unknown.similar_intents.length > 0 && unknown.similar_intents[0]) {
|
|
637
|
+
suggestions.push(`"${unknown.original_text}" might be: ${unknown.similar_intents[0].canonical_name}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return suggestions;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Update usage analytics
|
|
645
|
+
*/
|
|
646
|
+
updateAnalytics(validatedCount, confidence) {
|
|
647
|
+
this.dictionary.usage_analytics.total_validations++;
|
|
648
|
+
const current = this.dictionary.usage_analytics.average_confidence;
|
|
649
|
+
const total = this.dictionary.usage_analytics.total_validations;
|
|
650
|
+
// Running average
|
|
651
|
+
this.dictionary.usage_analytics.average_confidence =
|
|
652
|
+
(current * (total - 1) + confidence) / total;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Utility: capitalize first letter
|
|
656
|
+
*/
|
|
657
|
+
capitalize(str) {
|
|
658
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get current dictionary for inspection
|
|
662
|
+
*/
|
|
663
|
+
getDictionary() {
|
|
664
|
+
return this.dictionary;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Get list of available intents
|
|
668
|
+
*/
|
|
669
|
+
getAvailableIntents() {
|
|
670
|
+
return Object.keys(this.dictionary.mappings);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get intent mapping by canonical name
|
|
674
|
+
*/
|
|
675
|
+
getIntentMapping(canonicalName) {
|
|
676
|
+
return this.dictionary.mappings[canonicalName] || null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
exports.IntentValidator = IntentValidator;
|
|
680
|
+
//# sourceMappingURL=IntentValidator.js.map
|