@ncds/ui-admin-mcp 1.0.0-alpha.2 → 1.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/bin/components.bundle.js +15 -19
  2. package/bin/definitions/external/editor.d.ts +50 -0
  3. package/bin/definitions/external/editor.js +53 -0
  4. package/bin/definitions/external/step-guide.d.ts +61 -0
  5. package/bin/definitions/external/step-guide.js +52 -0
  6. package/bin/definitions/instructions.md +194 -10
  7. package/bin/definitions/js-api.json +352 -0
  8. package/bin/definitions/rules.json +36 -4
  9. package/bin/definitions/tool-definitions.json +33 -9
  10. package/bin/overrides/composition.json +2500 -0
  11. package/bin/server.d.ts +5 -0
  12. package/bin/server.js +97 -33
  13. package/bin/server.mjs +0 -0
  14. package/bin/tools/external/editor.d.ts +18 -0
  15. package/bin/tools/external/editor.js +88 -0
  16. package/bin/tools/external/step-guide.d.ts +19 -0
  17. package/bin/tools/external/step-guide.js +79 -0
  18. package/bin/tools/getComponentProps.d.ts +3 -0
  19. package/bin/tools/getComponentProps.js +12 -3
  20. package/bin/tools/listCompositionOverrides.d.ts +61 -0
  21. package/bin/tools/listCompositionOverrides.js +156 -0
  22. package/bin/tools/ping.d.ts +1 -1
  23. package/bin/tools/renderToHtml.d.ts +38 -7
  24. package/bin/tools/renderToHtml.js +785 -110
  25. package/bin/tools/searchComponent.d.ts +5 -0
  26. package/bin/tools/searchComponent.js +3 -3
  27. package/bin/tools/validateHtml.d.ts +8 -6
  28. package/bin/tools/validateHtml.js +74 -6
  29. package/bin/types.d.ts +60 -1
  30. package/bin/utils/bemValidator.d.ts +16 -8
  31. package/bin/utils/bemValidator.js +16 -4
  32. package/bin/utils/compliance.d.ts +7 -6
  33. package/bin/utils/compliance.js +8 -4
  34. package/bin/utils/dataLoader.d.ts +43 -14
  35. package/bin/utils/dataLoader.js +125 -22
  36. package/bin/utils/domEnvironment.js +51 -0
  37. package/bin/utils/fuzzyMatch.d.ts +4 -0
  38. package/bin/utils/fuzzyMatch.js +13 -3
  39. package/bin/utils/logger.d.ts +5 -5
  40. package/bin/utils/logger.js +5 -5
  41. package/bin/utils/response.d.ts +4 -2
  42. package/bin/utils/response.js +15 -4
  43. package/bin/utils/tokenValidator.d.ts +4 -3
  44. package/bin/utils/tokenValidator.js +13 -11
  45. package/bin/version.d.ts +4 -2
  46. package/bin/version.js +4 -2
  47. package/data/_icons.json +357 -2
  48. package/data/_meta.json +4 -5
  49. package/data/_tokens.json +8 -8
  50. package/data/badge-group.json +181 -4
  51. package/data/badge.json +146 -14
  52. package/data/block-container.json +95 -0
  53. package/data/block-header.json +208 -0
  54. package/data/bread-crumb.json +38 -2
  55. package/data/button-group.json +59 -0
  56. package/data/button.json +124 -1
  57. package/data/carousel-arrow.json +6 -11
  58. package/data/carousel-number-group.json +2 -12
  59. package/data/checkbox.json +1 -1
  60. package/data/combo-box.json +32 -8
  61. package/data/data-grid.json +240 -0
  62. package/data/date-picker.json +22 -2
  63. package/data/divider.json +1 -1
  64. package/data/dot.json +2 -2
  65. package/data/dropdown.json +187 -20
  66. package/data/editor.json +85 -0
  67. package/data/empty-state.json +168 -3
  68. package/data/featured-icon.json +20 -5
  69. package/data/file-input.json +176 -10
  70. package/data/horizontal-tab.json +219 -3
  71. package/data/image-file-input.json +176 -10
  72. package/data/input-base.json +165 -4
  73. package/data/modal.json +266 -5
  74. package/data/notification.json +56 -40
  75. package/data/number-input.json +164 -4
  76. package/data/page-title.json +135 -0
  77. package/data/pagination.json +8 -4
  78. package/data/password-input.json +252 -13
  79. package/data/progress-bar.json +28 -8
  80. package/data/progress-circle.json +9 -6
  81. package/data/radio.json +4 -3
  82. package/data/range-date-picker-with-buttons.json +187 -7
  83. package/data/range-date-picker.json +186 -6
  84. package/data/select-box.json +48 -16
  85. package/data/select.json +35 -25
  86. package/data/slider.json +1 -1
  87. package/data/spinner.json +3 -4
  88. package/data/step-guide.json +130 -0
  89. package/data/switch.json +66 -6
  90. package/data/table.json +293 -0
  91. package/data/tag.json +68 -6
  92. package/data/textarea.json +1 -1
  93. package/data/toggle.json +2 -2
  94. package/data/tooltip.json +16 -3
  95. package/data/vertical-tab.json +220 -3
  96. package/package.json +27 -25
  97. package/bin/instructions.d.ts +0 -1
  98. package/bin/instructions.js +0 -14
  99. package/bin/tools/getComponentHtml.d.ts +0 -3
  100. package/bin/tools/getComponentHtml.js +0 -30
@@ -3,57 +3,142 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getAllComponents = exports.getComponent = exports.loadInstructions = exports.loadComplianceRules = exports.loadTokenData = exports.loadMeta = exports.loadIconData = exports.loadComponentsFromDir = exports.DEFAULT_DATA_DIR = void 0;
6
+ exports.loadTokenData = exports.loadMeta = exports.loadJsApi = exports.loadInstructions = exports.loadIconData = exports.loadComponentsFromDir = exports.loadCompositionOverrides = exports.loadComplianceRules = exports.getComponent = exports.getAllComponents = exports.DEFAULT_DATA_DIR = void 0;
7
7
  /**
8
8
  * dataLoader — mcp/data/*.json + definitions/ 파일을 읽어 반환
9
9
  *
10
10
  * 함수 분류:
11
- * loadComponentsFromDir, loadIconData, loadCdnMeta, loadInstructions → 부수효과 (fs I/O)
11
+ * loadComponentsFromDir, loadCompositionOverrides, loadIconData, loadCdnMeta, loadInstructions → 부수효과 (fs I/O)
12
12
  * getComponent, getAllComponents → 순수 함수 (Map 조회)
13
13
  *
14
- * Map 소유권은 server.ts가 가진다. 이 파일은 상태를 보유하지 않는다.
14
+ * Map + 메타데이터 소유권은 server.ts가 가진다. 이 파일은 상태를 보유하지 않는다.
15
15
  */
16
16
  const fs_1 = __importDefault(require("fs"));
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const logger_js_1 = require("./logger.js");
19
19
  const response_js_1 = require("./response.js");
20
- exports.DEFAULT_DATA_DIR = path_1.default.resolve(__dirname, '../../data');
20
+ const DEFAULT_DATA_DIR = path_1.default.resolve(__dirname, '../../data');
21
+ exports.DEFAULT_DATA_DIR = DEFAULT_DATA_DIR;
22
+ /**
23
+ * P8: Composition Overrides 위치.
24
+ * extract-mcp-data.ts 가 만드는 data/*.json 위에 컴포넌트 디렉토리를 건드리지 않고
25
+ * allowedChildren / allowedParents / canonicalExample / canonicalExamples / bemClassesExtra 를
26
+ * 추가하는 단일 채널.
27
+ *
28
+ * 빌드 산출물(bin/) 에서는 src/overrides 가 build:server 단계에서 복사되지 않을 수 있으므로
29
+ * src 와 bin 양쪽을 fallback 으로 본다.
30
+ */
31
+ const COMPOSITION_OVERRIDES_CANDIDATES = [
32
+ path_1.default.resolve(__dirname, '../../src/overrides/composition.json'),
33
+ path_1.default.resolve(__dirname, '../overrides/composition.json'),
34
+ ];
35
+ /** composition.json 파일에서 메타 키를 제거하여 CompositionEntry 객체만 수집 */
36
+ const filterCompositionEntries = (parsed) => {
37
+ if (typeof parsed !== 'object' || !parsed)
38
+ return {};
39
+ const entries = {};
40
+ for (const [key, value] of Object.entries(parsed)) {
41
+ if (key.startsWith('_'))
42
+ continue;
43
+ if (value && typeof value === 'object' && !Array.isArray(value))
44
+ entries[key] = value;
45
+ }
46
+ return entries;
47
+ };
48
+ const loadCompositionOverrides = () => {
49
+ for (const candidate of COMPOSITION_OVERRIDES_CANDIDATES) {
50
+ try {
51
+ const raw = fs_1.default.readFileSync(candidate, 'utf-8');
52
+ const parsed = JSON.parse(raw);
53
+ const entries = filterCompositionEntries(parsed);
54
+ logger_js_1.logger.info(`composition overrides 로딩: ${Object.keys(entries).length}개 (${path_1.default.basename(candidate)})`);
55
+ return entries;
56
+ }
57
+ catch (err) {
58
+ // ENOENT: 파일 없으면 다음 후보 시도 (TOCTOU 회피)
59
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
60
+ continue;
61
+ }
62
+ logger_js_1.logger.warn(`composition overrides 파싱 실패 (${candidate}): ${(0, response_js_1.toErrorMessage)(err)}`);
63
+ return {};
64
+ }
65
+ }
66
+ return {};
67
+ };
68
+ exports.loadCompositionOverrides = loadCompositionOverrides;
69
+ /** P8: `_` 접두 메타 키를 객체에서 제거 (얕은 필터). canonicalExamples 같은 중첩 맵에서 _note 누출 방지. */
70
+ const stripMetaKeys = (obj) => {
71
+ if (!obj)
72
+ return obj;
73
+ const cleaned = {};
74
+ for (const [key, value] of Object.entries(obj)) {
75
+ if (key.startsWith('_'))
76
+ continue;
77
+ cleaned[key] = value;
78
+ }
79
+ return cleaned;
80
+ };
81
+ /** ComponentData 에 composition overrides 항목을 in-place 병합 — bemClasses 는 union, 나머지는 추가 필드. */
82
+ const mergeCompositionEntry = (component, entry) => {
83
+ if (entry.bemClassesExtra && entry.bemClassesExtra.length > 0) {
84
+ const merged = new Set([...component.bemClasses, ...entry.bemClassesExtra]);
85
+ component.bemClasses = [...merged].sort();
86
+ }
87
+ if (entry.allowedChildren)
88
+ component.allowedChildren = entry.allowedChildren;
89
+ if (entry.allowedParents)
90
+ component.allowedParents = entry.allowedParents;
91
+ if (entry.canonicalExample) {
92
+ // canonicalExample 내부 _note / _comment 같은 메타 키 제거 — 응답에 누출되지 않도록
93
+ component.canonicalExample = stripMetaKeys(entry.canonicalExample);
94
+ }
95
+ if (entry.canonicalExamples) {
96
+ // canonicalExamples 내부 _note / _comment 같은 메타 키 제거 — 응답에 누출되지 않도록
97
+ component.canonicalExamples = stripMetaKeys(entry.canonicalExamples);
98
+ }
99
+ // P13: description 끝에 append — list_components 응답에서 컴포넌트 용도 명시
100
+ if (entry.descriptionExtra) {
101
+ component.description = `${component.description}\n\n${entry.descriptionExtra}`;
102
+ }
103
+ // P13: aliases union — search_component 별칭 보강 (중복 제거)
104
+ if (entry.aliasesExtra && entry.aliasesExtra.length > 0) {
105
+ const set = new Set([...(component.aliases ?? []), ...entry.aliasesExtra]);
106
+ component.aliases = [...set];
107
+ }
108
+ };
21
109
  // ── 검증 헬퍼 (§ 3.8 복잡한 조건을 의도가 드러나는 이름으로 분리) ──────────
22
110
  /** JSON.parse 결과가 name: string 필드를 가진 컴포넌트 객체인지 검증 */
23
111
  const isValidComponentJson = (data) => !!data && typeof data === 'object' && 'name' in data && typeof data.name === 'string';
24
112
  /** JSON.parse 결과가 icons 배열을 가진 아이콘 데이터인지 검증 */
25
113
  const isValidIconJson = (data) => !!data && typeof data === 'object' && 'icons' in data && Array.isArray(data.icons);
26
114
  // ── 부수효과 함수 (fs I/O) ────────────────────────────────────────────────
27
- /** data/ 디렉토리의 JSON 파일을 읽어 componentMap으로 반환 */
115
+ /** data/ 디렉토리의 JSON 파일을 읽어 componentMap + compositionOverrides로 반환 */
28
116
  const loadComponentsFromDir = (dataDir) => {
29
117
  if (!fs_1.default.existsSync(dataDir)) {
30
- logger_js_1.logger.error(`mcp/data/ 디렉토리가 없습니다: ${dataDir}`);
31
- process.exit(1);
118
+ throw new Error(`mcp/data/ 디렉토리가 없습니다: ${dataDir}`);
32
119
  }
33
120
  const jsonFiles = fs_1.default.readdirSync(dataDir).filter((f) => f.endsWith('.json') && !f.startsWith('_'));
34
121
  if (jsonFiles.length === 0) {
35
- logger_js_1.logger.error(`mcp/data/ 디렉토리에 JSON 파일이 없습니다: ${dataDir}`);
36
- process.exit(1);
122
+ throw new Error(`mcp/data/ 디렉토리에 JSON 파일이 없습니다: ${dataDir}`);
37
123
  }
38
124
  const map = new Map();
125
+ // P8: composition overrides (composition.json) 를 한 번만 로딩해 각 컴포넌트에 병합 — 호출자에게도 반환
126
+ const compositionOverrides = loadCompositionOverrides();
39
127
  for (const file of jsonFiles) {
40
128
  const filePath = path_1.default.join(dataDir, file);
41
- try {
42
- const raw = fs_1.default.readFileSync(filePath, 'utf-8');
43
- const parsed = JSON.parse(raw);
44
- if (!isValidComponentJson(parsed)) {
45
- throw new Error('name 필드가 없거나 올바르지 않습니다');
46
- }
47
- const component = parsed;
48
- map.set(component.name, component);
49
- }
50
- catch (err) {
51
- logger_js_1.logger.error(`JSON 파싱 실패 (${file}): ${(0, response_js_1.toErrorMessage)(err)}`);
52
- process.exit(1);
129
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
130
+ const parsed = JSON.parse(raw);
131
+ if (!isValidComponentJson(parsed)) {
132
+ throw new Error(`JSON 파싱 실패 (${file}): name 필드가 없거나 올바르지 않습니다`);
53
133
  }
134
+ const component = parsed;
135
+ const compositionOverridesEntry = compositionOverrides[component.name];
136
+ if (compositionOverridesEntry)
137
+ mergeCompositionEntry(component, compositionOverridesEntry);
138
+ map.set(component.name, component);
54
139
  }
55
140
  logger_js_1.logger.info(`컴포넌트 ${map.size}개 로딩 완료`);
56
- return map;
141
+ return { map, compositionOverrides };
57
142
  };
58
143
  exports.loadComponentsFromDir = loadComponentsFromDir;
59
144
  /** data/_icons.json을 읽어 IconData로 반환 (파일 없으면 빈 데이터) */
@@ -165,6 +250,24 @@ const loadInstructions = (definitionsDir) => {
165
250
  return fs_1.default.readFileSync(instructionsPath, 'utf-8').trim();
166
251
  };
167
252
  exports.loadInstructions = loadInstructions;
253
+ /** definitions/js-api.json을 로드하여 exportName→JsApiInfo Map 반환 */
254
+ const loadJsApi = (definitionsDir) => {
255
+ const filePath = path_1.default.resolve(definitionsDir, 'js-api.json');
256
+ if (!fs_1.default.existsSync(filePath)) {
257
+ logger_js_1.logger.info('js-api.json이 없습니다 — JS API 힌트 비활성화');
258
+ return new Map();
259
+ }
260
+ try {
261
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
262
+ const data = JSON.parse(raw);
263
+ return new Map(Object.entries(data));
264
+ }
265
+ catch (err) {
266
+ logger_js_1.logger.error(`js-api.json 파싱 실패: ${(0, response_js_1.toErrorMessage)(err)}`);
267
+ return new Map();
268
+ }
269
+ };
270
+ exports.loadJsApi = loadJsApi;
168
271
  // ── 순수 함수 (외부 상태 없음, Map을 파라미터로 받음) ──────────────────────
169
272
  /** componentMap에서 이름으로 단일 컴포넌트 조회 */
170
273
  const getComponent = (map, name) => map.get(name);
@@ -1,8 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setupDomEnvironment = void 0;
4
+ /**
5
+ * monorepo에서 패키지가 여러 node_modules에 설치될 수 있음.
6
+ * 번들의 external require가 MCP 로컬 node_modules의 버전을 사용하도록 통일한다.
7
+ *
8
+ * 문제: esbuild 번들은 ui-admin cwd에서 빌드되므로, external require가
9
+ * 루트 node_modules(오래된 버전)를 resolve할 수 있다.
10
+ * 해결: MCP 패키지 기준 require.resolve로 경로를 고정한다.
11
+ */
12
+ const unifyModuleResolution = () => {
13
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
14
+ const Module = require('module');
15
+ const overrides = {};
16
+ const pin = (name) => {
17
+ try {
18
+ overrides[name] = require.resolve(name);
19
+ }
20
+ catch {
21
+ /* 미설치 패키지는 무시 */
22
+ }
23
+ };
24
+ // React 인스턴스 통일 (Hook dispatcher 일치 보장)
25
+ pin('react');
26
+ pin('react-dom');
27
+ pin('react/jsx-runtime');
28
+ // external 패키지 버전 통일 (monorepo hoisting 불일치 방지)
29
+ pin('@ncds/ui-admin-icon');
30
+ pin('swiper/react');
31
+ pin('swiper');
32
+ const origResolve = Module._resolveFilename;
33
+ Module._resolveFilename = function (request, parent, isMain, options) {
34
+ if (request in overrides)
35
+ return overrides[request];
36
+ return origResolve.call(this, request, parent, isMain, options);
37
+ };
38
+ };
4
39
  /** jsdom + React SSR 환경을 초기화하고 ReactRuntime을 반환 */
5
40
  const setupDomEnvironment = () => {
41
+ unifyModuleResolution();
6
42
  // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
7
43
  const { JSDOM } = require('jsdom');
8
44
  const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
@@ -10,9 +46,24 @@ const setupDomEnvironment = () => {
10
46
  globalThis.document = dom.window.document;
11
47
  if (typeof globalThis.window === 'undefined')
12
48
  globalThis.window = dom.window;
49
+ /* v8 ignore next 3 -- Node.js 환경에서 navigator 미존재 시 설정. 테스트 실행 순서에 따라 이미 설정된 경우 스킵 */
13
50
  if (typeof globalThis.navigator === 'undefined') {
14
51
  Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator, writable: true });
15
52
  }
53
+ // Swiper 등 외부 라이브러리가 HTMLElement를 직접 참조 — jsdom에서 전역 노출
54
+ for (const key of [
55
+ 'HTMLElement',
56
+ 'HTMLDivElement',
57
+ 'HTMLSpanElement',
58
+ 'Element',
59
+ 'Node',
60
+ 'Event',
61
+ 'CustomEvent',
62
+ ]) {
63
+ if (typeof globalThis[key] === 'undefined') {
64
+ globalThis[key] = dom.window[key];
65
+ }
66
+ }
16
67
  // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
17
68
  const ReactDOM = require('react-dom');
18
69
  ReactDOM.createPortal = (children) => children;
@@ -5,6 +5,10 @@
5
5
  * - 부분 포함 ("비번" ⊂ "비밀번호"는 안 되지만, "로딩" ⊂ "로딩중"은 됨)
6
6
  * - 편집 거리(Levenshtein) 기반 오타 허용 ("셀랙트" ↔ "셀렉트", "buttn" ↔ "button")
7
7
  */
8
+ /** Levenshtein 편집 거리 */
9
+ export declare const editDistance: (a: string, b: string) => number;
10
+ /** 편집 거리 기반 유사도 (0~1, 1이 완전 일치) */
11
+ export declare const similarity: (a: string, b: string) => number;
8
12
  export interface FuzzyResult {
9
13
  score: number;
10
14
  matchType: 'exact' | 'contains' | 'fuzzy';
@@ -7,7 +7,7 @@
7
7
  * - 편집 거리(Levenshtein) 기반 오타 허용 ("셀랙트" ↔ "셀렉트", "buttn" ↔ "button")
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.bestFuzzyMatch = exports.fuzzyMatch = void 0;
10
+ exports.bestFuzzyMatch = exports.fuzzyMatch = exports.similarity = exports.editDistance = void 0;
11
11
  // ── 상수 ──────────────────────────────────────────────────────────
12
12
  /** 포함 매칭 시 기본 점수 */
13
13
  const CONTAINS_BASE_SCORE = 0.7;
@@ -49,11 +49,21 @@ const editDistance = (a, b) => {
49
49
  }
50
50
  return prev[n];
51
51
  };
52
+ exports.editDistance = editDistance;
53
+ /** 편집 거리 기반 유사도 (0~1, 1이 완전 일치) */
54
+ const similarity = (a, b) => {
55
+ const maxLen = Math.max(a.length, b.length);
56
+ if (maxLen === 0)
57
+ return 1;
58
+ return 1 - (0, exports.editDistance)(a, b) / maxLen;
59
+ };
60
+ exports.similarity = similarity;
52
61
  /**
53
62
  * query가 target에 매칭되는지 퍼지 검사
54
63
  *
55
64
  * @returns null이면 매칭 실패, FuzzyResult면 매칭 성공
56
65
  */
66
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 정확/포함/퍼지 단계별 매칭 분기 — 알고리즘 본질, 별 리팩토링 거리
57
67
  const fuzzyMatch = (query, target) => {
58
68
  const q = normalize(query);
59
69
  const t = normalize(target);
@@ -71,7 +81,7 @@ const fuzzyMatch = (query, target) => {
71
81
  // 3. 편집 거리 기반 퍼지 매칭
72
82
  const isShortQuery = q.length <= SHORT_QUERY_LENGTH;
73
83
  const typoThreshold = isShortQuery ? 1 : Math.ceil(q.length * TYPO_THRESHOLD_RATIO);
74
- const dist = editDistance(q, t);
84
+ const dist = (0, exports.editDistance)(q, t);
75
85
  // target이 query보다 훨씬 길면, 부분 문자열 슬라이딩 윈도우로 비교
76
86
  const targetIsLonger = t.length > q.length + LENGTH_DIFF_FOR_SUBSTRING;
77
87
  const queryIsLongEnough = q.length >= SHORT_QUERY_LENGTH;
@@ -80,7 +90,7 @@ const fuzzyMatch = (query, target) => {
80
90
  if (shouldTrySubstringMatch) {
81
91
  for (let i = 0; i <= t.length - q.length; i++) {
82
92
  const sub = t.substring(i, i + q.length);
83
- const subDist = editDistance(q, sub);
93
+ const subDist = (0, exports.editDistance)(q, sub);
84
94
  if (subDist <= typoThreshold) {
85
95
  const subSimilarity = q.length === 0 ? 1 : 1 - subDist / q.length;
86
96
  return { score: SUBSTRING_BASE_SCORE + SUBSTRING_BONUS_MULTIPLIER * subSimilarity, matchType: 'fuzzy' };
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * stderr 전용 logger 유틸리티 — 부수효과 함수 (stderr I/O)
3
3
  *
4
- * ⚠️ console.log / console.warn / console.error 직접 사용 금지
4
+ * console.log / console.warn / console.error 직접 사용 금지.
5
5
  *
6
- * MCP stdio 프로토콜은 채널 역할이 엄격히 분리됩니다.
6
+ * MCP stdio 프로토콜은 채널 역할이 엄격히 분리된다.
7
7
  * stdin → 클라이언트 → 서버 요청(JSON-RPC)
8
- * stdout → 서버 → 클라이언트 응답(JSON-RPC) 프로토콜 전용 채널
8
+ * stdout → 서버 → 클라이언트 응답(JSON-RPC). 프로토콜 전용 채널
9
9
  * stderr → 로그 전용 (프로토콜 밖)
10
10
  *
11
- * console.log는 stdout 출력되어 JSON-RPC 스트림을 오염시킵니다.
12
- * 모든 로깅은 반드시 이 logger를 통해 stderr로 출력해야 합니다.
11
+ * console.log는 stdout 오염시켜 JSON-RPC 스트림을 깨뜨린다.
12
+ * 모든 로깅은 이 logger를 통해 stderr로 출력한다.
13
13
  */
14
14
  export declare const logger: {
15
15
  info: (msg: string) => void;
@@ -4,15 +4,15 @@ exports.logger = void 0;
4
4
  /**
5
5
  * stderr 전용 logger 유틸리티 — 부수효과 함수 (stderr I/O)
6
6
  *
7
- * ⚠️ console.log / console.warn / console.error 직접 사용 금지
7
+ * console.log / console.warn / console.error 직접 사용 금지.
8
8
  *
9
- * MCP stdio 프로토콜은 채널 역할이 엄격히 분리됩니다.
9
+ * MCP stdio 프로토콜은 채널 역할이 엄격히 분리된다.
10
10
  * stdin → 클라이언트 → 서버 요청(JSON-RPC)
11
- * stdout → 서버 → 클라이언트 응답(JSON-RPC) 프로토콜 전용 채널
11
+ * stdout → 서버 → 클라이언트 응답(JSON-RPC). 프로토콜 전용 채널
12
12
  * stderr → 로그 전용 (프로토콜 밖)
13
13
  *
14
- * console.log는 stdout 출력되어 JSON-RPC 스트림을 오염시킵니다.
15
- * 모든 로깅은 반드시 이 logger를 통해 stderr로 출력해야 합니다.
14
+ * console.log는 stdout 오염시켜 JSON-RPC 스트림을 깨뜨린다.
15
+ * 모든 로깅은 이 logger를 통해 stderr로 출력한다.
16
16
  */
17
17
  exports.logger = {
18
18
  info: (msg) => {
@@ -16,10 +16,12 @@ export type McpToolResponse = {
16
16
  };
17
17
  /** 성공 응답 — data를 JSON.stringify하여 반환 */
18
18
  export declare const successResponse: (data: unknown) => McpToolResponse;
19
- /** 오류 응답 — code + message + suggestion 구조로 isError: true 반환 */
20
- export declare const errorResponse: (code: McpErrorCode, message: string, suggestion: string) => McpToolResponse;
19
+ /** 오류 응답 — code + message + suggestion + optional warnings 구조로 isError: true 반환 */
20
+ export declare const errorResponse: (code: McpErrorCode, message: string, suggestion: string, warnings?: string[]) => McpToolResponse;
21
21
  /** COMPONENT_NOT_FOUND 오류 — 여러 tool에서 공통 사용 */
22
22
  export declare const componentNotFoundResponse: (name: string) => McpToolResponse;
23
+ /** ping 미호출 시 응답에 핵심 rules를 주입하는 래퍼 */
24
+ export declare const appendPingReminder: (response: McpToolResponse, reminder: string) => McpToolResponse;
23
25
  /** 입력 정규화 — name.trim().toLowerCase() */
24
26
  export declare const normalizeName: (name: string) => string;
25
27
  /** catch 블록에서 에러 메시지를 안전하게 추출 */
@@ -1,17 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toErrorMessage = exports.normalizeName = exports.componentNotFoundResponse = exports.errorResponse = exports.successResponse = void 0;
3
+ exports.toErrorMessage = exports.normalizeName = exports.appendPingReminder = exports.componentNotFoundResponse = exports.errorResponse = exports.successResponse = void 0;
4
4
  /** 성공 응답 — data를 JSON.stringify하여 반환 */
5
5
  const successResponse = (data) => ({
6
6
  content: [{ type: 'text', text: JSON.stringify(data) }],
7
7
  });
8
8
  exports.successResponse = successResponse;
9
- /** 오류 응답 — code + message + suggestion 구조로 isError: true 반환 */
10
- const errorResponse = (code, message, suggestion) => ({
9
+ /** 오류 응답 — code + message + suggestion + optional warnings 구조로 isError: true 반환 */
10
+ const errorResponse = (code, message, suggestion, warnings) => ({
11
11
  content: [
12
12
  {
13
13
  type: 'text',
14
- text: JSON.stringify({ code, message, suggestion }),
14
+ text: JSON.stringify({
15
+ code,
16
+ message,
17
+ suggestion,
18
+ ...(warnings && warnings.length > 0 && { warnings }),
19
+ }),
15
20
  },
16
21
  ],
17
22
  isError: true,
@@ -20,6 +25,12 @@ exports.errorResponse = errorResponse;
20
25
  /** COMPONENT_NOT_FOUND 오류 — 여러 tool에서 공통 사용 */
21
26
  const componentNotFoundResponse = (name) => (0, exports.errorResponse)('COMPONENT_NOT_FOUND', `Component '${name}' not found.`, 'Use search_component to find similar components, or list_components to see all available.');
22
27
  exports.componentNotFoundResponse = componentNotFoundResponse;
28
+ /** ping 미호출 시 응답에 핵심 rules를 주입하는 래퍼 */
29
+ const appendPingReminder = (response, reminder) => {
30
+ const text = `${response.content[0].text}\n\n${reminder}`;
31
+ return { ...response, content: [{ type: 'text', text }] };
32
+ };
33
+ exports.appendPingReminder = appendPingReminder;
23
34
  /** 입력 정규화 — name.trim().toLowerCase() */
24
35
  const normalizeName = (name) => name.trim().toLowerCase();
25
36
  exports.normalizeName = normalizeName;
@@ -6,11 +6,11 @@
6
6
  import type { parse } from 'node-html-parser';
7
7
  import type { ComplianceError, TokenData } from '../types.js';
8
8
  /** hex 값(lowercase) → 토큰 CSS 변수명 배열 역방향 매핑 빌드 — server.ts에서 1회 호출 */
9
- export declare const buildTokenValueMap: (tokenData: TokenData) => Map<string, string[]>;
9
+ declare const buildTokenValueMap: (tokenData: TokenData) => Map<string, string[]>;
10
10
  /** inline style + <style> 블록의 CSS 텍스트를 하나로 합침 */
11
- export declare const extractAllCss: (root: ReturnType<typeof parse>) => string;
11
+ declare const extractAllCss: (root: ReturnType<typeof parse>) => string;
12
12
  /** 토큰 미사용 + 무효 토큰 감지 */
13
- export declare const detectTokenIssues: (params: {
13
+ declare const detectTokenIssues: (params: {
14
14
  root: ReturnType<typeof parse>;
15
15
  tokenData: TokenData;
16
16
  tokenValueMap: Map<string, string[]>;
@@ -22,3 +22,4 @@ export declare const detectTokenIssues: (params: {
22
22
  invalid: number;
23
23
  };
24
24
  };
25
+ export { buildTokenValueMap, detectTokenIssues, extractAllCss };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectTokenIssues = exports.extractAllCss = exports.buildTokenValueMap = void 0;
3
+ exports.extractAllCss = exports.detectTokenIssues = exports.buildTokenValueMap = void 0;
4
4
  // ── server.ts 사전 계산용 ───────────────────────────────────────────────
5
5
  /** hex 값(lowercase) → 토큰 CSS 변수명 배열 역방향 매핑 빌드 — server.ts에서 1회 호출 */
6
6
  const buildTokenValueMap = (tokenData) => {
@@ -25,14 +25,18 @@ const HEX_PATTERN_SOURCE = /#([0-9a-fA-F]{3,8})\b/g.source;
25
25
  const RGB_PATTERN_SOURCE = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/g.source;
26
26
  const VAR_PATTERN_SOURCE = /var\(\s*(--[a-zA-Z0-9-]+)(?:\s*,\s*[^)]+)?\s*\)/g.source;
27
27
  // ── 순수 헬퍼 ────────────────────────────────────────────────────────────
28
+ const HEX_RADIX = 16;
29
+ const HEX_BYTE_LENGTH = 2;
30
+ const HEX_SHORT_LENGTH = 3;
31
+ const HEX_FULL_LENGTH = 6;
28
32
  /** rgb(r,g,b) → #rrggbb hex 변환 */
29
- const rgbToHex = (r, g, b) => `#${[r, g, b].map((v) => v.toString(16).padStart(2, '0')).join('')}`;
33
+ const rgbToHex = (r, g, b) => `#${[r, g, b].map((v) => v.toString(HEX_RADIX).padStart(HEX_BYTE_LENGTH, '0')).join('')}`;
30
34
  /** 3자리 hex를 6자리로 정규화 */
31
35
  const normalizeHex = (hex) => {
32
36
  const h = hex.toLowerCase().replace('#', '');
33
- if (h.length === 3)
37
+ if (h.length === HEX_SHORT_LENGTH)
34
38
  return `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`;
35
- return `#${h.slice(0, 6)}`;
39
+ return `#${h.slice(0, HEX_FULL_LENGTH)}`;
36
40
  };
37
41
  /** var()의 prefix가 토큰 시스템에 속하는지 확인 */
38
42
  const hasKnownTokenPrefix = (varName, prefixSet) => {
@@ -83,16 +87,15 @@ exports.extractAllCss = extractAllCss;
83
87
  /** CSS 텍스트에서 하드코딩된 색상값(hex/rgb)을 추출 */
84
88
  const collectHardcodedColors = (css) => {
85
89
  const colors = [];
86
- let match;
87
90
  const hexPattern = new RegExp(HEX_PATTERN_SOURCE, 'g');
88
- while ((match = hexPattern.exec(css)) !== null) {
91
+ for (const match of css.matchAll(hexPattern)) {
89
92
  colors.push({ raw: match[0], normalized: normalizeHex(match[0]) });
90
93
  }
91
94
  const rgbPattern = new RegExp(RGB_PATTERN_SOURCE, 'g');
92
- while ((match = rgbPattern.exec(css)) !== null) {
95
+ for (const match of css.matchAll(rgbPattern)) {
93
96
  colors.push({
94
97
  raw: match[0],
95
- normalized: rgbToHex(parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10)),
98
+ normalized: rgbToHex(Number.parseInt(match[1], 10), Number.parseInt(match[2], 10), Number.parseInt(match[3], 10)),
96
99
  });
97
100
  }
98
101
  return colors;
@@ -126,9 +129,8 @@ const collectInvalidTokenErrors = (css, tokenNameSet, tokenPrefixSet) => {
126
129
  const errors = [];
127
130
  let correct = 0;
128
131
  let invalid = 0;
129
- let varMatch;
130
132
  const varPattern = new RegExp(VAR_PATTERN_SOURCE, 'g');
131
- while ((varMatch = varPattern.exec(css)) !== null) {
133
+ for (const varMatch of css.matchAll(varPattern)) {
132
134
  const varName = varMatch[1];
133
135
  if (!hasKnownTokenPrefix(varName, tokenPrefixSet))
134
136
  continue;
@@ -151,7 +153,7 @@ const collectInvalidTokenErrors = (css, tokenNameSet, tokenPrefixSet) => {
151
153
  /** 토큰 미사용 + 무효 토큰 감지 */
152
154
  const detectTokenIssues = (params) => {
153
155
  const { root, tokenData, tokenValueMap } = params;
154
- const allCss = (0, exports.extractAllCss)(root);
156
+ const allCss = extractAllCss(root);
155
157
  const notUsed = collectTokenNotUsedErrors(allCss, tokenValueMap);
156
158
  const invalidResult = collectInvalidTokenErrors(allCss, buildTokenNameSet(tokenData), buildTokenPrefixSet(tokenData));
157
159
  return {
package/bin/version.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  /**
2
- * version 빌드 scripts/build-npm.js가 자동 생성 (수동 편집 금지)
2
+ * NCDS UI Admin MCP 서버가 노출하는 ui-admin 정합 기준 버전.
3
+ * scripts/generate-version-ts.ts 가 ui-admin/package.json 의 version 으로부터 자동 생성한다.
4
+ * 수동 편집 금지 — 빌드(`pnpm generate:version-ts` 또는 `pnpm build`) 시 덮어쓰여진다.
3
5
  */
4
- export declare const VERSION = "1.6.3";
6
+ export declare const VERSION = "1.8.7";
package/bin/version.js CHANGED
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  /**
5
- * version 빌드 scripts/build-npm.js가 자동 생성 (수동 편집 금지)
5
+ * NCDS UI Admin MCP 서버가 노출하는 ui-admin 정합 기준 버전.
6
+ * scripts/generate-version-ts.ts 가 ui-admin/package.json 의 version 으로부터 자동 생성한다.
7
+ * 수동 편집 금지 — 빌드(`pnpm generate:version-ts` 또는 `pnpm build`) 시 덮어쓰여진다.
6
8
  */
7
- exports.VERSION = '1.6.3';
9
+ exports.VERSION = '1.8.7';