@ryuenn3123/agentic-senior-core 3.0.50 → 4.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.
Files changed (83) hide show
  1. package/.agent-context/review-checklists/pr-checklist.md +1 -0
  2. package/.agent-context/rules/api-docs.md +63 -47
  3. package/.agent-context/rules/architecture.md +133 -120
  4. package/.agent-context/rules/database-design.md +36 -18
  5. package/.agent-context/rules/docker-runtime.md +66 -43
  6. package/.agent-context/rules/efficiency-vs-hype.md +38 -17
  7. package/.agent-context/rules/error-handling.md +35 -16
  8. package/.agent-context/rules/event-driven.md +35 -18
  9. package/.agent-context/rules/frontend-architecture.md +103 -76
  10. package/.agent-context/rules/git-workflow.md +81 -197
  11. package/.agent-context/rules/microservices.md +42 -41
  12. package/.agent-context/rules/naming-conv.md +27 -8
  13. package/.agent-context/rules/performance.md +32 -12
  14. package/.agent-context/rules/realtime.md +26 -9
  15. package/.agent-context/rules/security.md +39 -20
  16. package/.agent-context/rules/testing.md +36 -16
  17. package/AGENTS.md +9 -9
  18. package/README.md +10 -1
  19. package/lib/cli/commands/init.mjs +1 -0
  20. package/lib/cli/compiler.mjs +1 -0
  21. package/lib/cli/detector/constants.mjs +135 -0
  22. package/lib/cli/detector/design-evidence/collector.mjs +256 -0
  23. package/lib/cli/detector/design-evidence/constants.mjs +39 -0
  24. package/lib/cli/detector/design-evidence/file-traversal.mjs +83 -0
  25. package/lib/cli/detector/design-evidence/structured-attribute-evidence.mjs +117 -0
  26. package/lib/cli/detector/design-evidence/summary.mjs +109 -0
  27. package/lib/cli/detector/design-evidence/utility-helpers.mjs +122 -0
  28. package/lib/cli/detector/design-evidence.mjs +25 -610
  29. package/lib/cli/detector/stack-detection.mjs +243 -0
  30. package/lib/cli/detector/ui-signals.mjs +150 -0
  31. package/lib/cli/detector/workspace-scan.mjs +177 -0
  32. package/lib/cli/detector.mjs +20 -688
  33. package/lib/cli/memory-continuity.mjs +1 -0
  34. package/lib/cli/project-scaffolder/design-contract/sections/audits.mjs +96 -0
  35. package/lib/cli/project-scaffolder/design-contract/sections/conceptual-anchor.mjs +116 -0
  36. package/lib/cli/project-scaffolder/design-contract/sections/execution-handoff.mjs +211 -0
  37. package/lib/cli/project-scaffolder/design-contract/seed-signals.mjs +79 -0
  38. package/lib/cli/project-scaffolder/design-contract/signal-vocab.mjs +64 -0
  39. package/lib/cli/project-scaffolder/design-contract/validation/anchor-validators.mjs +222 -0
  40. package/lib/cli/project-scaffolder/design-contract/validation/audit-validators.mjs +117 -0
  41. package/lib/cli/project-scaffolder/design-contract/validation/completeness.mjs +83 -0
  42. package/lib/cli/project-scaffolder/design-contract/validation/execution-validators.mjs +328 -0
  43. package/lib/cli/project-scaffolder/design-contract/validation/helpers.mjs +8 -0
  44. package/lib/cli/project-scaffolder/design-contract/validation/structural-validators.mjs +79 -0
  45. package/lib/cli/project-scaffolder/design-contract/validation/system-validators.mjs +256 -0
  46. package/lib/cli/project-scaffolder/design-contract/validation.mjs +59 -896
  47. package/lib/cli/project-scaffolder/design-contract.mjs +147 -557
  48. package/mcp.json +30 -9
  49. package/package.json +17 -2
  50. package/scripts/audit-cache-layer-contract.mjs +258 -0
  51. package/scripts/audit-caching-scope-hygiene.mjs +263 -0
  52. package/scripts/audit-file-size.mjs +219 -0
  53. package/scripts/audit-reflection-citations.mjs +163 -0
  54. package/scripts/audit-release-bundle.mjs +170 -0
  55. package/scripts/audit-rule-id-uniqueness.mjs +313 -0
  56. package/scripts/benchmark-evidence-bundle.mjs +1 -0
  57. package/scripts/build-release-benchmark-bundle.mjs +204 -0
  58. package/scripts/context-triggered-audit.mjs +1 -0
  59. package/scripts/documentation-boundary-audit.mjs +1 -0
  60. package/scripts/explain-on-demand-audit.mjs +2 -1
  61. package/scripts/frontend-usability-audit.mjs +10 -10
  62. package/scripts/llm-judge/checklist-loader.mjs +45 -0
  63. package/scripts/llm-judge/constants.mjs +66 -0
  64. package/scripts/llm-judge/diff-collection.mjs +74 -0
  65. package/scripts/llm-judge/prompting.mjs +78 -0
  66. package/scripts/llm-judge/providers.mjs +111 -0
  67. package/scripts/llm-judge/verdict.mjs +134 -0
  68. package/scripts/llm-judge.mjs +21 -482
  69. package/scripts/mcp-server/tool-registry.mjs +55 -0
  70. package/scripts/mcp-server/tools.mjs +137 -1
  71. package/scripts/migrate-rule-format/id-prefix-table.mjs +37 -0
  72. package/scripts/migrate-rule-format/parse-legacy.mjs +180 -0
  73. package/scripts/migrate-rule-format/render-new.mjs +169 -0
  74. package/scripts/migrate-rule-format/roundtrip-validate.mjs +89 -0
  75. package/scripts/migrate-rule-format.mjs +192 -0
  76. package/scripts/release-gate/constants.mjs +1 -1
  77. package/scripts/release-gate/static-checks.mjs +1 -1
  78. package/scripts/rules-guardian-audit.mjs +5 -2
  79. package/scripts/single-source-lazy-loading-audit.mjs +2 -1
  80. package/scripts/ui-design-judge/git-input.mjs +3 -0
  81. package/scripts/validate/config.mjs +3 -2
  82. package/scripts/validate/coverage-checks.mjs +1 -1
  83. package/scripts/validate.mjs +93 -1
@@ -1,610 +1,25 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
-
4
- const FRONTEND_SCAN_DIRECTORY_NAMES = ['src', 'app', 'pages', 'components', 'styles'];
5
- const FRONTEND_SCAN_FILE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.vue', '.css', '.scss', '.sass']);
6
- export const FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES = new Set(['.git', 'node_modules', '.next', 'dist', 'build', 'coverage']);
7
- const FRONTEND_FILE_SCAN_LIMIT = 200;
8
- const FRONTEND_FILE_SIZE_LIMIT_BYTES = 200_000;
9
- const DESIGN_EVIDENCE_SAMPLE_LIMIT = 12;
10
- const COLOR_PATTERN = /#[0-9a-fA-F]{3,8}\b|rgba?\([^)]+\)|hsla?\([^)]+\)|oklch\([^)]+\)/g;
11
- const PROP_DRILLING_PATTERN = /<[A-Z][A-Za-z0-9_.:-]*(?:\s+[A-Za-z0-9_:-]+=\{[^}]+\}){5,}/g;
12
- const MEDIA_QUERY_PATTERN = /@media\b/g;
13
- const TAILWIND_BREAKPOINT_PATTERN = /\b(?:sm|md|lg|xl|2xl):/g;
14
- const ARBITRARY_BREAKPOINT_PATTERN = /\b(?:min|max)-\[[^\]]+\]:/g;
15
- const CSS_VARIABLE_DEFINITION_PATTERN = /--([a-zA-Z0-9-_]+)\s*:/g;
16
- const CSS_VARIABLE_REFERENCE_PATTERN = /var\(--([a-zA-Z0-9-_]+)\)/g;
17
- const RAW_SPACING_PATTERN = /\b(?:margin|padding|gap|column-gap|row-gap|min-width|max-width|min-height|max-height|width|height|top|right|bottom|left|inset|spaceBetween|paddingInline|paddingBlock|marginInline|marginBlock|gapX|gapY|spaceX|spaceY)\b[^;\n:]*[:=]\s*['"`{(]*(-?[0-9.]+(?:px|rem|em|vh|vw))/gi;
18
- const RAW_RADIUS_PATTERN = /\b(?:border-radius|borderRadius)\b[^;\n:]*[:=]\s*['"`{(]*([0-9.]+(?:px|rem|em)|9999px)/gi;
19
- const RAW_SHADOW_PATTERN = /\b(?:box-shadow|boxShadow)\b[^;\n:]*[:=]\s*['"`{(]*([^;\n}]+)/gi;
20
- const FONT_FAMILY_PATTERN = /\b(?:font-family|fontFamily)\b[^;\n:]*[:=]\s*['"`{(]*([^;\n}]+)/gi;
21
- const FONT_SIZE_PATTERN = /\b(?:font-size|fontSize)\b[^;\n:]*[:=]\s*['"`{(]*([0-9.]+(?:px|rem|em))/gi;
22
- const LINE_HEIGHT_PATTERN = /\b(?:line-height|lineHeight)\b[^;\n:]*[:=]\s*['"`{(]*([0-9.]+(?:px|rem|em)?)/gi;
23
- const LETTER_SPACING_PATTERN = /\b(?:letter-spacing|letterSpacing)\b[^;\n:]*[:=]\s*['"`{(]*([0-9.-]+(?:px|rem|em))/gi;
24
- const TRANSITION_PATTERN = /\btransition(?:-[a-z]+)?\b/g;
25
- const ANIMATION_PATTERN = /\banimation(?:-[a-z]+)?\b/g;
26
- const DURATION_PATTERN = /\b\d+(?:\.\d+)?m?s\b/g;
27
- const MEDIA_WIDTH_PATTERN = /\((?:min|max)-width:\s*([0-9.]+(?:px|rem|em))\)/g;
28
- const STRING_CLASS_ATTRIBUTE_PATTERN = /\b(?:className|class)\s*=\s*(?:"([^"]*)"|'([^']*)'|\{`([^`]*)`\}|\{"([^"]*)"\}|\{'([^']*)'\})/g;
29
- const EXPRESSION_CLASS_ATTRIBUTE_PATTERN = /\b(?:className|class|:class)\s*=\s*\{([^}\n]+)\}/g;
30
- const JSX_INLINE_STYLE_PATTERN = /\bstyle\s*=\s*\{\{([\s\S]*?)\}\}/g;
31
- const VUE_INLINE_STYLE_PATTERN = /\b:style\s*=\s*["']\{([^"']*)\}["']/g;
32
-
33
- async function collectFrontendSourceFilePaths(directoryPath, collectedFilePaths = []) {
34
- if (collectedFilePaths.length >= FRONTEND_FILE_SCAN_LIMIT) {
35
- return collectedFilePaths;
36
- }
37
-
38
- let directoryEntries;
39
- try {
40
- directoryEntries = await fs.readdir(directoryPath, { withFileTypes: true });
41
- } catch {
42
- return collectedFilePaths;
43
- }
44
-
45
- for (const directoryEntry of directoryEntries) {
46
- if (collectedFilePaths.length >= FRONTEND_FILE_SCAN_LIMIT) {
47
- break;
48
- }
49
-
50
- if (directoryEntry.isDirectory()) {
51
- if (FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES.has(directoryEntry.name)) {
52
- continue;
53
- }
54
-
55
- await collectFrontendSourceFilePaths(path.join(directoryPath, directoryEntry.name), collectedFilePaths);
56
- continue;
57
- }
58
-
59
- const fileExtension = path.extname(directoryEntry.name).toLowerCase();
60
- if (FRONTEND_SCAN_FILE_EXTENSIONS.has(fileExtension)) {
61
- collectedFilePaths.push(path.join(directoryPath, directoryEntry.name));
62
- }
63
- }
64
-
65
- return collectedFilePaths;
66
- }
67
-
68
- function countPatternMatches(sourceText, pattern) {
69
- return Array.from(sourceText.matchAll(pattern)).length;
70
- }
71
-
72
- function pushSampleValue(targetSamples, value, targetSet) {
73
- const normalizedValue = String(value || '').trim();
74
- if (!normalizedValue || targetSet.has(normalizedValue)) {
75
- return;
76
- }
77
-
78
- targetSet.add(normalizedValue);
79
- if (targetSamples.length < DESIGN_EVIDENCE_SAMPLE_LIMIT) {
80
- targetSamples.push(normalizedValue);
81
- }
82
- }
83
-
84
- function categorizeCssVariable(variableName) {
85
- const normalizedVariableName = String(variableName || '').trim().toLowerCase();
86
-
87
- if (/(color|surface|accent|bg|text|border|fill|stroke|ink|tone)/.test(normalizedVariableName)) {
88
- return 'color';
89
- }
90
-
91
- if (/(space|gap|padding|margin|size|width|height|inset)/.test(normalizedVariableName)) {
92
- return 'spacing';
93
- }
94
-
95
- if (/(radius|rounded|corner)/.test(normalizedVariableName)) {
96
- return 'radius';
97
- }
98
-
99
- if (/(shadow|elevation)/.test(normalizedVariableName)) {
100
- return 'shadow';
101
- }
102
-
103
- if (/(font|type|line|letter|tracking|leading)/.test(normalizedVariableName)) {
104
- return 'typography';
105
- }
106
-
107
- if (/(motion|duration|easing|ease|animation|transition)/.test(normalizedVariableName)) {
108
- return 'motion';
109
- }
110
-
111
- return 'other';
112
- }
113
-
114
- function inferColorKind(colorValue) {
115
- if (/^#/i.test(colorValue)) {
116
- return 'hex';
117
- }
118
-
119
- if (/^rgba?\(/i.test(colorValue)) {
120
- return 'rgb';
121
- }
122
-
123
- if (/^hsla?\(/i.test(colorValue)) {
124
- return 'hsl';
125
- }
126
-
127
- if (/^oklch\(/i.test(colorValue)) {
128
- return 'oklch';
129
- }
130
-
131
- return 'other';
132
- }
133
-
134
- function incrementCountMap(countMap, key) {
135
- countMap[key] = (countMap[key] || 0) + 1;
136
- }
137
-
138
- function getFirstDefinedCapture(matchGroups) {
139
- for (const capturedValue of matchGroups) {
140
- if (typeof capturedValue === 'string' && capturedValue.trim().length > 0) {
141
- return capturedValue;
142
- }
143
- }
144
-
145
- return '';
146
- }
147
-
148
- function normalizeEvidenceSample(rawValue) {
149
- return String(rawValue || '').replace(/\s+/g, ' ').trim();
150
- }
151
-
152
- function inferUtilityFamily(rawUtilityToken) {
153
- const normalizedUtilityToken = String(rawUtilityToken || '')
154
- .trim()
155
- .split(':')
156
- .pop()
157
- ?.replace(/^!/, '')
158
- ?.replace(/\/.+$/, '')
159
- ?.trim();
160
-
161
- if (!normalizedUtilityToken) {
162
- return '';
163
- }
164
-
165
- if (/^grid(?:-|$)/.test(normalizedUtilityToken)) {
166
- return 'grid';
167
- }
168
-
169
- if (/^flex(?:-|$)/.test(normalizedUtilityToken)) {
170
- return 'flex';
171
- }
172
-
173
- if (/^(?:block|inline|hidden|contents)$/.test(normalizedUtilityToken)) {
174
- return normalizedUtilityToken;
175
- }
176
-
177
- if (/^(?:min|max)-\[/.test(normalizedUtilityToken)) {
178
- return 'arbitrary-breakpoint';
179
- }
180
-
181
- return normalizedUtilityToken.split('-')[0];
182
- }
183
-
184
- function collectTailwindUtilityFamilies(classValue, utilityFamilyCounts, utilityFamilySamples, utilityFamilySampleSet) {
185
- for (const utilityToken of String(classValue || '').split(/\s+/g)) {
186
- const utilityFamily = inferUtilityFamily(utilityToken);
187
- if (!utilityFamily) {
188
- continue;
189
- }
190
-
191
- incrementCountMap(utilityFamilyCounts, utilityFamily);
192
- pushSampleValue(utilityFamilySamples, utilityFamily, utilityFamilySampleSet);
193
- }
194
- }
195
-
196
- function applyInlineStyleInspection(styleSourceText, summary, styleSampleSet) {
197
- const normalizedStyleSource = normalizeEvidenceSample(styleSourceText);
198
- pushSampleValue(summary.structuredInspection.inlineStyleSamples, normalizedStyleSource, styleSampleSet);
199
-
200
- const hardcodedColorCount = countPatternMatches(styleSourceText, COLOR_PATTERN);
201
- const rawSpacingCount = countPatternMatches(styleSourceText, RAW_SPACING_PATTERN);
202
- const rawRadiusCount = countPatternMatches(styleSourceText, RAW_RADIUS_PATTERN);
203
- const rawShadowCount = countPatternMatches(styleSourceText, RAW_SHADOW_PATTERN);
204
- const cssVariableReferenceCount = countPatternMatches(styleSourceText, CSS_VARIABLE_REFERENCE_PATTERN);
205
-
206
- summary.structuredInspection.inlineTokenBypassSignals.hardcodedColorCount += hardcodedColorCount;
207
- summary.structuredInspection.inlineTokenBypassSignals.rawSpacingCount += rawSpacingCount;
208
- summary.structuredInspection.inlineTokenBypassSignals.rawRadiusCount += rawRadiusCount;
209
- summary.structuredInspection.inlineTokenBypassSignals.rawShadowCount += rawShadowCount;
210
- summary.structuredInspection.inlineTokenBypassSignals.cssVariableReferenceCount += cssVariableReferenceCount;
211
- }
212
-
213
- function collectStructuredAttributeEvidence(sourceText, summary, classSampleSet, styleSampleSet, utilityFamilySampleSet) {
214
- for (const classAttributeMatch of sourceText.matchAll(STRING_CLASS_ATTRIBUTE_PATTERN)) {
215
- const classValue = getFirstDefinedCapture(classAttributeMatch.slice(1));
216
- if (!classValue) {
217
- continue;
218
- }
219
-
220
- summary.structuredInspection.classAttributeCount += 1;
221
- pushSampleValue(
222
- summary.structuredInspection.classAttributeSamples,
223
- normalizeEvidenceSample(classValue),
224
- classSampleSet
225
- );
226
- collectTailwindUtilityFamilies(
227
- classValue,
228
- summary.structuredInspection.utilityFamilyCounts,
229
- summary.structuredInspection.utilityFamilySamples,
230
- utilityFamilySampleSet
231
- );
232
- }
233
-
234
- for (const expressionClassAttributeMatch of sourceText.matchAll(EXPRESSION_CLASS_ATTRIBUTE_PATTERN)) {
235
- const expressionValue = getFirstDefinedCapture(expressionClassAttributeMatch.slice(1));
236
- if (!expressionValue) {
237
- continue;
238
- }
239
-
240
- summary.structuredInspection.boundClassExpressionCount += 1;
241
- pushSampleValue(
242
- summary.structuredInspection.classAttributeSamples,
243
- normalizeEvidenceSample(expressionValue),
244
- classSampleSet
245
- );
246
- }
247
-
248
- for (const inlineStyleMatch of sourceText.matchAll(JSX_INLINE_STYLE_PATTERN)) {
249
- const inlineStyleSource = getFirstDefinedCapture(inlineStyleMatch.slice(1));
250
- if (!inlineStyleSource) {
251
- continue;
252
- }
253
-
254
- summary.structuredInspection.inlineStyleObjectCount += 1;
255
- applyInlineStyleInspection(inlineStyleSource, summary, styleSampleSet);
256
- }
257
-
258
- for (const vueInlineStyleMatch of sourceText.matchAll(VUE_INLINE_STYLE_PATTERN)) {
259
- const inlineStyleSource = getFirstDefinedCapture(vueInlineStyleMatch.slice(1));
260
- if (!inlineStyleSource) {
261
- continue;
262
- }
263
-
264
- summary.structuredInspection.inlineStyleBindingCount += 1;
265
- applyInlineStyleInspection(inlineStyleSource, summary, styleSampleSet);
266
- }
267
- }
268
-
269
- function createDesignEvidenceSummary(scanRootRelativePaths) {
270
- return {
271
- summaryVersion: 'v1',
272
- source: 'lightweight-static-scan',
273
- scanRootRelativePaths,
274
- scannedFileCount: 0,
275
- cssVariables: {
276
- definitionCount: 0,
277
- referenceCount: 0,
278
- sampleNames: [],
279
- categoryCounts: {
280
- color: 0,
281
- spacing: 0,
282
- radius: 0,
283
- shadow: 0,
284
- typography: 0,
285
- motion: 0,
286
- other: 0,
287
- },
288
- },
289
- colors: {
290
- hardcodedCount: 0,
291
- kindCounts: {
292
- hex: 0,
293
- rgb: 0,
294
- hsl: 0,
295
- oklch: 0,
296
- other: 0,
297
- },
298
- sampleValues: [],
299
- },
300
- spacing: {
301
- rawValueCount: 0,
302
- sampleValues: [],
303
- },
304
- radius: {
305
- rawValueCount: 0,
306
- sampleValues: [],
307
- },
308
- shadow: {
309
- rawValueCount: 0,
310
- sampleValues: [],
311
- },
312
- typography: {
313
- fontFamilyCount: 0,
314
- fontSizeCount: 0,
315
- lineHeightCount: 0,
316
- letterSpacingCount: 0,
317
- fontFamilySamples: [],
318
- fontSizeSamples: [],
319
- lineHeightSamples: [],
320
- letterSpacingSamples: [],
321
- },
322
- motion: {
323
- transitionCount: 0,
324
- animationCount: 0,
325
- durationCount: 0,
326
- durationSamples: [],
327
- },
328
- tailwind: {
329
- breakpointUsageCount: 0,
330
- arbitraryBreakpointCount: 0,
331
- utilityFamilyCounts: {},
332
- utilityFamilySamples: [],
333
- },
334
- componentInventory: {
335
- componentFileCount: 0,
336
- pageFileCount: 0,
337
- layoutFileCount: 0,
338
- surfaceFileSamples: [],
339
- },
340
- structuredInspection: {
341
- mode: 'attribute-aware-static-scan',
342
- classAttributeCount: 0,
343
- boundClassExpressionCount: 0,
344
- inlineStyleObjectCount: 0,
345
- inlineStyleBindingCount: 0,
346
- classAttributeSamples: [],
347
- inlineStyleSamples: [],
348
- utilityFamilyCounts: {},
349
- utilityFamilySamples: [],
350
- inlineTokenBypassSignals: {
351
- hardcodedColorCount: 0,
352
- rawSpacingCount: 0,
353
- rawRadiusCount: 0,
354
- rawShadowCount: 0,
355
- cssVariableReferenceCount: 0,
356
- },
357
- },
358
- tokenBypassSignals: {
359
- hardcodedColorCount: 0,
360
- rawSpacingCount: 0,
361
- rawRadiusCount: 0,
362
- rawShadowCount: 0,
363
- inlineHardcodedColorCount: 0,
364
- inlineRawSpacingCount: 0,
365
- inlineRawRadiusCount: 0,
366
- inlineRawShadowCount: 0,
367
- inlineCssVariableReferenceCount: 0,
368
- },
369
- };
370
- }
371
-
372
- function registerSurfaceFile(summary, targetDirectoryPath, scannedFilePath, seenSurfaceFiles) {
373
- const relativeFilePath = path.relative(targetDirectoryPath, scannedFilePath).replace(/\\/g, '/');
374
- const normalizedBaseName = path.basename(scannedFilePath, path.extname(scannedFilePath)).toLowerCase();
375
- const looksLikeComponent = /[A-Z]/.test(path.basename(scannedFilePath, path.extname(scannedFilePath)))
376
- || relativeFilePath.includes('/components/')
377
- || relativeFilePath.startsWith('components/');
378
- const looksLikePage = normalizedBaseName === 'page'
379
- || normalizedBaseName === 'index'
380
- || relativeFilePath.includes('/pages/')
381
- || relativeFilePath.startsWith('pages/')
382
- || relativeFilePath.includes('/app/');
383
- const looksLikeLayout = normalizedBaseName === 'layout' || relativeFilePath.includes('/layouts/');
384
-
385
- if (looksLikeComponent) {
386
- summary.componentInventory.componentFileCount += 1;
387
- }
388
-
389
- if (looksLikePage) {
390
- summary.componentInventory.pageFileCount += 1;
391
- }
392
-
393
- if (looksLikeLayout) {
394
- summary.componentInventory.layoutFileCount += 1;
395
- }
396
-
397
- if ((looksLikeComponent || looksLikePage || looksLikeLayout) && !seenSurfaceFiles.has(relativeFilePath)) {
398
- seenSurfaceFiles.add(relativeFilePath);
399
- if (summary.componentInventory.surfaceFileSamples.length < DESIGN_EVIDENCE_SAMPLE_LIMIT) {
400
- summary.componentInventory.surfaceFileSamples.push(relativeFilePath);
401
- }
402
- }
403
- }
404
-
405
- function collectSampleMatches(sourceText, pattern, targetSamples, targetSet, transform = (match) => match[1] || match[0]) {
406
- for (const match of sourceText.matchAll(pattern)) {
407
- pushSampleValue(targetSamples, transform(match), targetSet);
408
- }
409
- }
410
-
411
- export async function collectFrontendDesignEvidence({
412
- targetDirectoryPath,
413
- markerNames,
414
- scanRootDirectoryPaths = [],
415
- }) {
416
- const candidateDirectoryPaths = FRONTEND_SCAN_DIRECTORY_NAMES
417
- .filter((directoryName) => markerNames.has(directoryName))
418
- .map((directoryName) => path.join(targetDirectoryPath, directoryName));
419
- const explicitScanRootDirectoryPaths = Array.isArray(scanRootDirectoryPaths)
420
- ? scanRootDirectoryPaths.filter((scanRootDirectoryPath) => typeof scanRootDirectoryPath === 'string' && scanRootDirectoryPath.trim().length > 0)
421
- : [];
422
- const resolvedCandidateDirectoryPaths = explicitScanRootDirectoryPaths.length > 0
423
- ? Array.from(new Set(explicitScanRootDirectoryPaths))
424
- : candidateDirectoryPaths.length > 0
425
- ? candidateDirectoryPaths
426
- : [targetDirectoryPath];
427
- const scannedFilePaths = [];
428
- const scanRootRelativePaths = resolvedCandidateDirectoryPaths
429
- .map((candidateDirectoryPath) => path.relative(targetDirectoryPath, candidateDirectoryPath).replace(/\\/g, '/') || '.');
430
- const designEvidenceSummary = createDesignEvidenceSummary(scanRootRelativePaths);
431
- const cssVariableSamples = new Set();
432
- const colorSamples = new Set();
433
- const spacingSamples = new Set();
434
- const radiusSamples = new Set();
435
- const shadowSamples = new Set();
436
- const fontFamilySamples = new Set();
437
- const fontSizeSamples = new Set();
438
- const lineHeightSamples = new Set();
439
- const letterSpacingSamples = new Set();
440
- const durationSamples = new Set();
441
- const utilityFamilySamples = new Set();
442
- const structuredClassAttributeSamples = new Set();
443
- const structuredInlineStyleSamples = new Set();
444
- const structuredUtilityFamilySamples = new Set();
445
- const seenSurfaceFiles = new Set();
446
- let hardcodedColorCount = 0;
447
- let propDrillingCandidateCount = 0;
448
- let mediaQueryCount = 0;
449
- let tailwindBreakpointUsageCount = 0;
450
- let arbitraryBreakpointCount = 0;
451
- const uniqueMediaWidths = new Set();
452
-
453
- for (const candidateDirectoryPath of resolvedCandidateDirectoryPaths) {
454
- await collectFrontendSourceFilePaths(candidateDirectoryPath, scannedFilePaths);
455
- if (scannedFilePaths.length >= FRONTEND_FILE_SCAN_LIMIT) {
456
- break;
457
- }
458
- }
459
-
460
- designEvidenceSummary.scannedFileCount = scannedFilePaths.length;
461
-
462
- for (const scannedFilePath of scannedFilePaths) {
463
- let sourceText;
464
-
465
- try {
466
- const fileStat = await fs.stat(scannedFilePath);
467
- if (fileStat.size > FRONTEND_FILE_SIZE_LIMIT_BYTES) {
468
- continue;
469
- }
470
-
471
- sourceText = await fs.readFile(scannedFilePath, 'utf8');
472
- } catch {
473
- continue;
474
- }
475
-
476
- registerSurfaceFile(designEvidenceSummary, targetDirectoryPath, scannedFilePath, seenSurfaceFiles);
477
- collectStructuredAttributeEvidence(
478
- sourceText,
479
- designEvidenceSummary,
480
- structuredClassAttributeSamples,
481
- structuredInlineStyleSamples,
482
- structuredUtilityFamilySamples
483
- );
484
-
485
- for (const cssVariableMatch of sourceText.matchAll(CSS_VARIABLE_DEFINITION_PATTERN)) {
486
- designEvidenceSummary.cssVariables.definitionCount += 1;
487
- const variableName = cssVariableMatch[1];
488
- const categoryKey = categorizeCssVariable(variableName);
489
- incrementCountMap(designEvidenceSummary.cssVariables.categoryCounts, categoryKey);
490
- pushSampleValue(designEvidenceSummary.cssVariables.sampleNames, variableName, cssVariableSamples);
491
- }
492
-
493
- for (const cssVariableReferenceMatch of sourceText.matchAll(CSS_VARIABLE_REFERENCE_PATTERN)) {
494
- designEvidenceSummary.cssVariables.referenceCount += 1;
495
- pushSampleValue(
496
- designEvidenceSummary.cssVariables.sampleNames,
497
- cssVariableReferenceMatch[1],
498
- cssVariableSamples
499
- );
500
- }
501
-
502
- for (const colorMatch of sourceText.matchAll(COLOR_PATTERN)) {
503
- const colorValue = colorMatch[0];
504
- hardcodedColorCount += 1;
505
- designEvidenceSummary.colors.hardcodedCount += 1;
506
- incrementCountMap(designEvidenceSummary.colors.kindCounts, inferColorKind(colorValue));
507
- pushSampleValue(designEvidenceSummary.colors.sampleValues, colorValue, colorSamples);
508
- }
509
-
510
- propDrillingCandidateCount += countPatternMatches(sourceText, PROP_DRILLING_PATTERN);
511
- mediaQueryCount += countPatternMatches(sourceText, MEDIA_QUERY_PATTERN);
512
- tailwindBreakpointUsageCount += countPatternMatches(sourceText, TAILWIND_BREAKPOINT_PATTERN);
513
- arbitraryBreakpointCount += countPatternMatches(sourceText, ARBITRARY_BREAKPOINT_PATTERN);
514
- designEvidenceSummary.tailwind.breakpointUsageCount += countPatternMatches(sourceText, TAILWIND_BREAKPOINT_PATTERN);
515
- designEvidenceSummary.tailwind.arbitraryBreakpointCount += countPatternMatches(sourceText, ARBITRARY_BREAKPOINT_PATTERN);
516
-
517
- for (const rawSpacingMatch of sourceText.matchAll(RAW_SPACING_PATTERN)) {
518
- designEvidenceSummary.spacing.rawValueCount += 1;
519
- pushSampleValue(designEvidenceSummary.spacing.sampleValues, rawSpacingMatch[1], spacingSamples);
520
- }
521
-
522
- for (const rawRadiusMatch of sourceText.matchAll(RAW_RADIUS_PATTERN)) {
523
- designEvidenceSummary.radius.rawValueCount += 1;
524
- pushSampleValue(designEvidenceSummary.radius.sampleValues, rawRadiusMatch[1], radiusSamples);
525
- }
526
-
527
- for (const rawShadowMatch of sourceText.matchAll(RAW_SHADOW_PATTERN)) {
528
- designEvidenceSummary.shadow.rawValueCount += 1;
529
- pushSampleValue(designEvidenceSummary.shadow.sampleValues, rawShadowMatch[1], shadowSamples);
530
- }
531
-
532
- for (const fontFamilyMatch of sourceText.matchAll(FONT_FAMILY_PATTERN)) {
533
- designEvidenceSummary.typography.fontFamilyCount += 1;
534
- pushSampleValue(designEvidenceSummary.typography.fontFamilySamples, fontFamilyMatch[1], fontFamilySamples);
535
- }
536
-
537
- for (const fontSizeMatch of sourceText.matchAll(FONT_SIZE_PATTERN)) {
538
- designEvidenceSummary.typography.fontSizeCount += 1;
539
- pushSampleValue(designEvidenceSummary.typography.fontSizeSamples, fontSizeMatch[1], fontSizeSamples);
540
- }
541
-
542
- for (const lineHeightMatch of sourceText.matchAll(LINE_HEIGHT_PATTERN)) {
543
- designEvidenceSummary.typography.lineHeightCount += 1;
544
- pushSampleValue(designEvidenceSummary.typography.lineHeightSamples, lineHeightMatch[1], lineHeightSamples);
545
- }
546
-
547
- for (const letterSpacingMatch of sourceText.matchAll(LETTER_SPACING_PATTERN)) {
548
- designEvidenceSummary.typography.letterSpacingCount += 1;
549
- pushSampleValue(
550
- designEvidenceSummary.typography.letterSpacingSamples,
551
- letterSpacingMatch[1],
552
- letterSpacingSamples
553
- );
554
- }
555
-
556
- designEvidenceSummary.motion.transitionCount += countPatternMatches(sourceText, TRANSITION_PATTERN);
557
- designEvidenceSummary.motion.animationCount += countPatternMatches(sourceText, ANIMATION_PATTERN);
558
-
559
- for (const durationMatch of sourceText.matchAll(DURATION_PATTERN)) {
560
- designEvidenceSummary.motion.durationCount += 1;
561
- pushSampleValue(designEvidenceSummary.motion.durationSamples, durationMatch[0], durationSamples);
562
- }
563
-
564
- for (const mediaWidthMatch of sourceText.matchAll(MEDIA_WIDTH_PATTERN)) {
565
- uniqueMediaWidths.add(mediaWidthMatch[1]);
566
- }
567
-
568
- }
569
-
570
- designEvidenceSummary.tailwind.utilityFamilyCounts = {
571
- ...designEvidenceSummary.structuredInspection.utilityFamilyCounts,
572
- };
573
- designEvidenceSummary.tailwind.utilityFamilySamples = [
574
- ...designEvidenceSummary.structuredInspection.utilityFamilySamples,
575
- ];
576
-
577
- designEvidenceSummary.tokenBypassSignals = {
578
- hardcodedColorCount: designEvidenceSummary.colors.hardcodedCount,
579
- rawSpacingCount: designEvidenceSummary.spacing.rawValueCount,
580
- rawRadiusCount: designEvidenceSummary.radius.rawValueCount,
581
- rawShadowCount: designEvidenceSummary.shadow.rawValueCount,
582
- inlineHardcodedColorCount: designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.hardcodedColorCount,
583
- inlineRawSpacingCount: designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.rawSpacingCount,
584
- inlineRawRadiusCount: designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.rawRadiusCount,
585
- inlineRawShadowCount: designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.rawShadowCount,
586
- inlineCssVariableReferenceCount: designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.cssVariableReferenceCount,
587
- };
588
-
589
- return {
590
- frontendEvidenceMetrics: {
591
- scannedFileCount: scannedFilePaths.length,
592
- hardcodedColorCount,
593
- propDrillingCandidateCount,
594
- mediaQueryCount,
595
- tailwindBreakpointUsageCount,
596
- arbitraryBreakpointCount,
597
- uniqueMediaWidthCount: uniqueMediaWidths.size,
598
- structuredClassAttributeCount: designEvidenceSummary.structuredInspection.classAttributeCount,
599
- boundClassExpressionCount: designEvidenceSummary.structuredInspection.boundClassExpressionCount,
600
- inlineStyleObjectCount: designEvidenceSummary.structuredInspection.inlineStyleObjectCount,
601
- inlineStyleBindingCount: designEvidenceSummary.structuredInspection.inlineStyleBindingCount,
602
- inlineStyleTokenBypassCount:
603
- designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.hardcodedColorCount
604
- + designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.rawSpacingCount
605
- + designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.rawRadiusCount
606
- + designEvidenceSummary.structuredInspection.inlineTokenBypassSignals.rawShadowCount,
607
- },
608
- designEvidenceSummary,
609
- };
610
- }
1
+ /**
2
+ * Aggregator re-export for the design evidence scan. Implementation lives in
3
+ * lib/cli/detector/design-evidence/* split per concern. Importers continue to
4
+ * import the same public surface (collectFrontendDesignEvidence and
5
+ * FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES) from this module.
6
+ *
7
+ * Public schema produced by collectFrontendDesignEvidence (kept here so static
8
+ * tooling can confirm the expected shape without crawling sub-files):
9
+ *
10
+ * designEvidenceSummary.summaryVersion: 'v1'
11
+ * designEvidenceSummary.source: 'lightweight-static-scan'
12
+ * designEvidenceSummary.cssVariables.{ definitionCount, referenceCount, sampleNames, categoryCounts }
13
+ * designEvidenceSummary.componentInventory.{ componentFileCount, pageFileCount, layoutFileCount, surfaceFileSamples }
14
+ * designEvidenceSummary.structuredInspection.{ classAttributeCount, inlineStyleObjectCount, ... }
15
+ * designEvidenceSummary.tokenBypassSignals.{ hardcodedColorCount, rawSpacingCount, rawRadiusCount, rawShadowCount, ... }
16
+ * designEvidenceSummary.tailwind.{ breakpointUsageCount, arbitraryBreakpointCount, utilityFamilyCounts, ... }
17
+ *
18
+ * frontendEvidenceMetrics.{ scannedFileCount, hardcodedColorCount, propDrillingCandidateCount,
19
+ * mediaQueryCount, tailwindBreakpointUsageCount, arbitraryBreakpointCount,
20
+ * uniqueMediaWidthCount, structuredClassAttributeCount, boundClassExpressionCount,
21
+ * inlineStyleObjectCount, inlineStyleBindingCount, inlineStyleTokenBypassCount }
22
+ */
23
+
24
+ export { FRONTEND_SCAN_IGNORE_DIRECTORY_NAMES } from './design-evidence/constants.mjs';
25
+ export { collectFrontendDesignEvidence } from './design-evidence/collector.mjs';