mycontext-cli 2.0.27 → 2.0.29

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.
Files changed (173) hide show
  1. package/README.md +71 -342
  2. package/dist/agents/implementations/CodeGenSubAgent.d.ts.map +1 -1
  3. package/dist/agents/implementations/CodeGenSubAgent.js +67 -14
  4. package/dist/agents/implementations/CodeGenSubAgent.js.map +1 -1
  5. package/dist/agents/implementations/DesignPipelineAgent.d.ts +80 -0
  6. package/dist/agents/implementations/DesignPipelineAgent.d.ts.map +1 -0
  7. package/dist/agents/implementations/DesignPipelineAgent.js +1406 -0
  8. package/dist/agents/implementations/DesignPipelineAgent.js.map +1 -0
  9. package/dist/agents/implementations/FeatureAssemblyAgent.d.ts +67 -0
  10. package/dist/agents/implementations/FeatureAssemblyAgent.d.ts.map +1 -0
  11. package/dist/agents/implementations/FeatureAssemblyAgent.js +564 -0
  12. package/dist/agents/implementations/FeatureAssemblyAgent.js.map +1 -0
  13. package/dist/agents/implementations/PromptConstructorAgent.d.ts +10 -0
  14. package/dist/agents/implementations/PromptConstructorAgent.d.ts.map +1 -1
  15. package/dist/agents/implementations/PromptConstructorAgent.js +172 -12
  16. package/dist/agents/implementations/PromptConstructorAgent.js.map +1 -1
  17. package/dist/agents/implementations/RoleBasedGenerator.d.ts +52 -0
  18. package/dist/agents/implementations/RoleBasedGenerator.d.ts.map +1 -0
  19. package/dist/agents/implementations/RoleBasedGenerator.js +370 -0
  20. package/dist/agents/implementations/RoleBasedGenerator.js.map +1 -0
  21. package/dist/cli.js +111 -56
  22. package/dist/cli.js.map +1 -1
  23. package/dist/clients/ClaudeSDKClient.d.ts +48 -0
  24. package/dist/clients/ClaudeSDKClient.d.ts.map +1 -0
  25. package/dist/clients/ClaudeSDKClient.js +158 -0
  26. package/dist/clients/ClaudeSDKClient.js.map +1 -0
  27. package/dist/clients/MyContextAIClient.d.ts +73 -0
  28. package/dist/clients/MyContextAIClient.d.ts.map +1 -0
  29. package/dist/clients/MyContextAIClient.js +280 -0
  30. package/dist/clients/MyContextAIClient.js.map +1 -0
  31. package/dist/clients/ProviderChain.d.ts +87 -0
  32. package/dist/clients/ProviderChain.d.ts.map +1 -0
  33. package/dist/clients/ProviderChain.js +246 -0
  34. package/dist/clients/ProviderChain.js.map +1 -0
  35. package/dist/clients/XAIClient.d.ts +48 -0
  36. package/dist/clients/XAIClient.d.ts.map +1 -0
  37. package/dist/clients/XAIClient.js +152 -0
  38. package/dist/clients/XAIClient.js.map +1 -0
  39. package/dist/commands/assemble-features.d.ts +40 -0
  40. package/dist/commands/assemble-features.d.ts.map +1 -0
  41. package/dist/commands/assemble-features.js +383 -0
  42. package/dist/commands/assemble-features.js.map +1 -0
  43. package/dist/commands/clone-starter.d.ts +32 -0
  44. package/dist/commands/clone-starter.d.ts.map +1 -0
  45. package/dist/commands/clone-starter.js +218 -0
  46. package/dist/commands/clone-starter.js.map +1 -0
  47. package/dist/commands/design-analyze.d.ts +46 -0
  48. package/dist/commands/design-analyze.d.ts.map +1 -0
  49. package/dist/commands/design-analyze.js +232 -0
  50. package/dist/commands/design-analyze.js.map +1 -0
  51. package/dist/commands/generate-components.d.ts +11 -0
  52. package/dist/commands/generate-components.d.ts.map +1 -1
  53. package/dist/commands/generate-components.js +342 -12
  54. package/dist/commands/generate-components.js.map +1 -1
  55. package/dist/commands/generate-context-files.d.ts +9 -0
  56. package/dist/commands/generate-context-files.d.ts.map +1 -1
  57. package/dist/commands/generate-context-files.js +57 -0
  58. package/dist/commands/generate-context-files.js.map +1 -1
  59. package/dist/commands/generate.d.ts +5 -0
  60. package/dist/commands/generate.d.ts.map +1 -1
  61. package/dist/commands/generate.js +109 -2
  62. package/dist/commands/generate.js.map +1 -1
  63. package/dist/commands/init.d.ts +1 -0
  64. package/dist/commands/init.d.ts.map +1 -1
  65. package/dist/commands/init.js +32 -10
  66. package/dist/commands/init.js.map +1 -1
  67. package/dist/commands/preview-components.d.ts +12 -0
  68. package/dist/commands/preview-components.d.ts.map +1 -0
  69. package/dist/commands/preview-components.js +122 -0
  70. package/dist/commands/preview-components.js.map +1 -0
  71. package/dist/commands/refine-component.d.ts +43 -0
  72. package/dist/commands/refine-component.d.ts.map +1 -0
  73. package/dist/commands/refine-component.js +313 -0
  74. package/dist/commands/refine-component.js.map +1 -0
  75. package/dist/commands/review-context.d.ts +47 -0
  76. package/dist/commands/review-context.d.ts.map +1 -0
  77. package/dist/commands/review-context.js +335 -0
  78. package/dist/commands/review-context.js.map +1 -0
  79. package/dist/commands/setup-complete.d.ts.map +1 -1
  80. package/dist/commands/setup-complete.js +38 -2
  81. package/dist/commands/setup-complete.js.map +1 -1
  82. package/dist/commands/workflow.d.ts.map +1 -1
  83. package/dist/commands/workflow.js +86 -7
  84. package/dist/commands/workflow.js.map +1 -1
  85. package/dist/config/intent-dictionary.json +3700 -0
  86. package/dist/package.json +12 -1
  87. package/dist/services/ContextValidator.d.ts +99 -0
  88. package/dist/services/ContextValidator.d.ts.map +1 -0
  89. package/dist/services/ContextValidator.js +433 -0
  90. package/dist/services/ContextValidator.js.map +1 -0
  91. package/dist/services/IntentEnricher.d.ts +61 -0
  92. package/dist/services/IntentEnricher.d.ts.map +1 -0
  93. package/dist/services/IntentEnricher.js +318 -0
  94. package/dist/services/IntentEnricher.js.map +1 -0
  95. package/dist/services/IntentValidator.d.ts +114 -0
  96. package/dist/services/IntentValidator.d.ts.map +1 -0
  97. package/dist/services/IntentValidator.js +680 -0
  98. package/dist/services/IntentValidator.js.map +1 -0
  99. package/dist/services/MutationLogger.d.ts +54 -0
  100. package/dist/services/MutationLogger.d.ts.map +1 -0
  101. package/dist/services/MutationLogger.js +164 -0
  102. package/dist/services/MutationLogger.js.map +1 -0
  103. package/dist/services/RegressionRunner.d.ts +49 -0
  104. package/dist/services/RegressionRunner.d.ts.map +1 -0
  105. package/dist/services/RegressionRunner.js +285 -0
  106. package/dist/services/RegressionRunner.js.map +1 -0
  107. package/dist/services/TriggerLogger.d.ts +101 -0
  108. package/dist/services/TriggerLogger.d.ts.map +1 -0
  109. package/dist/services/TriggerLogger.js +263 -0
  110. package/dist/services/TriggerLogger.js.map +1 -0
  111. package/dist/types/design-pipeline.d.ts +300 -0
  112. package/dist/types/design-pipeline.d.ts.map +1 -0
  113. package/dist/types/design-pipeline.js +9 -0
  114. package/dist/types/design-pipeline.js.map +1 -0
  115. package/dist/types/feature-bundle.d.ts +239 -0
  116. package/dist/types/feature-bundle.d.ts.map +1 -0
  117. package/dist/types/feature-bundle.js +9 -0
  118. package/dist/types/feature-bundle.js.map +1 -0
  119. package/dist/types/index.d.ts +2 -0
  120. package/dist/types/index.d.ts.map +1 -1
  121. package/dist/types/index.js +2 -0
  122. package/dist/types/index.js.map +1 -1
  123. package/dist/types/intent-dictionary.d.ts +525 -0
  124. package/dist/types/intent-dictionary.d.ts.map +1 -0
  125. package/dist/types/intent-dictionary.js +11 -0
  126. package/dist/types/intent-dictionary.js.map +1 -0
  127. package/dist/types/role-permissions.d.ts +167 -0
  128. package/dist/types/role-permissions.d.ts.map +1 -0
  129. package/dist/types/role-permissions.js +9 -0
  130. package/dist/types/role-permissions.js.map +1 -0
  131. package/dist/utils/contextEnricher.d.ts +41 -0
  132. package/dist/utils/contextEnricher.d.ts.map +1 -0
  133. package/dist/utils/contextEnricher.js +327 -0
  134. package/dist/utils/contextEnricher.js.map +1 -0
  135. package/dist/utils/designFallbacks.d.ts +48 -0
  136. package/dist/utils/designFallbacks.d.ts.map +1 -0
  137. package/dist/utils/designFallbacks.js +885 -0
  138. package/dist/utils/designFallbacks.js.map +1 -0
  139. package/dist/utils/designManifestManager.d.ts +89 -0
  140. package/dist/utils/designManifestManager.d.ts.map +1 -0
  141. package/dist/utils/designManifestManager.js +533 -0
  142. package/dist/utils/designManifestManager.js.map +1 -0
  143. package/dist/utils/designPipelineStateManager.d.ts +63 -0
  144. package/dist/utils/designPipelineStateManager.d.ts.map +1 -0
  145. package/dist/utils/designPipelineStateManager.js +174 -0
  146. package/dist/utils/designPipelineStateManager.js.map +1 -0
  147. package/dist/utils/envExampleGenerator.d.ts.map +1 -1
  148. package/dist/utils/envExampleGenerator.js +41 -171
  149. package/dist/utils/envExampleGenerator.js.map +1 -1
  150. package/dist/utils/featureBundleManager.d.ts +90 -0
  151. package/dist/utils/featureBundleManager.d.ts.map +1 -0
  152. package/dist/utils/featureBundleManager.js +340 -0
  153. package/dist/utils/featureBundleManager.js.map +1 -0
  154. package/dist/utils/githubCloner.d.ts +93 -0
  155. package/dist/utils/githubCloner.d.ts.map +1 -0
  156. package/dist/utils/githubCloner.js +305 -0
  157. package/dist/utils/githubCloner.js.map +1 -0
  158. package/dist/utils/hybridAIClient.d.ts.map +1 -1
  159. package/dist/utils/hybridAIClient.js +21 -0
  160. package/dist/utils/hybridAIClient.js.map +1 -1
  161. package/dist/utils/openRouterClient.d.ts +10 -0
  162. package/dist/utils/openRouterClient.d.ts.map +1 -0
  163. package/dist/utils/openRouterClient.js +61 -0
  164. package/dist/utils/openRouterClient.js.map +1 -0
  165. package/dist/utils/rolePermissionMapper.d.ts +89 -0
  166. package/dist/utils/rolePermissionMapper.d.ts.map +1 -0
  167. package/dist/utils/rolePermissionMapper.js +337 -0
  168. package/dist/utils/rolePermissionMapper.js.map +1 -0
  169. package/dist/utils/unifiedDesignContextLoader.d.ts +76 -0
  170. package/dist/utils/unifiedDesignContextLoader.d.ts.map +1 -0
  171. package/dist/utils/unifiedDesignContextLoader.js +344 -0
  172. package/dist/utils/unifiedDesignContextLoader.js.map +1 -0
  173. package/package.json +12 -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