@wispbit/local 1.0.26 → 1.0.28

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.
Files changed (37) hide show
  1. package/dist/cli.js +537 -396
  2. package/dist/cli.js.map +4 -4
  3. package/dist/package.json +2 -1
  4. package/dist/src/api/WispbitApiClient.d.ts +185 -0
  5. package/dist/src/api/WispbitApiClient.d.ts.map +1 -0
  6. package/dist/src/cli.d.ts.map +1 -1
  7. package/dist/src/environment/Config.d.ts +15 -5
  8. package/dist/src/environment/Config.d.ts.map +1 -1
  9. package/dist/src/providers/ViolationValidationProvider.d.ts +7 -11
  10. package/dist/src/providers/ViolationValidationProvider.d.ts.map +1 -1
  11. package/dist/src/providers/WispbitRuleProvider.d.ts +0 -16
  12. package/dist/src/providers/WispbitRuleProvider.d.ts.map +1 -1
  13. package/dist/src/providers/WispbitViolationValidationProvider.d.ts +4 -7
  14. package/dist/src/providers/WispbitViolationValidationProvider.d.ts.map +1 -1
  15. package/dist/src/schemas.d.ts +4 -200
  16. package/dist/src/schemas.d.ts.map +1 -1
  17. package/dist/src/steps/ExecutionEventEmitter.d.ts +37 -2
  18. package/dist/src/steps/ExecutionEventEmitter.d.ts.map +1 -1
  19. package/dist/src/steps/FileExecutionContext.d.ts +0 -4
  20. package/dist/src/steps/FileExecutionContext.d.ts.map +1 -1
  21. package/dist/src/steps/FileFilterStep.d.ts +0 -4
  22. package/dist/src/steps/FileFilterStep.d.ts.map +1 -1
  23. package/dist/src/steps/LLMStep.d.ts +0 -10
  24. package/dist/src/steps/LLMStep.d.ts.map +1 -1
  25. package/dist/src/steps/RuleExecutor.d.ts.map +1 -1
  26. package/dist/src/test/TestExecutor.d.ts +2 -2
  27. package/dist/src/test/TestExecutor.d.ts.map +1 -1
  28. package/dist/src/utils/formatters.d.ts +2 -2
  29. package/dist/src/utils/formatters.d.ts.map +1 -1
  30. package/dist/src/utils/patternMatching.d.ts +2 -0
  31. package/dist/src/utils/patternMatching.d.ts.map +1 -0
  32. package/dist/src/utils/validateRule.d.ts +37 -1
  33. package/dist/src/utils/validateRule.d.ts.map +1 -1
  34. package/dist/src/validationSchemas.d.ts +553 -0
  35. package/dist/src/validationSchemas.d.ts.map +1 -0
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -10,6 +10,340 @@ import dotenv from "dotenv";
10
10
  import meow from "meow";
11
11
  import semver from "semver";
12
12
 
13
+ // src/api/WispbitApiClient.ts
14
+ import pRetry from "p-retry";
15
+ import { z as z2 } from "zod";
16
+
17
+ // src/validationSchemas.ts
18
+ import { z } from "zod";
19
+
20
+ // src/languages.ts
21
+ import { existsSync } from "fs";
22
+ import { createRequire } from "module";
23
+ import path from "path";
24
+ import angular from "@ast-grep/lang-angular";
25
+ import bash from "@ast-grep/lang-bash";
26
+ import c from "@ast-grep/lang-c";
27
+ import cpp from "@ast-grep/lang-cpp";
28
+ import csharp from "@ast-grep/lang-csharp";
29
+ import css from "@ast-grep/lang-css";
30
+ import dart from "@ast-grep/lang-dart";
31
+ import elixir from "@ast-grep/lang-elixir";
32
+ import go from "@ast-grep/lang-go";
33
+ import haskell from "@ast-grep/lang-haskell";
34
+ import html from "@ast-grep/lang-html";
35
+ import java from "@ast-grep/lang-java";
36
+ import javascript from "@ast-grep/lang-javascript";
37
+ import json from "@ast-grep/lang-json";
38
+ import kotlin from "@ast-grep/lang-kotlin";
39
+ import lua from "@ast-grep/lang-lua";
40
+ import markdown from "@ast-grep/lang-markdown";
41
+ import php from "@ast-grep/lang-php";
42
+ import python from "@ast-grep/lang-python";
43
+ import ruby from "@ast-grep/lang-ruby";
44
+ import rust from "@ast-grep/lang-rust";
45
+ import scala from "@ast-grep/lang-scala";
46
+ import sql from "@ast-grep/lang-sql";
47
+ import swift from "@ast-grep/lang-swift";
48
+ import toml from "@ast-grep/lang-toml";
49
+ import tsx from "@ast-grep/lang-tsx";
50
+ import typescript from "@ast-grep/lang-typescript";
51
+ import yaml from "@ast-grep/lang-yaml";
52
+ import { registerDynamicLanguage } from "@ast-grep/napi";
53
+ console.debug = () => {
54
+ };
55
+ var Language = /* @__PURE__ */ ((Language2) => {
56
+ Language2["Angular"] = "Angular";
57
+ Language2["Bash"] = "Bash";
58
+ Language2["C"] = "C";
59
+ Language2["Cpp"] = "Cpp";
60
+ Language2["Csharp"] = "Csharp";
61
+ Language2["Css"] = "Css";
62
+ Language2["Dart"] = "Dart";
63
+ Language2["Elixir"] = "Elixir";
64
+ Language2["Go"] = "Go";
65
+ Language2["Haskell"] = "Haskell";
66
+ Language2["Html"] = "Html";
67
+ Language2["Java"] = "Java";
68
+ Language2["JavaScript"] = "JavaScript";
69
+ Language2["Json"] = "Json";
70
+ Language2["Kotlin"] = "Kotlin";
71
+ Language2["Lua"] = "Lua";
72
+ Language2["Markdown"] = "Markdown";
73
+ Language2["Php"] = "Php";
74
+ Language2["Python"] = "Python";
75
+ Language2["Ruby"] = "Ruby";
76
+ Language2["Rust"] = "Rust";
77
+ Language2["Scala"] = "Scala";
78
+ Language2["Sql"] = "Sql";
79
+ Language2["Swift"] = "Swift";
80
+ Language2["Toml"] = "Toml";
81
+ Language2["Tsx"] = "Tsx";
82
+ Language2["TypeScript"] = "TypeScript";
83
+ Language2["Yaml"] = "Yaml";
84
+ Language2["GraphQL"] = "GraphQL";
85
+ Language2["Unknown"] = "Unknown";
86
+ return Language2;
87
+ })(Language || {});
88
+ var require2 = createRequire(import.meta.url ? import.meta.url : __filename);
89
+ function getGraphQLLibPath() {
90
+ const graphqlDir = path.dirname(require2.resolve("tree-sitter-graphql"));
91
+ const releaseNode = path.join(graphqlDir, "../../build/Release/tree_sitter_graphql_binding.node");
92
+ if (existsSync(releaseNode)) {
93
+ return releaseNode;
94
+ }
95
+ const debugNode = path.join(graphqlDir, "../../build/Debug/tree_sitter_graphql_binding.node");
96
+ if (existsSync(debugNode)) {
97
+ return debugNode;
98
+ }
99
+ const soFile = path.join(graphqlDir, "parser.so");
100
+ if (existsSync(soFile)) {
101
+ return soFile;
102
+ }
103
+ return null;
104
+ }
105
+ var graphqlPath = getGraphQLLibPath();
106
+ var graphql = {
107
+ // node-gyp-build puts the .node file in build/Release/
108
+ libraryPath: graphqlPath,
109
+ /** the file extensions of the language. e.g. mojo */
110
+ extensions: ["graphql"],
111
+ /** the dylib symbol to load ts-language, default is `tree_sitter_{name}` */
112
+ languageSymbol: "tree_sitter_graphql",
113
+ /** the meta variable leading character, default is $ */
114
+ metaVarChar: "$",
115
+ /**
116
+ * An optional char to replace $ in your pattern.
117
+ * See https://ast-grep.github.io/advanced/custom-language.html#register-language-in-sgconfig-yml
118
+ */
119
+ expandoChar: void 0
120
+ };
121
+ var registeredLanguages = {
122
+ ["Angular" /* Angular */]: angular,
123
+ ["Bash" /* Bash */]: bash,
124
+ ["C" /* C */]: c,
125
+ ["Cpp" /* Cpp */]: cpp,
126
+ ["Csharp" /* Csharp */]: csharp,
127
+ ["Css" /* Css */]: css,
128
+ ["Dart" /* Dart */]: dart,
129
+ ["Elixir" /* Elixir */]: elixir,
130
+ ["Go" /* Go */]: go,
131
+ ["Haskell" /* Haskell */]: haskell,
132
+ ["Html" /* Html */]: html,
133
+ ["Java" /* Java */]: java,
134
+ ["JavaScript" /* JavaScript */]: javascript,
135
+ ["Json" /* Json */]: json,
136
+ ["Kotlin" /* Kotlin */]: kotlin,
137
+ ["Lua" /* Lua */]: lua,
138
+ ["Markdown" /* Markdown */]: markdown,
139
+ ["Php" /* Php */]: php,
140
+ ["Python" /* Python */]: python,
141
+ ["Ruby" /* Ruby */]: ruby,
142
+ ["Rust" /* Rust */]: rust,
143
+ ["Scala" /* Scala */]: scala,
144
+ ["Sql" /* Sql */]: sql,
145
+ ["Swift" /* Swift */]: swift,
146
+ ["Toml" /* Toml */]: toml,
147
+ ["Tsx" /* Tsx */]: tsx,
148
+ ["TypeScript" /* TypeScript */]: typescript,
149
+ ["Yaml" /* Yaml */]: yaml,
150
+ ...graphqlPath ? { ["GraphQL" /* GraphQL */]: graphql } : {}
151
+ };
152
+ registerDynamicLanguage(registeredLanguages);
153
+ var REGISTERED_LANGUAGE_EXTENSIONS = Object.entries(
154
+ registeredLanguages
155
+ ).reduce(
156
+ (acc, [language, registration]) => {
157
+ acc[language] = registration.extensions ?? [];
158
+ return acc;
159
+ },
160
+ {}
161
+ );
162
+ function getLanguageFromFilePath(filePath) {
163
+ const extension = path.extname(filePath).slice(1);
164
+ const language = findRegisteredLanguageFromExtension(extension);
165
+ return language ?? "Unknown" /* Unknown */;
166
+ }
167
+ function findRegisteredLanguageFromExtension(extension) {
168
+ return Object.keys(REGISTERED_LANGUAGE_EXTENSIONS).find(
169
+ (language) => REGISTERED_LANGUAGE_EXTENSIONS[language].includes(extension)
170
+ );
171
+ }
172
+
173
+ // src/validationSchemas.ts
174
+ var MatchRangeSchema = z.object({
175
+ start: z.object({
176
+ line: z.number().describe("0-based line number"),
177
+ column: z.number().describe("0-based column number")
178
+ }),
179
+ end: z.object({
180
+ line: z.number().describe("0-based line number"),
181
+ column: z.number().describe("0-based column number")
182
+ })
183
+ });
184
+ var MatchSourceSchema = z.lazy(
185
+ () => z.object({
186
+ filePath: z.string().describe("File path where this source match occurred"),
187
+ text: z.string().optional().describe("The matched text at this source step"),
188
+ range: MatchRangeSchema.describe("Position range of this source match"),
189
+ symbol: z.string().optional().describe("Optional symbol name if applicable"),
190
+ language: z.nativeEnum(Language).describe(
191
+ "The language this source match was found in (e.g., Language.TypeScript, Language.Python)"
192
+ )
193
+ })
194
+ );
195
+ var MatchMetadataSchema = z.object({
196
+ fromCache: z.boolean().optional().describe("Whether this match was retrieved from cache"),
197
+ llmValidation: z.object({
198
+ isViolation: z.boolean().describe("Whether the LLM determined this is a violation"),
199
+ confidence: z.number().min(0).max(1).describe("Confidence score (0-1) of the validation"),
200
+ reason: z.string().describe("Explanation of the validation decision")
201
+ }).optional().describe("LLM validation metadata if applicable")
202
+ }).optional().describe("Metadata about the match and its processing");
203
+ var MatchSchema = z.object({
204
+ filePath: z.string().describe("File path where the match was found"),
205
+ text: z.string().optional().describe("The matched text/code snippet"),
206
+ range: MatchRangeSchema.describe("Position range of the match"),
207
+ symbol: z.string().optional().describe("Optional symbol name if applicable"),
208
+ language: z.nativeEnum(Language).describe("The language this match was found in (e.g., Language.TypeScript, Language.Python)"),
209
+ source: z.array(MatchSourceSchema).optional().describe(
210
+ "Chain of sources that led to this match. First entry is the original source, last entry is the immediate parent."
211
+ ),
212
+ metadata: MatchMetadataSchema
213
+ });
214
+ var ValidateViolationResponseSchema = z.object({
215
+ validMatches: z.array(MatchSchema).describe("Matches that are valid violations"),
216
+ skippedMatches: z.array(MatchSchema).describe("Matches that are not violations")
217
+ });
218
+
219
+ // src/api/WispbitApiClient.ts
220
+ var InitializeRequestSchema = z2.object({
221
+ repository_url: z2.string(),
222
+ powerlint_version: z2.string(),
223
+ schema_version: z2.string()
224
+ });
225
+ var InitializeResponseSchema = z2.object({
226
+ configured: z2.boolean(),
227
+ invalid_api_key: z2.boolean().optional(),
228
+ is_valid_repository: z2.boolean().optional(),
229
+ config: z2.object({
230
+ ignored_globs: z2.array(z2.string())
231
+ }).optional()
232
+ });
233
+ var GetRulesRequestSchema = z2.object({
234
+ repository_url: z2.string(),
235
+ rule_ids: z2.array(z2.string()).optional(),
236
+ schema_version: z2.string(),
237
+ powerlint_version: z2.string()
238
+ });
239
+ var GetRulesResponseSchema = z2.object({
240
+ rules: z2.array(
241
+ z2.object({
242
+ id: z2.string(),
243
+ internalId: z2.string(),
244
+ internalVersionId: z2.string(),
245
+ message: z2.string(),
246
+ prompt: z2.string(),
247
+ severity: z2.enum(["suggestion", "violation"]),
248
+ schema: z2.any()
249
+ })
250
+ )
251
+ });
252
+ var ValidateViolationRequestSchema = z2.object({
253
+ rule: z2.object({
254
+ internalId: z2.string(),
255
+ internalVersionId: z2.string(),
256
+ contents: z2.string()
257
+ }),
258
+ matches: z2.array(z2.any()),
259
+ powerlint_version: z2.string(),
260
+ schema_version: z2.string()
261
+ });
262
+ var WispbitApiClient = class {
263
+ baseUrl;
264
+ apiKey;
265
+ constructor(config) {
266
+ this.baseUrl = config.baseUrl;
267
+ this.apiKey = config.apiKey;
268
+ }
269
+ /**
270
+ * Make a request to the Wispbit API with retry logic
271
+ */
272
+ async request(endpoint, data, responseSchema) {
273
+ const url = `${this.baseUrl}${endpoint}`;
274
+ const response = await pRetry(
275
+ async () => {
276
+ const res = await fetch(url, {
277
+ method: "POST",
278
+ headers: {
279
+ "Content-Type": "application/json",
280
+ Authorization: `Bearer ${this.apiKey}`
281
+ },
282
+ body: JSON.stringify(data)
283
+ });
284
+ if (!res.ok) {
285
+ const errorText = await res.text();
286
+ throw new Error(
287
+ `Wispbit API request failed: ${res.status} ${res.statusText} - ${errorText}`
288
+ );
289
+ }
290
+ return res;
291
+ },
292
+ {
293
+ retries: 3,
294
+ minTimeout: 1e3,
295
+ maxTimeout: 5e3,
296
+ onFailedAttempt: (error) => {
297
+ console.warn(
298
+ `API request to ${endpoint} failed (attempt ${error.attemptNumber}/4): ${error.message}`
299
+ );
300
+ }
301
+ }
302
+ );
303
+ const json2 = await response.json();
304
+ if (responseSchema) {
305
+ return responseSchema.parse(json2);
306
+ }
307
+ return json2;
308
+ }
309
+ /**
310
+ * Initialize PowerLint configuration with Wispbit
311
+ */
312
+ async initialize(request) {
313
+ const validatedRequest = InitializeRequestSchema.parse(request);
314
+ return await this.request("/plv1/initialize", validatedRequest, InitializeResponseSchema);
315
+ }
316
+ /**
317
+ * Get rules from Wispbit Cloud
318
+ */
319
+ async getRules(request) {
320
+ const validatedRequest = GetRulesRequestSchema.parse(request);
321
+ return await this.request("/plv1/get-rules", validatedRequest, GetRulesResponseSchema);
322
+ }
323
+ /**
324
+ * Validate violations with Wispbit
325
+ */
326
+ async validateViolation(request) {
327
+ const validatedRequest = ValidateViolationRequestSchema.parse(request);
328
+ return await this.request(
329
+ "/plv1/validate-violation",
330
+ validatedRequest,
331
+ ValidateViolationResponseSchema
332
+ );
333
+ }
334
+ /**
335
+ * Validate violations with Wispbit (internal endpoint for testing)
336
+ */
337
+ async validateViolationInternal(request) {
338
+ const validatedRequest = ValidateViolationRequestSchema.parse(request);
339
+ return await this.request(
340
+ "/plv1/internal/validate-violation",
341
+ validatedRequest,
342
+ ValidateViolationResponseSchema
343
+ );
344
+ }
345
+ };
346
+
13
347
  // src/version.ts
14
348
  import { readFileSync } from "fs";
15
349
  import { dirname, join } from "path";
@@ -41,13 +375,22 @@ var Config = class _Config {
41
375
  config;
42
376
  apiKey = null;
43
377
  baseUrl = null;
44
- constructor(config) {
378
+ apiClient = null;
379
+ useInternalEndpoint = false;
380
+ constructor(config, options) {
45
381
  this.config = {
46
382
  ...config,
47
383
  ignoredGlobs: config.ignoredGlobs || []
48
384
  };
49
385
  this.apiKey = config.apiKey || null;
50
386
  this.baseUrl = config.baseUrl || null;
387
+ this.useInternalEndpoint = (options == null ? void 0 : options.useInternalEndpoint) ?? false;
388
+ if (this.apiKey && this.baseUrl) {
389
+ this.apiClient = new WispbitApiClient({
390
+ baseUrl: this.baseUrl,
391
+ apiKey: this.apiKey
392
+ });
393
+ }
51
394
  }
52
395
  getIgnoredGlobs() {
53
396
  return this.config.ignoredGlobs || [];
@@ -66,6 +409,16 @@ var Config = class _Config {
66
409
  getBaseUrl() {
67
410
  return this.baseUrl;
68
411
  }
412
+ /**
413
+ * Get the Wispbit API client
414
+ * @returns The API client instance
415
+ */
416
+ getApiClient() {
417
+ if (!this.apiClient) {
418
+ throw new Error("API client not initialized. Config must have both apiKey and baseUrl.");
419
+ }
420
+ return this.apiClient;
421
+ }
69
422
  /**
70
423
  * Get the local PowerLint version
71
424
  * @returns The current PowerLint version
@@ -80,24 +433,6 @@ var Config = class _Config {
80
433
  getSchemaVersion() {
81
434
  return "v1";
82
435
  }
83
- /**
84
- * Validate API key with Wispbit API
85
- */
86
- static async validateApiKey(apiKey, repositoryUrl, baseUrl) {
87
- const response = await fetch(`${baseUrl}/plv1/initialize`, {
88
- method: "POST",
89
- headers: {
90
- "Content-Type": "application/json",
91
- Authorization: `Bearer ${apiKey}`
92
- },
93
- body: JSON.stringify({
94
- repository_url: repositoryUrl,
95
- powerlint_version: getCurrentVersion(),
96
- schema_version: "v1"
97
- })
98
- });
99
- return response.ok;
100
- }
101
436
  /**
102
437
  * Initialize configuration without network validation (for testing)
103
438
  * @param options Optional configuration options
@@ -107,11 +442,16 @@ var Config = class _Config {
107
442
  const finalBaseUrl = options.baseUrl || process.env.WISPBIT_API_BASE_URL || "https://api.wispbit.com";
108
443
  const finalApiKey = options.apiKey || process.env.WISPBIT_API_KEY || null;
109
444
  const ignoredGlobs = options.ignoredGlobs || [];
110
- return new _Config({
111
- ignoredGlobs,
112
- apiKey: finalApiKey || void 0,
113
- baseUrl: finalBaseUrl
114
- });
445
+ return new _Config(
446
+ {
447
+ ignoredGlobs,
448
+ apiKey: finalApiKey || void 0,
449
+ baseUrl: finalBaseUrl
450
+ },
451
+ {
452
+ useInternalEndpoint: true
453
+ }
454
+ );
115
455
  }
116
456
  /**
117
457
  * Initialize configuration by validating API key and repository URL with Wispbit
@@ -131,23 +471,15 @@ var Config = class _Config {
131
471
  return { failed: true, error: "INVALID_API_KEY" };
132
472
  }
133
473
  const repositoryUrl = await environment.getRepositoryUrl();
134
- const isValidApiKey = await _Config.validateApiKey(finalApiKey, repositoryUrl, finalBaseUrl);
135
- if (!isValidApiKey) {
136
- return { failed: true, error: "INVALID_API_KEY" };
137
- }
138
- const response = await fetch(`${finalBaseUrl}/plv1/initialize`, {
139
- method: "POST",
140
- headers: {
141
- "Content-Type": "application/json",
142
- Authorization: `Bearer ${finalApiKey}`
143
- },
144
- body: JSON.stringify({
145
- repository_url: repositoryUrl,
146
- powerlint_version: getCurrentVersion(),
147
- schema_version: "v1"
148
- })
474
+ const tempClient = new WispbitApiClient({
475
+ baseUrl: finalBaseUrl,
476
+ apiKey: finalApiKey
477
+ });
478
+ const result = await tempClient.initialize({
479
+ repository_url: repositoryUrl,
480
+ powerlint_version: getCurrentVersion(),
481
+ schema_version: "v1"
149
482
  });
150
- const result = await response.json();
151
483
  if (result.invalid_api_key) {
152
484
  return { failed: true, error: "INVALID_API_KEY" };
153
485
  }
@@ -164,11 +496,17 @@ var Config = class _Config {
164
496
  isConfigured() {
165
497
  return this.getApiKey() !== null;
166
498
  }
499
+ /**
500
+ * Check if the internal validation endpoint should be used
501
+ */
502
+ shouldUseInternalEndpoint() {
503
+ return this.useInternalEndpoint;
504
+ }
167
505
  };
168
506
 
169
507
  // src/utils/git.ts
170
508
  import { exec, execSync } from "child_process";
171
- import { existsSync, readFileSync as readFileSync2 } from "fs";
509
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
172
510
  import { promisify } from "util";
173
511
 
174
512
  // src/utils/hashString.ts
@@ -225,7 +563,7 @@ async function tryGetUpstream(repoRoot) {
225
563
  function tryGetGraphiteParent(repoRoot, branch) {
226
564
  const metadataPath = `.git/refs/branch-metadata/${branch}`;
227
565
  const fullPath = `${repoRoot}/${metadataPath}`;
228
- if (!existsSync(fullPath)) {
566
+ if (!existsSync2(fullPath)) {
229
567
  return void 0;
230
568
  }
231
569
  const content = readFileSync2(fullPath, "utf-8");
@@ -714,7 +1052,7 @@ var Environment = class {
714
1052
  // src/environment/Storage.ts
715
1053
  import * as fs from "fs/promises";
716
1054
  import os from "os";
717
- import path from "path";
1055
+ import path2 from "path";
718
1056
  import Keyv from "keyv";
719
1057
  import { KeyvFile } from "keyv-file";
720
1058
  var Storage = class {
@@ -728,13 +1066,13 @@ var Storage = class {
728
1066
  * This replaces the getConfigDirectory functionality
729
1067
  */
730
1068
  getStorageDirectory() {
731
- return path.join(os.homedir(), ".powerlint");
1069
+ return path2.join(os.homedir(), ".powerlint");
732
1070
  }
733
1071
  /**
734
1072
  * Get the base directory for indexes
735
1073
  */
736
1074
  getIndexDirectory() {
737
- return path.join(
1075
+ return path2.join(
738
1076
  this.getStorageDirectory(),
739
1077
  "indexes",
740
1078
  hashString(this.environment.getWorkspaceRoot())
@@ -744,7 +1082,7 @@ var Storage = class {
744
1082
  * Get the base directory for caches
745
1083
  */
746
1084
  getCacheDirectory() {
747
- return path.join(
1085
+ return path2.join(
748
1086
  this.getStorageDirectory(),
749
1087
  "cache",
750
1088
  hashString(this.environment.getWorkspaceRoot())
@@ -764,7 +1102,7 @@ var Storage = class {
764
1102
  const indexDir = this.getIndexDirectory();
765
1103
  await this.ensureDirectory(indexDir);
766
1104
  const file = fileName || `${language.toLowerCase()}-index.scip`;
767
- return path.join(indexDir, `${language.toLowerCase()}-${file}`);
1105
+ return path2.join(indexDir, `${language.toLowerCase()}-${file}`);
768
1106
  }
769
1107
  /**
770
1108
  * Check if an index exists for a language
@@ -801,7 +1139,7 @@ var Storage = class {
801
1139
  * Get the cache file path
802
1140
  */
803
1141
  getCacheFilePath() {
804
- return path.join(this.getCacheDirectory(), "cache.json");
1142
+ return path2.join(this.getCacheDirectory(), "cache.json");
805
1143
  }
806
1144
  /**
807
1145
  * Purge all storage (cache and indexes)
@@ -846,7 +1184,7 @@ var Storage = class {
846
1184
  const cacheDir = this.getCacheDirectory();
847
1185
  this.cacheStore = new Keyv({
848
1186
  store: new KeyvFile({
849
- filename: path.join(cacheDir, "cache.json")
1187
+ filename: path2.join(cacheDir, "cache.json")
850
1188
  })
851
1189
  });
852
1190
  }
@@ -889,13 +1227,6 @@ var Storage = class {
889
1227
  };
890
1228
 
891
1229
  // src/providers/WispbitRuleProvider.ts
892
- import { z } from "zod";
893
- var GetRulesSchema = z.object({
894
- repository_url: z.string(),
895
- rule_ids: z.array(z.string()).optional(),
896
- schema_version: z.string(),
897
- powerlint_version: z.string()
898
- });
899
1230
  var WispbitRuleProvider = class {
900
1231
  config;
901
1232
  environment;
@@ -903,26 +1234,6 @@ var WispbitRuleProvider = class {
903
1234
  this.config = config;
904
1235
  this.environment = environment;
905
1236
  }
906
- /**
907
- * Make a request to the Wispbit API
908
- */
909
- async makeApiRequest(endpoint, data) {
910
- const baseUrl = this.config.getBaseUrl();
911
- const apiKey = this.config.getApiKey();
912
- const url = `${baseUrl}${endpoint}`;
913
- const response = await fetch(url, {
914
- method: "POST",
915
- headers: {
916
- "Content-Type": "application/json",
917
- Authorization: `Bearer ${apiKey}`
918
- },
919
- body: JSON.stringify(data)
920
- });
921
- if (!response.ok) {
922
- throw new Error(`Wispbit API request failed: ${response.status} ${response.statusText}`);
923
- }
924
- return await response.json();
925
- }
926
1237
  /**
927
1238
  * Get the repository URL for API requests
928
1239
  */
@@ -956,19 +1267,14 @@ var WispbitRuleProvider = class {
956
1267
  */
957
1268
  async fetchRules(ruleIds) {
958
1269
  const repositoryUrl = await this.getRepositoryUrl();
959
- const requestData = {
1270
+ const apiClient = this.config.getApiClient();
1271
+ const response = await apiClient.getRules({
960
1272
  repository_url: repositoryUrl,
961
1273
  rule_ids: ruleIds,
962
1274
  schema_version: this.config.getSchemaVersion(),
963
1275
  powerlint_version: this.config.getLocalVersion()
964
- };
965
- GetRulesSchema.parse(requestData);
966
- const response = await this.makeApiRequest("/plv1/get-rules", requestData);
967
- if (!Array.isArray(response.rules)) {
968
- throw new Error("Invalid response format from Wispbit API: expected rules array");
969
- }
970
- const rules = response.rules;
971
- return rules.map((rule) => ({
1276
+ });
1277
+ return response.rules.map((rule) => ({
972
1278
  id: rule.id,
973
1279
  internalId: rule.internalId,
974
1280
  config: {
@@ -980,27 +1286,6 @@ var WispbitRuleProvider = class {
980
1286
  testCases: []
981
1287
  }));
982
1288
  }
983
- /**
984
- * Create a new rule in Wispbit Cloud
985
- */
986
- async createRule(_rule) {
987
- await Promise.resolve();
988
- throw new Error("Creating rules in Wispbit Cloud is not yet implemented");
989
- }
990
- /**
991
- * Update an existing rule in Wispbit Cloud
992
- */
993
- async updateRule(_ruleId, _rule) {
994
- await Promise.resolve();
995
- throw new Error("Updating rules in Wispbit Cloud is not yet implemented");
996
- }
997
- /**
998
- * Delete a rule from Wispbit Cloud
999
- */
1000
- async deleteRule(_ruleId) {
1001
- await Promise.resolve();
1002
- throw new Error("Deleting rules from Wispbit Cloud is not yet implemented");
1003
- }
1004
1289
  };
1005
1290
 
1006
1291
  // src/steps/ExecutionEventEmitter.ts
@@ -1083,11 +1368,18 @@ var ExecutionEventEmitter = class extends EventEmitter {
1083
1368
  this.emit("indexing:complete", { language, executionTime });
1084
1369
  }
1085
1370
  // Helper methods for step debugging
1086
- startStep(ruleId, stepName, stepType, inputs) {
1087
- this.emit("step:start", { ruleId, stepName, stepType, inputs });
1371
+ startStep(ruleId, stepName, stepType, inputs, parentStepName) {
1372
+ this.emit("step:start", { ruleId, stepName, stepType, inputs, parentStepName });
1088
1373
  }
1089
- completeStep(ruleId, stepName, stepType, outputs, executionTime = 0) {
1090
- this.emit("step:complete", { ruleId, stepName, stepType, outputs, executionTime });
1374
+ completeStep(ruleId, stepName, stepType, outputs, executionTime = 0, parentStepName) {
1375
+ this.emit("step:complete", {
1376
+ ruleId,
1377
+ stepName,
1378
+ stepType,
1379
+ outputs,
1380
+ executionTime,
1381
+ parentStepName
1382
+ });
1091
1383
  }
1092
1384
  // Helper methods for test events
1093
1385
  startTest(ruleId, testName) {
@@ -1096,6 +1388,9 @@ var ExecutionEventEmitter = class extends EventEmitter {
1096
1388
  testMatches(ruleId, testName, matches) {
1097
1389
  this.emit("test:matches", { ruleId, testName, matches });
1098
1390
  }
1391
+ completeTest(ruleId, testName, matches, stepExecutions) {
1392
+ this.emit("test:complete", { ruleId, testName, matches, stepExecutions });
1393
+ }
1099
1394
  // Helper methods for LLM validation events
1100
1395
  startLLMValidation(ruleId, matchCount, estimatedCost) {
1101
1396
  this.emit("llm:validation:start", { ruleId, matchCount, estimatedCost });
@@ -1116,9 +1411,18 @@ var ExecutionEventEmitter = class extends EventEmitter {
1116
1411
 
1117
1412
  // src/steps/FileExecutionContext.ts
1118
1413
  import * as fs2 from "fs";
1119
- import * as path2 from "path";
1414
+ import * as path3 from "path";
1120
1415
  import { glob } from "glob";
1416
+
1417
+ // src/utils/patternMatching.ts
1121
1418
  import ignore from "ignore";
1419
+ function matchesAnyPattern(filePath, patterns) {
1420
+ if (patterns.length === 0) return false;
1421
+ const ig = ignore().add(patterns);
1422
+ return ig.ignores(filePath);
1423
+ }
1424
+
1425
+ // src/steps/FileExecutionContext.ts
1122
1426
  var FileExecutionContext = class _FileExecutionContext {
1123
1427
  environment;
1124
1428
  _filePaths = [];
@@ -1253,14 +1557,14 @@ var FileExecutionContext = class _FileExecutionContext {
1253
1557
  } else if (this.mode === "diff") {
1254
1558
  const workspaceRoot = this.environment.getWorkspaceRoot();
1255
1559
  discoveredFiles = this.diffMode.changedFiles.filter(
1256
- (file) => fs2.existsSync(path2.resolve(workspaceRoot, file))
1560
+ (file) => fs2.existsSync(path3.resolve(workspaceRoot, file))
1257
1561
  ).map((file) => file);
1258
1562
  const allIgnorePatterns = this.config.getIgnoredGlobs();
1259
1563
  if (allIgnorePatterns.length > 0) {
1260
1564
  this.eventEmitter.fileDiscoveryProgress("Applying ignore patterns...");
1261
1565
  const beforeIgnore = discoveredFiles.length;
1262
1566
  discoveredFiles = discoveredFiles.filter(
1263
- (filePath2) => !this.matchesAnyPattern(filePath2, allIgnorePatterns)
1567
+ (filePath2) => !matchesAnyPattern(filePath2, allIgnorePatterns)
1264
1568
  );
1265
1569
  if (beforeIgnore !== discoveredFiles.length) {
1266
1570
  this.eventEmitter.fileDiscoveryProgress(
@@ -1286,7 +1590,7 @@ var FileExecutionContext = class _FileExecutionContext {
1286
1590
  */
1287
1591
  async discoverFilesFromPath(filePath, gitIgnoredFiles) {
1288
1592
  const workspaceRoot = this.environment.getWorkspaceRoot();
1289
- const fullPath = path2.resolve(workspaceRoot, filePath);
1593
+ const fullPath = path3.resolve(workspaceRoot, filePath);
1290
1594
  const allIgnorePatterns = this.config.getIgnoredGlobs();
1291
1595
  if (this.isGlobPattern(filePath)) {
1292
1596
  this.eventEmitter.fileDiscoveryProgress(
@@ -1304,7 +1608,7 @@ var FileExecutionContext = class _FileExecutionContext {
1304
1608
  this.eventEmitter.fileDiscoveryProgress(`File ${filePath} is ignored by git`);
1305
1609
  return [];
1306
1610
  }
1307
- if (this.matchesAnyPattern(filePath, allIgnorePatterns)) {
1611
+ if (matchesAnyPattern(filePath, allIgnorePatterns)) {
1308
1612
  this.eventEmitter.fileDiscoveryProgress(`File ${filePath} is ignored by patterns`);
1309
1613
  return [];
1310
1614
  } else {
@@ -1346,7 +1650,7 @@ var FileExecutionContext = class _FileExecutionContext {
1346
1650
  */
1347
1651
  async discoverFilesFromDirectory(dirPath, ignorePatterns, gitIgnoredFiles) {
1348
1652
  const workspaceRoot = this.environment.getWorkspaceRoot();
1349
- const globPattern = path2.join(dirPath, "**/*").replace(/\\/g, "/");
1653
+ const globPattern = path3.join(dirPath, "**/*").replace(/\\/g, "/");
1350
1654
  const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
1351
1655
  const matches = await glob(globPattern, {
1352
1656
  cwd: workspaceRoot,
@@ -1368,9 +1672,9 @@ var FileExecutionContext = class _FileExecutionContext {
1368
1672
  for (const dir of directories) {
1369
1673
  const stats = fs2.statSync(dir);
1370
1674
  if (!stats.isDirectory()) {
1371
- const relativePath = path2.relative(workspaceRoot, dir);
1675
+ const relativePath = path3.relative(workspaceRoot, dir);
1372
1676
  const isGitIgnored = gitIgnoredFiles.has(relativePath);
1373
- const shouldIgnore = this.matchesAnyPattern(relativePath, ignorePatterns);
1677
+ const shouldIgnore = matchesAnyPattern(relativePath, ignorePatterns);
1374
1678
  if (!isGitIgnored && !shouldIgnore) {
1375
1679
  allFiles.push(relativePath);
1376
1680
  }
@@ -1384,21 +1688,13 @@ var FileExecutionContext = class _FileExecutionContext {
1384
1688
  ignore: allIgnorePatterns
1385
1689
  });
1386
1690
  const relativePaths = matches.map((match) => {
1387
- const absolutePath = path2.resolve(dir, match);
1388
- return path2.relative(workspaceRoot, absolutePath);
1691
+ const absolutePath = path3.resolve(dir, match);
1692
+ return path3.relative(workspaceRoot, absolutePath);
1389
1693
  }).filter((relativePath) => !gitIgnoredFiles.has(relativePath));
1390
1694
  allFiles.push(...relativePaths);
1391
1695
  }
1392
1696
  return [...new Set(allFiles)];
1393
1697
  }
1394
- /**
1395
- * Check if a file matches any of the given patterns using the ignore package
1396
- */
1397
- matchesAnyPattern(filePath, patterns) {
1398
- if (patterns.length === 0) return false;
1399
- const ig = ignore().add(patterns);
1400
- return ig.ignores(filePath);
1401
- }
1402
1698
  /**
1403
1699
  * Check if a file should be processed
1404
1700
  */
@@ -1483,43 +1779,34 @@ var FileExecutionContext = class _FileExecutionContext {
1483
1779
 
1484
1780
  // src/steps/FileFilterStep.ts
1485
1781
  import * as fs3 from "fs";
1486
- import * as path3 from "path";
1487
- import ignore2 from "ignore";
1782
+ import * as path4 from "path";
1488
1783
  var FileFilterStep = class {
1489
1784
  environment;
1490
1785
  constructor(environment) {
1491
1786
  this.environment = environment;
1492
1787
  }
1493
- /**
1494
- * Check if a file matches any of the given patterns using the ignore package
1495
- */
1496
- matchesAnyPattern(filePath, patterns) {
1497
- if (patterns.length === 0) return false;
1498
- const ig = ignore2().add(patterns);
1499
- return ig.ignores(filePath);
1500
- }
1501
1788
  /**
1502
1789
  * Evaluate a single file filter condition for a given file path
1503
1790
  */
1504
1791
  evaluateCondition(condition, filePath) {
1505
1792
  const workspaceRoot = this.environment.getWorkspaceRoot();
1506
- const absoluteFilePath = path3.resolve(workspaceRoot, filePath);
1793
+ const absoluteFilePath = path4.resolve(workspaceRoot, filePath);
1507
1794
  if ("fs.siblingExists" in condition) {
1508
1795
  const { filename } = condition["fs.siblingExists"];
1509
- const dir = path3.dirname(absoluteFilePath);
1510
- const siblingPath = path3.join(dir, filename);
1796
+ const dir = path4.dirname(absoluteFilePath);
1797
+ const siblingPath = path4.join(dir, filename);
1511
1798
  return fs3.existsSync(siblingPath);
1512
1799
  }
1513
1800
  if ("fs.ancestorHas" in condition) {
1514
1801
  const { filename } = condition["fs.ancestorHas"];
1515
- let currentDir = path3.dirname(absoluteFilePath);
1516
- const root = path3.parse(currentDir).root;
1802
+ let currentDir = path4.dirname(absoluteFilePath);
1803
+ const root = path4.parse(currentDir).root;
1517
1804
  while (currentDir !== root) {
1518
- const targetPath = path3.join(currentDir, filename);
1805
+ const targetPath = path4.join(currentDir, filename);
1519
1806
  if (fs3.existsSync(targetPath)) {
1520
1807
  return true;
1521
1808
  }
1522
- const parentDir = path3.dirname(currentDir);
1809
+ const parentDir = path4.dirname(currentDir);
1523
1810
  if (parentDir === currentDir) break;
1524
1811
  currentDir = parentDir;
1525
1812
  }
@@ -1527,12 +1814,12 @@ var FileFilterStep = class {
1527
1814
  }
1528
1815
  if ("fs.siblingAny" in condition) {
1529
1816
  const { pattern } = condition["fs.siblingAny"];
1530
- const dir = path3.dirname(absoluteFilePath);
1817
+ const dir = path4.dirname(absoluteFilePath);
1531
1818
  if (!fs3.existsSync(dir)) {
1532
1819
  return false;
1533
1820
  }
1534
1821
  const siblings = fs3.readdirSync(dir);
1535
- return siblings.some((sibling) => this.matchesAnyPattern(sibling, [pattern]));
1822
+ return siblings.some((sibling) => matchesAnyPattern(sibling, [pattern]));
1536
1823
  }
1537
1824
  return false;
1538
1825
  }
@@ -1561,12 +1848,12 @@ var FileFilterStep = class {
1561
1848
  let filteredPaths = filePaths;
1562
1849
  if ((_a = options.include) == null ? void 0 : _a.length) {
1563
1850
  filteredPaths = filteredPaths.filter(
1564
- (filePath) => this.matchesAnyPattern(filePath, options.include)
1851
+ (filePath) => matchesAnyPattern(filePath, options.include)
1565
1852
  );
1566
1853
  }
1567
1854
  if ((_b = options.ignore) == null ? void 0 : _b.length) {
1568
1855
  filteredPaths = filteredPaths.filter(
1569
- (filePath) => !this.matchesAnyPattern(filePath, options.ignore)
1856
+ (filePath) => !matchesAnyPattern(filePath, options.ignore)
1570
1857
  );
1571
1858
  }
1572
1859
  if (Object.keys(options.conditions || {}).length > 0 && filteredPaths.length > 0) {
@@ -1585,126 +1872,6 @@ var FileFilterStep = class {
1585
1872
  // src/steps/FindMatchesStep.ts
1586
1873
  import path8 from "path";
1587
1874
 
1588
- // src/languages.ts
1589
- import { existsSync as existsSync4 } from "fs";
1590
- import { createRequire } from "module";
1591
- import path4 from "path";
1592
- import angular from "@ast-grep/lang-angular";
1593
- import bash from "@ast-grep/lang-bash";
1594
- import c from "@ast-grep/lang-c";
1595
- import cpp from "@ast-grep/lang-cpp";
1596
- import csharp from "@ast-grep/lang-csharp";
1597
- import css from "@ast-grep/lang-css";
1598
- import dart from "@ast-grep/lang-dart";
1599
- import elixir from "@ast-grep/lang-elixir";
1600
- import go from "@ast-grep/lang-go";
1601
- import haskell from "@ast-grep/lang-haskell";
1602
- import html from "@ast-grep/lang-html";
1603
- import java from "@ast-grep/lang-java";
1604
- import javascript from "@ast-grep/lang-javascript";
1605
- import json from "@ast-grep/lang-json";
1606
- import kotlin from "@ast-grep/lang-kotlin";
1607
- import lua from "@ast-grep/lang-lua";
1608
- import markdown from "@ast-grep/lang-markdown";
1609
- import php from "@ast-grep/lang-php";
1610
- import python from "@ast-grep/lang-python";
1611
- import ruby from "@ast-grep/lang-ruby";
1612
- import rust from "@ast-grep/lang-rust";
1613
- import scala from "@ast-grep/lang-scala";
1614
- import sql from "@ast-grep/lang-sql";
1615
- import swift from "@ast-grep/lang-swift";
1616
- import toml from "@ast-grep/lang-toml";
1617
- import tsx from "@ast-grep/lang-tsx";
1618
- import typescript from "@ast-grep/lang-typescript";
1619
- import yaml from "@ast-grep/lang-yaml";
1620
- import { registerDynamicLanguage } from "@ast-grep/napi";
1621
- console.debug = () => {
1622
- };
1623
- var require2 = createRequire(import.meta.url ? import.meta.url : __filename);
1624
- function getGraphQLLibPath() {
1625
- const graphqlDir = path4.dirname(require2.resolve("tree-sitter-graphql"));
1626
- const releaseNode = path4.join(graphqlDir, "../../build/Release/tree_sitter_graphql_binding.node");
1627
- if (existsSync4(releaseNode)) {
1628
- return releaseNode;
1629
- }
1630
- const debugNode = path4.join(graphqlDir, "../../build/Debug/tree_sitter_graphql_binding.node");
1631
- if (existsSync4(debugNode)) {
1632
- return debugNode;
1633
- }
1634
- const soFile = path4.join(graphqlDir, "parser.so");
1635
- if (existsSync4(soFile)) {
1636
- return soFile;
1637
- }
1638
- return null;
1639
- }
1640
- var graphqlPath = getGraphQLLibPath();
1641
- var graphql = {
1642
- // node-gyp-build puts the .node file in build/Release/
1643
- libraryPath: graphqlPath,
1644
- /** the file extensions of the language. e.g. mojo */
1645
- extensions: ["graphql"],
1646
- /** the dylib symbol to load ts-language, default is `tree_sitter_{name}` */
1647
- languageSymbol: "tree_sitter_graphql",
1648
- /** the meta variable leading character, default is $ */
1649
- metaVarChar: "$",
1650
- /**
1651
- * An optional char to replace $ in your pattern.
1652
- * See https://ast-grep.github.io/advanced/custom-language.html#register-language-in-sgconfig-yml
1653
- */
1654
- expandoChar: void 0
1655
- };
1656
- var registeredLanguages = {
1657
- ["Angular" /* Angular */]: angular,
1658
- ["Bash" /* Bash */]: bash,
1659
- ["C" /* C */]: c,
1660
- ["Cpp" /* Cpp */]: cpp,
1661
- ["Csharp" /* Csharp */]: csharp,
1662
- ["Css" /* Css */]: css,
1663
- ["Dart" /* Dart */]: dart,
1664
- ["Elixir" /* Elixir */]: elixir,
1665
- ["Go" /* Go */]: go,
1666
- ["Haskell" /* Haskell */]: haskell,
1667
- ["Html" /* Html */]: html,
1668
- ["Java" /* Java */]: java,
1669
- ["JavaScript" /* JavaScript */]: javascript,
1670
- ["Json" /* Json */]: json,
1671
- ["Kotlin" /* Kotlin */]: kotlin,
1672
- ["Lua" /* Lua */]: lua,
1673
- ["Markdown" /* Markdown */]: markdown,
1674
- ["Php" /* Php */]: php,
1675
- ["Python" /* Python */]: python,
1676
- ["Ruby" /* Ruby */]: ruby,
1677
- ["Rust" /* Rust */]: rust,
1678
- ["Scala" /* Scala */]: scala,
1679
- ["Sql" /* Sql */]: sql,
1680
- ["Swift" /* Swift */]: swift,
1681
- ["Toml" /* Toml */]: toml,
1682
- ["Tsx" /* Tsx */]: tsx,
1683
- ["TypeScript" /* TypeScript */]: typescript,
1684
- ["Yaml" /* Yaml */]: yaml,
1685
- ...graphqlPath ? { ["GraphQL" /* GraphQL */]: graphql } : {}
1686
- };
1687
- registerDynamicLanguage(registeredLanguages);
1688
- var REGISTERED_LANGUAGE_EXTENSIONS = Object.entries(
1689
- registeredLanguages
1690
- ).reduce(
1691
- (acc, [language, registration]) => {
1692
- acc[language] = registration.extensions ?? [];
1693
- return acc;
1694
- },
1695
- {}
1696
- );
1697
- function getLanguageFromFilePath(filePath) {
1698
- const extension = path4.extname(filePath).slice(1);
1699
- const language = findRegisteredLanguageFromExtension(extension);
1700
- return language ?? "Unknown" /* Unknown */;
1701
- }
1702
- function findRegisteredLanguageFromExtension(extension) {
1703
- return Object.keys(REGISTERED_LANGUAGE_EXTENSIONS).find(
1704
- (language) => REGISTERED_LANGUAGE_EXTENSIONS[language].includes(extension)
1705
- );
1706
- }
1707
-
1708
1875
  // src/providers/AstGrepAstProvider.ts
1709
1876
  import { readFile as readFile2 } from "fs/promises";
1710
1877
  import path5 from "path";
@@ -2464,7 +2631,7 @@ var FindMatchesStep = class {
2464
2631
 
2465
2632
  // src/steps/GotoDefinitionStep.ts
2466
2633
  import path9 from "path";
2467
- import ignore3 from "ignore";
2634
+ import ignore2 from "ignore";
2468
2635
  var GotoDefinitionStep = class {
2469
2636
  maxDepth = 1;
2470
2637
  environment;
@@ -2631,7 +2798,7 @@ var GotoDefinitionStep = class {
2631
2798
  return relativePath === spec.path;
2632
2799
  }
2633
2800
  if (spec.glob) {
2634
- const ig = ignore3().add(spec.glob);
2801
+ const ig = ignore2().add(spec.glob);
2635
2802
  return ig.ignores(relativePath);
2636
2803
  }
2637
2804
  if (spec.regex) {
@@ -2645,70 +2812,29 @@ var GotoDefinitionStep = class {
2645
2812
  // src/providers/WispbitViolationValidationProvider.ts
2646
2813
  var WispbitViolationValidationProvider = class {
2647
2814
  config;
2648
- constructor(config) {
2815
+ useInternalEndpoint;
2816
+ constructor(config, useInternalEndpoint = false) {
2649
2817
  this.config = config;
2818
+ this.useInternalEndpoint = useInternalEndpoint;
2650
2819
  }
2651
2820
  async validateViolations(params) {
2652
- var _a;
2653
- const matchesWithIds = params.matches.map((match) => ({
2654
- match,
2655
- matchId: this.generateMatchId(match)
2656
- }));
2657
- const baseUrl = this.config.getBaseUrl();
2658
- const apiKey = this.config.getApiKey();
2659
- const response = await fetch(`${baseUrl}/plv1/validate-violation`, {
2660
- method: "POST",
2661
- headers: {
2662
- "Content-Type": "application/json",
2663
- Authorization: `Bearer ${apiKey}`
2664
- },
2665
- body: JSON.stringify({
2666
- rule: params.rule,
2667
- matches: matchesWithIds.map(({ match, matchId }) => ({
2668
- matchId,
2669
- filePath: match.filePath,
2670
- range: match.range,
2671
- text: match.text,
2672
- language: match.language,
2673
- symbol: match.symbol,
2674
- source: match.source
2675
- })),
2676
- powerlint_version: this.config.getLocalVersion(),
2677
- schema_version: this.config.getSchemaVersion()
2678
- })
2821
+ const apiClient = this.config.getApiClient();
2822
+ const rulePayload = {
2823
+ internalId: params.rule.internalId,
2824
+ internalVersionId: params.rule.internalId,
2825
+ contents: params.rule.prompt
2826
+ };
2827
+ const method = this.useInternalEndpoint ? apiClient.validateViolationInternal.bind(apiClient) : apiClient.validateViolation.bind(apiClient);
2828
+ const result = await method({
2829
+ rule: rulePayload,
2830
+ matches: params.matches,
2831
+ powerlint_version: this.config.getLocalVersion(),
2832
+ schema_version: this.config.getSchemaVersion()
2679
2833
  });
2680
- if (!response.ok) {
2681
- const errorText = await response.text();
2682
- throw new Error(
2683
- `Wispbit validation failed: ${response.status} ${response.statusText} - ${errorText}`
2684
- );
2685
- }
2686
- const result = await response.json();
2687
- const results = [];
2688
- for (const { matchId } of matchesWithIds) {
2689
- const validationResult = (_a = result.results) == null ? void 0 : _a.find((r) => r.matchId === matchId);
2690
- results.push({
2691
- matchId,
2692
- isViolation: Boolean(validationResult == null ? void 0 : validationResult.isViolation),
2693
- reason: String((validationResult == null ? void 0 : validationResult.reason) || "No violation detected"),
2694
- confidence: typeof (validationResult == null ? void 0 : validationResult.confidence) === "number" ? validationResult.confidence : 1
2695
- });
2696
- }
2697
- return results;
2698
- }
2699
- /**
2700
- * Generate a unique ID for a single match
2701
- */
2702
- generateMatchId(match) {
2703
- const matchData = {
2704
- filePath: match.filePath,
2705
- startLine: match.range.start.line,
2706
- startColumn: match.range.start.column || 0,
2707
- endLine: match.range.end.line,
2708
- endColumn: match.range.end.column || 0,
2709
- text: match.text
2834
+ return {
2835
+ validMatches: result.validMatches,
2836
+ skippedMatches: result.skippedMatches
2710
2837
  };
2711
- return hashString(JSON.stringify(matchData)).substring(0, 16);
2712
2838
  }
2713
2839
  };
2714
2840
 
@@ -2725,31 +2851,20 @@ var LLMStep = class {
2725
2851
  this.storage = new Storage(environment);
2726
2852
  }
2727
2853
  /**
2728
- * Generate a unique ID for a single match
2729
- * Hash is based on filePath + range + text
2854
+ * Generate a cache key for a single match LLM validation
2730
2855
  */
2731
- generateMatchId(match) {
2856
+ generateMatchCacheKey(match) {
2732
2857
  const matchData = {
2733
2858
  filePath: match.filePath,
2734
2859
  startLine: match.range.start.line,
2735
2860
  startColumn: match.range.start.column || 0,
2736
2861
  endLine: match.range.end.line,
2737
2862
  endColumn: match.range.end.column || 0,
2738
- text: match.text
2863
+ text: match.text,
2864
+ prompt: match.text
2865
+ // Include prompt in cache key
2739
2866
  };
2740
- return hashString(JSON.stringify(matchData)).substring(0, 16);
2741
- }
2742
- /**
2743
- * Generate a hash for a prompt string
2744
- */
2745
- hashPrompt(prompt) {
2746
- return hashString(prompt);
2747
- }
2748
- /**
2749
- * Generate a cache key for a single match LLM validation
2750
- */
2751
- generateMatchCacheKey(matchId, promptHash) {
2752
- return `match_${matchId}_prompt_${promptHash}.json`;
2867
+ return JSON.stringify(matchData);
2753
2868
  }
2754
2869
  /**
2755
2870
  * Execute the actual LLM validation for uncached matches
@@ -2759,38 +2874,42 @@ var LLMStep = class {
2759
2874
  */
2760
2875
  async executeLLMValidation(rule, uncachedMatches) {
2761
2876
  const llmStartTime = Date.now();
2762
- const promptHash = this.hashPrompt(rule.prompt);
2763
- const validationProvider = new WispbitViolationValidationProvider(this.config);
2877
+ const validationProvider = new WispbitViolationValidationProvider(
2878
+ this.config,
2879
+ this.config.shouldUseInternalEndpoint()
2880
+ );
2764
2881
  const validationParams = {
2765
2882
  rule,
2766
2883
  matches: uncachedMatches
2767
2884
  };
2768
2885
  this.eventEmitter.startLLMValidation(rule.id, uncachedMatches.length);
2769
- const validationResults = await validationProvider.validateViolations(validationParams);
2770
- const newViolationMatches = [];
2771
- for (const result of validationResults) {
2772
- const originalMatch = uncachedMatches.find(
2773
- (match) => this.generateMatchId(match) === result.matchId
2774
- );
2775
- if (originalMatch && result.isViolation) {
2776
- newViolationMatches.push({
2777
- ...originalMatch,
2778
- metadata: {
2779
- fromCache: false,
2780
- llmValidation: {
2781
- isViolation: true,
2782
- confidence: result.confidence,
2783
- reason: result.reason
2784
- }
2785
- }
2786
- });
2886
+ const validationResponse = await validationProvider.validateViolations(validationParams);
2887
+ const newViolationMatches = validationResponse.validMatches.map((match) => ({
2888
+ ...match,
2889
+ metadata: {
2890
+ fromCache: false,
2891
+ llmValidation: {
2892
+ isViolation: true,
2893
+ confidence: 1,
2894
+ reason: "Confirmed violation"
2895
+ }
2787
2896
  }
2788
- const cacheKey = this.generateMatchCacheKey(result.matchId, promptHash);
2897
+ }));
2898
+ for (const match of validationResponse.validMatches) {
2899
+ const cacheKey = this.generateMatchCacheKey(match);
2789
2900
  const decision = {
2790
- matchId: result.matchId,
2791
- isViolation: result.isViolation,
2792
- confidence: result.confidence,
2793
- reason: result.reason
2901
+ isViolation: true,
2902
+ confidence: 1,
2903
+ reason: "Confirmed violation"
2904
+ };
2905
+ await this.storage.saveCache(rule.id, cacheKey, decision);
2906
+ }
2907
+ for (const match of validationResponse.skippedMatches) {
2908
+ const cacheKey = this.generateMatchCacheKey(match);
2909
+ const decision = {
2910
+ isViolation: false,
2911
+ confidence: 1,
2912
+ reason: "Not a violation"
2794
2913
  };
2795
2914
  await this.storage.saveCache(rule.id, cacheKey, decision);
2796
2915
  }
@@ -2814,12 +2933,10 @@ var LLMStep = class {
2814
2933
  */
2815
2934
  async execute(rule, previousMatches) {
2816
2935
  const startTime = Date.now();
2817
- const promptHash = this.hashPrompt(rule.prompt);
2818
2936
  const cachedMatches = [];
2819
2937
  const uncachedMatches = [];
2820
2938
  for (const match of previousMatches) {
2821
- const matchId = this.generateMatchId(match);
2822
- const cacheKey = this.generateMatchCacheKey(matchId, promptHash);
2939
+ const cacheKey = this.generateMatchCacheKey(match);
2823
2940
  const cachedDecision = await this.storage.readCache(rule.id, cacheKey);
2824
2941
  if (cachedDecision) {
2825
2942
  if (cachedDecision.isViolation) {
@@ -2886,7 +3003,7 @@ var RuleExecutor = class {
2886
3003
  /**
2887
3004
  * Execute a single step
2888
3005
  */
2889
- async executeStep(rule, stepName, stepConfig, filePaths, matches, options) {
3006
+ async executeStep(rule, stepName, stepConfig, filePaths, matches, options, _parentStepName) {
2890
3007
  const stepType = stepConfig.type;
2891
3008
  if (stepType === "ast-grep") {
2892
3009
  const { type: _type, language, ...schema } = stepConfig;
@@ -2903,13 +3020,34 @@ var RuleExecutor = class {
2903
3020
  for (let i = 0; i < stepConfig.steps.length; i++) {
2904
3021
  const subStep = stepConfig.steps[i];
2905
3022
  const subStepName = `${stepName}[${i}]`;
3023
+ this.eventEmitter.startStep(
3024
+ rule.id,
3025
+ subStepName,
3026
+ subStep.type,
3027
+ { filePaths, matches },
3028
+ stepName
3029
+ // parent step name
3030
+ );
3031
+ const subStepStartTime = Date.now();
2906
3032
  const subResult = await this.executeStep(
2907
3033
  rule,
2908
3034
  subStepName,
2909
3035
  subStep,
2910
3036
  filePaths,
2911
3037
  matches,
2912
- options
3038
+ options,
3039
+ stepName
3040
+ // parent step name
3041
+ );
3042
+ const subStepExecutionTime = Date.now() - subStepStartTime;
3043
+ this.eventEmitter.completeStep(
3044
+ rule.id,
3045
+ subStepName,
3046
+ subStep.type,
3047
+ subResult,
3048
+ subStepExecutionTime,
3049
+ stepName
3050
+ // parent step name
2913
3051
  );
2914
3052
  if (subResult.matches) {
2915
3053
  allMatches = allMatches.concat(subResult.matches);
@@ -3015,13 +3153,7 @@ var RuleExecutor = class {
3015
3153
  options
3016
3154
  );
3017
3155
  const stepExecutionTime = Date.now() - stepStartTime;
3018
- this.eventEmitter.emit("step:complete", {
3019
- ruleId,
3020
- stepName,
3021
- stepType: stepConfig.type,
3022
- outputs: result,
3023
- executionTime: stepExecutionTime
3024
- });
3156
+ this.eventEmitter.completeStep(ruleId, stepName, stepConfig.type, result, stepExecutionTime);
3025
3157
  if (result.filePaths !== void 0) {
3026
3158
  currentFilePaths = this.executionContext.filterFiles(result.filePaths);
3027
3159
  currentMatches = this.executionContext.filterMatchesByFilePaths(
@@ -3061,8 +3193,8 @@ var InvalidRuleFormatError = class extends Error {
3061
3193
  import { stripVTControlCharacters } from "node:util";
3062
3194
  import chalk from "chalk";
3063
3195
  import ora from "ora";
3064
- var VIOLATION_COLOR = "#ff6b35";
3065
- var SUGGESTION_COLOR = "#4a90e2";
3196
+ var VIOLATION_COLOR = "#f87171";
3197
+ var SUGGESTION_COLOR = "#60a5fa";
3066
3198
  var SKIPPED_COLOR = "#9b59b6";
3067
3199
  var BRAND_COLOR = "#fbbf24";
3068
3200
  function formatClickableRuleId(ruleId, internalId) {
@@ -3172,9 +3304,9 @@ function printSummary(results, summary) {
3172
3304
  }
3173
3305
  let messageType;
3174
3306
  if (ruleData.severity === "violation") {
3175
- messageType = chalk.hex(VIOLATION_COLOR)(" violation".padStart(9));
3307
+ messageType = chalk.hex(VIOLATION_COLOR)("\u25A0") + " " + chalk.hex(VIOLATION_COLOR).bold("violation".padStart(8));
3176
3308
  } else if (ruleData.severity === "suggestion") {
3177
- messageType = chalk.hex(SUGGESTION_COLOR)("suggestion".padEnd(7));
3309
+ messageType = chalk.hex(SUGGESTION_COLOR)("\u25CF") + " " + chalk.hex(SUGGESTION_COLOR).bold("suggestion".padEnd(6));
3178
3310
  } else {
3179
3311
  messageType = chalk.hex(SKIPPED_COLOR)(" skipped".padEnd(9));
3180
3312
  }
@@ -3190,12 +3322,12 @@ function printSummary(results, summary) {
3190
3322
  `;
3191
3323
  } else {
3192
3324
  const sortedMatches = ruleData.matches.sort((a, b) => a.filePath.localeCompare(b.filePath));
3193
- const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <= 20);
3325
+ const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <= 30);
3194
3326
  if (shouldShowCodeSnippets) {
3195
3327
  sortedMatches.forEach((match, index) => {
3196
- output += ` ${match.filePath}
3328
+ if (match.text && match.text.split("\n").length <= 30) {
3329
+ output += ` ${match.filePath}
3197
3330
  `;
3198
- if (match.text && match.text.split("\n").length <= 20) {
3199
3331
  const lines = match.text.split("\n");
3200
3332
  lines.forEach((line, lineIndex) => {
3201
3333
  const lineNumber = match.line + lineIndex;
@@ -3205,6 +3337,10 @@ function printSummary(results, summary) {
3205
3337
  output += ` ${lineNumberBox} ${chalk.dim(line)}
3206
3338
  `;
3207
3339
  });
3340
+ } else {
3341
+ const lineRange = match.endLine ? `(${match.line}:${match.endLine})` : `(${match.line})`;
3342
+ output += ` ${match.filePath} ${chalk.dim(lineRange)}
3343
+ `;
3208
3344
  }
3209
3345
  if (index < sortedMatches.length - 1) {
3210
3346
  output += `
@@ -3252,18 +3388,20 @@ function printRulesList(rules) {
3252
3388
  const tableData = [];
3253
3389
  tableData.push([
3254
3390
  "",
3255
- `${"id".padEnd(12)} ${chalk.dim("\u2502")} ${"severity".padEnd(12)} ${chalk.dim("\u2502")} message`
3391
+ `${"id".padEnd(12)} ${chalk.dim("\u2502")} ${"severity".padEnd(16)} ${chalk.dim("\u2502")} message`
3256
3392
  ]);
3257
3393
  tableData.push([
3258
3394
  "",
3259
- `${chalk.dim("\u2500".repeat(12))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(12))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(80))}`
3395
+ `${chalk.dim("\u2500".repeat(12))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(16))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(80))}`
3260
3396
  ]);
3261
3397
  for (const rule of rules) {
3262
- const severityColor = rule.config.severity === "violation" ? chalk.hex(VIOLATION_COLOR) : chalk.hex(SUGGESTION_COLOR);
3263
- const severityText = severityColor(rule.config.severity);
3398
+ const severityIcon = rule.config.severity === "violation" ? "\u25A0" : "\u25CF";
3399
+ const severityColor = rule.config.severity === "violation" ? chalk.hex(VIOLATION_COLOR).bold : chalk.hex(SUGGESTION_COLOR).bold;
3400
+ const severityIconColor = rule.config.severity === "violation" ? chalk.hex(VIOLATION_COLOR) : chalk.hex(SUGGESTION_COLOR);
3401
+ const severityText = severityIconColor(severityIcon) + " " + severityColor(rule.config.severity);
3264
3402
  const ruleId = formatClickableRuleId(rule.id, rule.internalId);
3265
3403
  const idPadding = Math.max(0, 12 - rule.id.length);
3266
- const severityPadding = Math.max(0, 12 - rule.config.severity.length);
3404
+ const severityPadding = Math.max(0, 16 - (rule.config.severity.length + 2));
3267
3405
  tableData.push([
3268
3406
  "",
3269
3407
  `${ruleId}${" ".repeat(idPadding)} ${chalk.dim("\u2502")} ${severityText}${" ".repeat(severityPadding)} ${chalk.dim("\u2502")} ${rule.config.message}`
@@ -3669,6 +3807,9 @@ async function saveApiKey(apiKey) {
3669
3807
  await fs5.writeFile(configPath, JSON.stringify(newConfig, null, 2));
3670
3808
  }
3671
3809
  async function loadApiKey() {
3810
+ if (process.env.WISPBIT_API_KEY) {
3811
+ return process.env.WISPBIT_API_KEY;
3812
+ }
3672
3813
  const configPath = getConfigFilePath();
3673
3814
  try {
3674
3815
  const configContent = await fs5.readFile(configPath, "utf-8");