@intentsolutionsio/data-validation-engine 1.0.0 → 1.0.5

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.
@@ -1,6 +1,34 @@
1
1
  ---
2
2
  name: validation-agent
3
3
  description: Implement data validation rules
4
+ tools:
5
+ - Read
6
+ - Write
7
+ - Edit
8
+ - Bash
9
+ - Glob
10
+ - Grep
11
+ - WebFetch
12
+ - WebSearch
13
+ - Task
14
+ - TodoWrite
15
+ model: sonnet
16
+ color: yellow
17
+ version: 1.0.0
18
+ author: Jeremy Longshore <jeremy@intentsolutions.io>
19
+ tags:
20
+ - database
21
+ - validation
22
+ disallowedTools: []
23
+ skills: []
24
+ background: false
25
+ # ── upgrade levers — uncomment + set when tuning this agent ──
26
+ # effort: high # reasoning depth: low/medium/high/xhigh/max (omit = inherit session)
27
+ # maxTurns: 50 # cap the agentic loop (omit = engine default)
28
+ # memory: project # persistent scope: user/project/local (omit = ephemeral)
29
+ # isolation: worktree # run in an isolated git worktree
30
+ # initialPrompt: "…" # seed the agent's first turn
31
+ # hooks / mcpServers / permissionMode → set at the PLUGIN level, not on a plugin agent
4
32
  ---
5
33
  # Data Validation Engine
6
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentsolutionsio/data-validation-engine",
3
- "version": "1.0.0",
3
+ "version": "1.0.5",
4
4
  "description": "Database plugin for data-validation-engine",
5
5
  "keywords": [
6
6
  "database",
@@ -1,17 +1,24 @@
1
1
  ---
2
2
  name: validating-database-integrity
3
- description: |
4
- Process use when you need to ensure database integrity through comprehensive data validation.
5
- This skill validates data types, ranges, formats, referential integrity, and business rules.
3
+ description: 'Process use when you need to ensure database integrity through comprehensive
4
+ data validation.
5
+
6
+ This skill validates data types, ranges, formats, referential integrity, and business
7
+ rules.
8
+
6
9
  Trigger with phrases like "validate database data", "implement data validation rules",
10
+
7
11
  "enforce data integrity constraints", or "validate data formats".
8
12
 
13
+ '
9
14
  allowed-tools: Read, Write, Edit, Grep, Glob, Bash(psql:*), Bash(mysql:*)
10
15
  version: 1.0.0
11
16
  author: Jeremy Longshore <jeremy@intentsolutions.io>
12
17
  license: MIT
13
- compatible-with: claude-code, codex, openclaw
14
- tags: [database, validating-database]
18
+ tags:
19
+ - database
20
+ - validating-database
21
+ compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
15
22
  ---
16
23
  # Data Validation Engine
17
24
 
@@ -97,4 +104,4 @@ Implement and enforce data integrity rules at the database level using CHECK con
97
104
  - PostgreSQL triggers: https://www.postgresql.org/docs/current/triggers.html
98
105
  - MySQL CHECK constraints (8.0.16+): https://dev.mysql.com/doc/refman/8.0/en/create-table-check-constraints.html
99
106
  - Data validation patterns: https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS
100
- - NOT VALID constraint option: https://www.postgresql.org/docs/current/sql-altertable.html
107
+ - NOT VALID constraint option: https://www.postgresql.org/docs/current/sql-altertable.html
@@ -1,4 +1,3 @@
1
1
  # References
2
2
 
3
3
  Bundled resources for data-validation-engine skill
4
-
@@ -10,8 +10,7 @@ import argparse
10
10
  import json
11
11
  import sys
12
12
  from datetime import datetime
13
- from pathlib import Path
14
- from typing import Dict, List, Any, Optional
13
+ from typing import Dict, Any
15
14
 
16
15
 
17
16
  class ValidationRuleConfigurator:
@@ -30,11 +29,9 @@ class ValidationRuleConfigurator:
30
29
  Args:
31
30
  column: Column name
32
31
  """
33
- self.rules.append({
34
- "rule": "not_null",
35
- "column": column,
36
- "description": f"Column {column} must not contain NULL values"
37
- })
32
+ self.rules.append(
33
+ {"rule": "not_null", "column": column, "description": f"Column {column} must not contain NULL values"}
34
+ )
38
35
 
39
36
  def add_unique_rule(self, column: str):
40
37
  """
@@ -43,18 +40,11 @@ class ValidationRuleConfigurator:
43
40
  Args:
44
41
  column: Column name
45
42
  """
46
- self.rules.append({
47
- "rule": "unique",
48
- "column": column,
49
- "description": f"Column {column} must contain unique values"
50
- })
51
-
52
- def add_range_rule(
53
- self,
54
- column: str,
55
- min_value: float,
56
- max_value: float
57
- ):
43
+ self.rules.append(
44
+ {"rule": "unique", "column": column, "description": f"Column {column} must contain unique values"}
45
+ )
46
+
47
+ def add_range_rule(self, column: str, min_value: float, max_value: float):
58
48
  """
59
49
  Add a RANGE validation rule.
60
50
 
@@ -63,13 +53,15 @@ class ValidationRuleConfigurator:
63
53
  min_value: Minimum allowed value
64
54
  max_value: Maximum allowed value
65
55
  """
66
- self.rules.append({
67
- "rule": "range",
68
- "column": column,
69
- "min": min_value,
70
- "max": max_value,
71
- "description": f"Column {column} values must be between {min_value} and {max_value}"
72
- })
56
+ self.rules.append(
57
+ {
58
+ "rule": "range",
59
+ "column": column,
60
+ "min": min_value,
61
+ "max": max_value,
62
+ "description": f"Column {column} values must be between {min_value} and {max_value}",
63
+ }
64
+ )
73
65
 
74
66
  def add_pattern_rule(self, column: str, pattern: str):
75
67
  """
@@ -79,12 +71,14 @@ class ValidationRuleConfigurator:
79
71
  column: Column name
80
72
  pattern: Regular expression pattern
81
73
  """
82
- self.rules.append({
83
- "rule": "pattern",
84
- "column": column,
85
- "pattern": pattern,
86
- "description": f"Column {column} values must match pattern: {pattern}"
87
- })
74
+ self.rules.append(
75
+ {
76
+ "rule": "pattern",
77
+ "column": column,
78
+ "pattern": pattern,
79
+ "description": f"Column {column} values must match pattern: {pattern}",
80
+ }
81
+ )
88
82
 
89
83
  def add_custom_rule(self, column: str, query: str):
90
84
  """
@@ -94,12 +88,9 @@ class ValidationRuleConfigurator:
94
88
  column: Column name
95
89
  query: Custom SQL query
96
90
  """
97
- self.rules.append({
98
- "rule": "custom",
99
- "column": column,
100
- "query": query,
101
- "description": f"Custom validation on {column}"
102
- })
91
+ self.rules.append(
92
+ {"rule": "custom", "column": column, "query": query, "description": f"Custom validation on {column}"}
93
+ )
103
94
 
104
95
  def remove_rule(self, index: int) -> bool:
105
96
  """
@@ -127,7 +118,7 @@ class ValidationRuleConfigurator:
127
118
  "table": self.table_name,
128
119
  "database": self.database,
129
120
  "created_at": datetime.now().isoformat(),
130
- "validations": self.rules
121
+ "validations": self.rules,
131
122
  }
132
123
 
133
124
  def load_config(self, filepath: str) -> bool:
@@ -141,7 +132,7 @@ class ValidationRuleConfigurator:
141
132
  True if successful, False otherwise
142
133
  """
143
134
  try:
144
- with open(filepath, 'r') as f:
135
+ with open(filepath, "r") as f:
145
136
  config = json.load(f)
146
137
 
147
138
  self.table_name = config.get("table", "")
@@ -164,7 +155,7 @@ class ValidationRuleConfigurator:
164
155
  True if successful, False otherwise
165
156
  """
166
157
  try:
167
- with open(filepath, 'w') as f:
158
+ with open(filepath, "w") as f:
168
159
  json.dump(self.get_config_dict(), f, indent=2)
169
160
  return True
170
161
  except Exception as e:
@@ -179,9 +170,9 @@ def interactive_mode(configurator: ValidationRuleConfigurator):
179
170
  Args:
180
171
  configurator: ValidationRuleConfigurator instance
181
172
  """
182
- print("\n" + "="*60)
173
+ print("\n" + "=" * 60)
183
174
  print("Data Validation Rule Configurator")
184
- print("="*60 + "\n")
175
+ print("=" * 60 + "\n")
185
176
 
186
177
  # Get table and database info
187
178
  configurator.table_name = input("Enter table name: ").strip()
@@ -258,9 +249,9 @@ def interactive_mode(configurator: ValidationRuleConfigurator):
258
249
  print("Invalid choice. Please try again.")
259
250
 
260
251
  # Summary and save
261
- print("\n" + "="*60)
252
+ print("\n" + "=" * 60)
262
253
  print("Configuration Summary")
263
- print("="*60)
254
+ print("=" * 60)
264
255
  print(f"Table: {configurator.table_name}")
265
256
  print(f"Database: {configurator.database or '(none specified)'}")
266
257
  print(f"Total Rules: {len(configurator.rules)}\n")
@@ -406,37 +397,16 @@ Examples:
406
397
 
407
398
  # Load and modify existing rules
408
399
  %(prog)s --load rules.json --not-null phone --output rules.json
409
- """
400
+ """,
410
401
  )
411
402
 
412
- parser.add_argument(
413
- "--table",
414
- help="Table name for non-interactive mode"
415
- )
416
- parser.add_argument(
417
- "--database",
418
- help="Database name"
419
- )
420
- parser.add_argument(
421
- "--not-null",
422
- help="Comma-separated columns that must not be NULL"
423
- )
424
- parser.add_argument(
425
- "--unique",
426
- help="Comma-separated columns that must be unique"
427
- )
428
- parser.add_argument(
429
- "--range",
430
- help="Range validations in format: col:min:max,col2:min2:max2"
431
- )
432
- parser.add_argument(
433
- "--load",
434
- help="Load existing configuration file"
435
- )
436
- parser.add_argument(
437
- "--output",
438
- help="Output file for configuration (JSON)"
439
- )
403
+ parser.add_argument("--table", help="Table name for non-interactive mode")
404
+ parser.add_argument("--database", help="Database name")
405
+ parser.add_argument("--not-null", help="Comma-separated columns that must not be NULL")
406
+ parser.add_argument("--unique", help="Comma-separated columns that must be unique")
407
+ parser.add_argument("--range", help="Range validations in format: col:min:max,col2:min2:max2")
408
+ parser.add_argument("--load", help="Load existing configuration file")
409
+ parser.add_argument("--output", help="Output file for configuration (JSON)")
440
410
 
441
411
  args = parser.parse_args()
442
412
 
@@ -10,8 +10,7 @@ import argparse
10
10
  import json
11
11
  import sys
12
12
  from datetime import datetime
13
- from pathlib import Path
14
- from typing import Dict, List, Any, Optional
13
+ from typing import Dict, Any
15
14
 
16
15
 
17
16
  class ValidationReportGenerator:
@@ -33,14 +32,14 @@ class ValidationReportGenerator:
33
32
  True if successful, False otherwise
34
33
  """
35
34
  try:
36
- with open(filepath, 'r') as f:
35
+ with open(filepath, "r") as f:
37
36
  data = json.load(f)
38
37
 
39
38
  self.results = data.get("validations", [])
40
39
  self.metadata = {
41
40
  "table": data.get("table", "unknown"),
42
41
  "timestamp": data.get("timestamp", datetime.now().isoformat()),
43
- "statistics": data.get("statistics", {})
42
+ "statistics": data.get("statistics", {}),
44
43
  }
45
44
 
46
45
  return True
@@ -56,13 +55,7 @@ class ValidationReportGenerator:
56
55
  Dictionary with summary stats
57
56
  """
58
57
  if not self.results:
59
- return {
60
- "total": 0,
61
- "passed": 0,
62
- "failed": 0,
63
- "pass_rate": 0.0,
64
- "issues": []
65
- }
58
+ return {"total": 0, "passed": 0, "failed": 0, "pass_rate": 0.0, "issues": []}
66
59
 
67
60
  total = len(self.results)
68
61
  passed = sum(1 for r in self.results if r.get("valid", False))
@@ -72,11 +65,13 @@ class ValidationReportGenerator:
72
65
  for result in self.results:
73
66
  if not result.get("valid", False):
74
67
  details = result.get("details", {})
75
- issues.append({
76
- "rule": details.get("rule", "unknown"),
77
- "column": details.get("column", "unknown"),
78
- "severity": self._determine_severity(details.get("rule"))
79
- })
68
+ issues.append(
69
+ {
70
+ "rule": details.get("rule", "unknown"),
71
+ "column": details.get("column", "unknown"),
72
+ "severity": self._determine_severity(details.get("rule")),
73
+ }
74
+ )
80
75
 
81
76
  return {
82
77
  "total": total,
@@ -86,7 +81,7 @@ class ValidationReportGenerator:
86
81
  "issues": issues,
87
82
  "critical_issues": sum(1 for i in issues if i["severity"] == "critical"),
88
83
  "high_issues": sum(1 for i in issues if i["severity"] == "high"),
89
- "medium_issues": sum(1 for i in issues if i["severity"] == "medium")
84
+ "medium_issues": sum(1 for i in issues if i["severity"] == "medium"),
90
85
  }
91
86
 
92
87
  def _determine_severity(self, rule: str) -> str:
@@ -120,7 +115,7 @@ class ValidationReportGenerator:
120
115
  "metadata": self.metadata,
121
116
  "summary": summary,
122
117
  "timestamp": datetime.now().isoformat(),
123
- "validations": self.results
118
+ "validations": self.results,
124
119
  }
125
120
 
126
121
  return json.dumps(report, indent=2)
@@ -130,15 +125,15 @@ class ValidationReportGenerator:
130
125
  summary = self.generate_summary()
131
126
 
132
127
  md = []
133
- md.append(f"# Data Validation Report")
128
+ md.append("# Data Validation Report")
134
129
  md.append(f"\n**Table:** {self.metadata.get('table', 'Unknown')}")
135
130
  md.append(f"**Generated:** {datetime.now().isoformat()}")
136
131
  md.append("")
137
132
 
138
133
  # Summary section
139
134
  md.append("## Executive Summary\n")
140
- md.append(f"| Metric | Value |")
141
- md.append(f"|--------|-------|")
135
+ md.append("| Metric | Value |")
136
+ md.append("|--------|-------|")
142
137
  md.append(f"| Total Checks | {summary['total']} |")
143
138
  md.append(f"| Passed | {summary['passed']} |")
144
139
  md.append(f"| Failed | {summary['failed']} |")
@@ -154,32 +149,32 @@ class ValidationReportGenerator:
154
149
  md.append("")
155
150
 
156
151
  # Issues section
157
- if summary['failed'] > 0:
152
+ if summary["failed"] > 0:
158
153
  md.append("## Issues Identified\n")
159
154
 
160
- if summary['critical_issues'] > 0:
155
+ if summary["critical_issues"] > 0:
161
156
  md.append("### Critical Issues\n")
162
- for issue in summary['issues']:
163
- if issue['severity'] == 'critical':
157
+ for issue in summary["issues"]:
158
+ if issue["severity"] == "critical":
164
159
  md.append(f"- **{issue['rule']}** on column `{issue['column']}`")
165
160
  md.append("")
166
161
 
167
- if summary['high_issues'] > 0:
162
+ if summary["high_issues"] > 0:
168
163
  md.append("### High Priority Issues\n")
169
- for issue in summary['issues']:
170
- if issue['severity'] == 'high':
164
+ for issue in summary["issues"]:
165
+ if issue["severity"] == "high":
171
166
  md.append(f"- **{issue['rule']}** on column `{issue['column']}`")
172
167
  md.append("")
173
168
 
174
- if summary['medium_issues'] > 0:
169
+ if summary["medium_issues"] > 0:
175
170
  md.append("### Medium Priority Issues\n")
176
- for issue in summary['issues']:
177
- if issue['severity'] == 'medium':
171
+ for issue in summary["issues"]:
172
+ if issue["severity"] == "medium":
178
173
  md.append(f"- **{issue['rule']}** on column `{issue['column']}`")
179
174
  md.append("")
180
175
 
181
176
  # Detailed results
182
- if summary['failed'] > 0:
177
+ if summary["failed"] > 0:
183
178
  md.append("## Detailed Validation Results\n")
184
179
  for result in self.results:
185
180
  if not result.get("valid", False):
@@ -194,19 +189,20 @@ class ValidationReportGenerator:
194
189
  elif rule == "not_null":
195
190
  md.append(f"**Issue:** Found {details.get('null_count', 0)} NULL values\n")
196
191
  elif rule == "unique":
197
- md.append(f"**Issue:** Found {details.get('duplicate_count', 0)} "
198
- f"duplicate groups\n")
192
+ md.append(f"**Issue:** Found {details.get('duplicate_count', 0)} duplicate groups\n")
199
193
  elif rule == "range":
200
- md.append(f"**Issue:** {details.get('out_of_range_count', 0)} values "
201
- f"outside range [{details.get('min')}, {details.get('max')}]\n")
194
+ md.append(
195
+ f"**Issue:** {details.get('out_of_range_count', 0)} values "
196
+ f"outside range [{details.get('min')}, {details.get('max')}]\n"
197
+ )
202
198
 
203
199
  # Recommendations
204
200
  md.append("## Recommendations\n")
205
- if summary['critical_issues'] > 0:
201
+ if summary["critical_issues"] > 0:
206
202
  md.append("1. **Immediately address critical issues** - These indicate data integrity problems")
207
- if summary['high_issues'] > 0:
203
+ if summary["high_issues"] > 0:
208
204
  md.append("2. **Resolve high priority issues within 1 week** - These affect data quality")
209
- if summary['failed'] == 0:
205
+ if summary["failed"] == 0:
210
206
  md.append("✅ **All validations passed!** - Data integrity is maintained")
211
207
  else:
212
208
  md.append("3. **Review validation rules** - Ensure they match business requirements")
@@ -254,18 +250,26 @@ class ValidationReportGenerator:
254
250
  "</head>",
255
251
  "<body>",
256
252
  "<div class='container'>",
257
- f"<h1>Data Validation Report</h1>",
253
+ "<h1>Data Validation Report</h1>",
258
254
  f"<p><strong>Table:</strong> {self.metadata.get('table', 'Unknown')}</p>",
259
- f"<p><strong>Generated:</strong> {datetime.now().isoformat()}</p>"
255
+ f"<p><strong>Generated:</strong> {datetime.now().isoformat()}</p>",
260
256
  ]
261
257
 
262
258
  # Summary metrics
263
259
  html.append("<h2>Summary</h2>")
264
260
  html.append("<div class='summary'>")
265
- html.append(f"<div class='metric'><div class='value'>{summary['total']}</div><div class='label'>Total Checks</div></div>")
266
- html.append(f"<div class='metric'><div class='value success'>{summary['passed']}</div><div class='label'>Passed</div></div>")
267
- html.append(f"<div class='metric'><div class='value critical'>{summary['failed']}</div><div class='label'>Failed</div></div>")
268
- html.append(f"<div class='metric'><div class='value'>{summary['pass_rate']:.1f}%</div><div class='label'>Pass Rate</div></div>")
261
+ html.append(
262
+ f"<div class='metric'><div class='value'>{summary['total']}</div><div class='label'>Total Checks</div></div>"
263
+ )
264
+ html.append(
265
+ f"<div class='metric'><div class='value success'>{summary['passed']}</div><div class='label'>Passed</div></div>"
266
+ )
267
+ html.append(
268
+ f"<div class='metric'><div class='value critical'>{summary['failed']}</div><div class='label'>Failed</div></div>"
269
+ )
270
+ html.append(
271
+ f"<div class='metric'><div class='value'>{summary['pass_rate']:.1f}%</div><div class='label'>Pass Rate</div></div>"
272
+ )
269
273
  html.append("</div>")
270
274
 
271
275
  # Table statistics
@@ -278,12 +282,12 @@ class ValidationReportGenerator:
278
282
  html.append("</table>")
279
283
 
280
284
  # Issues
281
- if summary['failed'] > 0:
285
+ if summary["failed"] > 0:
282
286
  html.append("<h2>Issues</h2>")
283
287
  html.append("<div class='issue-list'>")
284
288
 
285
- for issue in summary['issues']:
286
- severity_class = issue['severity']
289
+ for issue in summary["issues"]:
290
+ severity_class = issue["severity"]
287
291
  html.append(
288
292
  f"<div class='issue'>"
289
293
  f"<span class='{severity_class}'>{issue['severity'].upper()}</span>: "
@@ -319,29 +323,13 @@ Examples:
319
323
  %(prog)s --results validation.json --format markdown
320
324
  %(prog)s --results validation.json --format html --output report.html
321
325
  %(prog)s --results validation.json --format json --output report.json
322
- """
326
+ """,
323
327
  )
324
328
 
325
- parser.add_argument(
326
- "--results",
327
- required=True,
328
- help="Path to JSON file containing validation results"
329
- )
330
- parser.add_argument(
331
- "--format",
332
- default="markdown",
333
- choices=["json", "markdown", "html"],
334
- help="Report format"
335
- )
336
- parser.add_argument(
337
- "--output",
338
- help="Output file for report"
339
- )
340
- parser.add_argument(
341
- "--verbose",
342
- action="store_true",
343
- help="Print detailed output"
344
- )
329
+ parser.add_argument("--results", required=True, help="Path to JSON file containing validation results")
330
+ parser.add_argument("--format", default="markdown", choices=["json", "markdown", "html"], help="Report format")
331
+ parser.add_argument("--output", help="Output file for report")
332
+ parser.add_argument("--verbose", action="store_true", help="Print detailed output")
345
333
 
346
334
  args = parser.parse_args()
347
335
 
@@ -368,7 +356,7 @@ Examples:
368
356
 
369
357
  # Save to file if requested
370
358
  if args.output:
371
- with open(args.output, 'w') as f:
359
+ with open(args.output, "w") as f:
372
360
  f.write(report)
373
361
 
374
362
  if args.verbose: