@morya-ui/mcp 0.3.5 → 0.3.7

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/dist/tools.js CHANGED
@@ -5,6 +5,8 @@ import { filterPageSnippets, findPageSnippet, pageSnippets, scorePageSnippet } f
5
5
  import { designRules, findPattern, pagePatterns, scorePattern } from './patterns.js';
6
6
  import { countCatalogResources, countCatalogResourceTemplates } from './resources.js';
7
7
  import { resolveListCraftVariant, resolveStyleDirection, styleResolution, } from './style-presets.js';
8
+ import { codeCoversBlock, codeCoversComponent, mapReferenceBrief, } from './reference-brief.js';
9
+ import { findStyleShell, listStyleShells, styleShells } from './style-shells.js';
8
10
  function inspectButtonIconOnlyUsage(code, issues) {
9
11
  const pairedTagRe = /<MButton\b([^>]*)>([\s\S]*?)<\/MButton>/gi;
10
12
  let match = pairedTagRe.exec(code);
@@ -94,149 +96,149 @@ function generatedPageCode(patternId, intent, locale) {
94
96
  const wizardImports = isWizard ? ', MStepper, MSpace' : '';
95
97
  const settingsImports = isSettings ? ', MTabs' : '';
96
98
  const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
97
- const script = `<script setup lang="ts">
98
- import { ref } from 'vue'
99
- import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
100
-
101
- const loading = ref(false)
102
- const error = ref('')
103
- const title = ${JSON.stringify(title)}
104
- ${isList ? `const keyword = ref('')
105
- const rows = ref<Record<string, unknown>[]>([])
106
- const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
107
- ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
108
- ` : ''}${isDashboard ? `const metrics = ref([
109
- { label: '${zh ? '总用户' : 'Users'}', value: '0' },
110
- { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
111
- ])
112
- ` : ''}${isSettings ? `const activeTab = ref('general')
113
- const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
114
- ` : ''}${isWizard ? `const activeStep = ref(0)
115
- const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
116
- ` : ''}
117
-
118
- async function submit() {
119
- loading.value = true
120
- error.value = ''
121
- try {
122
- // Replace with the page API request.
123
- } finally {
124
- loading.value = false
125
- }
126
- }
99
+ const script = `<script setup lang="ts">
100
+ import { ref } from 'vue'
101
+ import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
102
+
103
+ const loading = ref(false)
104
+ const error = ref('')
105
+ const title = ${JSON.stringify(title)}
106
+ ${isList ? `const keyword = ref('')
107
+ const rows = ref<Record<string, unknown>[]>([])
108
+ const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
109
+ ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
110
+ ` : ''}${isDashboard ? `const metrics = ref([
111
+ { label: '${zh ? '总用户' : 'Users'}', value: '0' },
112
+ { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
113
+ ])
114
+ ` : ''}${isSettings ? `const activeTab = ref('general')
115
+ const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
116
+ ` : ''}${isWizard ? `const activeStep = ref(0)
117
+ const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
118
+ ` : ''}
119
+
120
+ async function submit() {
121
+ loading.value = true
122
+ error.value = ''
123
+ try {
124
+ // Replace with the page API request.
125
+ } finally {
126
+ loading.value = false
127
+ }
128
+ }
127
129
  </script>`;
128
- const listContent = ` <MPageHeader :title="title">
129
- <template #actions>
130
- <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
131
- </template>
132
- </MPageHeader>
133
- <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
134
- <MSpace wrap>
135
- <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
136
- <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
137
- <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
138
- </MSpace>
139
- </MPageFilters>
140
- <MTable :columns="columns" :rows="rows" :loading="loading" fill paginator :rows-per-page="10" striped bordered row-key="id">
141
- <template #cell-status="{ value }">
142
- <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
143
- </template>
144
- <template #empty>
145
- <MEmpty
146
- :title="${zh ? '暂无数据' : 'No data yet'}"
147
- :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
148
- >
149
- <template #extra>
150
- <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
151
- </template>
152
- </MEmpty>
153
- </template>
130
+ const listContent = ` <MPageHeader :title="title">
131
+ <template #actions>
132
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
133
+ </template>
134
+ </MPageHeader>
135
+ <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
136
+ <MSpace wrap>
137
+ <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
138
+ <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
139
+ <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
140
+ </MSpace>
141
+ </MPageFilters>
142
+ <MTable :columns="columns" :rows="rows" :loading="loading" fill paginator :rows-per-page="10" striped bordered row-key="id">
143
+ <template #cell-status="{ value }">
144
+ <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
145
+ </template>
146
+ <template #empty>
147
+ <MEmpty
148
+ :title="${zh ? '暂无数据' : 'No data yet'}"
149
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
150
+ >
151
+ <template #extra>
152
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
153
+ </template>
154
+ </MEmpty>
155
+ </template>
154
156
  </MTable>`;
155
- const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
156
- <MPageSection variant="form">
157
- <MForm @submit.prevent="submit">
158
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
159
- <MInput v-model="model.name" fluid />
160
- </MFormItem>
161
- <MPageSection variant="actions">
162
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
163
- <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
164
- </MPageSection>
165
- </MForm>
157
+ const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
158
+ <MPageSection variant="form">
159
+ <MForm @submit.prevent="submit">
160
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
161
+ <MInput v-model="model.name" fluid />
162
+ </MFormItem>
163
+ <MPageSection variant="actions">
164
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
165
+ <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
166
+ </MPageSection>
167
+ </MForm>
166
168
  </MPageSection>`;
167
- const dashboardContent = ` <MPageHeader :title="title" />
168
- <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
169
- <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
170
- <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
171
- </MGridItem>
172
- </MGrid>
173
- <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
174
- <MSkeleton v-if="loading" height="8rem" />
175
- <MEmpty
176
- v-else
177
- :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
178
- :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
179
- aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
180
- />
169
+ const dashboardContent = ` <MPageHeader :title="title" />
170
+ <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
171
+ <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
172
+ <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
173
+ </MGridItem>
174
+ </MGrid>
175
+ <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
176
+ <MSkeleton v-if="loading" height="8rem" />
177
+ <MEmpty
178
+ v-else
179
+ :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
180
+ :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
181
+ aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
182
+ />
181
183
  </MCard>`;
182
- const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
183
- <template #actions>
184
- <MSpace>
185
- <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
186
- <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
187
- <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
188
- </MSpace>
189
- </template>
190
- </MPageHeader>
191
- <MCard :title="${zh ? '基本信息' : 'Basics'}">
192
- <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
184
+ const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
185
+ <template #actions>
186
+ <MSpace>
187
+ <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
188
+ <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
189
+ <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
190
+ </MSpace>
191
+ </template>
192
+ </MPageHeader>
193
+ <MCard :title="${zh ? '基本信息' : 'Basics'}">
194
+ <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
193
195
  </MCard>`;
194
- const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
195
- <MTabs v-model="activeTab" :tabs="settingTabs">
196
- <template #default="{ activeValue }">
197
- <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
198
- <MForm @submit.prevent="submit">
199
- <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
200
- <MInput v-model="model.name" fluid />
201
- </MFormItem>
202
- <MPageSection variant="actions">
203
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
204
- </MPageSection>
205
- </MForm>
206
- </MPageSection>
207
- </template>
196
+ const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
197
+ <MTabs v-model="activeTab" :tabs="settingTabs">
198
+ <template #default="{ activeValue }">
199
+ <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
200
+ <MForm @submit.prevent="submit">
201
+ <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
202
+ <MInput v-model="model.name" fluid />
203
+ </MFormItem>
204
+ <MPageSection variant="actions">
205
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
206
+ </MPageSection>
207
+ </MForm>
208
+ </MPageSection>
209
+ </template>
208
210
  </MTabs>`;
209
- const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
210
- <MStepper v-model="activeStep" :steps="wizardSteps" linear />
211
- <MPageSection variant="form">
212
- <MForm @submit.prevent="submit">
213
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
214
- <MInput v-model="model.name" fluid />
215
- </MFormItem>
216
- <MPageSection variant="actions">
217
- <MSpace>
218
- <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
219
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
220
- </MSpace>
221
- </MPageSection>
222
- </MForm>
211
+ const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
212
+ <MStepper v-model="activeStep" :steps="wizardSteps" linear />
213
+ <MPageSection variant="form">
214
+ <MForm @submit.prevent="submit">
215
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
216
+ <MInput v-model="model.name" fluid />
217
+ </MFormItem>
218
+ <MPageSection variant="actions">
219
+ <MSpace>
220
+ <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
221
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
222
+ </MSpace>
223
+ </MPageSection>
224
+ </MForm>
223
225
  </MPageSection>`;
224
226
  let innerTemplate = '';
225
227
  if (isList) {
226
- innerTemplate = `<MConfigProvider :locale="zhCN">
227
- <MLayout has-sider fill-viewport>
228
- <MLayoutSider bordered />
229
- <MLayout>
230
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
231
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
232
- </MLayoutHeader>
233
- <MLayoutContent>
234
- <MPageContent fill>
235
- ${listContent}
236
- </MPageContent>
237
- </MLayoutContent>
238
- </MLayout>
239
- </MLayout>
228
+ innerTemplate = `<MConfigProvider :locale="zhCN">
229
+ <MLayout has-sider fill-viewport>
230
+ <MLayoutSider bordered />
231
+ <MLayout>
232
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
233
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
234
+ </MLayoutHeader>
235
+ <MLayoutContent>
236
+ <MPageContent fill>
237
+ ${listContent}
238
+ </MPageContent>
239
+ </MLayoutContent>
240
+ </MLayout>
241
+ </MLayout>
240
242
  </MConfigProvider>`;
241
243
  }
242
244
  else if (useLayoutShell) {
@@ -250,76 +252,76 @@ ${listContent}
250
252
  ? wizardContent
251
253
  : formContent;
252
254
  const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
253
- innerTemplate = `<MConfigProvider :locale="zhCN">
254
- <MLayout fill-viewport>
255
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
256
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
257
- </MLayoutHeader>
258
- <MLayoutContent>
259
- <MPageContent${pageContentAttrs}>
260
- ${content}
261
- </MPageContent>
262
- </MLayoutContent>
263
- </MLayout>
255
+ innerTemplate = `<MConfigProvider :locale="zhCN">
256
+ <MLayout fill-viewport>
257
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
258
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
259
+ </MLayoutHeader>
260
+ <MLayoutContent>
261
+ <MPageContent${pageContentAttrs}>
262
+ ${content}
263
+ </MPageContent>
264
+ </MLayoutContent>
265
+ </MLayout>
264
266
  </MConfigProvider>`;
265
267
  }
266
268
  else if (isAuth) {
267
- innerTemplate = `<MConfigProvider :locale="zhCN">
268
- <main class="m-generated-page m-generated-auth">
269
- <MCard>
270
- <MForm label-position="top" @submit.prevent="submit">
271
- <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
272
- <MInput type="email" fluid />
273
- </MFormItem>
274
- <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
275
- <MInput type="password" fluid />
276
- </MFormItem>
277
- <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
278
- </MForm>
279
- </MCard>
280
- </main>
269
+ innerTemplate = `<MConfigProvider :locale="zhCN">
270
+ <main class="m-generated-page m-generated-auth">
271
+ <MCard>
272
+ <MForm label-position="top" @submit.prevent="submit">
273
+ <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
274
+ <MInput type="email" fluid />
275
+ </MFormItem>
276
+ <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
277
+ <MInput type="password" fluid />
278
+ </MFormItem>
279
+ <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
280
+ </MForm>
281
+ </MCard>
282
+ </main>
281
283
  </MConfigProvider>`;
282
284
  }
283
285
  else if (isEmpty) {
284
- innerTemplate = `<MConfigProvider :locale="zhCN">
285
- <main class="m-generated-page">
286
- <MPageContent>
287
- <MPageHeader :title="title">
288
- <template #actions>
289
- <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
290
- </template>
291
- </MPageHeader>
292
- <MEmpty
293
- :title="${zh ? '暂无内容' : 'Nothing here yet'}"
294
- :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
295
- icon="database"
296
- >
297
- <template #extra>
298
- <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
299
- </template>
300
- </MEmpty>
301
- </MPageContent>
302
- </main>
286
+ innerTemplate = `<MConfigProvider :locale="zhCN">
287
+ <main class="m-generated-page">
288
+ <MPageContent>
289
+ <MPageHeader :title="title">
290
+ <template #actions>
291
+ <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
292
+ </template>
293
+ </MPageHeader>
294
+ <MEmpty
295
+ :title="${zh ? '暂无内容' : 'Nothing here yet'}"
296
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
297
+ icon="database"
298
+ >
299
+ <template #extra>
300
+ <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
301
+ </template>
302
+ </MEmpty>
303
+ </MPageContent>
304
+ </main>
303
305
  </MConfigProvider>`;
304
306
  }
305
307
  else {
306
- innerTemplate = `<MConfigProvider :locale="zhCN">
307
- <main class="m-generated-page">
308
- <MCard>
309
- <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
310
- <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
311
- </MCard>
312
- </main>
308
+ innerTemplate = `<MConfigProvider :locale="zhCN">
309
+ <main class="m-generated-page">
310
+ <MCard>
311
+ <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
312
+ <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
313
+ </MCard>
314
+ </main>
313
315
  </MConfigProvider>`;
314
316
  }
315
- const template = `<template>
316
- ${innerTemplate}
317
- <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
317
+ const template = `<template>
318
+ ${innerTemplate}
319
+ <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
318
320
  </template>`;
319
- const style = `<style scoped>
320
- .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
321
- .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
322
- .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
321
+ const style = `<style scoped>
322
+ .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
323
+ .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
324
+ .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
323
325
  </style>`;
324
326
  return { script, template, style };
325
327
  }
@@ -731,7 +733,11 @@ export function createToolHandlers(catalog = loadCatalog()) {
731
733
  });
732
734
  }
733
735
  const locale = resolveLocale(args.mode);
734
- const styleDirection = resolveStyleDirection({ intent: args.intent, style: args.style });
736
+ const styleFromBrief = args.brief?.style || args.brief?.description;
737
+ const styleDirection = resolveStyleDirection({
738
+ intent: args.intent,
739
+ style: args.style || styleFromBrief,
740
+ });
735
741
  const stylePayload = {
736
742
  resolution: styleDirection.resolution,
737
743
  confidence: styleDirection.confidence,
@@ -740,10 +746,26 @@ export function createToolHandlers(catalog = loadCatalog()) {
740
746
  priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
741
747
  hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
742
748
  note: locale === 'en-US'
743
- ? 'No named style-preset catalog. Follow an explicit user reference/description; if styleDirection.resolution is ask, ask before inventing the look. Prefer snippets for composition; golden pages are optional block-order checks, not aesthetics.'
744
- : '没有命名风格预设表。有明确参考/描述就跟用户;若 styleDirection.resolution 为 ask,先询问再写完整视觉。优先用 snippets 拼装;黄金样例仅可选核对块顺序,不是审美模板。',
749
+ ? 'No named style-preset catalog. Follow an explicit user reference/description; if styleDirection.resolution is ask, ask before inventing the look. Prefer snippets for composition; golden pages are optional block-order checks, not aesthetics. Pass brief for fidelity mapping; density=compact|default|spacious.'
750
+ : '没有命名风格预设表。有明确参考/描述就跟用户;若 styleDirection.resolution 为 ask,先询问再写完整视觉。优先用 snippets 拼装;黄金样例仅可选核对块顺序。传 brief 做还原映射;density=compact|default|spacious。',
745
751
  };
746
- const craftText = [args.style || '', args.intent || ''].join(' ');
752
+ const craftText = [args.style || '', args.intent || '', args.density || '', args.brief?.description || ''].join(' ');
753
+ const density = args.density === 'compact' || args.density === 'dense'
754
+ ? 'compact'
755
+ : args.density === 'spacious'
756
+ ? 'spacious'
757
+ : resolveListCraftVariant(craftText) === 'list-page-dense'
758
+ ? 'compact'
759
+ : 'default';
760
+ const briefMapped = args.brief || args.density || styleFromBrief
761
+ ? mapReferenceBrief({
762
+ ...(args.brief || {}),
763
+ intent: args.intent,
764
+ style: args.style || args.brief?.style,
765
+ density: args.density || args.brief?.density || density,
766
+ surface: args.brief?.surface || args.pageType,
767
+ })
768
+ : null;
747
769
  const resolvedGoldenId = resolveGoldenPageId({
748
770
  patternId: best.pattern.id,
749
771
  canonicalGoldenId: best.pattern.goldenPage,
@@ -754,15 +776,25 @@ export function createToolHandlers(catalog = loadCatalog()) {
754
776
  : best.pattern.goldenPage;
755
777
  let suggestedSnippets = [...(best.pattern.suggestedSnippets ?? [])];
756
778
  if (best.pattern.id === 'admin-list') {
757
- const listCraft = resolveListCraftVariant(craftText);
779
+ const listCraft = density === 'compact' ? 'list-page-dense' : resolveListCraftVariant(craftText);
758
780
  if (listCraft === 'list-page-dense') {
759
781
  suggestedSnippets = suggestedSnippets.map((id) => id === 'list-filters-stack' ? 'list-filters-dense' : id);
760
782
  }
761
783
  }
784
+ if (briefMapped?.suggestedSnippets.length) {
785
+ for (const id of briefMapped.suggestedSnippets) {
786
+ if (!suggestedSnippets.includes(id))
787
+ suggestedSnippets.push(id);
788
+ }
789
+ }
790
+ const shell = briefMapped?.suggestedShellId
791
+ ? findStyleShell(briefMapped.suggestedShellId)
792
+ : undefined;
762
793
  const result = {
763
794
  intent: args.intent,
764
795
  pageType: args.pageType,
765
796
  style: args.style,
797
+ density,
766
798
  matchedPattern: best.pattern.id,
767
799
  title: locale === 'en-US' ? best.pattern.titleEn : best.pattern.title,
768
800
  confidence: best.score,
@@ -776,6 +808,34 @@ export function createToolHandlers(catalog = loadCatalog()) {
776
808
  avoid: best.pattern.avoid,
777
809
  alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
778
810
  suggestedSnippets,
811
+ referenceMapping: briefMapped
812
+ ? {
813
+ surface: briefMapped.surface,
814
+ density: briefMapped.density,
815
+ mapping: briefMapped.mapping.map((item) => ({
816
+ block: item.block,
817
+ snippetId: item.snippetId,
818
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
819
+ })),
820
+ suggestedShellId: briefMapped.suggestedShellId,
821
+ craftCues: locale === 'en-US' ? briefMapped.craftCuesEn : briefMapped.craftCuesZh,
822
+ validationHints: briefMapped.validationHints,
823
+ tokenRoles: briefMapped.tokenRoles.map((item) => ({
824
+ role: item.role,
825
+ token: item.token,
826
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
827
+ })),
828
+ }
829
+ : null,
830
+ styleShell: shell
831
+ ? {
832
+ id: shell.id,
833
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
834
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
835
+ css: shell.css,
836
+ htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
837
+ }
838
+ : null,
779
839
  nextStep: (() => {
780
840
  const goldenId = resolvedGoldenId;
781
841
  const snippets = suggestedSnippets;
@@ -786,24 +846,31 @@ export function createToolHandlers(catalog = loadCatalog()) {
786
846
  : locale === 'en-US'
787
847
  ? ' Compose with get_page_snippet + recommend_component (relatedSnippets).'
788
848
  : ' 用 get_page_snippet + recommend_component(relatedSnippets)拼装。';
789
- const styleHint = locale === 'en-US'
790
- ? styleDirection.resolution === 'ask'
791
- ? ' Style is uncertain — ask the user for a short description or reference before inventing the look.'
792
- : ` Follow styleDirection (${styleDirection.resolution}): “${styleDirection.summary || 'user/reference'}”; honor explicit user words over companions.`
793
- : styleDirection.resolution === 'ask'
794
- ? ' 风格不确定 — 先请用户说明气质或贴参考,再写完整视觉。'
795
- : ` 按 styleDirection(${styleDirection.resolution})落实「${styleDirection.summary || '用户参考/描述'}」;用户明确指定优先于 companion。`;
796
- const goldenHint = goldenId
849
+ const briefHint = briefMapped
797
850
  ? locale === 'en-US'
798
- ? ` Optional: get_golden_page("${goldenId}") only to check whole-page block order (not the default clone target).`
799
- : ` 可选:仅当需核对整页块顺序时再 get_golden_page("${goldenId}")(默认不要整页克隆)。`
800
- : locale === 'en-US'
801
- ? ' No golden page for this pattern — follow get_pattern structure + snippets.'
802
- : ' 该模式暂无黄金样例:按 get_pattern 结构 + snippets 拼装。';
803
- if (locale === 'en-US') {
804
- return `${snippetHint.trim()}${styleHint}${goldenHint} Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page.`;
805
- }
806
- return `${snippetHint.trim()}${styleHint}${goldenHint} 不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
851
+ ? ' Follow referenceMapping block-by-block; pass the same brief to validate_page.'
852
+ : ' 按 referenceMapping 逐块落地;同一 brief 传给 validate_page。'
853
+ : '';
854
+ const shellHint = shell
855
+ ? locale === 'en-US'
856
+ ? ` Optional shell CSS from styleShell id=${shell.id} (or get_style_shells).`
857
+ : ` 可选签名壳 CSS:styleShell id=${shell.id}(或 get_style_shells)。`
858
+ : '';
859
+ const goldenHint = goldenId && args.includeScaffold
860
+ ? locale === 'en-US'
861
+ ? ` Scaffold mirrors ${goldenId} structure only.`
862
+ : ` scaffold 仅镜像 ${goldenId} 结构。`
863
+ : goldenId
864
+ ? locale === 'en-US'
865
+ ? ` Optional: get_golden_page("${goldenId}") only to check whole-page block order.`
866
+ : ` 可选:仅当需核对整页块顺序时再 get_golden_page("${goldenId}")。`
867
+ : locale === 'en-US'
868
+ ? ' Golden pages are optional block-order checks.'
869
+ : ' 黄金样例仅可选核对块顺序。';
870
+ const validateHint = locale === 'en-US'
871
+ ? ' Then always run validate_usage and validate_page (pass the same brief when used).'
872
+ : ' 然后必须跑 validate_usage 与 validate_page(若用了 brief 则原样传入)。';
873
+ return `${snippetHint}${briefHint}${shellHint}${goldenHint}${validateHint}`;
807
874
  })(),
808
875
  };
809
876
  if (args.includeScaffold) {
@@ -940,223 +1007,166 @@ export function createToolHandlers(catalog = loadCatalog()) {
940
1007
  const locale = resolveLocale(args.mode);
941
1008
  const code = args.code || '';
942
1009
  const suggestions = [];
943
- if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
1010
+ const push = (severity, standardId, type, messageZh, messageEn) => {
944
1011
  suggestions.push({
945
- standardId: 'tokens',
946
- type: 'raw-color',
947
- message: locale === 'en-US'
948
- ? 'Standard: prefer --m-* design tokens over hex colors.'
949
- : '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。',
1012
+ severity,
1013
+ standardId,
1014
+ type,
1015
+ message: locale === 'en-US' ? messageEn : messageZh,
950
1016
  });
1017
+ };
1018
+ if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
1019
+ push('craft', 'tokens', 'raw-color', '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。', 'Standard: prefer --m-* design tokens over hex colors.');
951
1020
  }
952
1021
  if (/\brgb\s*\(/i.test(code) && !/--m-/.test(code)) {
953
- suggestions.push({
954
- standardId: 'tokens',
955
- type: 'raw-color',
956
- message: locale === 'en-US'
957
- ? 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.'
958
- : '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。',
959
- });
1022
+ push('craft', 'tokens', 'raw-color', '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。', 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.');
960
1023
  }
961
1024
  if (/<MCard\b[^>]*>[\s\S]*?<MTable\b[^>]*\bbordered\b/i.test(code)) {
962
- suggestions.push({
963
- standardId: 'page-sections',
964
- type: 'double-border',
965
- message: locale === 'en-US'
966
- ? 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.'
967
- : '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。',
968
- });
1025
+ push('craft', 'page-sections', 'double-border', '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。', 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.');
969
1026
  }
970
1027
  if (/<MLayoutContent\b/i.test(code) && !/<MPageContent\b/i.test(code)) {
971
- suggestions.push({
972
- standardId: 'layout-shell',
973
- type: 'missing-page-content',
974
- message: locale === 'en-US'
975
- ? 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.'
976
- : '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。',
977
- });
1028
+ push('craft', 'layout-shell', 'missing-page-content', '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。', 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.');
978
1029
  }
979
1030
  if ((/\bpage[-_](?:list|form|dashboard)?[-_]?(?:filters|toolbar|content|header)\b/i.test(code) ||
980
1031
  /class="[^"]*filters/i.test(code)) &&
981
1032
  !/<MPage(?:Content|Filters|Toolbar|Header|Section|Stat)\b/i.test(code)) {
982
- suggestions.push({
983
- standardId: 'page-sections',
984
- type: 'custom-page-css',
985
- message: locale === 'en-US'
986
- ? 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.'
987
- : '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。',
988
- });
1033
+ push('craft', 'page-sections', 'custom-page-css', '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。', 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.');
989
1034
  }
990
1035
  if (/<MPageFilters\b/i.test(code) && /<MCard\b[^>]*>[\s\S]*?<MPageFilters\b/i.test(code)) {
991
- suggestions.push({
992
- standardId: 'page-sections',
993
- type: 'redundant-wrapper',
994
- message: locale === 'en-US'
995
- ? 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.'
996
- : '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。',
997
- });
1036
+ push('craft', 'page-sections', 'redundant-wrapper', '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。', 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.');
998
1037
  }
999
1038
  if (/<MPageSection\b[^>]*variant=["']form["']/i.test(code) &&
1000
1039
  /<MCard\b[^>]*>[\s\S]*?<MPageSection\b/i.test(code)) {
1001
- suggestions.push({
1002
- standardId: 'page-sections',
1003
- type: 'redundant-wrapper',
1004
- message: locale === 'en-US'
1005
- ? 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.'
1006
- : '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。',
1007
- });
1040
+ push('craft', 'page-sections', 'redundant-wrapper', '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。', 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.');
1008
1041
  }
1009
1042
  if (/<MPageContent\b[^>]*>[\s\S]*?<MPageContent\b/i.test(code)) {
1010
- suggestions.push({
1011
- standardId: 'layout-shell',
1012
- type: 'redundant-wrapper',
1013
- message: locale === 'en-US'
1014
- ? 'Standard: prefer a single MPageContent shell for page padding and section gap.'
1015
- : '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。',
1016
- });
1043
+ push('craft', 'layout-shell', 'redundant-wrapper', '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。', 'Standard: prefer a single MPageContent shell for page padding and section gap.');
1017
1044
  }
1018
1045
  if (/<(?:div|section|main)\b[^>]*style=["'][^"']*(?:padding|gap)\s*:[^"']*["'][^>]*>\s*<(?:MPage(?:Content|Filters|Toolbar|Header|Section)|MTable)\b/i.test(code)) {
1019
- suggestions.push({
1020
- standardId: 'page-sections',
1021
- type: 'redundant-wrapper',
1022
- message: locale === 'en-US'
1023
- ? 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.'
1024
- : '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。',
1025
- });
1046
+ push('craft', 'page-sections', 'redundant-wrapper', '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。', 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.');
1026
1047
  }
1027
1048
  if (/style="[^"]*overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code) ||
1028
1049
  /<style\b[^>]*>[\s\S]*?overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code)) {
1029
- suggestions.push({
1030
- standardId: 'scroll',
1031
- type: 'native-scroll',
1032
- message: locale === 'en-US'
1033
- ? 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.'
1034
- : '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。',
1035
- });
1050
+ push('craft', 'scroll', 'native-scroll', '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。', 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.');
1036
1051
  }
1037
1052
  if (/::-webkit-scrollbar|scrollbar-width\s*:/i.test(code)) {
1038
- suggestions.push({
1039
- standardId: 'scroll',
1040
- type: 'custom-scrollbar-css',
1041
- message: locale === 'en-US'
1042
- ? 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.'
1043
- : '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。',
1044
- });
1053
+ push('craft', 'scroll', 'custom-scrollbar-css', '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。', 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.');
1045
1054
  }
1046
1055
  if (/min-height\s*:\s*100vh/i.test(code) && !/<MLayout\b[^>]*fill-viewport/i.test(code)) {
1047
- suggestions.push({
1048
- standardId: 'layout-shell',
1049
- type: 'viewport-height',
1050
- message: locale === 'en-US'
1051
- ? 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.'
1052
- : '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
1053
- });
1056
+ push('craft', 'layout-shell', 'viewport-height', '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。', 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.');
1054
1057
  }
1055
- if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code))
1056
- && !/html\s*,\s*body\s*,\s*#app[\s\S]{0,80}height\s*:\s*100%/i.test(code)) {
1057
- suggestions.push({
1058
- standardId: 'layout-shell',
1059
- type: 'height-chain',
1060
- message: locale === 'en-US'
1061
- ? 'Prefer layout-app-shell / golden pages: with MLayout, set html, body, #app { height: 100% } when the shell is not using fillViewport.'
1062
- : '对齐 layout-app-shell / 黄金样例:未使用 fillViewport 时,配合 MLayout 设置 html, body, #app { height: 100% }。',
1063
- });
1058
+ if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code)) &&
1059
+ !/html\s*,\s*body\s*,\s*#app[\s\S]{0,80}height\s*:\s*100%/i.test(code)) {
1060
+ push('craft', 'layout-shell', 'height-chain', '对齐 layout-app-shell / 黄金样例:未使用 fillViewport 时,配合 MLayout 设置 html, body, #app { height: 100% }。', 'Prefer layout-app-shell / golden pages: with MLayout, set html, body, #app { height: 100% } when the shell is not using fillViewport.');
1064
1061
  }
1065
1062
  if (/<MMenu\b/i.test(code)) {
1066
1063
  const iconCount = (code.match(/\bicon\s*:/g) || []).length;
1067
1064
  const looksLikeMenuModel = /MMenu[\s\S]{0,1200}\b(label|model)\s*[:=]/i.test(code) || /:model=/i.test(code);
1068
1065
  if (looksLikeMenuModel && iconCount === 0) {
1069
- suggestions.push({
1070
- standardId: 'layout-shell',
1071
- type: 'menu-missing-icons',
1072
- message: locale === 'en-US'
1073
- ? 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).'
1074
- : '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。',
1075
- });
1066
+ push('craft', 'layout-shell', 'menu-missing-icons', '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。', 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).');
1076
1067
  }
1077
1068
  }
1078
1069
  if (/<MTable\b[^>]*(?::data|v-bind:data)\s*=/i.test(code) || /<MTable\b[^>]*\sdata\s*=/i.test(code)) {
1079
- suggestions.push({
1080
- standardId: 'feedback',
1081
- type: 'table-data-prop',
1082
- message: locale === 'en-US'
1083
- ? 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.'
1084
- : '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
1085
- });
1070
+ push('contract', 'feedback', 'table-data-prop', '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。', 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.');
1086
1071
  }
1087
- if (/fill-viewport|fillViewport/i.test(code)
1088
- && /<MTable\b[^>]*\bpaginator\b/i.test(code)
1089
- && /<MPageContent\b/i.test(code)
1090
- && !(/<MPageContent\b[^>]*\bfill\b/i.test(code)
1091
- && /<MTable\b[^>]*\bfill\b/i.test(code))) {
1092
- suggestions.push({
1093
- standardId: 'page-sections',
1094
- type: 'list-fill-height',
1095
- message: locale === 'en-US'
1096
- ? 'Advisory: for a full-viewport admin list whose main job is one table, consider MPageContent fill + MTable fill so the body scrolls and pagination stays at the page bottom. Skip fill for embedded/short/document-scroll tables (see page-scroll-choice / golden list-page).'
1097
- : '参考建议:若这是全视口后台主列表且表格是页面主任务,可考虑 MPageContent fill + MTable fill(表体滚动、分页贴底)。嵌入表/短页/整页文档滚动则跳过 fill(见 page-scroll-choice / 黄金样例 list-page)。',
1098
- });
1072
+ if (/<MTable\b[^>]*\bvirtual\b/i.test(code)
1073
+ && !(/<MTable\b[^>]*(?:max-height|maxHeight|table-height|tableHeight|fill)\b/i.test(code))) {
1074
+ push('contract', 'feedback', 'table-virtual-height', '契约:MTable virtual 需要明确高度(maxHeight / tableHeight / fill),否则视口无法计算。', 'Contract: MTable virtual requires an explicit height (maxHeight / tableHeight / fill).');
1075
+ }
1076
+ if (/vxe-table|VXETable|ElTableColumn|a-table\b/i.test(code) && /MTable|morya-ui/i.test(code)) {
1077
+ push('craft', 'feedback', 'table-prefer-mtable', '参考建议:管理列表优先用 MTable(virtual / resizable / filterable / edit-config 等企业能力)。仅当需要 Excel 级框选/透视时再引入专用网格库。', 'Advisory: prefer MTable for admin lists (virtual / resizable / filterable / edit-config). Reach for Excel-class grids only when area-select/pivot is required.');
1078
+ }
1079
+ if (/fill-viewport|fillViewport/i.test(code) &&
1080
+ /<MTable\b[^>]*\bpaginator\b/i.test(code) &&
1081
+ /<MPageContent\b/i.test(code) &&
1082
+ !(/<MPageContent\b[^>]*\bfill\b/i.test(code) && /<MTable\b[^>]*\bfill\b/i.test(code))) {
1083
+ push('craft', 'page-sections', 'list-fill-height', '参考建议:若这是全视口后台主列表且表格是页面主任务,可考虑 MPageContent fill + MTable fill(表体滚动、分页贴底)。嵌入表/短页/整页文档滚动则跳过 fill(见 page-scroll-choice / 黄金样例 list-page)。', 'Advisory: for a full-viewport admin list whose main job is one table, consider MPageContent fill + MTable fill so the body scrolls and pagination stays at the page bottom. Skip fill for embedded/short/document-scroll tables (see page-scroll-choice / golden list-page).');
1099
1084
  }
1100
1085
  if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
1101
- suggestions.push({
1102
- standardId: 'feedback',
1103
- type: 'mmessage-as-alert',
1104
- message: locale === 'en-US'
1105
- ? 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".'
1106
- : '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。',
1107
- });
1086
+ push('contract', 'feedback', 'mmessage-as-alert', '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。', 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".');
1108
1087
  }
1109
1088
  if (/toast\.(?:success|info|warn|warning|error)\s*\(\s*['"`][^'"`]+['"`]\s*\)/i.test(code)) {
1110
- suggestions.push({
1111
- standardId: 'feedback',
1112
- type: 'toast-one-liner',
1113
- message: locale === 'en-US'
1114
- ? 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.'
1115
- : '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。',
1116
- });
1089
+ push('contract', 'feedback', 'toast-one-liner', '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。', 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.');
1117
1090
  }
1118
1091
  if (/#cell-status[\s\S]{0,500}<MTag\b/i.test(code) &&
1119
1092
  !/#cell-status[\s\S]{0,500}<MStatus\b/i.test(code)) {
1120
- suggestions.push({
1121
- standardId: 'feedback',
1122
- type: 'status-cell-tag',
1123
- message: locale === 'en-US'
1124
- ? 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).'
1125
- : '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。',
1126
- });
1093
+ push('craft', 'feedback', 'status-cell-tag', '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。', 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).');
1127
1094
  }
1128
1095
  if (/<MTabs\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MTabs\b[^>]*\sitems\s*=/i.test(code)) {
1129
- suggestions.push({
1130
- standardId: 'feedback',
1131
- type: 'tabs-items-prop',
1132
- message: locale === 'en-US'
1133
- ? 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.'
1134
- : '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。',
1135
- });
1096
+ push('contract', 'feedback', 'tabs-items-prop', '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。', 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.');
1136
1097
  }
1137
1098
  if (/<MStepper\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MStepper\b[^>]*\sitems\s*=/i.test(code)) {
1138
- suggestions.push({
1139
- standardId: 'feedback',
1140
- type: 'stepper-items-prop',
1141
- message: locale === 'en-US'
1142
- ? 'Contract: MStepper uses the steps prop with v-model (not items). See wizard-form golden.'
1143
- : '契约:MStepper 使用 v-model + steps(不是 items)。见黄金样例 wizard-form。',
1144
- });
1099
+ push('contract', 'feedback', 'stepper-items-prop', '契约:MStepper 使用 v-model + steps(不是 items)。见黄金样例 wizard-form。', 'Contract: MStepper uses the steps prop with v-model (not items). See wizard-form golden.');
1100
+ }
1101
+ // Craft: placeholder / lorem copy
1102
+ if (/(?:title|description|label|placeholder)\s*=\s*["'](?:示例|样例|Name|No data|暂无数据|lorem|placeholder)["']/i.test(code) ||
1103
+ />\s*(?:示例|样例|No data|暂无数据|Lorem ipsum)\s*</i.test(code)) {
1104
+ push('craft', 'craft', 'placeholder-copy', '工艺建议:用领域真实文案,避免「示例 / Name / No data / 暂无数据」。', 'Craft: use domain-real copy — avoid “示例 / Name / No data / 暂无数据”.');
1105
+ }
1106
+ // Craft: more than one filled primary in viewport (heuristic)
1107
+ {
1108
+ const primaryFilled = code.match(/<MButton\b(?![^>]*(?:\btext\b|\boutlined\b|\blink\b))[^>]*\bseverity\s*=\s*["']primary["'][^>]*>|<MButton\b(?![^>]*(?:\btext\b|\boutlined\b|\blink\b|\bseverity\s*=))[^>]*>/gi);
1109
+ // Count explicit severity="primary" without text/outlined
1110
+ const explicitPrimary = (code.match(/<MButton\b(?![^>]*(?:\btext\b|\boutlined\b))[^>]*severity\s*=\s*["']primary["']/gi) || []).length;
1111
+ if (explicitPrimary > 1) {
1112
+ push('craft', 'craft', 'multiple-primary', '工艺建议:主视口内通常只需一个 filled primary 按钮。', 'Craft: usually only one filled primary button in the main viewport.');
1113
+ }
1114
+ void primaryFilled;
1115
+ }
1116
+ // Craft: icon-only without accessible name
1117
+ {
1118
+ const iconOnlyRe = /<MButton\b([^>]*)\b(?:icon-only|iconOnly)\b([^>]*)\/?>/gi;
1119
+ let m = iconOnlyRe.exec(code);
1120
+ while (m !== null) {
1121
+ const attrs = `${m[1] || ''}${m[2] || ''}`;
1122
+ if (!/\baria-label\b|\bariaLabel\b|\baria-labelledby\b/i.test(attrs)) {
1123
+ push('craft', 'a11y', 'icon-only-missing-aria', '工艺建议:仅图标的 MButton 需要 aria-label / ariaLabel。', 'Craft: icon-only MButton needs aria-label / ariaLabel.');
1124
+ break;
1125
+ }
1126
+ m = iconOnlyRe.exec(code);
1127
+ }
1128
+ }
1129
+ // Brief fidelity: required blocks / components
1130
+ const mapped = args.brief
1131
+ ? mapReferenceBrief(args.brief)
1132
+ : null;
1133
+ const requiredBlocks = [
1134
+ ...(args.requiredBlocks || []),
1135
+ ...(mapped?.validationHints.requiredBlocks || []),
1136
+ ];
1137
+ const requiredComponents = [
1138
+ ...(args.requiredComponents || []),
1139
+ ...(mapped?.validationHints.requiredComponents || []),
1140
+ ];
1141
+ for (const block of requiredBlocks) {
1142
+ if (!codeCoversBlock(code, block)) {
1143
+ push('contract', 'fidelity', 'brief-missing-block', `还原度:参考要求的区块「${block}」未在代码中检测到。`, `Fidelity: required reference block “${block}” was not detected in the code.`);
1144
+ }
1145
+ }
1146
+ for (const component of requiredComponents) {
1147
+ if (!codeCoversComponent(code, component)) {
1148
+ push('contract', 'fidelity', 'brief-missing-component', `还原度:参考要求的组件 M${component.replace(/^M/, '')} 未出现。`, `Fidelity: required component M${component.replace(/^M/, '')} is missing.`);
1149
+ }
1145
1150
  }
1151
+ const contract = suggestions.filter((item) => item.severity === 'contract');
1152
+ const craft = suggestions.filter((item) => item.severity === 'craft');
1153
+ const ok = contract.length === 0;
1146
1154
  return textResult({
1147
- ok: true,
1155
+ ok,
1148
1156
  advisory: true,
1149
1157
  summary: locale === 'en-US'
1150
1158
  ? suggestions.length === 0
1151
1159
  ? 'No deviations from page standards detected.'
1152
- : `${suggestions.length} advisory suggestion(s).`
1160
+ : `${contract.length} contract issue(s), ${craft.length} craft suggestion(s).`
1153
1161
  : suggestions.length === 0
1154
1162
  ? '未发现与页面标准的明显偏差。'
1155
- : `${suggestions.length} 条参考建议。`,
1163
+ : `${contract.length} 条契约问题,${craft.length} 条工艺建议。`,
1164
+ contract,
1165
+ craft,
1156
1166
  suggestions,
1157
1167
  nextStep: locale === 'en-US'
1158
- ? 'See get_design_rules.standards for the full recommended page standards.'
1159
- : '完整页面标准见 get_design_rules.standards。',
1168
+ ? 'Fix contract issues before delivery; craft suggestions may be waived with justification. Pass the same brief used in map_reference / recommend_page. See get_design_rules / get_style_shells.'
1169
+ : '契约问题交付前必须修好;工艺建议可说明后放过。brief 与 map_reference / recommend_page 保持一致。另见 get_design_rules / get_style_shells。',
1160
1170
  });
1161
1171
  }
1162
1172
  function getDesignRules(args = {}) {
@@ -1165,20 +1175,29 @@ export function createToolHandlers(catalog = loadCatalog()) {
1165
1175
  resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1166
1176
  hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
1167
1177
  note: locale === 'en-US'
1168
- ? 'No named style-preset catalog. Follow user reference/description; ask when uncertain.'
1169
- : '没有命名风格预设表。跟用户参考/描述;不确定就先问。',
1178
+ ? 'No named style-preset catalog. Follow user reference/description; ask when uncertain. Use map_reference for fidelity mapping and get_style_shells for pasteable token CSS.'
1179
+ : '没有命名风格预设表。跟用户参考/描述;不确定就先问。还原用 map_reference;可粘贴签名壳见 get_style_shells。',
1170
1180
  };
1181
+ const shells = styleShells.map((shell) => ({
1182
+ id: shell.id,
1183
+ surface: shell.surface,
1184
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1185
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1186
+ when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
1187
+ }));
1171
1188
  if (locale === 'zh-CN') {
1172
1189
  return textResult({
1173
1190
  meta: designRules.meta,
1174
1191
  standards: designRules.standards,
1175
1192
  styles,
1193
+ shells,
1176
1194
  tokens: designRules.tokens,
1177
1195
  composition: designRules.composition,
1178
1196
  actions: designRules.actions,
1179
1197
  status: designRules.status,
1180
1198
  feedback: designRules.feedback,
1181
1199
  global: designRules.global,
1200
+ themeOverride: '品牌差异优先在应用入口覆盖 --m-color-primary / 字体等主题变量,页面 CSS 只用 --m-*;见 DESIGN.md「主题覆盖」。',
1182
1201
  });
1183
1202
  }
1184
1203
  return textResult({
@@ -1191,6 +1210,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1191
1210
  mcp: item.mcp,
1192
1211
  })),
1193
1212
  styles,
1213
+ shells,
1194
1214
  tokens: {
1195
1215
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
1196
1216
  spacing: '--m-space-*',
@@ -1225,6 +1245,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1225
1245
  },
1226
1246
  doc: 'design-kit/.agents/skills/morya-ui-pages/references/feedback.md',
1227
1247
  },
1248
+ themeOverride: 'Prefer brand differences via theme overrides (--m-color-primary / fonts) at the app entry; page CSS uses --m-* only. See DESIGN.md theme override section.',
1228
1249
  global: [
1229
1250
  'Prefer library components and --m-* tokens.',
1230
1251
  'Default action feedback to message; prefer message over toast for single-line-only text.',
@@ -1233,9 +1254,81 @@ export function createToolHandlers(catalog = loadCatalog()) {
1233
1254
  'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
1234
1255
  'Prefer documented component variants over deep CSS overrides.',
1235
1256
  'Resolve visual style: follow explicit user reference/description first; else infer from clear prompt cues; if uncertain ask once. No named preset catalog. Golden pages lock structure, not aesthetics.',
1257
+ 'Pass brief to map_reference / recommend_page / validate_page for fidelity.',
1236
1258
  ],
1237
1259
  });
1238
1260
  }
1261
+ function mapReference(args) {
1262
+ const locale = resolveLocale(args.mode);
1263
+ const mapped = mapReferenceBrief(args);
1264
+ const shell = mapped.suggestedShellId ? findStyleShell(mapped.suggestedShellId) : undefined;
1265
+ return textResult({
1266
+ surface: mapped.surface,
1267
+ density: mapped.density,
1268
+ styleSummary: mapped.styleSummary,
1269
+ suggestedSnippets: mapped.suggestedSnippets,
1270
+ suggestedShellId: mapped.suggestedShellId,
1271
+ mapping: mapped.mapping.map((item) => ({
1272
+ block: item.block,
1273
+ snippetId: item.snippetId,
1274
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
1275
+ })),
1276
+ tokenRoles: mapped.tokenRoles.map((item) => ({
1277
+ role: item.role,
1278
+ token: item.token,
1279
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
1280
+ })),
1281
+ craftCues: locale === 'en-US' ? mapped.craftCuesEn : mapped.craftCuesZh,
1282
+ validationHints: mapped.validationHints,
1283
+ styleShell: shell
1284
+ ? {
1285
+ id: shell.id,
1286
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1287
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1288
+ css: shell.css,
1289
+ htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
1290
+ }
1291
+ : null,
1292
+ nextStep: locale === 'en-US'
1293
+ ? 'Compose get_page_snippet for each mapping.snippetId; apply styleShell.css when needed; pass the same brief to validate_page.'
1294
+ : '按 mapping.snippetId 逐个 get_page_snippet;需要时粘贴 styleShell.css;同一 brief 交给 validate_page。',
1295
+ });
1296
+ }
1297
+ function getStyleShells(args = {}) {
1298
+ const locale = resolveLocale(args.mode);
1299
+ if (args.shell) {
1300
+ const shell = findStyleShell(args.shell);
1301
+ if (!shell) {
1302
+ return textResult({
1303
+ error: `Style shell not found: ${args.shell}`,
1304
+ available: styleShells.map((item) => item.id),
1305
+ });
1306
+ }
1307
+ return textResult({
1308
+ id: shell.id,
1309
+ surface: shell.surface,
1310
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1311
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1312
+ when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
1313
+ htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
1314
+ css: shell.css,
1315
+ });
1316
+ }
1317
+ const items = listStyleShells(args.surface).map((shell) => ({
1318
+ id: shell.id,
1319
+ surface: shell.surface,
1320
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1321
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1322
+ when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
1323
+ }));
1324
+ return textResult({
1325
+ total: items.length,
1326
+ items,
1327
+ nextStep: locale === 'en-US'
1328
+ ? 'Pass shell id to get_style_shells({ shell }) for pasteable CSS. Brand color via theme --m-color-primary, not page hex.'
1329
+ : '传 shell id 给 get_style_shells({ shell }) 取可粘贴 CSS。品牌色走主题 --m-color-primary,不要在页面写 hex。',
1330
+ });
1331
+ }
1239
1332
  function getStyleDirection(args = {}) {
1240
1333
  const locale = resolveLocale(args.mode);
1241
1334
  const direction = resolveStyleDirection({ intent: args.intent, style: args.style });
@@ -1468,8 +1561,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
1468
1561
  'list_patterns',
1469
1562
  'get_pattern',
1470
1563
  'recommend_page',
1564
+ 'map_reference',
1471
1565
  'get_design_rules',
1472
1566
  'get_style_direction',
1567
+ 'get_style_shells',
1473
1568
  'recommend_component',
1474
1569
  'list_golden_pages',
1475
1570
  'get_golden_page',
@@ -1492,8 +1587,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
1492
1587
  listPatterns,
1493
1588
  getPattern,
1494
1589
  recommendPage,
1590
+ mapReference,
1495
1591
  getDesignRules,
1496
1592
  getStyleDirection,
1593
+ getStyleShells,
1497
1594
  recommendComponent,
1498
1595
  listGoldenPageCatalog,
1499
1596
  getGoldenPage,