@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/cli.js
CHANGED
|
@@ -7748,7 +7748,7 @@ var PluginLoader = class {
|
|
|
7748
7748
|
}
|
|
7749
7749
|
this.loaded = true;
|
|
7750
7750
|
if (this.plugins.size > 0) {
|
|
7751
|
-
console.
|
|
7751
|
+
console.error(`Loaded ${this.plugins.size} custom verifier(s)`);
|
|
7752
7752
|
}
|
|
7753
7753
|
if (this.loadErrors.length > 0) {
|
|
7754
7754
|
console.warn(`Failed to load ${this.loadErrors.length} plugin(s)`);
|
|
@@ -7770,7 +7770,7 @@ var PluginLoader = class {
|
|
|
7770
7770
|
`Plugin ID "${plugin.metadata.id}" is already registered. Each plugin must have a unique ID.`
|
|
7771
7771
|
);
|
|
7772
7772
|
}
|
|
7773
|
-
this.plugins.set(plugin.metadata.id, plugin
|
|
7773
|
+
this.plugins.set(plugin.metadata.id, plugin);
|
|
7774
7774
|
}
|
|
7775
7775
|
/**
|
|
7776
7776
|
* Validate plugin structure and metadata
|
|
@@ -7829,8 +7829,55 @@ var PluginLoader = class {
|
|
|
7829
7829
|
* @returns Verifier instance or null if not found
|
|
7830
7830
|
*/
|
|
7831
7831
|
getVerifier(id) {
|
|
7832
|
-
const
|
|
7833
|
-
return
|
|
7832
|
+
const plugin = this.plugins.get(id);
|
|
7833
|
+
return plugin ? plugin.createVerifier() : null;
|
|
7834
|
+
}
|
|
7835
|
+
/**
|
|
7836
|
+
* Get a plugin by ID (includes paramsSchema)
|
|
7837
|
+
*
|
|
7838
|
+
* @param id - Plugin ID
|
|
7839
|
+
* @returns Plugin or null if not found
|
|
7840
|
+
*/
|
|
7841
|
+
getPlugin(id) {
|
|
7842
|
+
return this.plugins.get(id) || null;
|
|
7843
|
+
}
|
|
7844
|
+
/**
|
|
7845
|
+
* Validate params against a plugin's paramsSchema
|
|
7846
|
+
*
|
|
7847
|
+
* @param id - Plugin ID
|
|
7848
|
+
* @param params - Parameters to validate
|
|
7849
|
+
* @returns Validation result with success flag and error message if failed
|
|
7850
|
+
*/
|
|
7851
|
+
validateParams(id, params) {
|
|
7852
|
+
const plugin = this.plugins.get(id);
|
|
7853
|
+
if (!plugin) {
|
|
7854
|
+
return { success: false, error: `Plugin ${id} not found` };
|
|
7855
|
+
}
|
|
7856
|
+
if (!plugin.paramsSchema) {
|
|
7857
|
+
return { success: true };
|
|
7858
|
+
}
|
|
7859
|
+
if (!params) {
|
|
7860
|
+
return { success: false, error: `Plugin ${id} requires params but none were provided` };
|
|
7861
|
+
}
|
|
7862
|
+
if (typeof plugin.paramsSchema !== "object" || !plugin.paramsSchema || !("parse" in plugin.paramsSchema)) {
|
|
7863
|
+
return { success: false, error: `Plugin ${id} has invalid paramsSchema (must be a Zod schema)` };
|
|
7864
|
+
}
|
|
7865
|
+
const schema = plugin.paramsSchema;
|
|
7866
|
+
if (schema.safeParse) {
|
|
7867
|
+
const result = schema.safeParse(params);
|
|
7868
|
+
if (!result.success) {
|
|
7869
|
+
const errors = result.error?.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ") || "Validation failed";
|
|
7870
|
+
return { success: false, error: `Invalid params for ${id}: ${errors}` };
|
|
7871
|
+
}
|
|
7872
|
+
} else {
|
|
7873
|
+
try {
|
|
7874
|
+
schema.parse(params);
|
|
7875
|
+
} catch (error) {
|
|
7876
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7877
|
+
return { success: false, error: `Invalid params for ${id}: ${message}` };
|
|
7878
|
+
}
|
|
7879
|
+
}
|
|
7880
|
+
return { success: true };
|
|
7834
7881
|
}
|
|
7835
7882
|
/**
|
|
7836
7883
|
* Get all registered plugin IDs
|
|
@@ -8145,9 +8192,13 @@ var VerificationEngine = class {
|
|
|
8145
8192
|
let passed = 0;
|
|
8146
8193
|
let failed = 0;
|
|
8147
8194
|
const skipped = 0;
|
|
8195
|
+
const abortController = new AbortController();
|
|
8148
8196
|
let timeoutHandle = null;
|
|
8149
8197
|
const timeoutPromise = new Promise((resolve2) => {
|
|
8150
|
-
timeoutHandle = setTimeout(() =>
|
|
8198
|
+
timeoutHandle = setTimeout(() => {
|
|
8199
|
+
abortController.abort();
|
|
8200
|
+
resolve2("timeout");
|
|
8201
|
+
}, timeout);
|
|
8151
8202
|
timeoutHandle.unref();
|
|
8152
8203
|
});
|
|
8153
8204
|
const verificationPromise = this.verifyFiles(
|
|
@@ -8156,6 +8207,7 @@ var VerificationEngine = class {
|
|
|
8156
8207
|
severityFilter,
|
|
8157
8208
|
cwd,
|
|
8158
8209
|
options.reporter,
|
|
8210
|
+
abortController.signal,
|
|
8159
8211
|
(violations, warnings, errors) => {
|
|
8160
8212
|
allViolations.push(...violations);
|
|
8161
8213
|
allWarnings.push(...warnings);
|
|
@@ -8214,10 +8266,13 @@ var VerificationEngine = class {
|
|
|
8214
8266
|
/**
|
|
8215
8267
|
* Verify a single file
|
|
8216
8268
|
*/
|
|
8217
|
-
async verifyFile(filePath, decisions, severityFilter, cwd = process.cwd(), reporter) {
|
|
8269
|
+
async verifyFile(filePath, decisions, severityFilter, cwd = process.cwd(), reporter, signal) {
|
|
8218
8270
|
const violations = [];
|
|
8219
8271
|
const warnings = [];
|
|
8220
8272
|
const errors = [];
|
|
8273
|
+
if (signal?.aborted) {
|
|
8274
|
+
return { violations, warnings, errors };
|
|
8275
|
+
}
|
|
8221
8276
|
const sourceFile = await this.astCache.get(filePath, this.project);
|
|
8222
8277
|
if (!sourceFile) return { violations, warnings, errors };
|
|
8223
8278
|
let fileHash = null;
|
|
@@ -8298,11 +8353,35 @@ var VerificationEngine = class {
|
|
|
8298
8353
|
continue;
|
|
8299
8354
|
}
|
|
8300
8355
|
}
|
|
8356
|
+
if (constraint.check?.verifier && constraint.check?.params) {
|
|
8357
|
+
const pluginLoader2 = getPluginLoader();
|
|
8358
|
+
const validationResult = pluginLoader2.validateParams(constraint.check.verifier, constraint.check.params);
|
|
8359
|
+
if (!validationResult.success) {
|
|
8360
|
+
warnings.push({
|
|
8361
|
+
type: "invalid_params",
|
|
8362
|
+
message: validationResult.error,
|
|
8363
|
+
decisionId: decision.metadata.id,
|
|
8364
|
+
constraintId: constraint.id,
|
|
8365
|
+
file: filePath
|
|
8366
|
+
});
|
|
8367
|
+
if (reporter) {
|
|
8368
|
+
reporter.add({
|
|
8369
|
+
file: filePath,
|
|
8370
|
+
decision,
|
|
8371
|
+
constraint,
|
|
8372
|
+
applied: false,
|
|
8373
|
+
reason: `Params validation failed: ${validationResult.error}`
|
|
8374
|
+
});
|
|
8375
|
+
}
|
|
8376
|
+
continue;
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8301
8379
|
const ctx = {
|
|
8302
8380
|
filePath,
|
|
8303
8381
|
sourceFile,
|
|
8304
8382
|
constraint,
|
|
8305
|
-
decisionId: decision.metadata.id
|
|
8383
|
+
decisionId: decision.metadata.id,
|
|
8384
|
+
signal
|
|
8306
8385
|
};
|
|
8307
8386
|
const verificationStart = Date.now();
|
|
8308
8387
|
try {
|
|
@@ -8378,12 +8457,15 @@ var VerificationEngine = class {
|
|
|
8378
8457
|
/**
|
|
8379
8458
|
* Verify multiple files
|
|
8380
8459
|
*/
|
|
8381
|
-
async verifyFiles(files, decisions, severityFilter, cwd, reporter, onFileVerified) {
|
|
8460
|
+
async verifyFiles(files, decisions, severityFilter, cwd, reporter, signal, onFileVerified) {
|
|
8382
8461
|
const BATCH_SIZE = 50;
|
|
8383
8462
|
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
8463
|
+
if (signal.aborted) {
|
|
8464
|
+
break;
|
|
8465
|
+
}
|
|
8384
8466
|
const batch = files.slice(i, i + BATCH_SIZE);
|
|
8385
8467
|
const results = await Promise.all(
|
|
8386
|
-
batch.map((file) => this.verifyFile(file, decisions, severityFilter, cwd, reporter))
|
|
8468
|
+
batch.map((file) => this.verifyFile(file, decisions, severityFilter, cwd, reporter, signal))
|
|
8387
8469
|
);
|
|
8388
8470
|
for (const result of results) {
|
|
8389
8471
|
onFileVerified(result.violations, result.warnings, result.errors);
|
|
@@ -10245,6 +10327,12 @@ var SpecBridgeLspServer = class {
|
|
|
10245
10327
|
}
|
|
10246
10328
|
try {
|
|
10247
10329
|
const config = await loadConfig(this.cwd);
|
|
10330
|
+
try {
|
|
10331
|
+
await getPluginLoader().loadPlugins(this.cwd);
|
|
10332
|
+
} catch (error) {
|
|
10333
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
10334
|
+
if (this.options.verbose) this.connection.console.error(chalk14.red(`Plugin load failed: ${msg}`));
|
|
10335
|
+
}
|
|
10248
10336
|
this.registry = createRegistry({ basePath: this.cwd });
|
|
10249
10337
|
await this.registry.load();
|
|
10250
10338
|
this.decisions = this.registry.getActive();
|
|
@@ -10274,7 +10362,7 @@ var SpecBridgeLspServer = class {
|
|
|
10274
10362
|
for (const decision of this.decisions) {
|
|
10275
10363
|
for (const constraint of decision.constraints) {
|
|
10276
10364
|
if (!shouldApplyConstraintToFile({ filePath, constraint, cwd: this.cwd })) continue;
|
|
10277
|
-
const verifier = selectVerifierForConstraint(constraint.rule, constraint.verifier);
|
|
10365
|
+
const verifier = selectVerifierForConstraint(constraint.rule, constraint.verifier, constraint.check);
|
|
10278
10366
|
if (!verifier) continue;
|
|
10279
10367
|
const ctx = {
|
|
10280
10368
|
filePath,
|
|
@@ -11787,7 +11875,7 @@ async function migrateDecisions(cwd, dryRun) {
|
|
|
11787
11875
|
if (!needsMigration) {
|
|
11788
11876
|
continue;
|
|
11789
11877
|
}
|
|
11790
|
-
|
|
11878
|
+
const migratedContent = content.replace(
|
|
11791
11879
|
/^(\s+)verifier:\s+(\S+)$/gm,
|
|
11792
11880
|
"$1check:\n$1 verifier: $2"
|
|
11793
11881
|
);
|