@nebutra/next-unicorn-skill 1.0.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.
- package/CHANGELOG.md +57 -0
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/SKILL.md +318 -0
- package/dist/analyzer/pattern-catalog.d.ts +35 -0
- package/dist/analyzer/pattern-catalog.d.ts.map +1 -0
- package/dist/analyzer/pattern-catalog.js +342 -0
- package/dist/analyzer/pattern-catalog.js.map +1 -0
- package/dist/analyzer/scanner.d.ts +33 -0
- package/dist/analyzer/scanner.d.ts.map +1 -0
- package/dist/analyzer/scanner.js +302 -0
- package/dist/analyzer/scanner.js.map +1 -0
- package/dist/auditor/ux-auditor.d.ts +26 -0
- package/dist/auditor/ux-auditor.d.ts.map +1 -0
- package/dist/auditor/ux-auditor.js +272 -0
- package/dist/auditor/ux-auditor.js.map +1 -0
- package/dist/checker/peer-dependency-checker.d.ts +62 -0
- package/dist/checker/peer-dependency-checker.d.ts.map +1 -0
- package/dist/checker/peer-dependency-checker.js +94 -0
- package/dist/checker/peer-dependency-checker.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +325 -0
- package/dist/index.js.map +1 -0
- package/dist/planner/migration-planner.d.ts +47 -0
- package/dist/planner/migration-planner.d.ts.map +1 -0
- package/dist/planner/migration-planner.js +144 -0
- package/dist/planner/migration-planner.js.map +1 -0
- package/dist/pr-creator/git-operations.d.ts +29 -0
- package/dist/pr-creator/git-operations.d.ts.map +1 -0
- package/dist/pr-creator/git-operations.js +10 -0
- package/dist/pr-creator/git-operations.js.map +1 -0
- package/dist/pr-creator/platform-client.d.ts +47 -0
- package/dist/pr-creator/platform-client.d.ts.map +1 -0
- package/dist/pr-creator/platform-client.js +9 -0
- package/dist/pr-creator/platform-client.js.map +1 -0
- package/dist/pr-creator/pr-description-builder.d.ts +23 -0
- package/dist/pr-creator/pr-description-builder.d.ts.map +1 -0
- package/dist/pr-creator/pr-description-builder.js +187 -0
- package/dist/pr-creator/pr-description-builder.js.map +1 -0
- package/dist/pr-creator/pr-executor.d.ts +30 -0
- package/dist/pr-creator/pr-executor.d.ts.map +1 -0
- package/dist/pr-creator/pr-executor.js +109 -0
- package/dist/pr-creator/pr-executor.js.map +1 -0
- package/dist/pr-creator/pr-strategy.d.ts +56 -0
- package/dist/pr-creator/pr-strategy.d.ts.map +1 -0
- package/dist/pr-creator/pr-strategy.js +125 -0
- package/dist/pr-creator/pr-strategy.js.map +1 -0
- package/dist/schemas/input.schema.d.ts +231 -0
- package/dist/schemas/input.schema.d.ts.map +1 -0
- package/dist/schemas/input.schema.js +159 -0
- package/dist/schemas/input.schema.js.map +1 -0
- package/dist/schemas/output.schema.d.ts +2543 -0
- package/dist/schemas/output.schema.d.ts.map +1 -0
- package/dist/schemas/output.schema.js +199 -0
- package/dist/schemas/output.schema.js.map +1 -0
- package/dist/scorer/impact-scorer.d.ts +45 -0
- package/dist/scorer/impact-scorer.d.ts.map +1 -0
- package/dist/scorer/impact-scorer.js +243 -0
- package/dist/scorer/impact-scorer.js.map +1 -0
- package/dist/security/osv-client.d.ts +72 -0
- package/dist/security/osv-client.d.ts.map +1 -0
- package/dist/security/osv-client.js +36 -0
- package/dist/security/osv-client.js.map +1 -0
- package/dist/security/vuln-report-builder.d.ts +18 -0
- package/dist/security/vuln-report-builder.d.ts.map +1 -0
- package/dist/security/vuln-report-builder.js +141 -0
- package/dist/security/vuln-report-builder.js.map +1 -0
- package/dist/security/vulnerability-scanner.d.ts +65 -0
- package/dist/security/vulnerability-scanner.d.ts.map +1 -0
- package/dist/security/vulnerability-scanner.js +140 -0
- package/dist/security/vulnerability-scanner.js.map +1 -0
- package/dist/updater/changelog-verifier.d.ts +29 -0
- package/dist/updater/changelog-verifier.d.ts.map +1 -0
- package/dist/updater/changelog-verifier.js +80 -0
- package/dist/updater/changelog-verifier.js.map +1 -0
- package/dist/updater/registry-client.d.ts +49 -0
- package/dist/updater/registry-client.d.ts.map +1 -0
- package/dist/updater/registry-client.js +10 -0
- package/dist/updater/registry-client.js.map +1 -0
- package/dist/updater/update-plan-builder.d.ts +23 -0
- package/dist/updater/update-plan-builder.d.ts.map +1 -0
- package/dist/updater/update-plan-builder.js +93 -0
- package/dist/updater/update-plan-builder.js.map +1 -0
- package/dist/updater/update-policy.d.ts +50 -0
- package/dist/updater/update-policy.d.ts.map +1 -0
- package/dist/updater/update-policy.js +118 -0
- package/dist/updater/update-policy.js.map +1 -0
- package/dist/updater/update-scorer.d.ts +51 -0
- package/dist/updater/update-scorer.d.ts.map +1 -0
- package/dist/updater/update-scorer.js +166 -0
- package/dist/updater/update-scorer.js.map +1 -0
- package/dist/utils/constraint-filter.d.ts +44 -0
- package/dist/utils/constraint-filter.d.ts.map +1 -0
- package/dist/utils/constraint-filter.js +69 -0
- package/dist/utils/constraint-filter.js.map +1 -0
- package/dist/utils/serializer.d.ts +17 -0
- package/dist/utils/serializer.d.ts.map +1 -0
- package/dist/utils/serializer.js +24 -0
- package/dist/utils/serializer.js.map +1 -0
- package/dist/utils/skill-parser.d.ts +29 -0
- package/dist/utils/skill-parser.d.ts.map +1 -0
- package/dist/utils/skill-parser.js +175 -0
- package/dist/utils/skill-parser.js.map +1 -0
- package/dist/verifier/context7.d.ts +48 -0
- package/dist/verifier/context7.d.ts.map +1 -0
- package/dist/verifier/context7.js +97 -0
- package/dist/verifier/context7.js.map +1 -0
- package/examples/backend-node/input.json +28 -0
- package/examples/backend-node/output.json +343 -0
- package/examples/frontend-nextjs/input.json +37 -0
- package/examples/frontend-nextjs/output.json +302 -0
- package/package.json +79 -0
- package/templates/deletion-checklist.md +42 -0
- package/templates/migration-plan.md +61 -0
- package/templates/prd-template.md +123 -0
- package/templates/summary-table.md +28 -0
- package/templates/update-plan.md +61 -0
- package/templates/vuln-report.md +50 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getPatternCatalog } from './pattern-catalog.js';
|
|
4
|
+
const MANIFEST_TYPES = [
|
|
5
|
+
{
|
|
6
|
+
file: 'package.json',
|
|
7
|
+
packageManager: 'npm',
|
|
8
|
+
language: 'typescript',
|
|
9
|
+
parseDeps: (content) => {
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(content);
|
|
12
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
file: 'pyproject.toml',
|
|
21
|
+
packageManager: 'pip',
|
|
22
|
+
language: 'python',
|
|
23
|
+
parseDeps: (content) => {
|
|
24
|
+
const deps = {};
|
|
25
|
+
// Simple regex to extract dependencies from pyproject.toml
|
|
26
|
+
const depSection = /\[(?:project\.)?dependencies\]\s*\n([\s\S]*?)(?:\n\[|$)/;
|
|
27
|
+
const match = depSection.exec(content);
|
|
28
|
+
if (match?.[1]) {
|
|
29
|
+
const lines = match[1].split('\n');
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const depMatch = /^\s*["']?([a-zA-Z0-9_-]+)["']?\s*(?:[><=!~]+\s*["']?([^"',\s]+))?/.exec(line.trim());
|
|
32
|
+
if (depMatch?.[1]) {
|
|
33
|
+
deps[depMatch[1]] = depMatch[2] ?? '*';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return deps;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
file: 'Cargo.toml',
|
|
42
|
+
packageManager: 'cargo',
|
|
43
|
+
language: 'rust',
|
|
44
|
+
parseDeps: (content) => {
|
|
45
|
+
const deps = {};
|
|
46
|
+
const depSection = /\[dependencies\]\s*\n([\s\S]*?)(?:\n\[|$)/;
|
|
47
|
+
const match = depSection.exec(content);
|
|
48
|
+
if (match?.[1]) {
|
|
49
|
+
const lines = match[1].split('\n');
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const depMatch = /^\s*([a-zA-Z0-9_-]+)\s*=\s*"([^"]+)"/.exec(line.trim());
|
|
52
|
+
if (depMatch?.[1] && depMatch[2]) {
|
|
53
|
+
deps[depMatch[1]] = depMatch[2];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return deps;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
file: 'go.mod',
|
|
62
|
+
packageManager: 'go',
|
|
63
|
+
language: 'go',
|
|
64
|
+
parseDeps: (content) => {
|
|
65
|
+
const deps = {};
|
|
66
|
+
const requireBlock = /require\s*\(([\s\S]*?)\)/;
|
|
67
|
+
const match = requireBlock.exec(content);
|
|
68
|
+
if (match?.[1]) {
|
|
69
|
+
const lines = match[1].split('\n');
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
const depMatch = /^\s*(\S+)\s+(\S+)/.exec(line.trim());
|
|
72
|
+
if (depMatch?.[1] && depMatch[2]) {
|
|
73
|
+
deps[depMatch[1]] = depMatch[2];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return deps;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// File-tree walking utilities
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
/** Directories to always skip when walking the file tree */
|
|
85
|
+
const SKIP_DIRS = new Set([
|
|
86
|
+
'node_modules',
|
|
87
|
+
'.git',
|
|
88
|
+
'dist',
|
|
89
|
+
'build',
|
|
90
|
+
'.next',
|
|
91
|
+
'__pycache__',
|
|
92
|
+
'.venv',
|
|
93
|
+
'venv',
|
|
94
|
+
'target',
|
|
95
|
+
'vendor',
|
|
96
|
+
'.turbo',
|
|
97
|
+
'coverage',
|
|
98
|
+
]);
|
|
99
|
+
/**
|
|
100
|
+
* Recursively walk a directory and yield file paths.
|
|
101
|
+
* Skips common non-source directories.
|
|
102
|
+
*/
|
|
103
|
+
function* walkDir(dir) {
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Unreadable directory — skip silently (Req 9.1 edge case)
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
const fullPath = path.join(dir, entry.name);
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
116
|
+
yield* walkDir(fullPath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (entry.isFile()) {
|
|
120
|
+
yield fullPath;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check whether a file path matches any of the glob-like patterns.
|
|
126
|
+
* Supports simple patterns: `**\/*.ext` and `*.ext`.
|
|
127
|
+
*/
|
|
128
|
+
function matchesFilePattern(filePath, patterns) {
|
|
129
|
+
const ext = path.extname(filePath);
|
|
130
|
+
const basename = path.basename(filePath);
|
|
131
|
+
for (const pattern of patterns) {
|
|
132
|
+
// Handle **/*.ext patterns
|
|
133
|
+
if (pattern.startsWith('**/')) {
|
|
134
|
+
const suffix = pattern.slice(3); // e.g. "*.ts"
|
|
135
|
+
if (suffix.startsWith('*.')) {
|
|
136
|
+
const requiredExt = suffix.slice(1); // e.g. ".ts"
|
|
137
|
+
if (ext === requiredExt)
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
else if (basename === suffix) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Handle *.ext patterns
|
|
145
|
+
else if (pattern.startsWith('*.')) {
|
|
146
|
+
const requiredExt = pattern.slice(1);
|
|
147
|
+
if (ext === requiredExt)
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
// Exact filename match
|
|
151
|
+
else if (basename === pattern) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Workspace detection
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Detect workspace roots by scanning for manifest files.
|
|
162
|
+
* For monorepos, each directory containing a manifest is a workspace root.
|
|
163
|
+
*/
|
|
164
|
+
function detectWorkspaces(repoPath) {
|
|
165
|
+
const workspaces = [];
|
|
166
|
+
const visited = new Set();
|
|
167
|
+
for (const filePath of walkDir(repoPath)) {
|
|
168
|
+
const dir = path.dirname(filePath);
|
|
169
|
+
const basename = path.basename(filePath);
|
|
170
|
+
for (const manifest of MANIFEST_TYPES) {
|
|
171
|
+
if (basename === manifest.file && !visited.has(`${dir}:${manifest.file}`)) {
|
|
172
|
+
visited.add(`${dir}:${manifest.file}`);
|
|
173
|
+
let content;
|
|
174
|
+
try {
|
|
175
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// Detect actual package manager from lockfiles
|
|
181
|
+
let packageManager = manifest.packageManager;
|
|
182
|
+
if (manifest.file === 'package.json') {
|
|
183
|
+
packageManager = detectNodePackageManager(dir);
|
|
184
|
+
}
|
|
185
|
+
workspaces.push({
|
|
186
|
+
root: path.relative(repoPath, dir) || '.',
|
|
187
|
+
packageManager,
|
|
188
|
+
language: manifest.language,
|
|
189
|
+
dependencies: manifest.parseDeps(content),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// If no workspaces found, create a root workspace from input metadata
|
|
195
|
+
if (workspaces.length === 0) {
|
|
196
|
+
workspaces.push({
|
|
197
|
+
root: '.',
|
|
198
|
+
packageManager: 'unknown',
|
|
199
|
+
language: 'unknown',
|
|
200
|
+
dependencies: {},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return workspaces;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Detect the Node.js package manager by checking for lockfiles.
|
|
207
|
+
*/
|
|
208
|
+
function detectNodePackageManager(dir) {
|
|
209
|
+
if (fileExists(path.join(dir, 'pnpm-lock.yaml')))
|
|
210
|
+
return 'pnpm';
|
|
211
|
+
if (fileExists(path.join(dir, 'yarn.lock')))
|
|
212
|
+
return 'yarn';
|
|
213
|
+
if (fileExists(path.join(dir, 'bun.lockb')))
|
|
214
|
+
return 'bun';
|
|
215
|
+
return 'npm';
|
|
216
|
+
}
|
|
217
|
+
function fileExists(filePath) {
|
|
218
|
+
try {
|
|
219
|
+
return fs.statSync(filePath).isFile();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Pattern matching
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
/**
|
|
229
|
+
* Scan a single file's content against the pattern catalog.
|
|
230
|
+
* Returns detections for each pattern match found.
|
|
231
|
+
*/
|
|
232
|
+
function scanFile(filePath, relativeFilePath, catalog) {
|
|
233
|
+
let content;
|
|
234
|
+
try {
|
|
235
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Unreadable file (binary, permissions) — skip
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
// Skip very large files (likely generated/minified)
|
|
242
|
+
if (content.length > 500_000)
|
|
243
|
+
return [];
|
|
244
|
+
const lines = content.split('\n');
|
|
245
|
+
const detections = [];
|
|
246
|
+
// Filter catalog to patterns whose filePatterns match this file
|
|
247
|
+
const applicablePatterns = catalog.filter((p) => matchesFilePattern(filePath, p.filePatterns));
|
|
248
|
+
for (const pattern of applicablePatterns) {
|
|
249
|
+
for (const regex of pattern.codePatterns) {
|
|
250
|
+
for (let i = 0; i < lines.length; i++) {
|
|
251
|
+
const line = lines[i];
|
|
252
|
+
if (line !== undefined && regex.test(line)) {
|
|
253
|
+
// Determine the line range — include surrounding context
|
|
254
|
+
const start = i + 1; // 1-indexed
|
|
255
|
+
const end = Math.min(i + 1, lines.length); // at least the matched line
|
|
256
|
+
detections.push({
|
|
257
|
+
filePath: relativeFilePath,
|
|
258
|
+
lineRange: { start, end },
|
|
259
|
+
patternCategory: pattern.id,
|
|
260
|
+
confidenceScore: pattern.confidenceBase,
|
|
261
|
+
suggestedLibrary: pattern.suggestedLibrary,
|
|
262
|
+
domain: pattern.domain,
|
|
263
|
+
});
|
|
264
|
+
// Only report the first match per pattern per file to avoid noise
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return detections;
|
|
271
|
+
}
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Main scanner entry point
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
/**
|
|
276
|
+
* Scan a codebase for hand-rolled implementations that could be replaced
|
|
277
|
+
* by third-party libraries.
|
|
278
|
+
*
|
|
279
|
+
* - Walks the file tree under `input.projectMetadata.repoPath`
|
|
280
|
+
* - Detects workspace roots for monorepos (package.json, pyproject.toml, etc.)
|
|
281
|
+
* - Matches source files against the pattern catalog
|
|
282
|
+
* - Returns structured `ScanResult` with detections and workspace info
|
|
283
|
+
*/
|
|
284
|
+
export async function scanCodebase(input) {
|
|
285
|
+
const repoPath = path.resolve(input.projectMetadata.repoPath);
|
|
286
|
+
// Verify the repo path exists
|
|
287
|
+
if (!fs.existsSync(repoPath)) {
|
|
288
|
+
return { detections: [], workspaces: [] };
|
|
289
|
+
}
|
|
290
|
+
const catalog = getPatternCatalog();
|
|
291
|
+
// Detect workspaces (monorepo support)
|
|
292
|
+
const workspaces = detectWorkspaces(repoPath);
|
|
293
|
+
// Walk the file tree and scan each source file
|
|
294
|
+
const detections = [];
|
|
295
|
+
for (const filePath of walkDir(repoPath)) {
|
|
296
|
+
const relativeFilePath = path.relative(repoPath, filePath);
|
|
297
|
+
const fileDetections = scanFile(filePath, relativeFilePath, catalog);
|
|
298
|
+
detections.push(...fileDetections);
|
|
299
|
+
}
|
|
300
|
+
return { detections, workspaces };
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/analyzer/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ScanResult } from '../analyzer/scanner.js';
|
|
2
|
+
import type { InputSchema } from '../schemas/input.schema.js';
|
|
3
|
+
export interface UxAuditResult {
|
|
4
|
+
items: UxAuditItem[];
|
|
5
|
+
}
|
|
6
|
+
export interface UxAuditItem {
|
|
7
|
+
category: UxCategory;
|
|
8
|
+
status: 'present' | 'partial' | 'missing';
|
|
9
|
+
filePaths: string[];
|
|
10
|
+
recommendedLibrary?: string;
|
|
11
|
+
rationale: string;
|
|
12
|
+
}
|
|
13
|
+
export type UxCategory = 'accessibility' | 'error-states' | 'empty-states' | 'loading-states' | 'form-validation' | 'performance-feel' | 'copy-consistency' | 'design-system-alignment';
|
|
14
|
+
/**
|
|
15
|
+
* Audit UX completeness across 8 categories by examining scanner detections
|
|
16
|
+
* and the project's current library dependencies.
|
|
17
|
+
*
|
|
18
|
+
* Always returns exactly 8 items — one for each UX category.
|
|
19
|
+
* Items with status "partial" or "missing" include a recommendedLibrary and rationale.
|
|
20
|
+
*
|
|
21
|
+
* @param scanResult - The result from the codebase scanner
|
|
22
|
+
* @param projectMetadata - Project metadata including current libraries
|
|
23
|
+
* @returns UxAuditResult with exactly 8 audit items
|
|
24
|
+
*/
|
|
25
|
+
export declare function auditUxCompleteness(scanResult: ScanResult, projectMetadata: InputSchema['projectMetadata']): UxAuditResult;
|
|
26
|
+
//# sourceMappingURL=ux-auditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ux-auditor.d.ts","sourceRoot":"","sources":["../../src/auditor/ux-auditor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAM9D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,UAAU,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,MAAM,UAAU,GAClB,eAAe,GACf,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,yBAAyB,CAAC;AAyS9B;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAAC,GAC9C,aAAa,CA0Cf"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
const ALL_UX_CATEGORIES = [
|
|
2
|
+
'accessibility',
|
|
3
|
+
'error-states',
|
|
4
|
+
'empty-states',
|
|
5
|
+
'loading-states',
|
|
6
|
+
'form-validation',
|
|
7
|
+
'performance-feel',
|
|
8
|
+
'copy-consistency',
|
|
9
|
+
'design-system-alignment',
|
|
10
|
+
];
|
|
11
|
+
const CATEGORY_CONFIGS = [
|
|
12
|
+
{
|
|
13
|
+
category: 'accessibility',
|
|
14
|
+
detectionPatternIds: [],
|
|
15
|
+
detectionDomains: ['ux-completeness'],
|
|
16
|
+
patternKeywords: [/a11y/i, /accessibility/i, /aria/i, /screen.?reader/i, /wcag/i],
|
|
17
|
+
libraryIndicators: [
|
|
18
|
+
'react-aria',
|
|
19
|
+
'@radix-ui/react-accessible-icon',
|
|
20
|
+
'axe-core',
|
|
21
|
+
'@axe-core/react',
|
|
22
|
+
'eslint-plugin-jsx-a11y',
|
|
23
|
+
'react-focus-lock',
|
|
24
|
+
'@reach/visually-hidden',
|
|
25
|
+
],
|
|
26
|
+
recommendedLibrary: 'react-aria',
|
|
27
|
+
missingRationale: 'No accessibility patterns detected. Add react-aria for accessible primitives with ARIA attributes, focus management, and keyboard navigation built-in.',
|
|
28
|
+
partialRationale: 'Some accessibility patterns found but coverage is incomplete. react-aria provides comprehensive accessible component primitives.',
|
|
29
|
+
presentRationale: 'Accessibility patterns detected across frontend files with proper ARIA attributes and focus management.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
category: 'error-states',
|
|
33
|
+
detectionPatternIds: ['observability-manual-error-tracking'],
|
|
34
|
+
detectionDomains: ['observability'],
|
|
35
|
+
patternKeywords: [/error.?boundar/i, /error.?state/i, /error.?handling/i, /catch/i, /fallback/i],
|
|
36
|
+
libraryIndicators: [
|
|
37
|
+
'react-error-boundary',
|
|
38
|
+
'@sentry/react',
|
|
39
|
+
'sentry',
|
|
40
|
+
'react-query',
|
|
41
|
+
'@tanstack/react-query',
|
|
42
|
+
],
|
|
43
|
+
recommendedLibrary: 'react-error-boundary',
|
|
44
|
+
missingRationale: 'No error boundary or error state patterns detected. react-error-boundary provides declarative error boundaries with fallback UI, retry, and reset capabilities.',
|
|
45
|
+
partialRationale: 'Some error handling found but missing structured error boundaries. react-error-boundary adds declarative fallback UI and recovery patterns.',
|
|
46
|
+
presentRationale: 'Error state handling detected with error boundaries and fallback UI patterns.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
category: 'empty-states',
|
|
50
|
+
detectionPatternIds: [],
|
|
51
|
+
detectionDomains: ['ux-completeness'],
|
|
52
|
+
patternKeywords: [/empty.?state/i, /no.?data/i, /no.?results/i, /placeholder/i, /zero.?state/i],
|
|
53
|
+
libraryIndicators: ['react-empty-state', '@illustrations/undraw'],
|
|
54
|
+
recommendedLibrary: 'react-empty-state',
|
|
55
|
+
missingRationale: 'No empty state patterns detected. Add dedicated empty state components with illustrations and call-to-action buttons for better user guidance.',
|
|
56
|
+
partialRationale: 'Some empty state handling found but not consistently applied. Consider a dedicated empty state component library for consistent UX.',
|
|
57
|
+
presentRationale: 'Empty state patterns detected with appropriate placeholder content and user guidance.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
category: 'loading-states',
|
|
61
|
+
detectionPatternIds: ['ux-manual-loading-states'],
|
|
62
|
+
detectionDomains: ['ux-completeness'],
|
|
63
|
+
patternKeywords: [/loading/i, /spinner/i, /skeleton/i, /suspense/i, /pending/i],
|
|
64
|
+
libraryIndicators: [
|
|
65
|
+
'react-loading-skeleton',
|
|
66
|
+
'react-spinners',
|
|
67
|
+
'react-content-loader',
|
|
68
|
+
'@tanstack/react-query',
|
|
69
|
+
'swr',
|
|
70
|
+
],
|
|
71
|
+
recommendedLibrary: 'react-loading-skeleton',
|
|
72
|
+
missingRationale: 'No loading state patterns detected. react-loading-skeleton provides animated placeholder UI that reduces perceived load time and prevents layout shift.',
|
|
73
|
+
partialRationale: 'Hand-rolled loading states found. react-loading-skeleton provides consistent, animated skeleton screens with automatic sizing.',
|
|
74
|
+
presentRationale: 'Loading state patterns detected with skeleton screens or spinner components.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
category: 'form-validation',
|
|
78
|
+
detectionPatternIds: ['ux-manual-form-validation'],
|
|
79
|
+
detectionDomains: ['ux-completeness'],
|
|
80
|
+
patternKeywords: [/form.?valid/i, /validation/i, /form.?error/i, /setError/i, /useForm/i],
|
|
81
|
+
libraryIndicators: [
|
|
82
|
+
'react-hook-form',
|
|
83
|
+
'formik',
|
|
84
|
+
'yup',
|
|
85
|
+
'zod',
|
|
86
|
+
'@hookform/resolvers',
|
|
87
|
+
'vest',
|
|
88
|
+
],
|
|
89
|
+
recommendedLibrary: 'react-hook-form',
|
|
90
|
+
missingRationale: 'No form validation patterns detected. react-hook-form provides performant form validation with minimal re-renders, schema integration, and accessible error messages.',
|
|
91
|
+
partialRationale: 'Hand-rolled form validation found. react-hook-form reduces boilerplate and provides consistent validation UX with schema-based validation support.',
|
|
92
|
+
presentRationale: 'Form validation patterns detected with structured validation library integration.',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
category: 'performance-feel',
|
|
96
|
+
detectionPatternIds: [],
|
|
97
|
+
detectionDomains: ['ux-completeness'],
|
|
98
|
+
patternKeywords: [
|
|
99
|
+
/virtuali[sz]/i,
|
|
100
|
+
/lazy/i,
|
|
101
|
+
/code.?split/i,
|
|
102
|
+
/optimistic/i,
|
|
103
|
+
/debounce/i,
|
|
104
|
+
/throttle/i,
|
|
105
|
+
/intersection.?observer/i,
|
|
106
|
+
/prefetch/i,
|
|
107
|
+
],
|
|
108
|
+
libraryIndicators: [
|
|
109
|
+
'react-virtual',
|
|
110
|
+
'@tanstack/react-virtual',
|
|
111
|
+
'react-window',
|
|
112
|
+
'react-virtualized',
|
|
113
|
+
'react-intersection-observer',
|
|
114
|
+
'framer-motion',
|
|
115
|
+
],
|
|
116
|
+
recommendedLibrary: '@tanstack/react-virtual',
|
|
117
|
+
missingRationale: 'No performance optimization patterns detected. @tanstack/react-virtual provides efficient list virtualization, reducing DOM nodes and improving scroll performance for large datasets.',
|
|
118
|
+
partialRationale: 'Some performance patterns found but missing virtualization or optimistic updates. @tanstack/react-virtual improves rendering performance for large lists.',
|
|
119
|
+
presentRationale: 'Performance optimization patterns detected including virtualization and lazy loading.',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
category: 'copy-consistency',
|
|
123
|
+
detectionPatternIds: ['i18n-manual-pluralization', 'i18n-manual-locale-detection'],
|
|
124
|
+
detectionDomains: ['i18n'],
|
|
125
|
+
patternKeywords: [/i18n/i, /l10n/i, /locale/i, /translat/i, /intl/i, /plurali[sz]/i],
|
|
126
|
+
libraryIndicators: [
|
|
127
|
+
'i18next',
|
|
128
|
+
'react-i18next',
|
|
129
|
+
'react-intl',
|
|
130
|
+
'next-intl',
|
|
131
|
+
'formatjs',
|
|
132
|
+
'@formatjs/intl',
|
|
133
|
+
],
|
|
134
|
+
recommendedLibrary: 'react-i18next',
|
|
135
|
+
missingRationale: 'No internationalization or copy management patterns detected. react-i18next provides structured copy management with pluralization, interpolation, and locale-aware formatting for consistent UI text.',
|
|
136
|
+
partialRationale: 'Hand-rolled i18n patterns found. react-i18next provides centralized copy management ensuring consistency across the application.',
|
|
137
|
+
presentRationale: 'Copy consistency patterns detected with internationalization library integration.',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
category: 'design-system-alignment',
|
|
141
|
+
detectionPatternIds: [],
|
|
142
|
+
detectionDomains: ['ux-completeness'],
|
|
143
|
+
patternKeywords: [
|
|
144
|
+
/design.?system/i,
|
|
145
|
+
/component.?library/i,
|
|
146
|
+
/theme/i,
|
|
147
|
+
/styled/i,
|
|
148
|
+
/tailwind/i,
|
|
149
|
+
/chakra/i,
|
|
150
|
+
/radix/i,
|
|
151
|
+
/shadcn/i,
|
|
152
|
+
],
|
|
153
|
+
libraryIndicators: [
|
|
154
|
+
'@radix-ui/react-dialog',
|
|
155
|
+
'@radix-ui/react-dropdown-menu',
|
|
156
|
+
'@radix-ui/react-popover',
|
|
157
|
+
'@radix-ui/react-select',
|
|
158
|
+
'@radix-ui/react-tooltip',
|
|
159
|
+
'@chakra-ui/react',
|
|
160
|
+
'@mui/material',
|
|
161
|
+
'antd',
|
|
162
|
+
'tailwindcss',
|
|
163
|
+
'styled-components',
|
|
164
|
+
'@emotion/react',
|
|
165
|
+
'class-variance-authority',
|
|
166
|
+
],
|
|
167
|
+
recommendedLibrary: '@radix-ui/themes',
|
|
168
|
+
missingRationale: 'No design system or component library patterns detected. @radix-ui/themes provides accessible, composable UI primitives that enforce design consistency across the application.',
|
|
169
|
+
partialRationale: 'Some design system patterns found but coverage is incomplete. @radix-ui/themes provides a comprehensive set of accessible, themed components.',
|
|
170
|
+
presentRationale: 'Design system alignment detected with component library and theming patterns.',
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Core audit logic
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
/**
|
|
177
|
+
* Determine the status of a UX category based on scanner detections and
|
|
178
|
+
* the project's current libraries.
|
|
179
|
+
*/
|
|
180
|
+
function evaluateCategory(config, detections, currentLibraries) {
|
|
181
|
+
const filePaths = [];
|
|
182
|
+
let hasDetections = false;
|
|
183
|
+
let hasLibrary = false;
|
|
184
|
+
// Check if any scanner detections match this category
|
|
185
|
+
for (const detection of detections) {
|
|
186
|
+
const matchesPatternId = config.detectionPatternIds.includes(detection.patternCategory);
|
|
187
|
+
const matchesDomain = config.detectionDomains.includes(detection.domain);
|
|
188
|
+
const matchesKeyword = config.patternKeywords.some((kw) => kw.test(detection.patternCategory) || kw.test(detection.domain));
|
|
189
|
+
if (matchesPatternId || matchesDomain || matchesKeyword) {
|
|
190
|
+
hasDetections = true;
|
|
191
|
+
if (!filePaths.includes(detection.filePath)) {
|
|
192
|
+
filePaths.push(detection.filePath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Check if any current libraries indicate coverage for this category
|
|
197
|
+
const libraryNames = Object.keys(currentLibraries);
|
|
198
|
+
for (const indicator of config.libraryIndicators) {
|
|
199
|
+
if (libraryNames.some((lib) => lib === indicator || lib.includes(indicator))) {
|
|
200
|
+
hasLibrary = true;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Determine status:
|
|
205
|
+
// - present: library installed AND no hand-rolled detections, OR library installed
|
|
206
|
+
// - partial: hand-rolled detections found (library may or may not be installed)
|
|
207
|
+
// - missing: no library and no detections
|
|
208
|
+
if (hasLibrary && !hasDetections) {
|
|
209
|
+
return { status: 'present', filePaths };
|
|
210
|
+
}
|
|
211
|
+
else if (hasDetections) {
|
|
212
|
+
// Hand-rolled patterns detected — partial coverage (library may help replace them)
|
|
213
|
+
return { status: 'partial', filePaths };
|
|
214
|
+
}
|
|
215
|
+
else if (hasLibrary) {
|
|
216
|
+
return { status: 'present', filePaths };
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
return { status: 'missing', filePaths };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Public API
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
/**
|
|
226
|
+
* Audit UX completeness across 8 categories by examining scanner detections
|
|
227
|
+
* and the project's current library dependencies.
|
|
228
|
+
*
|
|
229
|
+
* Always returns exactly 8 items — one for each UX category.
|
|
230
|
+
* Items with status "partial" or "missing" include a recommendedLibrary and rationale.
|
|
231
|
+
*
|
|
232
|
+
* @param scanResult - The result from the codebase scanner
|
|
233
|
+
* @param projectMetadata - Project metadata including current libraries
|
|
234
|
+
* @returns UxAuditResult with exactly 8 audit items
|
|
235
|
+
*/
|
|
236
|
+
export function auditUxCompleteness(scanResult, projectMetadata) {
|
|
237
|
+
const currentLibraries = projectMetadata.currentLibraries;
|
|
238
|
+
const items = [];
|
|
239
|
+
for (const config of CATEGORY_CONFIGS) {
|
|
240
|
+
const { status, filePaths } = evaluateCategory(config, scanResult.detections, currentLibraries);
|
|
241
|
+
const item = {
|
|
242
|
+
category: config.category,
|
|
243
|
+
status,
|
|
244
|
+
filePaths,
|
|
245
|
+
rationale: status === 'missing'
|
|
246
|
+
? config.missingRationale
|
|
247
|
+
: status === 'partial'
|
|
248
|
+
? config.partialRationale
|
|
249
|
+
: config.presentRationale,
|
|
250
|
+
};
|
|
251
|
+
// Add recommendedLibrary for partial or missing statuses (required by spec)
|
|
252
|
+
if (status === 'partial' || status === 'missing') {
|
|
253
|
+
item.recommendedLibrary = config.recommendedLibrary;
|
|
254
|
+
}
|
|
255
|
+
items.push(item);
|
|
256
|
+
}
|
|
257
|
+
// Ensure all 8 categories are present (defensive — should always be true)
|
|
258
|
+
const coveredCategories = new Set(items.map((i) => i.category));
|
|
259
|
+
for (const category of ALL_UX_CATEGORIES) {
|
|
260
|
+
if (!coveredCategories.has(category)) {
|
|
261
|
+
items.push({
|
|
262
|
+
category,
|
|
263
|
+
status: 'missing',
|
|
264
|
+
filePaths: [],
|
|
265
|
+
recommendedLibrary: 'unknown',
|
|
266
|
+
rationale: `No patterns detected for ${category}. Manual review recommended.`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return { items };
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=ux-auditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ux-auditor.js","sourceRoot":"","sources":["../../src/auditor/ux-auditor.ts"],"names":[],"mappings":"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"}
|