@schemyx/mcp 0.1.0 → 0.1.2

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 (106) hide show
  1. package/README.md +79 -2
  2. package/dist/backend-client.d.ts +1 -17
  3. package/dist/backend-client.js +15 -91
  4. package/dist/backend-client.js.map +1 -1
  5. package/dist/client/backend-client.d.ts +17 -0
  6. package/dist/client/backend-client.js +97 -0
  7. package/dist/client/backend-client.js.map +1 -0
  8. package/dist/client/local-theme-source.d.ts +19 -0
  9. package/dist/client/local-theme-source.js +737 -0
  10. package/dist/client/local-theme-source.js.map +1 -0
  11. package/dist/client/mcp-client.d.ts +15 -0
  12. package/dist/client/mcp-client.js +46 -0
  13. package/dist/client/mcp-client.js.map +1 -0
  14. package/dist/codebase-scanner/backend.d.ts +12 -0
  15. package/dist/codebase-scanner/backend.js +814 -0
  16. package/dist/codebase-scanner/backend.js.map +1 -0
  17. package/dist/codebase-scanner/bundle.d.ts +315 -0
  18. package/dist/codebase-scanner/bundle.js +5195 -0
  19. package/dist/codebase-scanner/bundle.js.map +1 -0
  20. package/dist/codebase-scanner/constants.d.ts +26 -0
  21. package/dist/codebase-scanner/constants.js +231 -0
  22. package/dist/codebase-scanner/constants.js.map +1 -0
  23. package/dist/codebase-scanner/database.d.ts +8 -0
  24. package/dist/codebase-scanner/database.js +1252 -0
  25. package/dist/codebase-scanner/database.js.map +1 -0
  26. package/dist/codebase-scanner/extractors.d.ts +241 -0
  27. package/dist/codebase-scanner/extractors.js +3513 -0
  28. package/dist/codebase-scanner/extractors.js.map +1 -0
  29. package/dist/codebase-scanner/files.d.ts +16 -0
  30. package/dist/codebase-scanner/files.js +250 -0
  31. package/dist/codebase-scanner/files.js.map +1 -0
  32. package/dist/codebase-scanner/index.d.ts +217 -0
  33. package/dist/codebase-scanner/index.js +387 -0
  34. package/dist/codebase-scanner/index.js.map +1 -0
  35. package/dist/codebase-scanner/recipes.d.ts +74 -0
  36. package/dist/codebase-scanner/recipes.js +743 -0
  37. package/dist/codebase-scanner/recipes.js.map +1 -0
  38. package/dist/codebase-scanner/storage.d.ts +19 -0
  39. package/dist/codebase-scanner/storage.js +103 -0
  40. package/dist/codebase-scanner/storage.js.map +1 -0
  41. package/dist/codebase-scanner/types.d.ts +743 -0
  42. package/dist/codebase-scanner/types.js +3 -0
  43. package/dist/codebase-scanner/types.js.map +1 -0
  44. package/dist/codebase-scanner/utils.d.ts +37 -0
  45. package/dist/codebase-scanner/utils.js +259 -0
  46. package/dist/codebase-scanner/utils.js.map +1 -0
  47. package/dist/codebase-scanner.d.ts +1 -0
  48. package/dist/codebase-scanner.js +18 -0
  49. package/dist/codebase-scanner.js.map +1 -0
  50. package/dist/config.d.ts +1 -2
  51. package/dist/config.js +15 -37
  52. package/dist/config.js.map +1 -1
  53. package/dist/local-theme-source.d.ts +1 -0
  54. package/dist/local-theme-source.js +18 -0
  55. package/dist/local-theme-source.js.map +1 -0
  56. package/dist/main.js +3 -3
  57. package/dist/main.js.map +1 -1
  58. package/dist/mcp-client.d.ts +1 -0
  59. package/dist/mcp-client.js +18 -0
  60. package/dist/mcp-client.js.map +1 -0
  61. package/dist/prompts.d.ts +1 -7
  62. package/dist/prompts.js +15 -52
  63. package/dist/prompts.js.map +1 -1
  64. package/dist/server/index.d.ts +4 -0
  65. package/dist/server/index.js +163 -0
  66. package/dist/server/index.js.map +1 -0
  67. package/dist/server/prompts.d.ts +7 -0
  68. package/dist/server/prompts.js +55 -0
  69. package/dist/server/prompts.js.map +1 -0
  70. package/dist/server/tool-definitions.d.ts +22 -0
  71. package/dist/server/tool-definitions.js +531 -0
  72. package/dist/server/tool-definitions.js.map +1 -0
  73. package/dist/server.d.ts +3 -3
  74. package/dist/server.js +33 -0
  75. package/dist/server.js.map +1 -1
  76. package/dist/shared/config.d.ts +2 -0
  77. package/dist/shared/config.js +54 -0
  78. package/dist/shared/config.js.map +1 -0
  79. package/dist/shared/text.d.ts +14 -0
  80. package/dist/shared/text.js +33 -0
  81. package/dist/shared/text.js.map +1 -0
  82. package/dist/shared/types.d.ts +118 -0
  83. package/dist/shared/types.js +3 -0
  84. package/dist/shared/types.js.map +1 -0
  85. package/dist/shared/uri.d.ts +6 -0
  86. package/dist/shared/uri.js +24 -0
  87. package/dist/shared/uri.js.map +1 -0
  88. package/dist/style-recipes.d.ts +1 -0
  89. package/dist/style-recipes.js +18 -0
  90. package/dist/style-recipes.js.map +1 -0
  91. package/dist/text.d.ts +1 -14
  92. package/dist/text.js +15 -30
  93. package/dist/text.js.map +1 -1
  94. package/dist/theme/style-recipes.d.ts +26 -0
  95. package/dist/theme/style-recipes.js +129 -0
  96. package/dist/theme/style-recipes.js.map +1 -0
  97. package/dist/tool-definitions.d.ts +1 -11
  98. package/dist/tool-definitions.js +15 -127
  99. package/dist/tool-definitions.js.map +1 -1
  100. package/dist/types.d.ts +1 -106
  101. package/dist/types.js +15 -0
  102. package/dist/types.js.map +1 -1
  103. package/dist/uri.d.ts +1 -6
  104. package/dist/uri.js +15 -21
  105. package/dist/uri.js.map +1 -1
  106. package/package.json +5 -2
@@ -0,0 +1,743 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toRecipeRecord = toRecipeRecord;
4
+ exports.groupRecipes = groupRecipes;
5
+ exports.toManifest = toManifest;
6
+ exports.toGroupsIndex = toGroupsIndex;
7
+ exports.toAliasesIndex = toAliasesIndex;
8
+ exports.toTermsIndex = toTermsIndex;
9
+ exports.recipeAliases = recipeAliases;
10
+ exports.recipeTerms = recipeTerms;
11
+ exports.readJsonLines = readJsonLines;
12
+ exports.readAllRecipes = readAllRecipes;
13
+ exports.scoreRecipe = scoreRecipe;
14
+ exports.toSearchMatch = toSearchMatch;
15
+ exports.readPatternScores = readPatternScores;
16
+ exports.readPatternDecision = readPatternDecision;
17
+ exports.numberScoreField = numberScoreField;
18
+ exports.unitScoreField = unitScoreField;
19
+ exports.resolveSearchStatus = resolveSearchStatus;
20
+ exports.statusWeight = statusWeight;
21
+ exports.confidenceWeight = confidenceWeight;
22
+ exports.normalizeDepth = normalizeDepth;
23
+ exports.toRelatedRecipe = toRelatedRecipe;
24
+ exports.addPackRecipe = addPackRecipe;
25
+ exports.trimRecipeForDepth = trimRecipeForDepth;
26
+ exports.collectGraphNeighborhood = collectGraphNeighborhood;
27
+ const node_fs_1 = require("node:fs");
28
+ const path = require("node:path");
29
+ const constants_1 = require("./constants");
30
+ const utils_1 = require("./utils");
31
+ function toRecipeRecord(entry) {
32
+ return {
33
+ k: entry.key,
34
+ g: entry.group,
35
+ d: entry.dependencies,
36
+ u: entry.usedBy,
37
+ s: entry.summary,
38
+ t: entry.tags,
39
+ ...(entry.path ? { p: entry.path } : {}),
40
+ confidence: entry.confidence,
41
+ status: entry.status,
42
+ v: entry.value,
43
+ };
44
+ }
45
+ function groupRecipes(recipes) {
46
+ const grouped = new Map();
47
+ for (const group of constants_1.recipeGroups) {
48
+ grouped.set(group, []);
49
+ }
50
+ for (const recipe of recipes) {
51
+ grouped.get(recipe.g)?.push(recipe);
52
+ }
53
+ return grouped;
54
+ }
55
+ function toManifest(bundle) {
56
+ return {
57
+ v: constants_1.bundleSchemaVersion,
58
+ scanId: bundle.scanId,
59
+ rootName: path.basename(bundle.rootPath),
60
+ projectType: bundle.projectType,
61
+ generatedAt: bundle.generatedAt,
62
+ localOnly: true,
63
+ schema: {
64
+ graph: constants_1.graphSchemaVersion,
65
+ index: constants_1.indexSchemaVersion,
66
+ recipe: constants_1.recipeSchemaVersion,
67
+ },
68
+ extractors: [
69
+ { id: 'core.files', version: '1.0.0' },
70
+ { id: 'typescript', version: '1.0.0' },
71
+ { id: 'react', version: '1.0.0' },
72
+ { id: 'nextjs.app-router', version: '1.0.0' },
73
+ { id: 'nestjs', version: '1.0.0' },
74
+ { id: 'prisma', version: '1.0.0' },
75
+ { id: 'database.code', version: '1.0.0' },
76
+ { id: 'styles', version: '1.1.0' },
77
+ { id: 'css.rules', version: '1.0.0' },
78
+ { id: 'theme.tokens', version: '1.0.0' },
79
+ { id: 'class.expressions', version: '1.0.0' },
80
+ { id: 'component-style-definitions', version: '1.0.0' },
81
+ { id: 'web.templates', version: '1.0.0' },
82
+ { id: 'php-web', version: '1.0.0' },
83
+ { id: 'python-web', version: '1.0.0' },
84
+ { id: 'ruby-web', version: '1.0.0' },
85
+ { id: 'sql', version: '1.0.0' },
86
+ { id: 'ui.patterns', version: '1.2.0' },
87
+ { id: 'ui.composition', version: '1.0.0' },
88
+ { id: 'ui.global-contracts', version: '1.0.0' },
89
+ { id: 'connected.patterns', version: '1.0.0' },
90
+ { id: 'concept.cluster', version: '1.0.0' },
91
+ { id: 'dataflow.model-reference', version: '1.0.0' },
92
+ ],
93
+ stats: {
94
+ files: bundle.files.length,
95
+ nodes: bundle.nodes.length,
96
+ edges: bundle.edges.length,
97
+ recipes: bundle.entries.length,
98
+ reviewItems: bundle.reviewItems.length,
99
+ },
100
+ paths: {
101
+ nodes: 'graph/nodes.jsonl',
102
+ edges: 'graph/edges.jsonl',
103
+ groups: 'index/groups.json',
104
+ aliases: 'index/aliases.json',
105
+ terms: 'index/terms.json',
106
+ review: 'review/items.json',
107
+ },
108
+ };
109
+ }
110
+ function toGroupsIndex(recipesByGroup) {
111
+ return {
112
+ v: constants_1.indexSchemaVersion,
113
+ groups: Object.fromEntries(constants_1.recipeGroups.map((group) => [
114
+ group,
115
+ {
116
+ count: recipesByGroup.get(group)?.length ?? 0,
117
+ recipeFile: `recipes/${group}.jsonl`,
118
+ },
119
+ ])),
120
+ };
121
+ }
122
+ function toAliasesIndex(recipes) {
123
+ const aliases = Object.create(null);
124
+ for (const recipe of recipes) {
125
+ for (const alias of recipeAliases(recipe)) {
126
+ const existing = Object.prototype.hasOwnProperty.call(aliases, alias) ? aliases[alias] : [];
127
+ aliases[alias] = (0, utils_1.unique)([...existing, recipe.k]).sort();
128
+ }
129
+ }
130
+ return {
131
+ v: constants_1.indexSchemaVersion,
132
+ aliases,
133
+ };
134
+ }
135
+ function toTermsIndex(recipes) {
136
+ const terms = Object.create(null);
137
+ for (const recipe of recipes) {
138
+ for (const term of recipeTerms(recipe)) {
139
+ const existing = Object.prototype.hasOwnProperty.call(terms, term) ? terms[term] : [];
140
+ terms[term] = (0, utils_1.unique)([...existing, recipe.k]).sort();
141
+ }
142
+ }
143
+ return {
144
+ v: constants_1.indexSchemaVersion,
145
+ terms,
146
+ };
147
+ }
148
+ function recipeAliases(recipe) {
149
+ return (0, utils_1.unique)([
150
+ recipe.k,
151
+ recipe.k.toLowerCase(),
152
+ typeof recipe.v.name === 'string' ? recipe.v.name : '',
153
+ typeof recipe.v.tableName === 'string' ? recipe.v.tableName : '',
154
+ typeof recipe.v.model === 'string' ? recipe.v.model : '',
155
+ typeof recipe.v.table === 'string' ? recipe.v.table : '',
156
+ typeof recipe.v.route === 'string' ? recipe.v.route : '',
157
+ typeof recipe.v.handler === 'string' ? recipe.v.handler : '',
158
+ ...recipe.t,
159
+ ])
160
+ .map((item) => item.toLowerCase())
161
+ .filter(Boolean);
162
+ }
163
+ function recipeTerms(recipe) {
164
+ return (0, utils_1.unique)([recipe.k, recipe.g, recipe.s, ...recipe.t, JSON.stringify(recipe.v)]
165
+ .join(' ')
166
+ .toLowerCase()
167
+ .split(/[^a-z0-9_-]+/)
168
+ .filter((term) => term.length > 1)).slice(0, 80);
169
+ }
170
+ async function readJsonLines(filePath) {
171
+ try {
172
+ const content = await node_fs_1.promises.readFile(filePath, 'utf8');
173
+ return content
174
+ .split('\n')
175
+ .map((line) => line.trim())
176
+ .filter(Boolean)
177
+ .map((line) => JSON.parse(line));
178
+ }
179
+ catch (error) {
180
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
181
+ return [];
182
+ }
183
+ throw error;
184
+ }
185
+ }
186
+ async function readAllRecipes(outputDir) {
187
+ const grouped = await Promise.all(constants_1.recipeGroups.map((group) => readJsonLines(path.join(outputDir, 'recipes', `${group}.jsonl`))));
188
+ return grouped.flat();
189
+ }
190
+ function scoreRecipe(recipe, exactKey, terms) {
191
+ if (exactKey && recipe.k === exactKey) {
192
+ return 100;
193
+ }
194
+ if (!terms.length) {
195
+ return statusWeight(recipe.status) + confidenceWeight(recipe.confidence);
196
+ }
197
+ const serializedValue = JSON.stringify(recipe.v).toLowerCase();
198
+ const haystack = [
199
+ recipe.k,
200
+ recipe.g,
201
+ recipe.s,
202
+ ...recipe.t,
203
+ ...recipe.d,
204
+ ...recipe.u,
205
+ String(recipe.v.name ?? ''),
206
+ String(recipe.v.path ?? ''),
207
+ String(recipe.v.route ?? ''),
208
+ String(recipe.v.handler ?? ''),
209
+ serializedValue.slice(0, 8000),
210
+ ]
211
+ .join(' ')
212
+ .toLowerCase();
213
+ let score = statusWeight(recipe.status) + confidenceWeight(recipe.confidence);
214
+ const ruleType = String(recipe.v.ruleType ?? '');
215
+ const patternScores = readPatternScores(recipe.v.scores);
216
+ const patternDecision = readPatternDecision(recipe.v.decision);
217
+ const wantsUiComposition = terms.some((term) => [
218
+ 'ui',
219
+ 'layout',
220
+ 'layouts',
221
+ 'page',
222
+ 'homepage',
223
+ 'home',
224
+ 'hero',
225
+ 'section',
226
+ 'sections',
227
+ 'card',
228
+ 'cards',
229
+ 'surface',
230
+ 'surfaces',
231
+ 'background',
232
+ 'backgrounds',
233
+ 'padding',
234
+ 'spacing',
235
+ 'responsive',
236
+ 'mobile',
237
+ 'desktop',
238
+ 'heading',
239
+ 'headings',
240
+ 'typography',
241
+ 'scale',
242
+ 'breakpoint',
243
+ 'breakpoints',
244
+ 'wrap',
245
+ 'nowrap',
246
+ 'no-wrap',
247
+ 'overflow',
248
+ 'parent',
249
+ 'parents',
250
+ 'badge',
251
+ 'badges',
252
+ 'chip',
253
+ 'chips',
254
+ 'eyebrow',
255
+ 'eyebrows',
256
+ 'brand',
257
+ 'lockup',
258
+ 'compound',
259
+ 'icon',
260
+ 'icons',
261
+ 'divider',
262
+ 'descriptor',
263
+ ].includes(term));
264
+ if (recipe.g === 'cluster') {
265
+ score += wantsUiComposition ? 4 : 18;
266
+ if (patternScores) {
267
+ score += patternScores.canonical * 8;
268
+ score -= patternScores.oneOffRisk * 5;
269
+ }
270
+ if (patternDecision?.reuse === 'prefer') {
271
+ score += 5;
272
+ }
273
+ else if (patternDecision?.reuse === 'review') {
274
+ score -= 4;
275
+ }
276
+ else if (patternDecision?.reuse === 'avoid') {
277
+ score -= 10;
278
+ }
279
+ }
280
+ if (ruleType === 'ui-composition') {
281
+ score += wantsUiComposition ? 36 : 14;
282
+ }
283
+ if (ruleType === 'ui-pattern') {
284
+ score += wantsUiComposition ? 16 : 6;
285
+ }
286
+ if (ruleType === 'ui-page-shell') {
287
+ const wantsPageShellContract = terms.some((term) => [
288
+ 'app-shell',
289
+ 'shell',
290
+ 'page-shell',
291
+ 'body',
292
+ 'background',
293
+ 'backgrounds',
294
+ 'bg-background',
295
+ 'route',
296
+ 'root',
297
+ 'transparent',
298
+ 'gradient',
299
+ 'glow',
300
+ ].includes(term));
301
+ score += wantsPageShellContract ? 92 : wantsUiComposition ? 44 : 12;
302
+ }
303
+ if (ruleType === 'ui-framed-surface') {
304
+ const wantsFramedSurfaceContract = terms.some((term) => [
305
+ 'section-frame',
306
+ 'context-surface',
307
+ 'frame',
308
+ 'framed',
309
+ 'surface',
310
+ 'surfaces',
311
+ 'grid',
312
+ 'overlay',
313
+ 'nested',
314
+ 'card',
315
+ 'cards',
316
+ 'panel',
317
+ 'panels',
318
+ 'pseudo',
319
+ ].includes(term));
320
+ score += wantsFramedSurfaceContract ? 92 : wantsUiComposition ? 44 : 12;
321
+ }
322
+ if (ruleType === 'connected-pattern') {
323
+ const domain = String(recipe.v.domain ?? '');
324
+ const concept = String(recipe.v.concept ?? '');
325
+ const usageCount = typeof recipe.v.usageCount === 'number' ? recipe.v.usageCount : 0;
326
+ const canonical = recipe.v.canonical === true;
327
+ const wantsStyleContract = terms.some((term) => [
328
+ 'style',
329
+ 'styles',
330
+ 'styling',
331
+ 'variant',
332
+ 'variants',
333
+ 'primary',
334
+ 'secondary',
335
+ 'button',
336
+ 'buttons',
337
+ 'card',
338
+ 'cards',
339
+ 'scale',
340
+ 'wrap',
341
+ 'nowrap',
342
+ 'overflow',
343
+ 'eyebrow',
344
+ 'badge',
345
+ 'chip',
346
+ 'brand',
347
+ 'lockup',
348
+ 'compound',
349
+ 'icon',
350
+ 'divider',
351
+ ].includes(term));
352
+ const wantsConnectedPattern = terms.some((term) => [
353
+ 'connected',
354
+ 'pattern',
355
+ 'patterns',
356
+ 'canonical',
357
+ 'structure',
358
+ 'composition',
359
+ 'flow',
360
+ 'flows',
361
+ 'graph',
362
+ 'children',
363
+ 'hierarchy',
364
+ 'parent',
365
+ 'parents',
366
+ 'responsive',
367
+ 'breakpoint',
368
+ 'breakpoints',
369
+ 'safety',
370
+ 'layout-safety',
371
+ 'scale',
372
+ 'compound',
373
+ 'brand',
374
+ 'lockup',
375
+ 'eyebrow',
376
+ 'badge',
377
+ 'chip',
378
+ 'icon',
379
+ 'divider',
380
+ ].includes(term));
381
+ const wantsBackendPattern = terms.some((term) => [
382
+ 'api',
383
+ 'route',
384
+ 'routes',
385
+ 'controller',
386
+ 'service',
387
+ 'database',
388
+ 'model',
389
+ 'prisma',
390
+ 'backend',
391
+ 'flow',
392
+ 'flows',
393
+ ].includes(term));
394
+ if (wantsStyleContract && !wantsConnectedPattern) {
395
+ score -= 22;
396
+ }
397
+ else if ((domain === 'frontend' && wantsUiComposition) ||
398
+ (domain !== 'frontend' && wantsBackendPattern)) {
399
+ score += 34;
400
+ }
401
+ else {
402
+ score += 16;
403
+ }
404
+ if (canonical && (!wantsStyleContract || wantsConnectedPattern)) {
405
+ score += 10;
406
+ }
407
+ if (patternScores && (!wantsStyleContract || wantsConnectedPattern)) {
408
+ score += patternScores.canonical * 14;
409
+ score -= patternScores.oneOffRisk * 10;
410
+ }
411
+ if (!wantsStyleContract || wantsConnectedPattern) {
412
+ if (patternDecision?.reuse === 'prefer') {
413
+ score += 8;
414
+ }
415
+ else if (patternDecision?.reuse === 'review') {
416
+ score -= 6;
417
+ }
418
+ else if (patternDecision?.reuse === 'avoid') {
419
+ score -= 14;
420
+ }
421
+ }
422
+ if (!wantsStyleContract || wantsConnectedPattern) {
423
+ const conceptTerms = concept.split(/[-_.]+/).filter(Boolean);
424
+ const conceptHits = conceptTerms.filter((term) => terms.includes(term) || terms.includes(`${term}s`)).length;
425
+ if (terms.includes(concept) || terms.includes(`${concept}s`)) {
426
+ score += 16;
427
+ }
428
+ score += conceptHits * 10;
429
+ for (const specificTerm of ['api', 'route', 'controller', 'service', 'model', 'data']) {
430
+ const termRequested = terms.includes(specificTerm);
431
+ const conceptMatchesTerm = conceptTerms.includes(specificTerm) || (specificTerm === 'data' && domain === 'data');
432
+ if (!termRequested) {
433
+ continue;
434
+ }
435
+ if (conceptMatchesTerm) {
436
+ score += 16;
437
+ }
438
+ else {
439
+ score -= 24;
440
+ }
441
+ }
442
+ }
443
+ if (!wantsStyleContract || wantsConnectedPattern) {
444
+ score += Math.min(usageCount, 12);
445
+ }
446
+ }
447
+ const locatorHaystack = [
448
+ recipe.k,
449
+ ...recipe.t,
450
+ String(recipe.v.name ?? ''),
451
+ String(recipe.v.path ?? ''),
452
+ ]
453
+ .join(' ')
454
+ .toLowerCase();
455
+ const wantsHero = terms.includes('hero') || terms.includes('headline');
456
+ const wantsHomepage = terms.includes('homepage') || terms.includes('home') || terms.includes('landing');
457
+ if (wantsHero && locatorHaystack.includes('hero')) {
458
+ score += 30;
459
+ }
460
+ if (wantsHomepage &&
461
+ ruleType === 'ui-composition' &&
462
+ (locatorHaystack.includes('hero-section') ||
463
+ locatorHaystack.includes('components.sections') ||
464
+ locatorHaystack.includes('route.root') ||
465
+ locatorHaystack.includes('app.page'))) {
466
+ score += 16;
467
+ }
468
+ if (String(recipe.v.kind ?? '') === 'component-style-definition') {
469
+ score += terms.some((term) => ['style', 'styles', 'styling', 'variant', 'variants', 'primary', 'secondary'].includes(term))
470
+ ? 28
471
+ : 12;
472
+ }
473
+ if (recipe.g === 'database' || recipe.g === 'model') {
474
+ const wantsDatabaseContract = terms.some((term) => [
475
+ 'database',
476
+ 'data',
477
+ 'db',
478
+ 'schema',
479
+ 'table',
480
+ 'tables',
481
+ 'model',
482
+ 'models',
483
+ 'field',
484
+ 'fields',
485
+ 'column',
486
+ 'columns',
487
+ 'relation',
488
+ 'relations',
489
+ 'foreign',
490
+ 'key',
491
+ 'index',
492
+ 'indexes',
493
+ 'migration',
494
+ 'migrations',
495
+ 'enum',
496
+ 'enums',
497
+ 'prisma',
498
+ 'sql',
499
+ 'drizzle',
500
+ 'typeorm',
501
+ 'sequelize',
502
+ 'mongoose',
503
+ 'read',
504
+ 'write',
505
+ 'writes',
506
+ 'create',
507
+ 'update',
508
+ 'delete',
509
+ 'upsert',
510
+ ].includes(term));
511
+ if (wantsDatabaseContract) {
512
+ score += recipe.g === 'database' ? 32 : 26;
513
+ const kind = String(recipe.v.kind ?? '');
514
+ if (kind === 'database-access' &&
515
+ terms.some((term) => ['write', 'writes', 'update', 'create', 'delete', 'upsert'].includes(term))) {
516
+ score += 18;
517
+ }
518
+ if (kind === 'database-migration' &&
519
+ terms.some((term) => ['migration', 'migrations', 'schema'].includes(term))) {
520
+ score += 18;
521
+ }
522
+ if (kind === 'database-enum' &&
523
+ terms.some((term) => ['enum', 'enums', 'status', 'values'].includes(term))) {
524
+ score += 18;
525
+ }
526
+ }
527
+ }
528
+ for (const term of terms) {
529
+ if (recipe.k.toLowerCase() === term) {
530
+ score += 20;
531
+ }
532
+ else if (recipe.k.toLowerCase().includes(term)) {
533
+ score += 10;
534
+ }
535
+ if (recipe.t.some((tag) => tag.toLowerCase() === term)) {
536
+ score += 8;
537
+ }
538
+ else if (haystack.includes(term)) {
539
+ score += 3;
540
+ }
541
+ }
542
+ return score;
543
+ }
544
+ function toSearchMatch(recipe, score) {
545
+ const scores = readPatternScores(recipe.v.scores);
546
+ const decision = readPatternDecision(recipe.v.decision);
547
+ return {
548
+ k: recipe.k,
549
+ g: recipe.g,
550
+ kind: String(recipe.v.kind ?? recipe.g),
551
+ name: typeof recipe.v.name === 'string' ? recipe.v.name : undefined,
552
+ s: recipe.s,
553
+ t: recipe.t,
554
+ d: recipe.d,
555
+ u: recipe.u,
556
+ p: recipe.p ?? (typeof recipe.v.path === 'string' ? recipe.v.path : null),
557
+ confidence: recipe.confidence,
558
+ status: recipe.status,
559
+ score: Number(score.toFixed(2)),
560
+ ...(scores ? { scores } : {}),
561
+ ...(decision ? { decision } : {}),
562
+ };
563
+ }
564
+ function readPatternScores(value) {
565
+ if (!value || typeof value !== 'object') {
566
+ return undefined;
567
+ }
568
+ const record = value;
569
+ const appearances = numberScoreField(record.appearances);
570
+ const files = numberScoreField(record.files);
571
+ const routes = numberScoreField(record.routes);
572
+ const structuralSimilarity = unitScoreField(record.structuralSimilarity);
573
+ const styleSimilarity = unitScoreField(record.styleSimilarity);
574
+ const behaviorSimilarity = unitScoreField(record.behaviorSimilarity);
575
+ const canonical = unitScoreField(record.canonical);
576
+ const oneOffRisk = unitScoreField(record.oneOffRisk);
577
+ if (appearances === undefined ||
578
+ files === undefined ||
579
+ routes === undefined ||
580
+ structuralSimilarity === undefined ||
581
+ styleSimilarity === undefined ||
582
+ behaviorSimilarity === undefined ||
583
+ canonical === undefined ||
584
+ oneOffRisk === undefined) {
585
+ return undefined;
586
+ }
587
+ return {
588
+ appearances,
589
+ files,
590
+ routes,
591
+ owners: (0, utils_1.stringArrayValue)(record.owners).slice(0, 12),
592
+ structuralSimilarity,
593
+ styleSimilarity,
594
+ behaviorSimilarity,
595
+ canonical,
596
+ oneOffRisk,
597
+ };
598
+ }
599
+ function readPatternDecision(value) {
600
+ if (!value || typeof value !== 'object') {
601
+ return undefined;
602
+ }
603
+ const record = value;
604
+ const reuse = record.reuse;
605
+ const scope = record.scope;
606
+ const reason = record.reason;
607
+ if (reuse !== 'prefer' && reuse !== 'allow' && reuse !== 'avoid' && reuse !== 'review') {
608
+ return undefined;
609
+ }
610
+ if (typeof scope !== 'string' || typeof reason !== 'string') {
611
+ return undefined;
612
+ }
613
+ return { reuse, scope, reason };
614
+ }
615
+ function numberScoreField(value) {
616
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
617
+ }
618
+ function unitScoreField(value) {
619
+ return typeof value === 'number' && Number.isFinite(value) ? (0, utils_1.clampPatternScore)(value) : undefined;
620
+ }
621
+ function resolveSearchStatus(matches, exactKeyLookup, reviewItems) {
622
+ if (!matches.length) {
623
+ return { status: 'not_found', confidence: 'none', why: [] };
624
+ }
625
+ const first = matches[0];
626
+ const reviewItem = reviewItems.find((item) => item.status === 'open' && item.candidates.includes(first.k));
627
+ const isHighConfidenceStyleContract = first.g === 'style' &&
628
+ first.kind === 'component-style-definition' &&
629
+ first.confidence === 'high';
630
+ if (!isHighConfidenceStyleContract && (first.status === 'needs_review' || reviewItem)) {
631
+ return {
632
+ status: 'needs_review',
633
+ confidence: first.confidence,
634
+ why: ['unresolved-review-item'],
635
+ reviewItem: reviewItem?.id,
636
+ };
637
+ }
638
+ if (exactKeyLookup || matches.length === 1 || first.status === 'approved' || first.score >= 12) {
639
+ return {
640
+ status: 'resolved',
641
+ resolvedKey: first.k,
642
+ confidence: first.confidence,
643
+ why: exactKeyLookup ? ['exact-key'] : ['best-ranked-match'],
644
+ };
645
+ }
646
+ return { status: 'candidates', confidence: first.confidence, why: ['multiple-candidates'] };
647
+ }
648
+ function statusWeight(status) {
649
+ switch (status) {
650
+ case 'approved':
651
+ return 20;
652
+ case 'inferred':
653
+ return 8;
654
+ case 'needs_review':
655
+ return 4;
656
+ case 'stale':
657
+ return 1;
658
+ }
659
+ }
660
+ function confidenceWeight(confidence) {
661
+ switch (confidence) {
662
+ case 'high':
663
+ return 6;
664
+ case 'medium':
665
+ return 4;
666
+ case 'low':
667
+ return 2;
668
+ case 'inferred':
669
+ return 1;
670
+ }
671
+ }
672
+ function normalizeDepth(value) {
673
+ const depth = (0, utils_1.stringArg)(value);
674
+ return depth === 'standard' || depth === 'deep' ? depth : 'brief';
675
+ }
676
+ function toRelatedRecipe(recipe) {
677
+ return {
678
+ k: recipe.k,
679
+ s: recipe.s,
680
+ p: recipe.p ?? (typeof recipe.v.path === 'string' ? recipe.v.path : undefined),
681
+ status: recipe.status,
682
+ confidence: recipe.confidence,
683
+ };
684
+ }
685
+ function addPackRecipe(selected, recipe, why) {
686
+ if (!selected.has(recipe.k)) {
687
+ selected.set(recipe.k, { recipe, why });
688
+ }
689
+ }
690
+ function trimRecipeForDepth(recipe, depth) {
691
+ if (depth === 'deep') {
692
+ return recipe;
693
+ }
694
+ if (depth === 'standard') {
695
+ return {
696
+ k: recipe.k,
697
+ g: recipe.g,
698
+ s: recipe.s,
699
+ d: recipe.d,
700
+ u: recipe.u.slice(0, 10),
701
+ p: recipe.p,
702
+ status: recipe.status,
703
+ confidence: recipe.confidence,
704
+ v: recipe.v,
705
+ };
706
+ }
707
+ return {
708
+ k: recipe.k,
709
+ g: recipe.g,
710
+ s: recipe.s,
711
+ p: recipe.p,
712
+ status: recipe.status,
713
+ confidence: recipe.confidence,
714
+ };
715
+ }
716
+ function collectGraphNeighborhood(key, edges, direction, radius) {
717
+ const selected = new Set([key]);
718
+ let frontier = new Set([key]);
719
+ for (let depth = 0; depth < radius; depth += 1) {
720
+ const next = new Set();
721
+ for (const edge of edges) {
722
+ for (const nodeKey of frontier) {
723
+ const outbound = edge.from === nodeKey && (direction === 'out' || direction === 'both');
724
+ const inbound = edge.to === nodeKey && (direction === 'in' || direction === 'both');
725
+ if (outbound && !selected.has(edge.to)) {
726
+ next.add(edge.to);
727
+ }
728
+ if (inbound && !selected.has(edge.from)) {
729
+ next.add(edge.from);
730
+ }
731
+ }
732
+ }
733
+ for (const item of next) {
734
+ selected.add(item);
735
+ }
736
+ frontier = next;
737
+ if (!frontier.size) {
738
+ break;
739
+ }
740
+ }
741
+ return selected;
742
+ }
743
+ //# sourceMappingURL=recipes.js.map