@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.
- package/README.md +126 -0
- package/dist/ConfigurationLoader.d.ts +76 -0
- package/dist/ConfigurationLoader.d.ts.map +1 -0
- package/dist/ConfigurationLoader.js +144 -0
- package/dist/ConfigurationLoader.js.map +1 -0
- package/dist/ConfigurationValidator.d.ts +31 -0
- package/dist/ConfigurationValidator.d.ts.map +1 -0
- package/dist/ConfigurationValidator.js +242 -0
- package/dist/ConfigurationValidator.js.map +1 -0
- package/dist/EventProcessor.d.ts +49 -0
- package/dist/EventProcessor.d.ts.map +1 -0
- package/dist/EventProcessor.js +215 -0
- package/dist/EventProcessor.js.map +1 -0
- package/dist/EventRecorderService.d.ts +305 -0
- package/dist/EventRecorderService.d.ts.map +1 -0
- package/dist/EventRecorderService.js +463 -0
- package/dist/EventRecorderService.js.map +1 -0
- package/dist/LibraryLoader.d.ts +63 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +188 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PathBasedEventProcessor.d.ts +90 -0
- package/dist/PathBasedEventProcessor.d.ts.map +1 -0
- package/dist/PathBasedEventProcessor.js +239 -0
- package/dist/PathBasedEventProcessor.js.map +1 -0
- package/dist/SessionManager.d.ts +194 -0
- package/dist/SessionManager.d.ts.map +1 -0
- package/dist/SessionManager.js +299 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/ValidationEngine.d.ts +31 -0
- package/dist/ValidationEngine.d.ts.map +1 -0
- package/dist/ValidationEngine.js +158 -0
- package/dist/ValidationEngine.js.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.js +248 -0
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/config.d.ts +57 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +382 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/engine.d.ts +70 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +252 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/implementations/connection-type-references.d.ts +7 -0
- package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
- package/dist/rules/implementations/connection-type-references.js +104 -0
- package/dist/rules/implementations/connection-type-references.js.map +1 -0
- package/dist/rules/implementations/dead-end-states.d.ts +17 -0
- package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
- package/dist/rules/implementations/dead-end-states.js +72 -0
- package/dist/rules/implementations/dead-end-states.js.map +1 -0
- package/dist/rules/implementations/index.d.ts +24 -0
- package/dist/rules/implementations/index.d.ts.map +1 -0
- package/dist/rules/implementations/index.js +62 -0
- package/dist/rules/implementations/index.js.map +1 -0
- package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
- package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
- package/dist/rules/implementations/library-node-type-match.js +123 -0
- package/dist/rules/implementations/library-node-type-match.js.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.js +54 -0
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.js +211 -0
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.js +47 -0
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.js +50 -0
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
- package/dist/rules/implementations/required-metadata.d.ts +7 -0
- package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
- package/dist/rules/implementations/required-metadata.js +57 -0
- package/dist/rules/implementations/required-metadata.js.map +1 -0
- package/dist/rules/implementations/state-transition-references.d.ts +7 -0
- package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
- package/dist/rules/implementations/state-transition-references.js +135 -0
- package/dist/rules/implementations/state-transition-references.js.map +1 -0
- package/dist/rules/implementations/unreachable-states.d.ts +7 -0
- package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
- package/dist/rules/implementations/unreachable-states.js +80 -0
- package/dist/rules/implementations/unreachable-states.js.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.js +109 -0
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
- package/dist/rules/implementations/valid-color-format.d.ts +7 -0
- package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
- package/dist/rules/implementations/valid-color-format.js +91 -0
- package/dist/rules/implementations/valid-color-format.js.map +1 -0
- package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
- package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-edge-types.js +244 -0
- package/dist/rules/implementations/valid-edge-types.js.map +1 -0
- package/dist/rules/implementations/valid-node-types.d.ts +7 -0
- package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-node-types.js +175 -0
- package/dist/rules/implementations/valid-node-types.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +45 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +309 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +35 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/types/canvas.d.ts +409 -0
- package/dist/types/canvas.d.ts.map +1 -0
- package/dist/types/canvas.js +70 -0
- package/dist/types/canvas.js.map +1 -0
- package/dist/types/index.d.ts +311 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/library.d.ts +185 -0
- package/dist/types/library.d.ts.map +1 -0
- package/dist/types/library.js +15 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/path-based-config.d.ts +230 -0
- package/dist/types/path-based-config.d.ts.map +1 -0
- package/dist/types/path-based-config.js +9 -0
- package/dist/types/path-based-config.js.map +1 -0
- package/dist/utils/CanvasConverter.d.ts +118 -0
- package/dist/utils/CanvasConverter.d.ts.map +1 -0
- package/dist/utils/CanvasConverter.js +315 -0
- package/dist/utils/CanvasConverter.js.map +1 -0
- package/dist/utils/GraphConverter.d.ts +18 -0
- package/dist/utils/GraphConverter.d.ts.map +1 -0
- package/dist/utils/GraphConverter.js +61 -0
- package/dist/utils/GraphConverter.js.map +1 -0
- package/dist/utils/LibraryConverter.d.ts +113 -0
- package/dist/utils/LibraryConverter.d.ts.map +1 -0
- package/dist/utils/LibraryConverter.js +166 -0
- package/dist/utils/LibraryConverter.js.map +1 -0
- package/dist/utils/PathMatcher.d.ts +55 -0
- package/dist/utils/PathMatcher.d.ts.map +1 -0
- package/dist/utils/PathMatcher.js +172 -0
- package/dist/utils/PathMatcher.js.map +1 -0
- package/dist/utils/YamlParser.d.ts +36 -0
- package/dist/utils/YamlParser.d.ts.map +1 -0
- package/dist/utils/YamlParser.js +63 -0
- package/dist/utils/YamlParser.js.map +1 -0
- package/package.json +47 -0
- package/src/ConfigurationLoader.test.ts +490 -0
- package/src/ConfigurationLoader.ts +185 -0
- package/src/ConfigurationValidator.test.ts +200 -0
- package/src/ConfigurationValidator.ts +283 -0
- package/src/EventProcessor.test.ts +405 -0
- package/src/EventProcessor.ts +250 -0
- package/src/EventRecorderService.test.ts +541 -0
- package/src/EventRecorderService.ts +744 -0
- package/src/LibraryLoader.ts +215 -0
- package/src/PathBasedEventProcessor.test.ts +567 -0
- package/src/PathBasedEventProcessor.ts +332 -0
- package/src/SessionManager.test.ts +424 -0
- package/src/SessionManager.ts +470 -0
- package/src/ValidationEngine.test.ts +371 -0
- package/src/ValidationEngine.ts +196 -0
- package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
- package/src/helpers/GraphInstrumentationHelper.ts +326 -0
- package/src/index.ts +85 -0
- package/src/rules/config.test.ts +278 -0
- package/src/rules/config.ts +459 -0
- package/src/rules/engine.test.ts +332 -0
- package/src/rules/engine.ts +318 -0
- package/src/rules/implementations/connection-type-references.ts +117 -0
- package/src/rules/implementations/dead-end-states.ts +101 -0
- package/src/rules/implementations/index.ts +73 -0
- package/src/rules/implementations/library-node-type-match.ts +148 -0
- package/src/rules/implementations/minimum-node-sources.ts +82 -0
- package/src/rules/implementations/no-unknown-fields.ts +342 -0
- package/src/rules/implementations/orphaned-edge-types.ts +55 -0
- package/src/rules/implementations/orphaned-node-types.ts +58 -0
- package/src/rules/implementations/required-metadata.ts +64 -0
- package/src/rules/implementations/state-transition-references.ts +151 -0
- package/src/rules/implementations/unreachable-states.ts +94 -0
- package/src/rules/implementations/valid-action-patterns.ts +136 -0
- package/src/rules/implementations/valid-color-format.ts +140 -0
- package/src/rules/implementations/valid-edge-types.ts +258 -0
- package/src/rules/implementations/valid-node-types.ts +189 -0
- package/src/rules/index.ts +95 -0
- package/src/rules/types.ts +426 -0
- package/src/types/canvas.ts +496 -0
- package/src/types/index.ts +382 -0
- package/src/types/library.ts +233 -0
- package/src/types/path-based-config.ts +281 -0
- package/src/utils/CanvasConverter.ts +431 -0
- package/src/utils/GraphConverter.test.ts +195 -0
- package/src/utils/GraphConverter.ts +71 -0
- package/src/utils/LibraryConverter.ts +245 -0
- package/src/utils/PathMatcher.test.ts +148 -0
- package/src/utils/PathMatcher.ts +183 -0
- 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
|
+
};
|