@lism-css/mcp 0.26.0 → 0.28.0

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.
@@ -1,38 +1,19 @@
1
- /**
2
- * Markdown から指定した見出しのセクションを抽出する。
3
- * 同レベル以上の次の見出しが来るまでの内容を返す。
4
- * @param md - Markdown 全文
5
- * @param heading - 見出しテキスト(`#` プレフィックスあり・なし両可)
6
- */
1
+ /** 指定見出しから同レベル以上の次の見出しまでを抽出する。headingは#の有無を問わない。 */
7
2
  export declare function extractSection(md: string, heading: string): string;
8
- /**
9
- * Markdown から全ての見出しとその開始行を抽出する。
10
- */
11
3
  export declare function listHeadings(md: string): {
12
4
  level: number;
13
5
  text: string;
14
6
  line: number;
15
7
  }[];
16
8
  export interface PropRow {
17
- /** Lism Prop 名(例: "fz") */
18
9
  prop: string;
19
- /** CSS プロパティ名(例: "font-size")。変数形式("--hl")も含む */
20
10
  cssProperty: string;
21
- /** 所属する ### セクション名 */
22
11
  sectionName: string;
23
- /** プリセット値クラス列の生テキスト(例: "-fz:base, -fz:5xl, ...") */
24
12
  presetColumn: string;
25
13
  }
26
- /**
27
- * property-class.md のテーブルを全て解析して PropRow[] を返す。
28
- * `| Prop | CSS プロパティ | ...` 形式のテーブルのみ対象とする。
29
- */
14
+ /** Property Classの対象テーブルを解析してPropRowへ変換する。 */
30
15
  export declare function parsePropRows(md: string): PropRow[];
31
- /**
32
- * Markdown からコンポーネント名に一致するセクションを探して返す。
33
- *
34
- * components-ui.md のように各コンポーネントが `## ComponentName` で始まる場合に有効。
35
- */
16
+ /** 各コンポーネントが`## ComponentName`で始まる文書向け。 */
36
17
  export declare function findComponentByHeading(md: string, name: string): string;
37
18
  /**
38
19
  * Markdown 内のテーブルセル(`` `<ComponentName>` ``)からコンポーネントを含む
@@ -1,14 +1,8 @@
1
- /** 見出しの `#` レベルを返す(見出しでなければ 0) */
2
1
  function headingLevel(line) {
3
2
  const m = line.match(/^(#{1,6})\s/);
4
3
  return m ? m[1].length : 0;
5
4
  }
6
- /**
7
- * Markdown から指定した見出しのセクションを抽出する。
8
- * 同レベル以上の次の見出しが来るまでの内容を返す。
9
- * @param md - Markdown 全文
10
- * @param heading - 見出しテキスト(`#` プレフィックスあり・なし両可)
11
- */
5
+ /** 指定見出しから同レベル以上の次の見出しまでを抽出する。headingは#の有無を問わない。 */
12
6
  export function extractSection(md, heading) {
13
7
  const headingText = heading.replace(/^#+\s*/, '').trim();
14
8
  const lines = md.split('\n');
@@ -34,9 +28,6 @@ export function extractSection(md, heading) {
34
28
  }
35
29
  return lines.slice(startIdx, endIdx).join('\n').trimEnd();
36
30
  }
37
- /**
38
- * Markdown から全ての見出しとその開始行を抽出する。
39
- */
40
31
  export function listHeadings(md) {
41
32
  return md.split('\n').flatMap((line, i) => {
42
33
  const lv = headingLevel(line);
@@ -45,38 +36,32 @@ export function listHeadings(md) {
45
36
  return [{ level: lv, text: line.replace(/^#+\s*/, '').trim(), line: i }];
46
37
  });
47
38
  }
48
- /**
49
- * property-class.md のテーブルを全て解析して PropRow[] を返す。
50
- * `| Prop | CSS プロパティ | ...` 形式のテーブルのみ対象とする。
51
- */
39
+ /** Property Classの対象テーブルを解析してPropRowへ変換する。 */
52
40
  export function parsePropRows(md) {
53
41
  const lines = md.split('\n');
54
42
  const rows = [];
55
43
  let currentSection = '';
56
44
  let inPropTable = false;
45
+ // 見出しと対象テーブルの範囲を追跡する。
57
46
  for (const line of lines) {
58
47
  const lv = headingLevel(line);
59
- // セクション見出しを追跡(### レベル)
60
48
  if (lv >= 2) {
61
49
  currentSection = line.replace(/^#+\s*/, '').trim();
62
50
  inPropTable = false;
63
51
  continue;
64
52
  }
65
- // Property Class テーブルのヘッダー行を検出
66
53
  if (line.includes('Prop') && line.includes('CSS プロパティ')) {
67
54
  inPropTable = true;
68
55
  continue;
69
56
  }
70
- // 区切り行はスキップ
71
57
  if (inPropTable && /^\|[-\s|:]+\|/.test(line)) {
72
58
  continue;
73
59
  }
74
- // テーブルの終了を検出
75
60
  if (inPropTable && !line.startsWith('|')) {
76
61
  inPropTable = false;
77
62
  continue;
78
63
  }
79
- // データ行を解析
64
+ // 対象テーブルの各行をPropRowへ変換する。
80
65
  if (inPropTable && line.startsWith('|')) {
81
66
  const cells = line
82
67
  .split('|')
@@ -84,9 +69,7 @@ export function parsePropRows(md) {
84
69
  .filter(Boolean);
85
70
  if (cells.length >= 2) {
86
71
  const prop = cells[0].replace(/`/g, '').trim();
87
- // CSS プロパティ: バッククォート除去、括弧内の注釈は保持しない
88
72
  const cssPropertyRaw = cells[1].replace(/`/g, '').trim();
89
- // `line-height`(`--hl` 経由) のような括弧注釈を除去
90
73
  const cssProperty = cssPropertyRaw
91
74
  .replace(/([^)]*)$/, '')
92
75
  .replace(/\([^)]*\)$/, '')
@@ -103,11 +86,7 @@ export function parsePropRows(md) {
103
86
  // ----------------------------------------------------------------
104
87
  // コンポーネント検索
105
88
  // ----------------------------------------------------------------
106
- /**
107
- * Markdown からコンポーネント名に一致するセクションを探して返す。
108
- *
109
- * components-ui.md のように各コンポーネントが `## ComponentName` で始まる場合に有効。
110
- */
89
+ /** 各コンポーネントが`## ComponentName`で始まる文書向け。 */
111
90
  export function findComponentByHeading(md, name) {
112
91
  return extractSection(md, name);
113
92
  }
@@ -119,10 +98,9 @@ export function findComponentByHeading(md, name) {
119
98
  export function findComponentInTables(md, name) {
120
99
  const nameLower = name.toLowerCase();
121
100
  const lines = md.split('\n');
122
- // コンポーネント名のパターン: `<Flex>`, `<flex>`, または単純に "flex" がテーブル行に含まれるか
123
101
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
124
102
  const pattern = new RegExp(`\`?<${escaped}>?\`?`, 'i');
125
- // まず対象行を探す
103
+ // コンポーネントを含むテーブル行を探す。
126
104
  let targetLineIdx = -1;
127
105
  for (let i = 0; i < lines.length; i++) {
128
106
  if (lines[i].startsWith('|') && (pattern.test(lines[i]) || lines[i].toLowerCase().includes(`\`${nameLower}\``))) {
@@ -132,7 +110,7 @@ export function findComponentInTables(md, name) {
132
110
  }
133
111
  if (targetLineIdx === -1)
134
112
  return '';
135
- // 対象行を含む ## セクションの開始を遡って探す
113
+ // 対象行を含む##節の範囲を決める。
136
114
  let sectionStart = -1;
137
115
  let sectionLevel = 0;
138
116
  for (let i = targetLineIdx; i >= 0; i--) {
@@ -145,7 +123,6 @@ export function findComponentInTables(md, name) {
145
123
  }
146
124
  if (sectionStart === -1)
147
125
  return lines.slice(0, targetLineIdx + 20).join('\n');
148
- // セクションの終端を探す
149
126
  let sectionEnd = lines.length;
150
127
  for (let i = sectionStart + 1; i < lines.length; i++) {
151
128
  const lv = headingLevel(lines[i]);
@@ -1,8 +1,5 @@
1
1
  import type { DocsEntry, SearchResult } from './types.js';
2
- /**
3
- * Property Class 記法(例: "-g:5", ".-p:20", "-fz")から prop 名を抽出する。
4
- * get-props-system.ts からも利用される共通ユーティリティ。
5
- */
2
+ /** Property Class記法からprop名を取り出す。get-props-system.tsからも利用する。 */
6
3
  export declare function parsePropClassName(input: string): string | null;
7
4
  export interface SearchDocsOptions {
8
5
  category?: string;
@@ -4,32 +4,22 @@ function tokenize(text) {
4
4
  .split(/[\s\-_./]+/)
5
5
  .filter((t) => t.length > 0);
6
6
  }
7
- // ".-g:5" or "-g:5" → prop="g", value="5" / "-p" → prop="p"
8
7
  const PROP_CLASS_RE = /^\.?-([a-z][a-z0-9-]*)(:.+)?$/i;
9
- /**
10
- * Property Class 記法(例: "-g:5", ".-p:20", "-fz")から prop 名を抽出する。
11
- * get-props-system.ts からも利用される共通ユーティリティ。
12
- */
8
+ /** Property Class記法からprop名を取り出す。get-props-system.tsからも利用する。 */
13
9
  export function parsePropClassName(input) {
14
10
  const m = input.match(PROP_CLASS_RE);
15
11
  return m ? m[1].toLowerCase() : null;
16
12
  }
17
- /**
18
- * 検索クエリをCSSプロパティ名やProperty Class記法で展開する。
19
- * 例: "font-size" → "font-size fz"
20
- * 例: "-g:5" → "-g:5 g gap property class"
21
- */
13
+ /** CSSプロパティとProperty Classを相互展開して検索語を補う。 */
22
14
  function expandQuery(query, cssPropertyMap) {
23
15
  const additions = [];
24
16
  const queryLower = query.toLowerCase();
25
17
  const parsedProp = parsePropClassName(queryLower.trim());
26
18
  if (cssPropertyMap) {
27
19
  for (const [cssProp, lismProps] of cssPropertyMap) {
28
- // Property Class 記法の逆引き(例: "-g:5" の "g" → "gap")
29
20
  if (parsedProp && lismProps.includes(parsedProp)) {
30
21
  additions.push(cssProp);
31
22
  }
32
- // CSSプロパティ名の展開(例: "font-size" → "fz")
33
23
  if (queryLower.includes(cssProp)) {
34
24
  additions.push(...lismProps);
35
25
  }
@@ -47,20 +37,16 @@ function scoreEntry(entry, queryTokens) {
47
37
  const headingsLower = entry.headings.join(' ').toLowerCase();
48
38
  const keywordsLower = entry.keywords.join(' ').toLowerCase();
49
39
  const snippetLower = entry.snippet.toLowerCase();
40
+ // titleからsnippetへ順に重みを下げる。
50
41
  for (const token of queryTokens) {
51
- // title matches are weighted highest
52
42
  if (titleLower.includes(token))
53
43
  score += 10;
54
- // keywords
55
44
  if (keywordsLower.includes(token))
56
45
  score += 5;
57
- // headings
58
46
  if (headingsLower.includes(token))
59
47
  score += 3;
60
- // description
61
48
  if (descLower.includes(token))
62
49
  score += 2;
63
- // snippet
64
50
  if (snippetLower.includes(token))
65
51
  score += 1;
66
52
  }
@@ -68,7 +54,7 @@ function scoreEntry(entry, queryTokens) {
68
54
  }
69
55
  export function searchDocs(entries, query, options) {
70
56
  const { category, limit = 10, cssPropertyMap, guideTopics } = options ?? {};
71
- // CSSプロパティ名・Property Class記法をLism prop名に展開してからトークナイズ
57
+ // CSSプロパティ名とProperty Class記法も同じ検索対象へ展開する。
72
58
  const expandedQuery = expandQuery(query, cssPropertyMap);
73
59
  const queryTokens = tokenize(expandedQuery);
74
60
  if (queryTokens.length === 0)
@@ -103,19 +89,14 @@ const SITE_BASE_URL = 'https://lism-css.com';
103
89
  function slugToPageUrl(slug) {
104
90
  return slug.startsWith('ui/') ? `${SITE_BASE_URL}/${slug}/` : `${SITE_BASE_URL}/docs/${slug}/`;
105
91
  }
106
- /** `sourcePath`(拡張子なし)の末尾セグメントを返す(例: `primitives/l--flex` → `l--flex`) */
107
92
  function getBasename(withoutExt) {
108
93
  const parts = withoutExt.split('/');
109
94
  return parts[parts.length - 1];
110
95
  }
111
- /**
112
- * 検索結果のページを詳しく見るための推奨フォローアップツール呼び出しを返す。
113
- * sourcePath による判定をカテゴリによる判定より優先する。
114
- */
96
+ /** 検索結果を掘り下げる推奨ツールを返す。カテゴリよりsourcePathの規則を優先する。 */
115
97
  function getNextTool(entry, guideTopics) {
116
98
  const withoutExt = entry.sourcePath.replace(/\.mdx$/, '');
117
99
  const basename = getBasename(withoutExt);
118
- // sourcePath ベースの判定(category より優先)
119
100
  if (withoutExt === 'core-components/lism-props') {
120
101
  return 'get_props_system()';
121
102
  }
@@ -128,13 +109,10 @@ function getNextTool(entry, guideTopics) {
128
109
  if (withoutExt.startsWith('property-class/')) {
129
110
  return `get_props_system(prop: "${basename}")`;
130
111
  }
131
- // ui/block-examples/(Chat, Timeline 等)と ui/components/(Card, Hero 等)は、
132
- // パッケージが提供するコンポーネントではなく Lism CSS での実装例ページなので get_component では解決できない。
133
- // 詳細が必要な場合は検索結果の url を参照してもらう。
112
+ // 実装例ページはパッケージ提供コンポーネントではないためget_componentでは解決できない。
134
113
  if (withoutExt.startsWith('ui/block-examples/') || withoutExt.startsWith('ui/components/')) {
135
114
  return null;
136
115
  }
137
- // category ベースの判定
138
116
  switch (entry.category) {
139
117
  case 'core-components':
140
118
  return `get_component(name: "${basename}")`;
@@ -156,12 +134,6 @@ function getNextTool(entry, guideTopics) {
156
134
  * IMPORTANT: `apps/docs/src/lib/contentSlug.ts` の `toContentSlug` と必ず同じロジックに保つこと。
157
135
  * 別ワークスペース(apps/docs)なので直接 import できず、ローカル実装で複製している。
158
136
  * apps/docs 側を変更した場合は必ずここも合わせて更新する。
159
- *
160
- * 例:
161
- * `primitives/l--tileGrid.mdx` → `primitives/l--tileGrid`
162
- * `trait-class/is--boxLink.mdx` → `trait-class/is--boxLink`
163
- * `core-components/Group.mdx` → `core-components/group`
164
- * `ui/DummyText.mdx` → `ui/dummytext`
165
137
  */
166
138
  const PRESERVE_CASE_PREFIXES = ['primitives/', 'trait-class/'];
167
139
  export function sourcePathToUrlSlug(sourcePath) {
@@ -3,7 +3,6 @@ import { loadPropsMarkdown } from '../lib/load-markdown.js';
3
3
  import { parsePropRows } from '../lib/markdown-utils.js';
4
4
  import { MetaInfoSchema } from '../lib/schemas.js';
5
5
  import { success, error, READ_ONLY_ANNOTATIONS } from '../lib/response.js';
6
- /** 変換結果の1行 */
7
6
  const ConversionEntrySchema = z.object({
8
7
  css: z.string(),
9
8
  lismProp: z.string().nullable(),
@@ -12,7 +11,6 @@ const ConversionEntrySchema = z.object({
12
11
  confidence: z.enum(['exact', 'approximate', 'unmapped']),
13
12
  note: z.string(),
14
13
  });
15
- /** コンポーネント提案 */
16
14
  const ComponentSuggestionSchema = z.object({
17
15
  name: z.string(),
18
16
  reason: z.string(),
@@ -21,7 +19,6 @@ const ComponentSuggestionSchema = z.object({
21
19
  // ----------------------------------------------------------------
22
20
  // CSS パース
23
21
  // ----------------------------------------------------------------
24
- /** @ルール(@media 等)が含まれていないか検査する */
25
22
  function detectAtRules(cssText) {
26
23
  const atRuleMatch = cssText.match(/^@(\w[\w-]*)/m);
27
24
  if (atRuleMatch) {
@@ -29,16 +26,12 @@ function detectAtRules(cssText) {
29
26
  }
30
27
  return null;
31
28
  }
32
- /**
33
- * CSS テキストから宣言を抽出する。
34
- * `;` で分割する際に `url()` 等の括弧内の `;` を無視する。
35
- */
29
+ /** CSSテキストから宣言を抽出する。url()などの括弧内にある`;`は区切りとみなさない。 */
36
30
  function parseCssDeclarations(cssText) {
37
- // コメント除去
31
+ // コメントとセレクタの外枠を取り除く。
38
32
  let cleaned = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
39
- // セレクタ + ブレースを除去(裸の宣言リストも受け付ける)
40
33
  cleaned = cleaned.replace(/[^{}]*\{/g, '').replace(/\}/g, '');
41
- // 括弧のネストを考慮して `;` で分割
34
+ // 括弧の深さを追いながら宣言単位に分ける。
42
35
  const segments = [];
43
36
  let current = '';
44
37
  let parenDepth = 0;
@@ -72,12 +65,10 @@ function parseCssDeclarations(cssText) {
72
65
  }
73
66
  return declarations;
74
67
  }
75
- /** プリセット値列から値を抽出する(例: "-fz:base, -fz:5xl" → ["base", "5xl"]) */
76
68
  function extractPresetValues(presetColumn, propName) {
77
69
  if (!presetColumn || presetColumn === '—' || presetColumn === '-')
78
70
  return [];
79
71
  const values = [];
80
- // -{prop}:{value} パターンを全て抽出
81
72
  const escaped = propName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
82
73
  const regex = new RegExp(`-${escaped}:([^,\\s\`〜]+)`, 'g');
83
74
  let match;
@@ -98,7 +89,6 @@ function buildCssPropertyMap(mappings) {
98
89
  const map = new Map();
99
90
  for (const mapping of mappings) {
100
91
  const normalized = mapping.cssProperty.toLowerCase();
101
- // CSS カスタムプロパティ形式はスキップ("--hl" 等)
102
92
  if (!normalized.startsWith('(class:')) {
103
93
  map.set(normalized, mapping);
104
94
  }
@@ -108,7 +98,6 @@ function buildCssPropertyMap(mappings) {
108
98
  // ----------------------------------------------------------------
109
99
  // 値のマッピング
110
100
  // ----------------------------------------------------------------
111
- /** よくある CSS 値 → Lism トークン値の変換テーブル */
112
101
  const VALUE_ALIASES = {
113
102
  'space-between': 'between',
114
103
  currentcolor: 'current',
@@ -119,10 +108,8 @@ function suggestValue(mapping, cssValue) {
119
108
  const tokens = mapping.presetValues;
120
109
  if (tokens.length === 0)
121
110
  return null;
122
- // 直接一致
123
111
  if (tokens.includes(cssValue))
124
112
  return cssValue;
125
- // エイリアス変換後に一致
126
113
  const aliased = VALUE_ALIASES[cssValue.toLowerCase()];
127
114
  if (aliased && tokens.includes(aliased))
128
115
  return aliased;
@@ -131,12 +118,12 @@ function suggestValue(mapping, cssValue) {
131
118
  // ----------------------------------------------------------------
132
119
  // コンポーネント検出
133
120
  // ----------------------------------------------------------------
121
+ /** display関連の宣言から利用できるLismレイアウトコンポーネントを提案する。 */
134
122
  function detectComponent(declarations) {
135
123
  const propMap = new Map(declarations.map((d) => [d.property, d.value.toLowerCase()]));
136
124
  const display = propMap.get('display');
137
125
  const flexDirection = propMap.get('flex-direction');
138
126
  const placeItems = propMap.get('place-items');
139
- // Stack: flex + column
140
127
  if (display === 'flex' && (flexDirection === 'column' || flexDirection === 'column-reverse')) {
141
128
  return {
142
129
  name: 'Stack',
@@ -144,7 +131,6 @@ function detectComponent(declarations) {
144
131
  implicitCss: ['display: flex', 'flex-direction: column'],
145
132
  };
146
133
  }
147
- // Center: grid + place-items: center
148
134
  if (display === 'grid' && placeItems === 'center') {
149
135
  return {
150
136
  name: 'Center',
@@ -152,7 +138,6 @@ function detectComponent(declarations) {
152
138
  implicitCss: ['display: grid', 'place-items: center'],
153
139
  };
154
140
  }
155
- // Flex
156
141
  if (display === 'flex') {
157
142
  return {
158
143
  name: 'Flex',
@@ -160,7 +145,6 @@ function detectComponent(declarations) {
160
145
  implicitCss: ['display: flex'],
161
146
  };
162
147
  }
163
- // Grid
164
148
  if (display === 'grid') {
165
149
  return {
166
150
  name: 'Grid',
@@ -177,6 +161,7 @@ function findCategory(mappings, propName) {
177
161
  const found = mappings.find((m) => m.prop === propName);
178
162
  return found?.sectionName ?? 'unknown';
179
163
  }
164
+ /** 変換結果からJSX使用例を組み立てる。 */
180
165
  function buildExample(conversions, component) {
181
166
  const tagName = component?.name ?? 'Lism';
182
167
  const implicitCssSet = new Set(component?.implicitCss.map((c) => c.split(':')[0].trim()) ?? []);
@@ -188,7 +173,7 @@ function buildExample(conversions, component) {
188
173
  styles.push(conv.css);
189
174
  continue;
190
175
  }
191
- // コンポーネントが暗黙的に付与する CSS はスキップ
176
+ // コンポーネントが暗黙に持つCSSは重複出力しない。
192
177
  if (implicitCssSet.has(cssProp))
193
178
  continue;
194
179
  if (conv.suggestedValue != null) {
@@ -232,7 +217,6 @@ export function registerConvertCss(server) {
232
217
  annotations: READ_ONLY_ANNOTATIONS,
233
218
  }, ({ css }) => {
234
219
  try {
235
- // @ ルール検出
236
220
  const atRuleError = detectAtRules(css);
237
221
  if (atRuleError) {
238
222
  return error(atRuleError);
@@ -244,7 +228,7 @@ export function registerConvertCss(server) {
244
228
  if (declarations.length === 0) {
245
229
  return error('No CSS declarations found. Provide CSS in "property: value;" format.');
246
230
  }
247
- // 各宣言を変換
231
+ // 各宣言をLism Propと候補値へ変換する。
248
232
  const conversions = declarations.map((decl) => {
249
233
  const mapping = cssPropertyMap.get(decl.property);
250
234
  if (!mapping) {
@@ -272,9 +256,8 @@ export function registerConvertCss(server) {
272
256
  : `Use as a custom value (category: ${category})`,
273
257
  };
274
258
  });
275
- // コンポーネント検出
259
+ // 変換結果からコンポーネント候補と使用例を組み立てる。
276
260
  const suggestedComponent = detectComponent(declarations);
277
- // 使用例
278
261
  const example = buildExample(conversions, suggestedComponent);
279
262
  return success({
280
263
  conversions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lism-css/mcp",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "MCP server for lism-css documentation and API reference.",
5
5
  "keywords": [
6
6
  "mcp",