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