dslinter 0.1.13 → 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.
Files changed (181) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +50 -29
  3. package/bin/dslinter.mjs +26 -5
  4. package/bin/lib/config-hide-component.mjs +44 -0
  5. package/bin/lib/config-hide-component.test.mjs +33 -0
  6. package/bin/lib/constants.mjs +20 -0
  7. package/bin/lib/dev-banner.mjs +16 -51
  8. package/bin/lib/dev-banner.test.mjs +20 -18
  9. package/bin/lib/enrich-playgrounds-from-ts.mjs +201 -0
  10. package/bin/lib/enrich-playgrounds-from-ts.test.mjs +74 -0
  11. package/bin/lib/enrich-report-cli.mjs +14 -0
  12. package/bin/lib/env.mjs +20 -0
  13. package/bin/lib/infer-prop-types-from-ts.mjs +381 -0
  14. package/bin/lib/infer-prop-types-from-ts.test.mjs +174 -0
  15. package/bin/lib/parse-args.mjs +13 -1
  16. package/bin/lib/parse-args.test.mjs +7 -1
  17. package/bin/lib/paths.mjs +8 -0
  18. package/bin/lib/project-root.mjs +72 -10
  19. package/bin/lib/project-root.test.mjs +32 -1
  20. package/bin/lib/prompt.mjs +31 -0
  21. package/bin/lib/resolve-project.mjs +78 -0
  22. package/bin/lib/resolve-project.test.mjs +74 -0
  23. package/bin/lib/run-scanner.mjs +40 -6
  24. package/bin/lib/scaffold-config.mjs +96 -8
  25. package/bin/lib/scaffold-config.test.mjs +12 -2
  26. package/bin/lib/scan-host.mjs +44 -0
  27. package/bin/lib/scan-host.test.mjs +41 -0
  28. package/bin/lib/setup-readiness.mjs +153 -0
  29. package/bin/lib/setup-readiness.test.mjs +32 -0
  30. package/bin/modes/build.mjs +31 -6
  31. package/bin/modes/dev.mjs +55 -21
  32. package/bin/modes/init.mjs +3 -22
  33. package/bin/modes/init.test.mjs +1 -1
  34. package/bin/modes/mcp.mjs +49 -0
  35. package/bin/modes/report.mjs +29 -4
  36. package/bin/modes/watch.mjs +85 -0
  37. package/dashboard-dist/assets/DashboardLayoutAuto-Bja3BuZZ.css +1 -0
  38. package/dashboard-dist/assets/DashboardLayoutAuto-h0gP_iKd.js +1 -0
  39. package/dashboard-dist/assets/axe-DDaE9JTN.js +20 -0
  40. package/dashboard-dist/assets/index-B9sZ6wHm.css +1 -0
  41. package/dashboard-dist/assets/index-DIDBt5ed.js +218 -0
  42. package/dashboard-dist/index.html +2 -2
  43. package/index.cjs +53 -52
  44. package/index.d.ts +3 -0
  45. package/package.json +18 -12
  46. package/shared/env.ts +15 -0
  47. package/shared/paths.ts +8 -0
  48. package/shared/reportPath.test.ts +19 -0
  49. package/shared/reportPath.ts +12 -0
  50. package/shared/servePort.ts +16 -0
  51. package/src/components/ComponentInspectPane.tsx +67 -19
  52. package/src/components/ComponentPlaygroundPane.tsx +262 -113
  53. package/src/components/DashboardCommandPalette.tsx +6 -11
  54. package/src/components/GovernancePane.tsx +2 -2
  55. package/src/components/HideFromCatalogButton.tsx +44 -0
  56. package/src/components/OpenInEditorButton.tsx +36 -0
  57. package/src/components/PlaygroundA11yAndCode.tsx +53 -53
  58. package/src/components/PlaygroundAppThemeWrapper.tsx +82 -0
  59. package/src/components/PlaygroundControls.tsx +5 -11
  60. package/src/components/PlaygroundPreviewErrorBoundary.tsx +54 -0
  61. package/src/components/PlaygroundUsageCode.tsx +6 -4
  62. package/src/components/PlaygroundVariantMatrix.tsx +101 -34
  63. package/src/components/Section.tsx +5 -2
  64. package/src/components/Sidebar.tsx +131 -46
  65. package/src/components/TruncatedPath.tsx +44 -0
  66. package/src/components/controlApiTable.test.ts +29 -0
  67. package/src/components/controlApiTable.ts +3 -0
  68. package/src/components/playgroundUsageHighlight.ts +14 -3
  69. package/src/components/ui/badge.tsx +1 -1
  70. package/src/components/ui/table.tsx +2 -2
  71. package/src/dashboard/ComponentCatalog.tsx +16 -23
  72. package/src/dashboard/ComponentUsageDetails.tsx +6 -15
  73. package/src/dashboard/DashboardBody.tsx +0 -35
  74. package/src/dashboard/FindingsList.tsx +65 -55
  75. package/src/dashboard/ScannedTokenWall.tsx +3 -3
  76. package/src/dashboard/aggregate.test.ts +74 -0
  77. package/src/dashboard/aggregate.ts +145 -21
  78. package/src/dashboard/catalogVisibility.test.ts +93 -0
  79. package/src/dashboard/catalogVisibility.ts +108 -0
  80. package/src/dashboard/editorLink.test.ts +57 -0
  81. package/src/dashboard/editorLink.ts +71 -0
  82. package/src/dashboard/paths.test.ts +49 -0
  83. package/src/dashboard/paths.ts +51 -3
  84. package/src/dashboard/updateDslintConfig.ts +22 -0
  85. package/src/dashboard/useWorkspaceReport.ts +21 -17
  86. package/src/index.ts +26 -0
  87. package/src/mcp/agent-context.ts +148 -0
  88. package/src/mcp/agent-query.test.ts +89 -0
  89. package/src/mcp/agent-query.ts +373 -0
  90. package/src/mcp/config.ts +53 -0
  91. package/src/mcp/index.ts +18 -0
  92. package/src/mcp/normalize-paths.ts +65 -0
  93. package/src/mcp/report-cache.ts +209 -0
  94. package/src/mcp/rule-catalog.json +156 -0
  95. package/src/mcp/rule-catalog.ts +33 -0
  96. package/src/mcp/schemas.ts +54 -0
  97. package/src/mcp/server.test.ts +44 -0
  98. package/src/mcp/server.ts +343 -0
  99. package/src/mcp/start.ts +29 -0
  100. package/src/mcp/verify-loop.test.ts +49 -0
  101. package/src/mcp/verify-loop.ts +149 -0
  102. package/src/playground/appPreviewTheme.test.ts +148 -0
  103. package/src/playground/appPreviewTheme.ts +137 -0
  104. package/src/playground/buildCompoundPlaygroundEntries.test.ts +348 -0
  105. package/src/playground/buildCompoundPlaygroundEntries.ts +625 -0
  106. package/src/playground/buildPlaygroundEntriesFromReport.test.ts +420 -6
  107. package/src/playground/buildPlaygroundEntriesFromReport.ts +206 -285
  108. package/src/playground/catalogIdFromPlaygroundExport.test.ts +15 -0
  109. package/src/playground/catalogIdFromPlaygroundExport.ts +8 -0
  110. package/src/playground/collectDefinedPlaygrounds.test.ts +59 -0
  111. package/src/playground/collectDefinedPlaygrounds.ts +68 -0
  112. package/src/playground/controls.ts +177 -0
  113. package/src/playground/createPlaygroundRegistry.ts +1 -1
  114. package/src/playground/definePlayground.tsx +88 -16
  115. package/src/playground/definePlaygroundFromKit.ts +17 -0
  116. package/src/playground/embedGlobKey.ts +8 -0
  117. package/src/playground/enrichKitControls.test.ts +25 -0
  118. package/src/playground/enrichKitControls.ts +197 -0
  119. package/src/playground/expandPlaygroundControls.test.ts +50 -0
  120. package/src/playground/expandPlaygroundControls.ts +97 -0
  121. package/src/playground/inferKitJsx.test.ts +77 -0
  122. package/src/playground/inferKitJsx.ts +165 -0
  123. package/src/playground/inferKitParams.test.ts +41 -0
  124. package/src/playground/inferKitParams.ts +113 -0
  125. package/src/playground/inferPropTypesFromTs.d.mts +47 -0
  126. package/src/playground/inferPropTypesFromTs.mjs +343 -0
  127. package/src/playground/inferPropTypesFromTs.test.ts +227 -0
  128. package/src/playground/inferPropTypesFromTs.ts +17 -0
  129. package/src/playground/mergePlaygroundEntries.test.ts +32 -0
  130. package/src/playground/mergePlaygroundEntries.ts +28 -0
  131. package/src/playground/playgroundJoin.test.ts +79 -19
  132. package/src/playground/playgroundJoin.ts +47 -22
  133. package/src/playground/playgroundModuleExport.test.ts +42 -0
  134. package/src/playground/playgroundModuleExport.ts +22 -0
  135. package/src/playground/playgroundSpecsKey.ts +8 -0
  136. package/src/playground/propCoerce.ts +91 -0
  137. package/src/playground/scanVariantA11y.test.ts +46 -0
  138. package/src/playground/scanVariantA11y.ts +107 -0
  139. package/src/playground/snippet.ts +83 -0
  140. package/src/playground/usePlaygroundFromReport.test.ts +18 -8
  141. package/src/playground/usePlaygroundFromReport.ts +3 -1
  142. package/src/report/a11yForModule.ts +2 -7
  143. package/src/report/a11yScoring.test.ts +24 -0
  144. package/src/report/a11yScoring.ts +17 -0
  145. package/src/report/index.ts +6 -0
  146. package/src/shell/DashboardLayout.tsx +71 -45
  147. package/src/shell/DashboardLayoutAuto.tsx +0 -4
  148. package/src/shell/hashRoute.test.ts +7 -15
  149. package/src/shell/hashRoute.ts +31 -31
  150. package/src/shell/useHashRoute.ts +38 -13
  151. package/src/styles/dashboard-theme.css +18 -7
  152. package/src/types/controls.ts +11 -0
  153. package/src/types/playground.ts +4 -0
  154. package/src/types/report.ts +32 -9
  155. package/templates/playground/buildRegistry.ts +1 -1
  156. package/templates/vite.dslinter.snippet.ts +15 -4
  157. package/vite/collectScanModules.test.ts +51 -3
  158. package/vite/collectScanModules.ts +85 -29
  159. package/vite/consumer.config.mjs +6 -3
  160. package/vite/consumerAlias.test.ts +47 -0
  161. package/vite/consumerAlias.ts +114 -0
  162. package/vite/embedTailwindSources.test.ts +74 -0
  163. package/vite/embedTailwindSources.ts +97 -0
  164. package/vite/loadConsumerAliases.test.ts +131 -0
  165. package/vite/loadConsumerAliases.ts +155 -0
  166. package/vite/openFileInEditor.mjs +196 -0
  167. package/vite/openFileInEditor.test.mjs +87 -0
  168. package/vite/plugin.resolve.test.ts +72 -0
  169. package/vite/plugin.ts +216 -19
  170. package/vite/reportPath.test.ts +19 -0
  171. package/vite/resolveWayfinderImport.ts +56 -0
  172. package/vite/shims/inertia-react.tsx +85 -0
  173. package/vite/shims/wayfinder-actions.ts +33 -0
  174. package/vite/shims/wayfinder-routes.ts +30 -0
  175. package/vite/shims/ziggy-js.ts +12 -0
  176. package/dashboard-dist/assets/DashboardLayoutAuto-Bm7yfyC-.css +0 -1
  177. package/dashboard-dist/assets/DashboardLayoutAuto-DgwO_itB.js +0 -1
  178. package/dashboard-dist/assets/index-Cbv7vXvH.css +0 -1
  179. package/dashboard-dist/assets/index-e20cwqnb.js +0 -206
  180. package/src/components/playgroundUsageTwoslash.ts +0 -69
  181. 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
+ }
@@ -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
+ }