@ipation/specbridge 1.2.1 → 2.0.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ipation/specbridge",
3
- "version": "1.2.1",
3
+ "version": "2.0.0",
4
4
  "description": "Architecture Decision Runtime - Transform architectural decisions into executable, verifiable constraints",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Example Custom Verifier Plugin
3
+ *
4
+ * This template demonstrates how to create a custom verifier for SpecBridge.
5
+ * Copy this file to your project's .specbridge/verifiers/ directory and customize it.
6
+ *
7
+ * Usage:
8
+ * 1. Copy to .specbridge/verifiers/my-custom.ts
9
+ * 2. Implement your verification logic
10
+ * 3. Reference in decisions: check.verifier: 'my-custom'
11
+ * 4. Run: specbridge verify
12
+ */
13
+
14
+ import {
15
+ defineVerifierPlugin,
16
+ createViolation,
17
+ type Verifier,
18
+ type VerificationContext,
19
+ type Violation,
20
+ } from '@ipation/specbridge';
21
+ import { z } from 'zod';
22
+ import { SyntaxKind } from 'ts-morph';
23
+
24
+ /**
25
+ * Define parameter schema for this verifier
26
+ * This validates the params passed in constraint.check.params
27
+ */
28
+ const ParamsSchema = z.object({
29
+ // Example: Maximum allowed length
30
+ maxLength: z.number().positive().optional().default(100),
31
+
32
+ // Example: Pattern to match/avoid
33
+ pattern: z.string().optional(),
34
+
35
+ // Example: Case sensitivity for pattern matching
36
+ caseSensitive: z.boolean().optional().default(false),
37
+ });
38
+
39
+ type Params = z.infer<typeof ParamsSchema>;
40
+
41
+ /**
42
+ * Custom Verifier Implementation
43
+ */
44
+ class MyCustomVerifier implements Verifier {
45
+ readonly id = 'my-custom';
46
+ readonly name = 'My Custom Verifier';
47
+ readonly description = 'Custom verification logic for project-specific patterns';
48
+
49
+ async verify(ctx: VerificationContext): Promise<Violation[]> {
50
+ const violations: Violation[] = [];
51
+
52
+ // Parse and validate parameters
53
+ const params: Params = ParamsSchema.parse(ctx.constraint.check?.params || {});
54
+
55
+ // Get the AST source file (ts-morph)
56
+ const sourceFile = ctx.sourceFile;
57
+
58
+ // Example 1: Check file length
59
+ const lineCount = sourceFile.getEndLineNumber();
60
+ if (lineCount > params.maxLength) {
61
+ violations.push(
62
+ createViolation({
63
+ decisionId: ctx.decisionId,
64
+ constraintId: ctx.constraint.id,
65
+ type: ctx.constraint.type,
66
+ severity: ctx.constraint.severity,
67
+ message: `File exceeds maximum length of ${params.maxLength} lines (found ${lineCount})`,
68
+ file: ctx.filePath,
69
+ line: 1,
70
+ suggestion: `Consider splitting this file into smaller modules`,
71
+ })
72
+ );
73
+ }
74
+
75
+ // Example 2: Check for specific patterns in identifiers
76
+ if (params.pattern) {
77
+ const regex = new RegExp(
78
+ params.pattern,
79
+ params.caseSensitive ? '' : 'i'
80
+ );
81
+
82
+ // Find all variable declarations
83
+ const variables = sourceFile.getVariableDeclarations();
84
+
85
+ for (const variable of variables) {
86
+ const name = variable.getName();
87
+
88
+ if (regex.test(name)) {
89
+ const startLine = variable.getStartLineNumber();
90
+
91
+ violations.push(
92
+ createViolation({
93
+ decisionId: ctx.decisionId,
94
+ constraintId: ctx.constraint.id,
95
+ type: ctx.constraint.type,
96
+ severity: ctx.constraint.severity,
97
+ message: `Variable "${name}" matches forbidden pattern: ${params.pattern}`,
98
+ file: ctx.filePath,
99
+ line: startLine,
100
+ suggestion: `Rename this variable to avoid the pattern`,
101
+ })
102
+ );
103
+ }
104
+ }
105
+ }
106
+
107
+ // Example 3: Check for specific AST patterns
108
+ // Find all function declarations with more than N parameters
109
+ const functions = sourceFile.getFunctions();
110
+
111
+ for (const func of functions) {
112
+ const paramCount = func.getParameters().length;
113
+
114
+ if (paramCount > 5) {
115
+ // Example threshold
116
+ violations.push(
117
+ createViolation({
118
+ decisionId: ctx.decisionId,
119
+ constraintId: ctx.constraint.id,
120
+ type: ctx.constraint.type,
121
+ severity: ctx.constraint.severity,
122
+ message: `Function "${func.getName() || '<anonymous>'}" has too many parameters (${paramCount})`,
123
+ file: ctx.filePath,
124
+ line: func.getStartLineNumber(),
125
+ suggestion: `Consider using an options object or splitting the function`,
126
+ })
127
+ );
128
+ }
129
+ }
130
+
131
+ // Example 4: Check for specific import patterns
132
+ const imports = sourceFile.getImportDeclarations();
133
+
134
+ for (const importDecl of imports) {
135
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
136
+
137
+ // Example: Forbid relative imports that go up more than one level
138
+ if (moduleSpecifier.startsWith('../..')) {
139
+ violations.push(
140
+ createViolation({
141
+ decisionId: ctx.decisionId,
142
+ constraintId: ctx.constraint.id,
143
+ type: ctx.constraint.type,
144
+ severity: ctx.constraint.severity,
145
+ message: `Deep relative import detected: ${moduleSpecifier}`,
146
+ file: ctx.filePath,
147
+ line: importDecl.getStartLineNumber(),
148
+ suggestion: `Use path aliases or refactor module structure`,
149
+ })
150
+ );
151
+ }
152
+ }
153
+
154
+ // Example 5: Check for specific syntax usage
155
+ // Find all console.log statements
156
+ const callExpressions = sourceFile.getDescendantsOfKind(
157
+ SyntaxKind.CallExpression
158
+ );
159
+
160
+ for (const call of callExpressions) {
161
+ const expression = call.getExpression();
162
+
163
+ if (
164
+ expression.getKind() === SyntaxKind.PropertyAccessExpression
165
+ ) {
166
+ const propAccess = expression.asKindOrThrow(
167
+ SyntaxKind.PropertyAccessExpression
168
+ );
169
+ const obj = propAccess.getExpression().getText();
170
+ const prop = propAccess.getName();
171
+
172
+ if (obj === 'console' && prop === 'log') {
173
+ violations.push(
174
+ createViolation({
175
+ decisionId: ctx.decisionId,
176
+ constraintId: ctx.constraint.id,
177
+ type: ctx.constraint.type,
178
+ severity: 'low',
179
+ message: `console.log() statement found`,
180
+ file: ctx.filePath,
181
+ line: call.getStartLineNumber(),
182
+ suggestion: `Remove debug logging or use a proper logger`,
183
+ })
184
+ );
185
+ }
186
+ }
187
+ }
188
+
189
+ return violations;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Export the plugin definition
195
+ * This is what SpecBridge will load
196
+ */
197
+ export default defineVerifierPlugin({
198
+ metadata: {
199
+ id: 'my-custom',
200
+ version: '1.0.0',
201
+ author: 'Your Name',
202
+ description: 'Custom verifier for project-specific patterns',
203
+ },
204
+ createVerifier: () => new MyCustomVerifier(),
205
+ paramsSchema: ParamsSchema,
206
+ });