@principal-ai/principal-view-core 0.5.6

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 (204) hide show
  1. package/README.md +126 -0
  2. package/dist/ConfigurationLoader.d.ts +76 -0
  3. package/dist/ConfigurationLoader.d.ts.map +1 -0
  4. package/dist/ConfigurationLoader.js +144 -0
  5. package/dist/ConfigurationLoader.js.map +1 -0
  6. package/dist/ConfigurationValidator.d.ts +31 -0
  7. package/dist/ConfigurationValidator.d.ts.map +1 -0
  8. package/dist/ConfigurationValidator.js +242 -0
  9. package/dist/ConfigurationValidator.js.map +1 -0
  10. package/dist/EventProcessor.d.ts +49 -0
  11. package/dist/EventProcessor.d.ts.map +1 -0
  12. package/dist/EventProcessor.js +215 -0
  13. package/dist/EventProcessor.js.map +1 -0
  14. package/dist/EventRecorderService.d.ts +305 -0
  15. package/dist/EventRecorderService.d.ts.map +1 -0
  16. package/dist/EventRecorderService.js +463 -0
  17. package/dist/EventRecorderService.js.map +1 -0
  18. package/dist/LibraryLoader.d.ts +63 -0
  19. package/dist/LibraryLoader.d.ts.map +1 -0
  20. package/dist/LibraryLoader.js +188 -0
  21. package/dist/LibraryLoader.js.map +1 -0
  22. package/dist/PathBasedEventProcessor.d.ts +90 -0
  23. package/dist/PathBasedEventProcessor.d.ts.map +1 -0
  24. package/dist/PathBasedEventProcessor.js +239 -0
  25. package/dist/PathBasedEventProcessor.js.map +1 -0
  26. package/dist/SessionManager.d.ts +194 -0
  27. package/dist/SessionManager.d.ts.map +1 -0
  28. package/dist/SessionManager.js +299 -0
  29. package/dist/SessionManager.js.map +1 -0
  30. package/dist/ValidationEngine.d.ts +31 -0
  31. package/dist/ValidationEngine.d.ts.map +1 -0
  32. package/dist/ValidationEngine.js +158 -0
  33. package/dist/ValidationEngine.js.map +1 -0
  34. package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
  35. package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
  36. package/dist/helpers/GraphInstrumentationHelper.js +248 -0
  37. package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +34 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/rules/config.d.ts +57 -0
  43. package/dist/rules/config.d.ts.map +1 -0
  44. package/dist/rules/config.js +382 -0
  45. package/dist/rules/config.js.map +1 -0
  46. package/dist/rules/engine.d.ts +70 -0
  47. package/dist/rules/engine.d.ts.map +1 -0
  48. package/dist/rules/engine.js +252 -0
  49. package/dist/rules/engine.js.map +1 -0
  50. package/dist/rules/implementations/connection-type-references.d.ts +7 -0
  51. package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
  52. package/dist/rules/implementations/connection-type-references.js +104 -0
  53. package/dist/rules/implementations/connection-type-references.js.map +1 -0
  54. package/dist/rules/implementations/dead-end-states.d.ts +17 -0
  55. package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
  56. package/dist/rules/implementations/dead-end-states.js +72 -0
  57. package/dist/rules/implementations/dead-end-states.js.map +1 -0
  58. package/dist/rules/implementations/index.d.ts +24 -0
  59. package/dist/rules/implementations/index.d.ts.map +1 -0
  60. package/dist/rules/implementations/index.js +62 -0
  61. package/dist/rules/implementations/index.js.map +1 -0
  62. package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
  63. package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
  64. package/dist/rules/implementations/library-node-type-match.js +123 -0
  65. package/dist/rules/implementations/library-node-type-match.js.map +1 -0
  66. package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
  67. package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
  68. package/dist/rules/implementations/minimum-node-sources.js +54 -0
  69. package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
  70. package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
  71. package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
  72. package/dist/rules/implementations/no-unknown-fields.js +211 -0
  73. package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
  74. package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
  75. package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
  76. package/dist/rules/implementations/orphaned-edge-types.js +47 -0
  77. package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
  78. package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
  79. package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
  80. package/dist/rules/implementations/orphaned-node-types.js +50 -0
  81. package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
  82. package/dist/rules/implementations/required-metadata.d.ts +7 -0
  83. package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
  84. package/dist/rules/implementations/required-metadata.js +57 -0
  85. package/dist/rules/implementations/required-metadata.js.map +1 -0
  86. package/dist/rules/implementations/state-transition-references.d.ts +7 -0
  87. package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
  88. package/dist/rules/implementations/state-transition-references.js +135 -0
  89. package/dist/rules/implementations/state-transition-references.js.map +1 -0
  90. package/dist/rules/implementations/unreachable-states.d.ts +7 -0
  91. package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
  92. package/dist/rules/implementations/unreachable-states.js +80 -0
  93. package/dist/rules/implementations/unreachable-states.js.map +1 -0
  94. package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
  95. package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
  96. package/dist/rules/implementations/valid-action-patterns.js +109 -0
  97. package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
  98. package/dist/rules/implementations/valid-color-format.d.ts +7 -0
  99. package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
  100. package/dist/rules/implementations/valid-color-format.js +91 -0
  101. package/dist/rules/implementations/valid-color-format.js.map +1 -0
  102. package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
  103. package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
  104. package/dist/rules/implementations/valid-edge-types.js +244 -0
  105. package/dist/rules/implementations/valid-edge-types.js.map +1 -0
  106. package/dist/rules/implementations/valid-node-types.d.ts +7 -0
  107. package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
  108. package/dist/rules/implementations/valid-node-types.js +175 -0
  109. package/dist/rules/implementations/valid-node-types.js.map +1 -0
  110. package/dist/rules/index.d.ts +28 -0
  111. package/dist/rules/index.d.ts.map +1 -0
  112. package/dist/rules/index.js +45 -0
  113. package/dist/rules/index.js.map +1 -0
  114. package/dist/rules/types.d.ts +309 -0
  115. package/dist/rules/types.d.ts.map +1 -0
  116. package/dist/rules/types.js +35 -0
  117. package/dist/rules/types.js.map +1 -0
  118. package/dist/types/canvas.d.ts +409 -0
  119. package/dist/types/canvas.d.ts.map +1 -0
  120. package/dist/types/canvas.js +70 -0
  121. package/dist/types/canvas.js.map +1 -0
  122. package/dist/types/index.d.ts +311 -0
  123. package/dist/types/index.d.ts.map +1 -0
  124. package/dist/types/index.js +13 -0
  125. package/dist/types/index.js.map +1 -0
  126. package/dist/types/library.d.ts +185 -0
  127. package/dist/types/library.d.ts.map +1 -0
  128. package/dist/types/library.js +15 -0
  129. package/dist/types/library.js.map +1 -0
  130. package/dist/types/path-based-config.d.ts +230 -0
  131. package/dist/types/path-based-config.d.ts.map +1 -0
  132. package/dist/types/path-based-config.js +9 -0
  133. package/dist/types/path-based-config.js.map +1 -0
  134. package/dist/utils/CanvasConverter.d.ts +118 -0
  135. package/dist/utils/CanvasConverter.d.ts.map +1 -0
  136. package/dist/utils/CanvasConverter.js +315 -0
  137. package/dist/utils/CanvasConverter.js.map +1 -0
  138. package/dist/utils/GraphConverter.d.ts +18 -0
  139. package/dist/utils/GraphConverter.d.ts.map +1 -0
  140. package/dist/utils/GraphConverter.js +61 -0
  141. package/dist/utils/GraphConverter.js.map +1 -0
  142. package/dist/utils/LibraryConverter.d.ts +113 -0
  143. package/dist/utils/LibraryConverter.d.ts.map +1 -0
  144. package/dist/utils/LibraryConverter.js +166 -0
  145. package/dist/utils/LibraryConverter.js.map +1 -0
  146. package/dist/utils/PathMatcher.d.ts +55 -0
  147. package/dist/utils/PathMatcher.d.ts.map +1 -0
  148. package/dist/utils/PathMatcher.js +172 -0
  149. package/dist/utils/PathMatcher.js.map +1 -0
  150. package/dist/utils/YamlParser.d.ts +36 -0
  151. package/dist/utils/YamlParser.d.ts.map +1 -0
  152. package/dist/utils/YamlParser.js +63 -0
  153. package/dist/utils/YamlParser.js.map +1 -0
  154. package/package.json +47 -0
  155. package/src/ConfigurationLoader.test.ts +490 -0
  156. package/src/ConfigurationLoader.ts +185 -0
  157. package/src/ConfigurationValidator.test.ts +200 -0
  158. package/src/ConfigurationValidator.ts +283 -0
  159. package/src/EventProcessor.test.ts +405 -0
  160. package/src/EventProcessor.ts +250 -0
  161. package/src/EventRecorderService.test.ts +541 -0
  162. package/src/EventRecorderService.ts +744 -0
  163. package/src/LibraryLoader.ts +215 -0
  164. package/src/PathBasedEventProcessor.test.ts +567 -0
  165. package/src/PathBasedEventProcessor.ts +332 -0
  166. package/src/SessionManager.test.ts +424 -0
  167. package/src/SessionManager.ts +470 -0
  168. package/src/ValidationEngine.test.ts +371 -0
  169. package/src/ValidationEngine.ts +196 -0
  170. package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
  171. package/src/helpers/GraphInstrumentationHelper.ts +326 -0
  172. package/src/index.ts +85 -0
  173. package/src/rules/config.test.ts +278 -0
  174. package/src/rules/config.ts +459 -0
  175. package/src/rules/engine.test.ts +332 -0
  176. package/src/rules/engine.ts +318 -0
  177. package/src/rules/implementations/connection-type-references.ts +117 -0
  178. package/src/rules/implementations/dead-end-states.ts +101 -0
  179. package/src/rules/implementations/index.ts +73 -0
  180. package/src/rules/implementations/library-node-type-match.ts +148 -0
  181. package/src/rules/implementations/minimum-node-sources.ts +82 -0
  182. package/src/rules/implementations/no-unknown-fields.ts +342 -0
  183. package/src/rules/implementations/orphaned-edge-types.ts +55 -0
  184. package/src/rules/implementations/orphaned-node-types.ts +58 -0
  185. package/src/rules/implementations/required-metadata.ts +64 -0
  186. package/src/rules/implementations/state-transition-references.ts +151 -0
  187. package/src/rules/implementations/unreachable-states.ts +94 -0
  188. package/src/rules/implementations/valid-action-patterns.ts +136 -0
  189. package/src/rules/implementations/valid-color-format.ts +140 -0
  190. package/src/rules/implementations/valid-edge-types.ts +258 -0
  191. package/src/rules/implementations/valid-node-types.ts +189 -0
  192. package/src/rules/index.ts +95 -0
  193. package/src/rules/types.ts +426 -0
  194. package/src/types/canvas.ts +496 -0
  195. package/src/types/index.ts +382 -0
  196. package/src/types/library.ts +233 -0
  197. package/src/types/path-based-config.ts +281 -0
  198. package/src/utils/CanvasConverter.ts +431 -0
  199. package/src/utils/GraphConverter.test.ts +195 -0
  200. package/src/utils/GraphConverter.ts +71 -0
  201. package/src/utils/LibraryConverter.ts +245 -0
  202. package/src/utils/PathMatcher.test.ts +148 -0
  203. package/src/utils/PathMatcher.ts +183 -0
  204. package/src/utils/YamlParser.ts +75 -0
@@ -0,0 +1,459 @@
1
+ /**
2
+ * VGC Configuration Loader and Validator
3
+ * Handles loading and validating .vgcrc files
4
+ */
5
+
6
+ import type { PrivuConfig, RuleSeverity, RuleConfig } from './types';
7
+
8
+ // ============================================================================
9
+ // Constants
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Valid config file names in resolution order
14
+ */
15
+ export const CONFIG_FILE_NAMES = [
16
+ '.vgcrc.json',
17
+ '.vgcrc.yaml',
18
+ '.vgcrc.yml',
19
+ 'vgc.config.json',
20
+ 'vgc.config.yaml',
21
+ 'vgc.config.yml',
22
+ ] as const;
23
+
24
+ /**
25
+ * Default include patterns
26
+ */
27
+ export const DEFAULT_INCLUDE_PATTERNS = ['.vgc/**/*.yaml', '.vgc/**/*.yml', '.vgc/**/*.json'];
28
+
29
+ /**
30
+ * Default exclude patterns
31
+ */
32
+ export const DEFAULT_EXCLUDE_PATTERNS = ['**/node_modules/**', '**/*.test.*'];
33
+
34
+ /**
35
+ * Valid severity values
36
+ */
37
+ export const VALID_SEVERITIES: readonly (RuleSeverity | false)[] = [
38
+ 'off',
39
+ 'warn',
40
+ 'error',
41
+ 0,
42
+ 1,
43
+ 2,
44
+ false,
45
+ ];
46
+
47
+ /**
48
+ * All valid rule IDs
49
+ */
50
+ export const VALID_RULE_IDS = [
51
+ // Schema rules
52
+ 'no-unknown-fields',
53
+ 'required-metadata',
54
+ 'valid-node-types',
55
+ 'valid-edge-types',
56
+ 'valid-color-format',
57
+ // Reference rules
58
+ 'connection-type-references',
59
+ 'state-transition-references',
60
+ // Structure rules
61
+ 'minimum-node-sources',
62
+ 'orphaned-node-types',
63
+ 'orphaned-edge-types',
64
+ 'unreachable-states',
65
+ 'dead-end-states',
66
+ // Pattern rules
67
+ 'valid-action-patterns',
68
+ // Library rules
69
+ 'library-node-type-match',
70
+ ] as const;
71
+
72
+ export type ValidRuleId = (typeof VALID_RULE_IDS)[number];
73
+
74
+ /**
75
+ * Rule-specific valid options
76
+ */
77
+ export const RULE_OPTIONS_SCHEMA: Record<string, string[]> = {
78
+ 'minimum-node-sources': ['minimum', 'excludeNodeTypes'],
79
+ 'dead-end-states': ['terminalStates'],
80
+ 'valid-action-patterns': ['strictMode'],
81
+ 'library-node-type-match': ['allowExtra'],
82
+ };
83
+
84
+ // ============================================================================
85
+ // Validation Errors
86
+ // ============================================================================
87
+
88
+ export interface ConfigValidationError {
89
+ path: string;
90
+ message: string;
91
+ suggestion?: string;
92
+ }
93
+
94
+ export interface ConfigValidationResult {
95
+ valid: boolean;
96
+ errors: ConfigValidationError[];
97
+ config?: PrivuConfig;
98
+ }
99
+
100
+ // ============================================================================
101
+ // Validation Functions
102
+ // ============================================================================
103
+
104
+ /**
105
+ * Validate a VGC configuration object
106
+ */
107
+ export function validatePrivuConfig(config: unknown): ConfigValidationResult {
108
+ const errors: ConfigValidationError[] = [];
109
+
110
+ if (typeof config !== 'object' || config === null) {
111
+ return {
112
+ valid: false,
113
+ errors: [{ path: '', message: 'Configuration must be an object' }],
114
+ };
115
+ }
116
+
117
+ const configObj = config as Record<string, unknown>;
118
+
119
+ // Validate known top-level fields
120
+ const validTopLevelFields = [
121
+ '$schema',
122
+ 'extends',
123
+ 'root',
124
+ 'library',
125
+ 'include',
126
+ 'exclude',
127
+ 'rules',
128
+ ];
129
+
130
+ for (const key of Object.keys(configObj)) {
131
+ if (!validTopLevelFields.includes(key)) {
132
+ const suggestion = findSimilarString(key, validTopLevelFields);
133
+ errors.push({
134
+ path: key,
135
+ message: `Unknown configuration field "${key}"`,
136
+ suggestion: suggestion ? `Did you mean "${suggestion}"?` : undefined,
137
+ });
138
+ }
139
+ }
140
+
141
+ // Validate $schema
142
+ if (configObj.$schema !== undefined && typeof configObj.$schema !== 'string') {
143
+ errors.push({
144
+ path: '$schema',
145
+ message: '$schema must be a string',
146
+ });
147
+ }
148
+
149
+ // Validate extends
150
+ if (configObj.extends !== undefined) {
151
+ if (
152
+ typeof configObj.extends !== 'string' &&
153
+ !(
154
+ Array.isArray(configObj.extends) &&
155
+ configObj.extends.every((e) => typeof e === 'string')
156
+ )
157
+ ) {
158
+ errors.push({
159
+ path: 'extends',
160
+ message: 'extends must be a string or array of strings',
161
+ });
162
+ }
163
+ }
164
+
165
+ // Validate root
166
+ if (configObj.root !== undefined && typeof configObj.root !== 'boolean') {
167
+ errors.push({
168
+ path: 'root',
169
+ message: 'root must be a boolean',
170
+ });
171
+ }
172
+
173
+ // Validate library
174
+ if (configObj.library !== undefined && typeof configObj.library !== 'string') {
175
+ errors.push({
176
+ path: 'library',
177
+ message: 'library must be a string path',
178
+ });
179
+ }
180
+
181
+ // Validate include
182
+ if (configObj.include !== undefined) {
183
+ if (
184
+ !Array.isArray(configObj.include) ||
185
+ !configObj.include.every((p) => typeof p === 'string')
186
+ ) {
187
+ errors.push({
188
+ path: 'include',
189
+ message: 'include must be an array of strings',
190
+ });
191
+ }
192
+ }
193
+
194
+ // Validate exclude
195
+ if (configObj.exclude !== undefined) {
196
+ if (
197
+ !Array.isArray(configObj.exclude) ||
198
+ !configObj.exclude.every((p) => typeof p === 'string')
199
+ ) {
200
+ errors.push({
201
+ path: 'exclude',
202
+ message: 'exclude must be an array of strings',
203
+ });
204
+ }
205
+ }
206
+
207
+ // Validate rules
208
+ if (configObj.rules !== undefined) {
209
+ if (typeof configObj.rules !== 'object' || configObj.rules === null) {
210
+ errors.push({
211
+ path: 'rules',
212
+ message: 'rules must be an object',
213
+ });
214
+ } else {
215
+ const rulesErrors = validateRulesConfig(configObj.rules as Record<string, unknown>);
216
+ errors.push(...rulesErrors);
217
+ }
218
+ }
219
+
220
+ if (errors.length > 0) {
221
+ return { valid: false, errors };
222
+ }
223
+
224
+ return {
225
+ valid: true,
226
+ errors: [],
227
+ config: configObj as PrivuConfig,
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Validate the rules section of a VGC config
233
+ */
234
+ function validateRulesConfig(rules: Record<string, unknown>): ConfigValidationError[] {
235
+ const errors: ConfigValidationError[] = [];
236
+
237
+ for (const [ruleId, ruleConfig] of Object.entries(rules)) {
238
+ const path = `rules.${ruleId}`;
239
+
240
+ // Check if rule ID is valid
241
+ if (!VALID_RULE_IDS.includes(ruleId as ValidRuleId)) {
242
+ const suggestion = findSimilarString(ruleId, [...VALID_RULE_IDS]);
243
+ errors.push({
244
+ path,
245
+ message: `Unknown rule ID "${ruleId}"`,
246
+ suggestion: suggestion ? `Did you mean "${suggestion}"?` : undefined,
247
+ });
248
+ continue;
249
+ }
250
+
251
+ // Handle false (disabled)
252
+ if (ruleConfig === false) {
253
+ continue;
254
+ }
255
+
256
+ // Handle severity shorthand
257
+ if (typeof ruleConfig === 'string' || typeof ruleConfig === 'number') {
258
+ if (!isValidSeverity(ruleConfig)) {
259
+ errors.push({
260
+ path,
261
+ message: `Invalid severity "${ruleConfig}" for rule "${ruleId}"`,
262
+ suggestion: 'Allowed values: "off", "warn", "error", 0, 1, 2',
263
+ });
264
+ }
265
+ continue;
266
+ }
267
+
268
+ // Handle full config object
269
+ if (typeof ruleConfig !== 'object' || ruleConfig === null) {
270
+ errors.push({
271
+ path,
272
+ message: `Rule configuration must be a severity string, number, false, or config object`,
273
+ });
274
+ continue;
275
+ }
276
+
277
+ const ruleConfigObj = ruleConfig as Record<string, unknown>;
278
+
279
+ // Validate config object fields
280
+ const validRuleConfigFields = ['severity', 'options'];
281
+ for (const key of Object.keys(ruleConfigObj)) {
282
+ if (!validRuleConfigFields.includes(key)) {
283
+ errors.push({
284
+ path: `${path}.${key}`,
285
+ message: `Unknown rule config field "${key}"`,
286
+ suggestion: `Allowed fields: ${validRuleConfigFields.join(', ')}`,
287
+ });
288
+ }
289
+ }
290
+
291
+ // Validate severity
292
+ if (ruleConfigObj.severity !== undefined) {
293
+ if (!isValidSeverity(ruleConfigObj.severity)) {
294
+ errors.push({
295
+ path: `${path}.severity`,
296
+ message: `Invalid severity "${ruleConfigObj.severity}"`,
297
+ suggestion: 'Allowed values: "off", "warn", "error", 0, 1, 2',
298
+ });
299
+ }
300
+ }
301
+
302
+ // Validate options
303
+ if (ruleConfigObj.options !== undefined) {
304
+ if (typeof ruleConfigObj.options !== 'object' || ruleConfigObj.options === null) {
305
+ errors.push({
306
+ path: `${path}.options`,
307
+ message: 'options must be an object',
308
+ });
309
+ } else {
310
+ // Validate rule-specific options
311
+ const validOptions = RULE_OPTIONS_SCHEMA[ruleId];
312
+ if (validOptions) {
313
+ const optionsObj = ruleConfigObj.options as Record<string, unknown>;
314
+ for (const optionKey of Object.keys(optionsObj)) {
315
+ if (!validOptions.includes(optionKey)) {
316
+ const suggestion = findSimilarString(optionKey, validOptions);
317
+ errors.push({
318
+ path: `${path}.options.${optionKey}`,
319
+ message: `Unknown option "${optionKey}" for rule "${ruleId}"`,
320
+ suggestion: suggestion
321
+ ? `Did you mean "${suggestion}"?`
322
+ : `Allowed options: ${validOptions.join(', ')}`,
323
+ });
324
+ }
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ return errors;
332
+ }
333
+
334
+ /**
335
+ * Check if a value is a valid severity
336
+ */
337
+ function isValidSeverity(value: unknown): value is RuleSeverity {
338
+ return VALID_SEVERITIES.includes(value as RuleSeverity);
339
+ }
340
+
341
+ // ============================================================================
342
+ // Default Config
343
+ // ============================================================================
344
+
345
+ /**
346
+ * Get the default VGC configuration
347
+ */
348
+ export function getDefaultConfig(): PrivuConfig {
349
+ return {
350
+ root: false,
351
+ include: DEFAULT_INCLUDE_PATTERNS,
352
+ exclude: DEFAULT_EXCLUDE_PATTERNS,
353
+ rules: {},
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Merge configs with proper precedence (later configs override earlier)
359
+ */
360
+ export function mergeConfigs(...configs: (PrivuConfig | undefined)[]): PrivuConfig {
361
+ const result = getDefaultConfig();
362
+
363
+ for (const config of configs) {
364
+ if (!config) continue;
365
+
366
+ if (config.root !== undefined) result.root = config.root;
367
+ if (config.library !== undefined) result.library = config.library;
368
+ if (config.include !== undefined) result.include = config.include;
369
+ if (config.exclude !== undefined) result.exclude = config.exclude;
370
+
371
+ // Merge rules
372
+ if (config.rules) {
373
+ result.rules = { ...result.rules, ...config.rules };
374
+ }
375
+ }
376
+
377
+ return result;
378
+ }
379
+
380
+ // ============================================================================
381
+ // Utility Functions
382
+ // ============================================================================
383
+
384
+ /**
385
+ * Find a similar string from a list (for "did you mean" suggestions)
386
+ */
387
+ function findSimilarString(input: string, candidates: string[]): string | null {
388
+ const inputLower = input.toLowerCase();
389
+ let bestMatch: string | null = null;
390
+ let bestDistance = Infinity;
391
+
392
+ for (const candidate of candidates) {
393
+ const candidateLower = candidate.toLowerCase();
394
+
395
+ // Exact substring match
396
+ if (inputLower.includes(candidateLower) || candidateLower.includes(inputLower)) {
397
+ return candidate;
398
+ }
399
+
400
+ // Calculate Levenshtein distance
401
+ const distance = levenshteinDistance(inputLower, candidateLower);
402
+
403
+ // Accept if distance is small relative to string length
404
+ if (distance <= 3 && distance < bestDistance) {
405
+ bestDistance = distance;
406
+ bestMatch = candidate;
407
+ }
408
+ }
409
+
410
+ return bestMatch;
411
+ }
412
+
413
+ /**
414
+ * Calculate Levenshtein edit distance between two strings
415
+ */
416
+ function levenshteinDistance(a: string, b: string): number {
417
+ const matrix: number[][] = [];
418
+
419
+ // Initialize matrix
420
+ for (let i = 0; i <= a.length; i++) {
421
+ matrix[i] = [i];
422
+ }
423
+ for (let j = 0; j <= b.length; j++) {
424
+ matrix[0][j] = j;
425
+ }
426
+
427
+ // Fill matrix
428
+ for (let i = 1; i <= a.length; i++) {
429
+ for (let j = 1; j <= b.length; j++) {
430
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
431
+ matrix[i][j] = Math.min(
432
+ matrix[i - 1][j] + 1, // deletion
433
+ matrix[i][j - 1] + 1, // insertion
434
+ matrix[i - 1][j - 1] + cost // substitution
435
+ );
436
+ }
437
+ }
438
+
439
+ return matrix[a.length][b.length];
440
+ }
441
+
442
+ /**
443
+ * Format config validation errors for display
444
+ */
445
+ export function formatConfigErrors(errors: ConfigValidationError[]): string {
446
+ const lines: string[] = [];
447
+
448
+ for (const error of errors) {
449
+ lines.push(` error ${error.path || '(root)'}: ${error.message}`);
450
+ if (error.suggestion) {
451
+ lines.push(` → ${error.suggestion}`);
452
+ }
453
+ }
454
+
455
+ lines.push('');
456
+ lines.push(` ✖ ${errors.length} configuration error${errors.length === 1 ? '' : 's'}`);
457
+
458
+ return lines.join('\n');
459
+ }