@wispbit/local 1.0.25 → 1.0.26

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