@chemx/create-chemx 26.9.16-1249 → 26.9.16-1408
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/README.md +4 -0
- package/assets/chemx-starter-kit-logo.gif +0 -0
- package/cli/audit/pattern-detector.js +90 -3
- package/cli/audit/pattern-detector.spec.js +36 -1
- package/cli/mcp/tools.js +14 -4
- package/cli/project-detector.js +8 -2
- package/cli/project-detector.spec.js +19 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Chemical X Protocol: Starter Kit
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="./assets/chemx-starter-kit-logo.gif" alt="Chemical X Starter Kit Comic Logo" width="380" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
[](https://chemicalx.xophz.com)
|
|
4
8
|
[](https://github.com/Chemical-X-Protocol/awesome-secret-sauce)
|
|
5
9
|
[](https://github.com/Chemical-X-Protocol/benchmarks)
|
|
Binary file
|
|
@@ -49,9 +49,10 @@ export const createPatternRegistry = () => {
|
|
|
49
49
|
suggestedCapsule = 'types/state.d.ts';
|
|
50
50
|
recommendation = 'Extract shared discriminated union into domain types to eliminate duplicate definitions';
|
|
51
51
|
} else if (bucket.type === 'UI_STRUCTURE') {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const categorization = categorizeUiStructure(bucket.detail);
|
|
53
|
+
label = categorization.label;
|
|
54
|
+
suggestedCapsule = categorization.suggestedCapsule;
|
|
55
|
+
recommendation = categorization.recommendation;
|
|
55
56
|
} else if (bucket.type === 'PREDICATE_LOGIC') {
|
|
56
57
|
label = `Duplicated Boolean Predicate Topology (${bucket.detail})`;
|
|
57
58
|
suggestedCapsule = 'usePredicateFilter.ts';
|
|
@@ -84,6 +85,92 @@ export const createPatternRegistry = () => {
|
|
|
84
85
|
return { record, resolveHarmonizationCandidates };
|
|
85
86
|
};
|
|
86
87
|
|
|
88
|
+
export const categorizeUiStructure = (signature = '') => {
|
|
89
|
+
const normalized = signature.toLowerCase();
|
|
90
|
+
|
|
91
|
+
const isSvg =
|
|
92
|
+
normalized.startsWith('svg>') ||
|
|
93
|
+
normalized.includes('path+path') ||
|
|
94
|
+
normalized.includes('polygon') ||
|
|
95
|
+
normalized.includes('circle');
|
|
96
|
+
if (isSvg) {
|
|
97
|
+
const isMultiLayer = (normalized.match(/path|circle|rect|polygon|g/g) || []).length > 3;
|
|
98
|
+
return {
|
|
99
|
+
label: `Shared Vector Glyph (${signature})`,
|
|
100
|
+
suggestedCapsule: isMultiLayer ? 'm-vector-glyph' : 'a-icon',
|
|
101
|
+
recommendation: 'Extract foundational icon atom or vector glyph capsule'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const hasButton =
|
|
106
|
+
normalized.includes('btn') ||
|
|
107
|
+
normalized.includes('button');
|
|
108
|
+
if (hasButton) {
|
|
109
|
+
const hasTextOrTitle =
|
|
110
|
+
normalized.includes('span') ||
|
|
111
|
+
normalized.includes('title') ||
|
|
112
|
+
normalized.includes('h1') ||
|
|
113
|
+
normalized.includes('h2') ||
|
|
114
|
+
normalized.includes('h3') ||
|
|
115
|
+
normalized.includes('h4') ||
|
|
116
|
+
normalized.includes('p');
|
|
117
|
+
|
|
118
|
+
if (hasTextOrTitle) {
|
|
119
|
+
return {
|
|
120
|
+
label: `Shared Action Header (${signature})`,
|
|
121
|
+
suggestedCapsule: 'm-action-header',
|
|
122
|
+
recommendation: 'Extract action header molecule capsule to standardize button actions'
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
label: `Shared Button Row (${signature})`,
|
|
128
|
+
suggestedCapsule: 'm-button-row',
|
|
129
|
+
recommendation: 'Extract button row molecule capsule to consolidate action bar'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const hasChipOrBadge =
|
|
134
|
+
normalized.includes('chip') ||
|
|
135
|
+
normalized.includes('badge') ||
|
|
136
|
+
normalized.includes('pill') ||
|
|
137
|
+
normalized.includes('tag');
|
|
138
|
+
if (hasChipOrBadge) {
|
|
139
|
+
const chipCount = (normalized.match(/chip|badge|pill|tag/g) || []).length;
|
|
140
|
+
if (chipCount > 1) {
|
|
141
|
+
return {
|
|
142
|
+
label: `Shared Pill Row (${signature})`,
|
|
143
|
+
suggestedCapsule: 'm-pill-row',
|
|
144
|
+
recommendation: 'Extract pill row molecule capsule to group status indicators'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
label: `Shared Status Badge (${signature})`,
|
|
150
|
+
suggestedCapsule: 'm-status-badge',
|
|
151
|
+
recommendation: 'Extract status badge atom or capsule to unify state tags'
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const hasFormInput =
|
|
156
|
+
normalized.includes('input') ||
|
|
157
|
+
normalized.includes('textarea') ||
|
|
158
|
+
normalized.includes('select');
|
|
159
|
+
if (hasFormInput) {
|
|
160
|
+
return {
|
|
161
|
+
label: `Shared Form Field (${signature})`,
|
|
162
|
+
suggestedCapsule: 'm-form-field',
|
|
163
|
+
recommendation: 'Extract form field molecule encapsulating label and input atom'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
label: `Shared UI Layout Structure (${signature})`,
|
|
169
|
+
suggestedCapsule: 'm-feature-card',
|
|
170
|
+
recommendation: 'Extract canonical molecule capsule before slicing consumer monoliths'
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
87
174
|
const VOID_ELEMENTS = new Set([
|
|
88
175
|
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'
|
|
89
176
|
]);
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
createPatternRegistry,
|
|
5
5
|
extractTemplateTokens,
|
|
6
6
|
buildTagTree,
|
|
7
|
-
recordTemplatePatterns
|
|
7
|
+
recordTemplatePatterns,
|
|
8
|
+
categorizeUiStructure
|
|
8
9
|
} from './pattern-detector.js';
|
|
9
10
|
|
|
10
11
|
test('extractTemplateTokens: extracts tags, ignoring comments and handling quotes', () => {
|
|
@@ -56,6 +57,40 @@ test('buildTagTree and recordTemplatePatterns: captures UI_STRUCTURE from Vue te
|
|
|
56
57
|
|
|
57
58
|
assert.ok(uiCandidate, 'Expected UI_STRUCTURE candidate to be found');
|
|
58
59
|
assert.equal(uiCandidate.detail, 'v-sheet>(x-btn+x-btn)');
|
|
60
|
+
assert.equal(uiCandidate.suggestedCapsule, 'm-button-row');
|
|
59
61
|
assert.equal(uiCandidate.fileCount, 2);
|
|
60
62
|
assert.deepEqual(uiCandidate.uniqueFiles.sort(), ['src/components/A.vue', 'src/components/B.vue']);
|
|
61
63
|
});
|
|
64
|
+
|
|
65
|
+
test('categorizeUiStructure: maps semantic AST topologies to specialized capsules', () => {
|
|
66
|
+
// SVG dual path -> a-icon
|
|
67
|
+
const svgRes = categorizeUiStructure('svg>(path+path)');
|
|
68
|
+
assert.equal(svgRes.suggestedCapsule, 'a-icon');
|
|
69
|
+
assert.ok(svgRes.label.includes('Vector Glyph'));
|
|
70
|
+
|
|
71
|
+
// Action header -> m-action-header
|
|
72
|
+
const actionRes = categorizeUiStructure('div>(span+v-btn)');
|
|
73
|
+
assert.equal(actionRes.suggestedCapsule, 'm-action-header');
|
|
74
|
+
assert.ok(actionRes.label.includes('Action Header'));
|
|
75
|
+
|
|
76
|
+
// Button row -> m-button-row
|
|
77
|
+
const btnRowRes = categorizeUiStructure('div>(v-btn+v-btn)');
|
|
78
|
+
assert.equal(btnRowRes.suggestedCapsule, 'm-button-row');
|
|
79
|
+
|
|
80
|
+
// Status badge -> m-status-badge
|
|
81
|
+
const badgeRes = categorizeUiStructure('div>(span+v-chip)');
|
|
82
|
+
assert.equal(badgeRes.suggestedCapsule, 'm-status-badge');
|
|
83
|
+
|
|
84
|
+
// Pill row -> m-pill-row
|
|
85
|
+
const pillRowRes = categorizeUiStructure('div>(v-chip+v-chip)');
|
|
86
|
+
assert.equal(pillRowRes.suggestedCapsule, 'm-pill-row');
|
|
87
|
+
|
|
88
|
+
// Form field -> m-form-field
|
|
89
|
+
const formRes = categorizeUiStructure('div>(label+input)');
|
|
90
|
+
assert.equal(formRes.suggestedCapsule, 'm-form-field');
|
|
91
|
+
|
|
92
|
+
// Fallback -> m-feature-card
|
|
93
|
+
const fallbackRes = categorizeUiStructure('div>(div+div)');
|
|
94
|
+
assert.equal(fallbackRes.suggestedCapsule, 'm-feature-card');
|
|
95
|
+
});
|
|
96
|
+
|
package/cli/mcp/tools.js
CHANGED
|
@@ -184,13 +184,23 @@ const handleQueryPatterns = (args = {}, cwd = process.cwd()) => {
|
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
if (isCompact) {
|
|
187
|
+
const sampleOccurrences = [];
|
|
188
|
+
const seenSampleFiles = new Set();
|
|
189
|
+
for (const occ of (p.occurrences || [])) {
|
|
190
|
+
if (!seenSampleFiles.has(occ.filePath)) {
|
|
191
|
+
seenSampleFiles.add(occ.filePath);
|
|
192
|
+
sampleOccurrences.push({
|
|
193
|
+
file: occ.filePath,
|
|
194
|
+
line: occ.line
|
|
195
|
+
});
|
|
196
|
+
if (sampleOccurrences.length >= 3) break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
187
200
|
return {
|
|
188
201
|
...base,
|
|
189
202
|
files: p.uniqueFiles || Array.from(new Set((p.occurrences || []).map((o) => o.filePath))),
|
|
190
|
-
sampleOccurrences
|
|
191
|
-
file: o.filePath,
|
|
192
|
-
line: o.line
|
|
193
|
-
}))
|
|
203
|
+
sampleOccurrences
|
|
194
204
|
};
|
|
195
205
|
}
|
|
196
206
|
|
package/cli/project-detector.js
CHANGED
|
@@ -51,9 +51,15 @@ export const detectInstalledFamily = (cwd = process.cwd()) => {
|
|
|
51
51
|
const atomsCandidate = ATOMS_PACKAGES.find((name) => Boolean(deps[name]));
|
|
52
52
|
const paletteCandidate = PALETTE_PACKAGES.find((name) => Boolean(deps[name]));
|
|
53
53
|
|
|
54
|
+
const localAtomsCandidate =
|
|
55
|
+
fs.existsSync(path.resolve(cwd, 'src/components/atoms')) ? '@/components/atoms' :
|
|
56
|
+
fs.existsSync(path.resolve(cwd, 'src/atoms')) ? '@/atoms' : null;
|
|
57
|
+
|
|
58
|
+
const resolvedAtomsPackage = atomsCandidate || localAtomsCandidate || null;
|
|
59
|
+
|
|
54
60
|
return {
|
|
55
|
-
hasAtoms: Boolean(
|
|
56
|
-
atomsPackage:
|
|
61
|
+
hasAtoms: Boolean(resolvedAtomsPackage),
|
|
62
|
+
atomsPackage: resolvedAtomsPackage,
|
|
57
63
|
hasPalette: Boolean(paletteCandidate),
|
|
58
64
|
palettePackage: paletteCandidate || null
|
|
59
65
|
};
|
|
@@ -102,3 +102,22 @@ test('project-detector: detectInstalledFamily discovers @chemx/x-atoms and @chem
|
|
|
102
102
|
|
|
103
103
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
test('project-detector: detectInstalledFamily falls back to local components/atoms facade', () => {
|
|
107
|
+
const tmpDir = path.resolve(process.cwd(), 'scratch/test-detector-local-atoms');
|
|
108
|
+
if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
109
|
+
fs.mkdirSync(path.join(tmpDir, 'src/components/atoms'), { recursive: true });
|
|
110
|
+
|
|
111
|
+
fs.writeFileSync(
|
|
112
|
+
path.join(tmpDir, 'package.json'),
|
|
113
|
+
JSON.stringify({ dependencies: {} }),
|
|
114
|
+
'utf-8'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const family = detectInstalledFamily(tmpDir);
|
|
118
|
+
assert.strictEqual(family.hasAtoms, true);
|
|
119
|
+
assert.strictEqual(family.atomsPackage, '@/components/atoms');
|
|
120
|
+
|
|
121
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
122
|
+
});
|
|
123
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chemx/create-chemx",
|
|
3
|
-
"version": "26.9.16-
|
|
3
|
+
"version": "26.9.16-1408",
|
|
4
4
|
"description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"scripts",
|
|
26
26
|
"README.md",
|
|
27
27
|
"LICENSE",
|
|
28
|
-
"docs"
|
|
28
|
+
"docs",
|
|
29
|
+
"assets"
|
|
29
30
|
],
|
|
30
31
|
"exports": {
|
|
31
32
|
".": "./cli/index.js",
|