@1money/component-ui 0.0.23 → 0.0.25

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 (107) hide show
  1. package/es/components/Table/interface.d.ts +2 -1
  2. package/es/components/Table/renderers/EmptyState.d.ts +2 -1
  3. package/es/components/Table/renderers/EmptyState.js +15 -8
  4. package/es/components/Table/style/Table.css +1 -1
  5. package/es/index.css +1 -1
  6. package/es/stories/docs/ComponentDocsPage.js +234 -0
  7. package/es/stories/docs/componentDocMeta.js +97 -0
  8. package/es/stories/docs/storybook-docs.css +79 -0
  9. package/lib/components/Table/interface.d.ts +2 -1
  10. package/lib/components/Table/renderers/EmptyState.d.ts +2 -1
  11. package/lib/components/Table/renderers/EmptyState.js +15 -7
  12. package/lib/components/Table/style/Table.css +1 -1
  13. package/lib/index.css +1 -1
  14. package/lib/stories/docs/ComponentDocsPage.js +244 -0
  15. package/lib/stories/docs/componentDocMeta.js +104 -0
  16. package/lib/stories/docs/storybook-docs.css +79 -0
  17. package/package.json +23 -8
  18. package/scripts/mcp-server/README.md +267 -0
  19. package/scripts/mcp-server/bin.mjs +2 -0
  20. package/scripts/mcp-server/drift.json +5 -0
  21. package/scripts/mcp-server/examples.generated.json +2651 -0
  22. package/scripts/mcp-server/index.generated.json +18098 -0
  23. package/scripts/mcp-server/index.mjs +308 -26
  24. package/scripts/mcp-server/tools/get-examples.mjs +125 -0
  25. package/scripts/mcp-server/tools/get-library-info.mjs +25 -0
  26. package/scripts/mcp-server/tools/get-symbol.mjs +232 -0
  27. package/scripts/mcp-server/tools/get-token.mjs +60 -0
  28. package/scripts/mcp-server/tools/list-icons.mjs +38 -0
  29. package/scripts/mcp-server/tools/list-symbols.mjs +46 -0
  30. package/scripts/mcp-server/tools/resolve-import.mjs +125 -0
  31. package/scripts/mcp-server/tools/search-symbols.mjs +79 -0
  32. package/.agents/skills/1money-component-dev/SKILL.md +0 -224
  33. package/.agents/skills/1money-component-dev/checklist.md +0 -159
  34. package/.agents/skills/1money-component-dev/references/ComponentPatterns.md +0 -478
  35. package/.agents/skills/1money-component-dev/references/FigmaExtractionChecklist.md +0 -144
  36. package/.agents/skills/1money-component-dev/references/HooksGuide.md +0 -360
  37. package/.agents/skills/1money-component-dev/references/SemanticColors.md +0 -215
  38. package/.agents/skills/1money-component-dev/references/StyleSystemAPI.md +0 -389
  39. package/.claude/settings.local.json +0 -120
  40. package/.claude/skills/1money-component-dev/SKILL.md +0 -229
  41. package/.claude/skills/1money-component-dev/checklist.md +0 -159
  42. package/.claude/skills/1money-component-dev/references/ComponentPatterns.md +0 -478
  43. package/.claude/skills/1money-component-dev/references/FigmaExtractionChecklist.md +0 -144
  44. package/.claude/skills/1money-component-dev/references/HooksGuide.md +0 -360
  45. package/.claude/skills/1money-component-dev/references/SemanticColors.md +0 -215
  46. package/.claude/skills/1money-component-dev/references/StyleSystemAPI.md +0 -389
  47. package/.claude/skills/1money-component-review/SKILL.md +0 -316
  48. package/.claude/skills/component-pipeline/SKILL.md +0 -116
  49. package/.claude/skills/component-pipeline/checklist.md +0 -125
  50. package/.hintrc +0 -13
  51. package/@types/global.d.ts +0 -28
  52. package/AGENTS.md +0 -546
  53. package/CLAUDE.md +0 -1
  54. package/jest.setup.d.ts +0 -1
  55. package/jest.setup.ts +0 -1
  56. package/patches/primereact.patch +0 -323
  57. package/patches/react-pro-sidebar.patch +0 -6421
  58. package/public/favicon.ico +0 -0
  59. package/public/fonts/Aeonik/Aeonik-Air.ttf +0 -0
  60. package/public/fonts/Aeonik/Aeonik-AirItalic.ttf +0 -0
  61. package/public/fonts/Aeonik/Aeonik-Black.ttf +0 -0
  62. package/public/fonts/Aeonik/Aeonik-BlackItalic.ttf +0 -0
  63. package/public/fonts/Aeonik/Aeonik-Bold.ttf +0 -0
  64. package/public/fonts/Aeonik/Aeonik-BoldItalic.ttf +0 -0
  65. package/public/fonts/Aeonik/Aeonik-Light.ttf +0 -0
  66. package/public/fonts/Aeonik/Aeonik-LightItalic.ttf +0 -0
  67. package/public/fonts/Aeonik/Aeonik-Medium.ttf +0 -0
  68. package/public/fonts/Aeonik/Aeonik-MediumItalic.ttf +0 -0
  69. package/public/fonts/Aeonik/Aeonik-Regular.ttf +0 -0
  70. package/public/fonts/Aeonik/Aeonik-RegularItalic.ttf +0 -0
  71. package/public/fonts/Aeonik/Aeonik-Thin.ttf +0 -0
  72. package/public/fonts/Aeonik/Aeonik-ThinItalic.ttf +0 -0
  73. package/public/fonts/Inter/Inter-Black.ttf +0 -0
  74. package/public/fonts/Inter/Inter-BlackItalic.ttf +0 -0
  75. package/public/fonts/Inter/Inter-Bold.ttf +0 -0
  76. package/public/fonts/Inter/Inter-BoldItalic.ttf +0 -0
  77. package/public/fonts/Inter/Inter-ExtraBold.ttf +0 -0
  78. package/public/fonts/Inter/Inter-ExtraBoldItalic.ttf +0 -0
  79. package/public/fonts/Inter/Inter-ExtraLight.ttf +0 -0
  80. package/public/fonts/Inter/Inter-ExtraLightItalic.ttf +0 -0
  81. package/public/fonts/Inter/Inter-Italic.ttf +0 -0
  82. package/public/fonts/Inter/Inter-Light.ttf +0 -0
  83. package/public/fonts/Inter/Inter-LightItalic.ttf +0 -0
  84. package/public/fonts/Inter/Inter-Medium.ttf +0 -0
  85. package/public/fonts/Inter/Inter-MediumItalic.ttf +0 -0
  86. package/public/fonts/Inter/Inter-Regular.ttf +0 -0
  87. package/public/fonts/Inter/Inter-SemiBold.ttf +0 -0
  88. package/public/fonts/Inter/Inter-SemiBoldItalic.ttf +0 -0
  89. package/public/fonts/Inter/Inter-Thin.ttf +0 -0
  90. package/public/fonts/Inter/Inter-ThinItalic.ttf +0 -0
  91. package/public/fonts/Outfit/Outfit-Black.ttf +0 -0
  92. package/public/fonts/Outfit/Outfit-Bold.ttf +0 -0
  93. package/public/fonts/Outfit/Outfit-ExtraBold.ttf +0 -0
  94. package/public/fonts/Outfit/Outfit-ExtraLight.ttf +0 -0
  95. package/public/fonts/Outfit/Outfit-Light.ttf +0 -0
  96. package/public/fonts/Outfit/Outfit-Medium.ttf +0 -0
  97. package/public/fonts/Outfit/Outfit-Regular.ttf +0 -0
  98. package/public/fonts/Outfit/Outfit-SemiBold.ttf +0 -0
  99. package/public/fonts/Outfit/Outfit-Thin.ttf +0 -0
  100. package/public/github-mark.svg +0 -3
  101. package/public/tokens/GYEN.svg +0 -9
  102. package/public/tokens/PYUSD.svg +0 -9
  103. package/public/tokens/USDT.svg +0 -6
  104. package/scripts/mcp-server/resources.d.mts +0 -1
  105. package/scripts/mcp-server/resources.mjs +0 -102
  106. package/test/jsdom-global-register.d.ts +0 -1
  107. package/test/jsdom-global-register.js +0 -1
@@ -0,0 +1,232 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-env node */
3
+ /**
4
+ * Tool: get_symbol
5
+ *
6
+ * Resolves flat names (`ProFormItem`) AND dotted paths (`ProForm.Item`) and
7
+ * returns a rich record. See plan §3.3 and §4 for the exact resolution
8
+ * algorithm.
9
+ */
10
+
11
+ const VALID_INCLUDES = ['props', 'examples', 'members', 'notes', 'related'];
12
+ const DEFAULT_INCLUDE = ['props', 'members'];
13
+ const LOCAL_PREFIX = '$local:';
14
+ const EXAMPLES_LIMIT = 3;
15
+
16
+ const ICONS_DOTTED = /^Icons\.[A-Z][A-Za-z0-9]*$/;
17
+ const HOOK_PREFIX = /^use[A-Z]/;
18
+
19
+ function validateInput(input) {
20
+ const args = input && typeof input === 'object' ? input : {};
21
+ if (typeof args.name !== 'string' || args.name.trim().length === 0) {
22
+ throw new Error('get_symbol: `name` must be a non-empty string');
23
+ }
24
+ let include;
25
+ if (args.include === undefined) {
26
+ include = DEFAULT_INCLUDE;
27
+ } else if (Array.isArray(args.include)) {
28
+ for (const entry of args.include) {
29
+ if (!VALID_INCLUDES.includes(entry)) {
30
+ throw new Error(
31
+ `get_symbol: invalid include entry '${entry}', expected one of ${VALID_INCLUDES.join(', ')}`
32
+ );
33
+ }
34
+ }
35
+ include = args.include;
36
+ } else {
37
+ throw new Error('get_symbol: `include` must be an array of strings when provided');
38
+ }
39
+ return { name: args.name.trim(), include };
40
+ }
41
+
42
+ /**
43
+ * Build a name -> symbol map and a reverse owner lookup that maps any flat
44
+ * member name back to its owner module (so we can populate namespaceAccess).
45
+ */
46
+ function buildLookups(index) {
47
+ const byName = new Map();
48
+ const ownerLookup = new Map();
49
+ for (const sym of index.symbols) {
50
+ byName.set(sym.name, sym);
51
+ }
52
+ for (const sym of index.symbols) {
53
+ if (sym.kind === 'module' && sym.memberMap && typeof sym.memberMap === 'object') {
54
+ for (const [key, target] of Object.entries(sym.memberMap)) {
55
+ if (typeof target === 'string' && !target.startsWith(LOCAL_PREFIX)) {
56
+ if (!ownerLookup.has(target)) {
57
+ ownerLookup.set(target, { owner: sym.name, memberKey: key });
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ return { byName, ownerLookup };
64
+ }
65
+
66
+ function notExported(inputName, hint) {
67
+ return { kind: 'not_exported', name: inputName, inputName, hint };
68
+ }
69
+
70
+ function buildHintForMissing(name) {
71
+ if (ICONS_DOTTED.test(name)) {
72
+ return 'Icon names are string keys, not exported symbols. Use `<Icons name="chevronDown" />` (camelCase) and call `list_icons` to discover available names.';
73
+ }
74
+ if (HOOK_PREFIX.test(name)) {
75
+ return 'This hook may live in `@1money/hooks`, not `@1money/component-ui`. Check that package.';
76
+ }
77
+ return `No exported symbol named '${name}'. Use search_symbols to discover candidates.`;
78
+ }
79
+
80
+ function buildImports(sym, owner, ownerMemberKey) {
81
+ const imports = {
82
+ named: `import { ${sym.name} } from '@1money/component-ui';`,
83
+ css: "import '@1money/component-ui/index.css'; // global, covers all"
84
+ };
85
+ if (owner && ownerMemberKey) {
86
+ imports.namespaceAccess = `${owner}.${ownerMemberKey}`;
87
+ }
88
+ if (sym.subpathImport) {
89
+ imports.subpath = `import { ${sym.name} } from '${sym.subpathImport}';`;
90
+ }
91
+ return imports;
92
+ }
93
+
94
+ function buildMembers(sym) {
95
+ if (sym.kind !== 'module' || !sym.memberMap || typeof sym.memberMap !== 'object') {
96
+ return [];
97
+ }
98
+ return Object.entries(sym.memberMap).map(([key, target]) => {
99
+ const isLocal = typeof target === 'string' && target.startsWith(LOCAL_PREFIX);
100
+ const flatName = isLocal ? target.slice(LOCAL_PREFIX.length) : target;
101
+ return { key, flatName, isLocal };
102
+ });
103
+ }
104
+
105
+ function buildCanonicalUsage(sym) {
106
+ if (typeof sym.canonicalUsage === 'string' && sym.canonicalUsage.length > 0) {
107
+ return sym.canonicalUsage;
108
+ }
109
+ if (sym.kind === 'module') {
110
+ return `// ${sym.name} is a namespace; see members via get_symbol('${sym.name}').members`;
111
+ }
112
+ return `import { ${sym.name} } from '@1money/component-ui';`;
113
+ }
114
+
115
+ function hydrateExamples(sym, examplesIndex) {
116
+ if (!examplesIndex || typeof examplesIndex.examples !== 'object') return [];
117
+ const refs = Array.isArray(sym.examples) ? sym.examples : [];
118
+ const hydrated = [];
119
+ for (const ref of refs) {
120
+ if (!ref || typeof ref.hash !== 'string') continue;
121
+ const body = examplesIndex.examples[ref.hash];
122
+ if (!body) continue;
123
+ hydrated.push({
124
+ title: body.title,
125
+ code: body.code,
126
+ source: body.source,
127
+ compilable: body.compilable,
128
+ });
129
+ if (hydrated.length >= EXAMPLES_LIMIT) break;
130
+ }
131
+ return hydrated;
132
+ }
133
+
134
+ function buildRecord(sym, inputName, { ownerLookup, include, examplesIndex }) {
135
+ const ownerRef = ownerLookup.get(sym.name);
136
+ const imports = buildImports(
137
+ sym,
138
+ ownerRef ? ownerRef.owner : null,
139
+ ownerRef ? ownerRef.memberKey : null
140
+ );
141
+
142
+ const record = {
143
+ name: sym.name,
144
+ inputName,
145
+ kind: sym.kind,
146
+ category: sym.category ?? null,
147
+ summary: sym.summary ?? '',
148
+ imports,
149
+ extends: Array.isArray(sym.extends) ? sym.extends : [],
150
+ members: [],
151
+ canonicalUsage: buildCanonicalUsage(sym),
152
+ relatedTypes: [],
153
+ relatedSymbols: Array.isArray(sym.relatedSymbols) ? sym.relatedSymbols : [],
154
+ examples: []
155
+ };
156
+
157
+ if (include.includes('props')) {
158
+ record.props = Array.isArray(sym.props) ? sym.props : [];
159
+ }
160
+ if (include.includes('members')) {
161
+ record.members = buildMembers(sym);
162
+ }
163
+ if (include.includes('notes')) {
164
+ record.notes = sym.notes ?? [];
165
+ }
166
+ if (include.includes('related')) {
167
+ // already included as relatedSymbols; include relatedTypes passthrough
168
+ record.relatedTypes = Array.isArray(sym.relatedTypes) ? sym.relatedTypes : [];
169
+ }
170
+ if (include.includes('examples')) {
171
+ record.examples = hydrateExamples(sym, examplesIndex);
172
+ }
173
+
174
+ return record;
175
+ }
176
+
177
+ /**
178
+ * Resolve a dotted-path lookup. Returns either the resolved symbol record
179
+ * or a not_exported object per plan §4.
180
+ */
181
+ function resolveDotted(inputName, byName) {
182
+ const dotIdx = inputName.indexOf('.');
183
+ const owner = inputName.slice(0, dotIdx);
184
+ const member = inputName.slice(dotIdx + 1);
185
+ const ownerSym = byName.get(owner);
186
+ if (ownerSym && ownerSym.memberMap && typeof ownerSym.memberMap === 'object') {
187
+ const target = ownerSym.memberMap[member];
188
+ if (target) {
189
+ if (typeof target === 'string' && target.startsWith(LOCAL_PREFIX)) {
190
+ return {
191
+ kind: 'not_exported',
192
+ name: inputName,
193
+ inputName,
194
+ hint: `This is an internal member of ${owner}, not a separately-exported symbol.`
195
+ };
196
+ }
197
+ const flat = byName.get(target);
198
+ if (flat) {
199
+ return { __sym: flat };
200
+ }
201
+ }
202
+ }
203
+ return null;
204
+ }
205
+
206
+ export default function getSymbol(input, ctx) {
207
+ const { name, include } = validateInput(input);
208
+ const { index, examplesIndex } = ctx;
209
+ const { byName, ownerLookup } = buildLookups(index);
210
+ const recordCtx = { ownerLookup, include, examplesIndex };
211
+
212
+ // Step 1: dotted-path attempt.
213
+ if (name.includes('.')) {
214
+ const dotted = resolveDotted(name, byName);
215
+ if (dotted && dotted.__sym) {
216
+ return buildRecord(dotted.__sym, name, recordCtx);
217
+ }
218
+ if (dotted && dotted.kind === 'not_exported') {
219
+ return dotted;
220
+ }
221
+ // fall through to fallback handlers.
222
+ return notExported(name, buildHintForMissing(name));
223
+ }
224
+
225
+ // Step 2: flat lookup.
226
+ const flat = byName.get(name);
227
+ if (flat) {
228
+ return buildRecord(flat, name, recordCtx);
229
+ }
230
+
231
+ return notExported(name, buildHintForMissing(name));
232
+ }
@@ -0,0 +1,60 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-env node */
3
+ /**
4
+ * Tool: get_token
5
+ *
6
+ * Looks up design tokens by name, kind, or name prefix. All filters
7
+ * combine with AND semantics when multiple are supplied.
8
+ */
9
+
10
+ const VALID_KINDS = [
11
+ 'color',
12
+ 'spacing',
13
+ 'radius',
14
+ 'shadow',
15
+ 'sizing',
16
+ 'opacity',
17
+ 'typography',
18
+ 'breakpoint'
19
+ ];
20
+
21
+ function validateInput(input) {
22
+ const args = input && typeof input === 'object' ? input : {};
23
+ const name = typeof args.name === 'string' ? args.name.trim() : null;
24
+ const prefix = typeof args.prefix === 'string' ? args.prefix.trim() : null;
25
+ let kind = null;
26
+ if (args.kind !== undefined) {
27
+ if (typeof args.kind !== 'string' || !VALID_KINDS.includes(args.kind)) {
28
+ throw new Error(
29
+ `get_token: invalid kind '${args.kind}', expected one of ${VALID_KINDS.join(', ')}`
30
+ );
31
+ }
32
+ kind = args.kind;
33
+ }
34
+ return { name, prefix, kind };
35
+ }
36
+
37
+ export default function getToken(input, ctx) {
38
+ const { name, prefix, kind } = validateInput(input);
39
+ const { index } = ctx;
40
+
41
+ const tokens = Array.isArray(index.tokens) ? index.tokens : [];
42
+
43
+ let result = tokens;
44
+ if (name) {
45
+ result = result.filter(
46
+ t =>
47
+ t.name === name ||
48
+ t.cssVar === name ||
49
+ t.scss === name
50
+ );
51
+ }
52
+ if (kind) {
53
+ result = result.filter(t => t.kind === kind);
54
+ }
55
+ if (prefix) {
56
+ result = result.filter(t => typeof t.name === 'string' && t.name.startsWith(prefix));
57
+ }
58
+
59
+ return { tokens: result };
60
+ }
@@ -0,0 +1,38 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-env node */
3
+ /**
4
+ * Tool: list_icons
5
+ *
6
+ * Returns available icon names (string keys) from the generated index.
7
+ */
8
+
9
+ function validateInput(input) {
10
+ const args = input && typeof input === 'object' ? input : {};
11
+ const query = typeof args.query === 'string' ? args.query.trim() : '';
12
+ const limit =
13
+ typeof args.limit === 'number' && Number.isFinite(args.limit) && args.limit > 0
14
+ ? Math.floor(args.limit)
15
+ : null;
16
+ return { query, limit };
17
+ }
18
+
19
+ export default function listIcons(input, ctx) {
20
+ const { query, limit } = validateInput(input);
21
+ const { index } = ctx;
22
+
23
+ const source = Array.isArray(index.icons) ? index.icons : [];
24
+ let filtered = source;
25
+ if (query) {
26
+ const lower = query.toLowerCase();
27
+ filtered = source.filter(entry =>
28
+ typeof entry?.name === 'string' && entry.name.toLowerCase().includes(lower)
29
+ );
30
+ }
31
+
32
+ const total = filtered.length;
33
+ const icons = (limit != null ? filtered.slice(0, limit) : filtered).map(entry => ({
34
+ name: entry.name
35
+ }));
36
+
37
+ return { total, icons };
38
+ }
@@ -0,0 +1,46 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-env node */
3
+ /**
4
+ * Tool: list_symbols
5
+ *
6
+ * Lists top-level exports from the generated index. Namespace roots (ProForm,
7
+ * Icons, Input) are surfaced as `kind: 'module'`.
8
+ */
9
+
10
+ const VALID_KINDS = ['component', 'module', 'hook', 'function', 'type', 'all'];
11
+
12
+ function validateInput(input) {
13
+ const args = input && typeof input === 'object' ? input : {};
14
+ const kind = args.kind ?? 'all';
15
+ if (!VALID_KINDS.includes(kind)) {
16
+ throw new Error(
17
+ `list_symbols: invalid kind '${kind}', expected one of ${VALID_KINDS.join(', ')}`
18
+ );
19
+ }
20
+ const category = typeof args.category === 'string' ? args.category : undefined;
21
+ return { kind, category };
22
+ }
23
+
24
+ export default function listSymbols(input, ctx) {
25
+ const { kind, category } = validateInput(input);
26
+ const { index } = ctx;
27
+
28
+ const filtered = index.symbols.filter(sym => {
29
+ if (kind !== 'all' && sym.kind !== kind) return false;
30
+ if (category && sym.category !== category) return false;
31
+ return true;
32
+ });
33
+
34
+ const symbols = filtered.map(sym => ({
35
+ name: sym.name,
36
+ kind: sym.kind,
37
+ category: sym.category ?? null,
38
+ summary: sym.summary ?? '',
39
+ importPath: sym.importPath ?? null
40
+ }));
41
+
42
+ // Stable alphabetical sort for predictable output.
43
+ symbols.sort((a, b) => a.name.localeCompare(b.name));
44
+
45
+ return { symbols };
46
+ }
@@ -0,0 +1,125 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-env node */
3
+ /**
4
+ * Tool: resolve_import
5
+ *
6
+ * Resolves how to import a symbol (or namespace member). Supports flat
7
+ * names (`Button`), namespace roots (`Icons`), and dotted-member paths
8
+ * (`ProForm.Item`). `Icons.<Name>` is explicitly rejected because icon
9
+ * names are string keys, not exported symbols.
10
+ */
11
+
12
+ const LOCAL_PREFIX = '$local:';
13
+ const ICONS_DOTTED = /^Icons\.[A-Z][A-Za-z0-9]*$/;
14
+
15
+ function validateInput(input) {
16
+ const args = input && typeof input === 'object' ? input : {};
17
+ if (typeof args.symbol !== 'string' || args.symbol.trim().length === 0) {
18
+ throw new Error('resolve_import: `symbol` must be a non-empty string');
19
+ }
20
+ return { symbol: args.symbol.trim() };
21
+ }
22
+
23
+ function buildLookups(index) {
24
+ const byName = new Map();
25
+ const ownerLookup = new Map();
26
+ for (const sym of index.symbols) byName.set(sym.name, sym);
27
+ for (const sym of index.symbols) {
28
+ if (sym.kind === 'module' && sym.memberMap) {
29
+ for (const [key, target] of Object.entries(sym.memberMap)) {
30
+ if (typeof target === 'string' && !target.startsWith(LOCAL_PREFIX)) {
31
+ if (!ownerLookup.has(target)) {
32
+ ownerLookup.set(target, { owner: sym.name, memberKey: key });
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return { byName, ownerLookup };
39
+ }
40
+
41
+ function notFound(inputSymbol, hint) {
42
+ return { found: false, inputSymbol, hint };
43
+ }
44
+
45
+ function buildResponse(sym, inputSymbol, { ownerLookup, drift }) {
46
+ const owner = ownerLookup.get(sym.name);
47
+ const sources = [{ specifier: '@1money/component-ui', style: 'named', preferred: true }];
48
+ if (sym.subpathImport) {
49
+ sources.push({ specifier: sym.subpathImport, style: 'named', preferred: false });
50
+ }
51
+ const response = {
52
+ found: true,
53
+ name: sym.name,
54
+ inputSymbol,
55
+ sources,
56
+ css: '@1money/component-ui/index.css'
57
+ };
58
+
59
+ const notes = [];
60
+ if (owner) {
61
+ notes.push(`Also accessible via ${owner.owner}.${owner.memberKey} namespace.`);
62
+ }
63
+
64
+ // Drift note: if any drift entry's subpath maps to this symbol's subpathImport.
65
+ if (sym.subpathImport && drift && Array.isArray(drift.orphanSubpath)) {
66
+ for (const orphan of drift.orphanSubpath) {
67
+ const orphanSpecifier = `@1money/component-ui${orphan.subpath.replace(/^\./, '')}`;
68
+ if (orphanSpecifier === sym.subpathImport) {
69
+ notes.push(`Note: subpath '${sym.subpathImport}' appears in drift report (${orphan.reason}).`);
70
+ }
71
+ }
72
+ }
73
+
74
+ if (notes.length > 0) response.notes = notes.join(' ');
75
+ return response;
76
+ }
77
+
78
+ export default function resolveImport(input, ctx) {
79
+ const { symbol } = validateInput(input);
80
+ const { index, drift } = ctx;
81
+
82
+ // Icons.<Name> short-circuit.
83
+ if (ICONS_DOTTED.test(symbol)) {
84
+ return notFound(
85
+ symbol,
86
+ 'Icons.<Name> is not a valid import; icons are string keys — use list_icons to discover names and `<Icons name="chevronDown" />` (camelCase).'
87
+ );
88
+ }
89
+
90
+ const { byName, ownerLookup } = buildLookups(index);
91
+
92
+ // Dotted path: owner.member
93
+ if (symbol.includes('.')) {
94
+ const dotIdx = symbol.indexOf('.');
95
+ const owner = symbol.slice(0, dotIdx);
96
+ const member = symbol.slice(dotIdx + 1);
97
+ const ownerSym = byName.get(owner);
98
+ if (ownerSym && ownerSym.memberMap) {
99
+ const target = ownerSym.memberMap[member];
100
+ if (target && !target.startsWith(LOCAL_PREFIX)) {
101
+ const flat = byName.get(target);
102
+ if (flat) return buildResponse(flat, symbol, { ownerLookup, drift });
103
+ }
104
+ if (target && target.startsWith(LOCAL_PREFIX)) {
105
+ return notFound(
106
+ symbol,
107
+ `${symbol} is an internal member of ${owner}; access it via the namespace only, e.g. \`import { ${owner} } from '@1money/component-ui'; <${owner}.${member} ... />\`.`
108
+ );
109
+ }
110
+ }
111
+ return notFound(
112
+ symbol,
113
+ `No exported symbol named '${symbol}'. Use search_symbols to discover candidates.`
114
+ );
115
+ }
116
+
117
+ // Flat lookup.
118
+ const flat = byName.get(symbol);
119
+ if (flat) return buildResponse(flat, symbol, { ownerLookup, drift });
120
+
121
+ return notFound(
122
+ symbol,
123
+ `No exported symbol named '${symbol}'. Use search_symbols to discover candidates.`
124
+ );
125
+ }
@@ -0,0 +1,79 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-env node */
3
+ /**
4
+ * Tool: search_symbols
5
+ *
6
+ * Fuzzy search over symbol records. Simple rule-based scorer.
7
+ */
8
+
9
+ const DEFAULT_LIMIT = 5;
10
+
11
+ function validateInput(input) {
12
+ const args = input && typeof input === 'object' ? input : {};
13
+ if (typeof args.query !== 'string' || args.query.trim().length === 0) {
14
+ throw new Error('search_symbols: `query` must be a non-empty string');
15
+ }
16
+ const limit =
17
+ typeof args.limit === 'number' && Number.isFinite(args.limit) && args.limit > 0
18
+ ? Math.floor(args.limit)
19
+ : DEFAULT_LIMIT;
20
+ return { query: args.query.trim(), limit };
21
+ }
22
+
23
+ function scoreSymbol(sym, q) {
24
+ const name = sym.name ?? '';
25
+ const lowerName = name.toLowerCase();
26
+ const lowerQ = q.toLowerCase();
27
+ const tags = Array.isArray(sym.searchTags) ? sym.searchTags : [];
28
+ const summary = (sym.summary ?? '').toLowerCase();
29
+ const propNames = Array.isArray(sym.props)
30
+ ? sym.props.map(p => (p && p.name ? String(p.name).toLowerCase() : ''))
31
+ : [];
32
+
33
+ // Name checks (most specific first).
34
+ if (lowerName === lowerQ) return { score: 100, matchedOn: 'name' };
35
+ if (lowerName.startsWith(lowerQ)) return { score: 80, matchedOn: 'name' };
36
+
37
+ // Tag exact.
38
+ if (tags.some(t => String(t).toLowerCase() === lowerQ)) {
39
+ return { score: 70, matchedOn: 'tag' };
40
+ }
41
+
42
+ // Name substring.
43
+ if (lowerName.includes(lowerQ)) return { score: 60, matchedOn: 'name' };
44
+
45
+ // Tag substring.
46
+ if (tags.some(t => String(t).toLowerCase().includes(lowerQ))) {
47
+ return { score: 50, matchedOn: 'tag' };
48
+ }
49
+
50
+ // Summary substring.
51
+ if (summary && summary.includes(lowerQ)) return { score: 30, matchedOn: 'summary' };
52
+
53
+ // Prop name match.
54
+ if (propNames.some(n => n === lowerQ || n.includes(lowerQ))) {
55
+ return { score: 20, matchedOn: 'prop' };
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ export default function searchSymbols(input, ctx) {
62
+ const { query, limit } = validateInput(input);
63
+ const { index } = ctx;
64
+
65
+ const hits = [];
66
+ for (const sym of index.symbols) {
67
+ const result = scoreSymbol(sym, query);
68
+ if (result) {
69
+ hits.push({ name: sym.name, score: result.score, matchedOn: result.matchedOn });
70
+ }
71
+ }
72
+
73
+ hits.sort((a, b) => {
74
+ if (b.score !== a.score) return b.score - a.score;
75
+ return a.name.localeCompare(b.name);
76
+ });
77
+
78
+ return { hits: hits.slice(0, limit) };
79
+ }