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