@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,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
|
+
};
|