@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
package/src/index.ts ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @principal-ai/principal-view-core
3
+ * Core logic and types for graph-based principal view framework
4
+ */
5
+
6
+ // Export all types
7
+ export * from './types';
8
+
9
+ // Export core classes
10
+ export { EventProcessor } from './EventProcessor';
11
+ export type { ProcessingResult } from './EventProcessor';
12
+
13
+ export { ValidationEngine } from './ValidationEngine';
14
+
15
+ export { ConfigurationValidator } from './ConfigurationValidator';
16
+ export type { ConfigurationValidationError, ConfigurationValidationResult } from './ConfigurationValidator';
17
+
18
+ // Export helpers
19
+ export { GraphInstrumentationHelper } from './helpers/GraphInstrumentationHelper';
20
+
21
+ // Export path-based processing (Milestone 1 & 2)
22
+ export { PathBasedEventProcessor } from './PathBasedEventProcessor';
23
+ export type { LogEntry } from './PathBasedEventProcessor';
24
+
25
+ // Export path utilities
26
+ export { PathMatcher } from './utils/PathMatcher';
27
+ export { GraphConverter } from './utils/GraphConverter';
28
+
29
+ // Export Canvas types and converter
30
+ export * from './types/canvas';
31
+ export { CanvasConverter } from './utils/CanvasConverter';
32
+ export type { ReactFlowNode, ReactFlowEdge } from './utils/CanvasConverter';
33
+
34
+ // Export session management (Event Recording System)
35
+ export { SessionManager } from './SessionManager';
36
+ export type {
37
+ SessionStatus,
38
+ SessionResult,
39
+ SessionMetadata,
40
+ EventSession,
41
+ CreateSessionOptions,
42
+ EndSessionOptions,
43
+ SessionChangeCallback,
44
+ SessionManagerConfig,
45
+ } from './SessionManager';
46
+
47
+ // Export event recorder service
48
+ export { EventRecorderService } from './EventRecorderService';
49
+ export type {
50
+ ProtocolMessageType,
51
+ ProtocolMessage,
52
+ SessionStartMessage,
53
+ SessionEndMessage,
54
+ LogMessage,
55
+ LogBatchMessage,
56
+ PingMessage,
57
+ PongMessage,
58
+ ErrorMessage,
59
+ AckMessage,
60
+ IncomingMessage,
61
+ OutgoingMessage,
62
+ RecordingMode,
63
+ EventCallback,
64
+ EventBatchCallback,
65
+ ConnectionState,
66
+ EventRecorderServiceConfig,
67
+ } from './EventRecorderService';
68
+
69
+ // Export configuration loading (Phase 2: Multi-config support)
70
+ export { ConfigurationLoader } from './ConfigurationLoader';
71
+ export type { ConfigurationFile, ConfigurationLoadResult } from './ConfigurationLoader';
72
+ export { parseYaml, isYamlFile, getConfigNameFromFilename } from './utils/YamlParser';
73
+ export type { YamlParseResult } from './utils/YamlParser';
74
+
75
+ // Export component library support
76
+ export { LibraryLoader } from './LibraryLoader';
77
+ export { LibraryConverter } from './utils/LibraryConverter';
78
+ export type { CreateNodeOptions, CreateEdgeOptions } from './utils/LibraryConverter';
79
+
80
+ // Re-export FileSystemAdapter from repository-abstraction
81
+ export type { FileSystemAdapter } from '@principal-ai/repository-abstraction';
82
+ export { InMemoryFileSystemAdapter } from '@principal-ai/repository-abstraction';
83
+
84
+ // Export rules engine
85
+ export * from './rules';
@@ -0,0 +1,278 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ validatePrivuConfig,
4
+ getDefaultConfig,
5
+ mergeConfigs,
6
+ formatConfigErrors,
7
+ VALID_RULE_IDS,
8
+ } from './config';
9
+ import type { PrivuConfig } from './types';
10
+
11
+ describe('validatePrivuConfig', () => {
12
+ it('should accept valid minimal config', () => {
13
+ const config = { root: true };
14
+ const result = validatePrivuConfig(config);
15
+ expect(result.valid).toBe(true);
16
+ expect(result.errors).toHaveLength(0);
17
+ });
18
+
19
+ it('should accept valid full config', () => {
20
+ const config: PrivuConfig = {
21
+ $schema: 'https://principal.ai/schemas/vgcrc.json',
22
+ root: true,
23
+ library: '.vgc/library.yaml',
24
+ include: ['.vgc/**/*.yaml'],
25
+ exclude: ['**/*.test.yaml'],
26
+ rules: {
27
+ 'required-metadata': 'error',
28
+ 'valid-color-format': 'warn',
29
+ 'orphaned-node-types': 'off',
30
+ 'minimum-node-sources': {
31
+ severity: 'error',
32
+ options: {
33
+ minimum: 1,
34
+ excludeNodeTypes: ['external'],
35
+ },
36
+ },
37
+ },
38
+ };
39
+ const result = validatePrivuConfig(config);
40
+ expect(result.valid).toBe(true);
41
+ });
42
+
43
+ it('should reject non-object config', () => {
44
+ const result = validatePrivuConfig('string');
45
+ expect(result.valid).toBe(false);
46
+ expect(result.errors[0].message).toContain('must be an object');
47
+ });
48
+
49
+ it('should reject null config', () => {
50
+ const result = validatePrivuConfig(null);
51
+ expect(result.valid).toBe(false);
52
+ });
53
+
54
+ it('should flag unknown top-level fields', () => {
55
+ const config = {
56
+ root: true,
57
+ unknownField: 'value',
58
+ };
59
+ const result = validatePrivuConfig(config);
60
+ expect(result.valid).toBe(false);
61
+ expect(result.errors.some((e) => e.message.includes('unknownField'))).toBe(true);
62
+ });
63
+
64
+ it('should validate root is boolean', () => {
65
+ const config = { root: 'yes' };
66
+ const result = validatePrivuConfig(config);
67
+ expect(result.valid).toBe(false);
68
+ expect(result.errors.some((e) => e.path === 'root')).toBe(true);
69
+ });
70
+
71
+ it('should validate library is string', () => {
72
+ const config = { library: 123 };
73
+ const result = validatePrivuConfig(config);
74
+ expect(result.valid).toBe(false);
75
+ expect(result.errors.some((e) => e.path === 'library')).toBe(true);
76
+ });
77
+
78
+ it('should validate include is array of strings', () => {
79
+ const config = { include: 'not-an-array' };
80
+ const result = validatePrivuConfig(config);
81
+ expect(result.valid).toBe(false);
82
+ expect(result.errors.some((e) => e.path === 'include')).toBe(true);
83
+ });
84
+
85
+ it('should validate extends is string or array', () => {
86
+ const validString = { extends: 'base-config' };
87
+ expect(validatePrivuConfig(validString).valid).toBe(true);
88
+
89
+ const validArray = { extends: ['config1', 'config2'] };
90
+ expect(validatePrivuConfig(validArray).valid).toBe(true);
91
+
92
+ const invalid = { extends: 123 };
93
+ expect(validatePrivuConfig(invalid).valid).toBe(false);
94
+ });
95
+
96
+ describe('rules validation', () => {
97
+ it('should accept valid rule ID', () => {
98
+ const config = {
99
+ rules: {
100
+ 'required-metadata': 'error',
101
+ },
102
+ };
103
+ const result = validatePrivuConfig(config);
104
+ expect(result.valid).toBe(true);
105
+ });
106
+
107
+ it('should flag unknown rule ID', () => {
108
+ const config = {
109
+ rules: {
110
+ 'unknown-rule': 'error',
111
+ },
112
+ };
113
+ const result = validatePrivuConfig(config);
114
+ expect(result.valid).toBe(false);
115
+ expect(result.errors.some((e) => e.message.includes('Unknown rule ID'))).toBe(true);
116
+ });
117
+
118
+ it('should suggest similar rule ID', () => {
119
+ const config = {
120
+ rules: {
121
+ 'required-metdata': 'error', // typo
122
+ },
123
+ };
124
+ const result = validatePrivuConfig(config);
125
+ expect(result.valid).toBe(false);
126
+ expect(result.errors.some((e) => e.suggestion?.includes('required-metadata'))).toBe(true);
127
+ });
128
+
129
+ it('should accept numeric severity', () => {
130
+ const config = {
131
+ rules: {
132
+ 'required-metadata': 2,
133
+ },
134
+ };
135
+ const result = validatePrivuConfig(config);
136
+ expect(result.valid).toBe(true);
137
+ });
138
+
139
+ it('should accept false to disable', () => {
140
+ const config = {
141
+ rules: {
142
+ 'required-metadata': false,
143
+ },
144
+ };
145
+ const result = validatePrivuConfig(config);
146
+ expect(result.valid).toBe(true);
147
+ });
148
+
149
+ it('should flag invalid severity', () => {
150
+ const config = {
151
+ rules: {
152
+ 'required-metadata': 'fatal',
153
+ },
154
+ };
155
+ const result = validatePrivuConfig(config);
156
+ expect(result.valid).toBe(false);
157
+ expect(result.errors.some((e) => e.message.includes('Invalid severity'))).toBe(true);
158
+ });
159
+
160
+ it('should validate rule config object', () => {
161
+ const valid = {
162
+ rules: {
163
+ 'minimum-node-sources': {
164
+ severity: 'error',
165
+ options: { minimum: 2 },
166
+ },
167
+ },
168
+ };
169
+ expect(validatePrivuConfig(valid).valid).toBe(true);
170
+ });
171
+
172
+ it('should flag unknown rule config fields', () => {
173
+ const config = {
174
+ rules: {
175
+ 'required-metadata': {
176
+ severity: 'error',
177
+ unknownField: 'value',
178
+ },
179
+ },
180
+ };
181
+ const result = validatePrivuConfig(config);
182
+ expect(result.valid).toBe(false);
183
+ expect(result.errors.some((e) => e.message.includes('unknownField'))).toBe(true);
184
+ });
185
+
186
+ it('should flag unknown rule options', () => {
187
+ const config = {
188
+ rules: {
189
+ 'minimum-node-sources': {
190
+ options: {
191
+ minumum: 2, // typo
192
+ },
193
+ },
194
+ },
195
+ };
196
+ const result = validatePrivuConfig(config);
197
+ expect(result.valid).toBe(false);
198
+ expect(result.errors.some((e) => e.suggestion?.includes('minimum'))).toBe(true);
199
+ });
200
+ });
201
+ });
202
+
203
+ describe('getDefaultConfig', () => {
204
+ it('should return default values', () => {
205
+ const config = getDefaultConfig();
206
+ expect(config.root).toBe(false);
207
+ expect(config.include).toBeDefined();
208
+ expect(config.exclude).toBeDefined();
209
+ expect(config.rules).toEqual({});
210
+ });
211
+ });
212
+
213
+ describe('mergeConfigs', () => {
214
+ it('should merge configs with later taking precedence', () => {
215
+ const base: PrivuConfig = {
216
+ root: false,
217
+ library: 'base-lib.yaml',
218
+ rules: {
219
+ 'required-metadata': 'warn',
220
+ },
221
+ };
222
+
223
+ const override: PrivuConfig = {
224
+ root: true,
225
+ rules: {
226
+ 'required-metadata': 'error',
227
+ 'valid-color-format': 'off',
228
+ },
229
+ };
230
+
231
+ const merged = mergeConfigs(base, override);
232
+ expect(merged.root).toBe(true);
233
+ expect(merged.library).toBe('base-lib.yaml');
234
+ expect(merged.rules?.['required-metadata']).toBe('error');
235
+ expect(merged.rules?.['valid-color-format']).toBe('off');
236
+ });
237
+
238
+ it('should handle undefined configs', () => {
239
+ const config: PrivuConfig = { root: true };
240
+ const merged = mergeConfigs(undefined, config, undefined);
241
+ expect(merged.root).toBe(true);
242
+ });
243
+ });
244
+
245
+ describe('formatConfigErrors', () => {
246
+ it('should format errors for display', () => {
247
+ const errors = [
248
+ { path: 'rules.unknown', message: 'Unknown rule', suggestion: 'Did you mean X?' },
249
+ { path: 'root', message: 'Must be boolean' },
250
+ ];
251
+
252
+ const output = formatConfigErrors(errors);
253
+ expect(output).toContain('rules.unknown');
254
+ expect(output).toContain('Unknown rule');
255
+ expect(output).toContain('Did you mean X?');
256
+ expect(output).toContain('2 configuration errors');
257
+ });
258
+
259
+ it('should handle single error', () => {
260
+ const errors = [{ path: 'root', message: 'Must be boolean' }];
261
+ const output = formatConfigErrors(errors);
262
+ expect(output).toContain('1 configuration error');
263
+ });
264
+ });
265
+
266
+ describe('VALID_RULE_IDS', () => {
267
+ it('should contain all 14 rule IDs', () => {
268
+ expect(VALID_RULE_IDS.length).toBe(14);
269
+ });
270
+
271
+ it('should include expected rules', () => {
272
+ expect(VALID_RULE_IDS).toContain('required-metadata');
273
+ expect(VALID_RULE_IDS).toContain('no-unknown-fields');
274
+ expect(VALID_RULE_IDS).toContain('minimum-node-sources');
275
+ expect(VALID_RULE_IDS).toContain('valid-action-patterns');
276
+ expect(VALID_RULE_IDS).toContain('library-node-type-match');
277
+ });
278
+ });