@wispbit/local 1.0.29 → 1.0.31

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 +99 -4241
  2. package/dist/index.js +1 -20
  3. package/dist/package.json +6 -9
  4. package/package.json +5 -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,101 @@
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 Q from"fs/promises";import*as Nt from"os";import*as je from"path";import Ot from"big.js";import A from"chalk";import Ti from"dotenv";import _i from"meow";import Ai from"semver";import jt from"p-retry";import{z as p}from"zod";var Wt=p.object({repository_url:p.string(),powerlint_version:p.string(),schema_version:p.string()}),Ut=p.object({configured:p.boolean(),invalid_api_key:p.boolean().optional(),is_valid_repository:p.boolean().optional(),config:p.object({ignored_globs:p.array(p.string())}).optional()}),Vt=p.object({repository_url:p.string(),rule_ids:p.array(p.string()).optional(),schema_version:p.string(),powerlint_version:p.string()}),qt=p.object({rules:p.array(p.object({id:p.string(),internalId:p.string(),internalVersionId:p.string(),message:p.string(),prompt:p.string(),severity:p.enum(["suggestion","violation"]),schema:p.any(),execution:p.enum(["llm","deterministic"]).optional()}))}),zt=p.object({rule:p.object({id:p.string(),versionId:p.string(),contents:p.string(),message:p.string()}),matches:p.array(p.any()),messages:p.array(p.any()),tools:p.array(p.enum(["read","grep","glob"])),files:p.array(p.object({path:p.string(),sha:p.string()})).optional(),schema_version:p.string(),powerlint_version:p.string(),repository_url:p.string(),commit:p.object({sha:p.string(),branch:p.string(),message:p.string(),timestamp:p.string()})}),Kt=p.discriminatedUnion("type",[p.object({type:p.literal("tool_request"),tool_calls:p.array(p.any()),usage:p.object({cost:p.string(),inputTokens:p.string(),outputTokens:p.string()})}),p.object({type:p.literal("matches"),matches:p.array(p.any()),usage:p.object({cost:p.string(),inputTokens:p.string(),outputTokens:p.string()}),files:p.array(p.object({path:p.string(),sha:p.string()})).optional()}),p.object({type:p.literal("request_files"),files:p.array(p.object({path:p.string(),sha:p.string()})),usage:p.object({cost:p.string(),inputTokens:p.string(),outputTokens:p.string()})})]),Ht=p.object({rule:p.object({id:p.string(),versionId:p.string(),contents:p.string(),message:p.string()}),matches:p.array(p.any()),files:p.array(p.object({path:p.string(),sha:p.string()})).optional(),schema_version:p.string(),powerlint_version:p.string(),repository_url:p.string(),commit:p.object({sha:p.string(),branch:p.string(),message:p.string(),timestamp:p.string()})}),Jt=p.object({type:p.literal("matches"),matches:p.array(p.any()),usage:p.object({cost:p.string(),inputTokens:p.string(),outputTokens:p.string()})}),me=class{baseUrl;apiKey;constructor(e){this.baseUrl=e.baseUrl,this.apiKey=e.apiKey}async request(e,t,n){let i=`${this.baseUrl}${e}`,a=await(await jt(async()=>{let s=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify(t)});if(!s.ok){let l=await s.text();throw new Error(`Wispbit API request failed: ${s.status} ${s.statusText} - ${l}`)}return s},{retries:3,minTimeout:1e3,maxTimeout:5e3,onFailedAttempt:s=>{console.warn(`API request to ${e} failed (attempt ${s.attemptNumber}/4): ${s.message}`)}})).json();return n?n.parse(a):a}async initialize(e){let t=Wt.parse(e);return await this.request("/plv1/initialize",t,Ut)}async getRules(e){let t=Vt.parse(e);return await this.request("/plv1/get-rules",t,qt)}async matchLLMSearch(e){let t=zt.parse(e);return await this.request("/plv1/llm-search",t,Kt)}async matchLLMValidate(e){let t=Ht.parse(e);return await this.request("/plv1/llm-validate",t,Jt)}};var Me=class{config;environment;constructor(e,t){this.config=e,this.environment=t}async search(e){let t=this.config.getApiClient(),n=await this.environment.getRepositoryUrl(),i=await this.environment.getCurrentCommit(),r={id:e.rule.internalId,versionId:e.rule.internalVersionId,contents:e.rule.prompt,message:e.rule.config.message};return await t.matchLLMSearch({rule:r,matches:e.matches,messages:e.messages,tools:e.tools,files:e.files,schema_version:this.config.getSchemaVersion(),powerlint_version:this.config.getLocalVersion(),repository_url:n,commit:i})}async validate(e){let t=this.config.getApiClient(),n=await this.environment.getRepositoryUrl(),i=await this.environment.getCurrentCommit(),r={id:e.rule.internalId,versionId:e.rule.internalVersionId,contents:e.rule.prompt,message:e.rule.config.message};return await t.matchLLMValidate({rule:r,matches:e.matches,files:e.files,schema_version:this.config.getSchemaVersion(),powerlint_version:this.config.getLocalVersion(),repository_url:n,commit:i})}};import{readFileSync as Yt}from"fs";import{dirname as Qt,join as Xt}from"path";import{fileURLToPath as Zt}from"url";import en from"latest-version";function Y(){let o=Zt(import.meta.url?import.meta.url:__filename),e=Qt(o),t=Xt(e,"../package.json");try{let n=Yt(t,"utf8");return JSON.parse(n).version}catch{return"0.0.0"}}async function et(){try{return await en("@wispbit/local")}catch{return Y()}}var ge=class o{config;apiKey=null;baseUrl=null;apiClient=null;llmProvider=null;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 me({baseUrl:this.baseUrl,apiKey:this.apiKey})),t!=null&&t.llmProvider&&(this.llmProvider=t.llmProvider)}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(){if(!this.llmProvider)throw new Error("LLM provider not initialized. Use Config.initialize() instead of initializeLocal().");return this.llmProvider}getLocalVersion(){return Y()}getSchemaVersion(){return"v1"}static initializeLocal(e={}){let t=e.ignoredGlobs||[];return new o({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",r=t||process.env.WISPBIT_API_KEY||null;if(!r)return console.log("No API key found"),{failed:!0,error:"INVALID_API_KEY"};let a=await e.getRepositoryUrl(),l=await new me({baseUrl:i,apiKey:r}).initialize({repository_url:a,powerlint_version:Y(),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)||[],u=new o({ignoredGlobs:c,apiKey:r,baseUrl:i},{});return u.llmProvider=new Me(u,e),u}isConfigured(){return this.getApiKey()!==null}};import{exec as nn,execSync as rn}from"child_process";import{existsSync as sn,readFileSync as on}from"fs";import{promisify as an}from"util";import{createHash as tn}from"crypto";function pe(o){return tn("sha256").update(o).digest("hex")}var F=an(nn);function nt(){return rn("git rev-parse --show-toplevel",{encoding:"utf-8"}).trim()}async function it(o){let{stdout:e}=await F("git ls-files --ignored --exclude-standard --others",{cwd:o,maxBuffer:52428800});return e.split(`
3
+ `).filter(Boolean).map(t=>t.trim())}async function rt(o,e="origin"){let{stdout:t}=await F(`git config --get remote.${e}.url`,{cwd:o});return t.trim()||null}async function st(o,e="origin"){try{let{stdout:t}=await F(`git rev-parse --abbrev-ref ${e}/HEAD`,{cwd:o});return t.trim().split("/").pop()||null}catch{let n=["main","master"];for(let i of n)try{return await F(`git rev-parse --verify ${i}`,{cwd:o}),i}catch{}return null}}async function ot(o){let{stdout:e}=await F("git rev-parse --abbrev-ref --symbolic-full-name @{u}",{cwd:o});return e.trim()||void 0}function at(o,e){let t=`.git/refs/branch-metadata/${e}`,n=`${o}/${t}`;if(!sn(n))return;let r=on(n,"utf-8").match(/"parent"\s*:\s*"([^"]+)"/);return r==null?void 0:r[1]}function B(o){return`'${o.replace(/'/g,"'\\''")}'`}function z(o){return o.map(B).join(" ")}function ln(o){let e={committed:!0,staged:!0,unstaged:!0,untracked:!0};if(!o||o.length===0)return e;let t=new Set(o);return{committed:t.has("committed"),staged:t.has("staged"),unstaged:t.has("unstaged"),untracked:t.has("untracked")}}function Se(o,e){if(!o)return[];let t=[],n=o.split("\0").filter(Boolean);for(let i=0;i<n.length;){let r=n[i];if(!r)break;if(r.startsWith("R")){let a=n[i+1],s=n[i+2];a&&s?(t.push({status:r,path:s,oldPath:a,source:e}),i+=3):i++}else{let a=n[i+1];a?(t.push({status:r,path:a,source:e}),i+=2):i++}}return t}function cn(o){return o?o.split("\0").filter(Boolean).map(e=>({status:"U",path:e,source:"untracked"})):[]}function un(o){let e=new Map;for(let t of o){let n=e.get(t.path);if(!n)e.set(t.path,t);else{let i=tt(n.source),r=tt(t.source);r>i?e.set(t.path,t):r===i&&t.oldPath&&(n.oldPath=n.oldPath||t.oldPath)}}return Array.from(e.values())}function tt(o){switch(o){case"untracked":return 4;case"unstaged":return 3;case"staged":return 2;case"committed":return 1}}function Ue(o){let e=new Map;if(!o)return e;let t=o.split(/^diff --git /m).filter(Boolean);for(let n of t){let a=n.split(`
4
+ `)[0].match(/a\/(.+?) b\//);if(a){let s=a[1];e.set(s,"diff --git "+n)}}return e}function mn(o){return o.status==="U"||o.status==="A"?"added":o.status==="D"?"removed":"modified"}function fe(o){if(!o)return"";let e=o.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(o){let e=o.match(/^(.+)\.\.\.(.+)$/);if(e)return[e[1],e[2],!0];let t=o.match(/^(.+)\.\.([^.].*)$/);return t?[t[1],t[2],!1]:[o,o,!1]}function gn(o){let[,e]=Pe(o);return e}async function Ve(o){let{stdout:e}=await F("git rev-parse --abbrev-ref HEAD",{cwd:o});return e.trim()}async function lt(o){let e=await Ve(o),{stdout:t}=await F("git rev-parse HEAD",{cwd:o}),n=t.trim(),{stdout:i}=await F('git log -1 --pretty=format:"%s"',{cwd:o}),r=i.trim(),{stdout:a}=await F('git log -1 --pretty=format:"%cI"',{cwd:o}),s=a.trim();return{sha:n,branch:e,message:r,timestamp:s}}function qe(o,e,t){if(!o.includes("..")||!(e.includes("staged")||e.includes("unstaged")||e.includes("untracked")))return null;let r=gn(o);return r==="HEAD"||r===t?null:`Worktree includes (staged, unstaged, untracked) require range to end at HEAD or current branch (${t}). Got: ${o}`}async function ze(o){let{stdout:e}=await F("git rev-parse --abbrev-ref HEAD",{cwd:o}),t=e.trim(),n=["committed","staged","unstaged","untracked"],i=at(o,t);if(i)return{commitSelector:`${i}...${t}`,include:n};let r=await st(o);if(r){if(t===r){let c=await ot(o).catch(()=>{});return c?{commitSelector:`${c}...HEAD`,include:n}:{commitSelector:"HEAD~1...HEAD",include:n}}let a=`origin/${r}`,{stdout:s}=await F(`git rev-parse --verify ${a}`,{cwd:o});return{commitSelector:`${s.trim()?a:r}...${t}`,include:n}}return{commitSelector:"HEAD...HEAD",include:n}}async function ct(o,e){let t=e.commitSelector.trim(),n=t.includes(".."),{stdout:i}=await F("git rev-parse --abbrev-ref HEAD",{cwd:o}),r=i.trim(),{stdout:a}=await F("git rev-parse HEAD",{cwd:o}),s=a.trim(),l="",c="";if(n)l=t,c=t;else{let x=at(o,r),d=await st(o),P=t||x||`origin/${d||"main"}`;c=P;let $=`git merge-base --fork-point ${B(P)} HEAD || git merge-base ${B(P)} HEAD`,{stdout:w}=await F($,{cwd:o});if(l=w.trim(),!t&&r===(d||"main")){let S=await ot(o).catch(()=>{});if(S){c=S;let{stdout:O}=await F(`git merge-base --fork-point ${B(S)} HEAD || git merge-base ${B(S)} HEAD`,{cwd:o});l=O.trim()}else c="HEAD~1",l="HEAD~1"}}let u=ln(e.include),m=qe(t,e.include,r);if(m)throw new Error(m);let g=[];if(u.committed)if(n){let[x,d]=Pe(t),{stdout:P}=await F(`git diff --name-status -M -z ${B(x)}..${B(d)}`,{cwd:o,maxBuffer:50*1024*1024});g.push(...Se(P,"committed"))}else{let{stdout:x}=await F(`git diff --name-status -M -z ${l}..HEAD`,{cwd:o,maxBuffer:52428800});g.push(...Se(x,"committed"))}if(u.staged){let{stdout:x}=await F("git diff --name-status -M -z --cached",{cwd:o,maxBuffer:52428800});g.push(...Se(x,"staged"))}if(u.unstaged){let{stdout:x}=await F("git diff --name-status -M -z",{cwd:o,maxBuffer:52428800});g.push(...Se(x,"unstaged"))}if(u.untracked){let{stdout:x}=await F("git ls-files --others --exclude-standard -z",{cwd:o,maxBuffer:52428800});g.push(...cn(x))}let h=un(g),v=h.filter(x=>x.source==="committed"),M=h.filter(x=>x.source==="staged"),E=h.filter(x=>x.source==="unstaged"),C=h.filter(x=>x.source==="untracked"),R=new Map;if(v.length>0){let x=v.map(w=>w.path),d="";if(n){let[w,S]=Pe(t);d=`git diff -U0 -M ${B(w)}..${B(S)} -- ${z(x)}`}else d=`git diff -U0 -M ${l}..HEAD -- ${z(x)}`;let{stdout:P}=await F(d,{cwd:o,maxBuffer:50*1024*1024});Ue(P).forEach((w,S)=>R.set(S,fe(w)))}if(M.length>0){let x=M.map(w=>w.path),d=`git diff -U0 -M --cached -- ${z(x)}`,{stdout:P}=await F(d,{cwd:o,maxBuffer:50*1024*1024});Ue(P).forEach((w,S)=>R.set(S,fe(w)))}if(E.length>0){let x=E.map(w=>w.path),d=`git diff -U0 -M -- ${z(x)}`,{stdout:P}=await F(d,{cwd:o,maxBuffer:50*1024*1024});Ue(P).forEach((w,S)=>R.set(S,fe(w)))}for(let x of C)try{let{stdout:d}=await F(`git diff -U0 --no-index /dev/null ${B(x.path)}`,{cwd:o,maxBuffer:52428800});R.set(x.path,fe(d))}catch(d){if(d.stdout)R.set(x.path,fe(d.stdout));else throw d}let b=new Map;if(v.length>0){let x=v.map(w=>w.path),d="";if(n){let[w,S]=Pe(t);d=`git diff --numstat ${B(w)}..${B(S)} -- ${z(x)}`}else d=`git diff --numstat ${l}..HEAD -- ${z(x)}`;let{stdout:P}=await F(d,{cwd:o,maxBuffer:50*1024*1024}),$=P.split(`
7
+ `).filter(Boolean);for(let w of $){let S=w.split(" ");if(S.length>=3){let[O,k,_]=S;b.set(_,{additions:parseInt(O)||0,deletions:parseInt(k)||0})}}}if(M.length>0){let x=M.map(w=>w.path),d=`git diff --numstat --cached -- ${z(x)}`,{stdout:P}=await F(d,{cwd:o,maxBuffer:50*1024*1024}),$=P.split(`
8
+ `).filter(Boolean);for(let w of $){let S=w.split(" ");if(S.length>=3){let[O,k,_]=S;b.set(_,{additions:parseInt(O)||0,deletions:parseInt(k)||0})}}}if(E.length>0){let x=E.map(w=>w.path),d=`git diff --numstat -- ${z(x)}`,{stdout:P}=await F(d,{cwd:o,maxBuffer:50*1024*1024}),$=P.split(`
9
+ `).filter(Boolean);for(let w of $){let S=w.split(" ");if(S.length>=3){let[O,k,_]=S;b.set(_,{additions:parseInt(O)||0,deletions:parseInt(k)||0})}}}for(let x of C){let P=(R.get(x.path)||"").split(`
10
+ `),$=0,w=0;for(let S of P)S.startsWith("+")&&!S.startsWith("+++")?$++:S.startsWith("-")&&!S.startsWith("---")&&w++;b.set(x.path,{additions:$,deletions:w})}let L=[];for(let x of h){let d=R.get(x.path)||"",P=b.get(x.path)||{additions:0,deletions:0},$=mn(x),w={filename:x.path,status:$,patch:d,additions:P.additions,deletions:P.deletions,sha:pe(d)};x.oldPath&&(w.oldFilename=x.oldPath),L.push(w)}return{files:L,currentBranch:r,currentCommit:s,diffBranch:c,diffCommit:l}}var X=class{workspaceRoot;repositoryUrl;constructor(e){this.repositoryUrl=(e==null?void 0:e.repositoryUrl)||null,this.workspaceRoot=(e==null?void 0:e.workspaceRoot)||nt()}getWorkspaceRoot(){return this.workspaceRoot}async getRepositoryUrl(e="origin"){if(this.repositoryUrl)return this.repositoryUrl;let t=await rt(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}async getCurrentCommit(){return await lt(this.workspaceRoot)}};import*as N from"fs/promises";import pn from"os";import Z from"path";import fn from"keyv";import{KeyvFile as hn}from"keyv-file";var ee=class{environment;cacheStore;constructor(e){this.environment=e}getStorageDirectory(){return Z.join(pn.homedir(),".powerlint")}getIndexDirectory(){return Z.join(this.getStorageDirectory(),"indexes",pe(this.environment.getWorkspaceRoot()))}getCacheDirectory(){return Z.join(this.getStorageDirectory(),"cache",pe(this.environment.getWorkspaceRoot()))}async ensureDirectory(e){await N.mkdir(e,{recursive:!0})}async getIndexFilePath(e,t){let n=this.getIndexDirectory();await this.ensureDirectory(n);let i=t||`${e.toLowerCase()}-index.scip`;return Z.join(n,`${e.toLowerCase()}-${i}`)}async indexExists(e,t){let n=await this.getIndexFilePath(e,t);try{return await N.access(n),!0}catch{return!1}}async readIndex(e,t){let n=await this.getIndexFilePath(e,t);try{return await N.access(n),await N.readFile(n)}catch{return null}}async saveIndex(e,t,n){let i=await this.getIndexFilePath(e,n);await N.writeFile(i,t)}getCacheFilePath(){return Z.join(this.getCacheDirectory(),"cache.json")}async purgeStorage(){let e=0,t=this.getStorageDirectory();try{await N.access(t),await N.rm(t,{recursive:!0,force:!0}),e++}catch{}return{success:!0,deletedCount:e}}async purgeCache(){let e=0,t=this.getCacheDirectory();try{await N.access(t),await N.rm(t,{recursive:!0,force:!0}),e++}catch{}return{success:!0,deletedCount:e}}getCacheStore(){if(!this.cacheStore){let e=this.getCacheDirectory();this.cacheStore=new fn({store:new hn({filename:Z.join(e,"cache.json")})})}return this.cacheStore}async saveCache(e,t,n){let i=this.getCacheStore(),r=`${e}:${t}`;await i.set(r,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 N.access(t),await N.rm(t,{recursive:!0,force:!0}),e++}catch{}return{success:!0,deletedCount:e}}};var he=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(r=>({id:r.id,internalId:r.internalId,internalVersionId:r.internalVersionId,config:{message:r.message,severity:r.severity,execution:r.execution||"llm",steps:r.schema},prompt:r.prompt,testCases:[]}))}};import{EventEmitter as dn}from"events";var G=class extends dn{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 r=Math.round(e/t*100);this.emit("rules:progress",{currentRule:e,totalRules:t,ruleId:n,percentage:r,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,r){this.emit("step:start",{ruleId:e,stepName:t,stepType:n,inputs:i,parentStepName:r})}completeStep(e,t,n,i,r=0,a){this.emit("step:complete",{ruleId:e,stepName:t,stepType:n,outputs:i,executionTime:r,parentStepName:a})}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,r,a,s,l){this.emit("llm:validation:complete",{ruleId:e,tokenCount:t,inputTokens:n,outputTokens:i,cost:r,executionTime:a,violationCount:s,fromCache:l})}llmToolExecution(e,t,n){this.emit("llm:tool-execution",{ruleId:e,toolName:t,...n})}startLLMValidationPhase(e,t){this.emit("llm:validation-phase:start",{ruleId:e,matchCount:t})}};import{cpus as $i}from"os";import*as te from"fs";import*as U from"path";import{glob as He}from"glob";function Ke(o,e){let{changedFiles:t}=e;return t.some(n=>o===n||o.endsWith(n)||n.endsWith(o))}function xn(o,e,t,n){let{fileChangeMap:i}=n,r=i.get(o);return r?r.status==="added"?!0:r.status==="removed"?!1:vn(r.patch,e,t):!1}function ut(o,e){return Ke(o.file.path,e)&&xn(o.file.path,o.range.start.line,o.range.end.line,e)}function vn(o,e,t){let n=o.split(`
11
+ `),i=0,r=!1;for(let a of n){let s=a.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);if(s){i=parseInt(s[1],10),r=!0;continue}if(r)if(a.startsWith("+")){if(i>=e&&i<=t)return!0;i++}else{if(a.startsWith("-"))continue;if(!a.startsWith("\\")){if(i>=e&&i<=t)return!0;i++}}}return!1}import yn from"ignore";function K(o,e){return e.length===0?!1:yn().add(e).ignores(o)}var Ee=class o{environment;_filePaths=[];mode;eventEmitter;config;diffMode;constructor(e,t,n,i){this.environment=t,this.mode=n,this.eventEmitter=i||new G,this.config=e}static async initialize(e,t,n,i,r,a){let s=new o(e,t,n,i);if(n==="diff"&&!r){let u=s.environment.getWorkspaceRoot(),m=a==null?void 0:a.include,g=a==null?void 0:a.commitSelector;if(!g||!m)throw new Error("Commit selector and include are required in diff mode");let h=await ct(u,{include:m,commitSelector:g}).catch(()=>{throw new Error("Diff mode requires a git repository. Please run this command from within a git repository.")});s.diffMode={gitChanges:h,changedFiles:h.files.map(v=>v.filename),fileChangeMap:new Map(h.files.map(v=>[v.filename,v]))},s.eventEmitter.fileDiscoveryProgress(`Found ${s.diffMode.changedFiles.length} changed files`)}let l=n==="check"?await s.loadGitIgnoredFiles():new Set,c=await s.discoverFiles(r,l);return s._filePaths=c,s}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.file.path))}get executionMode(){return this.mode}async loadGitIgnoredFiles(){let e=this.environment.getWorkspaceRoot(),t=await it(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(a=>te.existsSync(U.resolve(i,a))).map(a=>a);let r=this.config.getIgnoredGlobs();if(r.length>0){this.eventEmitter.fileDiscoveryProgress("Applying ignore patterns...");let a=n.length;n=n.filter(s=>!K(s,r)),a!==n.length&&this.eventEmitter.fileDiscoveryProgress(`Filtered out ${a-n.length} ignored files`)}}else{this.eventEmitter.fileDiscoveryProgress("Scanning workspace for files...");let i=this.environment.getWorkspaceRoot(),r=this.config.getIgnoredGlobs();n=await this.discoverAllFiles([i],r,t)}return this.eventEmitter.completeFileDiscovery(n.length,this.mode),n}async discoverFilesFromPath(e,t){let n=this.environment.getWorkspaceRoot(),i=U.resolve(n,e),r=this.config.getIgnoredGlobs();if(this.isGlobPattern(e))return this.eventEmitter.fileDiscoveryProgress(`Discovering files matching glob pattern: ${e}`),await this.discoverFilesFromGlob(e,r,t);if(!te.existsSync(i))throw new Error(`Path not found: ${e}`);let a=te.statSync(i);if(a.isFile())return this.eventEmitter.fileDiscoveryProgress(`Checking specific file: ${e}`),t.has(e)?(this.eventEmitter.fileDiscoveryProgress(`File ${e} is ignored by git`),[]):K(e,r)?(this.eventEmitter.fileDiscoveryProgress(`File ${e} is ignored by patterns`),[]):[e];if(a.isDirectory())return this.eventEmitter.fileDiscoveryProgress(`Discovering files in directory: ${e}`),await this.discoverFilesFromDirectory(e,r,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(),r=["**/node_modules/**","**/.git/**",...t],s=(await He(e,{cwd:i,nodir:!0,absolute:!1,ignore:r})).filter(l=>!n.has(l));return this.eventEmitter.fileDiscoveryProgress(`Found ${s.length} files matching pattern`),s}async discoverFilesFromDirectory(e,t,n){let i=this.environment.getWorkspaceRoot(),r=U.join(e,"**/*").replace(/\\/g,"/"),a=["**/node_modules/**","**/.git/**",...t],l=(await He(r,{cwd:i,nodir:!0,absolute:!1,ignore:a})).filter(c=>!n.has(c));return this.eventEmitter.fileDiscoveryProgress(`Found ${l.length} files in directory`),l}async discoverAllFiles(e,t,n){let i=[],r=this.environment.getWorkspaceRoot(),a=["**/node_modules/**","**/.git/**",...t];for(let s of e){if(!te.statSync(s).isDirectory()){let m=U.relative(r,s),g=n.has(m),h=K(m,t);!g&&!h&&i.push(m);continue}let u=(await He("**/*",{cwd:s,nodir:!0,absolute:!1,ignore:a})).map(m=>{let g=U.resolve(s,m);return U.relative(r,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 Ke(t,{changedFiles:this.diffMode.changedFiles,fileChangeMap:this.diffMode.fileChangeMap})}isMatchValid(e){if(this.mode==="check")return!0;let{match:t}=e;return ut(t,{changedFiles:this.diffMode.changedFiles,fileChangeMap:this.diffMode.fileChangeMap})}};import*as ne from"fs";import*as j from"path";var Re=class{environment;constructor(e){this.environment=e}evaluateCondition(e,t){let n=this.environment.getWorkspaceRoot(),i=j.resolve(n,t);if("fs.siblingExists"in e){let{filename:r}=e["fs.siblingExists"],a=j.dirname(i),s=j.join(a,r);return ne.existsSync(s)}if("fs.ancestorHas"in e){let{filename:r}=e["fs.ancestorHas"],a=j.dirname(i),s=j.parse(a).root;for(;a!==s;){let l=j.join(a,r);if(ne.existsSync(l))return!0;let c=j.dirname(a);if(c===a)break;a=c}return!1}if("fs.siblingAny"in e){let{pattern:r}=e["fs.siblingAny"],a=j.dirname(i);return ne.existsSync(a)?ne.readdirSync(a).some(l=>K(l,[r])):!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 a,s;let n=Date.now(),i=e;(a=t.include)!=null&&a.length&&(i=i.filter(l=>K(l,t.include))),(s=t.ignore)!=null&&s.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 r=Date.now()-n;return await Promise.resolve({filteredPaths:i,executionTime:r})}};import yi from"path";import{existsSync as Je}from"fs";import{createRequire as wn}from"module";import de from"path";import bn from"@ast-grep/lang-angular";import $n from"@ast-grep/lang-bash";import Mn from"@ast-grep/lang-c";import Sn from"@ast-grep/lang-cpp";import Pn from"@ast-grep/lang-csharp";import En from"@ast-grep/lang-css";import Rn from"@ast-grep/lang-dart";import Ln from"@ast-grep/lang-elixir";import In from"@ast-grep/lang-go";import Cn from"@ast-grep/lang-haskell";import kn from"@ast-grep/lang-html";import Fn from"@ast-grep/lang-java";import Dn from"@ast-grep/lang-javascript";import Tn from"@ast-grep/lang-json";import _n from"@ast-grep/lang-kotlin";import An from"@ast-grep/lang-lua";import On from"@ast-grep/lang-markdown";import Nn from"@ast-grep/lang-php";import Gn from"@ast-grep/lang-python";import Bn from"@ast-grep/lang-ruby";import jn from"@ast-grep/lang-rust";import Wn from"@ast-grep/lang-scala";import Un from"@ast-grep/lang-sql";import Vn from"@ast-grep/lang-swift";import qn from"@ast-grep/lang-toml";import zn from"@ast-grep/lang-tsx";import Kn from"@ast-grep/lang-typescript";import Hn from"@ast-grep/lang-yaml";import{registerDynamicLanguage as Jn}from"@ast-grep/napi";console.debug=()=>{};var Yn=wn(import.meta.url?import.meta.url:__filename);function Qn(){let o=de.dirname(Yn.resolve("tree-sitter-graphql")),e=de.join(o,"../../build/Release/tree_sitter_graphql_binding.node");if(Je(e))return e;let t=de.join(o,"../../build/Debug/tree_sitter_graphql_binding.node");if(Je(t))return t;let n=de.join(o,"parser.so");return Je(n)?n:null}var gt=Qn(),Xn={libraryPath:gt,extensions:["graphql"],languageSymbol:"tree_sitter_graphql",metaVarChar:"$",expandoChar:void 0},pt={Angular:bn,Bash:$n,C:Mn,Cpp:Sn,Csharp:Pn,Css:En,Dart:Rn,Elixir:Ln,Go:In,Haskell:Cn,Html:kn,Java:Fn,JavaScript:Dn,Json:Tn,Kotlin:_n,Lua:An,Markdown:On,Php:Nn,Python:Gn,Ruby:Bn,Rust:jn,Scala:Wn,Sql:Un,Swift:Vn,Toml:qn,Tsx:zn,TypeScript:Kn,Yaml:Hn,...gt?{GraphQL:Xn}:{}};Jn(pt);var mt=Object.entries(pt).reduce((o,[e,t])=>(o[e]=t.extensions??[],o),{});function ft(o){let e=de.extname(o).slice(1);return Zn(e)??"Unknown"}function Zn(o){return Object.keys(mt).find(e=>mt[e].includes(o))}import{readFile as Mt}from"fs/promises";import St from"path";import{parse as ui,parseAsync as mi}from"@ast-grep/napi";import{spawn as ri}from"child_process";import si from"crypto";import ie from"fs";import V,{dirname as vt,join as yt}from"path";import{glob as oi}from"glob";import{promises as ei}from"fs";import fs from"path";async function Le(o){try{return await ei.access(o),!0}catch{return!1}}import{execSync as ti}from"child_process";function ht(){return ti("which rg").toString().trim()}import Ie from"fs";import ni from"path";async function dt(o){try{if(o.includes("/")||o.includes("\\"))return await Ie.promises.access(o,Ie.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 r of n)for(let a of i){let s=ni.join(r,o+a);try{return await Ie.promises.access(s,Ie.constants.X_OK),!0}catch{}}return!1}catch{return!1}}import ii from"ignore";function xt(o){let e=o.match(/\{([^}]+)\}/);if(!e)return[o];let t=e[1].split(","),n=o.slice(0,e.index),i=o.slice(e.index+e[0].length),r=[];for(let a of t){let s=n+a+i;r.push(...xt(s))}return r}function xe(o,e){if(e.length===0)return!1;let t=[];for(let i of e)t.push(...xt(i));return ii().add(t).ignores(o)}function Ce(o,e){let t=new RegExp(e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g");return o.replace(t,"").replace(/\/\//g,"/")}async function ve(o){return await new Promise((e,t)=>{let n=si.createHash("sha256"),i=ie.createReadStream(o);i.on("error",r=>t(r)),i.on("data",r=>n.update(r)),i.on("end",()=>e(n.digest("hex")))})}function ke(o,e){try{let t=V.resolve(o),n=V.resolve(o,e),i=V.normalize(t),r=V.normalize(n);return r===i||r.startsWith(i+V.sep)?r:null}catch{return null}}function ai(o,e,t){try{let i=ie.readFileSync(o,"utf8").split(`
12
+ `),r=Math.max(0,e-1),a=Math.min(i.length,t);if(r===0&&a>=i.length)return i.map((u,m)=>`L${m+1} ${u}`).join(`
13
+ `);let s=i.slice(r,a),l="";r>0&&(l+=`[Lines 1-${r} omitted]
14
+ `);let c=s.map((u,m)=>`L${r+m+1} ${u}`);return l+=c.join(`
15
+ `),a<i.length&&(l+=`
16
+ [Lines ${a+1}-${i.length} omitted]`),l}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(`Failed to read file ${o}: ${i}`)}}async function wt(o,e,t=[]){var l,c;let{target_file:n}=o,i=o.offset?parseInt((l=o.offset)==null?void 0:l.toString(),10):void 0,r=o.limit?parseInt((c=o.limit)==null?void 0:c.toString(),10):void 0,a=n.startsWith("/")?n.slice(1):n;if(xe(a,t))return{error:`File not found or not accessible: ${n}. Please provide the correct path.`};let s=ke(e,a);if(!s)return{error:`Invalid file path: '${n}'`};if(!await Le(s))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(r!==void 0&&(Number.isNaN(Number(r))||r<1))return{error:`Invalid limit: ${r}. Limit must be a positive number.`};let u=i??1,g=Math.min(r??200,500),h=u+g-1,v=ai(s,u,h),M=await ve(s);return{content:v,sha:M}}catch(u){let m=u.message||String(u);return{error:Ce(`Error reading file: ${m}`,e)}}}async function li(o,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 r=await new Promise((l,c)=>{var v,M;let u=ri(o,i,{cwd:n,shell:!1,stdio:["ignore","pipe","pipe"],env:{LANG:"C",RIPGREP_CONFIG_PATH:""}}),m="",g="";(v=u.stdout)==null||v.on("data",E=>{m+=E.toString()}),(M=u.stderr)==null||M.on("data",E=>{g+=E.toString()});let h=setTimeout(()=>{u.kill("SIGTERM"),c(new Error("rg search operation timed out"))},3e4);u.on("close",E=>{clearTimeout(h),E===1&&!g?l({stdout:"",stderr:""}):E===0||E===1&&!g?l({stdout:m,stderr:g}):c(new Error(`ripgrep exited with code ${E}: ${g}`))}),u.on("error",E=>{clearTimeout(h),c(E)})});if(!r.stdout.trim())return[];let a=r.stdout.trim().split(`
17
+ `),s=[];for(let l of a){let c=l.match(/^([^:]+):(\d+):(.*)$/);if(c){let[,u,m,g]=c,h=u;h=V.relative(n,u);let v=V.join(n,h);try{if(ie.existsSync(v)){let M=await ve(v);s.push({file:h,line_number:parseInt(m,10),content:g,sha:M})}}catch{}}}return s}catch(r){let a=r;if(a.code===1&&!a.stderr)return[];throw a.signal==="SIGTERM"?new Error("rg search operation timed out"):r}}async function bt(o,e,t=[]){var s;let{pattern:n,include:i}=o,r=ht(),a=(s=o.path)!=null&&s.startsWith("/")?o.path.slice(1):o.path;try{if(!await dt(r))throw new Error(`ripgrep is not found or not executable: ${r}`);let c=null;if(a){let g=ke(e,a);if(!g)return{error:`Invalid path: '${a}'. Please provide the correct path.`};if(!await Le(g))return{error:`Path not found or not accessible: ${a}. Please provide the correct path.`};let h=await ie.promises.stat(g);if(h.isFile())c=vt(g),a=vt(a);else if(h.isDirectory())c=g;else return{error:`Path is neither a file nor a directory: ${a}`}}return{matches:(await li(r,n,i,c||e)).filter(g=>{let h=a?yt(a,g.file):g.file;return!xe(h,t)}).slice(0,50).map(g=>({...g,file:a?yt(a,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:Ce(c,e)}}}async function $t(o,e,t=[]){let n=o.pattern,i=o.path;i&&n.startsWith(i)&&(n=n.replace(i,"")),n=n.startsWith("/")?n.slice(1):n;let r;if(i){if(i=i.startsWith("/")?i.slice(1):i,i!=="."&&xe(i,t))return{files:[]};let a=ke(e,i);if(!a)return{error:`Path not found or not accessible: '${i}'. Please provide the correct path.`};r=a}else r=e;if(!await Le(r))return{error:`Directory not found or not accessible: ${i||"."}. Please provide the correct path.`};try{if(!(await ie.promises.stat(r)).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 ci(n,i,e)).filter(l=>!xe(l,t)).slice(0,50)}}catch(a){let s=typeof a=="object"&&a!==null&&"message"in a&&typeof a.message=="string"?a.message:String(a);return{error:Ce(s,e)}}}async function ci(o,e,t){try{let n;if(e){let a=e.startsWith("/")?e.slice(1):e,s=ke(t,a);if(!s)throw new Error(`Invalid directory path: potential directory traversal detected in '${a}'`);n=s}else n=t;let i=await oi(o,{cwd:n,nodir:!0,ignore:["node_modules/**",".git/**","**/__pycache__/**"],absolute:!1}),r=[];for(let a of i)try{let s=V.join(n,a),l=await ie.promises.stat(s);l.isFile()&&r.push({path:e?V.join(e,a):a,mtime:l.mtime})}catch{}return r.sort((a,s)=>s.mtime.getTime()-a.mtime.getTime()),r.map(a=>a.path)}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(Ce(`Glob search failed: ${i}`,t))}}function ye(o){return{...o,range:{start:{line:o.range.start.line-1,column:o.range.start.column-1},end:{line:o.range.end.line-1,column:o.range.end.column-1}}}}function re(o){return{...o,range:{start:{line:o.range.start.line+1,column:o.range.start.column+1},end:{line:o.range.end.line+1,column:o.range.end.column+1}}}}var Fe=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,r=this.getBatchSize(),a=[];for(let s=0;s<e.length;s+=r){let l=e.slice(s,s+r),c=await this.processBatch(l,n,i);a.push(...c)}return a}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=St.resolve(this.environment.getWorkspaceRoot(),l),u=await Mt(c,"utf-8"),g=(await mi(this.language,u)).root().findAll({rule:t,language:this.language,constraints:n});if(g.length===0)return[];let h=await ve(c);return g.map(v=>({filePath:l,sgNode:v,sha:h}))}),a=(await Promise.all(i)).flat(),s=[];for(let{filePath:l,sgNode:c,sha:u}of a){let m=c.range(),g={file:{path:l,sha:u},text:c.text(),range:{start:{line:m.start.line,column:m.start.column},end:{line:m.end.line,column:m.end.column}},language:this.language};s.push(re(g))}return s}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=St.resolve(this.environment.getWorkspaceRoot(),e.file.path),[i,r]=await Promise.all([Mt(n,"utf-8"),ve(n)]),a=ui(this.language,i).root(),s=ye(e),l=this.findNodeAtPosition(a,s.range.start.line,s.range.start.column);if(!l)return null;let c=l;for(;c;){let u=c.kind(),m=typeof u=="string"?u:String(u);if(t.has(m)){let g=c.range(),h={file:{path:e.file.path,sha:r},text:c.text(),range:{start:{line:g.start.line,column:g.start.column},end:{line:g.end.line,column:g.end.column}},language:this.language};return re(h)}c=c.parent()}return null}findNodeAtPosition(e,t,n){let i=e.range(),r=t>i.start.line||t===i.start.line&&n>=i.start.column,a=t<i.end.line||t===i.end.line&&n<=i.end.column;if(!r||!a)return null;let s=e.children();for(let l of s){let c=this.findNodeAtPosition(l,t,n);if(c)return c}return e}};import{spawn as fi}from"child_process";import Ye from"fs/promises";import{createRequire as hi}from"module";import di from"os";import Et from"path";import{readFile as gi}from"fs/promises";import pi from"path";async function se(o,e,t,n=!1){let i=pi.resolve(o,e),a=(await gi(i,"utf-8")).split(`
18
+ `),{start:s,end:l}=t,c=s.line-1,u=l.line-1;if(s.line===l.line){let g=a[c]||"";return n?g.substring(s.column-1,l.column-1):g}let m=[];for(let g=c;g<=u&&g<a.length;g++){let h=a[g];g===c?m.push(n?h.substring(s.column-1):h):g===u?m.push(n?h.substring(0,l.column-1):h):m.push(h)}return m.join(`
19
+ `)}var xi=hi(import.meta.url?import.meta.url:__filename),{scip:De}=xi("@sourcegraph/scip-root/bindings/typescript/scip.js"),Pt=/^\+ (.+?) \((\d+)ms\)$/;function vi(o,e,t,n,i){let r=o.toString();e+=r;let a=e.split(`
20
+ `),s=a.pop()||"",l=null;for(let c of a){let u=c.trim();u.match(Pt)&&(l=u)}for(let c of a){let u=c.trim();if(!u)continue;let m=u.match(Pt);if(m){if(u===l){let[,g,h]=m,v=Et.relative(i,g);t.indexingProgress(n,`Indexed ${v}`,v,parseInt(h,10))}}else t.indexingProgress(n,u)}return s}var Te=class{environment;storage;language;scipIndex=null;indexingPromise=null;eventEmitter;constructor(e,t,n){this.environment=e,this.storage=new ee(e),this.language=t,this.eventEmitter=n||new G}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=Et.join(di.tmpdir(),`scip-index-${this.language}-${Date.now()}.tmp`);this.eventEmitter.startIndexing(this.language);let n=this.getScipCommand(),i=fi(n,["index","--output",t],{cwd:this.environment.getWorkspaceRoot(),stdio:["ignore","pipe","pipe"],env:process.env}),r="";i.stdout.on("data",s=>{r=vi(s,r,this.eventEmitter,this.language,this.environment.getWorkspaceRoot())});let a="";i.stderr.on("data",s=>{a+=s.toString(),process.stderr.write(s)}),await new Promise((s,l)=>{i.on("close",async c=>{c===0?(await Ye.rename(t,e),s()):(await Ye.unlink(t).catch(()=>{}),l(new Error(`${n} index exited with code ${c}, stderr: ${a}`)))}),i.on("error",async c=>{await Ye.unlink(t).catch(()=>{}),l(c)})}),a&&console.error("SCIP indexing stderr:",a),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=ye(e);this.eventEmitter.startScipMatchLookup(this.language,t);let n=[],i=this.scipIndex.documents;for(let r of i){if(r.relative_path!==t.file.path)continue;this.eventEmitter.scipMatchLookupProgress(this.language,r);let a=this.findBestOverlappingOccurrence(r.occurrences,t);if(a){if(this.isExternalSymbol(a.symbol))continue;this.eventEmitter.scipMatchLookupProgress(this.language,a);let s=await this.findDefinitionForSymbol(a.symbol,i);s&&n.push(re(s))}}return n}async findReferences(e){if(await this.ensureIndexReady(),!this.scipIndex)return[];let t=ye(e),n=[],i=this.scipIndex.documents;for(let r of i){if(r.relative_path!==t.file.path)continue;let a=this.findBestOverlappingOccurrence(r.occurrences,t);if(a){if(this.isExternalSymbol(a.symbol))break;let s=await this.findReferencesForSymbol(a.symbol,i);n.push(...s.map(l=>re(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 r=i.range;if(!r||r.length<3||!this.rangesOverlap(r,t))continue;let a=0;if(!this.rangeFullyInside(r,t))continue;let s=(r.length===3,r[1]);a+=s*1e3,i.symbol_roles!==De.SymbolRole.Definition&&(a+=100),n.push({occurrence:i,score:a})}return n.length===0?null:(n.sort((i,r)=>r.score-i.score),n[0].occurrence)}rangesOverlap(e,t){if(e.length===3){let[n,i,r]=e;return t.range.start.line===t.range.end.line?n===t.range.start.line&&!(r<=t.range.start.column||i>=t.range.end.column):!(n<t.range.start.line||n>t.range.end.line||n===t.range.start.line&&r<=t.range.start.column||n===t.range.end.line&&i>=t.range.end.column)}else if(e.length===4){let[n,i,r,a]=e;return!(r<t.range.start.line||n>t.range.end.line)}return!1}rangeFullyInside(e,t){if(e.length===3){let[n,i,r]=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&&r>t.range.end.column)}else if(e.length===4){let[n,i,r,a]=e;return!(n<t.range.start.line||r>t.range.end.line||n===t.range.start.line&&i<t.range.start.column||r===t.range.end.line&&a>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===De.SymbolRole.Definition){let r=i.range??[],a=r[0]??0,s=r[1]??0,l=r.length===4?r[2]:a,c=r.length===4?r[3]:r[2]??s,u=await se(this.environment.getWorkspaceRoot(),n.relative_path,{start:{line:a,column:s},end:{line:l,column:c}});return{language:this.language,file:{path:n.relative_path,sha:""},range:{start:{line:a,column:s},end:{line:l,column:c}},text:u}}return null}async findReferencesForSymbol(e,t){let n=[];for(let i of t)for(let r of i.occurrences)if(r.symbol===e&&r.symbol_roles!==De.SymbolRole.Definition){let a=r.range??[],s=a[0]??0,l=a[1]??0,c=a.length===4?a[2]:s,u=a.length===4?a[3]:a[2]??l,m=await se(this.environment.getWorkspaceRoot(),i.relative_path,{start:{line:s,column:l},end:{line:c,column:u}});n.push({language:this.language,file:{path:i.relative_path,sha:""},range:{start:{line:s,column:l},end:{line:c,column:u}},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 De.Index.deserialize(e)}};var oe=class extends Error{constructor(e,t){super(`${e} is not supported: ${t}`),this.name="LanguageBackendNotSupportedError"}},ae=class{astProvider;intelligenceProvider;language;constructor(e,t,n){this.language=t,this.astProvider=new Fe(e,t),this.intelligenceProvider=new Te(e,t,n)}async findMatches(e,t){if(!this.astProvider)throw new oe("findMatches","no AST provider configured for this language");return await this.astProvider.findMatches(e,t)}async findDefinitions(e){if(!this.intelligenceProvider)throw new oe("findDefinitions","no intelligence provider configured for this language");return await this.intelligenceProvider.findDefinitions(e)}async findReferences(e){if(!this.intelligenceProvider)throw new oe("findReferences","no intelligence provider configured for this language");return await this.intelligenceProvider.findReferences(e)}async expandMatch(e){if(!this.astProvider)throw new oe("expandMatch","no AST provider configured for this language");return await this.astProvider.expandMatch(e)}};var _e=class{environment;eventEmitter;constructor(e,t){this.environment=e,this.eventEmitter=t}async execute(e,t,n){let i=Date.now(),r=n.filePaths.filter(m=>ft(m)===t),l=(await new ae(this.environment,t,this.eventEmitter).findMatches(r,e)).map(m=>{if(yi.isAbsolute(m.file.path))throw new Error(`Match provider returned absolute path: ${m.file.path}. 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 r of e){let a=t.find(l=>this.isMatchInRange(r,l));if(!a)continue;let s=[...a.evidence||[],{file:a.file,text:a.text,range:a.range,language:a.language}];i.push({...r,evidence:s})}return i}isMatchInRange(e,t){if(e.file.path!==t.file.path)return!1;let n=e.range.start,i=e.range.end,r=t.range.start,a=t.range.end,s=n.line>r.line||n.line===r.line&&n.column>=r.column,l=i.line<a.line||i.line===a.line&&i.column<=a.column;return s&&l}deduplicateMatches(e){if(e.length<=1)return e;let t=[];for(let n of e)e.some(r=>n===r?!1:this.isMatchInRange(n,r))||t.push(n);return t}};import wi from"path";import bi from"ignore";var Ae=class{maxDepth=1;environment;eventEmitter;constructor(e,t){this.environment=e,this.eventEmitter=t||new G}async execute(e,t,n={}){let i=Date.now(),r=new ae(this.environment,t,this.eventEmitter),a=await this.followDefinitions(e,r,this.maxDepth,n.where,t),s=Date.now()-i,l=a.map(c=>{let{depth:u,sourceMatch:m,...g}=c,h=m?[{file:m.file,text:m.text,range:m.range,language:m.language},...m.evidence||[]]:[];return{...g,source:h}});return{definitions:l,totalDefinitions:l.length,executionTime:s}}async followDefinitions(e,t,n,i,r){let a=[],s=new Set;for(let l of e)await this.followDefinitionsRecursive(l,t,0,n,i,a,s,l,r);return a}async followDefinitionsRecursive(e,t,n,i,r,a,s,l,c){if(n>=i)return;let u=`${e.file.path}:${e.range.start.line}:${e.range.start.column}`;if(s.has(u))return;s.add(u);let m=await t.findDefinitions(e);for(let g of m){let v={...await t.expandMatch(g)??g,language:c,depth:n,sourceMatch:l||e};r&&!this.matchesDefinitionFilter(v,r)||(a.push(v),n+1<i&&await this.followDefinitionsRecursive(g,t,n+1,i,r,a,s,l||e,c))}}matchesDefinitionFilter(e,t){var n;return t.all?t.all.every(i=>this.matchesDefinitionFilter(e,i)):t.any?t.any.some(i=>this.matchesDefinitionFilter(e,i)):t.not?!this.matchesDefinitionFilter(e,t.not):!(t.file&&!this.matchesFileSpec(e.file.path,t.file)||(n=t.function)!=null&&n.name)}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=wi.relative(this.environment.getWorkspaceRoot(),e);return t.path?n===t.path:t.glob?bi().add(t.glob).ignores(n):t.regex?new RegExp(t.regex).test(n):!0}};import q from"big.js";var Oe=class{environment;eventEmitter;config;maxIterations;constructor(e,t,n,i){this.environment=t,this.eventEmitter=n||new G,this.config=e,this.maxIterations=i||20}async executeTool(e,t,n,i){switch(t){case"read":{this.eventEmitter.llmToolExecution(e,"read",{file:n.target_file,offset:n.offset,limit:n.limit});let r=await wt(n,this.environment.getWorkspaceRoot());if(!("error"in r)){let a=n.target_file;i.some(s=>s.path===a)||i.push({path:a,sha:r.sha})}return"error"in r?r:r.content}case"grep":{this.eventEmitter.llmToolExecution(e,"grep",{path:n.path,pattern:n.pattern});let r=await bt(n,this.environment.getWorkspaceRoot());if(!("error"in r))for(let a of r.matches)i.some(s=>s.path===a.file)||i.push({path:a.file,sha:a.sha});return"error"in r?r:JSON.stringify(r.matches)}case"glob":{this.eventEmitter.llmToolExecution(e,"glob",{path:n.path,pattern:n.pattern});let r=await $t(n,this.environment.getWorkspaceRoot());return"error"in r?r:JSON.stringify(r.files)}default:return{error:`Unknown tool: ${t}`}}}async hydrateMatchesWithFileContent(e){let t=[];for(let n of e){let i=await se(this.environment.getWorkspaceRoot(),n.file.path,n.range),r=n.evidence;if(n.evidence&&n.evidence.length>0){r=[];for(let a of n.evidence){let s=await se(this.environment.getWorkspaceRoot(),a.file.path,a.range);r.push({...a,text:s})}}t.push({...n,text:i,evidence:r})}return t}async executeLLMValidation(e,t,n){let i=Date.now(),r=this.config.getLLMProvider();this.eventEmitter.startLLMValidation(e.id,t.length);let a=[],s=[],l=new q(0),c=new q(0),u=new q(0),m;for(let E=0;E<this.maxIterations;E++){let C=await r.search({rule:e,matches:t,tools:n,messages:a,files:s});if(C.usage&&(l=l.plus(new q(C.usage.inputTokens)),c=c.plus(new q(C.usage.outputTokens)),u=u.plus(new q(C.usage.cost))),C.type==="matches"){let R=await this.hydrateMatchesWithFileContent(C.matches);m={...C,matches:R};break}if(C.type==="request_files"){for(let R of C.files)s.some(b=>b.path===R.path)||s.push({path:R.path,sha:R.sha});continue}if(C.type==="tool_request"){a.push({type:"tool_request",tool_calls:C.tool_calls});let R=await Promise.all(C.tool_calls.map(async b=>{let L=JSON.parse(b.function.arguments),x=await this.executeTool(e.id,b.function.name,L,s),d=typeof x=="string"?x:JSON.stringify(x);return{tool_call_id:b.id,content:d}}));for(let b of R)a.push({type:"tool_response",tool_call_id:b.tool_call_id,content:b.content});continue}}if(!m)throw new Error(`Max iterations (${this.maxIterations}) reached without getting matches`);let g=m.matches;if(g.length===0){let E=l.plus(c).toNumber();return this.eventEmitter.completeLLMValidation(e.id,E,l.toNumber(),c.toNumber(),u.toString(),Date.now()-i,0,!1),{matches:[],executionTime:Date.now()-i}}this.eventEmitter.startLLMValidationPhase(e.id,g.length);let h=await r.validate({rule:e,matches:g,files:s});h.usage&&(l=l.plus(new q(h.usage.inputTokens)),c=c.plus(new q(h.usage.outputTokens)),u=u.plus(new q(h.usage.cost)));let v=await this.hydrateMatchesWithFileContent(h.matches),M=l.plus(c).toNumber();return this.eventEmitter.completeLLMValidation(e.id,M,l.toNumber(),c.toNumber(),u.toString(),Date.now()-i,v.length,!1),{matches:v,executionTime:Date.now()-i}}async execute(e,t,n){return t.length===0?{matches:[],executionTime:0}:await this.executeLLMValidation(e,t,n)}};var Ne=class{fileFilterStep;findMatchesStep;gotoDefinitionStep;llmStep;environment;executionContext;currentMode;eventEmitter;config;constructor(e,t,n){this.environment=t,this.eventEmitter=n||new G,this.config=e,this.fileFilterStep=new Re(this.environment),this.findMatchesStep=new _e(this.environment,this.eventEmitter),this.gotoDefinitionStep=new Ae(this.environment,this.eventEmitter),this.llmStep=new Oe(this.config,this.environment,this.eventEmitter)}async executeStep(e,t,n,i,r,a,s){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:r})).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 r.length===0?{matches:[]}:{matches:(await this.llmStep.execute(e,r,n.tools??[])).matches};throw new Error(`Unknown step type: ${l}`)}async executeRule(e,t){let n=e.id,i=e.config,r=[...this.executionContext.filePaths],a=[];for(let l of i.steps){let c=l.id,u=l.step;this.eventEmitter.startStep(n,c,u.type,{filePaths:r,matches:a});let m=Date.now(),g=await this.executeStep(e,c,u,r,a,t),h=Date.now()-m;if(this.eventEmitter.completeStep(n,c,u.type,g,h),g.filePaths!==void 0&&(r=this.executionContext.filterFiles(g.filePaths),a=this.executionContext.filterMatchesByFilePaths(a,r)),g.matches!==void 0&&(a=this.executionContext.filterMatches(g.matches)),r.length===0)break}let s=a.sort((l,c)=>l.file.path.localeCompare(c.file.path));return{ruleId:n,matches:s}}getPoolSize(){let e=$i().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 Ee.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=[],r=[...e],a=async u=>{let m=u.id,g=!1;for(let v of u.config.steps)if(v.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 h=await this.executeRule(u,t);return this.eventEmitter.emit("rules:rule-complete",{ruleId:m}),h};await(async()=>{let u=async()=>{if(r.length===0)return;let g=r.shift(),h=await a(g);i.push(h),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,h=l.get(m.ruleId)??0;return g-h});let c=i.reduce((u,m)=>u+m.matches.length,0);return this.eventEmitter.completeRules(e.length,c),i}};var Ge=class extends Error{constructor(t,n,i){super(`Invalid rule format in '${t}': ${n}`);this.validationErrors=i;this.name="InvalidRuleFormatError"}};import{stripVTControlCharacters as Ct}from"node:util";import f from"chalk";import Mi from"ora";var H="#f87171",J="#60a5fa",Si="#9b59b6",D="#fbbf24",Qe="#9ca3af",be="#9b59b6",Be="\u25A0",we="\u25CF",$e="\u2B25";function Pi(o,e){if(e){let t=`https://app.wispbit.com/rules/${e}`;return f.dim(`\x1B]8;;${t}\x1B\\${o}\x1B]8;;\x1B\\`)}return f.dim(o)}function kt(o,e){return o.length<=e?o:o.substring(0,e-3)+"..."}var Rt="#f8f8f8",Lt="#888888",Ei=[f.hex(D)("~(oo)~"),f.hex(D)("~(oO)~"),f.hex(D)("~(Oo)~"),f.hex(D)("~(OO)~"),f.hex(D)("~(\u25CFo)~"),f.hex(D)("~(o\u25CF)~"),f.hex(D)("~(\u25C9o)~"),f.hex(D)("~(o\u25C9)~"),f.hex(D)("\\(oo)/"),f.hex(D)("\\(oO)/"),f.hex(D)("\\(Oo)/"),f.hex(D)("\\(OO)/"),f.hex(D)("~(Oo)~"),f.hex(D)("~(oO)~"),f.hex(D)("~(\u25CEo)~"),f.hex(D)("~(o\u25CE)~"),f.hex(D)("~(oo)~"),f.hex(D)("~(OO)~"),f.hex(D)("~(Oo)~"),f.hex(D)("\\(oo)/"),f.hex(D)("\\(oO)/"),f.hex(D)("\\(Oo)/"),f.hex(D)("\\(OO)/")];function It(o,e){return e===1?o:`${o}s`}function Ri(o,e={}){let n=e.align||[],i=e.stringLength||(a=>Ct(a).length),r=o.reduce((a,s)=>(s.forEach((l,c)=>{let u=i(l);(!a[c]||u>a[c])&&(a[c]=u)}),a),[]);return o.map(a=>a.map((s,l)=>{let c=r[l]-i(s)||0,u=Array(Math.max(c+1,1)).join(" ");return n[l]==="r"?u+s:s+u}).join(" ").trimEnd()).join(`
21
+ `)}function Ft(o,e){let t=`
22
+ `,n=0,i=0,r=new Map;o.forEach(v=>{if(v.matches.length===0)return;let M=v.ruleId||"unknown";r.has(M)||r.set(M,{message:v.message,severity:v.severity,internalId:v.internalId,execution:v.execution,matches:[]});let E=r.get(M);v.matches.forEach(C=>{var R;E.matches.push({file:{path:C.file.path,sha:C.file.sha},line:C.range.start.line,column:C.range.start.column,endLine:C.range.end.line!==C.range.start.line?C.range.end.line:void 0,text:C.text,description:C.description,evidence:(R=C.evidence)==null?void 0:R.map(b=>({file:{path:b.file.path,sha:b.file.sha},text:b.text,range:b.range,description:b.description}))}),v.severity==="violation"?n++:i++})}),r.forEach((v,M)=>{if(v.matches.length===0&&v.severity!=="skipped")return;let E;v.severity==="violation"?E=f.hex(H)(Be):v.severity==="suggestion"?E=f.hex(J)(we):E=f.hex(Si)(we);let C=v.execution==="llm"?`${f.hex(be)($e)} `:"",R;if(v.internalId){let L=`https://app.wispbit.com/rules/${v.internalId}`;R=f.bold(`\x1B]8;;${L}\x1B\\${M}\x1B]8;;\x1B\\`)}else R=f.bold(M);let b=`${E} ${C}${R} ${v.message.replace(/([^ ])\.$/u,"$1")}`;if(t+=`${b}
23
+ `,t+=`${f.dim("\u2500".repeat(80))}
24
+ `,v.severity==="skipped")t+=`
25
+ `;else{let L=v.matches.sort((d,P)=>d.file.path.localeCompare(P.file.path));L.length<10&&L.some(d=>d.text&&d.text.split(`
26
+ `).length<=30)?L.forEach((d,P)=>{if(d.text&&d.text.split(`
27
+ `).length<=30){let $=d.description?` - ${f.dim(d.description)}`:"";t+=` ${d.file.path}${$}
28
+ `,d.text.split(`
29
+ `).forEach((S,O)=>{let k=d.line+O,_=f.bgHex(Rt).hex(Lt)(` ${String(k)} `);t+=` ${_} ${f.dim(S)}
30
+ `})}else{let $=d.endLine?`(${d.line}:${d.endLine})`:`(${d.line})`,w=d.description?` - ${f.dim(d.description)}`:"";t+=` ${d.file.path} ${f.dim($)}${w}
31
+ `}d.evidence&&d.evidence.length>0&&d.evidence.forEach($=>{if($.text&&$.text.split(`
32
+ `).length<=30){let w=$.description?` - ${f.dim($.description)}`:"";t+=` ${$.file.path}${w}
33
+ `,$.text.split(`
34
+ `).forEach((O,k)=>{let _=$.range.start.line+k,We=f.bgHex(Rt).hex(Lt)(` ${String(_)} `);t+=` ${We} ${f.dim(O)}
35
+ `})}else{let w=`${$.range.start.line}:${$.range.end.line}`,S=$.description?` - ${f.dim($.description)}`:"";t+=` ${$.file.path} ${f.dim(`(${w})`)}${S}
36
+ `}}),P<L.length-1&&(t+=`
37
+ `)}):L.forEach(d=>{let P=d.endLine?`(${d.line}:${d.endLine})`:`(${d.line})`,$=d.description?` - ${f.dim(d.description)}`:"";t+=` ${d.file.path} ${f.dim(P)}${$}
38
+ `,d.evidence&&d.evidence.length>0&&d.evidence.forEach(w=>{let S=`${w.range.start.line}:${w.range.end.line}`,O=w.description?` - ${f.dim(w.description)}`:"";t+=` ${w.file.path} ${f.dim(`(${S})`)}${O}
39
+ `})}),t+=`
40
+
41
+ `}}),console.log(t);let a=n+i,s=n>0?`${f.dim("violations".padStart(12))} ${f.hex(H)(Be)} ${f.hex(H).bold(n)}`:`${f.dim("violations".padStart(12))} ${f.dim(n)}`,l=i>0?`${f.dim("suggestions".padStart(12))} ${f.hex(J)(we)} ${f.hex(J).bold(i)}`:`${f.dim("suggestions".padStart(12))} ${f.dim(i)}`,c=e.totalFiles?`${e.totalFiles} ${It("file",e.totalFiles)}`:"0 files",u=`${e.totalRules} ${It("rule",e.totalRules)}`,m=e.executionTime?`${Math.round(e.executionTime)}ms`:"",g=[c,u,m].filter(Boolean).join(", "),h="";h+=`${s}
42
+ `,h+=`${l}
43
+ `,h+=`${f.dim("summary".padStart(12))} ${g}
44
+ `,console.log(a>0?f.reset(h):h),n>0&&process.exit(1)}function Dt(o){if(o.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 a of o){let s=a.config.severity==="violation"?Be:we,l=a.config.severity==="violation"?f.hex(H).bold:f.hex(J).bold,u=(a.config.severity==="violation"?f.hex(H):f.hex(J))(s)+" "+l(a.config.severity),m=Pi(a.id,a.internalId),g=a.config.execution==="llm"?f.hex(be)($e)+" "+f.hex(be)("llm"):f.hex(Qe)($e)+" "+f.hex(Qe)("deterministic"),h=kt(a.config.message,i),v=Math.max(0,12-a.id.length),M=Math.max(0,16-(a.config.severity.length+2)),E=Math.max(0,18-(a.config.execution.length+2));e.push(["",`${m}${" ".repeat(v)} ${f.dim("\u2502")} ${u}${" ".repeat(M)} ${f.dim("\u2502")} ${g}${" ".repeat(E)} ${f.dim("\u2502")} ${h}`])}let r=Ri(e,{align:["","l"],stringLength(a){return Ct(a).length}});console.log(`
45
+ `+r),console.log(f.dim(`
46
+ Use 'wispbit check --rule <rule-id>' to run a specific rule.`))}function Tt(o,e="pretty"){let t=[];for(let n of o)for(let i of n.matches){let r={file:i.file,range:i.range,language:i.language,text:i.text,evidence:i.evidence,ruleId:n.ruleId,internalId:n.internalId,severity:n.severity,message:n.message};t.push(r)}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 _t(o,e=!1){let t=null,n=null,i=null,r=new Map,a=()=>{if(!t)return;let s=[],l=process.stdout.columns||120;for(let[u,{isLlm:m,message:g,severity:h,dotAnimationFrame:v,toolInfo:M,validationPhase:E}]of r){if(M||E){let $=r.get(u);$.dotAnimationFrame=($.dotAnimationFrame+1)%3}let C=v+1,R=f.hex(D)(".".repeat(C).padEnd(3," ")),b=h==="violation"?f.hex(H)(Be):f.hex(J)(we),L=m?`${f.hex(be)($e)} ${f.hex(be)("llm")}`:f.hex(Qe)($e),x=Math.max(40,l-50),d=kt(g,x),P=`${b} ${L} ${f.dim(u)} ${d}`;if(E){let $=` ${R} ${f.dim(`validating ${E.matchCount} ${E.matchCount===1?"match":"matches"}`)}`;s.push(`${P}
47
+ ${$}`)}else if(M)if(M.toolName==="read"&&M.file){let $="";if(M.offset||M.limit){let S=M.offset?parseInt(M.offset,10):1,O=M.limit?parseInt(M.limit,10):200,k=S+O-1;$=` ${f.dim(`(lines ${S}-${k})`)}`}let w=` ${R} ${f.dim("tool: read")} ${M.file}${$}`;s.push(`${P}
48
+ ${w}`)}else if(M.toolName==="grep"){let $=M.path||".",w=M.pattern||"",S=` ${R} ${f.dim("tool: grep")} ${$} ${f.dim(`for "${w}"`)}`;s.push(`${P}
49
+ ${S}`)}else if(M.toolName==="glob"){let $=M.path||".",w=M.pattern||"",S=` ${R} ${f.dim("tool: glob")} ${$} ${f.dim(`for "${w}"`)}`;s.push(`${P}
50
+ ${S}`)}else s.push(P);else s.push(P)}let c="";if(i&&(i.mode==="check"?c=i.filePath?` ${f.dim(`(${i.filePath})`)}`:"":i.mode==="diff"&&(c=` ${f.dim(`(${i.baseCommit} \u2192 ${i.headCommit})`)}`)),s.length>0){let u=s.map((m,g)=>g===0?m:m.split(`
51
+ `).map((v,M)=>M===0?` ${v}`:v).join(`
52
+ `));t.text=`${u.join(`
53
+ `)}${c}`}else t.text=`${c}`};return o.on("execution:mode",s=>{i=s}),o.on("rules:start",s=>{t=Mi({spinner:{interval:120,frames:Ei},color:!1}).start();let l=()=>{t&&t.stopAndPersist(),process.exit(130)};process.on("SIGINT",l),process.on("SIGTERM",l);let c=setInterval(()=>{a()},200);t._updateInterval=c}),o.on("rules:rule-start",s=>{r.set(s.ruleId,{isLlm:s.isLlm,message:s.message,severity:s.severity,dotAnimationFrame:0}),a()}),o.on("rules:rule-complete",s=>{r.delete(s.ruleId),a()}),o.on("llm:tool-execution",s=>{let l=r.get(s.ruleId);l&&(l.toolInfo={toolName:s.toolName,file:s.file,path:s.path,pattern:s.pattern,offset:s.offset,limit:s.limit},a())}),o.on("llm:validation-phase:start",s=>{let l=r.get(s.ruleId);l&&(l.toolInfo=void 0,l.validationPhase={matchCount:s.matchCount},a())}),o.on("rules:progress",s=>{a()}),o.on("rules:complete",()=>{t&&(t._updateInterval&&clearInterval(t._updateInterval),t.stopAndPersist(),t=null),r.clear()}),o.on("files:discovery:start",s=>{e&&console.log(`
54
+ ${f.blue("Discovering files")} in ${f.bold(s.mode)} mode...`)}),o.on("files:discovery:progress",s=>{e&&console.log(` ${s.message}`)}),o.on("files:discovery:complete",s=>{e&&console.log(f.green("File discovery complete:"),`${s.totalFiles} files found (${s.executionTime}ms)`)}),o.on("files:filter",s=>{e&&s.originalCount!==s.filteredCount&&console.log(f.yellow("Filtered:"),`${s.originalCount} \u2192 ${s.filteredCount} ${s.filterType}`)}),o.on("indexing:start",s=>{n=Date.now()}),o.on("indexing:progress",s=>{t&&(t.color="yellow"),n&&Date.now()-n>1e3&&t&&(t.text=`Indexing ${s.language}...`),t&&(s.packageName&&s.timeMs?t.text=`Indexing ${s.language}: ${s.packageName}`:s.message!=="Indexing files..."&&(t.text=`Indexing ${s.language}: ${s.message}`)),e&&(s.packageName&&s.timeMs?console.log(` + ${s.packageName} (${s.timeMs}ms)`):s.message==="Indexing files..."?process.stdout.write("."):s.message!=="Indexing files..."&&console.log(` ${s.message}`))}),o.on("indexing:complete",s=>{n=null,t&&(t.color="blue"),e&&(process.stdout.write(`
55
+ `),console.log(f.green("Indexing complete for"),`${s.language} (${s.executionTime}ms)`))}),e&&(o.on("scip:match-lookup:start",s=>{console.log(f.cyan(`
56
+ \u{1F50D} [${s.language}] Starting SCIP match lookup for: ${JSON.stringify(s.match)}`))}),o.on("scip:match-lookup:progress",s=>{console.log(`${JSON.stringify(s.document)}`)}),o.on("scip:match-lookup:complete",s=>{console.log(f.green(`SCIP match lookup complete for: ${s.language}`))}),o.on("step:start",s=>{console.log(f.cyan(`
57
+ \u{1F527} [${s.ruleId}] Starting step: ${s.stepName} (${s.stepType})`)),console.log(` Input files: ${s.inputs.filePaths.length}`),console.log(` Input matches: ${s.inputs.matches.length}`),s.inputs.matches.length>0&&(console.log(" Match details:"),s.inputs.matches.forEach((l,c)=>{var u;console.log(` Match ${c+1}:`),console.log(` File: ${l.file.path}`),console.log(` Range: ${l.range.start.line}:${l.range.start.column} \u2192 ${l.range.end.line}:${l.range.end.column}`),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.range.start.line} \u2192 ${m.range.end.line}`)})),console.log(` Text preview: ${(u=l.text)==null?void 0:u.substring(0,120)}...`)}))}),o.on("step:complete",s=>{console.log(f.cyan(`\u2705 [${s.ruleId}] Completed step: ${s.stepName} (${s.stepType}) (${s.executionTime}ms)`)),s.outputs.filePaths!==void 0&&console.log(` Output files: ${s.outputs.filePaths.length}`),s.outputs.matches!==void 0&&(console.log(` Output matches: ${s.outputs.matches.length}`),s.outputs.matches.length>0&&(console.log(" Output match details:"),s.outputs.matches.forEach((l,c)=>{var u;console.log(` Match ${c+1}:`),console.log(` File: ${l.file.path}`),console.log(` Range: ${l.range.start.line}:${l.range.start.column} \u2192 ${l.range.end.line}:${l.range.end.column}`),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.range.start.line} \u2192 ${m.range.end.line}`)})),console.log(` Text preview: ${(u=l.text)==null?void 0:u.substring(0,120)}...`)})))}),o.on("test:start",s=>{console.log(f.magenta(`
58
+ \u{1F9EA} [${s.ruleId}] Running test: "${s.testName}"`))}),o.on("test:matches",s=>{console.log(f.magenta(` Found ${s.matches.length} match(es):`)),s.matches.forEach((l,c)=>{var u;console.log(` Match ${c+1}:`),console.log(` File: ${l.file.path}`),console.log(` Range: ${l.range.start.line}:${l.range.start.column} \u2192 ${l.range.end.line}:${l.range.end.column}`),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.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.stopAndPersist(),t=null),r.clear()}}import Li from"readline";import ue from"chalk";import Ii from"ora";import y from"chalk";var I="#fbbf24",le="#ff6b35",W="#4a90e2",ce="#9b59b6",Xe=[`
59
+ ${y.hex(I)(" \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")}
60
+ ${y.hex(I)(" \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")}
61
+ ${y.hex(I)(" \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 ")}
62
+ ${y.hex(I)(" \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 ")}
63
+ ${y.hex(I)(" \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 ")}
64
+ ${y.hex(I)(" \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 ")}
65
+ `,`
66
+ ${y.hex(W)(" \u2588\u2588\u2557 ")}${y.hex(I)("\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")}
67
+ ${y.hex(W)(" \u2588\u2588\u2551 ")}${y.hex(I)("\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")}
68
+ ${y.hex(W)(" \u2588\u2588\u2551 \u2588\u2557 ")}${y.hex(I)("\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 ")}
69
+ ${y.hex(W)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557")}${y.hex(I)("\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 ")}
70
+ ${y.hex(W)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588")}${y.hex(I)("\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 ")}
71
+ ${y.hex(W)(" \u255A\u2550\u2550\u255D\u255A\u2550")}${y.hex(I)("\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 ")}
72
+ `,`
73
+ ${y.hex(I)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557")}${y.hex(ce)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${y.hex(I)("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
74
+ ${y.hex(I)(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551")}${y.hex(ce)("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}${y.hex(I)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
75
+ ${y.hex(I)(" \u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551")}${y.hex(ce)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}${y.hex(I)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
76
+ ${y.hex(I)(" \u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551")}${y.hex(ce)("\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255D ")}${y.hex(I)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
77
+ ${y.hex(I)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551")}${y.hex(ce)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ")}${y.hex(I)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
78
+ ${y.hex(I)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D")}${y.hex(ce)("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ")}${y.hex(I)("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
79
+ `,`
80
+ ${y.hex(I)(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${y.hex(le)("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
81
+ ${y.hex(I)(" \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")}${y.hex(le)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
82
+ ${y.hex(I)(" \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")}${y.hex(le)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
83
+ ${y.hex(I)(" \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 ")}${y.hex(le)("\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
84
+ ${y.hex(I)(" \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 ")}${y.hex(le)("\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 ")}
85
+ ${y.hex(I)(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D ")}${y.hex(le)("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ")}
86
+ `,`
87
+ ${y.hex(I)(" \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")}${y.hex(W)("\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
88
+ ${y.hex(I)(" \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")}${y.hex(W)("\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D")}
89
+ ${y.hex(I)(" \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")}${y.hex(W)(" \u2588\u2588\u2551 ")}
90
+ ${y.hex(I)(" \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")}${y.hex(W)(" \u2588\u2588\u2551 ")}
91
+ ${y.hex(I)(" \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")}${y.hex(W)(" \u2588\u2588\u2551 ")}
92
+ ${y.hex(I)(" \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")}${y.hex(W)(" \u255A\u2550\u255D ")}
93
+ `];function Ci(){process.stdout.write("\x1B[2J\x1B[H")}function ki(o){return new Promise(e=>setTimeout(e,o))}async function Fi(o=3e3){let e=Ii({text:"",spinner:{interval:100,frames:Xe}}).start();await ki(o),e.stop()}async function Di(o){let e=Li.createInterface({input:process.stdin,output:process.stdout});return await new Promise(t=>{e.question(o,n=>{e.close(),t(n.trim())})})}async function At(){Ci(),await Fi(1500),console.log(Xe[0]),console.log(ue.hex(H)(`
94
+ Welcome to wispbit`)),console.log(ue(` The linter for AI
95
+ `)),console.log(ue.dim(" To use wispbit, you need an API key.")),console.log(ue.dim(` You can get one at: https://app.wispbit.com/api-keys
96
+ `));let o=await Di(ue.bold.hex(J)(" Enter your Wispbit API key (or press Enter to exit): "));return o||(console.log(ue.dim(`
97
+ Setup cancelled.`)),null)}Ti.config();function Gt(){return je.join(Nt.homedir(),".powerlint","config.json")}async function Oi(o){let e=Gt(),t=je.dirname(e);await Q.mkdir(t,{recursive:!0});let n={};try{let r=await Q.readFile(e,"utf-8");n=JSON.parse(r)}catch{}let i={...n,apiKey:o};await Q.writeFile(e,JSON.stringify(i,null,2))}async function Ni(){if(process.env.WISPBIT_API_KEY)return process.env.WISPBIT_API_KEY;let o=Gt();try{let e=await Q.readFile(o,"utf-8");return JSON.parse(e).apiKey||null}catch{return null}}async function Bt(o){let e=process.env.WISPBIT_API_KEY||null;e||(e=await Ni());let t=await ge.initialize(o,{apiKey:e||void 0});if("failed"in t){if(t.error==="INVALID_API_KEY"){let n=await At();if(n||process.exit(0),await o.getRepositoryUrl()||(console.log(A.red("Repository URL not found. Make sure you're in a git repository.")),process.exit(1)),console.log(A.dim("Validating API key...")),await Oi(n),t=await ge.initialize(o,{apiKey:n}),"failed"in t)console.log(A.red("Invalid API key. Please check your API key and try again.")),process.exit(1);else return console.log(A.green("Setup complete! powerlint has been configured.")),t}else if(t.error==="INVALID_REPOSITORY"){let n=await o.getRepositoryUrl();console.log(A.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 Gi(){let o=Y(),e=await et();Ai.gt(e,o)&&(console.log(A.bgHex("#b2f5ea").black.bold(` NEW VERSION AVAILABLE: ${e} (current: ${o}) `)),console.log(A.bgHex("#b2f5ea").black.bold(` Run 'pnpm install -g @wispbit/local' to update
98
+ `)))}var T=_i(`
3929
99
  wispbit - the linter for AI
3930
100
  https://wispbit.com/
3931
101
 
@@ -3970,317 +140,5 @@ Global options:
3970
140
  --repo-url <url> Override repository URL (default: from git remote)
3971
141
  -v, --version Show version number
3972
142
  -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
143
+ `,{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:Y()});async function Ze(o){let e=new X({repositoryUrl:o.repoUrl}),t=await Bt(e),{ruleId:n,json:i,mode:r,filePath:a}=o,{commitSelector:s,diffInclude:l}=o;if(r==="diff"){let b=e.getWorkspaceRoot();if(s)l||(l=(await ze(b)).include);else{let L=await ze(b);s=L.commitSelector,l||(l=L.include)}}if(r==="diff"&&s&&l){let b=e.getWorkspaceRoot(),L=await Ve(b),x=qe(s,l,L);x&&(console.error(A.red(x)),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 Gi();let m;try{let b=new he(t,e);n?m=[await b.loadRuleById(n)]:m=await b.loadAllRules()}catch(b){throw b instanceof Ge&&(console.error(A.red("Rule Validation Error:"),b.message),b.validationErrors&&b.validationErrors.length>0&&(console.error(A.red("Validation errors:")),b.validationErrors.forEach(L=>{console.error(A.red(" \u2022"),L)})),process.exit(1)),b}if(!c)if(r==="check"){let b=A.bgHex("#eab308").black(" CHECK "),L=A.dim(` (${a||"all files"})`);console.log(`${b}${L}`)}else{let b=A.bgHex("#4a90e2").black(" DIFF "),L=(s||"").replace(/\b[0-9a-f]{30,}\b/gi,d=>d.substring(0,7)),x=l?` ${L} ${A.dim(`[${l.join("/")}]`)}`:` ${L}`;console.log(`${b}${x}`)}let g=new G;if(r==="check")g.setExecutionMode("check",{filePath:a});else{let b=l?l.join(", "):"all";g.setExecutionMode("diff",{baseCommit:s||"",headCommit:b})}let h=o.json?void 0:_t(g,o.debug||!1),v=Date.now(),M=new Ne(t,e,g),E=0;g.on("files:discovery:complete",b=>{E=b.totalFiles});let C=await M.execute(m,{mode:r,filePath:a,diffOptions:r==="diff"?{include:l,commitSelector:s}:void 0}),R=[];for(let b of C){let L=m.find(x=>x.id===b.ruleId);if(L){let P={ruleId:b.ruleId,internalId:L.internalId,message:L.config.message,severity:L.config.severity,execution:L.config.execution,matches:b.matches,llmCost:void 0,llmTokens:void 0};R.push(P)}}if(h==null||h(),c)Tt(R,u);else{let b=R.filter(k=>k.severity==="violation"),L=R.filter(k=>k.severity==="suggestion"),x=b.reduce((k,_)=>k+_.matches.length,0),d=L.reduce((k,_)=>k+_.matches.length,0),P=x+d,$=R.filter(k=>k.llmCost).reduce((k,_)=>k.plus(new Ot(_.llmCost||0)),new Ot(0)),w=R.filter(k=>k.llmTokens).reduce((k,_)=>k+(_.llmTokens||0),0),S;if(r==="check")S={mode:"check",filePath:a};else{let k=(s||"").replace(/\b[0-9a-f]{30,}\b/gi,We=>We.substring(0,7)),_=l?l.join(", "):"all";S={mode:"diff",baseCommit:k,headCommit:_}}let O=Date.now()-v;Ft(R,{totalRules:m.length,violationCount:x,suggestionCount:d,totalMatches:P,totalLLMCost:$.toString(),totalLLMTokens:w,executionMode:S,executionTime:O,totalFiles:E})}return R}async function Bi(o){let e=new X({repositoryUrl:o}),t=await Bt(e),i=await new he(t,e).loadAllRules();Dt(i)}async function ji(){let o=T.input[0],e=T.input[1];switch(o){case"check":{let t=T.input[1];await Ze({ruleId:T.flags.rule,json:T.flags.json,debug:T.flags.debug,mode:"check",filePath:t,repoUrl:T.flags.repoUrl});break}case"diff":{let t;if(T.flags.include){let r=T.flags.include.split(",").map(l=>l.trim()),a=["committed","staged","unstaged","untracked"],s=r.filter(l=>!a.includes(l));s.length>0&&(console.error(A.red(`Invalid include(s): ${s.join(", ")}. Valid includes: ${a.join(", ")}`)),process.exit(1)),t=r}let n=T.input[1];await Ze({ruleId:T.flags.rule,json:T.flags.json,debug:T.flags.debug,mode:"diff",diffInclude:t,commitSelector:n,repoUrl:T.flags.repoUrl});break}case"list":{await Bi(T.flags.repoUrl);break}case"cache":{if(e==="purge"){let t=new X({repositoryUrl:T.flags.repoUrl}),i=await new ee(t).purgeCache();console.log(`
144
+ ${A.green("Cache purged successfully.")} Removed ${i.deletedCount} item(s).`)}else console.error(A.red("Unknown cache subcommand:"),e),T.showHelp(),process.exit(1);break}default:{let t;if(T.flags.include){let r=T.flags.include.split(",").map(l=>l.trim()),a=["committed","staged","unstaged","untracked"],s=r.filter(l=>!a.includes(l));s.length>0&&(console.error(A.red(`Invalid include(s): ${s.join(", ")}. Valid includes: ${a.join(", ")}`)),process.exit(1)),t=r}let n=T.input[0];await Ze({ruleId:T.flags.rule,json:T.flags.json,debug:T.flags.debug,mode:"diff",diffInclude:t,commitSelector:n,repoUrl:T.flags.repoUrl});break}}}ji();export{Gi as checkForUpdates};