@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,94 @@
1
+ /**
2
+ * Rule: unreachable-states
3
+ * Warns when states have no entry transitions (cannot be reached)
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
7
+
8
+ export const unreachableStates: GraphRule = {
9
+ id: 'unreachable-states',
10
+ name: 'Unreachable States',
11
+ description: 'States should have at least one entry transition or be an initial state',
12
+ impact: 'Unreachable states can never be entered and may indicate configuration errors',
13
+ severity: 'error',
14
+ category: 'structure',
15
+ enabled: true,
16
+ fixable: false,
17
+
18
+ async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
19
+ const violations: GraphRuleViolation[] = [];
20
+ const { configuration, configPath } = context;
21
+
22
+ if (!configuration.nodeTypes) {
23
+ return violations;
24
+ }
25
+
26
+ // For each nodeType with states, check if all states are reachable
27
+ for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
28
+ if (!nodeType.states || Object.keys(nodeType.states).length === 0) {
29
+ continue;
30
+ }
31
+
32
+ const stateIds = Object.keys(nodeType.states);
33
+
34
+ // If there's only one state, it's implicitly the initial state
35
+ if (stateIds.length <= 1) {
36
+ continue;
37
+ }
38
+
39
+ // Get state transitions for this nodeType
40
+ const transitions = configuration.validation?.stateTransitions?.[typeId];
41
+
42
+ if (!transitions || !Array.isArray(transitions) || transitions.length === 0) {
43
+ // No transitions defined - all states except the first are unreachable
44
+ // The first state is conventionally the initial state
45
+ for (let i = 1; i < stateIds.length; i++) {
46
+ violations.push({
47
+ ruleId: 'unreachable-states',
48
+ severity: 'error',
49
+ file: configPath,
50
+ path: `nodeTypes.${typeId}.states.${stateIds[i]}`,
51
+ message: `State "${stateIds[i]}" in node type "${typeId}" has no entry transitions`,
52
+ impact: 'This state can never be reached',
53
+ suggestion: `Add a state transition to "${stateIds[i]}" in validation.stateTransitions.${typeId}`,
54
+ fixable: false,
55
+ });
56
+ }
57
+ continue;
58
+ }
59
+
60
+ // Build set of states that can be reached (are targets of transitions)
61
+ const reachableStates = new Set<string>();
62
+
63
+ // First state is assumed to be the initial state
64
+ reachableStates.add(stateIds[0]);
65
+
66
+ // Add all states that are targets of transitions
67
+ for (const transition of transitions) {
68
+ if (Array.isArray(transition.to)) {
69
+ for (const toState of transition.to) {
70
+ reachableStates.add(toState);
71
+ }
72
+ }
73
+ }
74
+
75
+ // Check for unreachable states
76
+ for (const stateId of stateIds) {
77
+ if (!reachableStates.has(stateId)) {
78
+ violations.push({
79
+ ruleId: 'unreachable-states',
80
+ severity: 'error',
81
+ file: configPath,
82
+ path: `nodeTypes.${typeId}.states.${stateId}`,
83
+ message: `State "${stateId}" in node type "${typeId}" has no entry transitions`,
84
+ impact: 'This state can never be reached',
85
+ suggestion: `Add a state transition to "${stateId}" in validation.stateTransitions.${typeId}`,
86
+ fixable: false,
87
+ });
88
+ }
89
+ }
90
+ }
91
+
92
+ return violations;
93
+ },
94
+ };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Rule: valid-action-patterns
3
+ * Ensures action pattern regex is syntactically valid
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation, RuleOptions } from '../types';
7
+
8
+ /**
9
+ * Options for valid-action-patterns rule
10
+ */
11
+ export interface ValidActionPatternsOptions extends RuleOptions {
12
+ /**
13
+ * Enable strict mode - also validates capture group references
14
+ * @default false
15
+ */
16
+ strictMode: boolean;
17
+ }
18
+
19
+ const DEFAULT_OPTIONS: ValidActionPatternsOptions = {
20
+ strictMode: false,
21
+ };
22
+
23
+ export const validActionPatterns: GraphRule<ValidActionPatternsOptions> = {
24
+ id: 'valid-action-patterns',
25
+ name: 'Valid Action Patterns',
26
+ description: 'Action pattern regex must be syntactically valid',
27
+ impact: 'Invalid regex patterns will cause runtime errors during log processing',
28
+ severity: 'error',
29
+ category: 'pattern',
30
+ enabled: true,
31
+ fixable: false,
32
+ defaultOptions: DEFAULT_OPTIONS,
33
+
34
+ async check(context: GraphRuleContext<ValidActionPatternsOptions>): Promise<GraphRuleViolation[]> {
35
+ const violations: GraphRuleViolation[] = [];
36
+ const { configuration, configPath, options } = context;
37
+
38
+ if (!configuration.nodeTypes) {
39
+ return violations;
40
+ }
41
+
42
+ for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
43
+ // Check if nodeType has actions (from path-based config extension)
44
+ const nodeTypeAny = nodeType as unknown as Record<string, unknown>;
45
+ const actions = nodeTypeAny.actions;
46
+
47
+ if (!Array.isArray(actions)) {
48
+ continue;
49
+ }
50
+
51
+ for (let i = 0; i < actions.length; i++) {
52
+ const action = actions[i] as Record<string, unknown>;
53
+ const pattern = action.pattern;
54
+ const basePath = `nodeTypes.${typeId}.actions[${i}]`;
55
+
56
+ if (typeof pattern !== 'string') {
57
+ violations.push({
58
+ ruleId: 'valid-action-patterns',
59
+ severity: 'error',
60
+ file: configPath,
61
+ path: `${basePath}.pattern`,
62
+ message: `Action pattern must be a string`,
63
+ impact: 'Pattern matching will fail',
64
+ suggestion: 'Provide a valid regex pattern string',
65
+ fixable: false,
66
+ });
67
+ continue;
68
+ }
69
+
70
+ // Test if regex is valid
71
+ try {
72
+ new RegExp(pattern);
73
+ } catch (error) {
74
+ const errorMessage = error instanceof Error ? error.message : String(error);
75
+ violations.push({
76
+ ruleId: 'valid-action-patterns',
77
+ severity: 'error',
78
+ file: configPath,
79
+ path: `${basePath}.pattern`,
80
+ message: `Invalid regex pattern "${pattern}": ${errorMessage}`,
81
+ impact: 'Pattern matching will fail at runtime',
82
+ suggestion: 'Fix the regex syntax error',
83
+ fixable: false,
84
+ });
85
+ continue;
86
+ }
87
+
88
+ // In strict mode, validate capture group references in metadata
89
+ if (options.strictMode) {
90
+ const metadata = action.metadata as Record<string, unknown> | undefined;
91
+ if (metadata) {
92
+ // Extract capture group names from pattern
93
+ const captureGroupNames = extractCaptureGroupNames(pattern);
94
+
95
+ for (const [metaKey, metaValue] of Object.entries(metadata)) {
96
+ if (typeof metaValue === 'string' && metaValue.startsWith('$')) {
97
+ const groupName = metaValue.slice(1);
98
+ if (!captureGroupNames.has(groupName)) {
99
+ violations.push({
100
+ ruleId: 'valid-action-patterns',
101
+ severity: 'error',
102
+ file: configPath,
103
+ path: `${basePath}.metadata.${metaKey}`,
104
+ message: `Metadata references undefined capture group "$${groupName}"`,
105
+ impact: 'Metadata extraction will fail',
106
+ suggestion: `Available capture groups: ${[...captureGroupNames].join(', ') || '(none)'}`,
107
+ fixable: false,
108
+ });
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ return violations;
118
+ },
119
+ };
120
+
121
+ /**
122
+ * Extract named capture group names from a regex pattern
123
+ */
124
+ function extractCaptureGroupNames(pattern: string): Set<string> {
125
+ const names = new Set<string>();
126
+
127
+ // Match named capture groups: (?<name>...)
128
+ const namedGroupRegex = /\(\?<([a-zA-Z_][a-zA-Z0-9_]*)>/g;
129
+ let match;
130
+
131
+ while ((match = namedGroupRegex.exec(pattern)) !== null) {
132
+ names.add(match[1]);
133
+ }
134
+
135
+ return names;
136
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Rule: valid-color-format
3
+ * Ensures all color values are valid hex codes
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
7
+
8
+ /**
9
+ * Regex for valid hex color codes (#RGB or #RRGGBB)
10
+ */
11
+ const HEX_COLOR_REGEX = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
12
+
13
+ export const validColorFormat: GraphRule = {
14
+ id: 'valid-color-format',
15
+ name: 'Valid Color Format',
16
+ description: 'Color values must be valid hex codes (#RGB or #RRGGBB)',
17
+ impact: 'Invalid colors may render incorrectly or cause visualization errors',
18
+ severity: 'error',
19
+ category: 'schema',
20
+ enabled: true,
21
+ fixable: true,
22
+
23
+ async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
24
+ const violations: GraphRuleViolation[] = [];
25
+ const { configuration, configPath } = context;
26
+
27
+ // Check nodeTypes colors
28
+ if (configuration.nodeTypes) {
29
+ for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
30
+ // Check node color
31
+ if (nodeType.color && !isValidColor(nodeType.color)) {
32
+ violations.push(createColorViolation(
33
+ configPath,
34
+ `nodeTypes.${typeId}.color`,
35
+ nodeType.color,
36
+ `node type "${typeId}"`
37
+ ));
38
+ }
39
+
40
+ // Check node stroke color
41
+ if (nodeType.stroke && !isValidColor(nodeType.stroke)) {
42
+ violations.push(createColorViolation(
43
+ configPath,
44
+ `nodeTypes.${typeId}.stroke`,
45
+ nodeType.stroke,
46
+ `node type "${typeId}" stroke`
47
+ ));
48
+ }
49
+
50
+ // Check state colors
51
+ if (nodeType.states) {
52
+ for (const [stateName, stateDef] of Object.entries(nodeType.states)) {
53
+ if (stateDef.color && !isValidColor(stateDef.color)) {
54
+ violations.push(createColorViolation(
55
+ configPath,
56
+ `nodeTypes.${typeId}.states.${stateName}.color`,
57
+ stateDef.color,
58
+ `state "${stateName}" in node type "${typeId}"`
59
+ ));
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ // Check edgeTypes colors
67
+ if (configuration.edgeTypes) {
68
+ for (const [typeId, edgeType] of Object.entries(configuration.edgeTypes)) {
69
+ // Check edge color
70
+ if (edgeType.color && !isValidColor(edgeType.color)) {
71
+ violations.push(createColorViolation(
72
+ configPath,
73
+ `edgeTypes.${typeId}.color`,
74
+ edgeType.color,
75
+ `edge type "${typeId}"`
76
+ ));
77
+ }
78
+
79
+ // Check animation color
80
+ if (edgeType.animation?.color && !isValidColor(edgeType.animation.color)) {
81
+ violations.push(createColorViolation(
82
+ configPath,
83
+ `edgeTypes.${typeId}.animation.color`,
84
+ edgeType.animation.color,
85
+ `animation in edge type "${typeId}"`
86
+ ));
87
+ }
88
+ }
89
+ }
90
+
91
+ // Check display theme colors
92
+ if (configuration.display?.theme) {
93
+ const theme = configuration.display.theme;
94
+ const themeColors = ['primary', 'success', 'warning', 'danger', 'info'] as const;
95
+
96
+ for (const colorKey of themeColors) {
97
+ const colorValue = theme[colorKey];
98
+ if (colorValue && !isValidColor(colorValue)) {
99
+ violations.push(createColorViolation(
100
+ configPath,
101
+ `display.theme.${colorKey}`,
102
+ colorValue,
103
+ `theme ${colorKey} color`
104
+ ));
105
+ }
106
+ }
107
+ }
108
+
109
+ return violations;
110
+ },
111
+ };
112
+
113
+ /**
114
+ * Check if a color value is valid
115
+ */
116
+ function isValidColor(color: string): boolean {
117
+ return HEX_COLOR_REGEX.test(color);
118
+ }
119
+
120
+ /**
121
+ * Create a color format violation
122
+ */
123
+ function createColorViolation(
124
+ configPath: string | undefined,
125
+ path: string,
126
+ value: string,
127
+ description: string
128
+ ): GraphRuleViolation {
129
+ return {
130
+ ruleId: 'valid-color-format',
131
+ severity: 'error',
132
+ file: configPath,
133
+ path,
134
+ message: `Invalid color format "${value}" for ${description}`,
135
+ impact: 'Color may not render correctly',
136
+ suggestion: 'Use hex format: #RGB or #RRGGBB (e.g., #3b82f6 or #f00)',
137
+ fixable: true,
138
+ fixData: { path, value, description },
139
+ };
140
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Rule: valid-edge-types
3
+ * Ensures edgeTypes have required fields and valid values
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
7
+
8
+ /**
9
+ * Valid style values for edges
10
+ */
11
+ const VALID_STYLES = ['solid', 'dashed', 'dotted', 'animated'] as const;
12
+
13
+ /**
14
+ * Valid animation types
15
+ */
16
+ const VALID_ANIMATION_TYPES = ['flow', 'pulse', 'particle', 'glow'] as const;
17
+
18
+ /**
19
+ * Valid label positions
20
+ */
21
+ const VALID_LABEL_POSITIONS = ['start', 'middle', 'end'] as const;
22
+
23
+ /**
24
+ * Valid data schema field types
25
+ */
26
+ const VALID_DATA_TYPES = ['string', 'number', 'boolean', 'object', 'array'] as const;
27
+
28
+ export const validEdgeTypes: GraphRule = {
29
+ id: 'valid-edge-types',
30
+ name: 'Valid Edge Types',
31
+ description: 'edgeTypes must have required fields and valid values',
32
+ impact: 'Invalid edge types will cause rendering errors or unexpected behavior',
33
+ severity: 'error',
34
+ category: 'schema',
35
+ enabled: true,
36
+ fixable: false,
37
+
38
+ async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
39
+ const violations: GraphRuleViolation[] = [];
40
+ const { configuration, configPath } = context;
41
+
42
+ if (!configuration.edgeTypes) {
43
+ violations.push({
44
+ ruleId: 'valid-edge-types',
45
+ severity: 'error',
46
+ file: configPath,
47
+ path: 'edgeTypes',
48
+ message: 'Configuration is missing edgeTypes section',
49
+ impact: 'No edges can be defined without edgeTypes',
50
+ suggestion: 'Add an edgeTypes section with at least one edge type definition',
51
+ fixable: false,
52
+ });
53
+ return violations;
54
+ }
55
+
56
+ if (Object.keys(configuration.edgeTypes).length === 0) {
57
+ violations.push({
58
+ ruleId: 'valid-edge-types',
59
+ severity: 'error',
60
+ file: configPath,
61
+ path: 'edgeTypes',
62
+ message: 'edgeTypes section is empty',
63
+ impact: 'No edges can be defined without at least one edge type',
64
+ suggestion: 'Add at least one edge type definition',
65
+ fixable: false,
66
+ });
67
+ return violations;
68
+ }
69
+
70
+ for (const [typeId, edgeType] of Object.entries(configuration.edgeTypes)) {
71
+ const basePath = `edgeTypes.${typeId}`;
72
+
73
+ // Check required style field
74
+ if (!edgeType.style) {
75
+ violations.push({
76
+ ruleId: 'valid-edge-types',
77
+ severity: 'error',
78
+ file: configPath,
79
+ path: `${basePath}.style`,
80
+ message: `Edge type "${typeId}" is missing required "style" field`,
81
+ impact: 'Edge cannot be rendered without a style',
82
+ suggestion: `Add a style field. Valid values: ${VALID_STYLES.join(', ')}`,
83
+ fixable: false,
84
+ });
85
+ } else if (!VALID_STYLES.includes(edgeType.style as (typeof VALID_STYLES)[number])) {
86
+ violations.push({
87
+ ruleId: 'valid-edge-types',
88
+ severity: 'error',
89
+ file: configPath,
90
+ path: `${basePath}.style`,
91
+ message: `Edge type "${typeId}" has invalid style "${edgeType.style}"`,
92
+ impact: 'Edge may not render correctly',
93
+ suggestion: `Valid styles: ${VALID_STYLES.join(', ')}`,
94
+ fixable: false,
95
+ });
96
+ }
97
+
98
+ // Check width is a number if provided
99
+ if (edgeType.width !== undefined && typeof edgeType.width !== 'number') {
100
+ violations.push({
101
+ ruleId: 'valid-edge-types',
102
+ severity: 'error',
103
+ file: configPath,
104
+ path: `${basePath}.width`,
105
+ message: `Edge type "${typeId}" width must be a number`,
106
+ impact: 'Edge width may not render correctly',
107
+ suggestion: 'Provide width as a number (e.g., 2)',
108
+ fixable: false,
109
+ });
110
+ }
111
+
112
+ // Check directed is a boolean if provided
113
+ if (edgeType.directed !== undefined && typeof edgeType.directed !== 'boolean') {
114
+ violations.push({
115
+ ruleId: 'valid-edge-types',
116
+ severity: 'error',
117
+ file: configPath,
118
+ path: `${basePath}.directed`,
119
+ message: `Edge type "${typeId}" directed must be a boolean`,
120
+ impact: 'Edge direction may not work correctly',
121
+ suggestion: 'Provide directed as true or false',
122
+ fixable: false,
123
+ });
124
+ }
125
+
126
+ // Check animated is a boolean if provided
127
+ if (edgeType.animated !== undefined && typeof edgeType.animated !== 'boolean') {
128
+ violations.push({
129
+ ruleId: 'valid-edge-types',
130
+ severity: 'error',
131
+ file: configPath,
132
+ path: `${basePath}.animated`,
133
+ message: `Edge type "${typeId}" animated must be a boolean`,
134
+ impact: 'Edge animation may not work correctly',
135
+ suggestion: 'Provide animated as true or false',
136
+ fixable: false,
137
+ });
138
+ }
139
+
140
+ // Check label configuration
141
+ if (edgeType.label) {
142
+ if (typeof edgeType.label !== 'object' || edgeType.label === null) {
143
+ violations.push({
144
+ ruleId: 'valid-edge-types',
145
+ severity: 'error',
146
+ file: configPath,
147
+ path: `${basePath}.label`,
148
+ message: `Edge type "${typeId}" label must be an object`,
149
+ impact: 'Edge label configuration is invalid',
150
+ suggestion: 'Provide label as { field?: string, position?: "start" | "middle" | "end" }',
151
+ fixable: false,
152
+ });
153
+ } else {
154
+ if (edgeType.label.position !== undefined) {
155
+ if (!VALID_LABEL_POSITIONS.includes(edgeType.label.position as (typeof VALID_LABEL_POSITIONS)[number])) {
156
+ violations.push({
157
+ ruleId: 'valid-edge-types',
158
+ severity: 'error',
159
+ file: configPath,
160
+ path: `${basePath}.label.position`,
161
+ message: `Edge type "${typeId}" has invalid label position "${edgeType.label.position}"`,
162
+ impact: 'Label may not appear in expected position',
163
+ suggestion: `Valid positions: ${VALID_LABEL_POSITIONS.join(', ')}`,
164
+ fixable: false,
165
+ });
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Check animation configuration
172
+ if (edgeType.animation) {
173
+ if (typeof edgeType.animation !== 'object' || edgeType.animation === null) {
174
+ violations.push({
175
+ ruleId: 'valid-edge-types',
176
+ severity: 'error',
177
+ file: configPath,
178
+ path: `${basePath}.animation`,
179
+ message: `Edge type "${typeId}" animation must be an object`,
180
+ impact: 'Edge animation configuration is invalid',
181
+ suggestion: 'Provide animation as { type: "flow" | "pulse" | "particle" | "glow", duration?: number }',
182
+ fixable: false,
183
+ });
184
+ } else {
185
+ // Check animation type
186
+ if (!edgeType.animation.type) {
187
+ violations.push({
188
+ ruleId: 'valid-edge-types',
189
+ severity: 'error',
190
+ file: configPath,
191
+ path: `${basePath}.animation.type`,
192
+ message: `Edge type "${typeId}" animation is missing required "type" field`,
193
+ impact: 'Animation cannot run without a type',
194
+ suggestion: `Add a type field. Valid values: ${VALID_ANIMATION_TYPES.join(', ')}`,
195
+ fixable: false,
196
+ });
197
+ } else if (!VALID_ANIMATION_TYPES.includes(edgeType.animation.type as (typeof VALID_ANIMATION_TYPES)[number])) {
198
+ violations.push({
199
+ ruleId: 'valid-edge-types',
200
+ severity: 'error',
201
+ file: configPath,
202
+ path: `${basePath}.animation.type`,
203
+ message: `Edge type "${typeId}" has invalid animation type "${edgeType.animation.type}"`,
204
+ impact: 'Animation may not work correctly',
205
+ suggestion: `Valid animation types: ${VALID_ANIMATION_TYPES.join(', ')}`,
206
+ fixable: false,
207
+ });
208
+ }
209
+
210
+ // Check animation duration
211
+ if (edgeType.animation.duration !== undefined && typeof edgeType.animation.duration !== 'number') {
212
+ violations.push({
213
+ ruleId: 'valid-edge-types',
214
+ severity: 'error',
215
+ file: configPath,
216
+ path: `${basePath}.animation.duration`,
217
+ message: `Edge type "${typeId}" animation duration must be a number`,
218
+ impact: 'Animation timing may not work correctly',
219
+ suggestion: 'Provide duration as a number in milliseconds',
220
+ fixable: false,
221
+ });
222
+ }
223
+ }
224
+ }
225
+
226
+ // Check dataSchema field types
227
+ if (edgeType.dataSchema) {
228
+ for (const [fieldName, fieldDef] of Object.entries(edgeType.dataSchema)) {
229
+ if (!fieldDef.type) {
230
+ violations.push({
231
+ ruleId: 'valid-edge-types',
232
+ severity: 'error',
233
+ file: configPath,
234
+ path: `${basePath}.dataSchema.${fieldName}.type`,
235
+ message: `Data schema field "${fieldName}" in edge type "${typeId}" is missing "type"`,
236
+ impact: 'Data validation will not work for this field',
237
+ suggestion: `Add a type field. Valid values: ${VALID_DATA_TYPES.join(', ')}`,
238
+ fixable: false,
239
+ });
240
+ } else if (!VALID_DATA_TYPES.includes(fieldDef.type as (typeof VALID_DATA_TYPES)[number])) {
241
+ violations.push({
242
+ ruleId: 'valid-edge-types',
243
+ severity: 'error',
244
+ file: configPath,
245
+ path: `${basePath}.dataSchema.${fieldName}.type`,
246
+ message: `Invalid data type "${fieldDef.type}" for field "${fieldName}" in edge type "${typeId}"`,
247
+ impact: 'Data validation may fail',
248
+ suggestion: `Valid types: ${VALID_DATA_TYPES.join(', ')}`,
249
+ fixable: false,
250
+ });
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ return violations;
257
+ },
258
+ };