@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 CHANGED
@@ -7748,7 +7748,7 @@ var PluginLoader = class {
7748
7748
  }
7749
7749
  this.loaded = true;
7750
7750
  if (this.plugins.size > 0) {
7751
- console.log(`Loaded ${this.plugins.size} custom verifier(s)`);
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.createVerifier);
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 factory = this.plugins.get(id);
7833
- return factory ? factory() : null;
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(() => resolve2("timeout"), timeout);
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
- let migratedContent = content.replace(
11878
+ const migratedContent = content.replace(
11791
11879
  /^(\s+)verifier:\s+(\S+)$/gm,
11792
11880
  "$1check:\n$1 verifier: $2"
11793
11881
  );