@ipation/specbridge 2.0.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/dist/cli.js +99 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +91 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/verifiers/example-custom.ts +5 -0
package/dist/index.d.ts
CHANGED
|
@@ -179,7 +179,7 @@ interface LevelConfig {
|
|
|
179
179
|
* Warning during verification (non-blocking)
|
|
180
180
|
*/
|
|
181
181
|
interface VerificationWarning {
|
|
182
|
-
type: 'missing_verifier' | 'invalid_pattern' | 'other';
|
|
182
|
+
type: 'missing_verifier' | 'invalid_pattern' | 'invalid_params' | 'other';
|
|
183
183
|
message: string;
|
|
184
184
|
decisionId: string;
|
|
185
185
|
constraintId: string;
|
|
@@ -1540,7 +1540,7 @@ declare class VerificationEngine {
|
|
|
1540
1540
|
/**
|
|
1541
1541
|
* Verify a single file
|
|
1542
1542
|
*/
|
|
1543
|
-
verifyFile(filePath: string, decisions: Decision[], severityFilter?: Severity[], cwd?: string, reporter?: ExplainReporter): Promise<{
|
|
1543
|
+
verifyFile(filePath: string, decisions: Decision[], severityFilter?: Severity[], cwd?: string, reporter?: ExplainReporter, signal?: AbortSignal): Promise<{
|
|
1544
1544
|
violations: Violation[];
|
|
1545
1545
|
warnings: VerificationWarning[];
|
|
1546
1546
|
errors: VerificationIssue[];
|
|
@@ -1595,6 +1595,8 @@ interface VerificationContext {
|
|
|
1595
1595
|
sourceFile: SourceFile;
|
|
1596
1596
|
constraint: Constraint;
|
|
1597
1597
|
decisionId: string;
|
|
1598
|
+
/** Optional AbortSignal for cancellation support */
|
|
1599
|
+
signal?: AbortSignal;
|
|
1598
1600
|
}
|
|
1599
1601
|
/**
|
|
1600
1602
|
* Verifier interface - all verifiers must implement this
|
package/dist/index.js
CHANGED
|
@@ -7654,7 +7654,7 @@ var PluginLoader = class {
|
|
|
7654
7654
|
}
|
|
7655
7655
|
this.loaded = true;
|
|
7656
7656
|
if (this.plugins.size > 0) {
|
|
7657
|
-
console.
|
|
7657
|
+
console.error(`Loaded ${this.plugins.size} custom verifier(s)`);
|
|
7658
7658
|
}
|
|
7659
7659
|
if (this.loadErrors.length > 0) {
|
|
7660
7660
|
console.warn(`Failed to load ${this.loadErrors.length} plugin(s)`);
|
|
@@ -7676,7 +7676,7 @@ var PluginLoader = class {
|
|
|
7676
7676
|
`Plugin ID "${plugin.metadata.id}" is already registered. Each plugin must have a unique ID.`
|
|
7677
7677
|
);
|
|
7678
7678
|
}
|
|
7679
|
-
this.plugins.set(plugin.metadata.id, plugin
|
|
7679
|
+
this.plugins.set(plugin.metadata.id, plugin);
|
|
7680
7680
|
}
|
|
7681
7681
|
/**
|
|
7682
7682
|
* Validate plugin structure and metadata
|
|
@@ -7735,8 +7735,55 @@ var PluginLoader = class {
|
|
|
7735
7735
|
* @returns Verifier instance or null if not found
|
|
7736
7736
|
*/
|
|
7737
7737
|
getVerifier(id) {
|
|
7738
|
-
const
|
|
7739
|
-
return
|
|
7738
|
+
const plugin = this.plugins.get(id);
|
|
7739
|
+
return plugin ? plugin.createVerifier() : null;
|
|
7740
|
+
}
|
|
7741
|
+
/**
|
|
7742
|
+
* Get a plugin by ID (includes paramsSchema)
|
|
7743
|
+
*
|
|
7744
|
+
* @param id - Plugin ID
|
|
7745
|
+
* @returns Plugin or null if not found
|
|
7746
|
+
*/
|
|
7747
|
+
getPlugin(id) {
|
|
7748
|
+
return this.plugins.get(id) || null;
|
|
7749
|
+
}
|
|
7750
|
+
/**
|
|
7751
|
+
* Validate params against a plugin's paramsSchema
|
|
7752
|
+
*
|
|
7753
|
+
* @param id - Plugin ID
|
|
7754
|
+
* @param params - Parameters to validate
|
|
7755
|
+
* @returns Validation result with success flag and error message if failed
|
|
7756
|
+
*/
|
|
7757
|
+
validateParams(id, params) {
|
|
7758
|
+
const plugin = this.plugins.get(id);
|
|
7759
|
+
if (!plugin) {
|
|
7760
|
+
return { success: false, error: `Plugin ${id} not found` };
|
|
7761
|
+
}
|
|
7762
|
+
if (!plugin.paramsSchema) {
|
|
7763
|
+
return { success: true };
|
|
7764
|
+
}
|
|
7765
|
+
if (!params) {
|
|
7766
|
+
return { success: false, error: `Plugin ${id} requires params but none were provided` };
|
|
7767
|
+
}
|
|
7768
|
+
if (typeof plugin.paramsSchema !== "object" || !plugin.paramsSchema || !("parse" in plugin.paramsSchema)) {
|
|
7769
|
+
return { success: false, error: `Plugin ${id} has invalid paramsSchema (must be a Zod schema)` };
|
|
7770
|
+
}
|
|
7771
|
+
const schema = plugin.paramsSchema;
|
|
7772
|
+
if (schema.safeParse) {
|
|
7773
|
+
const result = schema.safeParse(params);
|
|
7774
|
+
if (!result.success) {
|
|
7775
|
+
const errors = result.error?.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ") || "Validation failed";
|
|
7776
|
+
return { success: false, error: `Invalid params for ${id}: ${errors}` };
|
|
7777
|
+
}
|
|
7778
|
+
} else {
|
|
7779
|
+
try {
|
|
7780
|
+
schema.parse(params);
|
|
7781
|
+
} catch (error) {
|
|
7782
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7783
|
+
return { success: false, error: `Invalid params for ${id}: ${message}` };
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
return { success: true };
|
|
7740
7787
|
}
|
|
7741
7788
|
/**
|
|
7742
7789
|
* Get all registered plugin IDs
|
|
@@ -8054,9 +8101,13 @@ var VerificationEngine = class {
|
|
|
8054
8101
|
let passed = 0;
|
|
8055
8102
|
let failed = 0;
|
|
8056
8103
|
const skipped = 0;
|
|
8104
|
+
const abortController = new AbortController();
|
|
8057
8105
|
let timeoutHandle = null;
|
|
8058
8106
|
const timeoutPromise = new Promise((resolve2) => {
|
|
8059
|
-
timeoutHandle = setTimeout(() =>
|
|
8107
|
+
timeoutHandle = setTimeout(() => {
|
|
8108
|
+
abortController.abort();
|
|
8109
|
+
resolve2("timeout");
|
|
8110
|
+
}, timeout);
|
|
8060
8111
|
timeoutHandle.unref();
|
|
8061
8112
|
});
|
|
8062
8113
|
const verificationPromise = this.verifyFiles(
|
|
@@ -8065,6 +8116,7 @@ var VerificationEngine = class {
|
|
|
8065
8116
|
severityFilter,
|
|
8066
8117
|
cwd,
|
|
8067
8118
|
options.reporter,
|
|
8119
|
+
abortController.signal,
|
|
8068
8120
|
(violations, warnings, errors) => {
|
|
8069
8121
|
allViolations.push(...violations);
|
|
8070
8122
|
allWarnings.push(...warnings);
|
|
@@ -8123,10 +8175,13 @@ var VerificationEngine = class {
|
|
|
8123
8175
|
/**
|
|
8124
8176
|
* Verify a single file
|
|
8125
8177
|
*/
|
|
8126
|
-
async verifyFile(filePath, decisions, severityFilter, cwd = process.cwd(), reporter) {
|
|
8178
|
+
async verifyFile(filePath, decisions, severityFilter, cwd = process.cwd(), reporter, signal) {
|
|
8127
8179
|
const violations = [];
|
|
8128
8180
|
const warnings = [];
|
|
8129
8181
|
const errors = [];
|
|
8182
|
+
if (signal?.aborted) {
|
|
8183
|
+
return { violations, warnings, errors };
|
|
8184
|
+
}
|
|
8130
8185
|
const sourceFile = await this.astCache.get(filePath, this.project);
|
|
8131
8186
|
if (!sourceFile) return { violations, warnings, errors };
|
|
8132
8187
|
let fileHash = null;
|
|
@@ -8207,11 +8262,35 @@ var VerificationEngine = class {
|
|
|
8207
8262
|
continue;
|
|
8208
8263
|
}
|
|
8209
8264
|
}
|
|
8265
|
+
if (constraint.check?.verifier && constraint.check?.params) {
|
|
8266
|
+
const pluginLoader2 = getPluginLoader();
|
|
8267
|
+
const validationResult = pluginLoader2.validateParams(constraint.check.verifier, constraint.check.params);
|
|
8268
|
+
if (!validationResult.success) {
|
|
8269
|
+
warnings.push({
|
|
8270
|
+
type: "invalid_params",
|
|
8271
|
+
message: validationResult.error,
|
|
8272
|
+
decisionId: decision.metadata.id,
|
|
8273
|
+
constraintId: constraint.id,
|
|
8274
|
+
file: filePath
|
|
8275
|
+
});
|
|
8276
|
+
if (reporter) {
|
|
8277
|
+
reporter.add({
|
|
8278
|
+
file: filePath,
|
|
8279
|
+
decision,
|
|
8280
|
+
constraint,
|
|
8281
|
+
applied: false,
|
|
8282
|
+
reason: `Params validation failed: ${validationResult.error}`
|
|
8283
|
+
});
|
|
8284
|
+
}
|
|
8285
|
+
continue;
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8210
8288
|
const ctx = {
|
|
8211
8289
|
filePath,
|
|
8212
8290
|
sourceFile,
|
|
8213
8291
|
constraint,
|
|
8214
|
-
decisionId: decision.metadata.id
|
|
8292
|
+
decisionId: decision.metadata.id,
|
|
8293
|
+
signal
|
|
8215
8294
|
};
|
|
8216
8295
|
const verificationStart = Date.now();
|
|
8217
8296
|
try {
|
|
@@ -8287,12 +8366,15 @@ var VerificationEngine = class {
|
|
|
8287
8366
|
/**
|
|
8288
8367
|
* Verify multiple files
|
|
8289
8368
|
*/
|
|
8290
|
-
async verifyFiles(files, decisions, severityFilter, cwd, reporter, onFileVerified) {
|
|
8369
|
+
async verifyFiles(files, decisions, severityFilter, cwd, reporter, signal, onFileVerified) {
|
|
8291
8370
|
const BATCH_SIZE = 50;
|
|
8292
8371
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
8372
|
+
if (signal.aborted) {
|
|
8373
|
+
break;
|
|
8374
|
+
}
|
|
8293
8375
|
const batch = files.slice(i, i + BATCH_SIZE);
|
|
8294
8376
|
const results = await Promise.all(
|
|
8295
|
-
batch.map((file) => this.verifyFile(file, decisions, severityFilter, cwd, reporter))
|
|
8377
|
+
batch.map((file) => this.verifyFile(file, decisions, severityFilter, cwd, reporter, signal))
|
|
8296
8378
|
);
|
|
8297
8379
|
for (const result of results) {
|
|
8298
8380
|
onFileVerified(result.violations, result.warnings, result.errors);
|