@schemyx/mcp 0.1.0 → 0.1.1
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 +76 -2
- package/dist/backend-client.d.ts +1 -17
- package/dist/backend-client.js +15 -91
- package/dist/backend-client.js.map +1 -1
- package/dist/client/backend-client.d.ts +17 -0
- package/dist/client/backend-client.js +97 -0
- package/dist/client/backend-client.js.map +1 -0
- package/dist/client/local-theme-source.d.ts +19 -0
- package/dist/client/local-theme-source.js +737 -0
- package/dist/client/local-theme-source.js.map +1 -0
- package/dist/client/mcp-client.d.ts +15 -0
- package/dist/client/mcp-client.js +46 -0
- package/dist/client/mcp-client.js.map +1 -0
- package/dist/codebase-scanner/backend.d.ts +12 -0
- package/dist/codebase-scanner/backend.js +814 -0
- package/dist/codebase-scanner/backend.js.map +1 -0
- package/dist/codebase-scanner/bundle.d.ts +81 -0
- package/dist/codebase-scanner/bundle.js +2177 -0
- package/dist/codebase-scanner/bundle.js.map +1 -0
- package/dist/codebase-scanner/constants.d.ts +26 -0
- package/dist/codebase-scanner/constants.js +230 -0
- package/dist/codebase-scanner/constants.js.map +1 -0
- package/dist/codebase-scanner/extractors.d.ts +187 -0
- package/dist/codebase-scanner/extractors.js +2600 -0
- package/dist/codebase-scanner/extractors.js.map +1 -0
- package/dist/codebase-scanner/files.d.ts +16 -0
- package/dist/codebase-scanner/files.js +233 -0
- package/dist/codebase-scanner/files.js.map +1 -0
- package/dist/codebase-scanner/index.d.ts +217 -0
- package/dist/codebase-scanner/index.js +387 -0
- package/dist/codebase-scanner/index.js.map +1 -0
- package/dist/codebase-scanner/recipes.d.ts +74 -0
- package/dist/codebase-scanner/recipes.js +585 -0
- package/dist/codebase-scanner/recipes.js.map +1 -0
- package/dist/codebase-scanner/storage.d.ts +19 -0
- package/dist/codebase-scanner/storage.js +103 -0
- package/dist/codebase-scanner/storage.js.map +1 -0
- package/dist/codebase-scanner/types.d.ts +522 -0
- package/dist/codebase-scanner/types.js +3 -0
- package/dist/codebase-scanner/types.js.map +1 -0
- package/dist/codebase-scanner/utils.d.ts +37 -0
- package/dist/codebase-scanner/utils.js +259 -0
- package/dist/codebase-scanner/utils.js.map +1 -0
- package/dist/codebase-scanner.d.ts +1 -0
- package/dist/codebase-scanner.js +18 -0
- package/dist/codebase-scanner.js.map +1 -0
- package/dist/config.d.ts +1 -2
- package/dist/config.js +15 -37
- package/dist/config.js.map +1 -1
- package/dist/local-theme-source.d.ts +1 -0
- package/dist/local-theme-source.js +18 -0
- package/dist/local-theme-source.js.map +1 -0
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/mcp-client.d.ts +1 -0
- package/dist/mcp-client.js +18 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/prompts.d.ts +1 -7
- package/dist/prompts.js +15 -52
- package/dist/prompts.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +163 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/prompts.d.ts +7 -0
- package/dist/server/prompts.js +55 -0
- package/dist/server/prompts.js.map +1 -0
- package/dist/server/tool-definitions.d.ts +22 -0
- package/dist/server/tool-definitions.js +531 -0
- package/dist/server/tool-definitions.js.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +33 -0
- package/dist/server.js.map +1 -1
- package/dist/shared/config.d.ts +2 -0
- package/dist/shared/config.js +54 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/text.d.ts +14 -0
- package/dist/shared/text.js +33 -0
- package/dist/shared/text.js.map +1 -0
- package/dist/shared/types.d.ts +118 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/uri.d.ts +6 -0
- package/dist/shared/uri.js +24 -0
- package/dist/shared/uri.js.map +1 -0
- package/dist/style-recipes.d.ts +1 -0
- package/dist/style-recipes.js +18 -0
- package/dist/style-recipes.js.map +1 -0
- package/dist/text.d.ts +1 -14
- package/dist/text.js +15 -30
- package/dist/text.js.map +1 -1
- package/dist/theme/style-recipes.d.ts +26 -0
- package/dist/theme/style-recipes.js +129 -0
- package/dist/theme/style-recipes.js.map +1 -0
- package/dist/tool-definitions.d.ts +1 -11
- package/dist/tool-definitions.js +15 -127
- package/dist/tool-definitions.js.map +1 -1
- package/dist/types.d.ts +1 -106
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/uri.d.ts +1 -6
- package/dist/uri.js +15 -21
- package/dist/uri.js.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { CodebaseBundle, CodebaseManifest, Confidence, ContextDepth, GraphEdge, PatternDecision, PatternScores, RecipeGroup, RecipeRecord, RecipeStatus, RegistryEntry, ReviewItem, SearchMatch } from './types';
|
|
2
|
+
export declare function toRecipeRecord(entry: RegistryEntry): RecipeRecord;
|
|
3
|
+
export declare function groupRecipes(recipes: RecipeRecord[]): Map<RecipeGroup, RecipeRecord[]>;
|
|
4
|
+
export declare function toManifest(bundle: CodebaseBundle): CodebaseManifest;
|
|
5
|
+
export declare function toGroupsIndex(recipesByGroup: Map<RecipeGroup, RecipeRecord[]>): {
|
|
6
|
+
v: string;
|
|
7
|
+
groups: {
|
|
8
|
+
[k: string]: {
|
|
9
|
+
count: number;
|
|
10
|
+
recipeFile: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare function toAliasesIndex(recipes: RecipeRecord[]): {
|
|
15
|
+
v: string;
|
|
16
|
+
aliases: Record<string, string[]>;
|
|
17
|
+
};
|
|
18
|
+
export declare function toTermsIndex(recipes: RecipeRecord[]): {
|
|
19
|
+
v: string;
|
|
20
|
+
terms: Record<string, string[]>;
|
|
21
|
+
};
|
|
22
|
+
export declare function recipeAliases(recipe: RecipeRecord): string[];
|
|
23
|
+
export declare function recipeTerms(recipe: RecipeRecord): string[];
|
|
24
|
+
export declare function readJsonLines<T>(filePath: string): Promise<T[]>;
|
|
25
|
+
export declare function readAllRecipes(outputDir: string): Promise<RecipeRecord[]>;
|
|
26
|
+
export declare function scoreRecipe(recipe: RecipeRecord, exactKey: string | undefined, terms: string[]): number;
|
|
27
|
+
export declare function toSearchMatch(recipe: RecipeRecord, score: number): SearchMatch;
|
|
28
|
+
export declare function readPatternScores(value: unknown): PatternScores | undefined;
|
|
29
|
+
export declare function readPatternDecision(value: unknown): PatternDecision | undefined;
|
|
30
|
+
export declare function numberScoreField(value: unknown): number | undefined;
|
|
31
|
+
export declare function unitScoreField(value: unknown): number | undefined;
|
|
32
|
+
export declare function resolveSearchStatus(matches: SearchMatch[], exactKeyLookup: boolean, reviewItems: ReviewItem[]): {
|
|
33
|
+
status: string;
|
|
34
|
+
resolvedKey?: string;
|
|
35
|
+
confidence: Confidence | 'none';
|
|
36
|
+
why: string[];
|
|
37
|
+
reviewItem?: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function statusWeight(status: RecipeStatus): number;
|
|
40
|
+
export declare function confidenceWeight(confidence: Confidence): number;
|
|
41
|
+
export declare function normalizeDepth(value: unknown): ContextDepth;
|
|
42
|
+
export declare function toRelatedRecipe(recipe: RecipeRecord): {
|
|
43
|
+
k: string;
|
|
44
|
+
s: string;
|
|
45
|
+
p: string | undefined;
|
|
46
|
+
status: RecipeStatus;
|
|
47
|
+
confidence: Confidence;
|
|
48
|
+
};
|
|
49
|
+
export declare function addPackRecipe(selected: Map<string, {
|
|
50
|
+
recipe: RecipeRecord;
|
|
51
|
+
why: string;
|
|
52
|
+
}>, recipe: RecipeRecord, why: string): void;
|
|
53
|
+
export declare function trimRecipeForDepth(recipe: RecipeRecord, depth: ContextDepth): RecipeRecord | {
|
|
54
|
+
k: string;
|
|
55
|
+
g: "file" | "component" | "hook" | "route" | "api" | "controller" | "service" | "module" | "model" | "style" | "dependency" | "cluster" | "rule";
|
|
56
|
+
s: string;
|
|
57
|
+
d: string[];
|
|
58
|
+
u: string[];
|
|
59
|
+
p: string | undefined;
|
|
60
|
+
status: RecipeStatus;
|
|
61
|
+
confidence: Confidence;
|
|
62
|
+
v: Record<string, unknown>;
|
|
63
|
+
} | {
|
|
64
|
+
k: string;
|
|
65
|
+
g: "file" | "component" | "hook" | "route" | "api" | "controller" | "service" | "module" | "model" | "style" | "dependency" | "cluster" | "rule";
|
|
66
|
+
s: string;
|
|
67
|
+
p: string | undefined;
|
|
68
|
+
status: RecipeStatus;
|
|
69
|
+
confidence: Confidence;
|
|
70
|
+
d?: undefined;
|
|
71
|
+
u?: undefined;
|
|
72
|
+
v?: undefined;
|
|
73
|
+
};
|
|
74
|
+
export declare function collectGraphNeighborhood(key: string, edges: GraphEdge[], direction: string, radius: number): Set<string>;
|
|
@@ -0,0 +1,585 @@
|
|
|
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: 'styles', version: '1.1.0' },
|
|
76
|
+
{ id: 'css.rules', version: '1.0.0' },
|
|
77
|
+
{ id: 'theme.tokens', version: '1.0.0' },
|
|
78
|
+
{ id: 'class.expressions', version: '1.0.0' },
|
|
79
|
+
{ id: 'component-style-definitions', version: '1.0.0' },
|
|
80
|
+
{ id: 'web.templates', version: '1.0.0' },
|
|
81
|
+
{ id: 'php-web', version: '1.0.0' },
|
|
82
|
+
{ id: 'python-web', version: '1.0.0' },
|
|
83
|
+
{ id: 'ruby-web', version: '1.0.0' },
|
|
84
|
+
{ id: 'sql', version: '1.0.0' },
|
|
85
|
+
{ id: 'ui.patterns', version: '1.2.0' },
|
|
86
|
+
{ id: 'ui.composition', version: '1.0.0' },
|
|
87
|
+
{ id: 'connected.patterns', version: '1.0.0' },
|
|
88
|
+
{ id: 'concept.cluster', version: '1.0.0' },
|
|
89
|
+
{ id: 'dataflow.model-reference', version: '1.0.0' },
|
|
90
|
+
],
|
|
91
|
+
stats: {
|
|
92
|
+
files: bundle.files.length,
|
|
93
|
+
nodes: bundle.nodes.length,
|
|
94
|
+
edges: bundle.edges.length,
|
|
95
|
+
recipes: bundle.entries.length,
|
|
96
|
+
reviewItems: bundle.reviewItems.length,
|
|
97
|
+
},
|
|
98
|
+
paths: {
|
|
99
|
+
nodes: 'graph/nodes.jsonl',
|
|
100
|
+
edges: 'graph/edges.jsonl',
|
|
101
|
+
groups: 'index/groups.json',
|
|
102
|
+
aliases: 'index/aliases.json',
|
|
103
|
+
terms: 'index/terms.json',
|
|
104
|
+
review: 'review/items.json',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function toGroupsIndex(recipesByGroup) {
|
|
109
|
+
return {
|
|
110
|
+
v: constants_1.indexSchemaVersion,
|
|
111
|
+
groups: Object.fromEntries(constants_1.recipeGroups.map((group) => [
|
|
112
|
+
group,
|
|
113
|
+
{
|
|
114
|
+
count: recipesByGroup.get(group)?.length ?? 0,
|
|
115
|
+
recipeFile: `recipes/${group}.jsonl`,
|
|
116
|
+
},
|
|
117
|
+
])),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function toAliasesIndex(recipes) {
|
|
121
|
+
const aliases = Object.create(null);
|
|
122
|
+
for (const recipe of recipes) {
|
|
123
|
+
for (const alias of recipeAliases(recipe)) {
|
|
124
|
+
const existing = Object.prototype.hasOwnProperty.call(aliases, alias) ? aliases[alias] : [];
|
|
125
|
+
aliases[alias] = (0, utils_1.unique)([...existing, recipe.k]).sort();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
v: constants_1.indexSchemaVersion,
|
|
130
|
+
aliases,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function toTermsIndex(recipes) {
|
|
134
|
+
const terms = Object.create(null);
|
|
135
|
+
for (const recipe of recipes) {
|
|
136
|
+
for (const term of recipeTerms(recipe)) {
|
|
137
|
+
const existing = Object.prototype.hasOwnProperty.call(terms, term) ? terms[term] : [];
|
|
138
|
+
terms[term] = (0, utils_1.unique)([...existing, recipe.k]).sort();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
v: constants_1.indexSchemaVersion,
|
|
143
|
+
terms,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function recipeAliases(recipe) {
|
|
147
|
+
return (0, utils_1.unique)([
|
|
148
|
+
recipe.k,
|
|
149
|
+
recipe.k.toLowerCase(),
|
|
150
|
+
typeof recipe.v.name === 'string' ? recipe.v.name : '',
|
|
151
|
+
typeof recipe.v.route === 'string' ? recipe.v.route : '',
|
|
152
|
+
typeof recipe.v.handler === 'string' ? recipe.v.handler : '',
|
|
153
|
+
...recipe.t,
|
|
154
|
+
])
|
|
155
|
+
.map((item) => item.toLowerCase())
|
|
156
|
+
.filter(Boolean);
|
|
157
|
+
}
|
|
158
|
+
function recipeTerms(recipe) {
|
|
159
|
+
return (0, utils_1.unique)([recipe.k, recipe.g, recipe.s, ...recipe.t, JSON.stringify(recipe.v)]
|
|
160
|
+
.join(' ')
|
|
161
|
+
.toLowerCase()
|
|
162
|
+
.split(/[^a-z0-9_-]+/)
|
|
163
|
+
.filter((term) => term.length > 1)).slice(0, 80);
|
|
164
|
+
}
|
|
165
|
+
async function readJsonLines(filePath) {
|
|
166
|
+
try {
|
|
167
|
+
const content = await node_fs_1.promises.readFile(filePath, 'utf8');
|
|
168
|
+
return content
|
|
169
|
+
.split('\n')
|
|
170
|
+
.map((line) => line.trim())
|
|
171
|
+
.filter(Boolean)
|
|
172
|
+
.map((line) => JSON.parse(line));
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function readAllRecipes(outputDir) {
|
|
182
|
+
const grouped = await Promise.all(constants_1.recipeGroups.map((group) => readJsonLines(path.join(outputDir, 'recipes', `${group}.jsonl`))));
|
|
183
|
+
return grouped.flat();
|
|
184
|
+
}
|
|
185
|
+
function scoreRecipe(recipe, exactKey, terms) {
|
|
186
|
+
if (exactKey && recipe.k === exactKey) {
|
|
187
|
+
return 100;
|
|
188
|
+
}
|
|
189
|
+
if (!terms.length) {
|
|
190
|
+
return statusWeight(recipe.status) + confidenceWeight(recipe.confidence);
|
|
191
|
+
}
|
|
192
|
+
const serializedValue = JSON.stringify(recipe.v).toLowerCase();
|
|
193
|
+
const haystack = [
|
|
194
|
+
recipe.k,
|
|
195
|
+
recipe.g,
|
|
196
|
+
recipe.s,
|
|
197
|
+
...recipe.t,
|
|
198
|
+
...recipe.d,
|
|
199
|
+
...recipe.u,
|
|
200
|
+
String(recipe.v.name ?? ''),
|
|
201
|
+
String(recipe.v.path ?? ''),
|
|
202
|
+
String(recipe.v.route ?? ''),
|
|
203
|
+
String(recipe.v.handler ?? ''),
|
|
204
|
+
serializedValue.slice(0, 8000),
|
|
205
|
+
]
|
|
206
|
+
.join(' ')
|
|
207
|
+
.toLowerCase();
|
|
208
|
+
let score = statusWeight(recipe.status) + confidenceWeight(recipe.confidence);
|
|
209
|
+
const ruleType = String(recipe.v.ruleType ?? '');
|
|
210
|
+
const patternScores = readPatternScores(recipe.v.scores);
|
|
211
|
+
const patternDecision = readPatternDecision(recipe.v.decision);
|
|
212
|
+
const wantsUiComposition = terms.some((term) => [
|
|
213
|
+
'ui',
|
|
214
|
+
'layout',
|
|
215
|
+
'layouts',
|
|
216
|
+
'page',
|
|
217
|
+
'homepage',
|
|
218
|
+
'home',
|
|
219
|
+
'hero',
|
|
220
|
+
'section',
|
|
221
|
+
'sections',
|
|
222
|
+
'card',
|
|
223
|
+
'cards',
|
|
224
|
+
'surface',
|
|
225
|
+
'surfaces',
|
|
226
|
+
'background',
|
|
227
|
+
'backgrounds',
|
|
228
|
+
'padding',
|
|
229
|
+
'spacing',
|
|
230
|
+
'responsive',
|
|
231
|
+
'mobile',
|
|
232
|
+
'desktop',
|
|
233
|
+
'heading',
|
|
234
|
+
'headings',
|
|
235
|
+
'typography',
|
|
236
|
+
].includes(term));
|
|
237
|
+
if (recipe.g === 'cluster') {
|
|
238
|
+
score += wantsUiComposition ? 4 : 18;
|
|
239
|
+
if (patternScores) {
|
|
240
|
+
score += patternScores.canonical * 8;
|
|
241
|
+
score -= patternScores.oneOffRisk * 5;
|
|
242
|
+
}
|
|
243
|
+
if (patternDecision?.reuse === 'prefer') {
|
|
244
|
+
score += 5;
|
|
245
|
+
}
|
|
246
|
+
else if (patternDecision?.reuse === 'review') {
|
|
247
|
+
score -= 4;
|
|
248
|
+
}
|
|
249
|
+
else if (patternDecision?.reuse === 'avoid') {
|
|
250
|
+
score -= 10;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (ruleType === 'ui-composition') {
|
|
254
|
+
score += wantsUiComposition ? 36 : 14;
|
|
255
|
+
}
|
|
256
|
+
if (ruleType === 'ui-pattern') {
|
|
257
|
+
score += wantsUiComposition ? 16 : 6;
|
|
258
|
+
}
|
|
259
|
+
if (ruleType === 'connected-pattern') {
|
|
260
|
+
const domain = String(recipe.v.domain ?? '');
|
|
261
|
+
const concept = String(recipe.v.concept ?? '');
|
|
262
|
+
const usageCount = typeof recipe.v.usageCount === 'number' ? recipe.v.usageCount : 0;
|
|
263
|
+
const canonical = recipe.v.canonical === true;
|
|
264
|
+
const wantsStyleContract = terms.some((term) => ['style', 'styles', 'styling', 'variant', 'variants', 'primary', 'secondary'].includes(term));
|
|
265
|
+
const wantsConnectedPattern = terms.some((term) => [
|
|
266
|
+
'connected',
|
|
267
|
+
'pattern',
|
|
268
|
+
'patterns',
|
|
269
|
+
'canonical',
|
|
270
|
+
'structure',
|
|
271
|
+
'composition',
|
|
272
|
+
'flow',
|
|
273
|
+
'flows',
|
|
274
|
+
'graph',
|
|
275
|
+
'children',
|
|
276
|
+
'hierarchy',
|
|
277
|
+
].includes(term));
|
|
278
|
+
const wantsBackendPattern = terms.some((term) => [
|
|
279
|
+
'api',
|
|
280
|
+
'route',
|
|
281
|
+
'routes',
|
|
282
|
+
'controller',
|
|
283
|
+
'service',
|
|
284
|
+
'database',
|
|
285
|
+
'model',
|
|
286
|
+
'prisma',
|
|
287
|
+
'backend',
|
|
288
|
+
'flow',
|
|
289
|
+
'flows',
|
|
290
|
+
].includes(term));
|
|
291
|
+
if (wantsStyleContract && !wantsConnectedPattern) {
|
|
292
|
+
score -= 22;
|
|
293
|
+
}
|
|
294
|
+
else if ((domain === 'frontend' && wantsUiComposition) ||
|
|
295
|
+
(domain !== 'frontend' && wantsBackendPattern)) {
|
|
296
|
+
score += 34;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
score += 16;
|
|
300
|
+
}
|
|
301
|
+
if (canonical && (!wantsStyleContract || wantsConnectedPattern)) {
|
|
302
|
+
score += 10;
|
|
303
|
+
}
|
|
304
|
+
if (patternScores && (!wantsStyleContract || wantsConnectedPattern)) {
|
|
305
|
+
score += patternScores.canonical * 14;
|
|
306
|
+
score -= patternScores.oneOffRisk * 10;
|
|
307
|
+
}
|
|
308
|
+
if (!wantsStyleContract || wantsConnectedPattern) {
|
|
309
|
+
if (patternDecision?.reuse === 'prefer') {
|
|
310
|
+
score += 8;
|
|
311
|
+
}
|
|
312
|
+
else if (patternDecision?.reuse === 'review') {
|
|
313
|
+
score -= 6;
|
|
314
|
+
}
|
|
315
|
+
else if (patternDecision?.reuse === 'avoid') {
|
|
316
|
+
score -= 14;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (!wantsStyleContract || wantsConnectedPattern) {
|
|
320
|
+
const conceptTerms = concept.split(/[-_.]+/).filter(Boolean);
|
|
321
|
+
const conceptHits = conceptTerms.filter((term) => terms.includes(term) || terms.includes(`${term}s`)).length;
|
|
322
|
+
if (terms.includes(concept) || terms.includes(`${concept}s`)) {
|
|
323
|
+
score += 16;
|
|
324
|
+
}
|
|
325
|
+
score += conceptHits * 10;
|
|
326
|
+
for (const specificTerm of ['api', 'route', 'controller', 'service', 'model', 'data']) {
|
|
327
|
+
const termRequested = terms.includes(specificTerm);
|
|
328
|
+
const conceptMatchesTerm = conceptTerms.includes(specificTerm) || (specificTerm === 'data' && domain === 'data');
|
|
329
|
+
if (!termRequested) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (conceptMatchesTerm) {
|
|
333
|
+
score += 16;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
score -= 24;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (!wantsStyleContract || wantsConnectedPattern) {
|
|
341
|
+
score += Math.min(usageCount, 12);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const locatorHaystack = [
|
|
345
|
+
recipe.k,
|
|
346
|
+
...recipe.t,
|
|
347
|
+
String(recipe.v.name ?? ''),
|
|
348
|
+
String(recipe.v.path ?? ''),
|
|
349
|
+
]
|
|
350
|
+
.join(' ')
|
|
351
|
+
.toLowerCase();
|
|
352
|
+
const wantsHero = terms.includes('hero') || terms.includes('headline');
|
|
353
|
+
const wantsHomepage = terms.includes('homepage') || terms.includes('home') || terms.includes('landing');
|
|
354
|
+
if (wantsHero && locatorHaystack.includes('hero')) {
|
|
355
|
+
score += 30;
|
|
356
|
+
}
|
|
357
|
+
if (wantsHomepage &&
|
|
358
|
+
ruleType === 'ui-composition' &&
|
|
359
|
+
(locatorHaystack.includes('hero-section') ||
|
|
360
|
+
locatorHaystack.includes('components.sections') ||
|
|
361
|
+
locatorHaystack.includes('route.root') ||
|
|
362
|
+
locatorHaystack.includes('app.page'))) {
|
|
363
|
+
score += 16;
|
|
364
|
+
}
|
|
365
|
+
if (String(recipe.v.kind ?? '') === 'component-style-definition') {
|
|
366
|
+
score += terms.some((term) => ['style', 'styles', 'styling', 'variant', 'variants', 'primary', 'secondary'].includes(term))
|
|
367
|
+
? 28
|
|
368
|
+
: 12;
|
|
369
|
+
}
|
|
370
|
+
for (const term of terms) {
|
|
371
|
+
if (recipe.k.toLowerCase() === term) {
|
|
372
|
+
score += 20;
|
|
373
|
+
}
|
|
374
|
+
else if (recipe.k.toLowerCase().includes(term)) {
|
|
375
|
+
score += 10;
|
|
376
|
+
}
|
|
377
|
+
if (recipe.t.some((tag) => tag.toLowerCase() === term)) {
|
|
378
|
+
score += 8;
|
|
379
|
+
}
|
|
380
|
+
else if (haystack.includes(term)) {
|
|
381
|
+
score += 3;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return score;
|
|
385
|
+
}
|
|
386
|
+
function toSearchMatch(recipe, score) {
|
|
387
|
+
const scores = readPatternScores(recipe.v.scores);
|
|
388
|
+
const decision = readPatternDecision(recipe.v.decision);
|
|
389
|
+
return {
|
|
390
|
+
k: recipe.k,
|
|
391
|
+
g: recipe.g,
|
|
392
|
+
kind: String(recipe.v.kind ?? recipe.g),
|
|
393
|
+
name: typeof recipe.v.name === 'string' ? recipe.v.name : undefined,
|
|
394
|
+
s: recipe.s,
|
|
395
|
+
t: recipe.t,
|
|
396
|
+
d: recipe.d,
|
|
397
|
+
u: recipe.u,
|
|
398
|
+
p: recipe.p ?? (typeof recipe.v.path === 'string' ? recipe.v.path : null),
|
|
399
|
+
confidence: recipe.confidence,
|
|
400
|
+
status: recipe.status,
|
|
401
|
+
score: Number(score.toFixed(2)),
|
|
402
|
+
...(scores ? { scores } : {}),
|
|
403
|
+
...(decision ? { decision } : {}),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function readPatternScores(value) {
|
|
407
|
+
if (!value || typeof value !== 'object') {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
const record = value;
|
|
411
|
+
const appearances = numberScoreField(record.appearances);
|
|
412
|
+
const files = numberScoreField(record.files);
|
|
413
|
+
const routes = numberScoreField(record.routes);
|
|
414
|
+
const structuralSimilarity = unitScoreField(record.structuralSimilarity);
|
|
415
|
+
const styleSimilarity = unitScoreField(record.styleSimilarity);
|
|
416
|
+
const behaviorSimilarity = unitScoreField(record.behaviorSimilarity);
|
|
417
|
+
const canonical = unitScoreField(record.canonical);
|
|
418
|
+
const oneOffRisk = unitScoreField(record.oneOffRisk);
|
|
419
|
+
if (appearances === undefined ||
|
|
420
|
+
files === undefined ||
|
|
421
|
+
routes === undefined ||
|
|
422
|
+
structuralSimilarity === undefined ||
|
|
423
|
+
styleSimilarity === undefined ||
|
|
424
|
+
behaviorSimilarity === undefined ||
|
|
425
|
+
canonical === undefined ||
|
|
426
|
+
oneOffRisk === undefined) {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
appearances,
|
|
431
|
+
files,
|
|
432
|
+
routes,
|
|
433
|
+
owners: (0, utils_1.stringArrayValue)(record.owners).slice(0, 12),
|
|
434
|
+
structuralSimilarity,
|
|
435
|
+
styleSimilarity,
|
|
436
|
+
behaviorSimilarity,
|
|
437
|
+
canonical,
|
|
438
|
+
oneOffRisk,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function readPatternDecision(value) {
|
|
442
|
+
if (!value || typeof value !== 'object') {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
const record = value;
|
|
446
|
+
const reuse = record.reuse;
|
|
447
|
+
const scope = record.scope;
|
|
448
|
+
const reason = record.reason;
|
|
449
|
+
if (reuse !== 'prefer' && reuse !== 'allow' && reuse !== 'avoid' && reuse !== 'review') {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
if (typeof scope !== 'string' || typeof reason !== 'string') {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
return { reuse, scope, reason };
|
|
456
|
+
}
|
|
457
|
+
function numberScoreField(value) {
|
|
458
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
459
|
+
}
|
|
460
|
+
function unitScoreField(value) {
|
|
461
|
+
return typeof value === 'number' && Number.isFinite(value) ? (0, utils_1.clampPatternScore)(value) : undefined;
|
|
462
|
+
}
|
|
463
|
+
function resolveSearchStatus(matches, exactKeyLookup, reviewItems) {
|
|
464
|
+
if (!matches.length) {
|
|
465
|
+
return { status: 'not_found', confidence: 'none', why: [] };
|
|
466
|
+
}
|
|
467
|
+
const first = matches[0];
|
|
468
|
+
const reviewItem = reviewItems.find((item) => item.status === 'open' && item.candidates.includes(first.k));
|
|
469
|
+
const isHighConfidenceStyleContract = first.g === 'style' &&
|
|
470
|
+
first.kind === 'component-style-definition' &&
|
|
471
|
+
first.confidence === 'high';
|
|
472
|
+
if (!isHighConfidenceStyleContract && (first.status === 'needs_review' || reviewItem)) {
|
|
473
|
+
return {
|
|
474
|
+
status: 'needs_review',
|
|
475
|
+
confidence: first.confidence,
|
|
476
|
+
why: ['unresolved-review-item'],
|
|
477
|
+
reviewItem: reviewItem?.id,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (exactKeyLookup || matches.length === 1 || first.status === 'approved' || first.score >= 12) {
|
|
481
|
+
return {
|
|
482
|
+
status: 'resolved',
|
|
483
|
+
resolvedKey: first.k,
|
|
484
|
+
confidence: first.confidence,
|
|
485
|
+
why: exactKeyLookup ? ['exact-key'] : ['best-ranked-match'],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return { status: 'candidates', confidence: first.confidence, why: ['multiple-candidates'] };
|
|
489
|
+
}
|
|
490
|
+
function statusWeight(status) {
|
|
491
|
+
switch (status) {
|
|
492
|
+
case 'approved':
|
|
493
|
+
return 20;
|
|
494
|
+
case 'inferred':
|
|
495
|
+
return 8;
|
|
496
|
+
case 'needs_review':
|
|
497
|
+
return 4;
|
|
498
|
+
case 'stale':
|
|
499
|
+
return 1;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function confidenceWeight(confidence) {
|
|
503
|
+
switch (confidence) {
|
|
504
|
+
case 'high':
|
|
505
|
+
return 6;
|
|
506
|
+
case 'medium':
|
|
507
|
+
return 4;
|
|
508
|
+
case 'low':
|
|
509
|
+
return 2;
|
|
510
|
+
case 'inferred':
|
|
511
|
+
return 1;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function normalizeDepth(value) {
|
|
515
|
+
const depth = (0, utils_1.stringArg)(value);
|
|
516
|
+
return depth === 'standard' || depth === 'deep' ? depth : 'brief';
|
|
517
|
+
}
|
|
518
|
+
function toRelatedRecipe(recipe) {
|
|
519
|
+
return {
|
|
520
|
+
k: recipe.k,
|
|
521
|
+
s: recipe.s,
|
|
522
|
+
p: recipe.p ?? (typeof recipe.v.path === 'string' ? recipe.v.path : undefined),
|
|
523
|
+
status: recipe.status,
|
|
524
|
+
confidence: recipe.confidence,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function addPackRecipe(selected, recipe, why) {
|
|
528
|
+
if (!selected.has(recipe.k)) {
|
|
529
|
+
selected.set(recipe.k, { recipe, why });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function trimRecipeForDepth(recipe, depth) {
|
|
533
|
+
if (depth === 'deep') {
|
|
534
|
+
return recipe;
|
|
535
|
+
}
|
|
536
|
+
if (depth === 'standard') {
|
|
537
|
+
return {
|
|
538
|
+
k: recipe.k,
|
|
539
|
+
g: recipe.g,
|
|
540
|
+
s: recipe.s,
|
|
541
|
+
d: recipe.d,
|
|
542
|
+
u: recipe.u.slice(0, 10),
|
|
543
|
+
p: recipe.p,
|
|
544
|
+
status: recipe.status,
|
|
545
|
+
confidence: recipe.confidence,
|
|
546
|
+
v: recipe.v,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
k: recipe.k,
|
|
551
|
+
g: recipe.g,
|
|
552
|
+
s: recipe.s,
|
|
553
|
+
p: recipe.p,
|
|
554
|
+
status: recipe.status,
|
|
555
|
+
confidence: recipe.confidence,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function collectGraphNeighborhood(key, edges, direction, radius) {
|
|
559
|
+
const selected = new Set([key]);
|
|
560
|
+
let frontier = new Set([key]);
|
|
561
|
+
for (let depth = 0; depth < radius; depth += 1) {
|
|
562
|
+
const next = new Set();
|
|
563
|
+
for (const edge of edges) {
|
|
564
|
+
for (const nodeKey of frontier) {
|
|
565
|
+
const outbound = edge.from === nodeKey && (direction === 'out' || direction === 'both');
|
|
566
|
+
const inbound = edge.to === nodeKey && (direction === 'in' || direction === 'both');
|
|
567
|
+
if (outbound && !selected.has(edge.to)) {
|
|
568
|
+
next.add(edge.to);
|
|
569
|
+
}
|
|
570
|
+
if (inbound && !selected.has(edge.from)) {
|
|
571
|
+
next.add(edge.from);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
for (const item of next) {
|
|
576
|
+
selected.add(item);
|
|
577
|
+
}
|
|
578
|
+
frontier = next;
|
|
579
|
+
if (!frontier.size) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return selected;
|
|
584
|
+
}
|
|
585
|
+
//# sourceMappingURL=recipes.js.map
|