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