@ipation/specbridge 1.3.0 → 2.1.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.3.0",
3
+ "version": "2.1.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,211 @@
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
+ // Check if verification was cancelled (optional, for long-running verifiers)
53
+ if (ctx.signal?.aborted) {
54
+ return violations;
55
+ }
56
+
57
+ // Parse and validate parameters
58
+ const params: Params = ParamsSchema.parse(ctx.constraint.check?.params || {});
59
+
60
+ // Get the AST source file (ts-morph)
61
+ const sourceFile = ctx.sourceFile;
62
+
63
+ // Example 1: Check file length
64
+ const lineCount = sourceFile.getEndLineNumber();
65
+ if (lineCount > params.maxLength) {
66
+ violations.push(
67
+ createViolation({
68
+ decisionId: ctx.decisionId,
69
+ constraintId: ctx.constraint.id,
70
+ type: ctx.constraint.type,
71
+ severity: ctx.constraint.severity,
72
+ message: `File exceeds maximum length of ${params.maxLength} lines (found ${lineCount})`,
73
+ file: ctx.filePath,
74
+ line: 1,
75
+ suggestion: `Consider splitting this file into smaller modules`,
76
+ })
77
+ );
78
+ }
79
+
80
+ // Example 2: Check for specific patterns in identifiers
81
+ if (params.pattern) {
82
+ const regex = new RegExp(
83
+ params.pattern,
84
+ params.caseSensitive ? '' : 'i'
85
+ );
86
+
87
+ // Find all variable declarations
88
+ const variables = sourceFile.getVariableDeclarations();
89
+
90
+ for (const variable of variables) {
91
+ const name = variable.getName();
92
+
93
+ if (regex.test(name)) {
94
+ const startLine = variable.getStartLineNumber();
95
+
96
+ violations.push(
97
+ createViolation({
98
+ decisionId: ctx.decisionId,
99
+ constraintId: ctx.constraint.id,
100
+ type: ctx.constraint.type,
101
+ severity: ctx.constraint.severity,
102
+ message: `Variable "${name}" matches forbidden pattern: ${params.pattern}`,
103
+ file: ctx.filePath,
104
+ line: startLine,
105
+ suggestion: `Rename this variable to avoid the pattern`,
106
+ })
107
+ );
108
+ }
109
+ }
110
+ }
111
+
112
+ // Example 3: Check for specific AST patterns
113
+ // Find all function declarations with more than N parameters
114
+ const functions = sourceFile.getFunctions();
115
+
116
+ for (const func of functions) {
117
+ const paramCount = func.getParameters().length;
118
+
119
+ if (paramCount > 5) {
120
+ // Example threshold
121
+ violations.push(
122
+ createViolation({
123
+ decisionId: ctx.decisionId,
124
+ constraintId: ctx.constraint.id,
125
+ type: ctx.constraint.type,
126
+ severity: ctx.constraint.severity,
127
+ message: `Function "${func.getName() || '<anonymous>'}" has too many parameters (${paramCount})`,
128
+ file: ctx.filePath,
129
+ line: func.getStartLineNumber(),
130
+ suggestion: `Consider using an options object or splitting the function`,
131
+ })
132
+ );
133
+ }
134
+ }
135
+
136
+ // Example 4: Check for specific import patterns
137
+ const imports = sourceFile.getImportDeclarations();
138
+
139
+ for (const importDecl of imports) {
140
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
141
+
142
+ // Example: Forbid relative imports that go up more than one level
143
+ if (moduleSpecifier.startsWith('../..')) {
144
+ violations.push(
145
+ createViolation({
146
+ decisionId: ctx.decisionId,
147
+ constraintId: ctx.constraint.id,
148
+ type: ctx.constraint.type,
149
+ severity: ctx.constraint.severity,
150
+ message: `Deep relative import detected: ${moduleSpecifier}`,
151
+ file: ctx.filePath,
152
+ line: importDecl.getStartLineNumber(),
153
+ suggestion: `Use path aliases or refactor module structure`,
154
+ })
155
+ );
156
+ }
157
+ }
158
+
159
+ // Example 5: Check for specific syntax usage
160
+ // Find all console.log statements
161
+ const callExpressions = sourceFile.getDescendantsOfKind(
162
+ SyntaxKind.CallExpression
163
+ );
164
+
165
+ for (const call of callExpressions) {
166
+ const expression = call.getExpression();
167
+
168
+ if (
169
+ expression.getKind() === SyntaxKind.PropertyAccessExpression
170
+ ) {
171
+ const propAccess = expression.asKindOrThrow(
172
+ SyntaxKind.PropertyAccessExpression
173
+ );
174
+ const obj = propAccess.getExpression().getText();
175
+ const prop = propAccess.getName();
176
+
177
+ if (obj === 'console' && prop === 'log') {
178
+ violations.push(
179
+ createViolation({
180
+ decisionId: ctx.decisionId,
181
+ constraintId: ctx.constraint.id,
182
+ type: ctx.constraint.type,
183
+ severity: 'low',
184
+ message: `console.log() statement found`,
185
+ file: ctx.filePath,
186
+ line: call.getStartLineNumber(),
187
+ suggestion: `Remove debug logging or use a proper logger`,
188
+ })
189
+ );
190
+ }
191
+ }
192
+ }
193
+
194
+ return violations;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Export the plugin definition
200
+ * This is what SpecBridge will load
201
+ */
202
+ export default defineVerifierPlugin({
203
+ metadata: {
204
+ id: 'my-custom',
205
+ version: '1.0.0',
206
+ author: 'Your Name',
207
+ description: 'Custom verifier for project-specific patterns',
208
+ },
209
+ createVerifier: () => new MyCustomVerifier(),
210
+ paramsSchema: ParamsSchema,
211
+ });