diff --git a/.claude/skills/design-handoff/defaults/tokens.yml b/.claude/skills/design-handoff/defaults/tokens.yml index 72acfe4..4505edf 100644 --- a/.claude/skills/design-handoff/defaults/tokens.yml +++ b/.claude/skills/design-handoff/defaults/tokens.yml @@ -327,16 +327,34 @@ shadow_unmapped: aliases: prototype_keys: T.pri: - token: --tk-pri - status: exact_match + - value: "#C4623A" + token: --tk-pri + status: exact_match + variant: patient + - value: "#3A6B8C" + token: --tk-pri.doctor + status: exact_match + variant: doctor T.priL: - token: --tk-pri-l - status: exact_match + - value: "#F0DDD4" + token: --tk-pri-l + status: exact_match + variant: patient + - value: "#D4E5F0" + token: --tk-pri-l.doctor + status: exact_match + variant: doctor T.priD: - token: --tk-pri-d - status: exact_match + - value: "#8B3E1F" + token: --tk-pri-d + status: exact_match + variant: patient + - value: "#2A4F6A" + token: --tk-pri-d.doctor + status: exact_match + variant: doctor T.bg: token: null diff --git a/.claude/skills/design-handoff/rules/interaction-rules.yml b/.claude/skills/design-handoff/rules/interaction-rules.yml index 1960a56..e502eeb 100644 --- a/.claude/skills/design-handoff/rules/interaction-rules.yml +++ b/.claude/skills/design-handoff/rules/interaction-rules.yml @@ -84,8 +84,14 @@ rules: name: "登录/注册触发" patterns: - "登录" - - "立即" - "注册" + - "微信登录" + - "立即登录" + exclude_patterns: + - "立即关注" + - "立即处理" + - "立即查看" + - "需要立即" require_all: false infer: component: "PrimaryButton" diff --git a/.claude/skills/design-handoff/scripts/infer-interactions.mjs b/.claude/skills/design-handoff/scripts/infer-interactions.mjs index 7c70d03..8010f65 100644 --- a/.claude/skills/design-handoff/scripts/infer-interactions.mjs +++ b/.claude/skills/design-handoff/scripts/infer-interactions.mjs @@ -121,6 +121,23 @@ function matchRule(rule, source, funcRanges) { ? matchedPatterns.length === rule.patterns.length : matchedPatterns.length > 0; + // 排除模式检查:如果任一 exclude_pattern 命中,该规则不匹配 + if (allMatched && Array.isArray(rule.exclude_patterns)) { + for (const exclPattern of rule.exclude_patterns) { + try { + const exclRegex = new RegExp(exclPattern); + if (exclRegex.test(source)) { + return { + matched: false, + matchedPatterns: [], + locations: [], + excluded_by: exclPattern, + }; + } + } catch (_e) { /* ignore regex errors */ } + } + } + return { matched: allMatched, matchedPatterns: allMatched ? matchedPatterns : [], diff --git a/.claude/skills/design-handoff/scripts/match-tokens.mjs b/.claude/skills/design-handoff/scripts/match-tokens.mjs index dd66e0a..68759bd 100644 --- a/.claude/skills/design-handoff/scripts/match-tokens.mjs +++ b/.claude/skills/design-handoff/scripts/match-tokens.mjs @@ -268,18 +268,53 @@ function buildTokenRegistry(tokensConfig) { // --------------------------------------------------------------------------- /** - * Layer 1: 别名直查 - * @returns {object|null} 匹配结果或 null + * Layer 1: 别名直查(支持值条件映射) + * + * alias 格式有两种: + * 单条: { token: "...", status: "exact_match" } + * 数组: [{ value: "#C4623A", token: "--tk-pri", variant: "patient" }, + * { value: "#3A6B8C", token: "--tk-pri.doctor", variant: "doctor" }] + * + * 数组格式按值匹配,单条格式直接匹配(兼容旧格式)。 */ -function matchByAlias(key, aliases) { +function matchByAlias(key, prototypeValue, aliases) { if (!aliases || !aliases.prototype_keys) return null; const alias = aliases.prototype_keys[key]; if (!alias) return null; - const result = { - method: 'alias', - confidence: null, - }; + // 数组格式:按值条件匹配 + if (Array.isArray(alias)) { + const normalizedInput = normalizeColor(prototypeValue) || normalizeNumericValue(prototypeValue) || String(prototypeValue); + for (const entry of alias) { + const normalizedAlias = normalizeColor(entry.value) || normalizeNumericValue(entry.value) || String(entry.value); + if (normalizedInput === normalizedAlias) { + const result = { + method: 'alias', + confidence: entry.status === 'exact_match' ? 'confirmed' : 'pending', + token: entry.token || null, + scssVar: entry.scss_var || null, + }; + if (entry.variant) result.variant = entry.variant; + if (entry.note) result.note = entry.note; + return result; + } + } + // 数组中无值匹配 → 降级到 Layer 2 + return null; + } + + // 单条格式(兼容旧格式) + const result = { method: 'alias', confidence: null }; + + // 值校验:如果 alias 有 value 字段,比较值 + if (alias.value && prototypeValue != null) { + const normAlias = normalizeColor(alias.value) || normalizeNumericValue(alias.value) || String(alias.value); + const normProto = normalizeColor(prototypeValue) || normalizeNumericValue(prototypeValue) || String(prototypeValue); + if (normAlias !== normProto) { + // 值不匹配 → 降级到 Layer 2 + return null; + } + } if (alias.status === 'exact_match') { result.token = alias.token || null; @@ -549,7 +584,7 @@ function main() { const prototypeTokens = parseResult.tokens || {}; for (const [key, value] of Object.entries(prototypeTokens)) { // Layer 1: 别名直查 - const aliasResult = matchByAlias(key, aliases); + const aliasResult = matchByAlias(key, value, aliases); if (aliasResult) { matched[key] = { ...aliasResult, @@ -617,9 +652,14 @@ function main() { const totalAlias = Object.keys(prototypeTokens).length; const totalInline = Object.values(inlineStyles).reduce((sum, arr) => sum + arr.length, 0); + // 检测 variant + const variantEntries = Object.values(matched).filter(e => e.variant); + const variant = variantEntries.length > 0 ? variantEntries[0].variant : null; + // ---- 输出 ---- const output = { source: parseResult.source || null, + variant, matched, unmatched, inlineTokenMap, diff --git a/.claude/skills/design-handoff/scripts/parse-prototype.mjs b/.claude/skills/design-handoff/scripts/parse-prototype.mjs index cef1fa8..276760f 100644 --- a/.claude/skills/design-handoff/scripts/parse-prototype.mjs +++ b/.claude/skills/design-handoff/scripts/parse-prototype.mjs @@ -263,18 +263,7 @@ function extractInlineStyles(source) { function extractScreens(source) { const screens = []; - // 策略:找所有 screen-wrap 块,从中提取 screen-label 文本和组件名 - // 两种形式: - // 1)