@wispbit/local 1.0.29 → 1.0.30

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 (107) hide show
  1. package/dist/cli.js +86 -4241
  2. package/dist/index.js +1 -20
  3. package/dist/package.json +6 -9
  4. package/package.json +4 -5
  5. package/dist/build.d.ts +0 -3
  6. package/dist/build.d.ts.map +0 -1
  7. package/dist/cli.js.map +0 -7
  8. package/dist/index.js.map +0 -7
  9. package/dist/src/api/WispbitApiClient.d.ts +0 -190
  10. package/dist/src/api/WispbitApiClient.d.ts.map +0 -1
  11. package/dist/src/cli.d.ts +0 -16
  12. package/dist/src/cli.d.ts.map +0 -1
  13. package/dist/src/config.d.ts +0 -6
  14. package/dist/src/config.d.ts.map +0 -1
  15. package/dist/src/environment/Config.d.ts +0 -79
  16. package/dist/src/environment/Config.d.ts.map +0 -1
  17. package/dist/src/environment/Environment.d.ts +0 -23
  18. package/dist/src/environment/Environment.d.ts.map +0 -1
  19. package/dist/src/environment/Sandbox.d.ts +0 -48
  20. package/dist/src/environment/Sandbox.d.ts.map +0 -1
  21. package/dist/src/environment/Storage.d.ts +0 -84
  22. package/dist/src/environment/Storage.d.ts.map +0 -1
  23. package/dist/src/index.d.ts +0 -16
  24. package/dist/src/index.d.ts.map +0 -1
  25. package/dist/src/languages.d.ts +0 -36
  26. package/dist/src/languages.d.ts.map +0 -1
  27. package/dist/src/providers/AstGrepAstProvider.d.ts +0 -44
  28. package/dist/src/providers/AstGrepAstProvider.d.ts.map +0 -1
  29. package/dist/src/providers/LanguageBackend.d.ts +0 -74
  30. package/dist/src/providers/LanguageBackend.d.ts.map +0 -1
  31. package/dist/src/providers/RuleProvider.d.ts +0 -46
  32. package/dist/src/providers/RuleProvider.d.ts.map +0 -1
  33. package/dist/src/providers/ScipIntelligenceProvider.d.ts +0 -84
  34. package/dist/src/providers/ScipIntelligenceProvider.d.ts.map +0 -1
  35. package/dist/src/providers/ViolationValidationProvider.d.ts +0 -38
  36. package/dist/src/providers/ViolationValidationProvider.d.ts.map +0 -1
  37. package/dist/src/providers/WispbitRuleProvider.d.ts +0 -29
  38. package/dist/src/providers/WispbitRuleProvider.d.ts.map +0 -1
  39. package/dist/src/providers/WispbitViolationValidationProvider.d.ts +0 -12
  40. package/dist/src/providers/WispbitViolationValidationProvider.d.ts.map +0 -1
  41. package/dist/src/schemas.d.ts +0 -1270
  42. package/dist/src/schemas.d.ts.map +0 -1
  43. package/dist/src/steps/ExecutionEventEmitter.d.ts +0 -191
  44. package/dist/src/steps/ExecutionEventEmitter.d.ts.map +0 -1
  45. package/dist/src/steps/FileExecutionContext.d.ts +0 -87
  46. package/dist/src/steps/FileExecutionContext.d.ts.map +0 -1
  47. package/dist/src/steps/FileFilterStep.d.ts +0 -28
  48. package/dist/src/steps/FileFilterStep.d.ts.map +0 -1
  49. package/dist/src/steps/FileFilterStep.test.d.ts +0 -2
  50. package/dist/src/steps/FileFilterStep.test.d.ts.map +0 -1
  51. package/dist/src/steps/FindMatchesStep.d.ts +0 -41
  52. package/dist/src/steps/FindMatchesStep.d.ts.map +0 -1
  53. package/dist/src/steps/FindMatchesStep.test.d.ts +0 -2
  54. package/dist/src/steps/FindMatchesStep.test.d.ts.map +0 -1
  55. package/dist/src/steps/GotoDefinitionStep.d.ts +0 -86
  56. package/dist/src/steps/GotoDefinitionStep.d.ts.map +0 -1
  57. package/dist/src/steps/LLMStep.d.ts +0 -40
  58. package/dist/src/steps/LLMStep.d.ts.map +0 -1
  59. package/dist/src/steps/RuleExecutor.d.ts +0 -39
  60. package/dist/src/steps/RuleExecutor.d.ts.map +0 -1
  61. package/dist/src/steps/RuleExecutor.test.d.ts +0 -2
  62. package/dist/src/steps/RuleExecutor.test.d.ts.map +0 -1
  63. package/dist/src/test/TestExecutor.d.ts +0 -38
  64. package/dist/src/test/TestExecutor.d.ts.map +0 -1
  65. package/dist/src/test/rules.test.d.ts +0 -2
  66. package/dist/src/test/rules.test.d.ts.map +0 -1
  67. package/dist/src/types.d.ts +0 -197
  68. package/dist/src/types.d.ts.map +0 -1
  69. package/dist/src/utils/asciiFrames.d.ts +0 -5
  70. package/dist/src/utils/asciiFrames.d.ts.map +0 -1
  71. package/dist/src/utils/coordinates.d.ts +0 -12
  72. package/dist/src/utils/coordinates.d.ts.map +0 -1
  73. package/dist/src/utils/debugLogger.d.ts +0 -6
  74. package/dist/src/utils/debugLogger.d.ts.map +0 -1
  75. package/dist/src/utils/diffValidation.d.ts +0 -24
  76. package/dist/src/utils/diffValidation.d.ts.map +0 -1
  77. package/dist/src/utils/diffValidation.test.d.ts +0 -2
  78. package/dist/src/utils/diffValidation.test.d.ts.map +0 -1
  79. package/dist/src/utils/formatters.d.ts +0 -58
  80. package/dist/src/utils/formatters.d.ts.map +0 -1
  81. package/dist/src/utils/generateTreeDump.d.ts +0 -19
  82. package/dist/src/utils/generateTreeDump.d.ts.map +0 -1
  83. package/dist/src/utils/git.d.ts +0 -68
  84. package/dist/src/utils/git.d.ts.map +0 -1
  85. package/dist/src/utils/git.test.d.ts +0 -2
  86. package/dist/src/utils/git.test.d.ts.map +0 -1
  87. package/dist/src/utils/hashString.d.ts +0 -2
  88. package/dist/src/utils/hashString.d.ts.map +0 -1
  89. package/dist/src/utils/patternMatching.d.ts +0 -2
  90. package/dist/src/utils/patternMatching.d.ts.map +0 -1
  91. package/dist/src/utils/readTextAtRange.d.ts +0 -10
  92. package/dist/src/utils/readTextAtRange.d.ts.map +0 -1
  93. package/dist/src/utils/ruleExecution.d.ts +0 -20
  94. package/dist/src/utils/ruleExecution.d.ts.map +0 -1
  95. package/dist/src/utils/snapshotComparison.d.ts +0 -16
  96. package/dist/src/utils/snapshotComparison.d.ts.map +0 -1
  97. package/dist/src/utils/startupScreen.d.ts +0 -5
  98. package/dist/src/utils/startupScreen.d.ts.map +0 -1
  99. package/dist/src/utils/validateRule.d.ts +0 -52
  100. package/dist/src/utils/validateRule.d.ts.map +0 -1
  101. package/dist/src/validationSchemas.d.ts +0 -553
  102. package/dist/src/validationSchemas.d.ts.map +0 -1
  103. package/dist/src/version.d.ts +0 -3
  104. package/dist/src/version.d.ts.map +0 -1
  105. package/dist/tsconfig.tsbuildinfo +0 -1
  106. package/dist/vitest.config.d.mts +0 -3
  107. package/dist/vitest.config.d.mts.map +0 -1
package/dist/cli.js CHANGED
@@ -1,3931 +1,88 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/cli.ts
4
- import * as fs5 from "fs/promises";
5
- import * as os3 from "os";
6
- import * as path10 from "path";
7
- import Big from "big.js";
8
- import chalk4 from "chalk";
9
- import dotenv from "dotenv";
10
- import meow from "meow";
11
- import semver from "semver";
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
- execution: z2.enum(["llm", "deterministic"]).optional()
250
- })
251
- )
252
- });
253
- var ValidateViolationRequestSchema = z2.object({
254
- rule: z2.object({
255
- internalId: z2.string(),
256
- internalVersionId: z2.string(),
257
- contents: z2.string()
258
- }),
259
- matches: z2.array(z2.any()),
260
- powerlint_version: z2.string(),
261
- schema_version: z2.string()
262
- });
263
- var WispbitApiClient = class {
264
- baseUrl;
265
- apiKey;
266
- constructor(config) {
267
- this.baseUrl = config.baseUrl;
268
- this.apiKey = config.apiKey;
269
- }
270
- /**
271
- * Make a request to the Wispbit API with retry logic
272
- */
273
- async request(endpoint, data, responseSchema) {
274
- const url = `${this.baseUrl}${endpoint}`;
275
- const response = await pRetry(
276
- async () => {
277
- const res = await fetch(url, {
278
- method: "POST",
279
- headers: {
280
- "Content-Type": "application/json",
281
- Authorization: `Bearer ${this.apiKey}`
282
- },
283
- body: JSON.stringify(data)
284
- });
285
- if (!res.ok) {
286
- const errorText = await res.text();
287
- throw new Error(
288
- `Wispbit API request failed: ${res.status} ${res.statusText} - ${errorText}`
289
- );
290
- }
291
- return res;
292
- },
293
- {
294
- retries: 3,
295
- minTimeout: 1e3,
296
- maxTimeout: 5e3,
297
- onFailedAttempt: (error) => {
298
- console.warn(
299
- `API request to ${endpoint} failed (attempt ${error.attemptNumber}/4): ${error.message}`
300
- );
301
- }
302
- }
303
- );
304
- const json2 = await response.json();
305
- if (responseSchema) {
306
- return responseSchema.parse(json2);
307
- }
308
- return json2;
309
- }
310
- /**
311
- * Initialize PowerLint configuration with Wispbit
312
- */
313
- async initialize(request) {
314
- const validatedRequest = InitializeRequestSchema.parse(request);
315
- return await this.request("/plv1/initialize", validatedRequest, InitializeResponseSchema);
316
- }
317
- /**
318
- * Get rules from Wispbit Cloud
319
- */
320
- async getRules(request) {
321
- const validatedRequest = GetRulesRequestSchema.parse(request);
322
- return await this.request("/plv1/get-rules", validatedRequest, GetRulesResponseSchema);
323
- }
324
- /**
325
- * Validate violations with Wispbit
326
- */
327
- async validateViolation(request) {
328
- const validatedRequest = ValidateViolationRequestSchema.parse(request);
329
- return await this.request(
330
- "/plv1/validate-violation",
331
- validatedRequest,
332
- ValidateViolationResponseSchema
333
- );
334
- }
335
- /**
336
- * Validate violations with Wispbit (internal endpoint for testing)
337
- */
338
- async validateViolationInternal(request) {
339
- const validatedRequest = ValidateViolationRequestSchema.parse(request);
340
- return await this.request(
341
- "/plv1/internal/validate-violation",
342
- validatedRequest,
343
- ValidateViolationResponseSchema
344
- );
345
- }
346
- };
347
-
348
- // src/version.ts
349
- import { readFileSync } from "fs";
350
- import { dirname, join } from "path";
351
- import { fileURLToPath } from "url";
352
- import latestVersion from "latest-version";
353
- function getCurrentVersion() {
354
- const filename = fileURLToPath(import.meta.url ? import.meta.url : __filename);
355
- const __dirname = dirname(filename);
356
- const packageJsonPath = join(__dirname, "../package.json");
357
- try {
358
- const file = readFileSync(packageJsonPath, "utf8");
359
- const packageJson = JSON.parse(file);
360
- return packageJson.version;
361
- } catch {
362
- return "0.0.0";
363
- }
364
- }
365
- async function getLatestVersion() {
366
- try {
367
- const latestCliVersion = await latestVersion("@wispbit/local");
368
- return latestCliVersion;
369
- } catch {
370
- return getCurrentVersion();
371
- }
372
- }
373
-
374
- // src/environment/Config.ts
375
- var Config = class _Config {
376
- config;
377
- apiKey = null;
378
- baseUrl = null;
379
- apiClient = null;
380
- useInternalEndpoint = false;
381
- constructor(config, options) {
382
- this.config = {
383
- ...config,
384
- ignoredGlobs: config.ignoredGlobs || []
385
- };
386
- this.apiKey = config.apiKey || null;
387
- this.baseUrl = config.baseUrl || null;
388
- this.useInternalEndpoint = (options == null ? void 0 : options.useInternalEndpoint) ?? false;
389
- if (this.apiKey && this.baseUrl) {
390
- this.apiClient = new WispbitApiClient({
391
- baseUrl: this.baseUrl,
392
- apiKey: this.apiKey
393
- });
394
- }
395
- }
396
- getIgnoredGlobs() {
397
- return this.config.ignoredGlobs || [];
398
- }
399
- /**
400
- * Get the Wispbit API key
401
- * @returns The API key or null if not set
402
- */
403
- getApiKey() {
404
- return this.apiKey;
405
- }
406
- /**
407
- * Get the Wispbit API base URL
408
- * @returns The base URL
409
- */
410
- getBaseUrl() {
411
- return this.baseUrl;
412
- }
413
- /**
414
- * Get the Wispbit API client
415
- * @returns The API client instance
416
- */
417
- getApiClient() {
418
- if (!this.apiClient) {
419
- throw new Error("API client not initialized. Config must have both apiKey and baseUrl.");
420
- }
421
- return this.apiClient;
422
- }
423
- /**
424
- * Get the local PowerLint version
425
- * @returns The current PowerLint version
426
- */
427
- getLocalVersion() {
428
- return getCurrentVersion();
429
- }
430
- /**
431
- * Get the schema version
432
- * @returns The schema version
433
- */
434
- getSchemaVersion() {
435
- return "v1";
436
- }
437
- /**
438
- * Initialize configuration without network validation (for testing)
439
- * @param options Optional configuration options
440
- * @returns Config instance
441
- */
442
- static initializeWithoutNetwork(options = {}) {
443
- const finalBaseUrl = options.baseUrl || process.env.WISPBIT_API_BASE_URL || "https://api.wispbit.com";
444
- const finalApiKey = options.apiKey || process.env.WISPBIT_API_KEY || null;
445
- const ignoredGlobs = options.ignoredGlobs || [];
446
- return new _Config(
447
- {
448
- ignoredGlobs,
449
- apiKey: finalApiKey || void 0,
450
- baseUrl: finalBaseUrl
451
- },
452
- {
453
- useInternalEndpoint: true
454
- }
455
- );
456
- }
457
- /**
458
- * Initialize configuration by validating API key and repository URL with Wispbit
459
- * @param environment Environment instance to get repository URL
460
- * @param apiKey Optional API key to use for initialization. If not provided, will use environment variable
461
- * @returns Promise<Config | null> - Config if valid, null if API key missing/invalid
462
- */
463
- static async initialize(environment, {
464
- apiKey,
465
- baseUrl
466
- }) {
467
- var _a;
468
- const finalBaseUrl = baseUrl || process.env.WISPBIT_API_BASE_URL || "https://api.wispbit.com";
469
- const finalApiKey = apiKey || process.env.WISPBIT_API_KEY || null;
470
- if (!finalApiKey) {
471
- console.log("No API key found");
472
- return { failed: true, error: "INVALID_API_KEY" };
473
- }
474
- const repositoryUrl = await environment.getRepositoryUrl();
475
- const tempClient = new WispbitApiClient({
476
- baseUrl: finalBaseUrl,
477
- apiKey: finalApiKey
478
- });
479
- const result = await tempClient.initialize({
480
- repository_url: repositoryUrl,
481
- powerlint_version: getCurrentVersion(),
482
- schema_version: "v1"
483
- });
484
- if (result.invalid_api_key) {
485
- return { failed: true, error: "INVALID_API_KEY" };
486
- }
487
- if (!result.is_valid_repository) {
488
- return { failed: true, error: "INVALID_REPOSITORY" };
489
- }
490
- const ignoredGlobs = ((_a = result.config) == null ? void 0 : _a.ignored_globs) || [];
491
- const config = new _Config({ ignoredGlobs, apiKey: finalApiKey, baseUrl: finalBaseUrl });
492
- return config;
493
- }
494
- /**
495
- * Check if PowerLint is configured (has valid API key)
496
- */
497
- isConfigured() {
498
- return this.getApiKey() !== null;
499
- }
500
- /**
501
- * Check if the internal validation endpoint should be used
502
- */
503
- shouldUseInternalEndpoint() {
504
- return this.useInternalEndpoint;
505
- }
506
- };
507
-
508
- // src/utils/git.ts
509
- import { exec, execSync } from "child_process";
510
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
511
- import { promisify } from "util";
512
-
513
- // src/utils/hashString.ts
514
- import { createHash } from "crypto";
515
- function hashString(str) {
516
- return createHash("sha256").update(str).digest("hex");
517
- }
518
-
519
- // src/utils/git.ts
520
- var execPromise = promisify(exec);
521
- function findGitRoot() {
522
- const stdout = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" });
523
- return stdout.trim();
524
- }
525
- async function getGitIgnoredFiles(repoRoot) {
526
- const { stdout } = await execPromise("git ls-files --ignored --exclude-standard --others", {
527
- cwd: repoRoot,
528
- maxBuffer: 50 * 1024 * 1024
529
- });
530
- return stdout.split("\n").filter(Boolean).map((file) => file.trim());
531
- }
532
- async function getRepositoryUrl(repoRoot, remoteName = "origin") {
533
- const { stdout } = await execPromise(`git config --get remote.${remoteName}.url`, {
534
- cwd: repoRoot
535
- });
536
- return stdout.trim() || null;
537
- }
538
- async function getDefaultBranch(repoRoot, remoteName = "origin") {
539
- try {
540
- const { stdout } = await execPromise(`git rev-parse --abbrev-ref ${remoteName}/HEAD`, {
541
- cwd: repoRoot
542
- });
543
- const fullRef = stdout.trim();
544
- const branchName = fullRef.split("/").pop();
545
- return branchName || null;
546
- } catch (error) {
547
- const commonBranches = ["main", "master"];
548
- for (const branch of commonBranches) {
549
- try {
550
- await execPromise(`git rev-parse --verify ${branch}`, { cwd: repoRoot });
551
- return branch;
552
- } catch {
553
- }
554
- }
555
- return null;
556
- }
557
- }
558
- async function tryGetUpstream(repoRoot) {
559
- const { stdout } = await execPromise(`git rev-parse --abbrev-ref --symbolic-full-name @{u}`, {
560
- cwd: repoRoot
561
- });
562
- return stdout.trim() || void 0;
563
- }
564
- function tryGetGraphiteParent(repoRoot, branch) {
565
- const metadataPath = `.git/refs/branch-metadata/${branch}`;
566
- const fullPath = `${repoRoot}/${metadataPath}`;
567
- if (!existsSync2(fullPath)) {
568
- return void 0;
569
- }
570
- const content = readFileSync2(fullPath, "utf-8");
571
- const match = content.match(/"parent"\s*:\s*"([^"]+)"/);
572
- return match == null ? void 0 : match[1];
573
- }
574
- function shellQuote(path11) {
575
- return `'${path11.replace(/'/g, "'\\''")}'`;
576
- }
577
- function joinAsShellArgs(paths) {
578
- return paths.map(shellQuote).join(" ");
579
- }
580
- function resolveIncludes(raw) {
581
- const all = {
582
- committed: true,
583
- staged: true,
584
- unstaged: true,
585
- untracked: true
586
- };
587
- if (!raw || raw.length === 0) return all;
588
- const s = new Set(raw);
589
- return {
590
- committed: s.has("committed"),
591
- staged: s.has("staged"),
592
- unstaged: s.has("unstaged"),
593
- untracked: s.has("untracked")
594
- };
595
- }
596
- function parseNameStatusZ(buffer, source) {
597
- if (!buffer) return [];
598
- const entries = [];
599
- const parts = buffer.split("\0").filter(Boolean);
600
- for (let i = 0; i < parts.length; ) {
601
- const status = parts[i];
602
- if (!status) break;
603
- if (status.startsWith("R")) {
604
- const oldPath = parts[i + 1];
605
- const newPath = parts[i + 2];
606
- if (oldPath && newPath) {
607
- entries.push({ status, path: newPath, oldPath, source });
608
- i += 3;
609
- } else {
610
- i++;
611
- }
612
- } else {
613
- const path11 = parts[i + 1];
614
- if (path11) {
615
- entries.push({ status, path: path11, source });
616
- i += 2;
617
- } else {
618
- i++;
619
- }
620
- }
621
- }
622
- return entries;
623
- }
624
- function parseLsFilesZ(buffer) {
625
- if (!buffer) return [];
626
- return buffer.split("\0").filter(Boolean).map((path11) => ({ status: "U", path: path11, source: "untracked" }));
627
- }
628
- function dedupeEntries(entries) {
629
- const byPath = /* @__PURE__ */ new Map();
630
- for (const entry of entries) {
631
- const existing = byPath.get(entry.path);
632
- if (!existing) {
633
- byPath.set(entry.path, entry);
634
- } else {
635
- const existingPriority = getPriority(existing.source);
636
- const newPriority = getPriority(entry.source);
637
- if (newPriority > existingPriority) {
638
- byPath.set(entry.path, entry);
639
- } else if (newPriority === existingPriority && entry.oldPath) {
640
- existing.oldPath = existing.oldPath || entry.oldPath;
641
- }
642
- }
643
- }
644
- return Array.from(byPath.values());
645
- }
646
- function getPriority(source) {
647
- switch (source) {
648
- case "untracked":
649
- return 4;
650
- case "unstaged":
651
- return 3;
652
- case "staged":
653
- return 2;
654
- case "committed":
655
- return 1;
656
- }
657
- }
658
- function splitGitPatchPerFile(patchOutput) {
659
- const patches = /* @__PURE__ */ new Map();
660
- if (!patchOutput) return patches;
661
- const sections = patchOutput.split(/^diff --git /m).filter(Boolean);
662
- for (const section of sections) {
663
- const lines = section.split("\n");
664
- const firstLine = lines[0];
665
- const match = firstLine.match(/a\/(.+?) b\//);
666
- if (match) {
667
- const filename = match[1];
668
- patches.set(filename, "diff --git " + section);
669
- }
670
- }
671
- return patches;
672
- }
673
- function toChangeStatus(entry) {
674
- if (entry.status === "U" || entry.status === "A") return "added";
675
- if (entry.status === "D") return "removed";
676
- return "modified";
677
- }
678
- function stripDiffHeaders(patch) {
679
- if (!patch) return "";
680
- const lines = patch.split("\n");
681
- const output = [];
682
- let inHunk = false;
683
- for (const line of lines) {
684
- if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("new file mode") || line.startsWith("deleted file mode") || line.startsWith("old mode") || line.startsWith("new mode") || line.startsWith("similarity index") || line.startsWith("rename from") || line.startsWith("rename to") || line.startsWith("copy from") || line.startsWith("copy to") || line === "\") {
685
- continue;
686
- }
687
- if (line.startsWith("@@")) {
688
- inHunk = true;
689
- }
690
- if (inHunk) {
691
- output.push(line);
692
- }
693
- }
694
- return output.join("\n").trimEnd();
695
- }
696
- function parseRange(selector) {
697
- const threeDotsMatch = selector.match(/^(.+)\.\.\.(.+)$/);
698
- if (threeDotsMatch) {
699
- return [threeDotsMatch[1], threeDotsMatch[2], true];
700
- }
701
- const twoDotsMatch = selector.match(/^(.+)\.\.([^.].*)$/);
702
- if (twoDotsMatch) {
703
- return [twoDotsMatch[1], twoDotsMatch[2], false];
704
- }
705
- return [selector, selector, false];
706
- }
707
- function parseRangeRight(selector) {
708
- const [, right] = parseRange(selector);
709
- return right;
710
- }
711
- async function getCurrentBranch(repoRoot) {
712
- const { stdout } = await execPromise("git rev-parse --abbrev-ref HEAD", {
713
- cwd: repoRoot
714
- });
715
- return stdout.trim();
716
- }
717
- function validateWorktreeIncludes(commitSelector, includes, currentBranch) {
718
- const isRange = commitSelector.includes("..");
719
- if (!isRange) {
720
- return null;
721
- }
722
- const hasWorktreeIncludes = includes.includes("staged") || includes.includes("unstaged") || includes.includes("untracked");
723
- if (!hasWorktreeIncludes) {
724
- return null;
725
- }
726
- const rangeRight = parseRangeRight(commitSelector);
727
- const endsAtCurrent = rangeRight === "HEAD" || rangeRight === currentBranch;
728
- if (!endsAtCurrent) {
729
- return `Worktree includes (staged, unstaged, untracked) require range to end at HEAD or current branch (${currentBranch}). Got: ${commitSelector}`;
730
- }
731
- return null;
732
- }
733
- async function getDefaultCommitSelector(repoRoot) {
734
- const { stdout: currentBranchOutput } = await execPromise("git rev-parse --abbrev-ref HEAD", {
735
- cwd: repoRoot
736
- });
737
- const currentBranch = currentBranchOutput.trim();
738
- const defaultInclude = ["committed", "staged", "unstaged", "untracked"];
739
- const graphiteParent = tryGetGraphiteParent(repoRoot, currentBranch);
740
- if (graphiteParent) {
741
- return {
742
- commitSelector: `${graphiteParent}...${currentBranch}`,
743
- include: defaultInclude
744
- };
745
- }
746
- const defaultBranch = await getDefaultBranch(repoRoot);
747
- if (defaultBranch) {
748
- if (currentBranch === defaultBranch) {
749
- const upstream = await tryGetUpstream(repoRoot).catch(() => void 0);
750
- if (upstream) {
751
- return {
752
- commitSelector: `${upstream}...HEAD`,
753
- include: defaultInclude
754
- };
755
- }
756
- return {
757
- commitSelector: `HEAD~1...HEAD`,
758
- include: defaultInclude
759
- };
760
- }
761
- const originBranch = `origin/${defaultBranch}`;
762
- const { stdout } = await execPromise(`git rev-parse --verify ${originBranch}`, {
763
- cwd: repoRoot
764
- });
765
- const base = stdout.trim() ? originBranch : defaultBranch;
766
- return {
767
- commitSelector: `${base}...${currentBranch}`,
768
- include: defaultInclude
769
- };
770
- }
771
- return {
772
- commitSelector: `HEAD...HEAD`,
773
- include: defaultInclude
774
- };
775
- }
776
- async function getChangedFiles(repoRoot, options) {
777
- const selector = options.commitSelector.trim();
778
- const isRange = selector.includes("..");
779
- const { stdout: currentBranchOutput } = await execPromise("git rev-parse --abbrev-ref HEAD", {
780
- cwd: repoRoot
781
- });
782
- const currentBranch = currentBranchOutput.trim();
783
- const { stdout: currentCommitOutput } = await execPromise("git rev-parse HEAD", {
784
- cwd: repoRoot
785
- });
786
- const currentCommit = currentCommitOutput.trim();
787
- let compareTo = "";
788
- let parentRef = "";
789
- if (isRange) {
790
- compareTo = selector;
791
- parentRef = selector;
792
- } else {
793
- const graphiteParent = tryGetGraphiteParent(repoRoot, currentBranch);
794
- const defaultBranch = await getDefaultBranch(repoRoot);
795
- const target = selector || graphiteParent || `origin/${defaultBranch || "main"}`;
796
- parentRef = target;
797
- const mergeBaseCmd = `git merge-base --fork-point ${shellQuote(target)} HEAD || git merge-base ${shellQuote(target)} HEAD`;
798
- const { stdout: mergeBaseOutput } = await execPromise(mergeBaseCmd, { cwd: repoRoot });
799
- compareTo = mergeBaseOutput.trim();
800
- if (!selector && currentBranch === (defaultBranch || "main")) {
801
- const upstream = await tryGetUpstream(repoRoot).catch(() => void 0);
802
- if (upstream) {
803
- parentRef = upstream;
804
- const { stdout: upstreamMergeBase } = await execPromise(
805
- `git merge-base --fork-point ${shellQuote(upstream)} HEAD || git merge-base ${shellQuote(upstream)} HEAD`,
806
- { cwd: repoRoot }
807
- );
808
- compareTo = upstreamMergeBase.trim();
809
- } else {
810
- parentRef = "HEAD~1";
811
- compareTo = "HEAD~1";
812
- }
813
- }
814
- }
815
- const includes = resolveIncludes(options.include);
816
- const validationError = validateWorktreeIncludes(selector, options.include, currentBranch);
817
- if (validationError) {
818
- throw new Error(validationError);
819
- }
820
- const allEntries = [];
821
- if (includes.committed) {
822
- if (isRange) {
823
- const [rangeA, rangeB] = parseRange(selector);
824
- const { stdout } = await execPromise(
825
- `git diff --name-status -M -z ${shellQuote(rangeA)}..${shellQuote(rangeB)}`,
826
- { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 }
827
- );
828
- allEntries.push(...parseNameStatusZ(stdout, "committed"));
829
- } else {
830
- const { stdout } = await execPromise(`git diff --name-status -M -z ${compareTo}..HEAD`, {
831
- cwd: repoRoot,
832
- maxBuffer: 50 * 1024 * 1024
833
- });
834
- allEntries.push(...parseNameStatusZ(stdout, "committed"));
835
- }
836
- }
837
- if (includes.staged) {
838
- const { stdout } = await execPromise(`git diff --name-status -M -z --cached`, {
839
- cwd: repoRoot,
840
- maxBuffer: 50 * 1024 * 1024
841
- });
842
- allEntries.push(...parseNameStatusZ(stdout, "staged"));
843
- }
844
- if (includes.unstaged) {
845
- const { stdout } = await execPromise(`git diff --name-status -M -z`, {
846
- cwd: repoRoot,
847
- maxBuffer: 50 * 1024 * 1024
848
- });
849
- allEntries.push(...parseNameStatusZ(stdout, "unstaged"));
850
- }
851
- if (includes.untracked) {
852
- const { stdout } = await execPromise(`git ls-files --others --exclude-standard -z`, {
853
- cwd: repoRoot,
854
- maxBuffer: 50 * 1024 * 1024
855
- });
856
- allEntries.push(...parseLsFilesZ(stdout));
857
- }
858
- const entries = dedupeEntries(allEntries);
859
- const committedEntries = entries.filter((e) => e.source === "committed");
860
- const stagedEntries = entries.filter((e) => e.source === "staged");
861
- const unstagedEntries = entries.filter((e) => e.source === "unstaged");
862
- const untrackedEntries = entries.filter((e) => e.source === "untracked");
863
- const patchByFile = /* @__PURE__ */ new Map();
864
- if (committedEntries.length > 0) {
865
- const paths = committedEntries.map((e) => e.path);
866
- let diffCmd = "";
867
- if (isRange) {
868
- const [rangeA, rangeB] = parseRange(selector);
869
- diffCmd = `git diff -U0 -M ${shellQuote(rangeA)}..${shellQuote(rangeB)} -- ${joinAsShellArgs(paths)}`;
870
- } else {
871
- diffCmd = `git diff -U0 -M ${compareTo}..HEAD -- ${joinAsShellArgs(paths)}`;
872
- }
873
- const { stdout: patchOutput } = await execPromise(diffCmd, {
874
- cwd: repoRoot,
875
- maxBuffer: 50 * 1024 * 1024
876
- });
877
- const patches = splitGitPatchPerFile(patchOutput);
878
- patches.forEach((patch, filename) => patchByFile.set(filename, stripDiffHeaders(patch)));
879
- }
880
- if (stagedEntries.length > 0) {
881
- const paths = stagedEntries.map((e) => e.path);
882
- const diffCmd = `git diff -U0 -M --cached -- ${joinAsShellArgs(paths)}`;
883
- const { stdout: patchOutput } = await execPromise(diffCmd, {
884
- cwd: repoRoot,
885
- maxBuffer: 50 * 1024 * 1024
886
- });
887
- const patches = splitGitPatchPerFile(patchOutput);
888
- patches.forEach((patch, filename) => patchByFile.set(filename, stripDiffHeaders(patch)));
889
- }
890
- if (unstagedEntries.length > 0) {
891
- const paths = unstagedEntries.map((e) => e.path);
892
- const diffCmd = `git diff -U0 -M -- ${joinAsShellArgs(paths)}`;
893
- const { stdout: patchOutput } = await execPromise(diffCmd, {
894
- cwd: repoRoot,
895
- maxBuffer: 50 * 1024 * 1024
896
- });
897
- const patches = splitGitPatchPerFile(patchOutput);
898
- patches.forEach((patch, filename) => patchByFile.set(filename, stripDiffHeaders(patch)));
899
- }
900
- for (const entry of untrackedEntries) {
901
- try {
902
- const { stdout } = await execPromise(
903
- `git diff -U0 --no-index /dev/null ${shellQuote(entry.path)}`,
904
- { cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 }
905
- );
906
- patchByFile.set(entry.path, stripDiffHeaders(stdout));
907
- } catch (error) {
908
- if (error.stdout) {
909
- patchByFile.set(entry.path, stripDiffHeaders(error.stdout));
910
- } else {
911
- throw error;
912
- }
913
- }
914
- }
915
- const statsByFile = /* @__PURE__ */ new Map();
916
- if (committedEntries.length > 0) {
917
- const paths = committedEntries.map((e) => e.path);
918
- let numstatCmd = "";
919
- if (isRange) {
920
- const [rangeA, rangeB] = parseRange(selector);
921
- numstatCmd = `git diff --numstat ${shellQuote(rangeA)}..${shellQuote(rangeB)} -- ${joinAsShellArgs(paths)}`;
922
- } else {
923
- numstatCmd = `git diff --numstat ${compareTo}..HEAD -- ${joinAsShellArgs(paths)}`;
924
- }
925
- const { stdout: numstatOutput } = await execPromise(numstatCmd, {
926
- cwd: repoRoot,
927
- maxBuffer: 50 * 1024 * 1024
928
- });
929
- const lines = numstatOutput.split("\n").filter(Boolean);
930
- for (const line of lines) {
931
- const parts = line.split(" ");
932
- if (parts.length >= 3) {
933
- const [addStr, delStr, filename] = parts;
934
- statsByFile.set(filename, {
935
- additions: parseInt(addStr) || 0,
936
- deletions: parseInt(delStr) || 0
937
- });
938
- }
939
- }
940
- }
941
- if (stagedEntries.length > 0) {
942
- const paths = stagedEntries.map((e) => e.path);
943
- const numstatCmd = `git diff --numstat --cached -- ${joinAsShellArgs(paths)}`;
944
- const { stdout: numstatOutput } = await execPromise(numstatCmd, {
945
- cwd: repoRoot,
946
- maxBuffer: 50 * 1024 * 1024
947
- });
948
- const lines = numstatOutput.split("\n").filter(Boolean);
949
- for (const line of lines) {
950
- const parts = line.split(" ");
951
- if (parts.length >= 3) {
952
- const [addStr, delStr, filename] = parts;
953
- statsByFile.set(filename, {
954
- additions: parseInt(addStr) || 0,
955
- deletions: parseInt(delStr) || 0
956
- });
957
- }
958
- }
959
- }
960
- if (unstagedEntries.length > 0) {
961
- const paths = unstagedEntries.map((e) => e.path);
962
- const numstatCmd = `git diff --numstat -- ${joinAsShellArgs(paths)}`;
963
- const { stdout: numstatOutput } = await execPromise(numstatCmd, {
964
- cwd: repoRoot,
965
- maxBuffer: 50 * 1024 * 1024
966
- });
967
- const lines = numstatOutput.split("\n").filter(Boolean);
968
- for (const line of lines) {
969
- const parts = line.split(" ");
970
- if (parts.length >= 3) {
971
- const [addStr, delStr, filename] = parts;
972
- statsByFile.set(filename, {
973
- additions: parseInt(addStr) || 0,
974
- deletions: parseInt(delStr) || 0
975
- });
976
- }
977
- }
978
- }
979
- for (const entry of untrackedEntries) {
980
- const patch = patchByFile.get(entry.path) || "";
981
- const lines = patch.split("\n");
982
- let additions = 0;
983
- let deletions = 0;
984
- for (const line of lines) {
985
- if (line.startsWith("+") && !line.startsWith("+++")) {
986
- additions++;
987
- } else if (line.startsWith("-") && !line.startsWith("---")) {
988
- deletions++;
989
- }
990
- }
991
- statsByFile.set(entry.path, { additions, deletions });
992
- }
993
- const fileChanges = [];
994
- for (const entry of entries) {
995
- const patch = patchByFile.get(entry.path) || "";
996
- const stats = statsByFile.get(entry.path) || { additions: 0, deletions: 0 };
997
- const status = toChangeStatus(entry);
998
- const fileChange = {
999
- filename: entry.path,
1000
- status,
1001
- patch,
1002
- additions: stats.additions,
1003
- deletions: stats.deletions,
1004
- sha: hashString(patch)
1005
- };
1006
- if (entry.oldPath) {
1007
- fileChange.oldFilename = entry.oldPath;
1008
- }
1009
- fileChanges.push(fileChange);
1010
- }
1011
- return {
1012
- files: fileChanges,
1013
- currentBranch,
1014
- currentCommit,
1015
- diffBranch: parentRef,
1016
- diffCommit: compareTo
1017
- };
1018
- }
1019
-
1020
- // src/environment/Environment.ts
1021
- var Environment = class {
1022
- workspaceRoot;
1023
- repositoryUrl;
1024
- constructor(config) {
1025
- this.repositoryUrl = (config == null ? void 0 : config.repositoryUrl) || null;
1026
- this.workspaceRoot = (config == null ? void 0 : config.workspaceRoot) || findGitRoot();
1027
- }
1028
- /**
1029
- * Get the workspace root directory
1030
- */
1031
- getWorkspaceRoot() {
1032
- return this.workspaceRoot;
1033
- }
1034
- /**
1035
- * Get the remote repository URL from Git config
1036
- * @param remoteName Name of the remote (default: origin)
1037
- * @returns The remote repository URL or null if not found
1038
- */
1039
- async getRepositoryUrl(remoteName = "origin") {
1040
- if (this.repositoryUrl) {
1041
- return this.repositoryUrl;
1042
- }
1043
- const repositoryUrl = await getRepositoryUrl(this.workspaceRoot, remoteName);
1044
- if (!repositoryUrl) {
1045
- throw new Error(
1046
- "Could not determine repository URL. Make sure you're in a Git repository with a remote origin."
1047
- );
1048
- }
1049
- return repositoryUrl;
1050
- }
1051
- };
1052
-
1053
- // src/environment/Storage.ts
1054
- import * as fs from "fs/promises";
1055
- import os from "os";
1056
- import path2 from "path";
1057
- import Keyv from "keyv";
1058
- import { KeyvFile } from "keyv-file";
1059
- var Storage = class {
1060
- environment;
1061
- cacheStore;
1062
- constructor(environment) {
1063
- this.environment = environment;
1064
- }
1065
- /**
1066
- * Get the base storage directory for powerlint
1067
- * This replaces the getConfigDirectory functionality
1068
- */
1069
- getStorageDirectory() {
1070
- return path2.join(os.homedir(), ".powerlint");
1071
- }
1072
- /**
1073
- * Get the base directory for indexes
1074
- */
1075
- getIndexDirectory() {
1076
- return path2.join(
1077
- this.getStorageDirectory(),
1078
- "indexes",
1079
- hashString(this.environment.getWorkspaceRoot())
1080
- );
1081
- }
1082
- /**
1083
- * Get the base directory for caches
1084
- */
1085
- getCacheDirectory() {
1086
- return path2.join(
1087
- this.getStorageDirectory(),
1088
- "cache",
1089
- hashString(this.environment.getWorkspaceRoot())
1090
- );
1091
- }
1092
- /**
1093
- * Ensure a directory exists, creating it if necessary
1094
- */
1095
- async ensureDirectory(dirPath) {
1096
- await fs.mkdir(dirPath, { recursive: true });
1097
- }
1098
- /**
1099
- * Get the file path for a specific index
1100
- * Ensures the index directory exists
1101
- */
1102
- async getIndexFilePath(language, fileName) {
1103
- const indexDir = this.getIndexDirectory();
1104
- await this.ensureDirectory(indexDir);
1105
- const file = fileName || `${language.toLowerCase()}-index.scip`;
1106
- return path2.join(indexDir, `${language.toLowerCase()}-${file}`);
1107
- }
1108
- /**
1109
- * Check if an index exists for a language
1110
- */
1111
- async indexExists(language, fileName) {
1112
- const indexPath = await this.getIndexFilePath(language, fileName);
1113
- try {
1114
- await fs.access(indexPath);
1115
- return true;
1116
- } catch {
1117
- return false;
1118
- }
1119
- }
1120
- /**
1121
- * Read an index file for a language
1122
- */
1123
- async readIndex(language, fileName) {
1124
- const indexPath = await this.getIndexFilePath(language, fileName);
1125
- try {
1126
- await fs.access(indexPath);
1127
- return await fs.readFile(indexPath);
1128
- } catch {
1129
- return null;
1130
- }
1131
- }
1132
- /**
1133
- * Save an index file for a language
1134
- */
1135
- async saveIndex(language, data, fileName) {
1136
- const indexPath = await this.getIndexFilePath(language, fileName);
1137
- await fs.writeFile(indexPath, data);
1138
- }
1139
- /**
1140
- * Get the cache file path
1141
- */
1142
- getCacheFilePath() {
1143
- return path2.join(this.getCacheDirectory(), "cache.json");
1144
- }
1145
- /**
1146
- * Purge all storage (cache and indexes)
1147
- * @returns Object with success status and details about what was purged
1148
- */
1149
- async purgeStorage() {
1150
- let deletedCount = 0;
1151
- const storagePath = this.getStorageDirectory();
1152
- try {
1153
- await fs.access(storagePath);
1154
- await fs.rm(storagePath, { recursive: true, force: true });
1155
- deletedCount++;
1156
- } catch {
1157
- }
1158
- return {
1159
- success: true,
1160
- deletedCount
1161
- };
1162
- }
1163
- /**
1164
- * Purge only cache data
1165
- */
1166
- async purgeCache() {
1167
- let deletedCount = 0;
1168
- const cachePath = this.getCacheDirectory();
1169
- try {
1170
- await fs.access(cachePath);
1171
- await fs.rm(cachePath, { recursive: true, force: true });
1172
- deletedCount++;
1173
- } catch {
1174
- }
1175
- return {
1176
- success: true,
1177
- deletedCount
1178
- };
1179
- }
1180
- /**
1181
- * Get or initialize the cache store
1182
- */
1183
- getCacheStore() {
1184
- if (!this.cacheStore) {
1185
- const cacheDir = this.getCacheDirectory();
1186
- this.cacheStore = new Keyv({
1187
- store: new KeyvFile({
1188
- filename: path2.join(cacheDir, "cache.json")
1189
- })
1190
- });
1191
- }
1192
- return this.cacheStore;
1193
- }
1194
- /**
1195
- * Save data to cache
1196
- */
1197
- async saveCache(ruleId, cacheKey, data) {
1198
- const cache = this.getCacheStore();
1199
- const key = `${ruleId}:${cacheKey}`;
1200
- await cache.set(key, data);
1201
- }
1202
- /**
1203
- * Read data from cache
1204
- */
1205
- async readCache(ruleId, cacheKey) {
1206
- const cache = this.getCacheStore();
1207
- const key = `${ruleId}:${cacheKey}`;
1208
- const data = await cache.get(key);
1209
- return data || null;
1210
- }
1211
- /**
1212
- * Purge only index data
1213
- */
1214
- async purgeIndexes() {
1215
- let deletedCount = 0;
1216
- const indexPath = this.getIndexDirectory();
1217
- try {
1218
- await fs.access(indexPath);
1219
- await fs.rm(indexPath, { recursive: true, force: true });
1220
- deletedCount++;
1221
- } catch {
1222
- }
1223
- return {
1224
- success: true,
1225
- deletedCount
1226
- };
1227
- }
1228
- };
1229
-
1230
- // src/providers/WispbitRuleProvider.ts
1231
- var WispbitRuleProvider = class {
1232
- config;
1233
- environment;
1234
- constructor(config, environment) {
1235
- this.config = config;
1236
- this.environment = environment;
1237
- }
1238
- /**
1239
- * Get the repository URL for API requests
1240
- */
1241
- async getRepositoryUrl() {
1242
- const repoUrl = await this.environment.getRepositoryUrl();
1243
- if (!repoUrl) {
1244
- throw new Error(
1245
- "Could not determine repository URL. Make sure you're in a Git repository with a remote origin."
1246
- );
1247
- }
1248
- return repoUrl;
1249
- }
1250
- /**
1251
- * Load a specific rule by ID from Wispbit Cloud
1252
- */
1253
- async loadRuleById(ruleId) {
1254
- const rules = await this.fetchRules([ruleId]);
1255
- if (rules.length === 0) {
1256
- throw new Error(`Rule with ID '${ruleId}' not found`);
1257
- }
1258
- return rules[0];
1259
- }
1260
- /**
1261
- * Load all rules from Wispbit Cloud for the configured repository
1262
- */
1263
- async loadAllRules() {
1264
- return await this.fetchRules();
1265
- }
1266
- /**
1267
- * Fetch rules from Wispbit Cloud API
1268
- */
1269
- async fetchRules(ruleIds) {
1270
- const repositoryUrl = await this.getRepositoryUrl();
1271
- const apiClient = this.config.getApiClient();
1272
- const response = await apiClient.getRules({
1273
- repository_url: repositoryUrl,
1274
- rule_ids: ruleIds,
1275
- schema_version: this.config.getSchemaVersion(),
1276
- powerlint_version: this.config.getLocalVersion()
1277
- });
1278
- return response.rules.map((rule) => ({
1279
- id: rule.id,
1280
- internalId: rule.internalId,
1281
- config: {
1282
- message: rule.message,
1283
- severity: rule.severity,
1284
- execution: rule.execution || "llm",
1285
- steps: rule.schema
1286
- },
1287
- prompt: rule.prompt,
1288
- testCases: []
1289
- }));
1290
- }
1291
- };
1292
-
1293
- // src/steps/ExecutionEventEmitter.ts
1294
- import { EventEmitter } from "events";
1295
- var ExecutionEventEmitter = class extends EventEmitter {
1296
- rulesStartTime = 0;
1297
- indexingStartTimes = /* @__PURE__ */ new Map();
1298
- fileDiscoveryStartTime = 0;
1299
- constructor() {
1300
- super();
1301
- this.setMaxListeners(20);
1302
- }
1303
- // Type-safe event emission
1304
- emit(event, data) {
1305
- return super.emit(event, data);
1306
- }
1307
- // Type-safe event listening
1308
- on(event, listener) {
1309
- return super.on(event, listener);
1310
- }
1311
- once(event, listener) {
1312
- return super.once(event, listener);
1313
- }
1314
- off(event, listener) {
1315
- return super.off(event, listener);
1316
- }
1317
- // Helper method for execution mode
1318
- setExecutionMode(mode, options) {
1319
- this.emit("execution:mode", { mode, ...options });
1320
- }
1321
- // Helper methods for rule progress
1322
- startRules(totalRules) {
1323
- this.rulesStartTime = Date.now();
1324
- this.emit("rules:start", { totalRules });
1325
- }
1326
- progressRule(currentRule, totalRules, ruleId, isLlm) {
1327
- const percentage = Math.round(currentRule / totalRules * 100);
1328
- this.emit("rules:progress", { currentRule, totalRules, ruleId, percentage, isLlm });
1329
- }
1330
- completeRules(totalRules, totalMatches) {
1331
- const executionTime = Date.now() - this.rulesStartTime;
1332
- this.emit("rules:complete", { totalRules, totalMatches, executionTime });
1333
- }
1334
- // Helper methods for file discovery
1335
- startFileDiscovery(mode) {
1336
- this.fileDiscoveryStartTime = Date.now();
1337
- this.emit("files:discovery:start", { mode });
1338
- }
1339
- fileDiscoveryProgress(message, currentCount) {
1340
- this.emit("files:discovery:progress", { message, currentCount });
1341
- }
1342
- completeFileDiscovery(totalFiles, mode) {
1343
- const executionTime = Date.now() - this.fileDiscoveryStartTime;
1344
- this.emit("files:discovery:complete", { totalFiles, mode, executionTime });
1345
- }
1346
- fileFilter(originalCount, filteredCount, filterType) {
1347
- this.emit("files:filter", { originalCount, filteredCount, filterType });
1348
- }
1349
- startScipMatchLookup(language, match) {
1350
- this.emit("scip:match-lookup:start", { language, match });
1351
- }
1352
- scipMatchLookupProgress(language, document) {
1353
- this.emit("scip:match-lookup:progress", { language, document });
1354
- }
1355
- scipMatchLookupComplete(language, document) {
1356
- this.emit("scip:match-lookup:complete", { language, document });
1357
- }
1358
- // Helper methods for indexing
1359
- startIndexing(language) {
1360
- this.indexingStartTimes.set(language, Date.now());
1361
- this.emit("indexing:start", { language });
1362
- }
1363
- indexingProgress(language, message, packageName, timeMs) {
1364
- this.emit("indexing:progress", { language, message, packageName, timeMs });
1365
- }
1366
- completeIndexing(language) {
1367
- const startTime = this.indexingStartTimes.get(language) || Date.now();
1368
- const executionTime = Date.now() - startTime;
1369
- this.indexingStartTimes.delete(language);
1370
- this.emit("indexing:complete", { language, executionTime });
1371
- }
1372
- // Helper methods for step debugging
1373
- startStep(ruleId, stepName, stepType, inputs, parentStepName) {
1374
- this.emit("step:start", { ruleId, stepName, stepType, inputs, parentStepName });
1375
- }
1376
- completeStep(ruleId, stepName, stepType, outputs, executionTime = 0, parentStepName) {
1377
- this.emit("step:complete", {
1378
- ruleId,
1379
- stepName,
1380
- stepType,
1381
- outputs,
1382
- executionTime,
1383
- parentStepName
1384
- });
1385
- }
1386
- // Helper methods for test events
1387
- startTest(ruleId, testName) {
1388
- this.emit("test:start", { ruleId, testName });
1389
- }
1390
- testMatches(ruleId, testName, matches) {
1391
- this.emit("test:matches", { ruleId, testName, matches });
1392
- }
1393
- completeTest(ruleId, testName, matches, stepExecutions) {
1394
- this.emit("test:complete", { ruleId, testName, matches, stepExecutions });
1395
- }
1396
- // Helper methods for LLM validation events
1397
- startLLMValidation(ruleId, matchCount, estimatedCost) {
1398
- this.emit("llm:validation:start", { ruleId, matchCount, estimatedCost });
1399
- }
1400
- llmValidationProgress(ruleId, tokenCount, elapsedTime, streamedText) {
1401
- this.emit("llm:validation:progress", { ruleId, tokenCount, elapsedTime, streamedText });
1402
- }
1403
- completeLLMValidation(ruleId, tokenCount, executionTime, violationCount, fromCache) {
1404
- this.emit("llm:validation:complete", {
1405
- ruleId,
1406
- tokenCount,
1407
- executionTime,
1408
- violationCount,
1409
- fromCache
1410
- });
1411
- }
1412
- };
1413
-
1414
- // src/steps/FileExecutionContext.ts
1415
- import * as fs2 from "fs";
1416
- import * as path3 from "path";
1417
- import { glob } from "glob";
1418
-
1419
- // src/utils/diffValidation.ts
1420
- function isFileValidInDiff(filePath, options) {
1421
- const { changedFiles } = options;
1422
- return changedFiles.some((changedFile) => {
1423
- return filePath === changedFile || filePath.endsWith(changedFile) || changedFile.endsWith(filePath);
1424
- });
1425
- }
1426
- function isLineRangeValidInDiff(filePath, startLine, endLine, options) {
1427
- const { fileChangeMap } = options;
1428
- const fileChange = fileChangeMap.get(filePath);
1429
- if (!fileChange) {
1430
- return false;
1431
- }
1432
- if (fileChange.status === "added") {
1433
- return true;
1434
- }
1435
- if (fileChange.status === "removed") {
1436
- return false;
1437
- }
1438
- return isLineRangeInPatch(fileChange.patch, startLine, endLine);
1439
- }
1440
- function isMatchValidInDiff(match, options) {
1441
- return isFileValidInDiff(match.filePath, options) && isLineRangeValidInDiff(match.filePath, match.range.start.line, match.range.end.line, options);
1442
- }
1443
- function isLineRangeInPatch(patch, startLine, endLine) {
1444
- const lines = patch.split("\n");
1445
- let currentNewLine = 0;
1446
- let inHunk = false;
1447
- for (const line of lines) {
1448
- const hunkMatch = line.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
1449
- if (hunkMatch) {
1450
- currentNewLine = parseInt(hunkMatch[1], 10);
1451
- inHunk = true;
1452
- continue;
1453
- }
1454
- if (!inHunk) continue;
1455
- if (line.startsWith("+")) {
1456
- if (currentNewLine >= startLine && currentNewLine <= endLine) {
1457
- return true;
1458
- }
1459
- currentNewLine++;
1460
- } else if (line.startsWith("-")) {
1461
- continue;
1462
- } else if (!line.startsWith("\\")) {
1463
- if (currentNewLine >= startLine && currentNewLine <= endLine) {
1464
- return true;
1465
- }
1466
- currentNewLine++;
1467
- }
1468
- }
1469
- return false;
1470
- }
1471
-
1472
- // src/utils/patternMatching.ts
1473
- import ignore from "ignore";
1474
- function matchesAnyPattern(filePath, patterns) {
1475
- if (patterns.length === 0) return false;
1476
- const ig = ignore().add(patterns);
1477
- return ig.ignores(filePath);
1478
- }
1479
-
1480
- // src/steps/FileExecutionContext.ts
1481
- var FileExecutionContext = class _FileExecutionContext {
1482
- environment;
1483
- _filePaths = [];
1484
- mode;
1485
- eventEmitter;
1486
- config;
1487
- diffMode;
1488
- constructor(config, environment, mode, eventEmitter) {
1489
- this.environment = environment;
1490
- this.mode = mode;
1491
- this.eventEmitter = eventEmitter || new ExecutionEventEmitter();
1492
- this.config = config;
1493
- }
1494
- /**
1495
- * Create and initialize an ExecutionContext
1496
- */
1497
- static async initialize(config, environment, mode, eventEmitter, filePath, diffOptions) {
1498
- const context = new _FileExecutionContext(config, environment, mode, eventEmitter);
1499
- if (mode === "diff" && !filePath) {
1500
- const workspaceRoot = context.environment.getWorkspaceRoot();
1501
- const include = diffOptions == null ? void 0 : diffOptions.include;
1502
- const commitSelector = diffOptions == null ? void 0 : diffOptions.commitSelector;
1503
- if (!commitSelector || !include)
1504
- throw new Error("Commit selector and include are required in diff mode");
1505
- const gitChanges = await getChangedFiles(workspaceRoot, {
1506
- include,
1507
- commitSelector
1508
- }).catch(() => {
1509
- throw new Error(
1510
- "Diff mode requires a git repository. Please run this command from within a git repository."
1511
- );
1512
- });
1513
- context.diffMode = {
1514
- gitChanges,
1515
- changedFiles: gitChanges.files.map((f) => f.filename),
1516
- fileChangeMap: new Map(gitChanges.files.map((file) => [file.filename, file]))
1517
- };
1518
- context.eventEmitter.fileDiscoveryProgress(
1519
- `Found ${context.diffMode.changedFiles.length} changed files`
1520
- );
1521
- }
1522
- const gitIgnoredFiles = mode === "check" ? await context.loadGitIgnoredFiles() : /* @__PURE__ */ new Set();
1523
- const initialFiles = await context.discoverFiles(filePath, gitIgnoredFiles);
1524
- context._filePaths = initialFiles;
1525
- return context;
1526
- }
1527
- // ===== State Management =====
1528
- get filePaths() {
1529
- return this._filePaths;
1530
- }
1531
- /**
1532
- * Filter files based on execution mode and return filtered files
1533
- */
1534
- filterFiles(filePaths) {
1535
- const originalCount = filePaths.length;
1536
- let filteredFiles;
1537
- if (this.mode === "check") {
1538
- filteredFiles = filePaths;
1539
- } else {
1540
- filteredFiles = filePaths.filter((filePath) => this.isFileValid({ filePath }));
1541
- }
1542
- if (originalCount !== filteredFiles.length) {
1543
- this.eventEmitter.fileFilter(originalCount, filteredFiles.length, `${this.mode}-mode-files`);
1544
- }
1545
- return filteredFiles;
1546
- }
1547
- /**
1548
- * Filter matches based on execution mode and return filtered matches
1549
- */
1550
- filterMatches(matches) {
1551
- const originalCount = matches.length;
1552
- const filteredMatches = matches.filter((match) => this.isMatchValid({ match }));
1553
- if (originalCount !== filteredMatches.length) {
1554
- this.eventEmitter.fileFilter(
1555
- originalCount,
1556
- filteredMatches.length,
1557
- `${this.mode}-mode-matches`
1558
- );
1559
- }
1560
- return filteredMatches;
1561
- }
1562
- /**
1563
- * Filter matches to only include those from the given file paths
1564
- */
1565
- filterMatchesByFilePaths(matches, filePaths) {
1566
- if (matches.length === 0) {
1567
- return matches;
1568
- }
1569
- const filePathSet = new Set(filePaths);
1570
- return matches.filter((match) => filePathSet.has(match.filePath));
1571
- }
1572
- get executionMode() {
1573
- return this.mode;
1574
- }
1575
- // ===== Git Ignored Files =====
1576
- /**
1577
- * Load git-ignored files from the repository
1578
- * Uses: git ls-files --ignored --exclude-standard --others
1579
- *
1580
- * Only called in "check" mode - in "diff" mode, git automatically excludes ignored files.
1581
- * Returns a Set for efficient lookup, which can be discarded after file discovery.
1582
- * If not in a git repository, returns an empty Set.
1583
- */
1584
- async loadGitIgnoredFiles() {
1585
- const workspaceRoot = this.environment.getWorkspaceRoot();
1586
- const ignoredFiles = await getGitIgnoredFiles(workspaceRoot).catch(() => {
1587
- this.eventEmitter.fileDiscoveryProgress("Not in a git repository, skipping git-ignored files");
1588
- return [];
1589
- });
1590
- const ignoredFilesSet = new Set(ignoredFiles);
1591
- if (ignoredFiles.length > 0) {
1592
- this.eventEmitter.fileDiscoveryProgress(`Found ${ignoredFiles.length} git-ignored files`);
1593
- }
1594
- return ignoredFilesSet;
1595
- }
1596
- // ===== File Discovery =====
1597
- /**
1598
- * Discover files based on the execution mode
1599
- * In 'scan' mode: discovers all files in workspace
1600
- * In 'diff' mode: gets changed files from git and returns only those
1601
- *
1602
- * The filePath parameter can be:
1603
- * - A specific filename (e.g., "src/file.ts")
1604
- * - A directory path (e.g., "src/components/")
1605
- * - A glob pattern (e.g., ".ts")
1606
- */
1607
- async discoverFiles(filePath, gitIgnoredFiles) {
1608
- this.eventEmitter.startFileDiscovery(this.mode);
1609
- let discoveredFiles;
1610
- if (filePath) {
1611
- discoveredFiles = await this.discoverFilesFromPath(filePath, gitIgnoredFiles);
1612
- } else if (this.mode === "diff") {
1613
- const workspaceRoot = this.environment.getWorkspaceRoot();
1614
- discoveredFiles = this.diffMode.changedFiles.filter(
1615
- (file) => fs2.existsSync(path3.resolve(workspaceRoot, file))
1616
- ).map((file) => file);
1617
- const allIgnorePatterns = this.config.getIgnoredGlobs();
1618
- if (allIgnorePatterns.length > 0) {
1619
- this.eventEmitter.fileDiscoveryProgress("Applying ignore patterns...");
1620
- const beforeIgnore = discoveredFiles.length;
1621
- discoveredFiles = discoveredFiles.filter(
1622
- (filePath2) => !matchesAnyPattern(filePath2, allIgnorePatterns)
1623
- );
1624
- if (beforeIgnore !== discoveredFiles.length) {
1625
- this.eventEmitter.fileDiscoveryProgress(
1626
- `Filtered out ${beforeIgnore - discoveredFiles.length} ignored files`
1627
- );
1628
- }
1629
- }
1630
- } else {
1631
- this.eventEmitter.fileDiscoveryProgress("Scanning workspace for files...");
1632
- const workspaceRoot = this.environment.getWorkspaceRoot();
1633
- const allIgnorePatterns = this.config.getIgnoredGlobs();
1634
- discoveredFiles = await this.discoverAllFiles(
1635
- [workspaceRoot],
1636
- allIgnorePatterns,
1637
- gitIgnoredFiles
1638
- );
1639
- }
1640
- this.eventEmitter.completeFileDiscovery(discoveredFiles.length, this.mode);
1641
- return discoveredFiles;
1642
- }
1643
- /**
1644
- * Discover files from a given path which can be a file, directory, or glob pattern
1645
- */
1646
- async discoverFilesFromPath(filePath, gitIgnoredFiles) {
1647
- const workspaceRoot = this.environment.getWorkspaceRoot();
1648
- const fullPath = path3.resolve(workspaceRoot, filePath);
1649
- const allIgnorePatterns = this.config.getIgnoredGlobs();
1650
- if (this.isGlobPattern(filePath)) {
1651
- this.eventEmitter.fileDiscoveryProgress(
1652
- `Discovering files matching glob pattern: ${filePath}`
1653
- );
1654
- return await this.discoverFilesFromGlob(filePath, allIgnorePatterns, gitIgnoredFiles);
1655
- }
1656
- if (!fs2.existsSync(fullPath)) {
1657
- throw new Error(`Path not found: ${filePath}`);
1658
- }
1659
- const stats = fs2.statSync(fullPath);
1660
- if (stats.isFile()) {
1661
- this.eventEmitter.fileDiscoveryProgress(`Checking specific file: ${filePath}`);
1662
- if (gitIgnoredFiles.has(filePath)) {
1663
- this.eventEmitter.fileDiscoveryProgress(`File ${filePath} is ignored by git`);
1664
- return [];
1665
- }
1666
- if (matchesAnyPattern(filePath, allIgnorePatterns)) {
1667
- this.eventEmitter.fileDiscoveryProgress(`File ${filePath} is ignored by patterns`);
1668
- return [];
1669
- } else {
1670
- return [filePath];
1671
- }
1672
- } else if (stats.isDirectory()) {
1673
- this.eventEmitter.fileDiscoveryProgress(`Discovering files in directory: ${filePath}`);
1674
- return await this.discoverFilesFromDirectory(filePath, allIgnorePatterns, gitIgnoredFiles);
1675
- } else {
1676
- throw new Error(`Path is neither a file nor directory: ${filePath}`);
1677
- }
1678
- }
1679
- /**
1680
- * Check if a path contains glob pattern characters
1681
- */
1682
- isGlobPattern(filePath) {
1683
- return /[*?[\]{}]/.test(filePath);
1684
- }
1685
- /**
1686
- * Discover files matching a glob pattern
1687
- */
1688
- async discoverFilesFromGlob(globPattern, ignorePatterns, gitIgnoredFiles) {
1689
- const workspaceRoot = this.environment.getWorkspaceRoot();
1690
- const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
1691
- const matches = await glob(globPattern, {
1692
- cwd: workspaceRoot,
1693
- nodir: true,
1694
- absolute: false,
1695
- ignore: allIgnorePatterns
1696
- });
1697
- const filteredMatches = matches.filter((match) => !gitIgnoredFiles.has(match));
1698
- this.eventEmitter.fileDiscoveryProgress(
1699
- `Found ${filteredMatches.length} files matching pattern`
1700
- );
1701
- return filteredMatches;
1702
- }
1703
- /**
1704
- * Discover all files in a directory
1705
- */
1706
- async discoverFilesFromDirectory(dirPath, ignorePatterns, gitIgnoredFiles) {
1707
- const workspaceRoot = this.environment.getWorkspaceRoot();
1708
- const globPattern = path3.join(dirPath, "**/*").replace(/\\/g, "/");
1709
- const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
1710
- const matches = await glob(globPattern, {
1711
- cwd: workspaceRoot,
1712
- nodir: true,
1713
- absolute: false,
1714
- ignore: allIgnorePatterns
1715
- });
1716
- const filteredMatches = matches.filter((match) => !gitIgnoredFiles.has(match));
1717
- this.eventEmitter.fileDiscoveryProgress(`Found ${filteredMatches.length} files in directory`);
1718
- return filteredMatches;
1719
- }
1720
- /**
1721
- * Discover all files from directories using glob patterns (scan mode)
1722
- */
1723
- async discoverAllFiles(directories, ignorePatterns, gitIgnoredFiles) {
1724
- const allFiles = [];
1725
- const workspaceRoot = this.environment.getWorkspaceRoot();
1726
- const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
1727
- for (const dir of directories) {
1728
- const stats = fs2.statSync(dir);
1729
- if (!stats.isDirectory()) {
1730
- const relativePath = path3.relative(workspaceRoot, dir);
1731
- const isGitIgnored = gitIgnoredFiles.has(relativePath);
1732
- const shouldIgnore = matchesAnyPattern(relativePath, ignorePatterns);
1733
- if (!isGitIgnored && !shouldIgnore) {
1734
- allFiles.push(relativePath);
1735
- }
1736
- continue;
1737
- }
1738
- const matches = await glob("**/*", {
1739
- cwd: dir,
1740
- nodir: true,
1741
- absolute: false,
1742
- // Get relative paths from the directory
1743
- ignore: allIgnorePatterns
1744
- });
1745
- const relativePaths = matches.map((match) => {
1746
- const absolutePath = path3.resolve(dir, match);
1747
- return path3.relative(workspaceRoot, absolutePath);
1748
- }).filter((relativePath) => !gitIgnoredFiles.has(relativePath));
1749
- allFiles.push(...relativePaths);
1750
- }
1751
- return [...new Set(allFiles)];
1752
- }
1753
- /**
1754
- * Check if a file should be processed
1755
- */
1756
- isFileValid(options) {
1757
- if (this.mode === "check") {
1758
- return true;
1759
- }
1760
- const { filePath } = options;
1761
- return isFileValidInDiff(filePath, {
1762
- changedFiles: this.diffMode.changedFiles,
1763
- fileChangeMap: this.diffMode.fileChangeMap
1764
- });
1765
- }
1766
- /**
1767
- * Check if a match should be processed
1768
- */
1769
- isMatchValid(options) {
1770
- if (this.mode === "check") {
1771
- return true;
1772
- }
1773
- const { match } = options;
1774
- return isMatchValidInDiff(match, {
1775
- changedFiles: this.diffMode.changedFiles,
1776
- fileChangeMap: this.diffMode.fileChangeMap
1777
- });
1778
- }
1779
- };
1780
-
1781
- // src/steps/FileFilterStep.ts
1782
- import * as fs3 from "fs";
1783
- import * as path4 from "path";
1784
- var FileFilterStep = class {
1785
- environment;
1786
- constructor(environment) {
1787
- this.environment = environment;
1788
- }
1789
- /**
1790
- * Evaluate a single file filter condition for a given file path
1791
- */
1792
- evaluateCondition(condition, filePath) {
1793
- const workspaceRoot = this.environment.getWorkspaceRoot();
1794
- const absoluteFilePath = path4.resolve(workspaceRoot, filePath);
1795
- if ("fs.siblingExists" in condition) {
1796
- const { filename } = condition["fs.siblingExists"];
1797
- const dir = path4.dirname(absoluteFilePath);
1798
- const siblingPath = path4.join(dir, filename);
1799
- return fs3.existsSync(siblingPath);
1800
- }
1801
- if ("fs.ancestorHas" in condition) {
1802
- const { filename } = condition["fs.ancestorHas"];
1803
- let currentDir = path4.dirname(absoluteFilePath);
1804
- const root = path4.parse(currentDir).root;
1805
- while (currentDir !== root) {
1806
- const targetPath = path4.join(currentDir, filename);
1807
- if (fs3.existsSync(targetPath)) {
1808
- return true;
1809
- }
1810
- const parentDir = path4.dirname(currentDir);
1811
- if (parentDir === currentDir) break;
1812
- currentDir = parentDir;
1813
- }
1814
- return false;
1815
- }
1816
- if ("fs.siblingAny" in condition) {
1817
- const { pattern } = condition["fs.siblingAny"];
1818
- const dir = path4.dirname(absoluteFilePath);
1819
- if (!fs3.existsSync(dir)) {
1820
- return false;
1821
- }
1822
- const siblings = fs3.readdirSync(dir);
1823
- return siblings.some((sibling) => matchesAnyPattern(sibling, [pattern]));
1824
- }
1825
- return false;
1826
- }
1827
- /**
1828
- * Evaluate file filter conditions for a given file path
1829
- */
1830
- evaluateConditions(conditions, filePath) {
1831
- const results = [];
1832
- if (conditions.all && conditions.all.length > 0) {
1833
- results.push(conditions.all.every((condition) => this.evaluateCondition(condition, filePath)));
1834
- }
1835
- if (conditions.any && conditions.any.length > 0) {
1836
- results.push(conditions.any.some((condition) => this.evaluateCondition(condition, filePath)));
1837
- }
1838
- if (conditions.not && conditions.not.length > 0) {
1839
- results.push(!conditions.not.some((condition) => this.evaluateCondition(condition, filePath)));
1840
- }
1841
- return results.length === 0 ? true : results.every((result) => result === true);
1842
- }
1843
- /**
1844
- * Execute file filter on file paths
1845
- */
1846
- async execute(filePaths, options) {
1847
- var _a, _b;
1848
- const startTime = Date.now();
1849
- let filteredPaths = filePaths;
1850
- if ((_a = options.include) == null ? void 0 : _a.length) {
1851
- filteredPaths = filteredPaths.filter(
1852
- (filePath) => matchesAnyPattern(filePath, options.include)
1853
- );
1854
- }
1855
- if ((_b = options.ignore) == null ? void 0 : _b.length) {
1856
- filteredPaths = filteredPaths.filter(
1857
- (filePath) => !matchesAnyPattern(filePath, options.ignore)
1858
- );
1859
- }
1860
- if (Object.keys(options.conditions || {}).length > 0 && filteredPaths.length > 0) {
1861
- filteredPaths = filteredPaths.filter(
1862
- (filePath) => this.evaluateConditions(options.conditions, filePath)
1863
- );
1864
- }
1865
- const executionTime = Date.now() - startTime;
1866
- return await Promise.resolve({
1867
- filteredPaths,
1868
- executionTime
1869
- });
1870
- }
1871
- };
1872
-
1873
- // src/steps/FindMatchesStep.ts
1874
- import path8 from "path";
1875
-
1876
- // src/providers/AstGrepAstProvider.ts
1877
- import { readFile as readFile2 } from "fs/promises";
1878
- import path5 from "path";
1879
- import { parse as parse2, parseAsync } from "@ast-grep/napi";
1880
-
1881
- // src/utils/coordinates.ts
1882
- function matchTo0Indexed(match) {
1883
- return {
1884
- ...match,
1885
- range: {
1886
- start: {
1887
- line: match.range.start.line - 1,
1888
- column: match.range.start.column - 1
1889
- },
1890
- end: {
1891
- line: match.range.end.line - 1,
1892
- column: match.range.end.column - 1
1893
- }
1894
- }
1895
- };
1896
- }
1897
- function matchTo1Indexed(match) {
1898
- return {
1899
- ...match,
1900
- range: {
1901
- start: {
1902
- line: match.range.start.line + 1,
1903
- column: match.range.start.column + 1
1904
- },
1905
- end: {
1906
- line: match.range.end.line + 1,
1907
- column: match.range.end.column + 1
1908
- }
1909
- }
1910
- };
1911
- }
1912
-
1913
- // src/providers/AstGrepAstProvider.ts
1914
- var AstGrepAstProvider = class {
1915
- environment;
1916
- language;
1917
- constructor(environment, language) {
1918
- this.environment = environment;
1919
- this.language = language;
1920
- }
1921
- /**
1922
- * Find all matches based on ast-grep pattern
1923
- * @param filePaths File paths to search in (relative to workspace root)
1924
- * @param schema The ast-grep schema (rule, constraints, etc.)
1925
- * @returns Array of matches found
1926
- */
1927
- async findMatches(filePaths, schema) {
1928
- if (filePaths.length === 0) {
1929
- return [];
1930
- }
1931
- const { rule, constraints } = schema;
1932
- const batchSize = this.getBatchSize();
1933
- const matches = [];
1934
- for (let i = 0; i < filePaths.length; i += batchSize) {
1935
- const batch = filePaths.slice(i, i + batchSize);
1936
- const batchMatches = await this.processBatch(batch, rule, constraints);
1937
- matches.push(...batchMatches);
1938
- }
1939
- return matches;
1940
- }
1941
- /**
1942
- * Get optimal batch size based on available CPU cores and thread pool size
1943
- */
1944
- getBatchSize() {
1945
- const threadPoolSize = parseInt(process.env.UV_THREADPOOL_SIZE || "4", 10);
1946
- return Math.max(2, Math.floor(threadPoolSize / 2));
1947
- }
1948
- /**
1949
- * Process a batch of files with memory-efficient approach
1950
- */
1951
- async processBatch(filePaths, rule, constraints) {
1952
- const parsePromises = filePaths.map(async (filePath) => {
1953
- const content = await readFile2(
1954
- path5.resolve(this.environment.getWorkspaceRoot(), filePath),
1955
- "utf-8"
1956
- );
1957
- const node = await parseAsync(this.language, content);
1958
- const foundNodes = node.root().findAll({
1959
- rule,
1960
- language: this.language,
1961
- constraints
1962
- });
1963
- return foundNodes.map((sgNode) => ({ filePath, sgNode }));
1964
- });
1965
- const batchResults = await Promise.all(parsePromises);
1966
- const sgNodes = batchResults.flat();
1967
- const matches = [];
1968
- for (const { filePath, sgNode } of sgNodes) {
1969
- const range = sgNode.range();
1970
- const match0Indexed = {
1971
- filePath,
1972
- text: sgNode.text(),
1973
- range: {
1974
- start: {
1975
- line: range.start.line,
1976
- column: range.start.column
1977
- },
1978
- end: {
1979
- line: range.end.line,
1980
- column: range.end.column
1981
- }
1982
- },
1983
- // Try to extract symbol if possible
1984
- symbol: this.extractSymbol(sgNode),
1985
- language: this.language
1986
- };
1987
- matches.push(matchTo1Indexed(match0Indexed));
1988
- }
1989
- return matches;
1990
- }
1991
- /**
1992
- * Extract symbol name from ast-grep node if possible
1993
- */
1994
- extractSymbol(node) {
1995
- const kind = node.kind();
1996
- if (kind === "call_expression") {
1997
- const functionNode = node.field("function");
1998
- if (functionNode) {
1999
- return this.extractSymbol(functionNode);
2000
- }
2001
- } else if (kind === "member_expression") {
2002
- const propertyNode = node.field("property");
2003
- if (propertyNode) {
2004
- return propertyNode.text();
2005
- }
2006
- } else if (kind === "identifier") {
2007
- return node.text();
2008
- }
2009
- return void 0;
2010
- }
2011
- /**
2012
- * Expand a match to its enclosing function, method, or class definition
2013
- * @param match The match to expand (typically a small range like a method name)
2014
- * @returns Expanded match with the full function/method/class body, or null if not found
2015
- */
2016
- async expandMatch(match) {
2017
- const targetNodeKinds = /* @__PURE__ */ new Set([
2018
- "method_definition",
2019
- "function_declaration",
2020
- "function_expression",
2021
- "arrow_function",
2022
- "class_declaration",
2023
- "export_statement",
2024
- "lexical_declaration"
2025
- // For const/let function expressions
2026
- ]);
2027
- const absolutePath = path5.resolve(this.environment.getWorkspaceRoot(), match.filePath);
2028
- const content = await readFile2(absolutePath, "utf-8");
2029
- const root = parse2(this.language, content).root();
2030
- const match0Indexed = matchTo0Indexed(match);
2031
- const nodeAtPosition = this.findNodeAtPosition(
2032
- root,
2033
- match0Indexed.range.start.line,
2034
- match0Indexed.range.start.column
2035
- );
2036
- if (!nodeAtPosition) {
2037
- return null;
2038
- }
2039
- let current = nodeAtPosition;
2040
- while (current) {
2041
- const kindValue = current.kind();
2042
- const kindStr = typeof kindValue === "string" ? kindValue : String(kindValue);
2043
- if (targetNodeKinds.has(kindStr)) {
2044
- const range = current.range();
2045
- const expandedMatch0Indexed = {
2046
- filePath: match.filePath,
2047
- text: current.text(),
2048
- range: {
2049
- start: { line: range.start.line, column: range.start.column },
2050
- end: { line: range.end.line, column: range.end.column }
2051
- },
2052
- symbol: match.symbol,
2053
- language: this.language
2054
- };
2055
- return matchTo1Indexed(expandedMatch0Indexed);
2056
- }
2057
- current = current.parent();
2058
- }
2059
- return null;
2060
- }
2061
- /**
2062
- * Find the deepest node at a given position
2063
- */
2064
- findNodeAtPosition(node, line, column) {
2065
- const range = node.range();
2066
- const isAfterStart = line > range.start.line || line === range.start.line && column >= range.start.column;
2067
- const isBeforeEnd = line < range.end.line || line === range.end.line && column <= range.end.column;
2068
- if (!isAfterStart || !isBeforeEnd) {
2069
- return null;
2070
- }
2071
- const children = node.children();
2072
- for (const child of children) {
2073
- const deeperNode = this.findNodeAtPosition(child, line, column);
2074
- if (deeperNode) {
2075
- return deeperNode;
2076
- }
2077
- }
2078
- return node;
2079
- }
2080
- };
2081
-
2082
- // src/providers/ScipIntelligenceProvider.ts
2083
- import { spawn } from "child_process";
2084
- import fs4 from "fs/promises";
2085
- import { createRequire as createRequire2 } from "module";
2086
- import os2 from "os";
2087
- import path7 from "path";
2088
-
2089
- // src/utils/readTextAtRange.ts
2090
- import { readFile as readFile3 } from "fs/promises";
2091
- import path6 from "path";
2092
- async function readTextAtRange(workspaceRoot, filePath, range) {
2093
- const absolutePath = path6.resolve(workspaceRoot, filePath);
2094
- const content = await readFile3(absolutePath, "utf-8");
2095
- const lines = content.split("\n");
2096
- const { start, end } = range;
2097
- if (start.line === end.line) {
2098
- const line = lines[start.line] || "";
2099
- return line.substring(start.column, end.column);
2100
- }
2101
- const result = [];
2102
- for (let i = start.line; i <= end.line && i < lines.length; i++) {
2103
- const line = lines[i];
2104
- if (i === start.line) {
2105
- result.push(line.substring(start.column));
2106
- } else if (i === end.line) {
2107
- result.push(line.substring(0, end.column));
2108
- } else {
2109
- result.push(line);
2110
- }
2111
- }
2112
- return result.join("\n");
2113
- }
2114
-
2115
- // src/providers/ScipIntelligenceProvider.ts
2116
- var require3 = createRequire2(import.meta.url ? import.meta.url : __filename);
2117
- var { scip } = require3("@sourcegraph/scip-root/bindings/typescript/scip.js");
2118
- var PACKAGE_REGEX = /^\+ (.+?) \((\d+)ms\)$/;
2119
- function processScipProgressData(data, buffer, eventEmitter, language, rootPath) {
2120
- const text = data.toString();
2121
- buffer += text;
2122
- const lines = buffer.split("\n");
2123
- const newBuffer = lines.pop() || "";
2124
- let latestPackageLine = null;
2125
- for (const line of lines) {
2126
- const trimmedLine = line.trim();
2127
- const packageMatch = trimmedLine.match(PACKAGE_REGEX);
2128
- if (packageMatch) {
2129
- latestPackageLine = trimmedLine;
2130
- }
2131
- }
2132
- for (const line of lines) {
2133
- const trimmedLine = line.trim();
2134
- if (!trimmedLine) continue;
2135
- const packageMatch = trimmedLine.match(PACKAGE_REGEX);
2136
- if (packageMatch) {
2137
- if (trimmedLine === latestPackageLine) {
2138
- const [, packagePath, timeMs] = packageMatch;
2139
- const relativePackagePath = path7.relative(rootPath, packagePath);
2140
- eventEmitter.indexingProgress(
2141
- language,
2142
- `Indexed ${relativePackagePath}`,
2143
- relativePackagePath,
2144
- parseInt(timeMs, 10)
2145
- );
2146
- }
2147
- } else {
2148
- eventEmitter.indexingProgress(language, trimmedLine);
2149
- }
2150
- }
2151
- return newBuffer;
2152
- }
2153
- var ScipIntelligenceProvider = class {
2154
- environment;
2155
- storage;
2156
- language;
2157
- scipIndex = null;
2158
- indexingPromise = null;
2159
- eventEmitter;
2160
- constructor(environment, language, eventEmitter) {
2161
- this.environment = environment;
2162
- this.storage = new Storage(environment);
2163
- this.language = language;
2164
- this.eventEmitter = eventEmitter || new ExecutionEventEmitter();
2165
- }
2166
- /**
2167
- * Get the SCIP CLI command for the given language
2168
- */
2169
- getScipCommand() {
2170
- switch (this.language) {
2171
- case "TypeScript" /* TypeScript */:
2172
- case "JavaScript" /* JavaScript */:
2173
- case "Tsx" /* Tsx */:
2174
- return "scip-typescript";
2175
- case "Python" /* Python */:
2176
- return "scip-python";
2177
- default:
2178
- throw new Error(`SCIP is not supported for language: ${this.language}`);
2179
- }
2180
- }
2181
- /**
2182
- * Ensure the index is ready, starting indexing if needed
2183
- */
2184
- async ensureIndexReady() {
2185
- if (this.scipIndex !== null) {
2186
- return;
2187
- }
2188
- if (this.indexingPromise !== null) {
2189
- return this.indexingPromise;
2190
- }
2191
- this.indexingPromise = this.startIndexing();
2192
- await this.indexingPromise;
2193
- }
2194
- /**
2195
- * Start the indexing process
2196
- */
2197
- async startIndexing() {
2198
- if (await this.storage.indexExists(this.language, `index.scip`)) {
2199
- await this.loadIndex();
2200
- return;
2201
- }
2202
- const finalIndexPath = await this.storage.getIndexFilePath(this.language, `index.scip`);
2203
- const tempIndexPath = path7.join(os2.tmpdir(), `scip-index-${this.language}-${Date.now()}.tmp`);
2204
- this.eventEmitter.startIndexing(this.language);
2205
- const scipCommand = this.getScipCommand();
2206
- const child = spawn(scipCommand, ["index", "--output", tempIndexPath], {
2207
- cwd: this.environment.getWorkspaceRoot(),
2208
- stdio: ["ignore", "pipe", "pipe"],
2209
- env: process.env
2210
- });
2211
- let progressBuffer = "";
2212
- child.stdout.on("data", (data) => {
2213
- progressBuffer = processScipProgressData(
2214
- data,
2215
- progressBuffer,
2216
- this.eventEmitter,
2217
- this.language,
2218
- this.environment.getWorkspaceRoot()
2219
- );
2220
- });
2221
- let stderr = "";
2222
- child.stderr.on("data", (data) => {
2223
- stderr += data.toString();
2224
- process.stderr.write(data);
2225
- });
2226
- await new Promise((resolve3, reject) => {
2227
- child.on("close", async (code) => {
2228
- if (code === 0) {
2229
- await fs4.rename(tempIndexPath, finalIndexPath);
2230
- resolve3();
2231
- } else {
2232
- await fs4.unlink(tempIndexPath).catch(() => {
2233
- });
2234
- reject(new Error(`${scipCommand} index exited with code ${code}, stderr: ${stderr}`));
2235
- }
2236
- });
2237
- child.on("error", async (err) => {
2238
- await fs4.unlink(tempIndexPath).catch(() => {
2239
- });
2240
- reject(err);
2241
- });
2242
- });
2243
- if (stderr) {
2244
- console.error("SCIP indexing stderr:", stderr);
2245
- }
2246
- this.eventEmitter.completeIndexing(this.language);
2247
- await this.loadIndex();
2248
- }
2249
- /**
2250
- * Find all definitions for a given match
2251
- * Automatically filters out external symbols
2252
- */
2253
- async findDefinitions(match) {
2254
- await this.ensureIndexReady();
2255
- if (!this.scipIndex) throw new Error("SCIP index not ready");
2256
- const match0Indexed = matchTo0Indexed(match);
2257
- this.eventEmitter.startScipMatchLookup(this.language, match0Indexed);
2258
- const definitions = [];
2259
- const documents = this.scipIndex.documents;
2260
- for (const document of documents) {
2261
- if (document.relative_path !== match0Indexed.filePath) continue;
2262
- this.eventEmitter.scipMatchLookupProgress(this.language, document);
2263
- const scipOcc = this.findBestOverlappingOccurrence(document.occurrences, match0Indexed);
2264
- if (scipOcc) {
2265
- if (this.isExternalSymbol(scipOcc.symbol)) {
2266
- continue;
2267
- }
2268
- this.eventEmitter.scipMatchLookupProgress(this.language, scipOcc);
2269
- const def = await this.findDefinitionForSymbol(scipOcc.symbol, documents);
2270
- if (def) {
2271
- definitions.push(matchTo1Indexed(def));
2272
- }
2273
- }
2274
- }
2275
- return definitions;
2276
- }
2277
- /**
2278
- * Find all references for a given match
2279
- * Automatically filters out external symbols
2280
- */
2281
- async findReferences(match) {
2282
- await this.ensureIndexReady();
2283
- if (!this.scipIndex) {
2284
- return [];
2285
- }
2286
- const match0Indexed = matchTo0Indexed(match);
2287
- const references = [];
2288
- const documents = this.scipIndex.documents;
2289
- for (const document of documents) {
2290
- if (document.relative_path !== match0Indexed.filePath) continue;
2291
- const scipOcc = this.findBestOverlappingOccurrence(document.occurrences, match0Indexed);
2292
- if (scipOcc) {
2293
- if (this.isExternalSymbol(scipOcc.symbol)) {
2294
- break;
2295
- }
2296
- const refs = await this.findReferencesForSymbol(scipOcc.symbol, documents);
2297
- references.push(...refs.map((ref) => matchTo1Indexed(ref)));
2298
- break;
2299
- }
2300
- }
2301
- return references;
2302
- }
2303
- /**
2304
- * Check if a symbol is from an external library (internal use)
2305
- */
2306
- isExternalSymbol(symbol) {
2307
- return symbol.includes("node_modules") || symbol.startsWith("npm/");
2308
- }
2309
- /**
2310
- * Find the best SCIP occurrence that overlaps with the given match
2311
- * Uses heuristics to pick the most relevant occurrence when multiple overlap
2312
- */
2313
- findBestOverlappingOccurrence(occurrences, match) {
2314
- const candidates = [];
2315
- for (const scipOcc of occurrences) {
2316
- const range = scipOcc.range;
2317
- if (!range || range.length < 3) continue;
2318
- if (!this.rangesOverlap(range, match)) continue;
2319
- let score = 0;
2320
- if (!this.rangeFullyInside(range, match)) continue;
2321
- const startCol = range.length === 3 ? range[1] : range[1];
2322
- score += startCol * 1e3;
2323
- if (scipOcc.symbol_roles !== scip.SymbolRole.Definition) {
2324
- score += 100;
2325
- }
2326
- if (match.symbol) {
2327
- const { className, methodName } = this.extractSymbolNames(scipOcc.symbol);
2328
- if (methodName === match.symbol || className === match.symbol) {
2329
- score += 1e4;
2330
- }
2331
- }
2332
- candidates.push({ occurrence: scipOcc, score });
2333
- }
2334
- if (candidates.length === 0) return null;
2335
- candidates.sort((a, b) => b.score - a.score);
2336
- return candidates[0].occurrence;
2337
- }
2338
- /**
2339
- * Check if two ranges overlap
2340
- */
2341
- rangesOverlap(scipRange, match) {
2342
- if (scipRange.length === 3) {
2343
- const [line, startCol, endCol] = scipRange;
2344
- if (match.range.start.line === match.range.end.line) {
2345
- return line === match.range.start.line && !(endCol <= match.range.start.column || startCol >= match.range.end.column);
2346
- } else {
2347
- if (line < match.range.start.line || line > match.range.end.line) return false;
2348
- if (line === match.range.start.line && endCol <= match.range.start.column) return false;
2349
- if (line === match.range.end.line && startCol >= match.range.end.column) return false;
2350
- return true;
2351
- }
2352
- } else if (scipRange.length === 4) {
2353
- const [startLine, _startCol, endLine, _endCol] = scipRange;
2354
- if (endLine < match.range.start.line || startLine > match.range.end.line) return false;
2355
- return true;
2356
- }
2357
- return false;
2358
- }
2359
- /**
2360
- * Check if SCIP range is fully inside the match span
2361
- */
2362
- rangeFullyInside(scipRange, match) {
2363
- if (scipRange.length === 3) {
2364
- const [line, startCol, endCol] = scipRange;
2365
- if (line < match.range.start.line || line > match.range.end.line) return false;
2366
- if (line === match.range.start.line && startCol < match.range.start.column) return false;
2367
- if (line === match.range.end.line && endCol > match.range.end.column) return false;
2368
- return true;
2369
- } else if (scipRange.length === 4) {
2370
- const [startLine, startCol, endLine, endCol] = scipRange;
2371
- if (startLine < match.range.start.line) return false;
2372
- if (endLine > match.range.end.line) return false;
2373
- if (startLine === match.range.start.line && startCol < match.range.start.column) return false;
2374
- if (endLine === match.range.end.line && endCol > match.range.end.column) return false;
2375
- return true;
2376
- }
2377
- return false;
2378
- }
2379
- /**
2380
- * Extract class and method names from SCIP symbol
2381
- * e.g., "scip-typescript npm @wispbit/server 1.0.0 src/services/`OrganizationService`#updateViolationCounts()."
2382
- * returns { className: "OrganizationService", methodName: "updateViolationCounts" }
2383
- *
2384
- * For standalone functions without a class:
2385
- * e.g., "scip-typescript npm @wispbit/server 1.0.0 src/utils/helper()."
2386
- * returns { className: null, methodName: "helper" }
2387
- */
2388
- extractSymbolNames(symbol) {
2389
- const classMethodMatch = symbol.match(/`([^`]+)`#([^#./`(]+)(?:\(\))?[.`]*$/);
2390
- if (classMethodMatch) {
2391
- return {
2392
- className: classMethodMatch[1],
2393
- methodName: classMethodMatch[2]
2394
- };
2395
- }
2396
- const methodMatch = symbol.match(/([^#./`(]+)(?:\(\))?[.`]*$/);
2397
- if (methodMatch) {
2398
- return {
2399
- className: null,
2400
- methodName: methodMatch[1]
2401
- };
2402
- }
2403
- return { className: null, methodName: null };
2404
- }
2405
- /**
2406
- * Find definition for a symbol across all documents
2407
- */
2408
- async findDefinitionForSymbol(symbol, documents) {
2409
- for (const document of documents) {
2410
- for (const occ of document.occurrences) {
2411
- if (occ.symbol === symbol && occ.symbol_roles === scip.SymbolRole.Definition) {
2412
- const range = occ.range ?? [];
2413
- const startLine = range[0] ?? 0;
2414
- const startColumn = range[1] ?? 0;
2415
- const endLine = range.length === 4 ? range[2] : startLine;
2416
- const endColumn = range.length === 4 ? range[3] : range[2] ?? startColumn;
2417
- const text = await readTextAtRange(
2418
- this.environment.getWorkspaceRoot(),
2419
- document.relative_path,
2420
- {
2421
- start: { line: startLine, column: startColumn },
2422
- end: { line: endLine, column: endColumn }
2423
- }
2424
- );
2425
- return {
2426
- language: this.language,
2427
- filePath: document.relative_path,
2428
- range: {
2429
- start: { line: startLine, column: startColumn },
2430
- end: { line: endLine, column: endColumn }
2431
- },
2432
- symbol,
2433
- text
2434
- };
2435
- }
2436
- }
2437
- }
2438
- return null;
2439
- }
2440
- /**
2441
- * Find all references for a symbol across all documents
2442
- */
2443
- async findReferencesForSymbol(symbol, documents) {
2444
- const references = [];
2445
- for (const document of documents) {
2446
- for (const occ of document.occurrences) {
2447
- if (occ.symbol === symbol && occ.symbol_roles !== scip.SymbolRole.Definition) {
2448
- const range = occ.range ?? [];
2449
- const startLine = range[0] ?? 0;
2450
- const startColumn = range[1] ?? 0;
2451
- const endLine = range.length === 4 ? range[2] : startLine;
2452
- const endColumn = range.length === 4 ? range[3] : range[2] ?? startColumn;
2453
- const text = await readTextAtRange(
2454
- this.environment.getWorkspaceRoot(),
2455
- document.relative_path,
2456
- {
2457
- start: { line: startLine, column: startColumn },
2458
- end: { line: endLine, column: endColumn }
2459
- }
2460
- );
2461
- references.push({
2462
- language: this.language,
2463
- filePath: document.relative_path,
2464
- range: {
2465
- start: { line: startLine, column: startColumn },
2466
- end: { line: endLine, column: endColumn }
2467
- },
2468
- symbol,
2469
- text
2470
- });
2471
- }
2472
- }
2473
- }
2474
- return references;
2475
- }
2476
- /**
2477
- * Load SCIP index from the configured path
2478
- */
2479
- async loadIndex() {
2480
- const buffer = await this.storage.readIndex(this.language, `index.scip`);
2481
- if (!buffer) {
2482
- throw new Error(`Index not found for language: ${this.language}`);
2483
- }
2484
- this.scipIndex = this.parseIndex(new Uint8Array(buffer));
2485
- }
2486
- /**
2487
- * Parse a SCIP index from bytes
2488
- */
2489
- parseIndex(buffer) {
2490
- return scip.Index.deserialize(buffer);
2491
- }
2492
- };
2493
-
2494
- // src/providers/LanguageBackend.ts
2495
- var LanguageBackendNotSupportedError = class extends Error {
2496
- constructor(methodName, reason) {
2497
- super(`${methodName} is not supported: ${reason}`);
2498
- this.name = "LanguageBackendNotSupportedError";
2499
- }
2500
- };
2501
- var LanguageBackend = class {
2502
- astProvider;
2503
- intelligenceProvider;
2504
- language;
2505
- constructor(environment, language, eventEmitter) {
2506
- this.language = language;
2507
- this.astProvider = new AstGrepAstProvider(environment, language);
2508
- this.intelligenceProvider = new ScipIntelligenceProvider(environment, language, eventEmitter);
2509
- }
2510
- /**
2511
- * Find all matches based on the AST provider
2512
- */
2513
- async findMatches(filePaths, schema) {
2514
- if (!this.astProvider) {
2515
- throw new LanguageBackendNotSupportedError(
2516
- "findMatches",
2517
- "no AST provider configured for this language"
2518
- );
2519
- }
2520
- return await this.astProvider.findMatches(filePaths, schema);
2521
- }
2522
- /**
2523
- * Find all definitions for a given match
2524
- */
2525
- async findDefinitions(match) {
2526
- if (!this.intelligenceProvider) {
2527
- throw new LanguageBackendNotSupportedError(
2528
- "findDefinitions",
2529
- "no intelligence provider configured for this language"
2530
- );
2531
- }
2532
- return await this.intelligenceProvider.findDefinitions(match);
2533
- }
2534
- /**
2535
- * Find all references for a given match
2536
- */
2537
- async findReferences(match) {
2538
- if (!this.intelligenceProvider) {
2539
- throw new LanguageBackendNotSupportedError(
2540
- "findReferences",
2541
- "no intelligence provider configured for this language"
2542
- );
2543
- }
2544
- return await this.intelligenceProvider.findReferences(match);
2545
- }
2546
- /**
2547
- * Expand a match to its enclosing function, method, or class definition
2548
- * Useful for expanding a definition point (e.g., method name) to the full body
2549
- */
2550
- async expandMatch(match) {
2551
- if (!this.astProvider) {
2552
- throw new LanguageBackendNotSupportedError(
2553
- "expandMatch",
2554
- "no AST provider configured for this language"
2555
- );
2556
- }
2557
- return await this.astProvider.expandMatch(match);
2558
- }
2559
- };
2560
-
2561
- // src/steps/FindMatchesStep.ts
2562
- var FindMatchesStep = class {
2563
- environment;
2564
- eventEmitter;
2565
- constructor(environment, eventEmitter) {
2566
- this.environment = environment;
2567
- this.eventEmitter = eventEmitter;
2568
- }
2569
- /**
2570
- * Execute pattern matching on files
2571
- * @param schema - The matching schema (rule, constraints, etc.) - provider-specific
2572
- * @param language - The language to use for pattern matching
2573
- * @param context - Context containing filePaths and optional previousMatches to filter against
2574
- */
2575
- async execute(schema, language, context) {
2576
- const startTime = Date.now();
2577
- const languageFilteredPaths = context.filePaths.filter((filePath) => {
2578
- const fileLanguage = getLanguageFromFilePath(filePath);
2579
- return fileLanguage === language;
2580
- });
2581
- const backend = new LanguageBackend(this.environment, language, this.eventEmitter);
2582
- const allMatches = await backend.findMatches(languageFilteredPaths, schema);
2583
- const matchesWithLanguage = allMatches.map((match) => {
2584
- if (path8.isAbsolute(match.filePath)) {
2585
- throw new Error(
2586
- `Match provider returned absolute path: ${match.filePath}. All file paths must be relative to workspace root.`
2587
- );
2588
- }
2589
- return {
2590
- ...match,
2591
- language
2592
- };
2593
- });
2594
- let filteredMatches = context.previousMatches && context.previousMatches.length > 0 ? this.filterMatchesByRanges(matchesWithLanguage, context.previousMatches, language) : matchesWithLanguage;
2595
- filteredMatches = this.deduplicateMatches(filteredMatches);
2596
- const executionTime = Date.now() - startTime;
2597
- return {
2598
- matches: filteredMatches,
2599
- totalMatches: filteredMatches.length,
2600
- executionTime
2601
- };
2602
- }
2603
- /**
2604
- * Filter matches to only include those within the ranges of previous matches
2605
- * A match is included if it falls within any of the previous match ranges
2606
- * The source is propagated from the previous match
2607
- */
2608
- filterMatchesByRanges(matches, previousMatches, _language) {
2609
- const result = [];
2610
- for (const match of matches) {
2611
- const containingMatch = previousMatches.find(
2612
- (prevMatch) => this.isMatchInRange(match, prevMatch)
2613
- );
2614
- if (!containingMatch) {
2615
- continue;
2616
- }
2617
- const source = [
2618
- ...containingMatch.source || [],
2619
- {
2620
- filePath: containingMatch.filePath,
2621
- text: containingMatch.text,
2622
- range: containingMatch.range,
2623
- symbol: containingMatch.symbol,
2624
- language: containingMatch.language
2625
- }
2626
- ];
2627
- result.push({
2628
- ...match,
2629
- source
2630
- });
2631
- }
2632
- return result;
2633
- }
2634
- /**
2635
- * Check if a match falls within the range of another match
2636
- * Both matches must be in the same file
2637
- */
2638
- isMatchInRange(match, rangeMatch) {
2639
- if (match.filePath !== rangeMatch.filePath) {
2640
- return false;
2641
- }
2642
- const matchStart = match.range.start;
2643
- const matchEnd = match.range.end;
2644
- const rangeStart = rangeMatch.range.start;
2645
- const rangeEnd = rangeMatch.range.end;
2646
- const startInRange = matchStart.line > rangeStart.line || matchStart.line === rangeStart.line && matchStart.column >= rangeStart.column;
2647
- const endInRange = matchEnd.line < rangeEnd.line || matchEnd.line === rangeEnd.line && matchEnd.column <= rangeEnd.column;
2648
- return startInRange && endInRange;
2649
- }
2650
- /**
2651
- * Remove duplicate matches by keeping only the largest overlapping match.
2652
- * If multiple matches overlap (one is contained within another), keep only the largest one.
2653
- */
2654
- deduplicateMatches(matches) {
2655
- if (matches.length <= 1) {
2656
- return matches;
2657
- }
2658
- const result = [];
2659
- for (const match of matches) {
2660
- const isContainedByAnother = matches.some((otherMatch) => {
2661
- if (match === otherMatch) return false;
2662
- return this.isMatchInRange(match, otherMatch);
2663
- });
2664
- if (!isContainedByAnother) {
2665
- result.push(match);
2666
- }
2667
- }
2668
- return result;
2669
- }
2670
- };
2671
-
2672
- // src/steps/GotoDefinitionStep.ts
2673
- import path9 from "path";
2674
- import ignore2 from "ignore";
2675
- var GotoDefinitionStep = class {
2676
- maxDepth = 1;
2677
- environment;
2678
- eventEmitter;
2679
- constructor(environment, eventEmitter) {
2680
- this.environment = environment;
2681
- this.eventEmitter = eventEmitter || new ExecutionEventEmitter();
2682
- }
2683
- /**
2684
- * Execute goto-definition on matches
2685
- */
2686
- async execute(matches, language, options = {}) {
2687
- const startTime = Date.now();
2688
- const backend = new LanguageBackend(this.environment, language, this.eventEmitter);
2689
- const definitions = await this.followDefinitions(
2690
- matches,
2691
- backend,
2692
- this.maxDepth,
2693
- options.where,
2694
- language
2695
- );
2696
- const executionTime = Date.now() - startTime;
2697
- const flattenedDefinitions = definitions.map((def) => {
2698
- const { depth: _depth, sourceMatch, ...match } = def;
2699
- const source = sourceMatch ? [
2700
- {
2701
- filePath: sourceMatch.filePath,
2702
- text: sourceMatch.text,
2703
- range: sourceMatch.range,
2704
- symbol: sourceMatch.symbol,
2705
- language: sourceMatch.language
2706
- },
2707
- ...sourceMatch.source || []
2708
- ] : [];
2709
- return {
2710
- ...match,
2711
- source
2712
- };
2713
- });
2714
- return {
2715
- definitions: flattenedDefinitions,
2716
- totalDefinitions: flattenedDefinitions.length,
2717
- executionTime
2718
- };
2719
- }
2720
- /**
2721
- * Follow definitions up to maxDepth
2722
- */
2723
- async followDefinitions(matches, backend, maxDepth, filter, language) {
2724
- const definitions = [];
2725
- const visited = /* @__PURE__ */ new Set();
2726
- for (const match of matches) {
2727
- await this.followDefinitionsRecursive(
2728
- match,
2729
- backend,
2730
- 0,
2731
- maxDepth,
2732
- filter,
2733
- definitions,
2734
- visited,
2735
- match,
2736
- language
2737
- );
2738
- }
2739
- return definitions;
2740
- }
2741
- /**
2742
- * Recursively follow definitions
2743
- */
2744
- async followDefinitionsRecursive(match, backend, currentDepth, maxDepth, filter, definitions, visited, sourceMatch, language) {
2745
- if (currentDepth >= maxDepth) {
2746
- return;
2747
- }
2748
- const key = `${match.filePath}:${match.range.start.line}:${match.range.start.column}:${match.symbol ?? ""}`;
2749
- if (visited.has(key)) {
2750
- return;
2751
- }
2752
- visited.add(key);
2753
- const backendDefinitions = await backend.findDefinitions(match);
2754
- for (const backendDef of backendDefinitions) {
2755
- const expandedDef = await backend.expandMatch(backendDef) ?? backendDef;
2756
- const defResult = {
2757
- ...expandedDef,
2758
- language,
2759
- depth: currentDepth,
2760
- sourceMatch: sourceMatch || match
2761
- };
2762
- if (filter && !this.matchesDefinitionFilter(defResult, filter)) {
2763
- continue;
2764
- }
2765
- definitions.push(defResult);
2766
- if (currentDepth + 1 < maxDepth) {
2767
- await this.followDefinitionsRecursive(
2768
- backendDef,
2769
- backend,
2770
- currentDepth + 1,
2771
- maxDepth,
2772
- filter,
2773
- definitions,
2774
- visited,
2775
- sourceMatch || match,
2776
- language
2777
- );
2778
- }
2779
- }
2780
- }
2781
- /**
2782
- * Check if a definition matches a filter
2783
- */
2784
- matchesDefinitionFilter(def, filter) {
2785
- var _a, _b, _c;
2786
- if (filter.all) {
2787
- return filter.all.every((f) => this.matchesDefinitionFilter(def, f));
2788
- }
2789
- if (filter.any) {
2790
- return filter.any.some((f) => this.matchesDefinitionFilter(def, f));
2791
- }
2792
- if (filter.not) {
2793
- return !this.matchesDefinitionFilter(def, filter.not);
2794
- }
2795
- if (filter.file) {
2796
- if (!this.matchesFileSpec(def.filePath, filter.file)) {
2797
- return false;
2798
- }
2799
- }
2800
- if ((_a = filter.method) == null ? void 0 : _a.name) {
2801
- const symbol = def.symbol;
2802
- if (!symbol || !this.matchesNameSpec(symbol, filter.method.name)) {
2803
- return false;
2804
- }
2805
- }
2806
- if ((_b = filter.function) == null ? void 0 : _b.name) {
2807
- const symbol = def.symbol;
2808
- if (!symbol || !this.matchesNameSpec(symbol, filter.function.name)) {
2809
- return false;
2810
- }
2811
- }
2812
- if ((_c = filter.class) == null ? void 0 : _c.name) {
2813
- }
2814
- return true;
2815
- }
2816
- /**
2817
- * Check if a name matches a name specification
2818
- */
2819
- matchesNameSpec(name, spec) {
2820
- if (spec.equals) {
2821
- return name === spec.equals;
2822
- }
2823
- if (spec.anyOf) {
2824
- return spec.anyOf.includes(name);
2825
- }
2826
- if (spec.regex) {
2827
- const regex = new RegExp(spec.regex);
2828
- return regex.test(name);
2829
- }
2830
- return true;
2831
- }
2832
- /**
2833
- * Check if a file path matches a file specification
2834
- */
2835
- matchesFileSpec(filePath, spec) {
2836
- const relativePath = path9.relative(this.environment.getWorkspaceRoot(), filePath);
2837
- if (spec.path) {
2838
- return relativePath === spec.path;
2839
- }
2840
- if (spec.glob) {
2841
- const ig = ignore2().add(spec.glob);
2842
- return ig.ignores(relativePath);
2843
- }
2844
- if (spec.regex) {
2845
- const regex = new RegExp(spec.regex);
2846
- return regex.test(relativePath);
2847
- }
2848
- return true;
2849
- }
2850
- };
2851
-
2852
- // src/providers/WispbitViolationValidationProvider.ts
2853
- var WispbitViolationValidationProvider = class {
2854
- config;
2855
- useInternalEndpoint;
2856
- constructor(config, useInternalEndpoint = false) {
2857
- this.config = config;
2858
- this.useInternalEndpoint = useInternalEndpoint;
2859
- }
2860
- async validateViolations(params) {
2861
- const apiClient = this.config.getApiClient();
2862
- const rulePayload = {
2863
- internalId: params.rule.internalId,
2864
- internalVersionId: params.rule.internalId,
2865
- contents: params.rule.prompt
2866
- };
2867
- const method = this.useInternalEndpoint ? apiClient.validateViolationInternal.bind(apiClient) : apiClient.validateViolation.bind(apiClient);
2868
- const result = await method({
2869
- rule: rulePayload,
2870
- matches: params.matches,
2871
- powerlint_version: this.config.getLocalVersion(),
2872
- schema_version: this.config.getSchemaVersion()
2873
- });
2874
- return {
2875
- validMatches: result.validMatches,
2876
- skippedMatches: result.skippedMatches
2877
- };
2878
- }
2879
- };
2880
-
2881
- // src/steps/LLMStep.ts
2882
- var LLMStep = class {
2883
- environment;
2884
- eventEmitter;
2885
- config;
2886
- storage;
2887
- constructor(config, environment, eventEmitter) {
2888
- this.environment = environment;
2889
- this.eventEmitter = eventEmitter || new ExecutionEventEmitter();
2890
- this.config = config;
2891
- this.storage = new Storage(environment);
2892
- }
2893
- /**
2894
- * Generate a cache key for a single match LLM validation
2895
- */
2896
- generateMatchCacheKey(match) {
2897
- const matchData = {
2898
- filePath: match.filePath,
2899
- startLine: match.range.start.line,
2900
- startColumn: match.range.start.column || 0,
2901
- endLine: match.range.end.line,
2902
- endColumn: match.range.end.column || 0,
2903
- text: match.text,
2904
- prompt: match.text
2905
- // Include prompt in cache key
2906
- };
2907
- return JSON.stringify(matchData);
2908
- }
2909
- /**
2910
- * Execute the actual LLM validation for uncached matches
2911
- * @param config - LLM step configuration (contains provider and model)
2912
- * @param uncachedMatches - Matches that need LLM evaluation
2913
- * @param promptContent - The loaded prompt content
2914
- */
2915
- async executeLLMValidation(rule, uncachedMatches) {
2916
- const llmStartTime = Date.now();
2917
- const validationProvider = new WispbitViolationValidationProvider(
2918
- this.config,
2919
- this.config.shouldUseInternalEndpoint()
2920
- );
2921
- const validationParams = {
2922
- rule,
2923
- matches: uncachedMatches
2924
- };
2925
- this.eventEmitter.startLLMValidation(rule.id, uncachedMatches.length);
2926
- const validationResponse = await validationProvider.validateViolations(validationParams);
2927
- const newViolationMatches = validationResponse.validMatches.map((match) => ({
2928
- ...match,
2929
- metadata: {
2930
- fromCache: false,
2931
- llmValidation: {
2932
- isViolation: true,
2933
- confidence: 1,
2934
- reason: "Confirmed violation"
2935
- }
2936
- }
2937
- }));
2938
- for (const match of validationResponse.validMatches) {
2939
- const cacheKey = this.generateMatchCacheKey(match);
2940
- const decision = {
2941
- isViolation: true,
2942
- confidence: 1,
2943
- reason: "Confirmed violation"
2944
- };
2945
- await this.storage.saveCache(rule.id, cacheKey, decision);
2946
- }
2947
- for (const match of validationResponse.skippedMatches) {
2948
- const cacheKey = this.generateMatchCacheKey(match);
2949
- const decision = {
2950
- isViolation: false,
2951
- confidence: 1,
2952
- reason: "Not a violation"
2953
- };
2954
- await this.storage.saveCache(rule.id, cacheKey, decision);
2955
- }
2956
- this.eventEmitter.completeLLMValidation(
2957
- rule.id,
2958
- 0,
2959
- // Token usage is handled internally by the validation providers
2960
- Date.now() - llmStartTime,
2961
- newViolationMatches.length,
2962
- false
2963
- );
2964
- return {
2965
- matches: newViolationMatches,
2966
- executionTime: Date.now() - llmStartTime
2967
- };
2968
- }
2969
- /**
2970
- * Execute the LLM step - always runs LLM validation immediately
2971
- * @param config - LLM step configuration
2972
- * @param previousMatches - Matches from previous steps to be judged by LLM
2973
- */
2974
- async execute(rule, previousMatches) {
2975
- const startTime = Date.now();
2976
- const cachedMatches = [];
2977
- const uncachedMatches = [];
2978
- for (const match of previousMatches) {
2979
- const cacheKey = this.generateMatchCacheKey(match);
2980
- const cachedDecision = await this.storage.readCache(rule.id, cacheKey);
2981
- if (cachedDecision) {
2982
- if (cachedDecision.isViolation) {
2983
- cachedMatches.push({
2984
- ...match,
2985
- metadata: {
2986
- fromCache: true,
2987
- llmValidation: {
2988
- isViolation: cachedDecision.isViolation,
2989
- confidence: cachedDecision.confidence,
2990
- reason: cachedDecision.reason
2991
- }
2992
- }
2993
- });
2994
- }
2995
- } else {
2996
- uncachedMatches.push(match);
2997
- }
2998
- }
2999
- let newViolationMatches = [];
3000
- if (uncachedMatches.length > 0) {
3001
- const llmResult = await this.executeLLMValidation(rule, uncachedMatches);
3002
- newViolationMatches = llmResult.matches;
3003
- }
3004
- if (uncachedMatches.length === 0) {
3005
- this.eventEmitter.completeLLMValidation(
3006
- rule.id,
3007
- 0,
3008
- // No tokens for cached result
3009
- Date.now() - startTime,
3010
- cachedMatches.length,
3011
- true
3012
- // fromCache = true
3013
- );
3014
- }
3015
- const allMatches = [...cachedMatches, ...newViolationMatches];
3016
- return {
3017
- matches: allMatches,
3018
- executionTime: Date.now() - startTime
3019
- };
3020
- }
3021
- };
3022
-
3023
- // src/steps/RuleExecutor.ts
3024
- var RuleExecutor = class {
3025
- fileFilterStep;
3026
- findMatchesStep;
3027
- gotoDefinitionStep;
3028
- llmStep;
3029
- environment;
3030
- executionContext;
3031
- currentMode;
3032
- eventEmitter;
3033
- config;
3034
- constructor(config, environment, eventEmitter) {
3035
- this.environment = environment;
3036
- this.eventEmitter = eventEmitter || new ExecutionEventEmitter();
3037
- this.config = config;
3038
- this.fileFilterStep = new FileFilterStep(this.environment);
3039
- this.findMatchesStep = new FindMatchesStep(this.environment, this.eventEmitter);
3040
- this.gotoDefinitionStep = new GotoDefinitionStep(this.environment, this.eventEmitter);
3041
- this.llmStep = new LLMStep(this.config, this.environment, this.eventEmitter);
3042
- }
3043
- /**
3044
- * Execute a single step
3045
- */
3046
- async executeStep(rule, stepName, stepConfig, filePaths, matches, options, _parentStepName) {
3047
- const stepType = stepConfig.type;
3048
- if (stepType === "ast-grep") {
3049
- const { type: _type, language, ...schema } = stepConfig;
3050
- const stepResult = await this.findMatchesStep.execute(schema, language, {
3051
- filePaths,
3052
- previousMatches: matches
3053
- });
3054
- return {
3055
- matches: stepResult.matches
3056
- };
3057
- }
3058
- if (stepType === "step-group") {
3059
- let allMatches = [];
3060
- for (let i = 0; i < stepConfig.steps.length; i++) {
3061
- const subStep = stepConfig.steps[i];
3062
- const subStepName = `${stepName}[${i}]`;
3063
- this.eventEmitter.startStep(
3064
- rule.id,
3065
- subStepName,
3066
- subStep.type,
3067
- { filePaths, matches },
3068
- stepName
3069
- // parent step name
3070
- );
3071
- const subStepStartTime = Date.now();
3072
- const subResult = await this.executeStep(
3073
- rule,
3074
- subStepName,
3075
- subStep,
3076
- filePaths,
3077
- matches,
3078
- options,
3079
- stepName
3080
- // parent step name
3081
- );
3082
- const subStepExecutionTime = Date.now() - subStepStartTime;
3083
- this.eventEmitter.completeStep(
3084
- rule.id,
3085
- subStepName,
3086
- subStep.type,
3087
- subResult,
3088
- subStepExecutionTime,
3089
- stepName
3090
- // parent step name
3091
- );
3092
- if (subResult.matches) {
3093
- allMatches = allMatches.concat(subResult.matches);
3094
- }
3095
- }
3096
- return {
3097
- matches: allMatches
3098
- };
3099
- }
3100
- if (stepType === "file-filter") {
3101
- const stepResult = await this.fileFilterStep.execute(filePaths, {
3102
- include: stepConfig.include,
3103
- ignore: stepConfig.ignore,
3104
- conditions: stepConfig.conditions
3105
- });
3106
- return {
3107
- filePaths: stepResult.filteredPaths
3108
- };
3109
- }
3110
- if (stepType === "goto-definition") {
3111
- if (matches.length === 0) {
3112
- return {
3113
- matches: []
3114
- };
3115
- }
3116
- const { language } = stepConfig;
3117
- const matchesForLanguage = matches.filter((m) => m.language === language);
3118
- if (matchesForLanguage.length === 0) {
3119
- return {
3120
- matches: []
3121
- };
3122
- }
3123
- const stepResult = await this.gotoDefinitionStep.execute(matchesForLanguage, language, {
3124
- where: stepConfig.where
3125
- });
3126
- const definitionFilePaths = [...new Set(stepResult.definitions.map((d) => d.filePath))];
3127
- return {
3128
- filePaths: definitionFilePaths,
3129
- matches: stepResult.definitions
3130
- };
3131
- }
3132
- if (stepType === "llm") {
3133
- if (matches.length === 0) {
3134
- return {
3135
- matches: []
3136
- };
3137
- }
3138
- const llmResult = await this.llmStep.execute(rule, matches);
3139
- return {
3140
- matches: llmResult.matches
3141
- };
3142
- }
3143
- throw new Error(`Unknown step type: ${stepType}`);
3144
- }
3145
- /**
3146
- * Execute all steps in multiple rule configurations
3147
- */
3148
- async execute(rules, options) {
3149
- if (this.currentMode && this.currentMode !== options.mode) {
3150
- throw new Error(`Execution mode mismatch: expected ${this.currentMode}, got ${options.mode}`);
3151
- }
3152
- if (!this.executionContext || this.currentMode !== options.mode) {
3153
- this.executionContext = await FileExecutionContext.initialize(
3154
- this.config,
3155
- this.environment,
3156
- options.mode,
3157
- this.eventEmitter,
3158
- options.filePath,
3159
- options.diffOptions
3160
- );
3161
- this.currentMode = options.mode;
3162
- }
3163
- this.eventEmitter.startRules(rules.length);
3164
- const results = [];
3165
- for (let i = 0; i < rules.length; i++) {
3166
- const rule = rules[i];
3167
- const ruleId = rule.id;
3168
- let isLlm = false;
3169
- for (const stepWithId of rule.config.steps) {
3170
- if (stepWithId.step.type === "llm") {
3171
- isLlm = true;
3172
- break;
3173
- }
3174
- }
3175
- this.eventEmitter.progressRule(i + 1, rules.length, ruleId, isLlm);
3176
- const ruleConfig = rule.config;
3177
- let currentFilePaths = [...this.executionContext.filePaths];
3178
- let currentMatches = [];
3179
- for (const stepWithId of ruleConfig.steps) {
3180
- const stepName = stepWithId.id;
3181
- const stepConfig = stepWithId.step;
3182
- this.eventEmitter.startStep(ruleId, stepName, stepConfig.type, {
3183
- filePaths: currentFilePaths,
3184
- matches: currentMatches
3185
- });
3186
- const stepStartTime = Date.now();
3187
- const result = await this.executeStep(
3188
- rule,
3189
- stepName,
3190
- stepConfig,
3191
- currentFilePaths,
3192
- currentMatches,
3193
- options
3194
- );
3195
- const stepExecutionTime = Date.now() - stepStartTime;
3196
- this.eventEmitter.completeStep(ruleId, stepName, stepConfig.type, result, stepExecutionTime);
3197
- if (result.filePaths !== void 0) {
3198
- currentFilePaths = this.executionContext.filterFiles(result.filePaths);
3199
- currentMatches = this.executionContext.filterMatchesByFilePaths(
3200
- currentMatches,
3201
- currentFilePaths
3202
- );
3203
- }
3204
- if (result.matches !== void 0) {
3205
- currentMatches = this.executionContext.filterMatches(result.matches);
3206
- }
3207
- if (currentFilePaths.length === 0) {
3208
- break;
3209
- }
3210
- }
3211
- const sortedMatches = currentMatches.sort((a, b) => a.filePath.localeCompare(b.filePath));
3212
- results.push({
3213
- ruleId,
3214
- matches: sortedMatches
3215
- });
3216
- }
3217
- const totalMatches = results.reduce((sum, result) => sum + result.matches.length, 0);
3218
- this.eventEmitter.completeRules(rules.length, totalMatches);
3219
- return results;
3220
- }
3221
- };
3222
-
3223
- // src/types.ts
3224
- var InvalidRuleFormatError = class extends Error {
3225
- constructor(ruleId, message, validationErrors) {
3226
- super(`Invalid rule format in '${ruleId}': ${message}`);
3227
- this.validationErrors = validationErrors;
3228
- this.name = "InvalidRuleFormatError";
3229
- }
3230
- };
3231
-
3232
- // src/utils/formatters.ts
3233
- import { stripVTControlCharacters } from "node:util";
3234
- import chalk from "chalk";
3235
- import ora from "ora";
3236
- var VIOLATION_COLOR = "#f87171";
3237
- var SUGGESTION_COLOR = "#60a5fa";
3238
- var SKIPPED_COLOR = "#9b59b6";
3239
- var BRAND_COLOR = "#fbbf24";
3240
- var DETERMINISTIC_COLOR = "#9ca3af";
3241
- var LLM_COLOR = "#9b59b6";
3242
- function formatClickableRuleId(ruleId, internalId) {
3243
- if (internalId) {
3244
- const url = `https://app.wispbit.com/rules/${internalId}`;
3245
- return `${chalk.dim.underline(ruleId)}${chalk.dim("|")}${chalk.dim.underline(url)}`;
3246
- }
3247
- return chalk.dim(ruleId);
3248
- }
3249
- function truncateMessage(message, maxWidth) {
3250
- if (message.length <= maxWidth) {
3251
- return message;
3252
- }
3253
- return message.substring(0, maxWidth - 3) + "...";
3254
- }
3255
- var LINE_NUMBER_BG_COLOR = "#f8f8f8";
3256
- var LINE_NUMBER_TEXT_COLOR = "#888888";
3257
- var loadingFrames = [
3258
- chalk.hex(BRAND_COLOR)("~(oo)~"),
3259
- chalk.hex(BRAND_COLOR)("~(oO)~"),
3260
- chalk.hex(BRAND_COLOR)("~(Oo)~"),
3261
- chalk.hex(BRAND_COLOR)("~(OO)~"),
3262
- chalk.hex(BRAND_COLOR)("~(\u25CFo)~"),
3263
- chalk.hex(BRAND_COLOR)("~(o\u25CF)~"),
3264
- chalk.hex(BRAND_COLOR)("~(\u25C9o)~"),
3265
- chalk.hex(BRAND_COLOR)("~(o\u25C9)~"),
3266
- chalk.hex(BRAND_COLOR)("\\(oo)/"),
3267
- chalk.hex(BRAND_COLOR)("\\(oO)/"),
3268
- chalk.hex(BRAND_COLOR)("\\(Oo)/"),
3269
- chalk.hex(BRAND_COLOR)("\\(OO)/"),
3270
- chalk.hex(BRAND_COLOR)("~(Oo)~"),
3271
- chalk.hex(BRAND_COLOR)("~(oO)~"),
3272
- chalk.hex(BRAND_COLOR)("~(\u25CEo)~"),
3273
- chalk.hex(BRAND_COLOR)("~(o\u25CE)~"),
3274
- chalk.hex(BRAND_COLOR)("~(oo)~"),
3275
- chalk.hex(BRAND_COLOR)("~(OO)~"),
3276
- chalk.hex(BRAND_COLOR)("~(Oo)~"),
3277
- chalk.hex(BRAND_COLOR)("\\(oo)/"),
3278
- chalk.hex(BRAND_COLOR)("\\(oO)/"),
3279
- chalk.hex(BRAND_COLOR)("\\(Oo)/"),
3280
- chalk.hex(BRAND_COLOR)("\\(OO)/")
3281
- ];
3282
- function pluralize(word, count) {
3283
- return count === 1 ? word : `${word}s`;
3284
- }
3285
- function textTable(rows, opts = {}) {
3286
- const hsep = " ";
3287
- const align = opts.align || [];
3288
- const stringLength = opts.stringLength || ((str) => stripVTControlCharacters(str).length);
3289
- const sizes = rows.reduce((acc, row) => {
3290
- row.forEach((c2, ix) => {
3291
- const n = stringLength(c2);
3292
- if (!acc[ix] || n > acc[ix]) {
3293
- acc[ix] = n;
3294
- }
3295
- });
3296
- return acc;
3297
- }, []);
3298
- return rows.map(
3299
- (row) => row.map((c2, ix) => {
3300
- const n = sizes[ix] - stringLength(c2) || 0;
3301
- const s = Array(Math.max(n + 1, 1)).join(" ");
3302
- if (align[ix] === "r") {
3303
- return s + c2;
3304
- }
3305
- return c2 + s;
3306
- }).join(hsep).trimEnd()
3307
- ).join("\n");
3308
- }
3309
- function printSummary(results, summary) {
3310
- let output = "\n";
3311
- let violationCount = 0;
3312
- let suggestionCount = 0;
3313
- const ruleResults = /* @__PURE__ */ new Map();
3314
- results.forEach((result) => {
3315
- if (result.matches.length === 0) {
3316
- return;
3317
- }
3318
- const ruleId = result.ruleId || "unknown";
3319
- if (!ruleResults.has(ruleId)) {
3320
- ruleResults.set(ruleId, {
3321
- message: result.message,
3322
- severity: result.severity,
3323
- internalId: result.internalId,
3324
- matches: [],
3325
- hasLlmValidation: false
3326
- });
3327
- }
3328
- const ruleData = ruleResults.get(ruleId);
3329
- result.matches.forEach((match) => {
3330
- var _a;
3331
- ruleData.matches.push({
3332
- filePath: match.filePath,
3333
- line: match.range.start.line,
3334
- column: match.range.start.column,
3335
- endLine: match.range.end.line !== match.range.start.line ? match.range.end.line : void 0,
3336
- text: match.text
3337
- });
3338
- if ((_a = match.metadata) == null ? void 0 : _a.llmValidation) {
3339
- ruleData.hasLlmValidation = true;
3340
- }
3341
- if (result.severity === "violation") {
3342
- violationCount++;
3343
- } else {
3344
- suggestionCount++;
3345
- }
3346
- });
3347
- });
3348
- ruleResults.forEach((ruleData, ruleId) => {
3349
- if (ruleData.matches.length === 0 && ruleData.severity !== "skipped") {
3350
- return;
3351
- }
3352
- let messageType;
3353
- if (ruleData.severity === "violation") {
3354
- messageType = chalk.hex(VIOLATION_COLOR)("\u25A0") + " " + chalk.hex(VIOLATION_COLOR).bold("violation".padStart(8));
3355
- } else if (ruleData.severity === "suggestion") {
3356
- messageType = chalk.hex(SUGGESTION_COLOR)("\u25CF") + " " + chalk.hex(SUGGESTION_COLOR).bold("suggestion".padEnd(6));
3357
- } else {
3358
- messageType = chalk.hex(SKIPPED_COLOR)(" skipped".padEnd(9));
3359
- }
3360
- const llmIndicator = ruleData.hasLlmValidation ? `${chalk.hex(SKIPPED_COLOR)("(llm)")} ` : "";
3361
- const clickableRuleId = formatClickableRuleId(ruleId, ruleData.internalId);
3362
- const ruleHeader = `${messageType} ${chalk.dim("\u2502")} ${llmIndicator}${ruleData.message.replace(/([^ ])\.$/u, "$1")} ${chalk.dim("(")}${clickableRuleId}${chalk.dim(")")}`;
3363
- output += `${ruleHeader}
3364
- `;
3365
- output += `${chalk.dim("\u2500".repeat(80))}
3366
- `;
3367
- if (ruleData.severity === "skipped") {
3368
- output += `
3369
- `;
3370
- } else {
3371
- const sortedMatches = ruleData.matches.sort((a, b) => a.filePath.localeCompare(b.filePath));
3372
- const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <= 30);
3373
- if (shouldShowCodeSnippets) {
3374
- sortedMatches.forEach((match, index) => {
3375
- if (match.text && match.text.split("\n").length <= 30) {
3376
- output += ` ${match.filePath}
3377
- `;
3378
- const lines = match.text.split("\n");
3379
- lines.forEach((line, lineIndex) => {
3380
- const lineNumber = match.line + lineIndex;
3381
- const lineNumberBox = chalk.bgHex(LINE_NUMBER_BG_COLOR).hex(LINE_NUMBER_TEXT_COLOR)(
3382
- ` ${String(lineNumber)} `
3383
- );
3384
- output += ` ${lineNumberBox} ${chalk.dim(line)}
3385
- `;
3386
- });
3387
- } else {
3388
- const lineRange = match.endLine ? `(${match.line}:${match.endLine})` : `(${match.line})`;
3389
- output += ` ${match.filePath} ${chalk.dim(lineRange)}
3390
- `;
3391
- }
3392
- if (index < sortedMatches.length - 1) {
3393
- output += `
3394
- `;
3395
- }
3396
- });
3397
- } else {
3398
- sortedMatches.forEach((match) => {
3399
- const lineRange = match.endLine ? `(${match.line}:${match.endLine})` : `(${match.line})`;
3400
- output += ` ${match.filePath} ${chalk.dim(lineRange)}
3401
- `;
3402
- });
3403
- }
3404
- output += `
3405
-
3406
- `;
3407
- }
3408
- });
3409
- console.log(output);
3410
- const total = violationCount + suggestionCount;
3411
- const violationText = violationCount > 0 ? `${chalk.dim("violations".padStart(12))} ${chalk.hex(VIOLATION_COLOR)("\u25A0")} ${chalk.hex(VIOLATION_COLOR).bold(violationCount)}` : `${chalk.dim("violations".padStart(12))} ${chalk.dim(violationCount)}`;
3412
- const suggestionText = suggestionCount > 0 ? `${chalk.dim("suggestions".padStart(12))} ${chalk.hex(SUGGESTION_COLOR)("\u25CF")} ${chalk.hex(SUGGESTION_COLOR).bold(suggestionCount)}` : `${chalk.dim("suggestions".padStart(12))} ${chalk.dim(suggestionCount)}`;
3413
- const filesText = summary.totalFiles ? `${summary.totalFiles} ${pluralize("file", summary.totalFiles)}` : "0 files";
3414
- const rulesText = `${summary.totalRules} ${pluralize("rule", summary.totalRules)}`;
3415
- const timeText = summary.executionTime ? `${Math.round(summary.executionTime)}ms` : "";
3416
- const detailsText = [filesText, rulesText, timeText].filter(Boolean).join(", ");
3417
- let summaryOutput = "";
3418
- summaryOutput += `${violationText}
3419
- `;
3420
- summaryOutput += `${suggestionText}
3421
- `;
3422
- summaryOutput += `${chalk.dim("summary".padStart(12))} ${detailsText}
3423
- `;
3424
- console.log(total > 0 ? chalk.reset(summaryOutput) : summaryOutput);
3425
- if (violationCount > 0) {
3426
- process.exit(1);
3427
- }
3428
- }
3429
- function printRulesList(rules) {
3430
- if (rules.length === 0) {
3431
- console.log(chalk.yellow("No rules found."));
3432
- console.log(chalk.dim("Go to https://app.wispbit.com/rules to create a rule."));
3433
- return;
3434
- }
3435
- const tableData = [];
3436
- tableData.push([
3437
- "",
3438
- `${"id".padEnd(12)} ${chalk.dim("\u2502")} ${"severity".padEnd(16)} ${chalk.dim("\u2502")} ${"execution".padEnd(18)} ${chalk.dim("\u2502")} message`
3439
- ]);
3440
- const terminalWidth = process.stdout.columns || 120;
3441
- const fixedColumnsWidth = 12 + 3 + 16 + 3 + 18 + 3;
3442
- const messageMaxWidth = Math.max(20, terminalWidth - fixedColumnsWidth - 10);
3443
- tableData.push([
3444
- "",
3445
- `${chalk.dim("\u2500".repeat(12))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(16))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(18))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(messageMaxWidth))}`
3446
- ]);
3447
- for (const rule of rules) {
3448
- const severityIcon = rule.config.severity === "violation" ? "\u25A0" : "\u25CF";
3449
- const severityColor = rule.config.severity === "violation" ? chalk.hex(VIOLATION_COLOR).bold : chalk.hex(SUGGESTION_COLOR).bold;
3450
- const severityIconColor = rule.config.severity === "violation" ? chalk.hex(VIOLATION_COLOR) : chalk.hex(SUGGESTION_COLOR);
3451
- const severityText = severityIconColor(severityIcon) + " " + severityColor(rule.config.severity);
3452
- const ruleId = formatClickableRuleId(rule.id, rule.internalId);
3453
- const executionText = rule.config.execution === "llm" ? chalk.hex(LLM_COLOR)("\u25C6") + " " + chalk.hex(LLM_COLOR).bold("llm") : chalk.hex(DETERMINISTIC_COLOR)("\u25C6") + " " + chalk.hex(DETERMINISTIC_COLOR).bold("deterministic");
3454
- const truncatedMessage = truncateMessage(rule.config.message, messageMaxWidth);
3455
- const idPadding = Math.max(0, 12 - rule.id.length);
3456
- const severityPadding = Math.max(0, 16 - (rule.config.severity.length + 2));
3457
- const executionPadding = Math.max(0, 18 - (rule.config.execution.length + 2));
3458
- tableData.push([
3459
- "",
3460
- `${ruleId}${" ".repeat(idPadding)} ${chalk.dim("\u2502")} ${severityText}${" ".repeat(severityPadding)} ${chalk.dim("\u2502")} ${executionText}${" ".repeat(executionPadding)} ${chalk.dim("\u2502")} ${truncatedMessage}`
3461
- ]);
3462
- }
3463
- const table = textTable(tableData, {
3464
- align: ["", "l"],
3465
- stringLength(str) {
3466
- return stripVTControlCharacters(str).length;
3467
- }
3468
- });
3469
- console.log("\n" + table);
3470
- console.log(chalk.dim(`
3471
- Use 'wispbit check --rule <rule-id>' to run a specific rule.`));
3472
- }
3473
- function outputJSON(results, format = "pretty") {
3474
- const matches = [];
3475
- for (const result of results) {
3476
- for (const match of result.matches) {
3477
- const jsonMatch = {
3478
- filePath: match.filePath,
3479
- range: match.range,
3480
- language: match.language,
3481
- text: match.text,
3482
- symbol: match.symbol,
3483
- source: match.source,
3484
- ruleId: result.ruleId,
3485
- internalId: result.internalId,
3486
- severity: result.severity,
3487
- message: result.message
3488
- };
3489
- matches.push(jsonMatch);
3490
- }
3491
- }
3492
- if (format === "stream") {
3493
- for (const match of matches) {
3494
- console.log(JSON.stringify(match));
3495
- }
3496
- } else if (format === "compact") {
3497
- console.log(JSON.stringify(matches));
3498
- } else {
3499
- console.log(JSON.stringify(matches, null, 2));
3500
- }
3501
- }
3502
- function setupTerminalReporter(eventEmitter, debugMode = false) {
3503
- let spinner = null;
3504
- let indexingStartTime = null;
3505
- let executionMode = null;
3506
- eventEmitter.on("execution:mode", (data) => {
3507
- executionMode = data;
3508
- });
3509
- eventEmitter.on("rules:start", (_data) => {
3510
- spinner = ora({
3511
- spinner: {
3512
- interval: 120,
3513
- frames: loadingFrames
3514
- },
3515
- color: false
3516
- }).start();
3517
- const handleInterrupt = () => {
3518
- if (spinner) {
3519
- spinner.stop();
3520
- }
3521
- process.exit(130);
3522
- };
3523
- process.on("SIGINT", handleInterrupt);
3524
- process.on("SIGTERM", handleInterrupt);
3525
- });
3526
- eventEmitter.on("rules:progress", (data) => {
3527
- if (spinner) {
3528
- let modeText = "";
3529
- if (executionMode) {
3530
- if (executionMode.mode === "check") {
3531
- modeText = executionMode.filePath ? ` (${executionMode.filePath})` : " (ALL FILES)";
3532
- } else if (executionMode.mode === "diff") {
3533
- modeText = ` (${executionMode.baseCommit} \u2192 ${executionMode.headCommit})`;
3534
- }
3535
- }
3536
- spinner.text = `${data.isLlm ? chalk.hex(SKIPPED_COLOR)("(llm) ") : ""}${data.ruleId}${modeText}`;
3537
- }
3538
- });
3539
- eventEmitter.on("rules:complete", () => {
3540
- if (spinner) {
3541
- spinner.stop();
3542
- spinner = null;
3543
- }
3544
- });
3545
- eventEmitter.on("files:discovery:start", (data) => {
3546
- if (debugMode) {
3547
- console.log(`
3548
- ${chalk.blue("Discovering files")} in ${chalk.bold(data.mode)} mode...`);
3549
- }
3550
- });
3551
- eventEmitter.on("files:discovery:progress", (data) => {
3552
- if (debugMode) {
3553
- console.log(` ${data.message}`);
3554
- }
3555
- });
3556
- eventEmitter.on("files:discovery:complete", (data) => {
3557
- if (debugMode) {
3558
- console.log(
3559
- chalk.green("File discovery complete:"),
3560
- `${data.totalFiles} files found (${data.executionTime}ms)`
3561
- );
3562
- }
3563
- });
3564
- eventEmitter.on("files:filter", (data) => {
3565
- if (debugMode && data.originalCount !== data.filteredCount) {
3566
- console.log(
3567
- chalk.yellow("Filtered:"),
3568
- `${data.originalCount} \u2192 ${data.filteredCount} ${data.filterType}`
3569
- );
3570
- }
3571
- });
3572
- eventEmitter.on("indexing:start", (_data) => {
3573
- indexingStartTime = Date.now();
3574
- });
3575
- eventEmitter.on("indexing:progress", (data) => {
3576
- if (spinner) {
3577
- spinner.color = "yellow";
3578
- }
3579
- if (indexingStartTime && Date.now() - indexingStartTime > 1e3) {
3580
- if (spinner) {
3581
- spinner.text = `Indexing ${data.language}...`;
3582
- }
3583
- }
3584
- if (spinner) {
3585
- if (data.packageName && data.timeMs) {
3586
- spinner.text = `Indexing ${data.language}: ${data.packageName}`;
3587
- } else if (data.message !== "Indexing files...") {
3588
- spinner.text = `Indexing ${data.language}: ${data.message}`;
3589
- }
3590
- }
3591
- if (debugMode) {
3592
- if (data.packageName && data.timeMs) {
3593
- console.log(` + ${data.packageName} (${data.timeMs}ms)`);
3594
- } else if (data.message === "Indexing files...") {
3595
- process.stdout.write(".");
3596
- } else if (data.message !== "Indexing files...") {
3597
- console.log(` ${data.message}`);
3598
- }
3599
- }
3600
- });
3601
- eventEmitter.on("indexing:complete", (data) => {
3602
- indexingStartTime = null;
3603
- if (spinner) {
3604
- spinner.color = "blue";
3605
- }
3606
- if (debugMode) {
3607
- process.stdout.write("\n");
3608
- console.log(
3609
- chalk.green("Indexing complete for"),
3610
- `${data.language} (${data.executionTime}ms)`
3611
- );
3612
- }
3613
- });
3614
- if (debugMode) {
3615
- eventEmitter.on("scip:match-lookup:start", (data) => {
3616
- console.log(
3617
- chalk.cyan(
3618
- `
3619
- \u{1F50D} [${data.language}] Starting SCIP match lookup for: ${JSON.stringify(data.match)}`
3620
- )
3621
- );
3622
- });
3623
- eventEmitter.on("scip:match-lookup:progress", (data) => {
3624
- console.log(`${JSON.stringify(data.document)}`);
3625
- });
3626
- eventEmitter.on("scip:match-lookup:complete", (data) => {
3627
- console.log(chalk.green(`SCIP match lookup complete for: ${data.language}`));
3628
- });
3629
- eventEmitter.on("step:start", (data) => {
3630
- console.log(
3631
- chalk.cyan(`
3632
- \u{1F527} [${data.ruleId}] Starting step: ${data.stepName} (${data.stepType})`)
3633
- );
3634
- console.log(` Input files: ${data.inputs.filePaths.length}`);
3635
- console.log(` Input matches: ${data.inputs.matches.length}`);
3636
- if (data.inputs.matches.length > 0) {
3637
- console.log(` Match details:`);
3638
- data.inputs.matches.forEach((match, idx) => {
3639
- var _a;
3640
- console.log(` Match ${idx + 1}:`);
3641
- console.log(` File: ${match.filePath}`);
3642
- console.log(
3643
- ` Range: ${match.range.start.line}:${match.range.start.column} \u2192 ${match.range.end.line}:${match.range.end.column}`
3644
- );
3645
- if (match.symbol) {
3646
- console.log(` Symbol: ${match.symbol}`);
3647
- }
3648
- if (match.source && match.source.length > 0) {
3649
- console.log(` Source chain: ${match.source.length} step(s)`);
3650
- match.source.forEach((src, srcIdx) => {
3651
- const range = `${src.range.start.line}:${src.range.start.column} \u2192 ${src.range.end.line}:${src.range.end.column}`;
3652
- console.log(
3653
- ` ${srcIdx + 1}. ${src.filePath} [${src.symbol || "N/A"}] @ ${range}`
3654
- );
3655
- });
3656
- }
3657
- console.log(` Text preview: ${(_a = match.text) == null ? void 0 : _a.substring(0, 120)}...`);
3658
- });
3659
- }
3660
- });
3661
- eventEmitter.on("step:complete", (data) => {
3662
- console.log(
3663
- chalk.cyan(
3664
- `\u2705 [${data.ruleId}] Completed step: ${data.stepName} (${data.stepType}) (${data.executionTime}ms)`
3665
- )
3666
- );
3667
- if (data.outputs.filePaths !== void 0) {
3668
- console.log(` Output files: ${data.outputs.filePaths.length}`);
3669
- }
3670
- if (data.outputs.matches !== void 0) {
3671
- console.log(` Output matches: ${data.outputs.matches.length}`);
3672
- if (data.outputs.matches.length > 0) {
3673
- console.log(` Output match details:`);
3674
- data.outputs.matches.forEach((match, idx) => {
3675
- var _a;
3676
- console.log(` Match ${idx + 1}:`);
3677
- console.log(` File: ${match.filePath}`);
3678
- console.log(
3679
- ` Range: ${match.range.start.line}:${match.range.start.column} \u2192 ${match.range.end.line}:${match.range.end.column}`
3680
- );
3681
- if (match.symbol) {
3682
- console.log(` Symbol: ${match.symbol}`);
3683
- }
3684
- if (match.source && match.source.length > 0) {
3685
- console.log(` Source chain: ${match.source.length} step(s)`);
3686
- match.source.forEach((src, srcIdx) => {
3687
- const range = `${src.range.start.line}:${src.range.start.column} \u2192 ${src.range.end.line}:${src.range.end.column}`;
3688
- console.log(
3689
- ` ${srcIdx + 1}. ${src.filePath} [${src.symbol || "N/A"}] @ ${range}`
3690
- );
3691
- });
3692
- }
3693
- console.log(` Text preview: ${(_a = match.text) == null ? void 0 : _a.substring(0, 120)}...`);
3694
- });
3695
- }
3696
- }
3697
- });
3698
- eventEmitter.on("test:start", (data) => {
3699
- console.log(chalk.magenta(`
3700
- \u{1F9EA} [${data.ruleId}] Running test: "${data.testName}"`));
3701
- });
3702
- eventEmitter.on("test:matches", (data) => {
3703
- console.log(chalk.magenta(` Found ${data.matches.length} match(es):`));
3704
- data.matches.forEach((match, idx) => {
3705
- var _a;
3706
- console.log(` Match ${idx + 1}:`);
3707
- console.log(` File: ${match.filePath}`);
3708
- console.log(
3709
- ` Range: ${match.range.start.line}:${match.range.start.column} \u2192 ${match.range.end.line}:${match.range.end.column}`
3710
- );
3711
- if (match.symbol) {
3712
- console.log(` Symbol: ${match.symbol}`);
3713
- }
3714
- if (match.source && match.source.length > 0) {
3715
- console.log(` Source chain: ${match.source.length} step(s)`);
3716
- match.source.forEach((src, srcIdx) => {
3717
- const range = `${src.range.start.line}:${src.range.start.column} \u2192 ${src.range.end.line}:${src.range.end.column}`;
3718
- console.log(` ${srcIdx + 1}. ${src.filePath} [${src.symbol || "N/A"}] @ ${range}`);
3719
- });
3720
- }
3721
- console.log(` Text preview: ${(_a = match.text) == null ? void 0 : _a.substring(0, 120)}...`);
3722
- });
3723
- });
3724
- }
3725
- return () => {
3726
- if (spinner) {
3727
- spinner.stop();
3728
- spinner = null;
3729
- }
3730
- };
3731
- }
3732
-
3733
- // src/utils/startupScreen.ts
3734
- import readline from "readline";
3735
- import chalk3 from "chalk";
3736
- import ora2 from "ora";
3737
-
3738
- // src/utils/asciiFrames.ts
3739
- import chalk2 from "chalk";
3740
- var BRAND_COLOR2 = "#fbbf24";
3741
- var VIOLATION_COLOR2 = "#ff6b35";
3742
- var SUGGESTION_COLOR2 = "#4a90e2";
3743
- var SKIPPED_COLOR2 = "#9b59b6";
3744
- var WISPBIT_FRAMES = [
3745
- // Frame 1 - Normal position
3746
- `
3747
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
3748
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
3749
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3750
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3751
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3752
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
3753
- `,
3754
- // Frame 2 - Wave starts from left
3755
- `
3756
- ${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2557 ")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
3757
- ${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2551 ")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
3758
- ${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2551 \u2588\u2557 ")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3759
- ${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3760
- ${chalk2.hex(SUGGESTION_COLOR2)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3761
- ${chalk2.hex(SUGGESTION_COLOR2)(" \u255A\u2550\u2550\u255D\u255A\u2550")}${chalk2.hex(BRAND_COLOR2)("\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
3762
- `,
3763
- // Frame 3 - Wave in middle
3764
- `
3765
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557")}${chalk2.hex(SKIPPED_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
3766
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551")}${chalk2.hex(SKIPPED_COLOR2)("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
3767
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551")}${chalk2.hex(SKIPPED_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3768
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551")}${chalk2.hex(SKIPPED_COLOR2)("\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D ")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3769
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${chalk2.hex(SKIPPED_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ")}${chalk2.hex(BRAND_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3770
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D")}${chalk2.hex(SKIPPED_COLOR2)("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ")}${chalk2.hex(BRAND_COLOR2)("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
3771
- `,
3772
- // Frame 4 - Wave towards right
3773
- `
3774
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${chalk2.hex(VIOLATION_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
3775
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}${chalk2.hex(VIOLATION_COLOR2)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
3776
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}${chalk2.hex(VIOLATION_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3777
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D ")}${chalk2.hex(VIOLATION_COLOR2)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3778
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ")}${chalk2.hex(VIOLATION_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
3779
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ")}${chalk2.hex(VIOLATION_COLOR2)("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
3780
- `,
3781
- // Frame 5 - Wave at end
3782
- `
3783
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557")}${chalk2.hex(SUGGESTION_COLOR2)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
3784
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551")}${chalk2.hex(SUGGESTION_COLOR2)("\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
3785
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2551 ")}
3786
- ${chalk2.hex(BRAND_COLOR2)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551")}${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2551 ")}
3787
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${chalk2.hex(SUGGESTION_COLOR2)(" \u2588\u2588\u2551 ")}
3788
- ${chalk2.hex(BRAND_COLOR2)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D")}${chalk2.hex(SUGGESTION_COLOR2)(" \u255A\u2550\u255D ")}
3789
- `
3790
- ];
3791
-
3792
- // src/utils/startupScreen.ts
3793
- function clearScreen() {
3794
- process.stdout.write("\x1B[2J\x1B[H");
3795
- }
3796
- function sleep(ms) {
3797
- return new Promise((resolve3) => setTimeout(resolve3, ms));
3798
- }
3799
- async function showAnimatedFrames(durationMs = 3e3) {
3800
- const spinner = ora2({
3801
- text: "",
3802
- spinner: {
3803
- interval: 100,
3804
- // ms per frame
3805
- frames: WISPBIT_FRAMES
3806
- }
3807
- }).start();
3808
- await sleep(durationMs);
3809
- spinner.stop();
3810
- }
3811
- async function promptForInput(question) {
3812
- const rl = readline.createInterface({
3813
- input: process.stdin,
3814
- output: process.stdout
3815
- });
3816
- return await new Promise((resolve3) => {
3817
- rl.question(question, (answer) => {
3818
- rl.close();
3819
- resolve3(answer.trim());
3820
- });
3821
- });
3822
- }
3823
- async function showStartupScreen() {
3824
- clearScreen();
3825
- await showAnimatedFrames(1500);
3826
- console.log(WISPBIT_FRAMES[0]);
3827
- console.log(chalk3.hex(VIOLATION_COLOR)("\n Welcome to wispbit"));
3828
- console.log(chalk3(" The linter for AI\n"));
3829
- console.log(chalk3.dim(" To use wispbit, you need an API key."));
3830
- console.log(chalk3.dim(" You can get one at: https://app.wispbit.com/api-keys\n"));
3831
- const apiKey = await promptForInput(
3832
- chalk3.bold.hex(SUGGESTION_COLOR)(" Enter your Wispbit API key (or press Enter to exit): ")
3833
- );
3834
- if (!apiKey) {
3835
- console.log(chalk3.dim("\n Setup cancelled."));
3836
- return null;
3837
- }
3838
- return apiKey;
3839
- }
3840
-
3841
- // src/cli.ts
3842
- dotenv.config();
3843
- function getConfigFilePath() {
3844
- return path10.join(os3.homedir(), ".powerlint", "config.json");
3845
- }
3846
- async function saveApiKey(apiKey) {
3847
- const configPath = getConfigFilePath();
3848
- const configDir = path10.dirname(configPath);
3849
- await fs5.mkdir(configDir, { recursive: true });
3850
- let existingConfig = {};
3851
- try {
3852
- const configContent = await fs5.readFile(configPath, "utf-8");
3853
- existingConfig = JSON.parse(configContent);
3854
- } catch {
3855
- }
3856
- const newConfig = {
3857
- ...existingConfig,
3858
- apiKey
3859
- };
3860
- await fs5.writeFile(configPath, JSON.stringify(newConfig, null, 2));
3861
- }
3862
- async function loadApiKey() {
3863
- if (process.env.WISPBIT_API_KEY) {
3864
- return process.env.WISPBIT_API_KEY;
3865
- }
3866
- const configPath = getConfigFilePath();
3867
- try {
3868
- const configContent = await fs5.readFile(configPath, "utf-8");
3869
- const config = JSON.parse(configContent);
3870
- return config.apiKey || null;
3871
- } catch {
3872
- return null;
3873
- }
3874
- }
3875
- async function ensureConfigured(environment) {
3876
- let apiKey = process.env.WISPBIT_API_KEY || null;
3877
- if (!apiKey) {
3878
- apiKey = await loadApiKey();
3879
- }
3880
- let config = await Config.initialize(environment, { apiKey: apiKey || void 0 });
3881
- if ("failed" in config) {
3882
- if (config.error === "INVALID_API_KEY") {
3883
- const newApiKey = await showStartupScreen();
3884
- if (!newApiKey) {
3885
- process.exit(0);
3886
- }
3887
- const repositoryUrl = await environment.getRepositoryUrl();
3888
- if (!repositoryUrl) {
3889
- console.log(chalk4.red("Repository URL not found. Make sure you're in a git repository."));
3890
- process.exit(1);
3891
- }
3892
- console.log(chalk4.dim("Validating API key..."));
3893
- await saveApiKey(newApiKey);
3894
- config = await Config.initialize(environment, { apiKey: newApiKey });
3895
- if ("failed" in config) {
3896
- console.log(chalk4.red("Invalid API key. Please check your API key and try again."));
3897
- process.exit(1);
3898
- } else {
3899
- console.log(chalk4.green("Setup complete! powerlint has been configured."));
3900
- return config;
3901
- }
3902
- } else if (config.error === "INVALID_REPOSITORY") {
3903
- const repositoryUrl = await environment.getRepositoryUrl();
3904
- console.log(
3905
- chalk4.red(
3906
- `No repository in wispbit found for url: ${repositoryUrl}. If you are passing the repository URL as a flag, make sure it is correct. If your git remote URL was recently modified, use "git remote set-url origin <new-url>" to update the remote URL.`
3907
- )
3908
- );
3909
- process.exit(1);
3910
- }
3911
- }
3912
- return config;
3913
- }
3914
- async function checkForUpdates() {
3915
- const currentVersion = getCurrentVersion();
3916
- const latestCliVersion = await getLatestVersion();
3917
- if (semver.gt(latestCliVersion, currentVersion)) {
3918
- console.log(
3919
- chalk4.bgHex("#b2f5ea").black.bold(` NEW VERSION AVAILABLE: ${latestCliVersion} (current: ${currentVersion}) `)
3920
- );
3921
- console.log(
3922
- chalk4.bgHex("#b2f5ea").black.bold(` Run 'pnpm install -g @wispbit/local' to update
3923
- `)
3924
- );
3925
- }
3926
- }
3927
- var cli = meow(
3928
- `
2
+ import*as Y from"fs/promises";import*as Ft from"os";import*as Ne from"path";import kt from"big.js";import T from"chalk";import Ii from"dotenv";import Li from"meow";import Ci from"semver";import At from"p-retry";import{z as v}from"zod";var Ot=v.object({repository_url:v.string(),powerlint_version:v.string(),schema_version:v.string()}),Nt=v.object({configured:v.boolean(),invalid_api_key:v.boolean().optional(),is_valid_repository:v.boolean().optional(),config:v.object({ignored_globs:v.array(v.string())}).optional()}),Gt=v.object({repository_url:v.string(),rule_ids:v.array(v.string()).optional(),schema_version:v.string(),powerlint_version:v.string()}),Bt=v.object({rules:v.array(v.object({id:v.string(),internalId:v.string(),internalVersionId:v.string(),message:v.string(),prompt:v.string(),severity:v.enum(["suggestion","violation"]),schema:v.any(),execution:v.enum(["llm","deterministic"]).optional()}))}),Wt=v.object({rule:v.object({internalId:v.string(),internalVersionId:v.string(),contents:v.string()}),matches:v.array(v.any()),messages:v.array(v.any()),tools:v.array(v.enum(["read","grep","glob"])),powerlint_version:v.string(),schema_version:v.string()}),Ut=v.discriminatedUnion("type",[v.object({type:v.literal("tool_request"),tool_calls:v.array(v.any())}),v.object({type:v.literal("violations"),violations:v.array(v.any()),skipped:v.array(v.any())})]),le=class{baseUrl;apiKey;constructor(e){this.baseUrl=e.baseUrl,this.apiKey=e.apiKey}async request(e,t,n){let i=`${this.baseUrl}${e}`,s=await(await At(async()=>{let a=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(t)});if(!a.ok){let l=await a.text();throw new Error(`Wispbit API request failed: ${a.status} ${a.statusText} - ${l}`)}return a},{retries:3,minTimeout:1e3,maxTimeout:5e3,onFailedAttempt:a=>{console.warn(`API request to ${e} failed (attempt ${a.attemptNumber}/4): ${a.message}`)}})).json();return n?n.parse(s):s}async initialize(e){let t=Ot.parse(e);return await this.request("/plv1/initialize",t,Nt)}async getRules(e){let t=Gt.parse(e);return await this.request("/plv1/get-rules",t,Bt)}async matchLLM(e){let t=Wt.parse(e);return await this.request("/plv1/llm",t,Ut)}};var be=class{config;constructor(e){this.config=e}async call(e){let t=this.config.getApiClient(),n={internalId:e.rule.internalId,internalVersionId:e.rule.internalId,contents:e.rule.prompt},i=await t.matchLLM({rule:n,matches:e.matches,messages:e.messages,tools:e.tools,powerlint_version:this.config.getLocalVersion(),schema_version:this.config.getSchemaVersion()});return i.type==="tool_request"?{type:"tool_request",tool_calls:i.tool_calls}:{type:"violations",violations:i.violations,skipped:i.skipped}}};import{readFileSync as jt}from"fs";import{dirname as Vt,join as zt}from"path";import{fileURLToPath as Kt}from"url";import qt from"latest-version";function J(){let r=Kt(import.meta.url?import.meta.url:__filename),e=Vt(r),t=zt(e,"../package.json");try{let n=jt(t,"utf8");return JSON.parse(n).version}catch{return"0.0.0"}}async function Ye(){try{return await qt("@wispbit/local")}catch{return J()}}var ce=class r{config;apiKey=null;baseUrl=null;apiClient=null;llmProvider;constructor(e,t){this.config={...e,ignoredGlobs:e.ignoredGlobs||[]},this.apiKey=e.apiKey||null,this.baseUrl=e.baseUrl||null,this.apiKey&&this.baseUrl&&(this.apiClient=new le({baseUrl:this.baseUrl,apiKey:this.apiKey})),t!=null&&t.llmProvider?this.llmProvider=t.llmProvider:this.llmProvider=new be(this)}getIgnoredGlobs(){return this.config.ignoredGlobs||[]}getApiKey(){return this.apiKey}getBaseUrl(){return this.baseUrl}getApiClient(){if(!this.apiClient)throw new Error("API client not initialized. Config must have both apiKey and baseUrl.");return this.apiClient}getLLMProvider(){return this.llmProvider}getLocalVersion(){return J()}getSchemaVersion(){return"v1"}static initializeLocal(e={}){let t=e.ignoredGlobs||[];return new r({ignoredGlobs:t},{llmProvider:e.llmProvider})}static async initialize(e,{apiKey:t,baseUrl:n}){var m;let i=n||process.env.WISPBIT_API_BASE_URL||"https://api.wispbit.com",o=t||process.env.WISPBIT_API_KEY||null;if(!o)return console.log("No API key found"),{failed:!0,error:"INVALID_API_KEY"};let s=await e.getRepositoryUrl(),l=await new le({baseUrl:i,apiKey:o}).initialize({repository_url:s,powerlint_version:J(),schema_version:"v1"});if(l.invalid_api_key)return{failed:!0,error:"INVALID_API_KEY"};if(!l.is_valid_repository)return{failed:!0,error:"INVALID_REPOSITORY"};let c=((m=l.config)==null?void 0:m.ignored_globs)||[];return new r({ignoredGlobs:c,apiKey:o,baseUrl:i})}isConfigured(){return this.getApiKey()!==null}};import{exec as Jt,execSync as Yt}from"child_process";import{existsSync as Qt,readFileSync as Xt}from"fs";import{promisify as Zt}from"util";import{createHash as Ht}from"crypto";function ue(r){return Ht("sha256").update(r).digest("hex")}var I=Zt(Jt);function Xe(){return Yt("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim()}async function Ze(r){let{stdout:e}=await I("git ls-files --ignored --exclude-standard --others",{cwd:r,maxBuffer:52428800});return e.split(`
3
+ `).filter(Boolean).map(t=>t.trim())}async function et(r,e="origin"){let{stdout:t}=await I(`git config --get remote.${e}.url`,{cwd:r});return t.trim()||null}async function tt(r,e="origin"){try{let{stdout:t}=await I(`git rev-parse --abbrev-ref ${e}/HEAD`,{cwd:r});return t.trim().split("/").pop()||null}catch{let n=["main","master"];for(let i of n)try{return await I(`git rev-parse --verify ${i}`,{cwd:r}),i}catch{}return null}}async function nt(r){let{stdout:e}=await I("git rev-parse --abbrev-ref --symbolic-full-name @{u}",{cwd:r});return e.trim()||void 0}function it(r,e){let t=`.git/refs/branch-metadata/${e}`,n=`${r}/${t}`;if(!Qt(n))return;let o=Xt(n,"utf-8").match(/"parent"\s*:\s*"([^"]+)"/);return o==null?void 0:o[1]}function G(r){return`'${r.replace(/'/g,"'\\''")}'`}function z(r){return r.map(G).join(" ")}function en(r){let e={committed:!0,staged:!0,unstaged:!0,untracked:!0};if(!r||r.length===0)return e;let t=new Set(r);return{committed:t.has("committed"),staged:t.has("staged"),unstaged:t.has("unstaged"),untracked:t.has("untracked")}}function we(r,e){if(!r)return[];let t=[],n=r.split("\0").filter(Boolean);for(let i=0;i<n.length;){let o=n[i];if(!o)break;if(o.startsWith("R")){let s=n[i+1],a=n[i+2];s&&a?(t.push({status:o,path:a,oldPath:s,source:e}),i+=3):i++}else{let s=n[i+1];s?(t.push({status:o,path:s,source:e}),i+=2):i++}}return t}function tn(r){return r?r.split("\0").filter(Boolean).map(e=>({status:"U",path:e,source:"untracked"})):[]}function nn(r){let e=new Map;for(let t of r){let n=e.get(t.path);if(!n)e.set(t.path,t);else{let i=Qe(n.source),o=Qe(t.source);o>i?e.set(t.path,t):o===i&&t.oldPath&&(n.oldPath=n.oldPath||t.oldPath)}}return Array.from(e.values())}function Qe(r){switch(r){case"untracked":return 4;case"unstaged":return 3;case"staged":return 2;case"committed":return 1}}function Ge(r){let e=new Map;if(!r)return e;let t=r.split(/^diff --git /m).filter(Boolean);for(let n of t){let s=n.split(`
4
+ `)[0].match(/a\/(.+?) b\//);if(s){let a=s[1];e.set(a,"diff --git "+n)}}return e}function rn(r){return r.status==="U"||r.status==="A"?"added":r.status==="D"?"removed":"modified"}function me(r){if(!r)return"";let e=r.split(`
5
+ `),t=[],n=!1;for(let i of e)i.startsWith("diff --git")||i.startsWith("index ")||i.startsWith("--- ")||i.startsWith("+++ ")||i.startsWith("new file mode")||i.startsWith("deleted file mode")||i.startsWith("old mode")||i.startsWith("new mode")||i.startsWith("similarity index")||i.startsWith("rename from")||i.startsWith("rename to")||i.startsWith("copy from")||i.startsWith("copy to")||i==="\"||(i.startsWith("@@")&&(n=!0),n&&t.push(i));return t.join(`
6
+ `).trimEnd()}function Pe(r){let e=r.match(/^(.+)\.\.\.(.+)$/);if(e)return[e[1],e[2],!0];let t=r.match(/^(.+)\.\.([^.].*)$/);return t?[t[1],t[2],!1]:[r,r,!1]}function sn(r){let[,e]=Pe(r);return e}async function rt(r){let{stdout:e}=await I("git rev-parse --abbrev-ref HEAD",{cwd:r});return e.trim()}function Be(r,e,t){if(!r.includes("..")||!(e.includes("staged")||e.includes("unstaged")||e.includes("untracked")))return null;let o=sn(r);return o==="HEAD"||o===t?null:`Worktree includes (staged, unstaged, untracked) require range to end at HEAD or current branch (${t}). Got: ${r}`}async function We(r){let{stdout:e}=await I("git rev-parse --abbrev-ref HEAD",{cwd:r}),t=e.trim(),n=["committed","staged","unstaged","untracked"],i=it(r,t);if(i)return{commitSelector:`${i}...${t}`,include:n};let o=await tt(r);if(o){if(t===o){let c=await nt(r).catch(()=>{});return c?{commitSelector:`${c}...HEAD`,include:n}:{commitSelector:"HEAD~1...HEAD",include:n}}let s=`origin/${o}`,{stdout:a}=await I(`git rev-parse --verify ${s}`,{cwd:r});return{commitSelector:`${a.trim()?s:o}...${t}`,include:n}}return{commitSelector:"HEAD...HEAD",include:n}}async function st(r,e){let t=e.commitSelector.trim(),n=t.includes(".."),{stdout:i}=await I("git rev-parse --abbrev-ref HEAD",{cwd:r}),o=i.trim(),{stdout:s}=await I("git rev-parse HEAD",{cwd:r}),a=s.trim(),l="",c="";if(n)l=t,c=t;else{let h=it(r,o),x=await tt(r),M=t||h||`origin/${x||"main"}`;c=M;let F=`git merge-base --fork-point ${G(M)} HEAD || git merge-base ${G(M)} HEAD`,{stdout:b}=await I(F,{cwd:r});if(l=b.trim(),!t&&o===(x||"main")){let S=await nt(r).catch(()=>{});if(S){c=S;let{stdout:U}=await I(`git merge-base --fork-point ${G(S)} HEAD || git merge-base ${G(S)} HEAD`,{cwd:r});l=U.trim()}else c="HEAD~1",l="HEAD~1"}}let u=en(e.include),m=Be(t,e.include,o);if(m)throw new Error(m);let g=[];if(u.committed)if(n){let[h,x]=Pe(t),{stdout:M}=await I(`git diff --name-status -M -z ${G(h)}..${G(x)}`,{cwd:r,maxBuffer:50*1024*1024});g.push(...we(M,"committed"))}else{let{stdout:h}=await I(`git diff --name-status -M -z ${l}..HEAD`,{cwd:r,maxBuffer:52428800});g.push(...we(h,"committed"))}if(u.staged){let{stdout:h}=await I("git diff --name-status -M -z --cached",{cwd:r,maxBuffer:52428800});g.push(...we(h,"staged"))}if(u.unstaged){let{stdout:h}=await I("git diff --name-status -M -z",{cwd:r,maxBuffer:52428800});g.push(...we(h,"unstaged"))}if(u.untracked){let{stdout:h}=await I("git ls-files --others --exclude-standard -z",{cwd:r,maxBuffer:52428800});g.push(...tn(h))}let p=nn(g),y=p.filter(h=>h.source==="committed"),D=p.filter(h=>h.source==="staged"),$=p.filter(h=>h.source==="unstaged"),A=p.filter(h=>h.source==="untracked"),k=new Map;if(y.length>0){let h=y.map(b=>b.path),x="";if(n){let[b,S]=Pe(t);x=`git diff -U0 -M ${G(b)}..${G(S)} -- ${z(h)}`}else x=`git diff -U0 -M ${l}..HEAD -- ${z(h)}`;let{stdout:M}=await I(x,{cwd:r,maxBuffer:50*1024*1024});Ge(M).forEach((b,S)=>k.set(S,me(b)))}if(D.length>0){let h=D.map(b=>b.path),x=`git diff -U0 -M --cached -- ${z(h)}`,{stdout:M}=await I(x,{cwd:r,maxBuffer:50*1024*1024});Ge(M).forEach((b,S)=>k.set(S,me(b)))}if($.length>0){let h=$.map(b=>b.path),x=`git diff -U0 -M -- ${z(h)}`,{stdout:M}=await I(x,{cwd:r,maxBuffer:50*1024*1024});Ge(M).forEach((b,S)=>k.set(S,me(b)))}for(let h of A)try{let{stdout:x}=await I(`git diff -U0 --no-index /dev/null ${G(h.path)}`,{cwd:r,maxBuffer:52428800});k.set(h.path,me(x))}catch(x){if(x.stdout)k.set(h.path,me(x.stdout));else throw x}let w=new Map;if(y.length>0){let h=y.map(b=>b.path),x="";if(n){let[b,S]=Pe(t);x=`git diff --numstat ${G(b)}..${G(S)} -- ${z(h)}`}else x=`git diff --numstat ${l}..HEAD -- ${z(h)}`;let{stdout:M}=await I(x,{cwd:r,maxBuffer:50*1024*1024}),F=M.split(`
7
+ `).filter(Boolean);for(let b of F){let S=b.split(" ");if(S.length>=3){let[U,R,O]=S;w.set(O,{additions:parseInt(U)||0,deletions:parseInt(R)||0})}}}if(D.length>0){let h=D.map(b=>b.path),x=`git diff --numstat --cached -- ${z(h)}`,{stdout:M}=await I(x,{cwd:r,maxBuffer:50*1024*1024}),F=M.split(`
8
+ `).filter(Boolean);for(let b of F){let S=b.split(" ");if(S.length>=3){let[U,R,O]=S;w.set(O,{additions:parseInt(U)||0,deletions:parseInt(R)||0})}}}if($.length>0){let h=$.map(b=>b.path),x=`git diff --numstat -- ${z(h)}`,{stdout:M}=await I(x,{cwd:r,maxBuffer:50*1024*1024}),F=M.split(`
9
+ `).filter(Boolean);for(let b of F){let S=b.split(" ");if(S.length>=3){let[U,R,O]=S;w.set(O,{additions:parseInt(U)||0,deletions:parseInt(R)||0})}}}for(let h of A){let M=(k.get(h.path)||"").split(`
10
+ `),F=0,b=0;for(let S of M)S.startsWith("+")&&!S.startsWith("+++")?F++:S.startsWith("-")&&!S.startsWith("---")&&b++;w.set(h.path,{additions:F,deletions:b})}let E=[];for(let h of p){let x=k.get(h.path)||"",M=w.get(h.path)||{additions:0,deletions:0},F=rn(h),b={filename:h.path,status:F,patch:x,additions:M.additions,deletions:M.deletions,sha:ue(x)};h.oldPath&&(b.oldFilename=h.oldPath),E.push(b)}return{files:E,currentBranch:o,currentCommit:a,diffBranch:c,diffCommit:l}}var Q=class{workspaceRoot;repositoryUrl;constructor(e){this.repositoryUrl=(e==null?void 0:e.repositoryUrl)||null,this.workspaceRoot=(e==null?void 0:e.workspaceRoot)||Xe()}getWorkspaceRoot(){return this.workspaceRoot}async getRepositoryUrl(e="origin"){if(this.repositoryUrl)return this.repositoryUrl;let t=await et(this.workspaceRoot,e);if(!t)throw new Error("Could not determine repository URL. Make sure you're in a Git repository with a remote origin.");return t}};import*as _ from"fs/promises";import on from"os";import X from"path";import an from"keyv";import{KeyvFile as ln}from"keyv-file";var Z=class{environment;cacheStore;constructor(e){this.environment=e}getStorageDirectory(){return X.join(on.homedir(),".powerlint")}getIndexDirectory(){return X.join(this.getStorageDirectory(),"indexes",ue(this.environment.getWorkspaceRoot()))}getCacheDirectory(){return X.join(this.getStorageDirectory(),"cache",ue(this.environment.getWorkspaceRoot()))}async ensureDirectory(e){await _.mkdir(e,{recursive:!0})}async getIndexFilePath(e,t){let n=this.getIndexDirectory();await this.ensureDirectory(n);let i=t||`${e.toLowerCase()}-index.scip`;return X.join(n,`${e.toLowerCase()}-${i}`)}async indexExists(e,t){let n=await this.getIndexFilePath(e,t);try{return await _.access(n),!0}catch{return!1}}async readIndex(e,t){let n=await this.getIndexFilePath(e,t);try{return await _.access(n),await _.readFile(n)}catch{return null}}async saveIndex(e,t,n){let i=await this.getIndexFilePath(e,n);await _.writeFile(i,t)}getCacheFilePath(){return X.join(this.getCacheDirectory(),"cache.json")}async purgeStorage(){let e=0,t=this.getStorageDirectory();try{await _.access(t),await _.rm(t,{recursive:!0,force:!0}),e++}catch{}return{success:!0,deletedCount:e}}async purgeCache(){let e=0,t=this.getCacheDirectory();try{await _.access(t),await _.rm(t,{recursive:!0,force:!0}),e++}catch{}return{success:!0,deletedCount:e}}getCacheStore(){if(!this.cacheStore){let e=this.getCacheDirectory();this.cacheStore=new an({store:new ln({filename:X.join(e,"cache.json")})})}return this.cacheStore}async saveCache(e,t,n){let i=this.getCacheStore(),o=`${e}:${t}`;await i.set(o,n)}async readCache(e,t){let n=this.getCacheStore(),i=`${e}:${t}`;return await n.get(i)||null}async purgeIndexes(){let e=0,t=this.getIndexDirectory();try{await _.access(t),await _.rm(t,{recursive:!0,force:!0}),e++}catch{}return{success:!0,deletedCount:e}}};var ge=class{config;environment;constructor(e,t){this.config=e,this.environment=t}async getRepositoryUrl(){let e=await this.environment.getRepositoryUrl();if(!e)throw new Error("Could not determine repository URL. Make sure you're in a Git repository with a remote origin.");return e}async loadRuleById(e){let t=await this.fetchRules([e]);if(t.length===0)throw new Error(`Rule with ID '${e}' not found`);return t[0]}async loadAllRules(){return await this.fetchRules()}async fetchRules(e){let t=await this.getRepositoryUrl();return(await this.config.getApiClient().getRules({repository_url:t,rule_ids:e,schema_version:this.config.getSchemaVersion(),powerlint_version:this.config.getLocalVersion()})).rules.map(o=>({id:o.id,internalId:o.internalId,config:{message:o.message,severity:o.severity,execution:o.execution||"llm",steps:o.schema},prompt:o.prompt,testCases:[]}))}};import{EventEmitter as cn}from"events";var N=class extends cn{rulesStartTime=0;indexingStartTimes=new Map;fileDiscoveryStartTime=0;constructor(){super(),this.setMaxListeners(20)}emit(e,t){return super.emit(e,t)}on(e,t){return super.on(e,t)}once(e,t){return super.once(e,t)}off(e,t){return super.off(e,t)}setExecutionMode(e,t){this.emit("execution:mode",{mode:e,...t})}startRules(e){this.rulesStartTime=Date.now(),this.emit("rules:start",{totalRules:e})}progressRule(e,t,n,i){let o=Math.round(e/t*100);this.emit("rules:progress",{currentRule:e,totalRules:t,ruleId:n,percentage:o,isLlm:i})}completeRules(e,t){let n=Date.now()-this.rulesStartTime;this.emit("rules:complete",{totalRules:e,totalMatches:t,executionTime:n})}startFileDiscovery(e){this.fileDiscoveryStartTime=Date.now(),this.emit("files:discovery:start",{mode:e})}fileDiscoveryProgress(e,t){this.emit("files:discovery:progress",{message:e,currentCount:t})}completeFileDiscovery(e,t){let n=Date.now()-this.fileDiscoveryStartTime;this.emit("files:discovery:complete",{totalFiles:e,mode:t,executionTime:n})}fileFilter(e,t,n){this.emit("files:filter",{originalCount:e,filteredCount:t,filterType:n})}startScipMatchLookup(e,t){this.emit("scip:match-lookup:start",{language:e,match:t})}scipMatchLookupProgress(e,t){this.emit("scip:match-lookup:progress",{language:e,document:t})}scipMatchLookupComplete(e,t){this.emit("scip:match-lookup:complete",{language:e,document:t})}startIndexing(e){this.indexingStartTimes.set(e,Date.now()),this.emit("indexing:start",{language:e})}indexingProgress(e,t,n,i){this.emit("indexing:progress",{language:e,message:t,packageName:n,timeMs:i})}completeIndexing(e){let t=this.indexingStartTimes.get(e)||Date.now(),n=Date.now()-t;this.indexingStartTimes.delete(e),this.emit("indexing:complete",{language:e,executionTime:n})}startStep(e,t,n,i,o){this.emit("step:start",{ruleId:e,stepName:t,stepType:n,inputs:i,parentStepName:o})}completeStep(e,t,n,i,o=0,s){this.emit("step:complete",{ruleId:e,stepName:t,stepType:n,outputs:i,executionTime:o,parentStepName:s})}startTest(e,t){this.emit("test:start",{ruleId:e,testName:t})}testMatches(e,t,n){this.emit("test:matches",{ruleId:e,testName:t,matches:n})}completeTest(e,t,n,i){this.emit("test:complete",{ruleId:e,testName:t,matches:n,stepExecutions:i})}startLLMValidation(e,t,n){this.emit("llm:validation:start",{ruleId:e,matchCount:t,estimatedCost:n})}llmValidationProgress(e,t,n,i){this.emit("llm:validation:progress",{ruleId:e,tokenCount:t,elapsedTime:n,streamedText:i})}completeLLMValidation(e,t,n,i,o){this.emit("llm:validation:complete",{ruleId:e,tokenCount:t,executionTime:n,violationCount:i,fromCache:o})}};import{cpus as pi}from"os";import*as ee from"fs";import*as j from"path";import{glob as je}from"glob";function Ue(r,e){let{changedFiles:t}=e;return t.some(n=>r===n||r.endsWith(n)||n.endsWith(r))}function un(r,e,t,n){let{fileChangeMap:i}=n,o=i.get(r);return o?o.status==="added"?!0:o.status==="removed"?!1:mn(o.patch,e,t):!1}function ot(r,e){return Ue(r.filePath,e)&&un(r.filePath,r.range.start.line,r.range.end.line,e)}function mn(r,e,t){let n=r.split(`
11
+ `),i=0,o=!1;for(let s of n){let a=s.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);if(a){i=parseInt(a[1],10),o=!0;continue}if(o)if(s.startsWith("+")){if(i>=e&&i<=t)return!0;i++}else{if(s.startsWith("-"))continue;if(!s.startsWith("\\")){if(i>=e&&i<=t)return!0;i++}}}return!1}import gn from"ignore";function K(r,e){return e.length===0?!1:gn().add(e).ignores(r)}var Se=class r{environment;_filePaths=[];mode;eventEmitter;config;diffMode;constructor(e,t,n,i){this.environment=t,this.mode=n,this.eventEmitter=i||new N,this.config=e}static async initialize(e,t,n,i,o,s){let a=new r(e,t,n,i);if(n==="diff"&&!o){let u=a.environment.getWorkspaceRoot(),m=s==null?void 0:s.include,g=s==null?void 0:s.commitSelector;if(!g||!m)throw new Error("Commit selector and include are required in diff mode");let p=await st(u,{include:m,commitSelector:g}).catch(()=>{throw new Error("Diff mode requires a git repository. Please run this command from within a git repository.")});a.diffMode={gitChanges:p,changedFiles:p.files.map(y=>y.filename),fileChangeMap:new Map(p.files.map(y=>[y.filename,y]))},a.eventEmitter.fileDiscoveryProgress(`Found ${a.diffMode.changedFiles.length} changed files`)}let l=n==="check"?await a.loadGitIgnoredFiles():new Set,c=await a.discoverFiles(o,l);return a._filePaths=c,a}get filePaths(){return this._filePaths}filterFiles(e){let t=e.length,n;return this.mode==="check"?n=e:n=e.filter(i=>this.isFileValid({filePath:i})),t!==n.length&&this.eventEmitter.fileFilter(t,n.length,`${this.mode}-mode-files`),n}filterMatches(e){let t=e.length,n=e.filter(i=>this.isMatchValid({match:i}));return t!==n.length&&this.eventEmitter.fileFilter(t,n.length,`${this.mode}-mode-matches`),n}filterMatchesByFilePaths(e,t){if(e.length===0)return e;let n=new Set(t);return e.filter(i=>n.has(i.filePath))}get executionMode(){return this.mode}async loadGitIgnoredFiles(){let e=this.environment.getWorkspaceRoot(),t=await Ze(e).catch(()=>(this.eventEmitter.fileDiscoveryProgress("Not in a git repository, skipping git-ignored files"),[])),n=new Set(t);return t.length>0&&this.eventEmitter.fileDiscoveryProgress(`Found ${t.length} git-ignored files`),n}async discoverFiles(e,t){this.eventEmitter.startFileDiscovery(this.mode);let n;if(e)n=await this.discoverFilesFromPath(e,t);else if(this.mode==="diff"){let i=this.environment.getWorkspaceRoot();n=this.diffMode.changedFiles.filter(s=>ee.existsSync(j.resolve(i,s))).map(s=>s);let o=this.config.getIgnoredGlobs();if(o.length>0){this.eventEmitter.fileDiscoveryProgress("Applying ignore patterns...");let s=n.length;n=n.filter(a=>!K(a,o)),s!==n.length&&this.eventEmitter.fileDiscoveryProgress(`Filtered out ${s-n.length} ignored files`)}}else{this.eventEmitter.fileDiscoveryProgress("Scanning workspace for files...");let i=this.environment.getWorkspaceRoot(),o=this.config.getIgnoredGlobs();n=await this.discoverAllFiles([i],o,t)}return this.eventEmitter.completeFileDiscovery(n.length,this.mode),n}async discoverFilesFromPath(e,t){let n=this.environment.getWorkspaceRoot(),i=j.resolve(n,e),o=this.config.getIgnoredGlobs();if(this.isGlobPattern(e))return this.eventEmitter.fileDiscoveryProgress(`Discovering files matching glob pattern: ${e}`),await this.discoverFilesFromGlob(e,o,t);if(!ee.existsSync(i))throw new Error(`Path not found: ${e}`);let s=ee.statSync(i);if(s.isFile())return this.eventEmitter.fileDiscoveryProgress(`Checking specific file: ${e}`),t.has(e)?(this.eventEmitter.fileDiscoveryProgress(`File ${e} is ignored by git`),[]):K(e,o)?(this.eventEmitter.fileDiscoveryProgress(`File ${e} is ignored by patterns`),[]):[e];if(s.isDirectory())return this.eventEmitter.fileDiscoveryProgress(`Discovering files in directory: ${e}`),await this.discoverFilesFromDirectory(e,o,t);throw new Error(`Path is neither a file nor directory: ${e}`)}isGlobPattern(e){return/[*?[\]{}]/.test(e)}async discoverFilesFromGlob(e,t,n){let i=this.environment.getWorkspaceRoot(),o=["**/node_modules/**","**/.git/**",...t],a=(await je(e,{cwd:i,nodir:!0,absolute:!1,ignore:o})).filter(l=>!n.has(l));return this.eventEmitter.fileDiscoveryProgress(`Found ${a.length} files matching pattern`),a}async discoverFilesFromDirectory(e,t,n){let i=this.environment.getWorkspaceRoot(),o=j.join(e,"**/*").replace(/\\/g,"/"),s=["**/node_modules/**","**/.git/**",...t],l=(await je(o,{cwd:i,nodir:!0,absolute:!1,ignore:s})).filter(c=>!n.has(c));return this.eventEmitter.fileDiscoveryProgress(`Found ${l.length} files in directory`),l}async discoverAllFiles(e,t,n){let i=[],o=this.environment.getWorkspaceRoot(),s=["**/node_modules/**","**/.git/**",...t];for(let a of e){if(!ee.statSync(a).isDirectory()){let m=j.relative(o,a),g=n.has(m),p=K(m,t);!g&&!p&&i.push(m);continue}let u=(await je("**/*",{cwd:a,nodir:!0,absolute:!1,ignore:s})).map(m=>{let g=j.resolve(a,m);return j.relative(o,g)}).filter(m=>!n.has(m));i.push(...u)}return[...new Set(i)]}isFileValid(e){if(this.mode==="check")return!0;let{filePath:t}=e;return Ue(t,{changedFiles:this.diffMode.changedFiles,fileChangeMap:this.diffMode.fileChangeMap})}isMatchValid(e){if(this.mode==="check")return!0;let{match:t}=e;return ot(t,{changedFiles:this.diffMode.changedFiles,fileChangeMap:this.diffMode.fileChangeMap})}};import*as te from"fs";import*as B from"path";var Me=class{environment;constructor(e){this.environment=e}evaluateCondition(e,t){let n=this.environment.getWorkspaceRoot(),i=B.resolve(n,t);if("fs.siblingExists"in e){let{filename:o}=e["fs.siblingExists"],s=B.dirname(i),a=B.join(s,o);return te.existsSync(a)}if("fs.ancestorHas"in e){let{filename:o}=e["fs.ancestorHas"],s=B.dirname(i),a=B.parse(s).root;for(;s!==a;){let l=B.join(s,o);if(te.existsSync(l))return!0;let c=B.dirname(s);if(c===s)break;s=c}return!1}if("fs.siblingAny"in e){let{pattern:o}=e["fs.siblingAny"],s=B.dirname(i);return te.existsSync(s)?te.readdirSync(s).some(l=>K(l,[o])):!1}return!1}evaluateConditions(e,t){let n=[];return e.all&&e.all.length>0&&n.push(e.all.every(i=>this.evaluateCondition(i,t))),e.any&&e.any.length>0&&n.push(e.any.some(i=>this.evaluateCondition(i,t))),e.not&&e.not.length>0&&n.push(!e.not.some(i=>this.evaluateCondition(i,t))),n.length===0?!0:n.every(i=>i===!0)}async execute(e,t){var s,a;let n=Date.now(),i=e;(s=t.include)!=null&&s.length&&(i=i.filter(l=>K(l,t.include))),(a=t.ignore)!=null&&a.length&&(i=i.filter(l=>!K(l,t.ignore))),Object.keys(t.conditions||{}).length>0&&i.length>0&&(i=i.filter(l=>this.evaluateConditions(t.conditions,l)));let o=Date.now()-n;return await Promise.resolve({filteredPaths:i,executionTime:o})}};import ni from"path";import{existsSync as Ve}from"fs";import{createRequire as fn}from"module";import fe from"path";import pn from"@ast-grep/lang-angular";import dn from"@ast-grep/lang-bash";import hn from"@ast-grep/lang-c";import xn from"@ast-grep/lang-cpp";import vn from"@ast-grep/lang-csharp";import yn from"@ast-grep/lang-css";import bn from"@ast-grep/lang-dart";import wn from"@ast-grep/lang-elixir";import Pn from"@ast-grep/lang-go";import Sn from"@ast-grep/lang-haskell";import Mn from"@ast-grep/lang-html";import En from"@ast-grep/lang-java";import $n from"@ast-grep/lang-javascript";import Rn from"@ast-grep/lang-json";import In from"@ast-grep/lang-kotlin";import Ln from"@ast-grep/lang-lua";import Cn from"@ast-grep/lang-markdown";import kn from"@ast-grep/lang-php";import Fn from"@ast-grep/lang-python";import Dn from"@ast-grep/lang-ruby";import Tn from"@ast-grep/lang-rust";import _n from"@ast-grep/lang-scala";import An from"@ast-grep/lang-sql";import On from"@ast-grep/lang-swift";import Nn from"@ast-grep/lang-toml";import Gn from"@ast-grep/lang-tsx";import Bn from"@ast-grep/lang-typescript";import Wn from"@ast-grep/lang-yaml";import{registerDynamicLanguage as Un}from"@ast-grep/napi";console.debug=()=>{};var jn=fn(import.meta.url?import.meta.url:__filename);function Vn(){let r=fe.dirname(jn.resolve("tree-sitter-graphql")),e=fe.join(r,"../../build/Release/tree_sitter_graphql_binding.node");if(Ve(e))return e;let t=fe.join(r,"../../build/Debug/tree_sitter_graphql_binding.node");if(Ve(t))return t;let n=fe.join(r,"parser.so");return Ve(n)?n:null}var lt=Vn(),zn={libraryPath:lt,extensions:["graphql"],languageSymbol:"tree_sitter_graphql",metaVarChar:"$",expandoChar:void 0},ct={Angular:pn,Bash:dn,C:hn,Cpp:xn,Csharp:vn,Css:yn,Dart:bn,Elixir:wn,Go:Pn,Haskell:Sn,Html:Mn,Java:En,JavaScript:$n,Json:Rn,Kotlin:In,Lua:Ln,Markdown:Cn,Php:kn,Python:Fn,Ruby:Dn,Rust:Tn,Scala:_n,Sql:An,Swift:On,Toml:Nn,Tsx:Gn,TypeScript:Bn,Yaml:Wn,...lt?{GraphQL:zn}:{}};Un(ct);var at=Object.entries(ct).reduce((r,[e,t])=>(r[e]=t.extensions??[],r),{});function ut(r){let e=fe.extname(r).slice(1);return Kn(e)??"Unknown"}function Kn(r){return Object.keys(at).find(e=>at[e].includes(r))}import{readFile as mt}from"fs/promises";import gt from"path";import{parse as qn,parseAsync as Hn}from"@ast-grep/napi";function pe(r){return{...r,range:{start:{line:r.range.start.line-1,column:r.range.start.column-1},end:{line:r.range.end.line-1,column:r.range.end.column-1}}}}function ne(r){return{...r,range:{start:{line:r.range.start.line+1,column:r.range.start.column+1},end:{line:r.range.end.line+1,column:r.range.end.column+1}}}}var Ee=class{environment;language;constructor(e,t){this.environment=e,this.language=t}async findMatches(e,t){if(e.length===0)return[];let{rule:n,constraints:i}=t,o=this.getBatchSize(),s=[];for(let a=0;a<e.length;a+=o){let l=e.slice(a,a+o),c=await this.processBatch(l,n,i);s.push(...c)}return s}getBatchSize(){let e=parseInt(process.env.UV_THREADPOOL_SIZE||"4",10);return Math.max(2,Math.floor(e/2))}async processBatch(e,t,n){let i=e.map(async l=>{let c=await mt(gt.resolve(this.environment.getWorkspaceRoot(),l),"utf-8");return(await Hn(this.language,c)).root().findAll({rule:t,language:this.language,constraints:n}).map(g=>({filePath:l,sgNode:g}))}),s=(await Promise.all(i)).flat(),a=[];for(let{filePath:l,sgNode:c}of s){let u=c.range(),m={filePath:l,text:c.text(),range:{start:{line:u.start.line,column:u.start.column},end:{line:u.end.line,column:u.end.column}},symbol:this.extractSymbol(c),language:this.language};a.push(ne(m))}return a}extractSymbol(e){let t=e.kind();if(t==="call_expression"){let n=e.field("function");if(n)return this.extractSymbol(n)}else if(t==="member_expression"){let n=e.field("property");if(n)return n.text()}else if(t==="identifier")return e.text()}async expandMatch(e){let t=new Set(["method_definition","function_declaration","function_expression","arrow_function","class_declaration","export_statement","lexical_declaration"]),n=gt.resolve(this.environment.getWorkspaceRoot(),e.filePath),i=await mt(n,"utf-8"),o=qn(this.language,i).root(),s=pe(e),a=this.findNodeAtPosition(o,s.range.start.line,s.range.start.column);if(!a)return null;let l=a;for(;l;){let c=l.kind(),u=typeof c=="string"?c:String(c);if(t.has(u)){let m=l.range(),g={filePath:e.filePath,text:l.text(),range:{start:{line:m.start.line,column:m.start.column},end:{line:m.end.line,column:m.end.column}},symbol:e.symbol,language:this.language};return ne(g)}l=l.parent()}return null}findNodeAtPosition(e,t,n){let i=e.range(),o=t>i.start.line||t===i.start.line&&n>=i.start.column,s=t<i.end.line||t===i.end.line&&n<=i.end.column;if(!o||!s)return null;let a=e.children();for(let l of a){let c=this.findNodeAtPosition(l,t,n);if(c)return c}return e}};import{spawn as Qn}from"child_process";import Ke from"fs/promises";import{createRequire as Xn}from"module";import Zn from"os";import pt from"path";import{readFile as Jn}from"fs/promises";import Yn from"path";async function ze(r,e,t){let n=Yn.resolve(r,e),o=(await Jn(n,"utf-8")).split(`
12
+ `),{start:s,end:a}=t;if(s.line===a.line)return(o[s.line]||"").substring(s.column,a.column);let l=[];for(let c=s.line;c<=a.line&&c<o.length;c++){let u=o[c];c===s.line?l.push(u.substring(s.column)):c===a.line?l.push(u.substring(0,a.column)):l.push(u)}return l.join(`
13
+ `)}var ei=Xn(import.meta.url?import.meta.url:__filename),{scip:$e}=ei("@sourcegraph/scip-root/bindings/typescript/scip.js"),ft=/^\+ (.+?) \((\d+)ms\)$/;function ti(r,e,t,n,i){let o=r.toString();e+=o;let s=e.split(`
14
+ `),a=s.pop()||"",l=null;for(let c of s){let u=c.trim();u.match(ft)&&(l=u)}for(let c of s){let u=c.trim();if(!u)continue;let m=u.match(ft);if(m){if(u===l){let[,g,p]=m,y=pt.relative(i,g);t.indexingProgress(n,`Indexed ${y}`,y,parseInt(p,10))}}else t.indexingProgress(n,u)}return a}var Re=class{environment;storage;language;scipIndex=null;indexingPromise=null;eventEmitter;constructor(e,t,n){this.environment=e,this.storage=new Z(e),this.language=t,this.eventEmitter=n||new N}getScipCommand(){switch(this.language){case"TypeScript":case"JavaScript":case"Tsx":return"scip-typescript";case"Python":return"scip-python";default:throw new Error(`SCIP is not supported for language: ${this.language}`)}}async ensureIndexReady(){if(this.scipIndex===null){if(this.indexingPromise!==null)return this.indexingPromise;this.indexingPromise=this.startIndexing(),await this.indexingPromise}}async startIndexing(){if(await this.storage.indexExists(this.language,"index.scip")){await this.loadIndex();return}let e=await this.storage.getIndexFilePath(this.language,"index.scip"),t=pt.join(Zn.tmpdir(),`scip-index-${this.language}-${Date.now()}.tmp`);this.eventEmitter.startIndexing(this.language);let n=this.getScipCommand(),i=Qn(n,["index","--output",t],{cwd:this.environment.getWorkspaceRoot(),stdio:["ignore","pipe","pipe"],env:process.env}),o="";i.stdout.on("data",a=>{o=ti(a,o,this.eventEmitter,this.language,this.environment.getWorkspaceRoot())});let s="";i.stderr.on("data",a=>{s+=a.toString(),process.stderr.write(a)}),await new Promise((a,l)=>{i.on("close",async c=>{c===0?(await Ke.rename(t,e),a()):(await Ke.unlink(t).catch(()=>{}),l(new Error(`${n} index exited with code ${c}, stderr: ${s}`)))}),i.on("error",async c=>{await Ke.unlink(t).catch(()=>{}),l(c)})}),s&&console.error("SCIP indexing stderr:",s),this.eventEmitter.completeIndexing(this.language),await this.loadIndex()}async findDefinitions(e){if(await this.ensureIndexReady(),!this.scipIndex)throw new Error("SCIP index not ready");let t=pe(e);this.eventEmitter.startScipMatchLookup(this.language,t);let n=[],i=this.scipIndex.documents;for(let o of i){if(o.relative_path!==t.filePath)continue;this.eventEmitter.scipMatchLookupProgress(this.language,o);let s=this.findBestOverlappingOccurrence(o.occurrences,t);if(s){if(this.isExternalSymbol(s.symbol))continue;this.eventEmitter.scipMatchLookupProgress(this.language,s);let a=await this.findDefinitionForSymbol(s.symbol,i);a&&n.push(ne(a))}}return n}async findReferences(e){if(await this.ensureIndexReady(),!this.scipIndex)return[];let t=pe(e),n=[],i=this.scipIndex.documents;for(let o of i){if(o.relative_path!==t.filePath)continue;let s=this.findBestOverlappingOccurrence(o.occurrences,t);if(s){if(this.isExternalSymbol(s.symbol))break;let a=await this.findReferencesForSymbol(s.symbol,i);n.push(...a.map(l=>ne(l)));break}}return n}isExternalSymbol(e){return e.includes("node_modules")||e.startsWith("npm/")}findBestOverlappingOccurrence(e,t){let n=[];for(let i of e){let o=i.range;if(!o||o.length<3||!this.rangesOverlap(o,t))continue;let s=0;if(!this.rangeFullyInside(o,t))continue;let a=(o.length===3,o[1]);if(s+=a*1e3,i.symbol_roles!==$e.SymbolRole.Definition&&(s+=100),t.symbol){let{className:l,methodName:c}=this.extractSymbolNames(i.symbol);(c===t.symbol||l===t.symbol)&&(s+=1e4)}n.push({occurrence:i,score:s})}return n.length===0?null:(n.sort((i,o)=>o.score-i.score),n[0].occurrence)}rangesOverlap(e,t){if(e.length===3){let[n,i,o]=e;return t.range.start.line===t.range.end.line?n===t.range.start.line&&!(o<=t.range.start.column||i>=t.range.end.column):!(n<t.range.start.line||n>t.range.end.line||n===t.range.start.line&&o<=t.range.start.column||n===t.range.end.line&&i>=t.range.end.column)}else if(e.length===4){let[n,i,o,s]=e;return!(o<t.range.start.line||n>t.range.end.line)}return!1}rangeFullyInside(e,t){if(e.length===3){let[n,i,o]=e;return!(n<t.range.start.line||n>t.range.end.line||n===t.range.start.line&&i<t.range.start.column||n===t.range.end.line&&o>t.range.end.column)}else if(e.length===4){let[n,i,o,s]=e;return!(n<t.range.start.line||o>t.range.end.line||n===t.range.start.line&&i<t.range.start.column||o===t.range.end.line&&s>t.range.end.column)}return!1}extractSymbolNames(e){let t=e.match(/`([^`]+)`#([^#./`(]+)(?:\(\))?[.`]*$/);if(t)return{className:t[1],methodName:t[2]};let n=e.match(/([^#./`(]+)(?:\(\))?[.`]*$/);return n?{className:null,methodName:n[1]}:{className:null,methodName:null}}async findDefinitionForSymbol(e,t){for(let n of t)for(let i of n.occurrences)if(i.symbol===e&&i.symbol_roles===$e.SymbolRole.Definition){let o=i.range??[],s=o[0]??0,a=o[1]??0,l=o.length===4?o[2]:s,c=o.length===4?o[3]:o[2]??a,u=await ze(this.environment.getWorkspaceRoot(),n.relative_path,{start:{line:s,column:a},end:{line:l,column:c}});return{language:this.language,filePath:n.relative_path,range:{start:{line:s,column:a},end:{line:l,column:c}},symbol:e,text:u}}return null}async findReferencesForSymbol(e,t){let n=[];for(let i of t)for(let o of i.occurrences)if(o.symbol===e&&o.symbol_roles!==$e.SymbolRole.Definition){let s=o.range??[],a=s[0]??0,l=s[1]??0,c=s.length===4?s[2]:a,u=s.length===4?s[3]:s[2]??l,m=await ze(this.environment.getWorkspaceRoot(),i.relative_path,{start:{line:a,column:l},end:{line:c,column:u}});n.push({language:this.language,filePath:i.relative_path,range:{start:{line:a,column:l},end:{line:c,column:u}},symbol:e,text:m})}return n}async loadIndex(){let e=await this.storage.readIndex(this.language,"index.scip");if(!e)throw new Error(`Index not found for language: ${this.language}`);this.scipIndex=this.parseIndex(new Uint8Array(e))}parseIndex(e){return $e.Index.deserialize(e)}};var ie=class extends Error{constructor(e,t){super(`${e} is not supported: ${t}`),this.name="LanguageBackendNotSupportedError"}},re=class{astProvider;intelligenceProvider;language;constructor(e,t,n){this.language=t,this.astProvider=new Ee(e,t),this.intelligenceProvider=new Re(e,t,n)}async findMatches(e,t){if(!this.astProvider)throw new ie("findMatches","no AST provider configured for this language");return await this.astProvider.findMatches(e,t)}async findDefinitions(e){if(!this.intelligenceProvider)throw new ie("findDefinitions","no intelligence provider configured for this language");return await this.intelligenceProvider.findDefinitions(e)}async findReferences(e){if(!this.intelligenceProvider)throw new ie("findReferences","no intelligence provider configured for this language");return await this.intelligenceProvider.findReferences(e)}async expandMatch(e){if(!this.astProvider)throw new ie("expandMatch","no AST provider configured for this language");return await this.astProvider.expandMatch(e)}};var Ie=class{environment;eventEmitter;constructor(e,t){this.environment=e,this.eventEmitter=t}async execute(e,t,n){let i=Date.now(),o=n.filePaths.filter(m=>ut(m)===t),l=(await new re(this.environment,t,this.eventEmitter).findMatches(o,e)).map(m=>{if(ni.isAbsolute(m.filePath))throw new Error(`Match provider returned absolute path: ${m.filePath}. All file paths must be relative to workspace root.`);return{...m,language:t}}),c=n.previousMatches&&n.previousMatches.length>0?this.filterMatchesByRanges(l,n.previousMatches,t):l;c=this.deduplicateMatches(c);let u=Date.now()-i;return{matches:c,totalMatches:c.length,executionTime:u}}filterMatchesByRanges(e,t,n){let i=[];for(let o of e){let s=t.find(l=>this.isMatchInRange(o,l));if(!s)continue;let a=[...s.evidence||[],{filePath:s.filePath,text:s.text,range:s.range,symbol:s.symbol,language:s.language}];i.push({...o,evidence:a})}return i}isMatchInRange(e,t){if(e.filePath!==t.filePath)return!1;let n=e.range.start,i=e.range.end,o=t.range.start,s=t.range.end,a=n.line>o.line||n.line===o.line&&n.column>=o.column,l=i.line<s.line||i.line===s.line&&i.column<=s.column;return a&&l}deduplicateMatches(e){if(e.length<=1)return e;let t=[];for(let n of e)e.some(o=>n===o?!1:this.isMatchInRange(n,o))||t.push(n);return t}};import ii from"path";import ri from"ignore";var Le=class{maxDepth=1;environment;eventEmitter;constructor(e,t){this.environment=e,this.eventEmitter=t||new N}async execute(e,t,n={}){let i=Date.now(),o=new re(this.environment,t,this.eventEmitter),s=await this.followDefinitions(e,o,this.maxDepth,n.where,t),a=Date.now()-i,l=s.map(c=>{let{depth:u,sourceMatch:m,...g}=c,p=m?[{filePath:m.filePath,text:m.text,range:m.range,symbol:m.symbol,language:m.language},...m.evidence||[]]:[];return{...g,source:p}});return{definitions:l,totalDefinitions:l.length,executionTime:a}}async followDefinitions(e,t,n,i,o){let s=[],a=new Set;for(let l of e)await this.followDefinitionsRecursive(l,t,0,n,i,s,a,l,o);return s}async followDefinitionsRecursive(e,t,n,i,o,s,a,l,c){if(n>=i)return;let u=`${e.filePath}:${e.range.start.line}:${e.range.start.column}:${e.symbol??""}`;if(a.has(u))return;a.add(u);let m=await t.findDefinitions(e);for(let g of m){let y={...await t.expandMatch(g)??g,language:c,depth:n,sourceMatch:l||e};o&&!this.matchesDefinitionFilter(y,o)||(s.push(y),n+1<i&&await this.followDefinitionsRecursive(g,t,n+1,i,o,s,a,l||e,c))}}matchesDefinitionFilter(e,t){var n,i,o;if(t.all)return t.all.every(s=>this.matchesDefinitionFilter(e,s));if(t.any)return t.any.some(s=>this.matchesDefinitionFilter(e,s));if(t.not)return!this.matchesDefinitionFilter(e,t.not);if(t.file&&!this.matchesFileSpec(e.filePath,t.file))return!1;if((n=t.method)!=null&&n.name){let s=e.symbol;if(!s||!this.matchesNameSpec(s,t.method.name))return!1}if((i=t.function)!=null&&i.name){let s=e.symbol;if(!s||!this.matchesNameSpec(s,t.function.name))return!1}return(o=t.class)!=null&&o.name,!0}matchesNameSpec(e,t){return t.equals?e===t.equals:t.anyOf?t.anyOf.includes(e):t.regex?new RegExp(t.regex).test(e):!0}matchesFileSpec(e,t){let n=ii.relative(this.environment.getWorkspaceRoot(),e);return t.path?n===t.path:t.glob?ri().add(t.glob).ignores(n):t.regex?new RegExp(t.regex).test(n):!0}};import{spawn as ci}from"child_process";import he from"fs";import V,{dirname as vt,join as yt}from"path";import{glob as ui}from"glob";import{promises as si}from"fs";import Ws from"path";async function Ce(r){try{return await si.access(r),!0}catch{return!1}}import{execSync as oi}from"child_process";function dt(){return oi("which rg").toString().trim()}import ke from"fs";import ai from"path";async function ht(r){try{if(r.includes("/")||r.includes("\\"))return await ke.promises.access(r,ke.constants.X_OK),!0;let e=process.env.PATH||"",t=process.platform==="win32"?";":":",n=e.split(t),i=process.platform==="win32"?(process.env.PATHEXT||".exe;.cmd;.bat").split(";"):[""];for(let o of n)for(let s of i){let a=ai.join(o,r+s);try{return await ke.promises.access(a,ke.constants.X_OK),!0}catch{}}return!1}catch{return!1}}import li from"ignore";function xt(r){let e=r.match(/\{([^}]+)\}/);if(!e)return[r];let t=e[1].split(","),n=r.slice(0,e.index),i=r.slice(e.index+e[0].length),o=[];for(let s of t){let a=n+s+i;o.push(...xt(a))}return o}function de(r,e){if(e.length===0)return!1;let t=[];for(let i of e)t.push(...xt(i));return li().add(t).ignores(r)}function Fe(r,e){let t=new RegExp(e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");return r.replace(t,"").replace(/\/\//g,"/")}function De(r,e){try{let t=V.resolve(r),n=V.resolve(r,e),i=V.normalize(t),o=V.normalize(n);return o===i||o.startsWith(i+V.sep)?o:null}catch{return null}}function mi(r,e,t){try{let i=he.readFileSync(r,"utf8").split(`
15
+ `),o=Math.max(0,e-1),s=Math.min(i.length,t);if(o===0&&s>=i.length)return i.map((u,m)=>`L${m+1} ${u}`).join(`
16
+ `);let a=i.slice(o,s),l="";o>0&&(l+=`[Lines 1-${o} omitted]
17
+ `);let c=a.map((u,m)=>`L${o+m+1} ${u}`);return l+=c.join(`
18
+ `),s<i.length&&(l+=`
19
+ [Lines ${s+1}-${i.length} omitted]`),l}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(`Failed to read file ${r}: ${i}`)}}async function bt(r,e,t=[]){var l,c;let{target_file:n}=r,i=r.offset?parseInt((l=r.offset)==null?void 0:l.toString(),10):void 0,o=r.limit?parseInt((c=r.limit)==null?void 0:c.toString(),10):void 0,s=n.startsWith("/")?n.slice(1):n;if(de(s,t))return{error:`File not found or not accessible: ${n}. Please provide the correct path.`};let a=De(e,s);if(!a)return{error:`Invalid file path: '${n}'`};if(!await Ce(a))return{error:`File not found or not accessible: ${n}. Please provide the correct path. It could also have been deleted as part of the PR.`};try{if(i!==void 0&&(Number.isNaN(Number(i))||i<0))return{error:`Invalid offset: ${i}. Offset must be a non-negative number starting from 0.`};if(o!==void 0&&(Number.isNaN(Number(o))||o<1))return{error:`Invalid limit: ${o}. Limit must be a positive number.`};let u=i??1,g=Math.min(o??200,500),p=u+g-1;return{content:mi(a,u,p)}}catch(u){let m=u.message||String(u);return{error:Fe(`Error reading file: ${m}`,e)}}}async function gi(r,e,t,n="."){let i=["--no-config","--line-number","--color=never","--max-columns=300","--max-filesize=1M","--max-count=50","-i"];t&&i.push("-g",t),i.push(e),i.push(n);try{let o=await new Promise((l,c)=>{var y,D;let u=ci(r,i,{cwd:n,shell:!1,stdio:["ignore","pipe","pipe"],env:{LANG:"C",RIPGREP_CONFIG_PATH:""}}),m="",g="";(y=u.stdout)==null||y.on("data",$=>{m+=$.toString()}),(D=u.stderr)==null||D.on("data",$=>{g+=$.toString()});let p=setTimeout(()=>{u.kill("SIGTERM"),c(new Error("rg search operation timed out"))},3e4);u.on("close",$=>{clearTimeout(p),$===1&&!g?l({stdout:"",stderr:""}):$===0||$===1&&!g?l({stdout:m,stderr:g}):c(new Error(`ripgrep exited with code ${$}: ${g}`))}),u.on("error",$=>{clearTimeout(p),c($)})});if(!o.stdout.trim())return[];let s=o.stdout.trim().split(`
20
+ `),a=[];for(let l of s){let c=l.match(/^([^:]+):(\d+):(.*)$/);if(c){let[,u,m,g]=c,p=u;p=V.relative(n,u);let y=V.join(n,p);try{he.existsSync(y)&&a.push({file:p,line_number:parseInt(m,10),content:g})}catch{}}}return a}catch(o){let s=o;if(s.code===1&&!s.stderr)return[];throw s.signal==="SIGTERM"?new Error("rg search operation timed out"):o}}async function wt(r,e,t=[]){var a;let{pattern:n,include:i}=r,o=dt(),s=(a=r.path)!=null&&a.startsWith("/")?r.path.slice(1):r.path;try{if(!await ht(o))throw new Error(`ripgrep is not found or not executable: ${o}`);let c=null;if(s){let g=De(e,s);if(!g)return{error:`Invalid path: '${s}'. Please provide the correct path.`};if(!await Ce(g))return{error:`Path not found or not accessible: ${s}. Please provide the correct path.`};let p=await he.promises.stat(g);if(p.isFile())c=vt(g),s=vt(s);else if(p.isDirectory())c=g;else return{error:`Path is neither a file nor a directory: ${s}`}}return{matches:(await gi(o,n,i,c||e)).filter(g=>{let p=s?yt(s,g.file):g.file;return!de(p,t)}).slice(0,50).map(g=>({...g,file:s?yt(s,g.file):g.file}))}}catch(l){let c=typeof l=="object"&&l!==null&&"message"in l&&typeof l.message=="string"?l.message:String(l);return{error:Fe(c,e)}}}async function Pt(r,e,t=[]){let n=r.pattern,i=r.path;i&&n.startsWith(i)&&(n=n.replace(i,"")),n=n.startsWith("/")?n.slice(1):n;let o;if(i){if(i=i.startsWith("/")?i.slice(1):i,i!=="."&&de(i,t))return{files:[]};let s=De(e,i);if(!s)return{error:`Path not found or not accessible: '${i}'. Please provide the correct path.`};o=s}else o=e;if(!await Ce(o))return{error:`Directory not found or not accessible: ${i||"."}. Please provide the correct path.`};try{if(!(await he.promises.stat(o)).isDirectory())return{error:`Not a directory: ${i||"."}. Please provide the correct path.`}}catch{return{error:`Directory not found or not accessible: ${i||"."}. Please provide the correct path.`}}try{return{files:(await fi(n,i,e)).filter(l=>!de(l,t)).slice(0,50)}}catch(s){let a=typeof s=="object"&&s!==null&&"message"in s&&typeof s.message=="string"?s.message:String(s);return{error:Fe(a,e)}}}async function fi(r,e,t){try{let n;if(e){let s=e.startsWith("/")?e.slice(1):e,a=De(t,s);if(!a)throw new Error(`Invalid directory path: potential directory traversal detected in '${s}'`);n=a}else n=t;let i=await ui(r,{cwd:n,nodir:!0,ignore:["node_modules/**",".git/**","**/__pycache__/**"],absolute:!1}),o=[];for(let s of i)try{let a=V.join(n,s),l=await he.promises.stat(a);l.isFile()&&o.push({path:e?V.join(e,s):s,mtime:l.mtime})}catch{}return o.sort((s,a)=>a.mtime.getTime()-s.mtime.getTime()),o.map(s=>s.path)}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(Fe(`Glob search failed: ${i}`,t))}}var Te=class{environment;eventEmitter;config;maxIterations;constructor(e,t,n,i){this.environment=t,this.eventEmitter=n||new N,this.config=e,this.maxIterations=i||20}async executeTool(e,t){switch(e){case"read":{let n=await bt(t,this.environment.getWorkspaceRoot());return"error"in n?n:n.content}case"grep":{let n=await wt(t,this.environment.getWorkspaceRoot());return"error"in n?n:JSON.stringify(n.matches)}case"glob":{let n=await Pt(t,this.environment.getWorkspaceRoot());return"error"in n?n:JSON.stringify(n.files)}default:return{error:`Unknown tool: ${e}`}}}async executeLLMValidation(e,t,n){let i=Date.now(),o=this.config.getLLMProvider();this.eventEmitter.startLLMValidation(e.id,t.length);let s=[];for(let a=0;a<this.maxIterations;a++){let l=await o.call({rule:e,matches:t,tools:n,messages:s});if(l.type==="violations"){let u=l.violations.map(m=>m.match).map(m=>({...m,metadata:{llmValidation:{isViolation:!0,confidence:1,reason:"Confirmed violation"}}}));return this.eventEmitter.completeLLMValidation(e.id,0,Date.now()-i,u.length,!1),{matches:u,executionTime:Date.now()-i}}if(l.type==="tool_request"){s.push({type:"tool_request",tool_calls:l.tool_calls});let c=await Promise.all(l.tool_calls.map(async u=>{let m=JSON.parse(u.function.arguments),g=await this.executeTool(u.function.name,m),p=typeof g=="string"?g:JSON.stringify(g);return{tool_call_id:u.id,content:p}}));for(let u of c)s.push({type:"tool_response",tool_call_id:u.tool_call_id,content:u.content});continue}}throw new Error(`Max iterations (${this.maxIterations}) reached without getting violations`)}async execute(e,t,n){return t.length===0?{matches:[],executionTime:0}:await this.executeLLMValidation(e,t,n)}};var _e=class{fileFilterStep;findMatchesStep;gotoDefinitionStep;llmStep;environment;executionContext;currentMode;eventEmitter;config;constructor(e,t,n){this.environment=t,this.eventEmitter=n||new N,this.config=e,this.fileFilterStep=new Me(this.environment),this.findMatchesStep=new Ie(this.environment,this.eventEmitter),this.gotoDefinitionStep=new Le(this.environment,this.eventEmitter),this.llmStep=new Te(this.config,this.environment,this.eventEmitter)}async executeStep(e,t,n,i,o,s,a){let l=n.type;if(l==="ast-grep"){let{type:c,language:u,...m}=n;return{matches:(await this.findMatchesStep.execute(m,u,{filePaths:i,previousMatches:o})).matches}}if(l==="step-group")throw new Error("step-group step is disabled");if(l==="file-filter")return{filePaths:(await this.fileFilterStep.execute(i,{include:n.include,ignore:n.ignore,conditions:n.conditions})).filteredPaths};if(l==="goto-definition")throw new Error("goto-definition step is disabled");if(l==="llm")return o.length===0?{matches:[]}:{matches:(await this.llmStep.execute(e,o,n.tools??[])).matches};throw new Error(`Unknown step type: ${l}`)}async executeRule(e,t){let n=e.id,i=e.config,o=[...this.executionContext.filePaths],s=[];for(let l of i.steps){let c=l.id,u=l.step;this.eventEmitter.startStep(n,c,u.type,{filePaths:o,matches:s});let m=Date.now(),g=await this.executeStep(e,c,u,o,s,t),p=Date.now()-m;if(this.eventEmitter.completeStep(n,c,u.type,g,p),g.filePaths!==void 0&&(o=this.executionContext.filterFiles(g.filePaths),s=this.executionContext.filterMatchesByFilePaths(s,o)),g.matches!==void 0&&(s=this.executionContext.filterMatches(g.matches)),o.length===0)break}let a=s.sort((l,c)=>l.filePath.localeCompare(c.filePath));return{ruleId:n,matches:a}}getPoolSize(){let e=pi().length;return Math.max(2,Math.min(8,e))}async execute(e,t){if(this.currentMode&&this.currentMode!==t.mode)throw new Error(`Execution mode mismatch: expected ${this.currentMode}, got ${t.mode}`);(!this.executionContext||this.currentMode!==t.mode)&&(this.executionContext=await Se.initialize(this.config,this.environment,t.mode,this.eventEmitter,t.filePath,t.diffOptions),this.currentMode=t.mode),this.eventEmitter.startRules(e.length);let n=this.getPoolSize(),i=[],o=[...e],s=async u=>{let m=u.id,g=!1;for(let y of u.config.steps)if(y.step.type==="llm"){g=!0;break}this.eventEmitter.emit("rules:rule-start",{ruleId:m,isLlm:g,message:u.config.message,severity:u.config.severity});let p=await this.executeRule(u,t);return this.eventEmitter.emit("rules:rule-complete",{ruleId:m}),p};await(async()=>{let u=async()=>{if(o.length===0)return;let g=o.shift(),p=await s(g);i.push(p),await u()},m=[];for(let g=0;g<Math.min(n,e.length);g++)m.push(u());await Promise.all(m)})();let l=new Map(e.map((u,m)=>[u.id,m]));i.sort((u,m)=>{let g=l.get(u.ruleId)??0,p=l.get(m.ruleId)??0;return g-p});let c=i.reduce((u,m)=>u+m.matches.length,0);return this.eventEmitter.completeRules(e.length,c),i}};var Ae=class extends Error{constructor(t,n,i){super(`Invalid rule format in '${t}': ${n}`);this.validationErrors=i;this.name="InvalidRuleFormatError"}};import{stripVTControlCharacters as Mt}from"node:util";import f from"chalk";import di from"ora";var q="#f87171",H="#60a5fa",hi="#9b59b6",C="#fbbf24",qe="#9ca3af",ve="#9b59b6",Oe="\u25A0",xe="\u25CF",ye="\u2B25";function Et(r,e){if(e){let t=`https://app.wispbit.com/rules/${e}`;return f.dim.underline(`\x1B]8;;${t}\x1B\\${r}\x1B]8;;\x1B\\`)}return f.dim.underline(r)}function xi(r,e){return r.length<=e?r:r.substring(0,e-3)+"..."}var vi="#f8f8f8",yi="#888888",bi=[f.hex(C)("~(oo)~"),f.hex(C)("~(oO)~"),f.hex(C)("~(Oo)~"),f.hex(C)("~(OO)~"),f.hex(C)("~(\u25CFo)~"),f.hex(C)("~(o\u25CF)~"),f.hex(C)("~(\u25C9o)~"),f.hex(C)("~(o\u25C9)~"),f.hex(C)("\\(oo)/"),f.hex(C)("\\(oO)/"),f.hex(C)("\\(Oo)/"),f.hex(C)("\\(OO)/"),f.hex(C)("~(Oo)~"),f.hex(C)("~(oO)~"),f.hex(C)("~(\u25CEo)~"),f.hex(C)("~(o\u25CE)~"),f.hex(C)("~(oo)~"),f.hex(C)("~(OO)~"),f.hex(C)("~(Oo)~"),f.hex(C)("\\(oo)/"),f.hex(C)("\\(oO)/"),f.hex(C)("\\(Oo)/"),f.hex(C)("\\(OO)/")];function St(r,e){return e===1?r:`${r}s`}function wi(r,e={}){let n=e.align||[],i=e.stringLength||(s=>Mt(s).length),o=r.reduce((s,a)=>(a.forEach((l,c)=>{let u=i(l);(!s[c]||u>s[c])&&(s[c]=u)}),s),[]);return r.map(s=>s.map((a,l)=>{let c=o[l]-i(a)||0,u=Array(Math.max(c+1,1)).join(" ");return n[l]==="r"?u+a:a+u}).join(" ").trimEnd()).join(`
21
+ `)}function $t(r,e){let t=`
22
+ `,n=0,i=0,o=new Map;r.forEach(y=>{if(y.matches.length===0)return;let D=y.ruleId||"unknown";o.has(D)||o.set(D,{message:y.message,severity:y.severity,internalId:y.internalId,matches:[],hasLlmValidation:!1});let $=o.get(D);y.matches.forEach(A=>{var k;$.matches.push({filePath:A.filePath,line:A.range.start.line,column:A.range.start.column,endLine:A.range.end.line!==A.range.start.line?A.range.end.line:void 0,text:A.text}),(k=A.metadata)!=null&&k.llmValidation&&($.hasLlmValidation=!0),y.severity==="violation"?n++:i++})}),o.forEach((y,D)=>{if(y.matches.length===0&&y.severity!=="skipped")return;let $;y.severity==="violation"?$=f.hex(q)(Oe):y.severity==="suggestion"?$=f.hex(H)(xe):$=f.hex(hi)(xe);let A=y.hasLlmValidation?`${f.hex(ve)(ye)} `:"",w=`${Et(D,y.internalId)} ${$} ${A}${y.message.replace(/([^ ])\.$/u,"$1")}`;if(t+=`${w}
23
+ `,t+=`${f.dim("\u2500".repeat(80))}
24
+ `,y.severity==="skipped")t+=`
25
+ `;else{let E=y.matches.sort((x,M)=>x.filePath.localeCompare(M.filePath));E.length<10&&E.some(x=>x.text&&x.text.split(`
26
+ `).length<=30)?E.forEach((x,M)=>{if(x.text&&x.text.split(`
27
+ `).length<=30)t+=` ${x.filePath}
28
+ `,x.text.split(`
29
+ `).forEach((b,S)=>{let U=x.line+S,R=f.bgHex(vi).hex(yi)(` ${String(U)} `);t+=` ${R} ${f.dim(b)}
30
+ `});else{let F=x.endLine?`(${x.line}:${x.endLine})`:`(${x.line})`;t+=` ${x.filePath} ${f.dim(F)}
31
+ `}M<E.length-1&&(t+=`
32
+ `)}):E.forEach(x=>{let M=x.endLine?`(${x.line}:${x.endLine})`:`(${x.line})`;t+=` ${x.filePath} ${f.dim(M)}
33
+ `}),t+=`
34
+
35
+ `}}),console.log(t);let s=n+i,a=n>0?`${f.dim("violations".padStart(12))} ${f.hex(q)(Oe)} ${f.hex(q).bold(n)}`:`${f.dim("violations".padStart(12))} ${f.dim(n)}`,l=i>0?`${f.dim("suggestions".padStart(12))} ${f.hex(H)(xe)} ${f.hex(H).bold(i)}`:`${f.dim("suggestions".padStart(12))} ${f.dim(i)}`,c=e.totalFiles?`${e.totalFiles} ${St("file",e.totalFiles)}`:"0 files",u=`${e.totalRules} ${St("rule",e.totalRules)}`,m=e.executionTime?`${Math.round(e.executionTime)}ms`:"",g=[c,u,m].filter(Boolean).join(", "),p="";p+=`${a}
36
+ `,p+=`${l}
37
+ `,p+=`${f.dim("summary".padStart(12))} ${g}
38
+ `,console.log(s>0?f.reset(p):p),n>0&&process.exit(1)}function Rt(r){if(r.length===0){console.log(f.yellow("No rules found.")),console.log(f.dim("Go to https://app.wispbit.com/rules to create a rule."));return}let e=[];e.push(["",`${"id".padEnd(12)} ${f.dim("\u2502")} ${"severity".padEnd(16)} ${f.dim("\u2502")} ${"execution".padEnd(18)} ${f.dim("\u2502")} message`]);let t=process.stdout.columns||120,i=Math.max(20,t-55-10);e.push(["",`${f.dim("\u2500".repeat(12))} ${f.dim("\u253C")} ${f.dim("\u2500".repeat(16))} ${f.dim("\u253C")} ${f.dim("\u2500".repeat(18))} ${f.dim("\u253C")} ${f.dim("\u2500".repeat(i))}`]);for(let s of r){let a=s.config.severity==="violation"?Oe:xe,l=s.config.severity==="violation"?f.hex(q).bold:f.hex(H).bold,u=(s.config.severity==="violation"?f.hex(q):f.hex(H))(a)+" "+l(s.config.severity),m=Et(s.id,s.internalId),g=s.config.execution==="llm"?f.hex(ve)(ye)+" "+f.hex(ve)("llm"):f.hex(qe)(ye)+" "+f.hex(qe)("deterministic"),p=xi(s.config.message,i),y=Math.max(0,12-s.id.length),D=Math.max(0,16-(s.config.severity.length+2)),$=Math.max(0,18-(s.config.execution.length+2));e.push(["",`${m}${" ".repeat(y)} ${f.dim("\u2502")} ${u}${" ".repeat(D)} ${f.dim("\u2502")} ${g}${" ".repeat($)} ${f.dim("\u2502")} ${p}`])}let o=wi(e,{align:["","l"],stringLength(s){return Mt(s).length}});console.log(`
39
+ `+o),console.log(f.dim(`
40
+ Use 'wispbit check --rule <rule-id>' to run a specific rule.`))}function It(r,e="pretty"){let t=[];for(let n of r)for(let i of n.matches){let o={filePath:i.filePath,range:i.range,language:i.language,text:i.text,symbol:i.symbol,evidence:i.evidence,ruleId:n.ruleId,internalId:n.internalId,severity:n.severity,message:n.message};t.push(o)}if(e==="stream")for(let n of t)console.log(JSON.stringify(n));else console.log(e==="compact"?JSON.stringify(t):JSON.stringify(t,null,2))}function Lt(r,e=!1){let t=null,n=null,i=null,o=new Map,s=()=>{if(!t)return;let a=[];for(let[c,{isLlm:u,message:m,severity:g}]of o){let p=g==="violation"?f.hex(q)(Oe):f.hex(H)(xe),y=u?`${f.hex(ve)(ye)} ${f.hex(ve)("llm")}`:f.hex(qe)(ye);a.push(`${p} ${y} ${c}: ${f.dim(m)}`)}let l="";i&&(i.mode==="check"?l=i.filePath?` (${i.filePath})`:" (ALL FILES)":i.mode==="diff"&&(l=` (${i.baseCommit} \u2192 ${i.headCommit})`)),a.length>0?t.text=`${a.join(", ")}${l}`:t.text=`${l}`};return r.on("execution:mode",a=>{i=a}),r.on("rules:start",a=>{t=di({spinner:{interval:120,frames:bi},color:!1}).start();let l=()=>{t&&t.stop(),process.exit(130)};process.on("SIGINT",l),process.on("SIGTERM",l);let c=setInterval(()=>{s()},200);t._updateInterval=c}),r.on("rules:rule-start",a=>{o.set(a.ruleId,{isLlm:a.isLlm,message:a.message,severity:a.severity}),s()}),r.on("rules:rule-complete",a=>{o.delete(a.ruleId),s()}),r.on("rules:progress",a=>{s()}),r.on("rules:complete",()=>{t&&(t._updateInterval&&clearInterval(t._updateInterval),t.stop(),t=null),o.clear()}),r.on("files:discovery:start",a=>{e&&console.log(`
41
+ ${f.blue("Discovering files")} in ${f.bold(a.mode)} mode...`)}),r.on("files:discovery:progress",a=>{e&&console.log(` ${a.message}`)}),r.on("files:discovery:complete",a=>{e&&console.log(f.green("File discovery complete:"),`${a.totalFiles} files found (${a.executionTime}ms)`)}),r.on("files:filter",a=>{e&&a.originalCount!==a.filteredCount&&console.log(f.yellow("Filtered:"),`${a.originalCount} \u2192 ${a.filteredCount} ${a.filterType}`)}),r.on("indexing:start",a=>{n=Date.now()}),r.on("indexing:progress",a=>{t&&(t.color="yellow"),n&&Date.now()-n>1e3&&t&&(t.text=`Indexing ${a.language}...`),t&&(a.packageName&&a.timeMs?t.text=`Indexing ${a.language}: ${a.packageName}`:a.message!=="Indexing files..."&&(t.text=`Indexing ${a.language}: ${a.message}`)),e&&(a.packageName&&a.timeMs?console.log(` + ${a.packageName} (${a.timeMs}ms)`):a.message==="Indexing files..."?process.stdout.write("."):a.message!=="Indexing files..."&&console.log(` ${a.message}`))}),r.on("indexing:complete",a=>{n=null,t&&(t.color="blue"),e&&(process.stdout.write(`
42
+ `),console.log(f.green("Indexing complete for"),`${a.language} (${a.executionTime}ms)`))}),e&&(r.on("scip:match-lookup:start",a=>{console.log(f.cyan(`
43
+ \u{1F50D} [${a.language}] Starting SCIP match lookup for: ${JSON.stringify(a.match)}`))}),r.on("scip:match-lookup:progress",a=>{console.log(`${JSON.stringify(a.document)}`)}),r.on("scip:match-lookup:complete",a=>{console.log(f.green(`SCIP match lookup complete for: ${a.language}`))}),r.on("step:start",a=>{console.log(f.cyan(`
44
+ \u{1F527} [${a.ruleId}] Starting step: ${a.stepName} (${a.stepType})`)),console.log(` Input files: ${a.inputs.filePaths.length}`),console.log(` Input matches: ${a.inputs.matches.length}`),a.inputs.matches.length>0&&(console.log(" Match details:"),a.inputs.matches.forEach((l,c)=>{var u;console.log(` Match ${c+1}:`),console.log(` File: ${l.filePath}`),console.log(` Range: ${l.range.start.line}:${l.range.start.column} \u2192 ${l.range.end.line}:${l.range.end.column}`),l.symbol&&console.log(` Symbol: ${l.symbol}`),l.evidence&&l.evidence.length>0&&(console.log(` Evidence chain: ${l.evidence.length} evidence(s)`),l.evidence.forEach((m,g)=>{console.log(` ${g+1}. ${m.filePath} [${m.symbol||"N/A"}] @ ${m.range.start.line} \u2192 ${m.range.end.line}`)})),console.log(` Text preview: ${(u=l.text)==null?void 0:u.substring(0,120)}...`)}))}),r.on("step:complete",a=>{console.log(f.cyan(`\u2705 [${a.ruleId}] Completed step: ${a.stepName} (${a.stepType}) (${a.executionTime}ms)`)),a.outputs.filePaths!==void 0&&console.log(` Output files: ${a.outputs.filePaths.length}`),a.outputs.matches!==void 0&&(console.log(` Output matches: ${a.outputs.matches.length}`),a.outputs.matches.length>0&&(console.log(" Output match details:"),a.outputs.matches.forEach((l,c)=>{var u;console.log(` Match ${c+1}:`),console.log(` File: ${l.filePath}`),console.log(` Range: ${l.range.start.line}:${l.range.start.column} \u2192 ${l.range.end.line}:${l.range.end.column}`),l.symbol&&console.log(` Symbol: ${l.symbol}`),l.evidence&&l.evidence.length>0&&(console.log(` Evidence chain: ${l.evidence.length} evidence(s)`),l.evidence.forEach((m,g)=>{console.log(` ${g+1}. ${m.filePath} [${m.symbol||"N/A"}] @ ${m.range.start.line} \u2192 ${m.range.end.line}`)})),console.log(` Text preview: ${(u=l.text)==null?void 0:u.substring(0,120)}...`)})))}),r.on("test:start",a=>{console.log(f.magenta(`
45
+ \u{1F9EA} [${a.ruleId}] Running test: "${a.testName}"`))}),r.on("test:matches",a=>{console.log(f.magenta(` Found ${a.matches.length} match(es):`)),a.matches.forEach((l,c)=>{var u;console.log(` Match ${c+1}:`),console.log(` File: ${l.filePath}`),console.log(` Range: ${l.range.start.line}:${l.range.start.column} \u2192 ${l.range.end.line}:${l.range.end.column}`),l.symbol&&console.log(` Symbol: ${l.symbol}`),l.evidence&&l.evidence.length>0&&(console.log(` Evidence chain: ${l.evidence.length} evidence(s)`),l.evidence.forEach((m,g)=>{console.log(` ${g+1}. ${m.filePath} [${m.symbol||"N/A"}] @ ${m.range.start.line} \u2192 ${m.range.end.line}`)})),console.log(` Text preview: ${(u=l.text)==null?void 0:u.substring(0,120)}...`)})})),()=>{t&&(t._updateInterval&&clearInterval(t._updateInterval),t.stop(),t=null),o.clear()}}import Pi from"readline";import ae from"chalk";import Si from"ora";import d from"chalk";var P="#fbbf24",se="#ff6b35",W="#4a90e2",oe="#9b59b6",He=[`
46
+ ${d.hex(P)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
47
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
48
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
49
+ ${d.hex(P)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
50
+ ${d.hex(P)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
51
+ ${d.hex(P)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
52
+ `,`
53
+ ${d.hex(W)(" \u2588\u2588\u2557 ")}${d.hex(P)("\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
54
+ ${d.hex(W)(" \u2588\u2588\u2551 ")}${d.hex(P)("\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
55
+ ${d.hex(W)(" \u2588\u2588\u2551 \u2588\u2557 ")}${d.hex(P)("\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
56
+ ${d.hex(W)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557")}${d.hex(P)("\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
57
+ ${d.hex(W)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588")}${d.hex(P)("\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
58
+ ${d.hex(W)(" \u255A\u2550\u2550\u255D\u255A\u2550")}${d.hex(P)("\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
59
+ `,`
60
+ ${d.hex(P)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557")}${d.hex(oe)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${d.hex(P)("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
61
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551")}${d.hex(oe)("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}${d.hex(P)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
62
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551")}${d.hex(oe)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}${d.hex(P)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
63
+ ${d.hex(P)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551")}${d.hex(oe)("\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D ")}${d.hex(P)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
64
+ ${d.hex(P)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${d.hex(oe)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ")}${d.hex(P)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
65
+ ${d.hex(P)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D")}${d.hex(oe)("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ")}${d.hex(P)("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
66
+ `,`
67
+ ${d.hex(P)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${d.hex(se)("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
68
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}${d.hex(se)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
69
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}${d.hex(se)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
70
+ ${d.hex(P)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D ")}${d.hex(se)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
71
+ ${d.hex(P)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ")}${d.hex(se)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
72
+ ${d.hex(P)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ")}${d.hex(se)("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
73
+ `,`
74
+ ${d.hex(P)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557")}${d.hex(W)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
75
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551")}${d.hex(W)("\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
76
+ ${d.hex(P)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${d.hex(W)(" \u2588\u2588\u2551 ")}
77
+ ${d.hex(P)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551")}${d.hex(W)(" \u2588\u2588\u2551 ")}
78
+ ${d.hex(P)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${d.hex(W)(" \u2588\u2588\u2551 ")}
79
+ ${d.hex(P)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D")}${d.hex(W)(" \u255A\u2550\u255D ")}
80
+ `];function Mi(){process.stdout.write("\x1B[2J\x1B[H")}function Ei(r){return new Promise(e=>setTimeout(e,r))}async function $i(r=3e3){let e=Si({text:"",spinner:{interval:100,frames:He}}).start();await Ei(r),e.stop()}async function Ri(r){let e=Pi.createInterface({input:process.stdin,output:process.stdout});return await new Promise(t=>{e.question(r,n=>{e.close(),t(n.trim())})})}async function Ct(){Mi(),await $i(1500),console.log(He[0]),console.log(ae.hex(q)(`
81
+ Welcome to wispbit`)),console.log(ae(` The linter for AI
82
+ `)),console.log(ae.dim(" To use wispbit, you need an API key.")),console.log(ae.dim(` You can get one at: https://app.wispbit.com/api-keys
83
+ `));let r=await Ri(ae.bold.hex(H)(" Enter your Wispbit API key (or press Enter to exit): "));return r||(console.log(ae.dim(`
84
+ Setup cancelled.`)),null)}Ii.config();function Dt(){return Ne.join(Ft.homedir(),".powerlint","config.json")}async function ki(r){let e=Dt(),t=Ne.dirname(e);await Y.mkdir(t,{recursive:!0});let n={};try{let o=await Y.readFile(e,"utf-8");n=JSON.parse(o)}catch{}let i={...n,apiKey:r};await Y.writeFile(e,JSON.stringify(i,null,2))}async function Fi(){if(process.env.WISPBIT_API_KEY)return process.env.WISPBIT_API_KEY;let r=Dt();try{let e=await Y.readFile(r,"utf-8");return JSON.parse(e).apiKey||null}catch{return null}}async function Tt(r){let e=process.env.WISPBIT_API_KEY||null;e||(e=await Fi());let t=await ce.initialize(r,{apiKey:e||void 0});if("failed"in t){if(t.error==="INVALID_API_KEY"){let n=await Ct();if(n||process.exit(0),await r.getRepositoryUrl()||(console.log(T.red("Repository URL not found. Make sure you're in a git repository.")),process.exit(1)),console.log(T.dim("Validating API key...")),await ki(n),t=await ce.initialize(r,{apiKey:n}),"failed"in t)console.log(T.red("Invalid API key. Please check your API key and try again.")),process.exit(1);else return console.log(T.green("Setup complete! powerlint has been configured.")),t}else if(t.error==="INVALID_REPOSITORY"){let n=await r.getRepositoryUrl();console.log(T.red(`No repository in wispbit found for url: ${n}. If you are passing the repository URL as a flag, make sure it is correct. If your git remote URL was recently modified, use "git remote set-url origin <new-url>" to update the remote URL.`)),process.exit(1)}}return t}async function Di(){let r=J(),e=await Ye();Ci.gt(e,r)&&(console.log(T.bgHex("#b2f5ea").black.bold(` NEW VERSION AVAILABLE: ${e} (current: ${r}) `)),console.log(T.bgHex("#b2f5ea").black.bold(` Run 'pnpm install -g @wispbit/local' to update
85
+ `)))}var L=Li(`
3929
86
  wispbit - the linter for AI
3930
87
  https://wispbit.com/
3931
88
 
@@ -3970,317 +127,5 @@ Global options:
3970
127
  --repo-url <url> Override repository URL (default: from git remote)
3971
128
  -v, --version Show version number
3972
129
  -h, --help Show help
3973
- `,
3974
- {
3975
- importMeta: import.meta,
3976
- flags: {
3977
- // Scan/diff options
3978
- rule: {
3979
- type: "string"
3980
- },
3981
- json: {
3982
- type: "string"
3983
- },
3984
- // Diff include
3985
- include: {
3986
- type: "string"
3987
- },
3988
- // Repository URL override
3989
- repoUrl: {
3990
- type: "string"
3991
- },
3992
- // Debug option
3993
- debug: {
3994
- type: "boolean",
3995
- shortFlag: "d",
3996
- default: false
3997
- },
3998
- // Global options
3999
- version: {
4000
- type: "boolean",
4001
- shortFlag: "v"
4002
- },
4003
- help: {
4004
- type: "boolean",
4005
- shortFlag: "h"
4006
- }
4007
- },
4008
- version: getCurrentVersion()
4009
- }
4010
- );
4011
- async function executeCommand(options) {
4012
- const environment = new Environment({ repositoryUrl: options.repoUrl });
4013
- const config = await ensureConfigured(environment);
4014
- const { ruleId, json: json2, mode, filePath } = options;
4015
- let { commitSelector, diffInclude } = options;
4016
- if (mode === "diff") {
4017
- const repoRoot = environment.getWorkspaceRoot();
4018
- if (!commitSelector) {
4019
- const defaults = await getDefaultCommitSelector(repoRoot);
4020
- commitSelector = defaults.commitSelector;
4021
- if (!diffInclude) {
4022
- diffInclude = defaults.include;
4023
- }
4024
- } else {
4025
- if (!diffInclude) {
4026
- const defaults = await getDefaultCommitSelector(repoRoot);
4027
- diffInclude = defaults.include;
4028
- }
4029
- }
4030
- }
4031
- if (mode === "diff" && commitSelector && diffInclude) {
4032
- const repoRoot = environment.getWorkspaceRoot();
4033
- const currentBranch = await getCurrentBranch(repoRoot);
4034
- const validationError = validateWorktreeIncludes(commitSelector, diffInclude, currentBranch);
4035
- if (validationError) {
4036
- console.error(chalk4.red(validationError));
4037
- process.exit(1);
4038
- }
4039
- }
4040
- let jsonOutput = false;
4041
- let jsonFormat = "pretty";
4042
- if (json2 !== void 0) {
4043
- jsonOutput = true;
4044
- if (typeof json2 === "string") {
4045
- if (json2 === "stream") {
4046
- jsonFormat = "stream";
4047
- } else if (json2 === "compact") {
4048
- jsonFormat = "compact";
4049
- } else {
4050
- jsonFormat = "pretty";
4051
- }
4052
- } else {
4053
- jsonFormat = "pretty";
4054
- }
4055
- } else {
4056
- await checkForUpdates();
4057
- }
4058
- let rules;
4059
- try {
4060
- const ruleProvider = new WispbitRuleProvider(config, environment);
4061
- if (ruleId) {
4062
- const rule = await ruleProvider.loadRuleById(ruleId);
4063
- rules = [rule];
4064
- } else {
4065
- rules = await ruleProvider.loadAllRules();
4066
- }
4067
- } catch (error) {
4068
- if (error instanceof InvalidRuleFormatError) {
4069
- console.error(chalk4.red("Rule Validation Error:"), error.message);
4070
- if (error.validationErrors && error.validationErrors.length > 0) {
4071
- console.error(chalk4.red("Validation errors:"));
4072
- error.validationErrors.forEach((err) => {
4073
- console.error(chalk4.red(" \u2022"), err);
4074
- });
4075
- }
4076
- process.exit(1);
4077
- }
4078
- throw error;
4079
- }
4080
- if (!jsonOutput) {
4081
- if (mode === "check") {
4082
- const modeBox = chalk4.bgHex("#eab308").black(" CHECK ");
4083
- const targetInfo = chalk4.dim(` (${filePath || "all files"})`);
4084
- console.log(`${modeBox}${targetInfo}`);
4085
- } else {
4086
- const modeBox = chalk4.bgHex("#4a90e2").black(" DIFF ");
4087
- const truncatedRange = (commitSelector || "").replace(
4088
- /\b[0-9a-f]{30,}\b/gi,
4089
- (match) => match.substring(0, 7)
4090
- );
4091
- const includeInfo = diffInclude ? ` ${truncatedRange} ${chalk4.dim(`[${diffInclude.join("/")}]`)}` : ` ${truncatedRange}`;
4092
- console.log(`${modeBox}${includeInfo}`);
4093
- }
4094
- }
4095
- const eventEmitter = new ExecutionEventEmitter();
4096
- if (mode === "check") {
4097
- eventEmitter.setExecutionMode("check", { filePath });
4098
- } else {
4099
- const includeDisplay = diffInclude ? diffInclude.join(", ") : "all";
4100
- eventEmitter.setExecutionMode("diff", {
4101
- baseCommit: commitSelector || "",
4102
- headCommit: includeDisplay
4103
- });
4104
- }
4105
- const cleanupTerminalReporter = !options.json ? setupTerminalReporter(eventEmitter, options.debug || false) : void 0;
4106
- const executionStartTime = Date.now();
4107
- const ruleExecutor = new RuleExecutor(config, environment, eventEmitter);
4108
- let totalFiles = 0;
4109
- eventEmitter.on("files:discovery:complete", (data) => {
4110
- totalFiles = data.totalFiles;
4111
- });
4112
- const ruleResults = await ruleExecutor.execute(rules, {
4113
- mode,
4114
- filePath,
4115
- diffOptions: mode === "diff" ? {
4116
- include: diffInclude,
4117
- commitSelector
4118
- } : void 0
4119
- });
4120
- const results = [];
4121
- for (const ruleResult of ruleResults) {
4122
- const rule = rules.find((r) => r.id === ruleResult.ruleId);
4123
- if (rule) {
4124
- let llmCost;
4125
- let llmTokens;
4126
- const result = {
4127
- ruleId: ruleResult.ruleId,
4128
- internalId: rule.internalId,
4129
- message: rule.config.message,
4130
- severity: rule.config.severity,
4131
- matches: ruleResult.matches,
4132
- llmCost,
4133
- llmTokens
4134
- };
4135
- results.push(result);
4136
- }
4137
- }
4138
- cleanupTerminalReporter == null ? void 0 : cleanupTerminalReporter();
4139
- if (jsonOutput) {
4140
- outputJSON(results, jsonFormat);
4141
- } else {
4142
- const violationResults = results.filter((r) => r.severity === "violation");
4143
- const suggestionResults = results.filter((r) => r.severity === "suggestion");
4144
- const violationCount = violationResults.reduce((sum, r) => sum + r.matches.length, 0);
4145
- const suggestionCount = suggestionResults.reduce((sum, r) => sum + r.matches.length, 0);
4146
- const totalMatches = violationCount + suggestionCount;
4147
- const totalLLMCost = results.filter((r) => r.llmCost).reduce((sum, r) => sum.plus(new Big(r.llmCost || 0)), new Big(0));
4148
- const totalLLMTokens = results.filter((r) => r.llmTokens).reduce((sum, r) => sum + (r.llmTokens || 0), 0);
4149
- let executionModeInfo;
4150
- if (mode === "check") {
4151
- executionModeInfo = { mode: "check", filePath };
4152
- } else {
4153
- const truncatedRange = (commitSelector || "").replace(
4154
- /\b[0-9a-f]{30,}\b/gi,
4155
- (match) => match.substring(0, 7)
4156
- );
4157
- const includeDisplay = diffInclude ? diffInclude.join(", ") : "all";
4158
- executionModeInfo = {
4159
- mode: "diff",
4160
- baseCommit: truncatedRange,
4161
- headCommit: includeDisplay
4162
- };
4163
- }
4164
- const executionTime = Date.now() - executionStartTime;
4165
- printSummary(results, {
4166
- totalRules: rules.length,
4167
- violationCount,
4168
- suggestionCount,
4169
- totalMatches,
4170
- totalLLMCost: totalLLMCost.toString(),
4171
- totalLLMTokens,
4172
- executionMode: executionModeInfo,
4173
- executionTime,
4174
- totalFiles
4175
- });
4176
- }
4177
- return results;
4178
- }
4179
- async function listRules(repoUrl) {
4180
- const environment = new Environment({ repositoryUrl: repoUrl });
4181
- const config = await ensureConfigured(environment);
4182
- const ruleProvider = new WispbitRuleProvider(config, environment);
4183
- const rules = await ruleProvider.loadAllRules();
4184
- printRulesList(rules);
4185
- }
4186
- async function main() {
4187
- const command = cli.input[0];
4188
- const subcommand = cli.input[1];
4189
- switch (command) {
4190
- case "check": {
4191
- const filePath = cli.input[1];
4192
- await executeCommand({
4193
- ruleId: cli.flags.rule,
4194
- json: cli.flags.json,
4195
- debug: cli.flags.debug,
4196
- mode: "check",
4197
- filePath,
4198
- repoUrl: cli.flags.repoUrl
4199
- });
4200
- break;
4201
- }
4202
- case "diff": {
4203
- let diffInclude;
4204
- if (cli.flags.include) {
4205
- const includeString = cli.flags.include;
4206
- const includes = includeString.split(",").map((f) => f.trim());
4207
- const validIncludes = ["committed", "staged", "unstaged", "untracked"];
4208
- const invalidIncludes = includes.filter((f) => !validIncludes.includes(f));
4209
- if (invalidIncludes.length > 0) {
4210
- console.error(
4211
- chalk4.red(
4212
- `Invalid include(s): ${invalidIncludes.join(", ")}. Valid includes: ${validIncludes.join(", ")}`
4213
- )
4214
- );
4215
- process.exit(1);
4216
- }
4217
- diffInclude = includes;
4218
- }
4219
- const commitSelector = cli.input[1];
4220
- await executeCommand({
4221
- ruleId: cli.flags.rule,
4222
- json: cli.flags.json,
4223
- debug: cli.flags.debug,
4224
- mode: "diff",
4225
- diffInclude,
4226
- commitSelector,
4227
- repoUrl: cli.flags.repoUrl
4228
- });
4229
- break;
4230
- }
4231
- case "list": {
4232
- await listRules(cli.flags.repoUrl);
4233
- break;
4234
- }
4235
- case "cache": {
4236
- if (subcommand === "purge") {
4237
- const environment = new Environment({ repositoryUrl: cli.flags.repoUrl });
4238
- const storage = new Storage(environment);
4239
- const result = await storage.purgeCache();
4240
- console.log(
4241
- `
4242
- ${chalk4.green("Cache purged successfully.")} Removed ${result.deletedCount} item(s).`
4243
- );
4244
- } else {
4245
- console.error(chalk4.red("Unknown cache subcommand:"), subcommand);
4246
- cli.showHelp();
4247
- process.exit(1);
4248
- }
4249
- break;
4250
- }
4251
- default: {
4252
- let diffInclude;
4253
- if (cli.flags.include) {
4254
- const includeString = cli.flags.include;
4255
- const includes = includeString.split(",").map((f) => f.trim());
4256
- const validIncludes = ["committed", "staged", "unstaged", "untracked"];
4257
- const invalidIncludes = includes.filter((f) => !validIncludes.includes(f));
4258
- if (invalidIncludes.length > 0) {
4259
- console.error(
4260
- chalk4.red(
4261
- `Invalid include(s): ${invalidIncludes.join(", ")}. Valid includes: ${validIncludes.join(", ")}`
4262
- )
4263
- );
4264
- process.exit(1);
4265
- }
4266
- diffInclude = includes;
4267
- }
4268
- const commitSelector = cli.input[0];
4269
- await executeCommand({
4270
- ruleId: cli.flags.rule,
4271
- json: cli.flags.json,
4272
- debug: cli.flags.debug,
4273
- mode: "diff",
4274
- diffInclude,
4275
- commitSelector,
4276
- repoUrl: cli.flags.repoUrl
4277
- });
4278
- break;
4279
- }
4280
- }
4281
- }
4282
- main();
4283
- export {
4284
- checkForUpdates
4285
- };
4286
- //# sourceMappingURL=cli.js.map
130
+ `,{importMeta:import.meta,flags:{rule:{type:"string"},json:{type:"string"},include:{type:"string"},repoUrl:{type:"string"},debug:{type:"boolean",shortFlag:"d",default:!1},version:{type:"boolean",shortFlag:"v"},help:{type:"boolean",shortFlag:"h"}},version:J()});async function Je(r){let e=new Q({repositoryUrl:r.repoUrl}),t=await Tt(e),{ruleId:n,json:i,mode:o,filePath:s}=r,{commitSelector:a,diffInclude:l}=r;if(o==="diff"){let w=e.getWorkspaceRoot();if(a)l||(l=(await We(w)).include);else{let E=await We(w);a=E.commitSelector,l||(l=E.include)}}if(o==="diff"&&a&&l){let w=e.getWorkspaceRoot(),E=await rt(w),h=Be(a,l,E);h&&(console.error(T.red(h)),process.exit(1))}let c=!1,u="pretty";i!==void 0?(c=!0,typeof i=="string"?i==="stream"?u="stream":i==="compact"?u="compact":u="pretty":u="pretty"):await Di();let m;try{let w=new ge(t,e);n?m=[await w.loadRuleById(n)]:m=await w.loadAllRules()}catch(w){throw w instanceof Ae&&(console.error(T.red("Rule Validation Error:"),w.message),w.validationErrors&&w.validationErrors.length>0&&(console.error(T.red("Validation errors:")),w.validationErrors.forEach(E=>{console.error(T.red(" \u2022"),E)})),process.exit(1)),w}if(!c)if(o==="check"){let w=T.bgHex("#eab308").black(" CHECK "),E=T.dim(` (${s||"all files"})`);console.log(`${w}${E}`)}else{let w=T.bgHex("#4a90e2").black(" DIFF "),E=(a||"").replace(/\b[0-9a-f]{30,}\b/gi,x=>x.substring(0,7)),h=l?` ${E} ${T.dim(`[${l.join("/")}]`)}`:` ${E}`;console.log(`${w}${h}`)}let g=new N;if(o==="check")g.setExecutionMode("check",{filePath:s});else{let w=l?l.join(", "):"all";g.setExecutionMode("diff",{baseCommit:a||"",headCommit:w})}let p=r.json?void 0:Lt(g,r.debug||!1),y=Date.now(),D=new _e(t,e,g),$=0;g.on("files:discovery:complete",w=>{$=w.totalFiles});let A=await D.execute(m,{mode:o,filePath:s,diffOptions:o==="diff"?{include:l,commitSelector:a}:void 0}),k=[];for(let w of A){let E=m.find(h=>h.id===w.ruleId);if(E){let M={ruleId:w.ruleId,internalId:E.internalId,message:E.config.message,severity:E.config.severity,matches:w.matches,llmCost:void 0,llmTokens:void 0};k.push(M)}}if(p==null||p(),c)It(k,u);else{let w=k.filter(R=>R.severity==="violation"),E=k.filter(R=>R.severity==="suggestion"),h=w.reduce((R,O)=>R+O.matches.length,0),x=E.reduce((R,O)=>R+O.matches.length,0),M=h+x,F=k.filter(R=>R.llmCost).reduce((R,O)=>R.plus(new kt(O.llmCost||0)),new kt(0)),b=k.filter(R=>R.llmTokens).reduce((R,O)=>R+(O.llmTokens||0),0),S;if(o==="check")S={mode:"check",filePath:s};else{let R=(a||"").replace(/\b[0-9a-f]{30,}\b/gi,_t=>_t.substring(0,7)),O=l?l.join(", "):"all";S={mode:"diff",baseCommit:R,headCommit:O}}let U=Date.now()-y;$t(k,{totalRules:m.length,violationCount:h,suggestionCount:x,totalMatches:M,totalLLMCost:F.toString(),totalLLMTokens:b,executionMode:S,executionTime:U,totalFiles:$})}return k}async function Ti(r){let e=new Q({repositoryUrl:r}),t=await Tt(e),i=await new ge(t,e).loadAllRules();Rt(i)}async function _i(){let r=L.input[0],e=L.input[1];switch(r){case"check":{let t=L.input[1];await Je({ruleId:L.flags.rule,json:L.flags.json,debug:L.flags.debug,mode:"check",filePath:t,repoUrl:L.flags.repoUrl});break}case"diff":{let t;if(L.flags.include){let o=L.flags.include.split(",").map(l=>l.trim()),s=["committed","staged","unstaged","untracked"],a=o.filter(l=>!s.includes(l));a.length>0&&(console.error(T.red(`Invalid include(s): ${a.join(", ")}. Valid includes: ${s.join(", ")}`)),process.exit(1)),t=o}let n=L.input[1];await Je({ruleId:L.flags.rule,json:L.flags.json,debug:L.flags.debug,mode:"diff",diffInclude:t,commitSelector:n,repoUrl:L.flags.repoUrl});break}case"list":{await Ti(L.flags.repoUrl);break}case"cache":{if(e==="purge"){let t=new Q({repositoryUrl:L.flags.repoUrl}),i=await new Z(t).purgeCache();console.log(`
131
+ ${T.green("Cache purged successfully.")} Removed ${i.deletedCount} item(s).`)}else console.error(T.red("Unknown cache subcommand:"),e),L.showHelp(),process.exit(1);break}default:{let t;if(L.flags.include){let o=L.flags.include.split(",").map(l=>l.trim()),s=["committed","staged","unstaged","untracked"],a=o.filter(l=>!s.includes(l));a.length>0&&(console.error(T.red(`Invalid include(s): ${a.join(", ")}. Valid includes: ${s.join(", ")}`)),process.exit(1)),t=o}let n=L.input[0];await Je({ruleId:L.flags.rule,json:L.flags.json,debug:L.flags.debug,mode:"diff",diffInclude:t,commitSelector:n,repoUrl:L.flags.repoUrl});break}}}_i();export{Di as checkForUpdates};