dslinter 0.1.5 → 0.2.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 +112 -0
- package/README.md +54 -27
- package/bin/dslinter.mjs +26 -5
- package/bin/lib/config-hide-component.mjs +44 -0
- package/bin/lib/config-hide-component.test.mjs +33 -0
- package/bin/lib/constants.mjs +20 -0
- package/bin/lib/dev-banner.mjs +16 -51
- package/bin/lib/dev-banner.test.mjs +20 -18
- package/bin/lib/enrich-playgrounds-from-ts.mjs +201 -0
- package/bin/lib/enrich-playgrounds-from-ts.test.mjs +74 -0
- package/bin/lib/enrich-report-cli.mjs +14 -0
- package/bin/lib/env.mjs +20 -0
- package/bin/lib/infer-prop-types-from-ts.mjs +381 -0
- package/bin/lib/infer-prop-types-from-ts.test.mjs +174 -0
- package/bin/lib/parse-args.mjs +13 -1
- package/bin/lib/parse-args.test.mjs +7 -1
- package/bin/lib/paths.mjs +8 -0
- package/bin/lib/project-root.mjs +92 -24
- package/bin/lib/project-root.test.mjs +52 -0
- package/bin/lib/prompt.mjs +31 -0
- package/bin/lib/resolve-project.mjs +78 -0
- package/bin/lib/resolve-project.test.mjs +74 -0
- package/bin/lib/run-scanner.mjs +40 -6
- package/bin/lib/scaffold-config.mjs +163 -0
- package/bin/lib/scaffold-config.test.mjs +43 -0
- package/bin/lib/scan-host.mjs +44 -0
- package/bin/lib/scan-host.test.mjs +41 -0
- package/bin/lib/setup-readiness.mjs +153 -0
- package/bin/lib/setup-readiness.test.mjs +32 -0
- package/bin/modes/build.mjs +31 -6
- package/bin/modes/dev.mjs +56 -13
- package/bin/modes/init.mjs +35 -47
- package/bin/modes/init.test.mjs +16 -0
- package/bin/modes/mcp.mjs +49 -0
- package/bin/modes/report.mjs +29 -4
- package/bin/modes/watch.mjs +85 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-Bja3BuZZ.css +1 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-h0gP_iKd.js +1 -0
- package/dashboard-dist/assets/axe-DDaE9JTN.js +20 -0
- package/dashboard-dist/assets/index-B9sZ6wHm.css +1 -0
- package/dashboard-dist/assets/index-DIDBt5ed.js +218 -0
- package/dashboard-dist/index.html +2 -2
- package/index.cjs +53 -52
- package/index.d.ts +3 -0
- package/package.json +18 -12
- package/shared/env.ts +15 -0
- package/shared/paths.ts +8 -0
- package/shared/reportPath.test.ts +19 -0
- package/shared/reportPath.ts +12 -0
- package/shared/servePort.ts +16 -0
- package/src/components/ComponentInspectPane.tsx +67 -19
- package/src/components/ComponentPlaygroundPane.tsx +262 -113
- package/src/components/DashboardCommandPalette.tsx +6 -11
- package/src/components/GovernancePane.tsx +2 -2
- package/src/components/HideFromCatalogButton.tsx +44 -0
- package/src/components/OpenInEditorButton.tsx +36 -0
- package/src/components/PlaygroundA11yAndCode.tsx +53 -53
- package/src/components/PlaygroundAppThemeWrapper.tsx +82 -0
- package/src/components/PlaygroundControls.tsx +5 -11
- package/src/components/PlaygroundPreviewErrorBoundary.tsx +54 -0
- package/src/components/PlaygroundUsageCode.tsx +6 -4
- package/src/components/PlaygroundVariantMatrix.tsx +101 -34
- package/src/components/Section.tsx +5 -2
- package/src/components/Sidebar.tsx +131 -46
- package/src/components/TruncatedPath.tsx +44 -0
- package/src/components/controlApiTable.test.ts +29 -0
- package/src/components/controlApiTable.ts +3 -0
- package/src/components/playgroundUsageHighlight.ts +14 -3
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/table.tsx +2 -2
- package/src/dashboard/ComponentCatalog.tsx +16 -23
- package/src/dashboard/ComponentUsageDetails.tsx +6 -15
- package/src/dashboard/DashboardBody.tsx +0 -35
- package/src/dashboard/FindingsList.tsx +65 -55
- package/src/dashboard/ScannedTokenWall.tsx +3 -3
- package/src/dashboard/aggregate.test.ts +74 -0
- package/src/dashboard/aggregate.ts +145 -21
- package/src/dashboard/catalogVisibility.test.ts +93 -0
- package/src/dashboard/catalogVisibility.ts +108 -0
- package/src/dashboard/editorLink.test.ts +57 -0
- package/src/dashboard/editorLink.ts +71 -0
- package/src/dashboard/paths.test.ts +49 -0
- package/src/dashboard/paths.ts +51 -3
- package/src/dashboard/updateDslintConfig.ts +22 -0
- package/src/dashboard/useWorkspaceReport.ts +21 -17
- package/src/index.ts +26 -0
- package/src/mcp/agent-context.ts +148 -0
- package/src/mcp/agent-query.test.ts +89 -0
- package/src/mcp/agent-query.ts +373 -0
- package/src/mcp/config.ts +53 -0
- package/src/mcp/index.ts +18 -0
- package/src/mcp/normalize-paths.ts +65 -0
- package/src/mcp/report-cache.ts +209 -0
- package/src/mcp/rule-catalog.json +156 -0
- package/src/mcp/rule-catalog.ts +33 -0
- package/src/mcp/schemas.ts +54 -0
- package/src/mcp/server.test.ts +44 -0
- package/src/mcp/server.ts +343 -0
- package/src/mcp/start.ts +29 -0
- package/src/mcp/verify-loop.test.ts +49 -0
- package/src/mcp/verify-loop.ts +149 -0
- package/src/playground/appPreviewTheme.test.ts +148 -0
- package/src/playground/appPreviewTheme.ts +137 -0
- package/src/playground/buildCompoundPlaygroundEntries.test.ts +348 -0
- package/src/playground/buildCompoundPlaygroundEntries.ts +625 -0
- package/src/playground/buildPlaygroundEntriesFromReport.test.ts +420 -6
- package/src/playground/buildPlaygroundEntriesFromReport.ts +206 -285
- package/src/playground/catalogIdFromPlaygroundExport.test.ts +15 -0
- package/src/playground/catalogIdFromPlaygroundExport.ts +8 -0
- package/src/playground/collectDefinedPlaygrounds.test.ts +59 -0
- package/src/playground/collectDefinedPlaygrounds.ts +68 -0
- package/src/playground/controls.ts +177 -0
- package/src/playground/createPlaygroundRegistry.ts +1 -1
- package/src/playground/definePlayground.tsx +88 -16
- package/src/playground/definePlaygroundFromKit.ts +17 -0
- package/src/playground/embedGlobKey.ts +8 -0
- package/src/playground/enrichKitControls.test.ts +25 -0
- package/src/playground/enrichKitControls.ts +197 -0
- package/src/playground/expandPlaygroundControls.test.ts +50 -0
- package/src/playground/expandPlaygroundControls.ts +97 -0
- package/src/playground/inferKitJsx.test.ts +77 -0
- package/src/playground/inferKitJsx.ts +165 -0
- package/src/playground/inferKitParams.test.ts +41 -0
- package/src/playground/inferKitParams.ts +113 -0
- package/src/playground/inferPropTypesFromTs.d.mts +47 -0
- package/src/playground/inferPropTypesFromTs.mjs +343 -0
- package/src/playground/inferPropTypesFromTs.test.ts +227 -0
- package/src/playground/inferPropTypesFromTs.ts +17 -0
- package/src/playground/mergePlaygroundEntries.test.ts +32 -0
- package/src/playground/mergePlaygroundEntries.ts +28 -0
- package/src/playground/playgroundJoin.test.ts +79 -19
- package/src/playground/playgroundJoin.ts +47 -22
- package/src/playground/playgroundModuleExport.test.ts +42 -0
- package/src/playground/playgroundModuleExport.ts +22 -0
- package/src/playground/playgroundSpecsKey.ts +8 -0
- package/src/playground/propCoerce.ts +91 -0
- package/src/playground/scanVariantA11y.test.ts +46 -0
- package/src/playground/scanVariantA11y.ts +107 -0
- package/src/playground/snippet.ts +83 -0
- package/src/playground/usePlaygroundFromReport.test.ts +18 -8
- package/src/playground/usePlaygroundFromReport.ts +3 -1
- package/src/report/a11yForModule.ts +2 -7
- package/src/report/a11yScoring.test.ts +24 -0
- package/src/report/a11yScoring.ts +17 -0
- package/src/report/index.ts +6 -0
- package/src/shell/DashboardLayout.tsx +71 -45
- package/src/shell/DashboardLayoutAuto.tsx +0 -4
- package/src/shell/hashRoute.test.ts +7 -15
- package/src/shell/hashRoute.ts +31 -31
- package/src/shell/useHashRoute.ts +38 -13
- package/src/styles/dashboard-theme.css +18 -7
- package/src/types/controls.ts +11 -0
- package/src/types/playground.ts +4 -0
- package/src/types/report.ts +32 -9
- package/templates/playground/buildRegistry.ts +1 -1
- package/templates/vite.dslinter.snippet.ts +15 -4
- package/vite/collectScanModules.test.ts +51 -3
- package/vite/collectScanModules.ts +85 -29
- package/vite/consumer.config.mjs +6 -3
- package/vite/consumerAlias.test.ts +47 -0
- package/vite/consumerAlias.ts +114 -0
- package/vite/embedTailwindSources.test.ts +74 -0
- package/vite/embedTailwindSources.ts +97 -0
- package/vite/loadConsumerAliases.test.ts +131 -0
- package/vite/loadConsumerAliases.ts +155 -0
- package/vite/openFileInEditor.mjs +196 -0
- package/vite/openFileInEditor.test.mjs +87 -0
- package/vite/plugin.resolve.test.ts +72 -0
- package/vite/plugin.ts +216 -19
- package/vite/reportPath.test.ts +19 -0
- package/vite/resolveWayfinderImport.ts +56 -0
- package/vite/shims/inertia-react.tsx +85 -0
- package/vite/shims/wayfinder-actions.ts +33 -0
- package/vite/shims/wayfinder-routes.ts +30 -0
- package/vite/shims/ziggy-js.ts +12 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-BPPtPsYh.css +0 -1
- package/dashboard-dist/assets/DashboardLayoutAuto-Dp3bAQxH.js +0 -1
- package/dashboard-dist/assets/index-DsjwnDdX.js +0 -206
- package/dashboard-dist/assets/index-jaCmZJlW.css +0 -1
- package/src/components/playgroundUsageTwoslash.ts +0 -69
- package/templates/vite.dslint-scan-alias.snippet.ts +0 -4
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateDeclaredProps,
|
|
3
|
+
aggregateDefinitions,
|
|
4
|
+
catalogComponentNames,
|
|
5
|
+
usageMap,
|
|
6
|
+
} from "../dashboard/aggregate";
|
|
7
|
+
import { findingsForComponent } from "../report/findingsForComponent";
|
|
8
|
+
import { controlsForSpec } from "../playground/controls";
|
|
9
|
+
import { genericUsageSnippet } from "../playground/snippet";
|
|
10
|
+
import { pillarForRule, ruleById, ruleCatalog } from "./rule-catalog";
|
|
11
|
+
import { findingMatchesPath } from "./normalize-paths";
|
|
12
|
+
import type {
|
|
13
|
+
ConfigSnapshot,
|
|
14
|
+
CssTokenCategory,
|
|
15
|
+
CssTokenSummary,
|
|
16
|
+
LintFinding,
|
|
17
|
+
PlaygroundSpec,
|
|
18
|
+
Severity,
|
|
19
|
+
UsageLocation,
|
|
20
|
+
UsageSummary,
|
|
21
|
+
WorkspaceReport,
|
|
22
|
+
} from "../types/report";
|
|
23
|
+
|
|
24
|
+
export type CatalogEntry = {
|
|
25
|
+
name: string;
|
|
26
|
+
reference_count: number;
|
|
27
|
+
import_path: string | null;
|
|
28
|
+
deprecated: boolean;
|
|
29
|
+
duplicate: boolean;
|
|
30
|
+
definition_paths: string[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ComponentSpec = {
|
|
34
|
+
name: string;
|
|
35
|
+
import_path: string | null;
|
|
36
|
+
declared_props: string[];
|
|
37
|
+
declared_prop_options?: Record<string, string[]>;
|
|
38
|
+
declared_prop_defaults?: Record<string, string>;
|
|
39
|
+
declared_prop_kinds?: Record<string, string>;
|
|
40
|
+
usage: UsageSummary | null;
|
|
41
|
+
findings: LintFinding[];
|
|
42
|
+
example_jsx: string | null;
|
|
43
|
+
deprecated: boolean;
|
|
44
|
+
duplicates: string[] | null;
|
|
45
|
+
definition_paths: string[];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type FindingsQuery = {
|
|
49
|
+
component?: string;
|
|
50
|
+
rule_prefix?: string;
|
|
51
|
+
severity?: Severity;
|
|
52
|
+
path?: string;
|
|
53
|
+
limit?: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type GovernanceSummary = {
|
|
57
|
+
scores: WorkspaceReport["scores"];
|
|
58
|
+
finding_counts: Record<string, number>;
|
|
59
|
+
total_findings: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function configSnapshot(report: WorkspaceReport): ConfigSnapshot {
|
|
63
|
+
return report.config_snapshot ?? {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function deprecatedSet(report: WorkspaceReport): Set<string> {
|
|
67
|
+
return new Set(configSnapshot(report).deprecated_components ?? []);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function playgroundForComponent(
|
|
71
|
+
report: WorkspaceReport,
|
|
72
|
+
name: string,
|
|
73
|
+
): PlaygroundSpec | undefined {
|
|
74
|
+
return (report.playgrounds ?? []).find(
|
|
75
|
+
(p) => p.export_name === name || p.id === name,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function importPathForComponent(
|
|
80
|
+
report: WorkspaceReport,
|
|
81
|
+
name: string,
|
|
82
|
+
): string | null {
|
|
83
|
+
const pg = playgroundForComponent(report, name);
|
|
84
|
+
return pg?.rel_path ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function duplicateLocations(
|
|
88
|
+
report: WorkspaceReport,
|
|
89
|
+
name: string,
|
|
90
|
+
): string[] | null {
|
|
91
|
+
const dup = (report.duplicate_components ?? []).find((d) => d.name === name);
|
|
92
|
+
return dup ? dup.locations : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function valuesFromUsageLocation(loc: UsageLocation): Record<string, unknown> {
|
|
96
|
+
const values: Record<string, unknown> = {};
|
|
97
|
+
if (loc.prop_values) {
|
|
98
|
+
for (const [k, v] of Object.entries(loc.prop_values)) {
|
|
99
|
+
if (k === "children") {
|
|
100
|
+
values[k] = v;
|
|
101
|
+
} else if (v === "true") {
|
|
102
|
+
values[k] = true;
|
|
103
|
+
} else if (v === "false") {
|
|
104
|
+
values[k] = false;
|
|
105
|
+
} else if (/^-?\d+(\.\d+)?$/.test(v)) {
|
|
106
|
+
values[k] = Number(v);
|
|
107
|
+
} else {
|
|
108
|
+
values[k] = v;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const prop of loc.props) {
|
|
113
|
+
if (!(prop in values) && prop !== "children") {
|
|
114
|
+
values[prop] = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return values;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function exampleJsxForComponent(
|
|
121
|
+
report: WorkspaceReport,
|
|
122
|
+
name: string,
|
|
123
|
+
): string | null {
|
|
124
|
+
const usage = usageMap(report).get(name);
|
|
125
|
+
const pg = playgroundForComponent(report, name);
|
|
126
|
+
const declared = aggregateDeclaredProps(report).get(name) ?? pg?.declared_props ?? [];
|
|
127
|
+
const controls = controlsForSpec(
|
|
128
|
+
name,
|
|
129
|
+
declared,
|
|
130
|
+
pg?.declared_prop_kinds,
|
|
131
|
+
pg?.declared_prop_options,
|
|
132
|
+
pg?.declared_prop_defaults,
|
|
133
|
+
{},
|
|
134
|
+
pg?.export_name ?? name,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const loc = usage?.usage_locations?.[0];
|
|
138
|
+
if (loc) {
|
|
139
|
+
return genericUsageSnippet(name, valuesFromUsageLocation(loc), controls);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (controls.length > 0) {
|
|
143
|
+
const defaults: Record<string, unknown> = {};
|
|
144
|
+
for (const c of controls) {
|
|
145
|
+
defaults[c.key] = c.default;
|
|
146
|
+
}
|
|
147
|
+
return genericUsageSnippet(name, defaults, controls);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return `<${name} />`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function catalogSummary(
|
|
154
|
+
report: WorkspaceReport,
|
|
155
|
+
opts: { query?: string; limit?: number } = {},
|
|
156
|
+
): CatalogEntry[] {
|
|
157
|
+
const defs = aggregateDefinitions(report);
|
|
158
|
+
const usages = usageMap(report);
|
|
159
|
+
const deprecated = deprecatedSet(report);
|
|
160
|
+
const names = catalogComponentNames(defs, usages, report);
|
|
161
|
+
const q = opts.query?.trim().toLowerCase();
|
|
162
|
+
|
|
163
|
+
let entries: CatalogEntry[] = names.map((name) => {
|
|
164
|
+
const usage = usages.get(name);
|
|
165
|
+
return {
|
|
166
|
+
name,
|
|
167
|
+
reference_count: usage?.reference_count ?? 0,
|
|
168
|
+
import_path: importPathForComponent(report, name),
|
|
169
|
+
deprecated: deprecated.has(name),
|
|
170
|
+
duplicate: duplicateLocations(report, name) !== null,
|
|
171
|
+
definition_paths: (defs.get(name) ?? []).map((s) => s.path),
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (q) {
|
|
176
|
+
entries = entries.filter(
|
|
177
|
+
(e) =>
|
|
178
|
+
e.name.toLowerCase().includes(q) ||
|
|
179
|
+
(e.import_path?.toLowerCase().includes(q) ?? false),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
entries.sort(
|
|
184
|
+
(a, b) =>
|
|
185
|
+
b.reference_count - a.reference_count || a.name.localeCompare(b.name),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const limit = opts.limit ?? 100;
|
|
189
|
+
return entries.slice(0, limit);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function componentSpec(
|
|
193
|
+
report: WorkspaceReport,
|
|
194
|
+
name: string,
|
|
195
|
+
): ComponentSpec | null {
|
|
196
|
+
const defs = aggregateDefinitions(report);
|
|
197
|
+
if (!defs.has(name) && !usageMap(report).has(name)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const pg = playgroundForComponent(report, name);
|
|
202
|
+
const declared = aggregateDeclaredProps(report).get(name) ?? pg?.declared_props ?? [];
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
name,
|
|
206
|
+
import_path: importPathForComponent(report, name),
|
|
207
|
+
declared_props: declared,
|
|
208
|
+
declared_prop_options: pg?.declared_prop_options,
|
|
209
|
+
declared_prop_defaults: pg?.declared_prop_defaults,
|
|
210
|
+
declared_prop_kinds: pg?.declared_prop_kinds as Record<string, string> | undefined,
|
|
211
|
+
usage: usageMap(report).get(name) ?? null,
|
|
212
|
+
findings: findingsForComponent(report, name),
|
|
213
|
+
example_jsx: exampleJsxForComponent(report, name),
|
|
214
|
+
deprecated: deprecatedSet(report).has(name),
|
|
215
|
+
duplicates: duplicateLocations(report, name),
|
|
216
|
+
definition_paths: (defs.get(name) ?? []).map((s) => s.path),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function findingsQuery(
|
|
221
|
+
report: WorkspaceReport,
|
|
222
|
+
filters: FindingsQuery,
|
|
223
|
+
): LintFinding[] {
|
|
224
|
+
let rows = [...(report.findings ?? [])];
|
|
225
|
+
|
|
226
|
+
if (filters.component) {
|
|
227
|
+
const componentFindings = findingsForComponent(report, filters.component);
|
|
228
|
+
const ids = new Set(
|
|
229
|
+
componentFindings.map(
|
|
230
|
+
(f) => `${f.rule_id}:${f.path}:${f.line ?? "x"}`,
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
rows = rows.filter((f) =>
|
|
234
|
+
ids.has(`${f.rule_id}:${f.path}:${f.line ?? "x"}`),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (filters.rule_prefix) {
|
|
239
|
+
const prefix = filters.rule_prefix;
|
|
240
|
+
rows = rows.filter((f) => f.rule_id.startsWith(prefix));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (filters.severity) {
|
|
244
|
+
rows = rows.filter((f) => f.severity === filters.severity);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (filters.path) {
|
|
248
|
+
rows = rows.filter((f) => findingMatchesPath(f, filters.path!));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const severityOrder: Record<Severity, number> = {
|
|
252
|
+
error: 0,
|
|
253
|
+
warning: 1,
|
|
254
|
+
info: 2,
|
|
255
|
+
};
|
|
256
|
+
rows.sort(
|
|
257
|
+
(a, b) =>
|
|
258
|
+
severityOrder[a.severity] - severityOrder[b.severity] ||
|
|
259
|
+
a.path.localeCompare(b.path) ||
|
|
260
|
+
(a.line ?? 0) - (b.line ?? 0),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const limit = filters.limit ?? 50;
|
|
264
|
+
return rows.slice(0, limit);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function findingsForPaths(
|
|
268
|
+
report: WorkspaceReport,
|
|
269
|
+
paths: string[],
|
|
270
|
+
): LintFinding[] {
|
|
271
|
+
const normalized = paths.map((p) => p.replace(/\\/g, "/"));
|
|
272
|
+
return (report.findings ?? [])
|
|
273
|
+
.filter((f) => normalized.some((p) => findingMatchesPath(f, p)))
|
|
274
|
+
.sort(
|
|
275
|
+
(a, b) =>
|
|
276
|
+
a.path.localeCompare(b.path) ||
|
|
277
|
+
(a.line ?? 0) - (b.line ?? 0) ||
|
|
278
|
+
a.rule_id.localeCompare(b.rule_id),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export type TokenSummaryEntry = {
|
|
283
|
+
name: string;
|
|
284
|
+
category: CssTokenCategory;
|
|
285
|
+
value: string;
|
|
286
|
+
reference_count: number;
|
|
287
|
+
unused: boolean;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export function tokenSummary(
|
|
291
|
+
report: WorkspaceReport,
|
|
292
|
+
category?: CssTokenCategory,
|
|
293
|
+
): { tokens: TokenSummaryEntry[]; unused_count: number } {
|
|
294
|
+
const css: CssTokenSummary | undefined = report.css_tokens;
|
|
295
|
+
if (!css) return { tokens: [], unused_count: 0 };
|
|
296
|
+
|
|
297
|
+
const unusedSet = new Set(css.unused_tokens ?? []);
|
|
298
|
+
const usageByName = new Map(
|
|
299
|
+
(css.usage_by_token ?? []).map((u) => [u.name, u.reference_count]),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
let tokens: TokenSummaryEntry[] = css.definitions.map((d) => ({
|
|
303
|
+
name: d.name,
|
|
304
|
+
category: d.category,
|
|
305
|
+
value: d.value,
|
|
306
|
+
reference_count: usageByName.get(d.name) ?? 0,
|
|
307
|
+
unused: unusedSet.has(d.name),
|
|
308
|
+
}));
|
|
309
|
+
|
|
310
|
+
if (category) {
|
|
311
|
+
tokens = tokens.filter((t) => t.category === category);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
tokens.sort((a, b) => a.name.localeCompare(b.name));
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
tokens,
|
|
318
|
+
unused_count: (css.unused_tokens ?? []).length,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function governanceSummary(report: WorkspaceReport): GovernanceSummary {
|
|
323
|
+
const finding_counts: Record<string, number> = {
|
|
324
|
+
a11y: 0,
|
|
325
|
+
token: 0,
|
|
326
|
+
usage: 0,
|
|
327
|
+
code: 0,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
for (const f of report.findings ?? []) {
|
|
331
|
+
const pillar = pillarForRule(f.rule_id);
|
|
332
|
+
finding_counts[pillar] = (finding_counts[pillar] ?? 0) + 1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
scores: report.scores,
|
|
337
|
+
finding_counts,
|
|
338
|
+
total_findings: report.findings?.length ?? 0,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function usageExamples(
|
|
343
|
+
report: WorkspaceReport,
|
|
344
|
+
component: string,
|
|
345
|
+
limit = 10,
|
|
346
|
+
): {
|
|
347
|
+
component: string;
|
|
348
|
+
prop_frequencies: Record<string, number>;
|
|
349
|
+
prop_value_frequencies: Record<string, Record<string, number>>;
|
|
350
|
+
examples: UsageLocation[];
|
|
351
|
+
} | null {
|
|
352
|
+
const usage = usageMap(report).get(component);
|
|
353
|
+
if (!usage) return null;
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
component,
|
|
357
|
+
prop_frequencies: usage.prop_frequencies ?? {},
|
|
358
|
+
prop_value_frequencies: usage.prop_value_frequencies ?? {},
|
|
359
|
+
examples: (usage.usage_locations ?? []).slice(0, limit),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function policyFromReport(report: WorkspaceReport) {
|
|
364
|
+
const snap = configSnapshot(report);
|
|
365
|
+
return {
|
|
366
|
+
deprecated_components: snap.deprecated_components ?? [],
|
|
367
|
+
known_tokens: snap.known_tokens ?? [],
|
|
368
|
+
include_dirs: snap.include_dirs ?? [],
|
|
369
|
+
rules: ruleCatalog(),
|
|
370
|
+
schema_version: report.schema_version ?? 0,
|
|
371
|
+
generated_at: report.generated_at ?? null,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
const REPORT_FILE_NAME = "dslinter-report.json";
|
|
4
|
+
|
|
5
|
+
function resolveReportFilePath(scanRoot: string): string {
|
|
6
|
+
const fromEnv = process.env.DSLINTER_REPORT_PATH?.trim();
|
|
7
|
+
if (fromEnv) return resolve(fromEnv);
|
|
8
|
+
return resolve(scanRoot, "public", REPORT_FILE_NAME);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type McpConfig = {
|
|
12
|
+
cwd: string;
|
|
13
|
+
scanPath: string;
|
|
14
|
+
projectRoot: string;
|
|
15
|
+
reportPath: string;
|
|
16
|
+
devUrl: string;
|
|
17
|
+
ttlMs: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function envInt(name: string, fallback: number): number {
|
|
21
|
+
const raw = process.env[name]?.trim();
|
|
22
|
+
if (!raw) return fallback;
|
|
23
|
+
const n = Number.parseInt(raw, 10);
|
|
24
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Build MCP config from env + explicit paths (set by bin/modes/mcp.mjs). */
|
|
28
|
+
export function buildMcpConfig(opts: {
|
|
29
|
+
cwd?: string;
|
|
30
|
+
scanPath: string;
|
|
31
|
+
projectRoot: string;
|
|
32
|
+
reportPath?: string;
|
|
33
|
+
devUrl?: string;
|
|
34
|
+
ttlMs?: number;
|
|
35
|
+
}): McpConfig {
|
|
36
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
37
|
+
const scanPath = resolve(
|
|
38
|
+
process.env.DSLINTER_SCAN_ROOT?.trim() || opts.scanPath,
|
|
39
|
+
);
|
|
40
|
+
const projectRoot = resolve(opts.projectRoot);
|
|
41
|
+
const reportPath = resolve(
|
|
42
|
+
process.env.DSLINTER_REPORT_PATH?.trim() ||
|
|
43
|
+
opts.reportPath ||
|
|
44
|
+
resolveReportFilePath(scanPath),
|
|
45
|
+
);
|
|
46
|
+
const devUrl =
|
|
47
|
+
opts.devUrl ??
|
|
48
|
+
process.env.DSLINTER_MCP_DEV_URL?.trim() ??
|
|
49
|
+
"http://127.0.0.1:7878";
|
|
50
|
+
const ttlMs = opts.ttlMs ?? envInt("DSLINTER_MCP_TTL_MS", 60_000);
|
|
51
|
+
|
|
52
|
+
return { cwd, scanPath, projectRoot, reportPath, devUrl, ttlMs };
|
|
53
|
+
}
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { buildMcpConfig, type McpConfig } from "./config";
|
|
2
|
+
export {
|
|
3
|
+
catalogSummary,
|
|
4
|
+
componentSpec,
|
|
5
|
+
findingsQuery,
|
|
6
|
+
governanceSummary,
|
|
7
|
+
policyFromReport,
|
|
8
|
+
tokenSummary,
|
|
9
|
+
usageExamples,
|
|
10
|
+
type CatalogEntry,
|
|
11
|
+
type ComponentSpec,
|
|
12
|
+
} from "./agent-query";
|
|
13
|
+
export { buildAgentContext } from "./agent-context";
|
|
14
|
+
export { normalizeReportPaths } from "./normalize-paths";
|
|
15
|
+
export { ReportCache, loadBaseline, saveBaseline } from "./report-cache";
|
|
16
|
+
export { createDslinterMcpServer, runMcpServer, runMcpSelfTest } from "./server";
|
|
17
|
+
export { ruleCatalog, ruleById, pillarForRule } from "./rule-catalog";
|
|
18
|
+
export { suggestFix, computeDrift, findingsForPaths } from "./verify-loop";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { LintFinding, WorkspaceReport } from "../types/report";
|
|
2
|
+
|
|
3
|
+
function normalizeOnePath(path: string, root: string): string {
|
|
4
|
+
if (!path) return path;
|
|
5
|
+
const normRoot = root.replace(/\\/g, "/").replace(/\/$/, "");
|
|
6
|
+
const normPath = path.replace(/\\/g, "/");
|
|
7
|
+
if (normPath.startsWith(normRoot + "/")) {
|
|
8
|
+
return normPath.slice(normRoot.length + 1);
|
|
9
|
+
}
|
|
10
|
+
if (normPath === normRoot) return ".";
|
|
11
|
+
return normPath;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Make report paths relative to root for portable agent context. */
|
|
15
|
+
export function normalizeReportPaths(report: WorkspaceReport): WorkspaceReport {
|
|
16
|
+
const root = report.root.replace(/\\/g, "/");
|
|
17
|
+
const rel = (p: string) => normalizeOnePath(p, root);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...report,
|
|
21
|
+
files: (report.files ?? []).map((f) => ({
|
|
22
|
+
...f,
|
|
23
|
+
path: rel(f.path),
|
|
24
|
+
})),
|
|
25
|
+
findings: (report.findings ?? []).map((f) => ({
|
|
26
|
+
...f,
|
|
27
|
+
path: rel(f.path),
|
|
28
|
+
})),
|
|
29
|
+
duplicate_components: (report.duplicate_components ?? []).map((d) => ({
|
|
30
|
+
...d,
|
|
31
|
+
locations: d.locations.map(rel),
|
|
32
|
+
})),
|
|
33
|
+
usage_by_component: (report.usage_by_component ?? []).map((u) => ({
|
|
34
|
+
...u,
|
|
35
|
+
files: u.files.map(rel),
|
|
36
|
+
usage_locations: u.usage_locations?.map((loc) => ({
|
|
37
|
+
...loc,
|
|
38
|
+
path: rel(loc.path),
|
|
39
|
+
})),
|
|
40
|
+
})),
|
|
41
|
+
css_tokens: report.css_tokens
|
|
42
|
+
? {
|
|
43
|
+
...report.css_tokens,
|
|
44
|
+
definitions: report.css_tokens.definitions.map((d) => ({
|
|
45
|
+
...d,
|
|
46
|
+
path: rel(d.path),
|
|
47
|
+
})),
|
|
48
|
+
usage_by_token: report.css_tokens.usage_by_token.map((u) => ({
|
|
49
|
+
...u,
|
|
50
|
+
files: u.files.map(rel),
|
|
51
|
+
usage_locations: u.usage_locations?.map((loc) => ({
|
|
52
|
+
...loc,
|
|
53
|
+
path: rel(loc.path),
|
|
54
|
+
})),
|
|
55
|
+
})),
|
|
56
|
+
}
|
|
57
|
+
: undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function findingMatchesPath(finding: LintFinding, pathFilter: string): boolean {
|
|
62
|
+
const norm = pathFilter.replace(/\\/g, "/");
|
|
63
|
+
const fp = finding.path.replace(/\\/g, "/");
|
|
64
|
+
return fp === norm || fp.endsWith("/" + norm) || fp.endsWith(norm);
|
|
65
|
+
}
|