@jarrodmedrano/claude-skills 1.0.3 → 1.0.4

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 (26) hide show
  1. package/.claude/skills/bevy/SKILL.md +406 -0
  2. package/.claude/skills/bevy/references/bevy_specific_tips.md +385 -0
  3. package/.claude/skills/bevy/references/common_pitfalls.md +217 -0
  4. package/.claude/skills/bevy/references/ecs_patterns.md +277 -0
  5. package/.claude/skills/bevy/references/project_structure.md +116 -0
  6. package/.claude/skills/bevy/references/ui_development.md +147 -0
  7. package/.claude/skills/domain-driven-design/SKILL.md +459 -0
  8. package/.claude/skills/domain-driven-design/references/ddd_foundations_and_patterns.md +664 -0
  9. package/.claude/skills/domain-driven-design/references/rich_hickey_principles.md +406 -0
  10. package/.claude/skills/domain-driven-design/references/visualization_examples.md +790 -0
  11. package/.claude/skills/domain-driven-design/references/wlaschin_patterns.md +639 -0
  12. package/.claude/skills/godot/SKILL.md +728 -0
  13. package/.claude/skills/godot/assets/templates/attribute_template.gd +109 -0
  14. package/.claude/skills/godot/assets/templates/component_template.gd +76 -0
  15. package/.claude/skills/godot/assets/templates/interaction_template.gd +108 -0
  16. package/.claude/skills/godot/assets/templates/item_resource.tres +11 -0
  17. package/.claude/skills/godot/assets/templates/spell_resource.tres +20 -0
  18. package/.claude/skills/godot/references/architecture-patterns.md +608 -0
  19. package/.claude/skills/godot/references/common-pitfalls.md +518 -0
  20. package/.claude/skills/godot/references/file-formats.md +491 -0
  21. package/.claude/skills/godot/references/godot4-physics-api.md +302 -0
  22. package/.claude/skills/godot/scripts/validate_tres.py +145 -0
  23. package/.claude/skills/godot/scripts/validate_tscn.py +170 -0
  24. package/.claude/skills/react-three-fiber/SKILL.md +2055 -0
  25. package/.claude/skills/react-three-fiber/scripts/build-scene.ts +171 -0
  26. package/package.json +1 -1
@@ -0,0 +1,302 @@
1
+ # Godot 4.x Physics API Quick Reference
2
+
3
+ This reference covers common physics operations in Godot 4.x, focusing on correct API usage and common patterns.
4
+
5
+ ## Raycasting
6
+
7
+ ### PhysicsRayQueryParameters3D (Correct Class Name)
8
+
9
+ **Important**: The class is `PhysicsRayQueryParameters3D`, NOT `PhysicsRayQuery3D`.
10
+
11
+ ### Basic Raycast Setup
12
+
13
+ ```gdscript
14
+ # Get the physics space
15
+ var space_state = get_world_3d().direct_space_state
16
+
17
+ # Create ray query parameters
18
+ var query = PhysicsRayQueryParameters3D.create(from_position, to_position)
19
+
20
+ # Optional: Exclude specific bodies
21
+ query.exclude = [self, other_body]
22
+
23
+ # Optional: Set collision mask (which layers to check)
24
+ query.collision_mask = 1 # Layer 1 only
25
+ query.collision_mask = 0b0011 # Layers 1 and 2
26
+
27
+ # Perform the raycast
28
+ var result = space_state.intersect_ray(query)
29
+
30
+ # Check if hit something
31
+ if result:
32
+ var hit_object = result.collider
33
+ var hit_position = result.position
34
+ var hit_normal = result.normal
35
+ ```
36
+
37
+ ### Common Raycast Patterns
38
+
39
+ **Camera-based raycast (first-person interaction):**
40
+ ```gdscript
41
+ var camera = get_viewport().get_camera_3d()
42
+ var from = camera.global_position
43
+ var to = from + (-camera.global_transform.basis.z * range)
44
+
45
+ var query = PhysicsRayQueryParameters3D.create(from, to)
46
+ query.exclude = [player]
47
+ var result = space_state.intersect_ray(query)
48
+ ```
49
+
50
+ **Downward raycast (ground detection):**
51
+ ```gdscript
52
+ var from = global_position
53
+ var to = global_position + Vector3.DOWN * 10.0
54
+
55
+ var query = PhysicsRayQueryParameters3D.create(from, to)
56
+ var result = space_state.intersect_ray(query)
57
+
58
+ if result:
59
+ var distance_to_ground = from.distance_to(result.position)
60
+ ```
61
+
62
+ **Line-of-sight check:**
63
+ ```gdscript
64
+ func has_line_of_sight(target: Node3D) -> bool:
65
+ var space_state = get_world_3d().direct_space_state
66
+ var from = global_position
67
+ var to = target.global_position
68
+
69
+ var query = PhysicsRayQueryParameters3D.create(from, to)
70
+ query.exclude = [self, target]
71
+
72
+ var result = space_state.intersect_ray(query)
73
+ return not result # True if nothing blocking
74
+ ```
75
+
76
+ ## Shape Queries
77
+
78
+ ### PhysicsShapeQueryParameters3D
79
+
80
+ For checking if a shape overlaps with objects (useful for area attacks, detection zones).
81
+
82
+ ```gdscript
83
+ # Create shape query
84
+ var query = PhysicsShapeQueryParameters3D.new()
85
+
86
+ # Set the shape (sphere, box, capsule, etc.)
87
+ var sphere = SphereShape3D.new()
88
+ sphere.radius = 2.0
89
+ query.shape = sphere
90
+
91
+ # Set transform (position and rotation)
92
+ query.transform = Transform3D(Basis(), global_position)
93
+
94
+ # Optional: collision mask
95
+ query.collision_mask = 1
96
+
97
+ # Perform query
98
+ var space_state = get_world_3d().direct_space_state
99
+ var results = space_state.intersect_shape(query)
100
+
101
+ # Iterate results
102
+ for result in results:
103
+ var collider = result.collider
104
+ # Do something with each overlapping object
105
+ ```
106
+
107
+ ### Common Shape Query Patterns
108
+
109
+ **Area attack (sphere around player):**
110
+ ```gdscript
111
+ func perform_area_attack(radius: float, damage: float):
112
+ var query = PhysicsShapeQueryParameters3D.new()
113
+ var sphere = SphereShape3D.new()
114
+ sphere.radius = radius
115
+ query.shape = sphere
116
+ query.transform = Transform3D(Basis(), global_position)
117
+ query.collision_mask = 2 # Enemies layer
118
+
119
+ var space_state = get_world_3d().direct_space_state
120
+ var results = space_state.intersect_shape(query)
121
+
122
+ for result in results:
123
+ if result.collider.has_method("take_damage"):
124
+ result.collider.take_damage(damage)
125
+ ```
126
+
127
+ **Cone detection (enemy field of view):**
128
+ ```gdscript
129
+ # Use multiple raycasts in a cone pattern
130
+ func detect_in_cone(angle_degrees: float, range: float) -> Array:
131
+ var detected = []
132
+ var rays = 5 # Number of rays in cone
133
+
134
+ for i in range(rays):
135
+ var angle = -angle_degrees/2 + (angle_degrees / (rays-1)) * i
136
+ var direction = global_transform.basis.z.rotated(Vector3.UP, deg_to_rad(angle))
137
+
138
+ var from = global_position
139
+ var to = from + direction * range
140
+
141
+ var query = PhysicsRayQueryParameters3D.create(from, to)
142
+ var result = space_state.intersect_ray(query)
143
+
144
+ if result:
145
+ detected.append(result.collider)
146
+
147
+ return detected
148
+ ```
149
+
150
+ ## Collision Layers and Masks
151
+
152
+ Understanding layers is critical for efficient physics.
153
+
154
+ ### Layer Setup
155
+
156
+ ```gdscript
157
+ # In project settings, name your layers:
158
+ # Layer 1: World (walls, floors)
159
+ # Layer 2: Player
160
+ # Layer 3: Enemies
161
+ # Layer 4: Projectiles
162
+ # Layer 5: Interactables
163
+
164
+ # Set what layers an object is ON
165
+ collision_layer = 0b00010 # Layer 2 (Player)
166
+
167
+ # Set what layers an object can COLLIDE WITH
168
+ collision_mask = 0b00101 # Layers 1 (World) and 3 (Enemies)
169
+ ```
170
+
171
+ ### Common Layer Patterns
172
+
173
+ **Player should collide with world and enemies:**
174
+ ```gdscript
175
+ # Player
176
+ collision_layer = 0b00010 # Layer 2
177
+ collision_mask = 0b00101 # Layers 1 (world) and 3 (enemies)
178
+ ```
179
+
180
+ **Enemy projectile should hit player but not other enemies:**
181
+ ```gdscript
182
+ # Enemy Projectile
183
+ collision_layer = 0b01000 # Layer 4
184
+ collision_mask = 0b00011 # Layers 1 (world) and 2 (player)
185
+ ```
186
+
187
+ **Interaction raycast should only hit interactables:**
188
+ ```gdscript
189
+ var query = PhysicsRayQueryParameters3D.create(from, to)
190
+ query.collision_mask = 0b10000 # Layer 5 (interactables) only
191
+ ```
192
+
193
+ ## Area3D vs RigidBody3D vs StaticBody3D vs CharacterBody3D
194
+
195
+ ### When to Use Each
196
+
197
+ **StaticBody3D**
198
+ - Non-moving collision objects (walls, floors, obstacles)
199
+ - Cannot be moved by physics
200
+ - Very efficient
201
+
202
+ **CharacterBody3D**
203
+ - Player characters, NPCs with custom movement
204
+ - Controlled by code, not physics engine
205
+ - Has `move_and_slide()` for smooth movement
206
+ - Use for anything you want direct control over
207
+
208
+ **RigidBody3D**
209
+ - Objects controlled by physics (crates, barrels, ragdolls)
210
+ - Affected by gravity, forces, collisions
211
+ - Good for destructible/pushable objects
212
+
213
+ **Area3D**
214
+ - Trigger zones (doesn't block movement)
215
+ - Detection volumes (pickup radius, damage zones)
216
+ - Cannot collide physically, only detect overlaps
217
+
218
+ ### Common Patterns
219
+
220
+ **Damage zone (Area3D):**
221
+ ```gdscript
222
+ extends Area3D
223
+
224
+ signal body_damaged(body: Node3D, damage: float)
225
+
226
+ @export var damage_per_second: float = 10.0
227
+
228
+ func _ready():
229
+ body_entered.connect(_on_body_entered)
230
+ body_exited.connect(_on_body_exited)
231
+
232
+ var bodies_inside: Array[Node3D] = []
233
+
234
+ func _on_body_entered(body: Node3D):
235
+ bodies_inside.append(body)
236
+
237
+ func _on_body_exited(body: Node3D):
238
+ bodies_inside.erase(body)
239
+
240
+ func _process(delta: float):
241
+ for body in bodies_inside:
242
+ if body.has_method("take_damage"):
243
+ body.take_damage(damage_per_second * delta)
244
+ body_damaged.emit(body, damage_per_second * delta)
245
+ ```
246
+
247
+ **Pickup detection (Area3D child of player):**
248
+ ```gdscript
249
+ # As child of CharacterBody3D (player)
250
+ extends Area3D
251
+
252
+ func _ready():
253
+ collision_layer = 0 # Not on any layer
254
+ collision_mask = 0b10000 # Only detect interactables
255
+
256
+ body_entered.connect(_on_pickup_entered)
257
+
258
+ func _on_pickup_entered(body: Node3D):
259
+ if body.has_method("pickup"):
260
+ body.pickup(get_parent()) # Pass player to pickup
261
+ ```
262
+
263
+ ## Performance Tips
264
+
265
+ 1. **Use collision layers efficiently** - Don't check unnecessary layers
266
+ 2. **Limit raycast distance** - Shorter rays are faster
267
+ 3. **Cache space_state** - Don't call `get_world_3d().direct_space_state` every frame if possible
268
+ 4. **Use Areas for detection** - More efficient than frequent raycasts
269
+ 5. **Exclude irrelevant bodies** - Use `query.exclude` to skip known objects
270
+
271
+ ## Common Gotchas
272
+
273
+ 1. **Wrong class name**: It's `PhysicsRayQueryParameters3D`, not `PhysicsRayQuery3D`
274
+ 2. **Forgetting collision mask**: Raycast won't hit anything if mask is 0
275
+ 3. **Self-collision**: Always exclude `self` from queries
276
+ 4. **Result dictionary**: Check `if result:` before accessing `result.collider`
277
+ 5. **Global vs local positions**: Raycasts use global coordinates
278
+ 6. **Basis direction**: `-transform.basis.z` is forward, not `transform.basis.z`
279
+
280
+ ## Debugging Physics
281
+
282
+ ```gdscript
283
+ # Visualize raycasts in editor
284
+ func _draw_debug_ray(from: Vector3, to: Vector3, hit: bool):
285
+ var immediate = ImmediateMesh.new()
286
+ var material = StandardMaterial3D.new()
287
+ material.albedo_color = Color.RED if hit else Color.GREEN
288
+ material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
289
+
290
+ # Add debug line drawing here
291
+ # (Use MeshInstance3D with ImmediateMesh for runtime visualization)
292
+
293
+ # Print collision layers
294
+ func debug_print_layers():
295
+ print("collision_layer: ", collision_layer, " (binary: ", String.num_int64(collision_layer, 2), ")")
296
+ print("collision_mask: ", collision_mask, " (binary: ", String.num_int64(collision_mask, 2), ")")
297
+ ```
298
+
299
+ ## Further Reading
300
+
301
+ - Official Godot 4 Physics Docs: https://docs.godotengine.org/en/stable/tutorials/physics/
302
+ - Understanding collision layers: https://docs.godotengine.org/en/stable/tutorials/physics/physics_introduction.html#collision-layers-and-masks
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validates Godot .tres (resource) files for common syntax errors.
4
+
5
+ Common mistakes this catches:
6
+ - Using preload() instead of ExtResource()
7
+ - Using GDScript syntax (var, const, func) in resource files
8
+ - Missing ExtResource declarations
9
+ - Incorrect array type syntax
10
+ """
11
+
12
+ import sys
13
+ import re
14
+ from pathlib import Path
15
+
16
+
17
+ class TresValidator:
18
+ def __init__(self, file_path):
19
+ self.file_path = Path(file_path)
20
+ self.errors = []
21
+ self.warnings = []
22
+
23
+ def validate(self):
24
+ """Run all validation checks."""
25
+ if not self.file_path.exists():
26
+ self.errors.append(f"File not found: {self.file_path}")
27
+ return False
28
+
29
+ content = self.file_path.read_text()
30
+
31
+ self._check_preload_usage(content)
32
+ self._check_gdscript_keywords(content)
33
+ self._check_array_syntax(content)
34
+ self._check_resource_references(content)
35
+
36
+ return len(self.errors) == 0
37
+
38
+ def _check_preload_usage(self, content):
39
+ """Check for illegal preload() usage."""
40
+ preload_pattern = r'preload\s*\('
41
+ matches = list(re.finditer(preload_pattern, content, re.IGNORECASE))
42
+
43
+ for match in matches:
44
+ line_num = content[:match.start()].count('\n') + 1
45
+ self.errors.append(
46
+ f"Line {line_num}: Found 'preload()' - use ExtResource() instead in .tres files"
47
+ )
48
+
49
+ def _check_gdscript_keywords(self, content):
50
+ """Check for GDScript keywords that shouldn't be in .tres files."""
51
+ # Split into lines and check each
52
+ lines = content.split('\n')
53
+ keywords = ['var ', 'const ', 'func ', 'class_name ', 'extends ']
54
+
55
+ for i, line in enumerate(lines, 1):
56
+ # Skip comments
57
+ if line.strip().startswith('#'):
58
+ continue
59
+
60
+ for keyword in keywords:
61
+ if keyword in line and not line.strip().startswith('['):
62
+ self.errors.append(
63
+ f"Line {i}: Found GDScript keyword '{keyword.strip()}' - "
64
+ f"not allowed in .tres files"
65
+ )
66
+
67
+ def _check_array_syntax(self, content):
68
+ """Check for proper typed array syntax."""
69
+ # Look for array assignments without type
70
+ untyped_array_pattern = r'=\s*\[[^\]]*\]'
71
+ lines = content.split('\n')
72
+
73
+ for i, line in enumerate(lines, 1):
74
+ # Skip resource headers
75
+ if line.strip().startswith('[') and line.strip().endswith(']'):
76
+ continue
77
+
78
+ if re.search(untyped_array_pattern, line):
79
+ # Check if it's preceded by Array[Type]
80
+ if 'Array[' not in line:
81
+ self.warnings.append(
82
+ f"Line {i}: Array may need type specification - "
83
+ f"use Array[Type]([...]) syntax"
84
+ )
85
+
86
+ def _check_resource_references(self, content):
87
+ """Check that ExtResource IDs are declared."""
88
+ # Find all ExtResource usages
89
+ usage_pattern = r'ExtResource\s*\(\s*"([^"]+)"\s*\)'
90
+ usages = re.findall(usage_pattern, content)
91
+
92
+ # Find all ExtResource declarations
93
+ decl_pattern = r'\[ext_resource[^\]]*id\s*=\s*"([^"]+)"'
94
+ declarations = re.findall(decl_pattern, content)
95
+
96
+ # Check for undefined references
97
+ for resource_id in set(usages):
98
+ if resource_id not in declarations:
99
+ self.errors.append(
100
+ f"ExtResource('{resource_id}') used but not declared - "
101
+ f"add [ext_resource ...] declaration"
102
+ )
103
+
104
+ def print_results(self):
105
+ """Print validation results."""
106
+ print(f"\n{'='*60}")
107
+ print(f"Validating: {self.file_path}")
108
+ print(f"{'='*60}\n")
109
+
110
+ if self.errors:
111
+ print("❌ ERRORS:")
112
+ for error in self.errors:
113
+ print(f" • {error}")
114
+ print()
115
+
116
+ if self.warnings:
117
+ print("⚠️ WARNINGS:")
118
+ for warning in self.warnings:
119
+ print(f" • {warning}")
120
+ print()
121
+
122
+ if not self.errors and not self.warnings:
123
+ print("✅ No issues found!\n")
124
+ elif not self.errors:
125
+ print("✅ No errors (only warnings)\n")
126
+ else:
127
+ print(f"❌ Found {len(self.errors)} error(s)\n")
128
+
129
+ return len(self.errors) == 0
130
+
131
+
132
+ def main():
133
+ if len(sys.argv) < 2:
134
+ print("Usage: validate_tres.py <file.tres>")
135
+ sys.exit(1)
136
+
137
+ validator = TresValidator(sys.argv[1])
138
+ validator.validate()
139
+ success = validator.print_results()
140
+
141
+ sys.exit(0 if success else 1)
142
+
143
+
144
+ if __name__ == "__main__":
145
+ main()
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validates Godot .tscn (scene) files for common structural errors.
4
+
5
+ Common mistakes this catches:
6
+ - Missing ExtResource declarations for used resources
7
+ - Invalid parent references
8
+ - Malformed node entries
9
+ - UID format issues
10
+ """
11
+
12
+ import sys
13
+ import re
14
+ from pathlib import Path
15
+
16
+
17
+ class TscnValidator:
18
+ def __init__(self, file_path):
19
+ self.file_path = Path(file_path)
20
+ self.errors = []
21
+ self.warnings = []
22
+
23
+ def validate(self):
24
+ """Run all validation checks."""
25
+ if not self.file_path.exists():
26
+ self.errors.append(f"File not found: {self.file_path}")
27
+ return False
28
+
29
+ content = self.file_path.read_text()
30
+
31
+ self._check_header(content)
32
+ self._check_resource_references(content)
33
+ self._check_node_structure(content)
34
+ self._check_parent_references(content)
35
+
36
+ return len(self.errors) == 0
37
+
38
+ def _check_header(self, content):
39
+ """Check for valid scene file header."""
40
+ lines = content.split('\n')
41
+ if not lines:
42
+ self.errors.append("Empty file")
43
+ return
44
+
45
+ first_line = lines[0].strip()
46
+ if not first_line.startswith('[gd_scene'):
47
+ self.errors.append(
48
+ "Invalid header - should start with [gd_scene load_steps=N format=3 ...]"
49
+ )
50
+
51
+ # Check for UID
52
+ if 'uid=' not in first_line:
53
+ self.warnings.append("Missing UID in header - may cause reference issues")
54
+
55
+ def _check_resource_references(self, content):
56
+ """Check that all ExtResource and SubResource references are declared."""
57
+ # Find all ExtResource usages
58
+ ext_usage_pattern = r'ExtResource\s*\(\s*"([^"]+)"\s*\)'
59
+ ext_usages = re.findall(ext_usage_pattern, content)
60
+
61
+ # Find all ExtResource declarations
62
+ ext_decl_pattern = r'\[ext_resource[^\]]*id\s*=\s*"([^"]+)"'
63
+ ext_declarations = re.findall(ext_decl_pattern, content)
64
+
65
+ # Check for undefined ExtResource references
66
+ for resource_id in set(ext_usages):
67
+ if resource_id not in ext_declarations:
68
+ self.errors.append(
69
+ f"ExtResource('{resource_id}') used but not declared"
70
+ )
71
+
72
+ # Find all SubResource usages
73
+ sub_usage_pattern = r'SubResource\s*\(\s*"([^"]+)"\s*\)'
74
+ sub_usages = re.findall(sub_usage_pattern, content)
75
+
76
+ # Find all SubResource declarations
77
+ sub_decl_pattern = r'\[sub_resource[^\]]*id\s*=\s*"([^"]+)"'
78
+ sub_declarations = re.findall(sub_decl_pattern, content)
79
+
80
+ # Check for undefined SubResource references
81
+ for resource_id in set(sub_usages):
82
+ if resource_id not in sub_declarations:
83
+ self.errors.append(
84
+ f"SubResource('{resource_id}') used but not declared"
85
+ )
86
+
87
+ def _check_node_structure(self, content):
88
+ """Check for valid node entries."""
89
+ lines = content.split('\n')
90
+ node_pattern = r'^\[node\s+name="([^"]+)"'
91
+
92
+ for i, line in enumerate(lines, 1):
93
+ match = re.match(node_pattern, line)
94
+ if match:
95
+ # Check if node has required attributes
96
+ if 'type=' not in line and 'instance=' not in line and 'parent=' in line:
97
+ # Child nodes without type or instance might be invalid
98
+ self.warnings.append(
99
+ f"Line {i}: Node '{match.group(1)}' has parent but no type or instance"
100
+ )
101
+
102
+ def _check_parent_references(self, content):
103
+ """Check that parent references are valid."""
104
+ lines = content.split('\n')
105
+
106
+ # Collect all node names
107
+ node_pattern = r'\[node\s+name="([^"]+)"'
108
+ nodes = []
109
+
110
+ for line in lines:
111
+ match = re.match(node_pattern, line)
112
+ if match:
113
+ nodes.append(match.group(1))
114
+
115
+ # Check parent references
116
+ parent_pattern = r'parent="([^"]+)"'
117
+ for i, line in enumerate(lines, 1):
118
+ match = re.search(parent_pattern, line)
119
+ if match:
120
+ parent = match.group(1)
121
+ # "." is valid (root), others should exist
122
+ if parent != "." and '/' not in parent:
123
+ # Simple parent reference
124
+ if parent not in nodes:
125
+ self.warnings.append(
126
+ f"Line {i}: Parent '{parent}' not found in scene"
127
+ )
128
+
129
+ def print_results(self):
130
+ """Print validation results."""
131
+ print(f"\n{'='*60}")
132
+ print(f"Validating: {self.file_path}")
133
+ print(f"{'='*60}\n")
134
+
135
+ if self.errors:
136
+ print("❌ ERRORS:")
137
+ for error in self.errors:
138
+ print(f" • {error}")
139
+ print()
140
+
141
+ if self.warnings:
142
+ print("⚠️ WARNINGS:")
143
+ for warning in self.warnings:
144
+ print(f" • {warning}")
145
+ print()
146
+
147
+ if not self.errors and not self.warnings:
148
+ print("✅ No issues found!\n")
149
+ elif not self.errors:
150
+ print("✅ No errors (only warnings)\n")
151
+ else:
152
+ print(f"❌ Found {len(self.errors)} error(s)\n")
153
+
154
+ return len(self.errors) == 0
155
+
156
+
157
+ def main():
158
+ if len(sys.argv) < 2:
159
+ print("Usage: validate_tscn.py <file.tscn>")
160
+ sys.exit(1)
161
+
162
+ validator = TscnValidator(sys.argv[1])
163
+ validator.validate()
164
+ success = validator.print_results()
165
+
166
+ sys.exit(0 if success else 1)
167
+
168
+
169
+ if __name__ == "__main__":
170
+ main()