@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,117 @@
1
+ /**
2
+ * Rule: connection-type-references
3
+ * Ensures allowedConnections only reference existing nodeTypes and edgeTypes
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
7
+
8
+ export const connectionTypeReferences: GraphRule = {
9
+ id: 'connection-type-references',
10
+ name: 'Connection Type References',
11
+ description: 'allowedConnections must reference existing nodeTypes and edgeTypes',
12
+ impact: 'Invalid references will cause connection validation to fail',
13
+ severity: 'error',
14
+ category: 'reference',
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.allowedConnections || configuration.allowedConnections.length === 0) {
23
+ return violations;
24
+ }
25
+
26
+ // Get defined node and edge types
27
+ const nodeTypeIds = new Set(Object.keys(configuration.nodeTypes ?? {}));
28
+ const edgeTypeIds = new Set(Object.keys(configuration.edgeTypes ?? {}));
29
+
30
+ for (let i = 0; i < configuration.allowedConnections.length; i++) {
31
+ const connection = configuration.allowedConnections[i];
32
+ const basePath = `allowedConnections[${i}]`;
33
+
34
+ // Check 'from' references a valid nodeType
35
+ if (connection.from && !nodeTypeIds.has(connection.from)) {
36
+ const suggestion = findSimilar(connection.from, nodeTypeIds);
37
+ violations.push({
38
+ ruleId: 'connection-type-references',
39
+ severity: 'error',
40
+ file: configPath,
41
+ path: `${basePath}.from`,
42
+ message: `Connection rule references undefined nodeType "${connection.from}"`,
43
+ impact: 'This connection rule will not match any nodes',
44
+ suggestion: suggestion
45
+ ? `Did you mean "${suggestion}"?`
46
+ : `Available nodeTypes: ${[...nodeTypeIds].join(', ') || '(none defined)'}`,
47
+ fixable: false,
48
+ });
49
+ }
50
+
51
+ // Check 'to' references a valid nodeType
52
+ if (connection.to && !nodeTypeIds.has(connection.to)) {
53
+ const suggestion = findSimilar(connection.to, nodeTypeIds);
54
+ violations.push({
55
+ ruleId: 'connection-type-references',
56
+ severity: 'error',
57
+ file: configPath,
58
+ path: `${basePath}.to`,
59
+ message: `Connection rule references undefined nodeType "${connection.to}"`,
60
+ impact: 'This connection rule will not match any nodes',
61
+ suggestion: suggestion
62
+ ? `Did you mean "${suggestion}"?`
63
+ : `Available nodeTypes: ${[...nodeTypeIds].join(', ') || '(none defined)'}`,
64
+ fixable: false,
65
+ });
66
+ }
67
+
68
+ // Check 'via' references a valid edgeType
69
+ if (connection.via && !edgeTypeIds.has(connection.via)) {
70
+ const suggestion = findSimilar(connection.via, edgeTypeIds);
71
+ violations.push({
72
+ ruleId: 'connection-type-references',
73
+ severity: 'error',
74
+ file: configPath,
75
+ path: `${basePath}.via`,
76
+ message: `Connection rule references undefined edgeType "${connection.via}"`,
77
+ impact: 'This connection rule will not match any edges',
78
+ suggestion: suggestion
79
+ ? `Did you mean "${suggestion}"?`
80
+ : `Available edgeTypes: ${[...edgeTypeIds].join(', ') || '(none defined)'}`,
81
+ fixable: false,
82
+ });
83
+ }
84
+ }
85
+
86
+ return violations;
87
+ },
88
+ };
89
+
90
+ /**
91
+ * Find a similar string from a set (for "did you mean" suggestions)
92
+ */
93
+ function findSimilar(input: string, candidates: Set<string>): string | null {
94
+ const inputLower = input.toLowerCase();
95
+
96
+ for (const candidate of candidates) {
97
+ const candidateLower = candidate.toLowerCase();
98
+
99
+ // Exact substring match
100
+ if (inputLower.includes(candidateLower) || candidateLower.includes(inputLower)) {
101
+ return candidate;
102
+ }
103
+
104
+ // Small edit distance
105
+ if (Math.abs(input.length - candidate.length) <= 2) {
106
+ let differences = 0;
107
+ const minLen = Math.min(inputLower.length, candidateLower.length);
108
+ for (let i = 0; i < minLen; i++) {
109
+ if (inputLower[i] !== candidateLower[i]) differences++;
110
+ }
111
+ differences += Math.abs(input.length - candidate.length);
112
+ if (differences <= 2) return candidate;
113
+ }
114
+ }
115
+
116
+ return null;
117
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Rule: dead-end-states
3
+ * Warns when states have no exit transitions (except terminal states)
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation, RuleOptions } from '../types';
7
+
8
+ /**
9
+ * Options for dead-end-states rule
10
+ */
11
+ export interface DeadEndStatesOptions extends RuleOptions {
12
+ /**
13
+ * States that are intentionally terminal (no exits expected)
14
+ * @default ["completed", "failed", "terminated", "done", "error", "finished", "end"]
15
+ */
16
+ terminalStates: string[];
17
+ }
18
+
19
+ const DEFAULT_OPTIONS: DeadEndStatesOptions = {
20
+ terminalStates: ['completed', 'failed', 'terminated', 'done', 'error', 'finished', 'end'],
21
+ };
22
+
23
+ export const deadEndStates: GraphRule<DeadEndStatesOptions> = {
24
+ id: 'dead-end-states',
25
+ name: 'Dead End States',
26
+ description: 'States should have exit transitions unless they are terminal states',
27
+ impact: 'Dead-end states may trap entities unexpectedly',
28
+ severity: 'error',
29
+ category: 'structure',
30
+ enabled: true,
31
+ fixable: false,
32
+ defaultOptions: DEFAULT_OPTIONS,
33
+
34
+ async check(context: GraphRuleContext<DeadEndStatesOptions>): Promise<GraphRuleViolation[]> {
35
+ const violations: GraphRuleViolation[] = [];
36
+ const { configuration, configPath, options } = context;
37
+
38
+ const terminalStates = new Set(
39
+ (options.terminalStates ?? DEFAULT_OPTIONS.terminalStates).map((s) => s.toLowerCase())
40
+ );
41
+
42
+ if (!configuration.nodeTypes) {
43
+ return violations;
44
+ }
45
+
46
+ // For each nodeType with states, check if all non-terminal states have exits
47
+ for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
48
+ if (!nodeType.states || Object.keys(nodeType.states).length === 0) {
49
+ continue;
50
+ }
51
+
52
+ const stateIds = Object.keys(nodeType.states);
53
+
54
+ // If there's only one state, no transitions expected
55
+ if (stateIds.length <= 1) {
56
+ continue;
57
+ }
58
+
59
+ // Get state transitions for this nodeType
60
+ const transitions = configuration.validation?.stateTransitions?.[typeId];
61
+
62
+ // Build set of states that have exit transitions
63
+ const statesWithExits = new Set<string>();
64
+
65
+ if (transitions && Array.isArray(transitions)) {
66
+ for (const transition of transitions) {
67
+ if (transition.from) {
68
+ statesWithExits.add(transition.from);
69
+ }
70
+ }
71
+ }
72
+
73
+ // Check for dead-end states
74
+ for (const stateId of stateIds) {
75
+ // Skip if this state has exits
76
+ if (statesWithExits.has(stateId)) {
77
+ continue;
78
+ }
79
+
80
+ // Skip if this is a known terminal state
81
+ if (terminalStates.has(stateId.toLowerCase())) {
82
+ continue;
83
+ }
84
+
85
+ // This is a dead-end state
86
+ violations.push({
87
+ ruleId: 'dead-end-states',
88
+ severity: 'error',
89
+ file: configPath,
90
+ path: `nodeTypes.${typeId}.states.${stateId}`,
91
+ message: `State "${stateId}" in node type "${typeId}" has no exit transitions`,
92
+ impact: 'Entities in this state cannot transition to other states',
93
+ suggestion: `Add a transition from "${stateId}" in validation.stateTransitions.${typeId}, or mark it as a terminal state`,
94
+ fixable: false,
95
+ });
96
+ }
97
+ }
98
+
99
+ return violations;
100
+ },
101
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Built-in rule implementations
3
+ */
4
+
5
+ // Schema rules
6
+ export { requiredMetadata } from './required-metadata';
7
+ export { validNodeTypes } from './valid-node-types';
8
+ export { validEdgeTypes } from './valid-edge-types';
9
+ export { validColorFormat } from './valid-color-format';
10
+ export { noUnknownFields } from './no-unknown-fields';
11
+
12
+ // Reference rules
13
+ export { connectionTypeReferences } from './connection-type-references';
14
+ export { stateTransitionReferences } from './state-transition-references';
15
+
16
+ // Structure rules
17
+ export { minimumNodeSources, type MinimumNodeSourcesOptions } from './minimum-node-sources';
18
+ export { orphanedNodeTypes } from './orphaned-node-types';
19
+ export { orphanedEdgeTypes } from './orphaned-edge-types';
20
+ export { unreachableStates } from './unreachable-states';
21
+ export { deadEndStates, type DeadEndStatesOptions } from './dead-end-states';
22
+
23
+ // Pattern rules
24
+ export { validActionPatterns, type ValidActionPatternsOptions } from './valid-action-patterns';
25
+
26
+ // Library rules
27
+ export { libraryNodeTypeMatch, type LibraryNodeTypeMatchOptions } from './library-node-type-match';
28
+
29
+ import type { GraphRule } from '../types';
30
+ import { requiredMetadata } from './required-metadata';
31
+ import { validNodeTypes } from './valid-node-types';
32
+ import { validEdgeTypes } from './valid-edge-types';
33
+ import { validColorFormat } from './valid-color-format';
34
+ import { noUnknownFields } from './no-unknown-fields';
35
+ import { connectionTypeReferences } from './connection-type-references';
36
+ import { stateTransitionReferences } from './state-transition-references';
37
+ import { minimumNodeSources } from './minimum-node-sources';
38
+ import { orphanedNodeTypes } from './orphaned-node-types';
39
+ import { orphanedEdgeTypes } from './orphaned-edge-types';
40
+ import { unreachableStates } from './unreachable-states';
41
+ import { deadEndStates } from './dead-end-states';
42
+ import { validActionPatterns } from './valid-action-patterns';
43
+ import { libraryNodeTypeMatch } from './library-node-type-match';
44
+
45
+ /**
46
+ * All built-in rules
47
+ * Cast to GraphRule[] since rules with specific options extend the base interface
48
+ */
49
+ export const builtinRules: GraphRule[] = [
50
+ // Schema rules (5)
51
+ noUnknownFields,
52
+ requiredMetadata,
53
+ validNodeTypes,
54
+ validEdgeTypes,
55
+ validColorFormat,
56
+
57
+ // Reference rules (2)
58
+ connectionTypeReferences,
59
+ stateTransitionReferences,
60
+
61
+ // Structure rules (5)
62
+ minimumNodeSources as unknown as GraphRule,
63
+ orphanedNodeTypes,
64
+ orphanedEdgeTypes,
65
+ unreachableStates,
66
+ deadEndStates as unknown as GraphRule,
67
+
68
+ // Pattern rules (1)
69
+ validActionPatterns as unknown as GraphRule,
70
+
71
+ // Library rules (1)
72
+ libraryNodeTypeMatch as unknown as GraphRule,
73
+ ];
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Rule: library-node-type-match
3
+ * Ensures nodeTypes match library nodeComponents when a library is provided
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation, RuleOptions } from '../types';
7
+
8
+ /**
9
+ * Options for library-node-type-match rule
10
+ */
11
+ export interface LibraryNodeTypeMatchOptions extends RuleOptions {
12
+ /**
13
+ * Allow extra nodeTypes not defined in the library
14
+ * @default false
15
+ */
16
+ allowExtra: boolean;
17
+ }
18
+
19
+ const DEFAULT_OPTIONS: LibraryNodeTypeMatchOptions = {
20
+ allowExtra: false,
21
+ };
22
+
23
+ export const libraryNodeTypeMatch: GraphRule<LibraryNodeTypeMatchOptions> = {
24
+ id: 'library-node-type-match',
25
+ name: 'Library Node Type Match',
26
+ description: 'NodeTypes must match library nodeComponents when a library is provided',
27
+ impact: 'Mismatched types may cause inconsistent behavior or missing features',
28
+ severity: 'error',
29
+ category: 'library',
30
+ enabled: true,
31
+ fixable: false,
32
+ defaultOptions: DEFAULT_OPTIONS,
33
+
34
+ async check(context: GraphRuleContext<LibraryNodeTypeMatchOptions>): Promise<GraphRuleViolation[]> {
35
+ const violations: GraphRuleViolation[] = [];
36
+ const { configuration, library, configPath, libraryPath, options } = context;
37
+
38
+ // Skip if no library is provided
39
+ if (!library) {
40
+ return violations;
41
+ }
42
+
43
+ // Skip if library has no nodeComponents
44
+ if (!library.nodeComponents || Object.keys(library.nodeComponents).length === 0) {
45
+ return violations;
46
+ }
47
+
48
+ const libraryNodeTypes = new Set(Object.keys(library.nodeComponents));
49
+ const configNodeTypes = Object.keys(configuration.nodeTypes ?? {});
50
+
51
+ // Check each nodeType in configuration
52
+ for (const typeId of configNodeTypes) {
53
+ if (!libraryNodeTypes.has(typeId)) {
54
+ if (!options.allowExtra) {
55
+ const suggestion = findSimilar(typeId, libraryNodeTypes);
56
+ violations.push({
57
+ ruleId: 'library-node-type-match',
58
+ severity: 'error',
59
+ file: configPath,
60
+ path: `nodeTypes.${typeId}`,
61
+ message: `Node type "${typeId}" is not defined in the component library`,
62
+ impact: 'This node type may be missing library-defined behavior or styling',
63
+ suggestion: suggestion
64
+ ? `Did you mean "${suggestion}"?`
65
+ : `Available library nodeComponents: ${[...libraryNodeTypes].join(', ')}`,
66
+ fixable: false,
67
+ });
68
+ }
69
+ } else {
70
+ // Validate that config matches library definition
71
+ const libraryComponent = library.nodeComponents[typeId];
72
+ const configNodeType = configuration.nodeTypes![typeId];
73
+
74
+ // Check shape matches if library defines one
75
+ if (libraryComponent.shape && configNodeType.shape !== libraryComponent.shape) {
76
+ violations.push({
77
+ ruleId: 'library-node-type-match',
78
+ severity: 'error',
79
+ file: configPath,
80
+ path: `nodeTypes.${typeId}.shape`,
81
+ message: `Node type "${typeId}" shape "${configNodeType.shape}" doesn't match library definition "${libraryComponent.shape}"`,
82
+ impact: 'Node may render differently than expected',
83
+ suggestion: `Use shape "${libraryComponent.shape}" as defined in the library`,
84
+ fixable: true,
85
+ fixData: {
86
+ typeId,
87
+ field: 'shape',
88
+ expected: libraryComponent.shape,
89
+ actual: configNodeType.shape,
90
+ },
91
+ });
92
+ }
93
+
94
+ // Check that config defines all states from library
95
+ if (libraryComponent.states && Object.keys(libraryComponent.states).length > 0) {
96
+ const libraryStates = Object.keys(libraryComponent.states);
97
+ const configStates = configNodeType.states ? Object.keys(configNodeType.states) : [];
98
+
99
+ for (const stateId of libraryStates) {
100
+ if (!configStates.includes(stateId)) {
101
+ violations.push({
102
+ ruleId: 'library-node-type-match',
103
+ severity: 'error',
104
+ file: configPath,
105
+ path: `nodeTypes.${typeId}.states`,
106
+ message: `Node type "${typeId}" is missing state "${stateId}" defined in library`,
107
+ impact: 'State transitions to this state may fail',
108
+ suggestion: `Add state "${stateId}" to nodeTypes.${typeId}.states`,
109
+ fixable: false,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ return violations;
118
+ },
119
+ };
120
+
121
+ /**
122
+ * Find a similar string from a set (for "did you mean" suggestions)
123
+ */
124
+ function findSimilar(input: string, candidates: Set<string>): string | null {
125
+ const inputLower = input.toLowerCase();
126
+
127
+ for (const candidate of candidates) {
128
+ const candidateLower = candidate.toLowerCase();
129
+
130
+ // Exact substring match
131
+ if (inputLower.includes(candidateLower) || candidateLower.includes(inputLower)) {
132
+ return candidate;
133
+ }
134
+
135
+ // Small edit distance
136
+ if (Math.abs(input.length - candidate.length) <= 2) {
137
+ let differences = 0;
138
+ const minLen = Math.min(inputLower.length, candidateLower.length);
139
+ for (let i = 0; i < minLen; i++) {
140
+ if (inputLower[i] !== candidateLower[i]) differences++;
141
+ }
142
+ differences += Math.abs(input.length - candidate.length);
143
+ if (differences <= 2) return candidate;
144
+ }
145
+ }
146
+
147
+ return null;
148
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Rule: minimum-node-sources
3
+ * Ensures each nodeType has at least a minimum number of sources defined
4
+ */
5
+
6
+ import type { GraphRule, GraphRuleContext, GraphRuleViolation, RuleOptions } from '../types';
7
+
8
+ /**
9
+ * Options for minimum-node-sources rule
10
+ */
11
+ export interface MinimumNodeSourcesOptions extends RuleOptions {
12
+ /**
13
+ * Minimum number of sources required per nodeType
14
+ * @default 1
15
+ */
16
+ minimum: number;
17
+
18
+ /**
19
+ * NodeType IDs to exclude from this check
20
+ * @default []
21
+ */
22
+ excludeNodeTypes: string[];
23
+ }
24
+
25
+ const DEFAULT_OPTIONS: MinimumNodeSourcesOptions = {
26
+ minimum: 1,
27
+ excludeNodeTypes: [],
28
+ };
29
+
30
+ export const minimumNodeSources: GraphRule<MinimumNodeSourcesOptions> = {
31
+ id: 'minimum-node-sources',
32
+ name: 'Minimum Node Sources',
33
+ description: 'Each nodeType must have at least a minimum number of sources defined',
34
+ impact: 'NodeTypes without sources cannot be associated with log activity',
35
+ severity: 'error',
36
+ category: 'structure',
37
+ enabled: true,
38
+ fixable: false,
39
+ defaultOptions: DEFAULT_OPTIONS,
40
+
41
+ async check(context: GraphRuleContext<MinimumNodeSourcesOptions>): Promise<GraphRuleViolation[]> {
42
+ const violations: GraphRuleViolation[] = [];
43
+ const { configuration, configPath, options } = context;
44
+
45
+ const minimum = options.minimum ?? DEFAULT_OPTIONS.minimum;
46
+ const excludeNodeTypes = options.excludeNodeTypes ?? DEFAULT_OPTIONS.excludeNodeTypes;
47
+
48
+ if (!configuration.nodeTypes) {
49
+ return violations;
50
+ }
51
+
52
+ for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
53
+ // Skip excluded node types
54
+ if (excludeNodeTypes.includes(typeId)) {
55
+ continue;
56
+ }
57
+
58
+ // Get sources from nodeType (may be extended via path-based config)
59
+ const nodeTypeAny = nodeType as unknown as Record<string, unknown>;
60
+ const sources = nodeTypeAny.sources;
61
+ const sourceCount = Array.isArray(sources) ? sources.length : 0;
62
+
63
+ if (sourceCount < minimum) {
64
+ violations.push({
65
+ ruleId: 'minimum-node-sources',
66
+ severity: 'error',
67
+ file: configPath,
68
+ path: `nodeTypes.${typeId}.sources`,
69
+ message:
70
+ sourceCount === 0
71
+ ? `Node type "${typeId}" has no sources defined`
72
+ : `Node type "${typeId}" has ${sourceCount} source(s), minimum required is ${minimum}`,
73
+ impact: 'This node type cannot be associated with log activity from source files',
74
+ suggestion: `Add at least ${minimum} source path(s) to nodeTypes.${typeId}.sources`,
75
+ fixable: false,
76
+ });
77
+ }
78
+ }
79
+
80
+ return violations;
81
+ },
82
+ };