@wispbit/local 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +698 -281
- package/dist/cli.js.map +3 -3
- package/dist/index.js +10 -3857
- package/dist/index.js.map +4 -4
- package/dist/package.json +2 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/schemas.d.ts +9 -314
- package/dist/src/schemas.d.ts.map +1 -1
- package/dist/src/steps/FileExecutionContext.d.ts +16 -3
- package/dist/src/steps/FileExecutionContext.d.ts.map +1 -1
- package/dist/src/steps/FileFilterStep.d.ts +2 -5
- package/dist/src/steps/FileFilterStep.d.ts.map +1 -1
- package/dist/src/steps/GotoDefinitionStep.d.ts.map +1 -1
- package/dist/src/steps/RuleExecutor.d.ts +5 -1
- package/dist/src/steps/RuleExecutor.d.ts.map +1 -1
- package/dist/src/test/TestExecutor.d.ts +6 -1
- package/dist/src/test/TestExecutor.d.ts.map +1 -1
- package/dist/src/types.d.ts +1 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/debugLogger.d.ts +6 -0
- package/dist/src/utils/debugLogger.d.ts.map +1 -0
- package/dist/src/utils/formatters.d.ts.map +1 -1
- package/dist/src/utils/git.d.ts +31 -2
- package/dist/src/utils/git.d.ts.map +1 -1
- package/dist/src/utils/git.test.d.ts +2 -0
- package/dist/src/utils/git.test.d.ts.map +1 -0
- package/dist/src/utils/validateRule.d.ts +1 -1
- package/dist/src/utils/validateRule.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -168,6 +168,7 @@ var Config = class _Config {
|
|
|
168
168
|
|
|
169
169
|
// src/utils/git.ts
|
|
170
170
|
import { exec, execSync } from "child_process";
|
|
171
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
171
172
|
import { promisify } from "util";
|
|
172
173
|
|
|
173
174
|
// src/utils/hashString.ts
|
|
@@ -182,189 +183,498 @@ function findGitRoot() {
|
|
|
182
183
|
const stdout = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" });
|
|
183
184
|
return stdout.trim();
|
|
184
185
|
}
|
|
186
|
+
async function getGitIgnoredFiles(repoRoot) {
|
|
187
|
+
const { stdout } = await execPromise("git ls-files --ignored --exclude-standard --others", {
|
|
188
|
+
cwd: repoRoot,
|
|
189
|
+
maxBuffer: 50 * 1024 * 1024
|
|
190
|
+
});
|
|
191
|
+
return stdout.split("\n").filter(Boolean).map((file) => file.trim());
|
|
192
|
+
}
|
|
185
193
|
async function getRepositoryUrl(repoRoot, remoteName = "origin") {
|
|
186
|
-
|
|
187
|
-
const { stdout } = await execPromise(`git remote show ${remoteName}`, {
|
|
194
|
+
const { stdout } = await execPromise(`git config --get remote.${remoteName}.url`, {
|
|
188
195
|
cwd: repoRoot
|
|
189
196
|
});
|
|
190
|
-
|
|
191
|
-
if (fetchUrlLine) {
|
|
192
|
-
return ((_a = fetchUrlLine.split("Fetch URL:").pop()) == null ? void 0 : _a.trim()) || null;
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
197
|
+
return stdout.trim() || null;
|
|
195
198
|
}
|
|
196
199
|
async function getDefaultBranch(repoRoot, remoteName = "origin") {
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
try {
|
|
201
|
+
const { stdout } = await execPromise(`git rev-parse --abbrev-ref ${remoteName}/HEAD`, {
|
|
202
|
+
cwd: repoRoot
|
|
203
|
+
});
|
|
204
|
+
const fullRef = stdout.trim();
|
|
205
|
+
const branchName = fullRef.split("/").pop();
|
|
206
|
+
return branchName || null;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
const commonBranches = ["main", "master"];
|
|
209
|
+
for (const branch of commonBranches) {
|
|
210
|
+
try {
|
|
211
|
+
await execPromise(`git rev-parse --verify ${branch}`, { cwd: repoRoot });
|
|
212
|
+
return branch;
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function tryGetUpstream(repoRoot) {
|
|
220
|
+
const { stdout } = await execPromise(`git rev-parse --abbrev-ref --symbolic-full-name @{u}`, {
|
|
221
|
+
cwd: repoRoot
|
|
222
|
+
});
|
|
223
|
+
return stdout.trim() || void 0;
|
|
224
|
+
}
|
|
225
|
+
function tryGetGraphiteParent(repoRoot, branch) {
|
|
226
|
+
const metadataPath = `.git/refs/branch-metadata/${branch}`;
|
|
227
|
+
const fullPath = `${repoRoot}/${metadataPath}`;
|
|
228
|
+
if (!existsSync(fullPath)) {
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
const content = readFileSync2(fullPath, "utf-8");
|
|
232
|
+
const match = content.match(/"parent"\s*:\s*"([^"]+)"/);
|
|
233
|
+
return match == null ? void 0 : match[1];
|
|
234
|
+
}
|
|
235
|
+
function shellQuote(path11) {
|
|
236
|
+
return `'${path11.replace(/'/g, "'\\''")}'`;
|
|
237
|
+
}
|
|
238
|
+
function joinAsShellArgs(paths) {
|
|
239
|
+
return paths.map(shellQuote).join(" ");
|
|
240
|
+
}
|
|
241
|
+
function resolveIncludes(raw) {
|
|
242
|
+
const all = {
|
|
243
|
+
committed: true,
|
|
244
|
+
staged: true,
|
|
245
|
+
unstaged: true,
|
|
246
|
+
untracked: true
|
|
247
|
+
};
|
|
248
|
+
if (!raw || raw.length === 0) return all;
|
|
249
|
+
const s = new Set(raw);
|
|
250
|
+
return {
|
|
251
|
+
committed: s.has("committed"),
|
|
252
|
+
staged: s.has("staged"),
|
|
253
|
+
unstaged: s.has("unstaged"),
|
|
254
|
+
untracked: s.has("untracked")
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function parseNameStatusZ(buffer, source) {
|
|
258
|
+
if (!buffer) return [];
|
|
259
|
+
const entries = [];
|
|
260
|
+
const parts = buffer.split("\0").filter(Boolean);
|
|
261
|
+
for (let i = 0; i < parts.length; ) {
|
|
262
|
+
const status = parts[i];
|
|
263
|
+
if (!status) break;
|
|
264
|
+
if (status.startsWith("R")) {
|
|
265
|
+
const oldPath = parts[i + 1];
|
|
266
|
+
const newPath = parts[i + 2];
|
|
267
|
+
if (oldPath && newPath) {
|
|
268
|
+
entries.push({ status, path: newPath, oldPath, source });
|
|
269
|
+
i += 3;
|
|
270
|
+
} else {
|
|
271
|
+
i++;
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
const path11 = parts[i + 1];
|
|
275
|
+
if (path11) {
|
|
276
|
+
entries.push({ status, path: path11, source });
|
|
277
|
+
i += 2;
|
|
278
|
+
} else {
|
|
279
|
+
i++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return entries;
|
|
284
|
+
}
|
|
285
|
+
function parseLsFilesZ(buffer) {
|
|
286
|
+
if (!buffer) return [];
|
|
287
|
+
return buffer.split("\0").filter(Boolean).map((path11) => ({ status: "U", path: path11, source: "untracked" }));
|
|
288
|
+
}
|
|
289
|
+
function dedupeEntries(entries) {
|
|
290
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const entry of entries) {
|
|
292
|
+
const existing = byPath.get(entry.path);
|
|
293
|
+
if (!existing) {
|
|
294
|
+
byPath.set(entry.path, entry);
|
|
295
|
+
} else {
|
|
296
|
+
const existingPriority = getPriority(existing.source);
|
|
297
|
+
const newPriority = getPriority(entry.source);
|
|
298
|
+
if (newPriority > existingPriority) {
|
|
299
|
+
byPath.set(entry.path, entry);
|
|
300
|
+
} else if (newPriority === existingPriority && entry.oldPath) {
|
|
301
|
+
existing.oldPath = existing.oldPath || entry.oldPath;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return Array.from(byPath.values());
|
|
306
|
+
}
|
|
307
|
+
function getPriority(source) {
|
|
308
|
+
switch (source) {
|
|
309
|
+
case "untracked":
|
|
310
|
+
return 4;
|
|
311
|
+
case "unstaged":
|
|
312
|
+
return 3;
|
|
313
|
+
case "staged":
|
|
314
|
+
return 2;
|
|
315
|
+
case "committed":
|
|
316
|
+
return 1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function splitGitPatchPerFile(patchOutput) {
|
|
320
|
+
const patches = /* @__PURE__ */ new Map();
|
|
321
|
+
if (!patchOutput) return patches;
|
|
322
|
+
const sections = patchOutput.split(/^diff --git /m).filter(Boolean);
|
|
323
|
+
for (const section of sections) {
|
|
324
|
+
const lines = section.split("\n");
|
|
325
|
+
const firstLine = lines[0];
|
|
326
|
+
const match = firstLine.match(/a\/(.+?) b\//);
|
|
327
|
+
if (match) {
|
|
328
|
+
const filename = match[1];
|
|
329
|
+
patches.set(filename, "diff --git " + section);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return patches;
|
|
333
|
+
}
|
|
334
|
+
function toChangeStatus(entry) {
|
|
335
|
+
if (entry.status === "U" || entry.status === "A") return "added";
|
|
336
|
+
if (entry.status === "D") return "removed";
|
|
337
|
+
return "modified";
|
|
338
|
+
}
|
|
339
|
+
function stripDiffHeaders(patch) {
|
|
340
|
+
if (!patch) return "";
|
|
341
|
+
const lines = patch.split("\n");
|
|
342
|
+
const output = [];
|
|
343
|
+
let inHunk = false;
|
|
344
|
+
for (const line of lines) {
|
|
345
|
+
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 === "\") {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (line.startsWith("@@")) {
|
|
349
|
+
inHunk = true;
|
|
350
|
+
}
|
|
351
|
+
if (inHunk) {
|
|
352
|
+
output.push(line);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return output.join("\n").trimEnd();
|
|
356
|
+
}
|
|
357
|
+
function parseRange(selector) {
|
|
358
|
+
const threeDotsMatch = selector.match(/^(.+)\.\.\.(.+)$/);
|
|
359
|
+
if (threeDotsMatch) {
|
|
360
|
+
return [threeDotsMatch[1], threeDotsMatch[2], true];
|
|
361
|
+
}
|
|
362
|
+
const twoDotsMatch = selector.match(/^(.+)\.\.([^.].*)$/);
|
|
363
|
+
if (twoDotsMatch) {
|
|
364
|
+
return [twoDotsMatch[1], twoDotsMatch[2], false];
|
|
365
|
+
}
|
|
366
|
+
return [selector, selector, false];
|
|
367
|
+
}
|
|
368
|
+
function parseRangeRight(selector) {
|
|
369
|
+
const [, right] = parseRange(selector);
|
|
370
|
+
return right;
|
|
371
|
+
}
|
|
372
|
+
async function getCurrentBranch(repoRoot) {
|
|
373
|
+
const { stdout } = await execPromise("git rev-parse --abbrev-ref HEAD", {
|
|
199
374
|
cwd: repoRoot
|
|
200
375
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
376
|
+
return stdout.trim();
|
|
377
|
+
}
|
|
378
|
+
function validateWorktreeIncludes(commitSelector, includes, currentBranch) {
|
|
379
|
+
const isRange = commitSelector.includes("..");
|
|
380
|
+
if (!isRange) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const hasWorktreeIncludes = includes.includes("staged") || includes.includes("unstaged") || includes.includes("untracked");
|
|
384
|
+
if (!hasWorktreeIncludes) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const rangeRight = parseRangeRight(commitSelector);
|
|
388
|
+
const endsAtCurrent = rangeRight === "HEAD" || rangeRight === currentBranch;
|
|
389
|
+
if (!endsAtCurrent) {
|
|
390
|
+
return `Worktree includes (staged, unstaged, untracked) require range to end at HEAD or current branch (${currentBranch}). Got: ${commitSelector}`;
|
|
204
391
|
}
|
|
205
392
|
return null;
|
|
206
393
|
}
|
|
207
|
-
async function
|
|
208
|
-
var _a, _b;
|
|
394
|
+
async function getDefaultCommitSelector(repoRoot) {
|
|
209
395
|
const { stdout: currentBranchOutput } = await execPromise("git rev-parse --abbrev-ref HEAD", {
|
|
210
396
|
cwd: repoRoot
|
|
211
397
|
});
|
|
212
398
|
const currentBranch = currentBranchOutput.trim();
|
|
399
|
+
const defaultInclude = ["committed", "staged", "unstaged", "untracked"];
|
|
400
|
+
const graphiteParent = tryGetGraphiteParent(repoRoot, currentBranch);
|
|
401
|
+
if (graphiteParent) {
|
|
402
|
+
return {
|
|
403
|
+
commitSelector: `${graphiteParent}...${currentBranch}`,
|
|
404
|
+
include: defaultInclude
|
|
405
|
+
};
|
|
406
|
+
}
|
|
213
407
|
const defaultBranch = await getDefaultBranch(repoRoot);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
408
|
+
if (defaultBranch) {
|
|
409
|
+
if (currentBranch === defaultBranch) {
|
|
410
|
+
const upstream = await tryGetUpstream(repoRoot).catch(() => void 0);
|
|
411
|
+
if (upstream) {
|
|
412
|
+
return {
|
|
413
|
+
commitSelector: `${upstream}...HEAD`,
|
|
414
|
+
include: defaultInclude
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
commitSelector: `HEAD~1...HEAD`,
|
|
419
|
+
include: defaultInclude
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const originBranch = `origin/${defaultBranch}`;
|
|
423
|
+
const { stdout } = await execPromise(`git rev-parse --verify ${originBranch}`, {
|
|
224
424
|
cwd: repoRoot
|
|
225
425
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
`git merge-base ${currentBranch} ${compareTo}`,
|
|
232
|
-
{ cwd: repoRoot }
|
|
233
|
-
);
|
|
234
|
-
mergeBase = mergeBaseOutput.trim();
|
|
235
|
-
} catch (error) {
|
|
236
|
-
mergeBase = "HEAD^";
|
|
426
|
+
const base = stdout.trim() ? originBranch : defaultBranch;
|
|
427
|
+
return {
|
|
428
|
+
commitSelector: `${base}...${currentBranch}`,
|
|
429
|
+
include: defaultInclude
|
|
430
|
+
};
|
|
237
431
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
const { stdout: diffOutput } = await execPromise(`git diff ${mergeBase} --name-only`, {
|
|
432
|
+
return {
|
|
433
|
+
commitSelector: `HEAD...HEAD`,
|
|
434
|
+
include: defaultInclude
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
async function getChangedFiles(repoRoot, options) {
|
|
438
|
+
const selector = options.commitSelector.trim();
|
|
439
|
+
const isRange = selector.includes("..");
|
|
440
|
+
const { stdout: currentBranchOutput } = await execPromise("git rev-parse --abbrev-ref HEAD", {
|
|
249
441
|
cwd: repoRoot
|
|
250
442
|
});
|
|
251
|
-
const
|
|
252
|
-
const { stdout:
|
|
443
|
+
const currentBranch = currentBranchOutput.trim();
|
|
444
|
+
const { stdout: currentCommitOutput } = await execPromise("git rev-parse HEAD", {
|
|
253
445
|
cwd: repoRoot
|
|
254
446
|
});
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
447
|
+
const currentCommit = currentCommitOutput.trim();
|
|
448
|
+
let compareTo = "";
|
|
449
|
+
let parentRef = "";
|
|
450
|
+
if (isRange) {
|
|
451
|
+
compareTo = selector;
|
|
452
|
+
parentRef = selector;
|
|
453
|
+
} else {
|
|
454
|
+
const graphiteParent = tryGetGraphiteParent(repoRoot, currentBranch);
|
|
455
|
+
const defaultBranch = await getDefaultBranch(repoRoot);
|
|
456
|
+
const target = selector || graphiteParent || `origin/${defaultBranch || "main"}`;
|
|
457
|
+
parentRef = target;
|
|
458
|
+
const mergeBaseCmd = `git merge-base --fork-point ${shellQuote(target)} HEAD || git merge-base ${shellQuote(target)} HEAD`;
|
|
459
|
+
const { stdout: mergeBaseOutput } = await execPromise(mergeBaseCmd, { cwd: repoRoot });
|
|
460
|
+
compareTo = mergeBaseOutput.trim();
|
|
461
|
+
if (!selector && currentBranch === (defaultBranch || "main")) {
|
|
462
|
+
const upstream = await tryGetUpstream(repoRoot).catch(() => void 0);
|
|
463
|
+
if (upstream) {
|
|
464
|
+
parentRef = upstream;
|
|
465
|
+
const { stdout: upstreamMergeBase } = await execPromise(
|
|
466
|
+
`git merge-base --fork-point ${shellQuote(upstream)} HEAD || git merge-base ${shellQuote(upstream)} HEAD`,
|
|
467
|
+
{ cwd: repoRoot }
|
|
468
|
+
);
|
|
469
|
+
compareTo = upstreamMergeBase.trim();
|
|
470
|
+
} else {
|
|
471
|
+
parentRef = "HEAD~1";
|
|
472
|
+
compareTo = "HEAD~1";
|
|
473
|
+
}
|
|
261
474
|
}
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
`git diff
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
475
|
+
}
|
|
476
|
+
const includes = resolveIncludes(options.include);
|
|
477
|
+
const validationError = validateWorktreeIncludes(selector, options.include, currentBranch);
|
|
478
|
+
if (validationError) {
|
|
479
|
+
throw new Error(validationError);
|
|
480
|
+
}
|
|
481
|
+
const allEntries = [];
|
|
482
|
+
if (includes.committed) {
|
|
483
|
+
if (isRange) {
|
|
484
|
+
const [rangeA, rangeB] = parseRange(selector);
|
|
485
|
+
const { stdout } = await execPromise(
|
|
486
|
+
`git diff --name-status -M -z ${shellQuote(rangeA)}..${shellQuote(rangeB)}`,
|
|
487
|
+
{ cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 }
|
|
488
|
+
);
|
|
489
|
+
allEntries.push(...parseNameStatusZ(stdout, "committed"));
|
|
490
|
+
} else {
|
|
491
|
+
const { stdout } = await execPromise(`git diff --name-status -M -z ${compareTo}..HEAD`, {
|
|
492
|
+
cwd: repoRoot,
|
|
493
|
+
maxBuffer: 50 * 1024 * 1024
|
|
494
|
+
});
|
|
495
|
+
allEntries.push(...parseNameStatusZ(stdout, "committed"));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (includes.staged) {
|
|
499
|
+
const { stdout } = await execPromise(`git diff --name-status -M -z --cached`, {
|
|
500
|
+
cwd: repoRoot,
|
|
501
|
+
maxBuffer: 50 * 1024 * 1024
|
|
502
|
+
});
|
|
503
|
+
allEntries.push(...parseNameStatusZ(stdout, "staged"));
|
|
504
|
+
}
|
|
505
|
+
if (includes.unstaged) {
|
|
506
|
+
const { stdout } = await execPromise(`git diff --name-status -M -z`, {
|
|
507
|
+
cwd: repoRoot,
|
|
508
|
+
maxBuffer: 50 * 1024 * 1024
|
|
509
|
+
});
|
|
510
|
+
allEntries.push(...parseNameStatusZ(stdout, "unstaged"));
|
|
511
|
+
}
|
|
512
|
+
if (includes.untracked) {
|
|
513
|
+
const { stdout } = await execPromise(`git ls-files --others --exclude-standard -z`, {
|
|
514
|
+
cwd: repoRoot,
|
|
515
|
+
maxBuffer: 50 * 1024 * 1024
|
|
516
|
+
});
|
|
517
|
+
allEntries.push(...parseLsFilesZ(stdout));
|
|
518
|
+
}
|
|
519
|
+
const entries = dedupeEntries(allEntries);
|
|
520
|
+
const committedEntries = entries.filter((e) => e.source === "committed");
|
|
521
|
+
const stagedEntries = entries.filter((e) => e.source === "staged");
|
|
522
|
+
const unstagedEntries = entries.filter((e) => e.source === "unstaged");
|
|
523
|
+
const untrackedEntries = entries.filter((e) => e.source === "untracked");
|
|
524
|
+
const patchByFile = /* @__PURE__ */ new Map();
|
|
525
|
+
if (committedEntries.length > 0) {
|
|
526
|
+
const paths = committedEntries.map((e) => e.path);
|
|
527
|
+
let diffCmd = "";
|
|
528
|
+
if (isRange) {
|
|
529
|
+
const [rangeA, rangeB] = parseRange(selector);
|
|
530
|
+
diffCmd = `git diff -U0 -M ${shellQuote(rangeA)}..${shellQuote(rangeB)} -- ${joinAsShellArgs(paths)}`;
|
|
531
|
+
} else {
|
|
532
|
+
diffCmd = `git diff -U0 -M ${compareTo}..HEAD -- ${joinAsShellArgs(paths)}`;
|
|
533
|
+
}
|
|
534
|
+
const { stdout: patchOutput } = await execPromise(diffCmd, {
|
|
535
|
+
cwd: repoRoot,
|
|
536
|
+
maxBuffer: 50 * 1024 * 1024
|
|
537
|
+
});
|
|
538
|
+
const patches = splitGitPatchPerFile(patchOutput);
|
|
539
|
+
patches.forEach((patch, filename) => patchByFile.set(filename, stripDiffHeaders(patch)));
|
|
540
|
+
}
|
|
541
|
+
if (stagedEntries.length > 0) {
|
|
542
|
+
const paths = stagedEntries.map((e) => e.path);
|
|
543
|
+
const diffCmd = `git diff -U0 -M --cached -- ${joinAsShellArgs(paths)}`;
|
|
544
|
+
const { stdout: patchOutput } = await execPromise(diffCmd, {
|
|
545
|
+
cwd: repoRoot,
|
|
546
|
+
maxBuffer: 50 * 1024 * 1024
|
|
547
|
+
});
|
|
548
|
+
const patches = splitGitPatchPerFile(patchOutput);
|
|
549
|
+
patches.forEach((patch, filename) => patchByFile.set(filename, stripDiffHeaders(patch)));
|
|
550
|
+
}
|
|
551
|
+
if (unstagedEntries.length > 0) {
|
|
552
|
+
const paths = unstagedEntries.map((e) => e.path);
|
|
553
|
+
const diffCmd = `git diff -U0 -M -- ${joinAsShellArgs(paths)}`;
|
|
554
|
+
const { stdout: patchOutput } = await execPromise(diffCmd, {
|
|
555
|
+
cwd: repoRoot,
|
|
556
|
+
maxBuffer: 50 * 1024 * 1024
|
|
557
|
+
});
|
|
558
|
+
const patches = splitGitPatchPerFile(patchOutput);
|
|
559
|
+
patches.forEach((patch, filename) => patchByFile.set(filename, stripDiffHeaders(patch)));
|
|
560
|
+
}
|
|
561
|
+
for (const entry of untrackedEntries) {
|
|
562
|
+
try {
|
|
563
|
+
const { stdout } = await execPromise(
|
|
564
|
+
`git diff -U0 --no-index /dev/null ${shellQuote(entry.path)}`,
|
|
565
|
+
{ cwd: repoRoot, maxBuffer: 50 * 1024 * 1024 }
|
|
566
|
+
);
|
|
567
|
+
patchByFile.set(entry.path, stripDiffHeaders(stdout));
|
|
568
|
+
} catch (error) {
|
|
569
|
+
if (error.stdout) {
|
|
570
|
+
patchByFile.set(entry.path, stripDiffHeaders(error.stdout));
|
|
571
|
+
} else {
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const statsByFile = /* @__PURE__ */ new Map();
|
|
577
|
+
if (committedEntries.length > 0) {
|
|
578
|
+
const paths = committedEntries.map((e) => e.path);
|
|
579
|
+
let numstatCmd = "";
|
|
580
|
+
if (isRange) {
|
|
581
|
+
const [rangeA, rangeB] = parseRange(selector);
|
|
582
|
+
numstatCmd = `git diff --numstat ${shellQuote(rangeA)}..${shellQuote(rangeB)} -- ${joinAsShellArgs(paths)}`;
|
|
583
|
+
} else {
|
|
584
|
+
numstatCmd = `git diff --numstat ${compareTo}..HEAD -- ${joinAsShellArgs(paths)}`;
|
|
585
|
+
}
|
|
586
|
+
const { stdout: numstatOutput } = await execPromise(numstatCmd, {
|
|
587
|
+
cwd: repoRoot,
|
|
588
|
+
maxBuffer: 50 * 1024 * 1024
|
|
589
|
+
});
|
|
590
|
+
const lines = numstatOutput.split("\n").filter(Boolean);
|
|
591
|
+
for (const line of lines) {
|
|
283
592
|
const parts = line.split(" ");
|
|
284
593
|
if (parts.length >= 3) {
|
|
285
|
-
const [
|
|
286
|
-
|
|
287
|
-
additions: parseInt(
|
|
288
|
-
deletions: parseInt(
|
|
594
|
+
const [addStr, delStr, filename] = parts;
|
|
595
|
+
statsByFile.set(filename, {
|
|
596
|
+
additions: parseInt(addStr) || 0,
|
|
597
|
+
deletions: parseInt(delStr) || 0
|
|
289
598
|
});
|
|
290
599
|
}
|
|
291
|
-
}
|
|
600
|
+
}
|
|
292
601
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const diffSections = batchDiffOutput.split(/^diff --git /m).filter(Boolean);
|
|
300
|
-
diffSections.forEach((section) => {
|
|
301
|
-
const lines = section.split("\n");
|
|
302
|
-
const firstLine = lines[0];
|
|
303
|
-
const match = firstLine.match(/a\/(.+?) b\//);
|
|
304
|
-
if (match) {
|
|
305
|
-
const filename = match[1];
|
|
306
|
-
const diffContent = lines.slice(1).filter((line) => {
|
|
307
|
-
return !(line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ "));
|
|
308
|
-
}).join("\n");
|
|
309
|
-
fileDiffs.set(filename, diffContent);
|
|
310
|
-
}
|
|
602
|
+
if (stagedEntries.length > 0) {
|
|
603
|
+
const paths = stagedEntries.map((e) => e.path);
|
|
604
|
+
const numstatCmd = `git diff --numstat --cached -- ${joinAsShellArgs(paths)}`;
|
|
605
|
+
const { stdout: numstatOutput } = await execPromise(numstatCmd, {
|
|
606
|
+
cwd: repoRoot,
|
|
607
|
+
maxBuffer: 50 * 1024 * 1024
|
|
311
608
|
});
|
|
609
|
+
const lines = numstatOutput.split("\n").filter(Boolean);
|
|
610
|
+
for (const line of lines) {
|
|
611
|
+
const parts = line.split(" ");
|
|
612
|
+
if (parts.length >= 3) {
|
|
613
|
+
const [addStr, delStr, filename] = parts;
|
|
614
|
+
statsByFile.set(filename, {
|
|
615
|
+
additions: parseInt(addStr) || 0,
|
|
616
|
+
deletions: parseInt(delStr) || 0
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
312
620
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
{
|
|
320
|
-
cwd: repoRoot
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
return { file, content: lastContent };
|
|
621
|
+
if (unstagedEntries.length > 0) {
|
|
622
|
+
const paths = unstagedEntries.map((e) => e.path);
|
|
623
|
+
const numstatCmd = `git diff --numstat -- ${joinAsShellArgs(paths)}`;
|
|
624
|
+
const { stdout: numstatOutput } = await execPromise(numstatCmd, {
|
|
625
|
+
cwd: repoRoot,
|
|
626
|
+
maxBuffer: 50 * 1024 * 1024
|
|
324
627
|
});
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
628
|
+
const lines = numstatOutput.split("\n").filter(Boolean);
|
|
629
|
+
for (const line of lines) {
|
|
630
|
+
const parts = line.split(" ");
|
|
631
|
+
if (parts.length >= 3) {
|
|
632
|
+
const [addStr, delStr, filename] = parts;
|
|
633
|
+
statsByFile.set(filename, {
|
|
634
|
+
additions: parseInt(addStr) || 0,
|
|
635
|
+
deletions: parseInt(delStr) || 0
|
|
636
|
+
});
|
|
329
637
|
}
|
|
330
|
-
}
|
|
638
|
+
}
|
|
331
639
|
}
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
const
|
|
640
|
+
for (const entry of untrackedEntries) {
|
|
641
|
+
const patch = patchByFile.get(entry.path) || "";
|
|
642
|
+
const lines = patch.split("\n");
|
|
335
643
|
let additions = 0;
|
|
336
644
|
let deletions = 0;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
deletions
|
|
342
|
-
diffOutput2 = lastContent.split("\n").map((line) => `-${line}`).join("\n");
|
|
343
|
-
}
|
|
344
|
-
} else {
|
|
345
|
-
const stats = fileStats.get(file);
|
|
346
|
-
if (stats) {
|
|
347
|
-
additions = stats.additions;
|
|
348
|
-
deletions = stats.deletions;
|
|
645
|
+
for (const line of lines) {
|
|
646
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
647
|
+
additions++;
|
|
648
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
649
|
+
deletions++;
|
|
349
650
|
}
|
|
350
|
-
diffOutput2 = fileDiffs.get(file) || "";
|
|
351
651
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
652
|
+
statsByFile.set(entry.path, { additions, deletions });
|
|
653
|
+
}
|
|
654
|
+
const fileChanges = [];
|
|
655
|
+
for (const entry of entries) {
|
|
656
|
+
const patch = patchByFile.get(entry.path) || "";
|
|
657
|
+
const stats = statsByFile.get(entry.path) || { additions: 0, deletions: 0 };
|
|
658
|
+
const status = toChangeStatus(entry);
|
|
659
|
+
const fileChange = {
|
|
660
|
+
filename: entry.path,
|
|
355
661
|
status,
|
|
356
|
-
patch
|
|
357
|
-
additions,
|
|
358
|
-
deletions,
|
|
359
|
-
sha: hashString(
|
|
360
|
-
}
|
|
662
|
+
patch,
|
|
663
|
+
additions: stats.additions,
|
|
664
|
+
deletions: stats.deletions,
|
|
665
|
+
sha: hashString(patch)
|
|
666
|
+
};
|
|
667
|
+
if (entry.oldPath) {
|
|
668
|
+
fileChange.oldFilename = entry.oldPath;
|
|
669
|
+
}
|
|
670
|
+
fileChanges.push(fileChange);
|
|
361
671
|
}
|
|
362
672
|
return {
|
|
363
673
|
files: fileChanges,
|
|
364
674
|
currentBranch,
|
|
365
675
|
currentCommit,
|
|
366
|
-
|
|
367
|
-
|
|
676
|
+
diffBranch: parentRef,
|
|
677
|
+
diffCommit: compareTo
|
|
368
678
|
};
|
|
369
679
|
}
|
|
370
680
|
|
|
@@ -808,7 +1118,7 @@ var ExecutionEventEmitter = class extends EventEmitter {
|
|
|
808
1118
|
import * as fs2 from "fs";
|
|
809
1119
|
import * as path2 from "path";
|
|
810
1120
|
import { glob } from "glob";
|
|
811
|
-
import
|
|
1121
|
+
import ignore from "ignore";
|
|
812
1122
|
var FileExecutionContext = class _FileExecutionContext {
|
|
813
1123
|
environment;
|
|
814
1124
|
_filePaths = [];
|
|
@@ -825,9 +1135,33 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
825
1135
|
/**
|
|
826
1136
|
* Create and initialize an ExecutionContext
|
|
827
1137
|
*/
|
|
828
|
-
static async initialize(config, environment, mode, eventEmitter, filePath,
|
|
1138
|
+
static async initialize(config, environment, mode, eventEmitter, filePath, diffOptions) {
|
|
829
1139
|
const context = new _FileExecutionContext(config, environment, mode, eventEmitter);
|
|
830
|
-
|
|
1140
|
+
if (mode === "diff" && !filePath) {
|
|
1141
|
+
const workspaceRoot = context.environment.getWorkspaceRoot();
|
|
1142
|
+
const include = diffOptions == null ? void 0 : diffOptions.include;
|
|
1143
|
+
const commitSelector = diffOptions == null ? void 0 : diffOptions.commitSelector;
|
|
1144
|
+
if (!commitSelector || !include)
|
|
1145
|
+
throw new Error("Commit selector and include are required in diff mode");
|
|
1146
|
+
const gitChanges = await getChangedFiles(workspaceRoot, {
|
|
1147
|
+
include,
|
|
1148
|
+
commitSelector
|
|
1149
|
+
}).catch(() => {
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
"Diff mode requires a git repository. Please run this command from within a git repository."
|
|
1152
|
+
);
|
|
1153
|
+
});
|
|
1154
|
+
context.diffMode = {
|
|
1155
|
+
gitChanges,
|
|
1156
|
+
changedFiles: gitChanges.files.map((f) => f.filename),
|
|
1157
|
+
fileChangeMap: new Map(gitChanges.files.map((file) => [file.filename, file]))
|
|
1158
|
+
};
|
|
1159
|
+
context.eventEmitter.fileDiscoveryProgress(
|
|
1160
|
+
`Found ${context.diffMode.changedFiles.length} changed files`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
const gitIgnoredFiles = mode === "check" ? await context.loadGitIgnoredFiles() : /* @__PURE__ */ new Set();
|
|
1164
|
+
const initialFiles = await context.discoverFiles(filePath, gitIgnoredFiles);
|
|
831
1165
|
context._filePaths = initialFiles;
|
|
832
1166
|
return context;
|
|
833
1167
|
}
|
|
@@ -843,8 +1177,6 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
843
1177
|
let filteredFiles;
|
|
844
1178
|
if (this.mode === "check") {
|
|
845
1179
|
filteredFiles = filePaths;
|
|
846
|
-
} else if (!this.diffMode) {
|
|
847
|
-
filteredFiles = filePaths;
|
|
848
1180
|
} else {
|
|
849
1181
|
filteredFiles = filePaths.filter((filePath) => this.isFileValid({ filePath }));
|
|
850
1182
|
}
|
|
@@ -881,6 +1213,27 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
881
1213
|
get executionMode() {
|
|
882
1214
|
return this.mode;
|
|
883
1215
|
}
|
|
1216
|
+
// ===== Git Ignored Files =====
|
|
1217
|
+
/**
|
|
1218
|
+
* Load git-ignored files from the repository
|
|
1219
|
+
* Uses: git ls-files --ignored --exclude-standard --others
|
|
1220
|
+
*
|
|
1221
|
+
* Only called in "check" mode - in "diff" mode, git automatically excludes ignored files.
|
|
1222
|
+
* Returns a Set for efficient lookup, which can be discarded after file discovery.
|
|
1223
|
+
* If not in a git repository, returns an empty Set.
|
|
1224
|
+
*/
|
|
1225
|
+
async loadGitIgnoredFiles() {
|
|
1226
|
+
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
1227
|
+
const ignoredFiles = await getGitIgnoredFiles(workspaceRoot).catch(() => {
|
|
1228
|
+
this.eventEmitter.fileDiscoveryProgress("Not in a git repository, skipping git-ignored files");
|
|
1229
|
+
return [];
|
|
1230
|
+
});
|
|
1231
|
+
const ignoredFilesSet = new Set(ignoredFiles);
|
|
1232
|
+
if (ignoredFiles.length > 0) {
|
|
1233
|
+
this.eventEmitter.fileDiscoveryProgress(`Found ${ignoredFiles.length} git-ignored files`);
|
|
1234
|
+
}
|
|
1235
|
+
return ignoredFilesSet;
|
|
1236
|
+
}
|
|
884
1237
|
// ===== File Discovery =====
|
|
885
1238
|
/**
|
|
886
1239
|
* Discover files based on the execution mode
|
|
@@ -892,38 +1245,23 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
892
1245
|
* - A directory path (e.g., "src/components/")
|
|
893
1246
|
* - A glob pattern (e.g., ".ts")
|
|
894
1247
|
*/
|
|
895
|
-
async discoverFiles(filePath,
|
|
1248
|
+
async discoverFiles(filePath, gitIgnoredFiles) {
|
|
896
1249
|
this.eventEmitter.startFileDiscovery(this.mode);
|
|
897
1250
|
let discoveredFiles;
|
|
898
1251
|
if (filePath) {
|
|
899
|
-
discoveredFiles = await this.discoverFilesFromPath(filePath);
|
|
1252
|
+
discoveredFiles = await this.discoverFilesFromPath(filePath, gitIgnoredFiles);
|
|
900
1253
|
} else if (this.mode === "diff") {
|
|
901
1254
|
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
this.eventEmitter.fileDiscoveryProgress("Getting changed files from git...");
|
|
906
|
-
}
|
|
907
|
-
const gitChanges = await getChangedFiles(workspaceRoot, baseSha);
|
|
908
|
-
this.diffMode = {
|
|
909
|
-
gitChanges,
|
|
910
|
-
changedFiles: gitChanges.files.map((f) => f.filename),
|
|
911
|
-
fileChangeMap: new Map(gitChanges.files.map((file) => [file.filename, file]))
|
|
912
|
-
};
|
|
913
|
-
this.eventEmitter.fileDiscoveryProgress(
|
|
914
|
-
`Found ${this.diffMode.changedFiles.length} changed files`
|
|
915
|
-
);
|
|
916
|
-
discoveredFiles = this.diffMode.changedFiles.filter((file) => fs2.existsSync(path2.resolve(workspaceRoot, file))).map((file) => file);
|
|
917
|
-
this.eventEmitter.fileDiscoveryProgress("Applying ignore patterns...");
|
|
1255
|
+
discoveredFiles = this.diffMode.changedFiles.filter(
|
|
1256
|
+
(file) => fs2.existsSync(path2.resolve(workspaceRoot, file))
|
|
1257
|
+
).map((file) => file);
|
|
918
1258
|
const allIgnorePatterns = this.config.getIgnoredGlobs();
|
|
919
1259
|
if (allIgnorePatterns.length > 0) {
|
|
1260
|
+
this.eventEmitter.fileDiscoveryProgress("Applying ignore patterns...");
|
|
920
1261
|
const beforeIgnore = discoveredFiles.length;
|
|
921
|
-
discoveredFiles = discoveredFiles.filter(
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
);
|
|
925
|
-
return !matchesIgnore;
|
|
926
|
-
});
|
|
1262
|
+
discoveredFiles = discoveredFiles.filter(
|
|
1263
|
+
(filePath2) => !this.matchesAnyPattern(filePath2, allIgnorePatterns)
|
|
1264
|
+
);
|
|
927
1265
|
if (beforeIgnore !== discoveredFiles.length) {
|
|
928
1266
|
this.eventEmitter.fileDiscoveryProgress(
|
|
929
1267
|
`Filtered out ${beforeIgnore - discoveredFiles.length} ignored files`
|
|
@@ -934,7 +1272,11 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
934
1272
|
this.eventEmitter.fileDiscoveryProgress("Scanning workspace for files...");
|
|
935
1273
|
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
936
1274
|
const allIgnorePatterns = this.config.getIgnoredGlobs();
|
|
937
|
-
discoveredFiles = await this.discoverAllFiles(
|
|
1275
|
+
discoveredFiles = await this.discoverAllFiles(
|
|
1276
|
+
[workspaceRoot],
|
|
1277
|
+
allIgnorePatterns,
|
|
1278
|
+
gitIgnoredFiles
|
|
1279
|
+
);
|
|
938
1280
|
}
|
|
939
1281
|
this.eventEmitter.completeFileDiscovery(discoveredFiles.length, this.mode);
|
|
940
1282
|
return discoveredFiles;
|
|
@@ -942,7 +1284,7 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
942
1284
|
/**
|
|
943
1285
|
* Discover files from a given path which can be a file, directory, or glob pattern
|
|
944
1286
|
*/
|
|
945
|
-
async discoverFilesFromPath(filePath) {
|
|
1287
|
+
async discoverFilesFromPath(filePath, gitIgnoredFiles) {
|
|
946
1288
|
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
947
1289
|
const fullPath = path2.resolve(workspaceRoot, filePath);
|
|
948
1290
|
const allIgnorePatterns = this.config.getIgnoredGlobs();
|
|
@@ -950,7 +1292,7 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
950
1292
|
this.eventEmitter.fileDiscoveryProgress(
|
|
951
1293
|
`Discovering files matching glob pattern: ${filePath}`
|
|
952
1294
|
);
|
|
953
|
-
return await this.discoverFilesFromGlob(filePath, allIgnorePatterns);
|
|
1295
|
+
return await this.discoverFilesFromGlob(filePath, allIgnorePatterns, gitIgnoredFiles);
|
|
954
1296
|
}
|
|
955
1297
|
if (!fs2.existsSync(fullPath)) {
|
|
956
1298
|
throw new Error(`Path not found: ${filePath}`);
|
|
@@ -958,10 +1300,11 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
958
1300
|
const stats = fs2.statSync(fullPath);
|
|
959
1301
|
if (stats.isFile()) {
|
|
960
1302
|
this.eventEmitter.fileDiscoveryProgress(`Checking specific file: ${filePath}`);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1303
|
+
if (gitIgnoredFiles.has(filePath)) {
|
|
1304
|
+
this.eventEmitter.fileDiscoveryProgress(`File ${filePath} is ignored by git`);
|
|
1305
|
+
return [];
|
|
1306
|
+
}
|
|
1307
|
+
if (this.matchesAnyPattern(filePath, allIgnorePatterns)) {
|
|
965
1308
|
this.eventEmitter.fileDiscoveryProgress(`File ${filePath} is ignored by patterns`);
|
|
966
1309
|
return [];
|
|
967
1310
|
} else {
|
|
@@ -969,7 +1312,7 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
969
1312
|
}
|
|
970
1313
|
} else if (stats.isDirectory()) {
|
|
971
1314
|
this.eventEmitter.fileDiscoveryProgress(`Discovering files in directory: ${filePath}`);
|
|
972
|
-
return await this.discoverFilesFromDirectory(filePath, allIgnorePatterns);
|
|
1315
|
+
return await this.discoverFilesFromDirectory(filePath, allIgnorePatterns, gitIgnoredFiles);
|
|
973
1316
|
} else {
|
|
974
1317
|
throw new Error(`Path is neither a file nor directory: ${filePath}`);
|
|
975
1318
|
}
|
|
@@ -983,7 +1326,7 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
983
1326
|
/**
|
|
984
1327
|
* Discover files matching a glob pattern
|
|
985
1328
|
*/
|
|
986
|
-
async discoverFilesFromGlob(globPattern, ignorePatterns) {
|
|
1329
|
+
async discoverFilesFromGlob(globPattern, ignorePatterns, gitIgnoredFiles) {
|
|
987
1330
|
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
988
1331
|
const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
|
|
989
1332
|
const matches = await glob(globPattern, {
|
|
@@ -992,13 +1335,16 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
992
1335
|
absolute: false,
|
|
993
1336
|
ignore: allIgnorePatterns
|
|
994
1337
|
});
|
|
995
|
-
|
|
996
|
-
|
|
1338
|
+
const filteredMatches = matches.filter((match) => !gitIgnoredFiles.has(match));
|
|
1339
|
+
this.eventEmitter.fileDiscoveryProgress(
|
|
1340
|
+
`Found ${filteredMatches.length} files matching pattern`
|
|
1341
|
+
);
|
|
1342
|
+
return filteredMatches;
|
|
997
1343
|
}
|
|
998
1344
|
/**
|
|
999
1345
|
* Discover all files in a directory
|
|
1000
1346
|
*/
|
|
1001
|
-
async discoverFilesFromDirectory(dirPath, ignorePatterns) {
|
|
1347
|
+
async discoverFilesFromDirectory(dirPath, ignorePatterns, gitIgnoredFiles) {
|
|
1002
1348
|
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
1003
1349
|
const globPattern = path2.join(dirPath, "**/*").replace(/\\/g, "/");
|
|
1004
1350
|
const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
|
|
@@ -1008,22 +1354,25 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1008
1354
|
absolute: false,
|
|
1009
1355
|
ignore: allIgnorePatterns
|
|
1010
1356
|
});
|
|
1011
|
-
|
|
1012
|
-
|
|
1357
|
+
const filteredMatches = matches.filter((match) => !gitIgnoredFiles.has(match));
|
|
1358
|
+
this.eventEmitter.fileDiscoveryProgress(`Found ${filteredMatches.length} files in directory`);
|
|
1359
|
+
return filteredMatches;
|
|
1013
1360
|
}
|
|
1014
1361
|
/**
|
|
1015
1362
|
* Discover all files from directories using glob patterns (scan mode)
|
|
1016
1363
|
*/
|
|
1017
|
-
async discoverAllFiles(directories, ignorePatterns) {
|
|
1364
|
+
async discoverAllFiles(directories, ignorePatterns, gitIgnoredFiles) {
|
|
1018
1365
|
const allFiles = [];
|
|
1019
1366
|
const workspaceRoot = this.environment.getWorkspaceRoot();
|
|
1020
1367
|
const allIgnorePatterns = ["**/node_modules/**", "**/.git/**", ...ignorePatterns];
|
|
1021
1368
|
for (const dir of directories) {
|
|
1022
1369
|
const stats = fs2.statSync(dir);
|
|
1023
1370
|
if (!stats.isDirectory()) {
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
|
|
1371
|
+
const relativePath = path2.relative(workspaceRoot, dir);
|
|
1372
|
+
const isGitIgnored = gitIgnoredFiles.has(relativePath);
|
|
1373
|
+
const shouldIgnore = this.matchesAnyPattern(relativePath, ignorePatterns);
|
|
1374
|
+
if (!isGitIgnored && !shouldIgnore) {
|
|
1375
|
+
allFiles.push(relativePath);
|
|
1027
1376
|
}
|
|
1028
1377
|
continue;
|
|
1029
1378
|
}
|
|
@@ -1037,16 +1386,18 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1037
1386
|
const relativePaths = matches.map((match) => {
|
|
1038
1387
|
const absolutePath = path2.resolve(dir, match);
|
|
1039
1388
|
return path2.relative(workspaceRoot, absolutePath);
|
|
1040
|
-
});
|
|
1389
|
+
}).filter((relativePath) => !gitIgnoredFiles.has(relativePath));
|
|
1041
1390
|
allFiles.push(...relativePaths);
|
|
1042
1391
|
}
|
|
1043
1392
|
return [...new Set(allFiles)];
|
|
1044
1393
|
}
|
|
1045
1394
|
/**
|
|
1046
|
-
*
|
|
1395
|
+
* Check if a file matches any of the given patterns using the ignore package
|
|
1047
1396
|
*/
|
|
1048
|
-
|
|
1049
|
-
|
|
1397
|
+
matchesAnyPattern(filePath, patterns) {
|
|
1398
|
+
if (patterns.length === 0) return false;
|
|
1399
|
+
const ig = ignore().add(patterns);
|
|
1400
|
+
return ig.ignores(filePath);
|
|
1050
1401
|
}
|
|
1051
1402
|
/**
|
|
1052
1403
|
* Check if a file should be processed
|
|
@@ -1055,9 +1406,6 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1055
1406
|
if (this.mode === "check") {
|
|
1056
1407
|
return true;
|
|
1057
1408
|
}
|
|
1058
|
-
if (!this.diffMode) {
|
|
1059
|
-
return true;
|
|
1060
|
-
}
|
|
1061
1409
|
const { filePath } = options;
|
|
1062
1410
|
return this.diffMode.changedFiles.some((changedFile) => {
|
|
1063
1411
|
return filePath === changedFile || filePath.endsWith(changedFile) || changedFile.endsWith(filePath);
|
|
@@ -1070,9 +1418,6 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1070
1418
|
if (this.mode === "check") {
|
|
1071
1419
|
return true;
|
|
1072
1420
|
}
|
|
1073
|
-
if (!this.diffMode) {
|
|
1074
|
-
return true;
|
|
1075
|
-
}
|
|
1076
1421
|
const { filePath, startLine, endLine } = options;
|
|
1077
1422
|
const fileChange = this.diffMode.fileChangeMap.get(filePath);
|
|
1078
1423
|
if (!fileChange) {
|
|
@@ -1093,9 +1438,6 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1093
1438
|
if (this.mode === "check") {
|
|
1094
1439
|
return true;
|
|
1095
1440
|
}
|
|
1096
|
-
if (!this.diffMode) {
|
|
1097
|
-
return true;
|
|
1098
|
-
}
|
|
1099
1441
|
const { match } = options;
|
|
1100
1442
|
return this.isFileValid({ filePath: match.filePath }) && this.isLineRangeValid({
|
|
1101
1443
|
filePath: match.filePath,
|
|
@@ -1142,20 +1484,19 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1142
1484
|
// src/steps/FileFilterStep.ts
|
|
1143
1485
|
import * as fs3 from "fs";
|
|
1144
1486
|
import * as path3 from "path";
|
|
1145
|
-
import
|
|
1487
|
+
import ignore2 from "ignore";
|
|
1146
1488
|
var FileFilterStep = class {
|
|
1147
1489
|
environment;
|
|
1148
1490
|
constructor(environment) {
|
|
1149
1491
|
this.environment = environment;
|
|
1150
1492
|
}
|
|
1151
1493
|
/**
|
|
1152
|
-
*
|
|
1153
|
-
* @param path - The path to test
|
|
1154
|
-
* @param pattern - The glob pattern to match against
|
|
1155
|
-
* @returns true if the path matches the pattern
|
|
1494
|
+
* Check if a file matches any of the given patterns using the ignore package
|
|
1156
1495
|
*/
|
|
1157
|
-
|
|
1158
|
-
|
|
1496
|
+
matchesAnyPattern(filePath, patterns) {
|
|
1497
|
+
if (patterns.length === 0) return false;
|
|
1498
|
+
const ig = ignore2().add(patterns);
|
|
1499
|
+
return ig.ignores(filePath);
|
|
1159
1500
|
}
|
|
1160
1501
|
/**
|
|
1161
1502
|
* Evaluate a single file filter condition for a given file path
|
|
@@ -1169,10 +1510,6 @@ var FileFilterStep = class {
|
|
|
1169
1510
|
const siblingPath = path3.join(dir, filename);
|
|
1170
1511
|
return fs3.existsSync(siblingPath);
|
|
1171
1512
|
}
|
|
1172
|
-
if ("fs.pathMatches" in condition) {
|
|
1173
|
-
const { pattern } = condition["fs.pathMatches"];
|
|
1174
|
-
return this.matchesPattern(filePath, pattern);
|
|
1175
|
-
}
|
|
1176
1513
|
if ("fs.ancestorHas" in condition) {
|
|
1177
1514
|
const { filename } = condition["fs.ancestorHas"];
|
|
1178
1515
|
let currentDir = path3.dirname(absoluteFilePath);
|
|
@@ -1195,7 +1532,7 @@ var FileFilterStep = class {
|
|
|
1195
1532
|
return false;
|
|
1196
1533
|
}
|
|
1197
1534
|
const siblings = fs3.readdirSync(dir);
|
|
1198
|
-
return siblings.some((sibling) => this.
|
|
1535
|
+
return siblings.some((sibling) => this.matchesAnyPattern(sibling, [pattern]));
|
|
1199
1536
|
}
|
|
1200
1537
|
return false;
|
|
1201
1538
|
}
|
|
@@ -1204,13 +1541,13 @@ var FileFilterStep = class {
|
|
|
1204
1541
|
*/
|
|
1205
1542
|
evaluateConditions(conditions, filePath) {
|
|
1206
1543
|
const results = [];
|
|
1207
|
-
if (conditions.all) {
|
|
1544
|
+
if (conditions.all && conditions.all.length > 0) {
|
|
1208
1545
|
results.push(conditions.all.every((condition) => this.evaluateCondition(condition, filePath)));
|
|
1209
1546
|
}
|
|
1210
|
-
if (conditions.any) {
|
|
1547
|
+
if (conditions.any && conditions.any.length > 0) {
|
|
1211
1548
|
results.push(conditions.any.some((condition) => this.evaluateCondition(condition, filePath)));
|
|
1212
1549
|
}
|
|
1213
|
-
if (conditions.not) {
|
|
1550
|
+
if (conditions.not && conditions.not.length > 0) {
|
|
1214
1551
|
results.push(!conditions.not.some((condition) => this.evaluateCondition(condition, filePath)));
|
|
1215
1552
|
}
|
|
1216
1553
|
return results.length === 0 ? true : results.every((result) => result === true);
|
|
@@ -1224,18 +1561,15 @@ var FileFilterStep = class {
|
|
|
1224
1561
|
let filteredPaths = filePaths;
|
|
1225
1562
|
if ((_a = options.include) == null ? void 0 : _a.length) {
|
|
1226
1563
|
filteredPaths = filteredPaths.filter(
|
|
1227
|
-
(filePath) =>
|
|
1564
|
+
(filePath) => this.matchesAnyPattern(filePath, options.include)
|
|
1228
1565
|
);
|
|
1229
1566
|
}
|
|
1230
1567
|
if ((_b = options.ignore) == null ? void 0 : _b.length) {
|
|
1231
|
-
filteredPaths = filteredPaths.filter(
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
);
|
|
1235
|
-
return !matchesIgnore;
|
|
1236
|
-
});
|
|
1568
|
+
filteredPaths = filteredPaths.filter(
|
|
1569
|
+
(filePath) => !this.matchesAnyPattern(filePath, options.ignore)
|
|
1570
|
+
);
|
|
1237
1571
|
}
|
|
1238
|
-
if (options.conditions) {
|
|
1572
|
+
if (Object.keys(options.conditions || {}).length > 0 && filteredPaths.length > 0) {
|
|
1239
1573
|
filteredPaths = filteredPaths.filter(
|
|
1240
1574
|
(filePath) => this.evaluateConditions(options.conditions, filePath)
|
|
1241
1575
|
);
|
|
@@ -1252,7 +1586,7 @@ var FileFilterStep = class {
|
|
|
1252
1586
|
import path8 from "path";
|
|
1253
1587
|
|
|
1254
1588
|
// src/languages.ts
|
|
1255
|
-
import { existsSync as
|
|
1589
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1256
1590
|
import { createRequire } from "module";
|
|
1257
1591
|
import path4 from "path";
|
|
1258
1592
|
import angular from "@ast-grep/lang-angular";
|
|
@@ -1290,15 +1624,15 @@ var require2 = createRequire(import.meta.url ? import.meta.url : __filename);
|
|
|
1290
1624
|
function getGraphQLLibPath() {
|
|
1291
1625
|
const graphqlDir = path4.dirname(require2.resolve("tree-sitter-graphql"));
|
|
1292
1626
|
const releaseNode = path4.join(graphqlDir, "../../build/Release/tree_sitter_graphql_binding.node");
|
|
1293
|
-
if (
|
|
1627
|
+
if (existsSync4(releaseNode)) {
|
|
1294
1628
|
return releaseNode;
|
|
1295
1629
|
}
|
|
1296
1630
|
const debugNode = path4.join(graphqlDir, "../../build/Debug/tree_sitter_graphql_binding.node");
|
|
1297
|
-
if (
|
|
1631
|
+
if (existsSync4(debugNode)) {
|
|
1298
1632
|
return debugNode;
|
|
1299
1633
|
}
|
|
1300
1634
|
const soFile = path4.join(graphqlDir, "parser.so");
|
|
1301
|
-
if (
|
|
1635
|
+
if (existsSync4(soFile)) {
|
|
1302
1636
|
return soFile;
|
|
1303
1637
|
}
|
|
1304
1638
|
return null;
|
|
@@ -2130,7 +2464,7 @@ var FindMatchesStep = class {
|
|
|
2130
2464
|
|
|
2131
2465
|
// src/steps/GotoDefinitionStep.ts
|
|
2132
2466
|
import path9 from "path";
|
|
2133
|
-
import
|
|
2467
|
+
import ignore3 from "ignore";
|
|
2134
2468
|
var GotoDefinitionStep = class {
|
|
2135
2469
|
maxDepth = 1;
|
|
2136
2470
|
environment;
|
|
@@ -2297,7 +2631,8 @@ var GotoDefinitionStep = class {
|
|
|
2297
2631
|
return relativePath === spec.path;
|
|
2298
2632
|
}
|
|
2299
2633
|
if (spec.glob) {
|
|
2300
|
-
|
|
2634
|
+
const ig = ignore3().add(spec.glob);
|
|
2635
|
+
return ig.ignores(relativePath);
|
|
2301
2636
|
}
|
|
2302
2637
|
if (spec.regex) {
|
|
2303
2638
|
const regex = new RegExp(spec.regex);
|
|
@@ -2643,7 +2978,7 @@ var RuleExecutor = class {
|
|
|
2643
2978
|
options.mode,
|
|
2644
2979
|
this.eventEmitter,
|
|
2645
2980
|
options.filePath,
|
|
2646
|
-
options.
|
|
2981
|
+
options.diffOptions
|
|
2647
2982
|
);
|
|
2648
2983
|
this.currentMode = options.mode;
|
|
2649
2984
|
}
|
|
@@ -2732,9 +3067,9 @@ var SKIPPED_COLOR = "#9b59b6";
|
|
|
2732
3067
|
var BRAND_COLOR = "#fbbf24";
|
|
2733
3068
|
function formatClickableRuleId(ruleId, internalId) {
|
|
2734
3069
|
if (internalId) {
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
3070
|
+
const url = `https://app.wispbit.com/rules/${internalId}`;
|
|
3071
|
+
const link = `\x1B]8;;${url}\x07${chalk.dim.underline(ruleId)}\x1B]8;;\x07`;
|
|
3072
|
+
return link;
|
|
2738
3073
|
}
|
|
2739
3074
|
return chalk.dim(ruleId);
|
|
2740
3075
|
}
|
|
@@ -2855,12 +3190,12 @@ function printSummary(results, summary) {
|
|
|
2855
3190
|
`;
|
|
2856
3191
|
} else {
|
|
2857
3192
|
const sortedMatches = ruleData.matches.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
2858
|
-
const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <=
|
|
3193
|
+
const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <= 20);
|
|
2859
3194
|
if (shouldShowCodeSnippets) {
|
|
2860
3195
|
sortedMatches.forEach((match, index) => {
|
|
2861
3196
|
output += ` ${match.filePath}
|
|
2862
3197
|
`;
|
|
2863
|
-
if (match.text && match.text.split("\n").length <=
|
|
3198
|
+
if (match.text && match.text.split("\n").length <= 20) {
|
|
2864
3199
|
const lines = match.text.split("\n");
|
|
2865
3200
|
lines.forEach((line, lineIndex) => {
|
|
2866
3201
|
const lineNumber = match.line + lineIndex;
|
|
@@ -2888,24 +3223,22 @@ function printSummary(results, summary) {
|
|
|
2888
3223
|
`;
|
|
2889
3224
|
}
|
|
2890
3225
|
});
|
|
3226
|
+
console.log(output);
|
|
2891
3227
|
const total = violationCount + suggestionCount;
|
|
2892
|
-
if (total === 0) {
|
|
2893
|
-
console.log(`
|
|
2894
|
-
no results`);
|
|
2895
|
-
}
|
|
2896
3228
|
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)}`;
|
|
2897
3229
|
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)}`;
|
|
2898
3230
|
const filesText = summary.totalFiles ? `${summary.totalFiles} ${pluralize("file", summary.totalFiles)}` : "0 files";
|
|
2899
3231
|
const rulesText = `${summary.totalRules} ${pluralize("rule", summary.totalRules)}`;
|
|
2900
3232
|
const timeText = summary.executionTime ? `${Math.round(summary.executionTime)}ms` : "";
|
|
2901
3233
|
const detailsText = [filesText, rulesText, timeText].filter(Boolean).join(", ");
|
|
2902
|
-
|
|
3234
|
+
let summaryOutput = "";
|
|
3235
|
+
summaryOutput += `${violationText}
|
|
2903
3236
|
`;
|
|
2904
|
-
|
|
3237
|
+
summaryOutput += `${suggestionText}
|
|
2905
3238
|
`;
|
|
2906
|
-
|
|
3239
|
+
summaryOutput += `${chalk.dim("summary".padStart(12))} ${detailsText}
|
|
2907
3240
|
`;
|
|
2908
|
-
console.log(total > 0 ? chalk.reset(
|
|
3241
|
+
console.log(total > 0 ? chalk.reset(summaryOutput) : summaryOutput);
|
|
2909
3242
|
if (violationCount > 0) {
|
|
2910
3243
|
process.exit(1);
|
|
2911
3244
|
}
|
|
@@ -3406,13 +3739,13 @@ https://wispbit.com/
|
|
|
3406
3739
|
Usage:
|
|
3407
3740
|
$ wispbit [diff-options] (run diff by default)
|
|
3408
3741
|
$ wispbit check [file-path/directory] [check-options]
|
|
3409
|
-
$ wispbit diff [diff-options]
|
|
3742
|
+
$ wispbit diff [commit] [diff-options]
|
|
3410
3743
|
$ wispbit list
|
|
3411
3744
|
$ wispbit cache purge
|
|
3412
3745
|
|
|
3413
3746
|
Commands:
|
|
3414
|
-
check [file-path/directory]
|
|
3415
|
-
diff
|
|
3747
|
+
check [file-path/directory] Run linting rules against a specific file/folder or entire codebase
|
|
3748
|
+
diff [commit] Run linting rules on changed files
|
|
3416
3749
|
list List all available rules with their ID, message, and severity
|
|
3417
3750
|
cache purge Purge the cache directory (indexes, caching, etc.)
|
|
3418
3751
|
|
|
@@ -3424,10 +3757,21 @@ Options for check:
|
|
|
3424
3757
|
Options for diff:
|
|
3425
3758
|
--rule <ruleId> Optional rule ID to run specific rule
|
|
3426
3759
|
--json [format] Output in JSON format (pretty, stream, compact)
|
|
3427
|
-
--
|
|
3428
|
-
|
|
3760
|
+
--include <types> Include change types (comma-separated): committed, staged, unstaged, untracked
|
|
3761
|
+
\u2022 committed: All committed changes (modified, deleted, renamed)
|
|
3762
|
+
\u2022 staged: Only staged changes ready to commit
|
|
3763
|
+
\u2022 unstaged: Only unstaged tracked file modifications
|
|
3764
|
+
\u2022 untracked: Only new untracked files
|
|
3765
|
+
(default: all four types)
|
|
3429
3766
|
-d, --debug Enable debug output
|
|
3430
3767
|
|
|
3768
|
+
Diff examples:
|
|
3769
|
+
$ wispbit diff Show all changes (default: committed,staged,unstaged,untracked)
|
|
3770
|
+
$ wispbit diff --include staged Show only staged changes
|
|
3771
|
+
$ wispbit diff --include staged,untracked Show staged and untracked changes
|
|
3772
|
+
$ wispbit diff HEAD~3 Compare working directory against 3 commits ago
|
|
3773
|
+
$ wispbit diff main..HEAD Compare against main branch including worktree changes
|
|
3774
|
+
$ wispbit abc123 --include committed Compare against specific commit SHA, show only committed
|
|
3431
3775
|
|
|
3432
3776
|
Global options:
|
|
3433
3777
|
-v, --version Show version number
|
|
@@ -3443,10 +3787,8 @@ Global options:
|
|
|
3443
3787
|
json: {
|
|
3444
3788
|
type: "string"
|
|
3445
3789
|
},
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
},
|
|
3449
|
-
head: {
|
|
3790
|
+
// Diff include
|
|
3791
|
+
include: {
|
|
3450
3792
|
type: "string"
|
|
3451
3793
|
},
|
|
3452
3794
|
// Debug option
|
|
@@ -3472,14 +3814,43 @@ async function executeCommand(options) {
|
|
|
3472
3814
|
const environment = new Environment();
|
|
3473
3815
|
const config = await ensureConfigured();
|
|
3474
3816
|
const { ruleId, json: json2, mode, filePath } = options;
|
|
3817
|
+
let { commitSelector, diffInclude } = options;
|
|
3818
|
+
if (mode === "diff") {
|
|
3819
|
+
const repoRoot = environment.getWorkspaceRoot();
|
|
3820
|
+
if (!commitSelector) {
|
|
3821
|
+
const defaults = await getDefaultCommitSelector(repoRoot);
|
|
3822
|
+
commitSelector = defaults.commitSelector;
|
|
3823
|
+
if (!diffInclude) {
|
|
3824
|
+
diffInclude = defaults.include;
|
|
3825
|
+
}
|
|
3826
|
+
} else {
|
|
3827
|
+
if (!diffInclude) {
|
|
3828
|
+
const defaults = await getDefaultCommitSelector(repoRoot);
|
|
3829
|
+
diffInclude = defaults.include;
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
if (mode === "diff" && commitSelector && diffInclude) {
|
|
3834
|
+
const repoRoot = environment.getWorkspaceRoot();
|
|
3835
|
+
const currentBranch = await getCurrentBranch(repoRoot);
|
|
3836
|
+
const validationError = validateWorktreeIncludes(commitSelector, diffInclude, currentBranch);
|
|
3837
|
+
if (validationError) {
|
|
3838
|
+
console.error(chalk4.red(validationError));
|
|
3839
|
+
process.exit(1);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3475
3842
|
let jsonOutput = false;
|
|
3476
3843
|
let jsonFormat = "pretty";
|
|
3477
|
-
if (json2) {
|
|
3844
|
+
if (json2 !== void 0) {
|
|
3478
3845
|
jsonOutput = true;
|
|
3479
|
-
if (json2 === "
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3846
|
+
if (typeof json2 === "string") {
|
|
3847
|
+
if (json2 === "stream") {
|
|
3848
|
+
jsonFormat = "stream";
|
|
3849
|
+
} else if (json2 === "compact") {
|
|
3850
|
+
jsonFormat = "compact";
|
|
3851
|
+
} else {
|
|
3852
|
+
jsonFormat = "pretty";
|
|
3853
|
+
}
|
|
3483
3854
|
} else {
|
|
3484
3855
|
jsonFormat = "pretty";
|
|
3485
3856
|
}
|
|
@@ -3508,26 +3879,30 @@ async function executeCommand(options) {
|
|
|
3508
3879
|
}
|
|
3509
3880
|
throw error;
|
|
3510
3881
|
}
|
|
3511
|
-
if (!
|
|
3882
|
+
if (!jsonOutput) {
|
|
3512
3883
|
if (mode === "check") {
|
|
3513
3884
|
const modeBox = chalk4.bgHex("#eab308").black(" CHECK ");
|
|
3514
3885
|
const targetInfo = chalk4.dim(` (${filePath || "all files"})`);
|
|
3515
3886
|
console.log(`${modeBox}${targetInfo}`);
|
|
3516
3887
|
} else {
|
|
3517
|
-
const baseCommit = cli.flags.base || "origin/main";
|
|
3518
|
-
const headCommit = cli.flags.head || "HEAD";
|
|
3519
3888
|
const modeBox = chalk4.bgHex("#4a90e2").black(" DIFF ");
|
|
3520
|
-
const
|
|
3521
|
-
|
|
3889
|
+
const truncatedRange = (commitSelector || "").replace(
|
|
3890
|
+
/\b[0-9a-f]{30,}\b/gi,
|
|
3891
|
+
(match) => match.substring(0, 7)
|
|
3892
|
+
);
|
|
3893
|
+
const includeInfo = diffInclude ? ` ${truncatedRange} ${chalk4.dim(`[${diffInclude.join("/")}]`)}` : ` ${truncatedRange}`;
|
|
3894
|
+
console.log(`${modeBox}${includeInfo}`);
|
|
3522
3895
|
}
|
|
3523
3896
|
}
|
|
3524
3897
|
const eventEmitter = new ExecutionEventEmitter();
|
|
3525
3898
|
if (mode === "check") {
|
|
3526
3899
|
eventEmitter.setExecutionMode("check", { filePath });
|
|
3527
3900
|
} else {
|
|
3528
|
-
const
|
|
3529
|
-
|
|
3530
|
-
|
|
3901
|
+
const includeDisplay = diffInclude ? diffInclude.join(", ") : "all";
|
|
3902
|
+
eventEmitter.setExecutionMode("diff", {
|
|
3903
|
+
baseCommit: commitSelector || "",
|
|
3904
|
+
headCommit: includeDisplay
|
|
3905
|
+
});
|
|
3531
3906
|
}
|
|
3532
3907
|
const cleanupTerminalReporter = !options.json ? setupTerminalReporter(eventEmitter, options.debug || false) : void 0;
|
|
3533
3908
|
const executionStartTime = Date.now();
|
|
@@ -3539,7 +3914,10 @@ async function executeCommand(options) {
|
|
|
3539
3914
|
const ruleResults = await ruleExecutor.execute(rules, {
|
|
3540
3915
|
mode,
|
|
3541
3916
|
filePath,
|
|
3542
|
-
|
|
3917
|
+
diffOptions: mode === "diff" ? {
|
|
3918
|
+
include: diffInclude,
|
|
3919
|
+
commitSelector
|
|
3920
|
+
} : void 0
|
|
3543
3921
|
});
|
|
3544
3922
|
const results = [];
|
|
3545
3923
|
for (const ruleResult of ruleResults) {
|
|
@@ -3574,9 +3952,16 @@ async function executeCommand(options) {
|
|
|
3574
3952
|
if (mode === "check") {
|
|
3575
3953
|
executionModeInfo = { mode: "check", filePath };
|
|
3576
3954
|
} else {
|
|
3577
|
-
const
|
|
3578
|
-
|
|
3579
|
-
|
|
3955
|
+
const truncatedRange = (commitSelector || "").replace(
|
|
3956
|
+
/\b[0-9a-f]{30,}\b/gi,
|
|
3957
|
+
(match) => match.substring(0, 7)
|
|
3958
|
+
);
|
|
3959
|
+
const includeDisplay = diffInclude ? diffInclude.join(", ") : "all";
|
|
3960
|
+
executionModeInfo = {
|
|
3961
|
+
mode: "diff",
|
|
3962
|
+
baseCommit: truncatedRange,
|
|
3963
|
+
headCommit: includeDisplay
|
|
3964
|
+
};
|
|
3580
3965
|
}
|
|
3581
3966
|
const executionTime = Date.now() - executionStartTime;
|
|
3582
3967
|
printSummary(results, {
|
|
@@ -3616,11 +4001,30 @@ async function main() {
|
|
|
3616
4001
|
break;
|
|
3617
4002
|
}
|
|
3618
4003
|
case "diff": {
|
|
4004
|
+
let diffInclude;
|
|
4005
|
+
if (cli.flags.include) {
|
|
4006
|
+
const includeString = cli.flags.include;
|
|
4007
|
+
const includes = includeString.split(",").map((f) => f.trim());
|
|
4008
|
+
const validIncludes = ["committed", "staged", "unstaged", "untracked"];
|
|
4009
|
+
const invalidIncludes = includes.filter((f) => !validIncludes.includes(f));
|
|
4010
|
+
if (invalidIncludes.length > 0) {
|
|
4011
|
+
console.error(
|
|
4012
|
+
chalk4.red(
|
|
4013
|
+
`Invalid include(s): ${invalidIncludes.join(", ")}. Valid includes: ${validIncludes.join(", ")}`
|
|
4014
|
+
)
|
|
4015
|
+
);
|
|
4016
|
+
process.exit(1);
|
|
4017
|
+
}
|
|
4018
|
+
diffInclude = includes;
|
|
4019
|
+
}
|
|
4020
|
+
const commitSelector = cli.input[1];
|
|
3619
4021
|
await executeCommand({
|
|
3620
4022
|
ruleId: cli.flags.rule,
|
|
3621
4023
|
json: cli.flags.json,
|
|
3622
4024
|
debug: cli.flags.debug,
|
|
3623
|
-
mode: "diff"
|
|
4025
|
+
mode: "diff",
|
|
4026
|
+
diffInclude,
|
|
4027
|
+
commitSelector
|
|
3624
4028
|
});
|
|
3625
4029
|
break;
|
|
3626
4030
|
}
|
|
@@ -3645,18 +4049,31 @@ ${chalk4.green("Cache purged successfully.")} Removed ${result.deletedCount} ite
|
|
|
3645
4049
|
break;
|
|
3646
4050
|
}
|
|
3647
4051
|
default: {
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
4052
|
+
let diffInclude;
|
|
4053
|
+
if (cli.flags.include) {
|
|
4054
|
+
const includeString = cli.flags.include;
|
|
4055
|
+
const includes = includeString.split(",").map((f) => f.trim());
|
|
4056
|
+
const validIncludes = ["committed", "staged", "unstaged", "untracked"];
|
|
4057
|
+
const invalidIncludes = includes.filter((f) => !validIncludes.includes(f));
|
|
4058
|
+
if (invalidIncludes.length > 0) {
|
|
4059
|
+
console.error(
|
|
4060
|
+
chalk4.red(
|
|
4061
|
+
`Invalid include(s): ${invalidIncludes.join(", ")}. Valid includes: ${validIncludes.join(", ")}`
|
|
4062
|
+
)
|
|
4063
|
+
);
|
|
4064
|
+
process.exit(1);
|
|
4065
|
+
}
|
|
4066
|
+
diffInclude = includes;
|
|
3659
4067
|
}
|
|
4068
|
+
const commitSelector = cli.input[0];
|
|
4069
|
+
await executeCommand({
|
|
4070
|
+
ruleId: cli.flags.rule,
|
|
4071
|
+
json: cli.flags.json,
|
|
4072
|
+
debug: cli.flags.debug,
|
|
4073
|
+
mode: "diff",
|
|
4074
|
+
diffInclude,
|
|
4075
|
+
commitSelector
|
|
4076
|
+
});
|
|
3660
4077
|
break;
|
|
3661
4078
|
}
|
|
3662
4079
|
}
|