@wispbit/local 1.0.27 → 1.0.29
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 +171 -105
- package/dist/cli.js.map +4 -4
- package/dist/package.json +1 -1
- package/dist/src/api/WispbitApiClient.d.ts +5 -0
- package/dist/src/api/WispbitApiClient.d.ts.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/providers/AstGrepAstProvider.d.ts.map +1 -1
- package/dist/src/providers/ScipIntelligenceProvider.d.ts.map +1 -1
- package/dist/src/providers/WispbitRuleProvider.d.ts.map +1 -1
- package/dist/src/steps/FileExecutionContext.d.ts +3 -10
- package/dist/src/steps/FileExecutionContext.d.ts.map +1 -1
- package/dist/src/types.d.ts +1 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/coordinates.d.ts +12 -0
- package/dist/src/utils/coordinates.d.ts.map +1 -0
- package/dist/src/utils/diffValidation.d.ts +24 -0
- package/dist/src/utils/diffValidation.d.ts.map +1 -0
- package/dist/src/utils/diffValidation.test.d.ts +2 -0
- package/dist/src/utils/diffValidation.test.d.ts.map +1 -0
- package/dist/src/utils/formatters.d.ts +5 -2
- package/dist/src/utils/formatters.d.ts.map +1 -1
- package/dist/src/utils/ruleExecution.d.ts +20 -0
- package/dist/src/utils/ruleExecution.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 +1 -1
package/dist/cli.js
CHANGED
|
@@ -245,7 +245,8 @@ var GetRulesResponseSchema = z2.object({
|
|
|
245
245
|
message: z2.string(),
|
|
246
246
|
prompt: z2.string(),
|
|
247
247
|
severity: z2.enum(["suggestion", "violation"]),
|
|
248
|
-
schema: z2.any()
|
|
248
|
+
schema: z2.any(),
|
|
249
|
+
execution: z2.enum(["llm", "deterministic"]).optional()
|
|
249
250
|
})
|
|
250
251
|
)
|
|
251
252
|
});
|
|
@@ -1280,6 +1281,7 @@ var WispbitRuleProvider = class {
|
|
|
1280
1281
|
config: {
|
|
1281
1282
|
message: rule.message,
|
|
1282
1283
|
severity: rule.severity,
|
|
1284
|
+
execution: rule.execution || "llm",
|
|
1283
1285
|
steps: rule.schema
|
|
1284
1286
|
},
|
|
1285
1287
|
prompt: rule.prompt,
|
|
@@ -1414,6 +1416,59 @@ import * as fs2 from "fs";
|
|
|
1414
1416
|
import * as path3 from "path";
|
|
1415
1417
|
import { glob } from "glob";
|
|
1416
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
|
+
|
|
1417
1472
|
// src/utils/patternMatching.ts
|
|
1418
1473
|
import ignore from "ignore";
|
|
1419
1474
|
function matchesAnyPattern(filePath, patterns) {
|
|
@@ -1703,30 +1758,11 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1703
1758
|
return true;
|
|
1704
1759
|
}
|
|
1705
1760
|
const { filePath } = options;
|
|
1706
|
-
return
|
|
1707
|
-
|
|
1761
|
+
return isFileValidInDiff(filePath, {
|
|
1762
|
+
changedFiles: this.diffMode.changedFiles,
|
|
1763
|
+
fileChangeMap: this.diffMode.fileChangeMap
|
|
1708
1764
|
});
|
|
1709
1765
|
}
|
|
1710
|
-
/**
|
|
1711
|
-
* Check if a specific line range should be processed
|
|
1712
|
-
*/
|
|
1713
|
-
isLineRangeValid(options) {
|
|
1714
|
-
if (this.mode === "check") {
|
|
1715
|
-
return true;
|
|
1716
|
-
}
|
|
1717
|
-
const { filePath, startLine, endLine } = options;
|
|
1718
|
-
const fileChange = this.diffMode.fileChangeMap.get(filePath);
|
|
1719
|
-
if (!fileChange) {
|
|
1720
|
-
return false;
|
|
1721
|
-
}
|
|
1722
|
-
if (fileChange.status === "added") {
|
|
1723
|
-
return true;
|
|
1724
|
-
}
|
|
1725
|
-
if (fileChange.status === "removed") {
|
|
1726
|
-
return false;
|
|
1727
|
-
}
|
|
1728
|
-
return this.isLineRangeInPatch({ patch: fileChange.patch, startLine, endLine });
|
|
1729
|
-
}
|
|
1730
1766
|
/**
|
|
1731
1767
|
* Check if a match should be processed
|
|
1732
1768
|
*/
|
|
@@ -1735,46 +1771,11 @@ var FileExecutionContext = class _FileExecutionContext {
|
|
|
1735
1771
|
return true;
|
|
1736
1772
|
}
|
|
1737
1773
|
const { match } = options;
|
|
1738
|
-
return
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
endLine: match.range.end.line
|
|
1774
|
+
return isMatchValidInDiff(match, {
|
|
1775
|
+
changedFiles: this.diffMode.changedFiles,
|
|
1776
|
+
fileChangeMap: this.diffMode.fileChangeMap
|
|
1742
1777
|
});
|
|
1743
1778
|
}
|
|
1744
|
-
// ===== Helper Methods =====
|
|
1745
|
-
/**
|
|
1746
|
-
* Parse a git patch to determine if a line range intersects with changed lines
|
|
1747
|
-
* Adapted from patchParser.ts logic
|
|
1748
|
-
*/
|
|
1749
|
-
isLineRangeInPatch(options) {
|
|
1750
|
-
const { patch, startLine, endLine } = options;
|
|
1751
|
-
const lines = patch.split("\n");
|
|
1752
|
-
let currentNewLine = 0;
|
|
1753
|
-
let inHunk = false;
|
|
1754
|
-
for (const line of lines) {
|
|
1755
|
-
const hunkMatch = line.match(/^@@\s+-\d+(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
|
|
1756
|
-
if (hunkMatch) {
|
|
1757
|
-
currentNewLine = parseInt(hunkMatch[1], 10);
|
|
1758
|
-
inHunk = true;
|
|
1759
|
-
continue;
|
|
1760
|
-
}
|
|
1761
|
-
if (!inHunk) continue;
|
|
1762
|
-
if (line.startsWith("+")) {
|
|
1763
|
-
if (currentNewLine >= startLine && currentNewLine <= endLine) {
|
|
1764
|
-
return true;
|
|
1765
|
-
}
|
|
1766
|
-
currentNewLine++;
|
|
1767
|
-
} else if (line.startsWith("-")) {
|
|
1768
|
-
continue;
|
|
1769
|
-
} else if (!line.startsWith("\\")) {
|
|
1770
|
-
if (currentNewLine >= startLine && currentNewLine <= endLine) {
|
|
1771
|
-
return true;
|
|
1772
|
-
}
|
|
1773
|
-
currentNewLine++;
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
return false;
|
|
1777
|
-
}
|
|
1778
1779
|
};
|
|
1779
1780
|
|
|
1780
1781
|
// src/steps/FileFilterStep.ts
|
|
@@ -1876,6 +1877,40 @@ import path8 from "path";
|
|
|
1876
1877
|
import { readFile as readFile2 } from "fs/promises";
|
|
1877
1878
|
import path5 from "path";
|
|
1878
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
|
|
1879
1914
|
var AstGrepAstProvider = class {
|
|
1880
1915
|
environment;
|
|
1881
1916
|
language;
|
|
@@ -1932,7 +1967,7 @@ var AstGrepAstProvider = class {
|
|
|
1932
1967
|
const matches = [];
|
|
1933
1968
|
for (const { filePath, sgNode } of sgNodes) {
|
|
1934
1969
|
const range = sgNode.range();
|
|
1935
|
-
|
|
1970
|
+
const match0Indexed = {
|
|
1936
1971
|
filePath,
|
|
1937
1972
|
text: sgNode.text(),
|
|
1938
1973
|
range: {
|
|
@@ -1948,7 +1983,8 @@ var AstGrepAstProvider = class {
|
|
|
1948
1983
|
// Try to extract symbol if possible
|
|
1949
1984
|
symbol: this.extractSymbol(sgNode),
|
|
1950
1985
|
language: this.language
|
|
1951
|
-
}
|
|
1986
|
+
};
|
|
1987
|
+
matches.push(matchTo1Indexed(match0Indexed));
|
|
1952
1988
|
}
|
|
1953
1989
|
return matches;
|
|
1954
1990
|
}
|
|
@@ -1991,10 +2027,11 @@ var AstGrepAstProvider = class {
|
|
|
1991
2027
|
const absolutePath = path5.resolve(this.environment.getWorkspaceRoot(), match.filePath);
|
|
1992
2028
|
const content = await readFile2(absolutePath, "utf-8");
|
|
1993
2029
|
const root = parse2(this.language, content).root();
|
|
2030
|
+
const match0Indexed = matchTo0Indexed(match);
|
|
1994
2031
|
const nodeAtPosition = this.findNodeAtPosition(
|
|
1995
2032
|
root,
|
|
1996
|
-
|
|
1997
|
-
|
|
2033
|
+
match0Indexed.range.start.line,
|
|
2034
|
+
match0Indexed.range.start.column
|
|
1998
2035
|
);
|
|
1999
2036
|
if (!nodeAtPosition) {
|
|
2000
2037
|
return null;
|
|
@@ -2005,7 +2042,7 @@ var AstGrepAstProvider = class {
|
|
|
2005
2042
|
const kindStr = typeof kindValue === "string" ? kindValue : String(kindValue);
|
|
2006
2043
|
if (targetNodeKinds.has(kindStr)) {
|
|
2007
2044
|
const range = current.range();
|
|
2008
|
-
|
|
2045
|
+
const expandedMatch0Indexed = {
|
|
2009
2046
|
filePath: match.filePath,
|
|
2010
2047
|
text: current.text(),
|
|
2011
2048
|
range: {
|
|
@@ -2015,6 +2052,7 @@ var AstGrepAstProvider = class {
|
|
|
2015
2052
|
symbol: match.symbol,
|
|
2016
2053
|
language: this.language
|
|
2017
2054
|
};
|
|
2055
|
+
return matchTo1Indexed(expandedMatch0Indexed);
|
|
2018
2056
|
}
|
|
2019
2057
|
current = current.parent();
|
|
2020
2058
|
}
|
|
@@ -2215,13 +2253,14 @@ var ScipIntelligenceProvider = class {
|
|
|
2215
2253
|
async findDefinitions(match) {
|
|
2216
2254
|
await this.ensureIndexReady();
|
|
2217
2255
|
if (!this.scipIndex) throw new Error("SCIP index not ready");
|
|
2218
|
-
|
|
2256
|
+
const match0Indexed = matchTo0Indexed(match);
|
|
2257
|
+
this.eventEmitter.startScipMatchLookup(this.language, match0Indexed);
|
|
2219
2258
|
const definitions = [];
|
|
2220
2259
|
const documents = this.scipIndex.documents;
|
|
2221
2260
|
for (const document of documents) {
|
|
2222
|
-
if (document.relative_path !==
|
|
2261
|
+
if (document.relative_path !== match0Indexed.filePath) continue;
|
|
2223
2262
|
this.eventEmitter.scipMatchLookupProgress(this.language, document);
|
|
2224
|
-
const scipOcc = this.findBestOverlappingOccurrence(document.occurrences,
|
|
2263
|
+
const scipOcc = this.findBestOverlappingOccurrence(document.occurrences, match0Indexed);
|
|
2225
2264
|
if (scipOcc) {
|
|
2226
2265
|
if (this.isExternalSymbol(scipOcc.symbol)) {
|
|
2227
2266
|
continue;
|
|
@@ -2229,7 +2268,7 @@ var ScipIntelligenceProvider = class {
|
|
|
2229
2268
|
this.eventEmitter.scipMatchLookupProgress(this.language, scipOcc);
|
|
2230
2269
|
const def = await this.findDefinitionForSymbol(scipOcc.symbol, documents);
|
|
2231
2270
|
if (def) {
|
|
2232
|
-
definitions.push(def);
|
|
2271
|
+
definitions.push(matchTo1Indexed(def));
|
|
2233
2272
|
}
|
|
2234
2273
|
}
|
|
2235
2274
|
}
|
|
@@ -2244,17 +2283,18 @@ var ScipIntelligenceProvider = class {
|
|
|
2244
2283
|
if (!this.scipIndex) {
|
|
2245
2284
|
return [];
|
|
2246
2285
|
}
|
|
2286
|
+
const match0Indexed = matchTo0Indexed(match);
|
|
2247
2287
|
const references = [];
|
|
2248
2288
|
const documents = this.scipIndex.documents;
|
|
2249
2289
|
for (const document of documents) {
|
|
2250
|
-
if (document.relative_path !==
|
|
2251
|
-
const scipOcc = this.findBestOverlappingOccurrence(document.occurrences,
|
|
2290
|
+
if (document.relative_path !== match0Indexed.filePath) continue;
|
|
2291
|
+
const scipOcc = this.findBestOverlappingOccurrence(document.occurrences, match0Indexed);
|
|
2252
2292
|
if (scipOcc) {
|
|
2253
2293
|
if (this.isExternalSymbol(scipOcc.symbol)) {
|
|
2254
2294
|
break;
|
|
2255
2295
|
}
|
|
2256
2296
|
const refs = await this.findReferencesForSymbol(scipOcc.symbol, documents);
|
|
2257
|
-
references.push(...refs);
|
|
2297
|
+
references.push(...refs.map((ref) => matchTo1Indexed(ref)));
|
|
2258
2298
|
break;
|
|
2259
2299
|
}
|
|
2260
2300
|
}
|
|
@@ -3193,18 +3233,25 @@ var InvalidRuleFormatError = class extends Error {
|
|
|
3193
3233
|
import { stripVTControlCharacters } from "node:util";
|
|
3194
3234
|
import chalk from "chalk";
|
|
3195
3235
|
import ora from "ora";
|
|
3196
|
-
var VIOLATION_COLOR = "#
|
|
3197
|
-
var SUGGESTION_COLOR = "#
|
|
3236
|
+
var VIOLATION_COLOR = "#f87171";
|
|
3237
|
+
var SUGGESTION_COLOR = "#60a5fa";
|
|
3198
3238
|
var SKIPPED_COLOR = "#9b59b6";
|
|
3199
3239
|
var BRAND_COLOR = "#fbbf24";
|
|
3240
|
+
var DETERMINISTIC_COLOR = "#9ca3af";
|
|
3241
|
+
var LLM_COLOR = "#9b59b6";
|
|
3200
3242
|
function formatClickableRuleId(ruleId, internalId) {
|
|
3201
3243
|
if (internalId) {
|
|
3202
3244
|
const url = `https://app.wispbit.com/rules/${internalId}`;
|
|
3203
|
-
|
|
3204
|
-
return link;
|
|
3245
|
+
return `${chalk.dim.underline(ruleId)}${chalk.dim("|")}${chalk.dim.underline(url)}`;
|
|
3205
3246
|
}
|
|
3206
3247
|
return chalk.dim(ruleId);
|
|
3207
3248
|
}
|
|
3249
|
+
function truncateMessage(message, maxWidth) {
|
|
3250
|
+
if (message.length <= maxWidth) {
|
|
3251
|
+
return message;
|
|
3252
|
+
}
|
|
3253
|
+
return message.substring(0, maxWidth - 3) + "...";
|
|
3254
|
+
}
|
|
3208
3255
|
var LINE_NUMBER_BG_COLOR = "#f8f8f8";
|
|
3209
3256
|
var LINE_NUMBER_TEXT_COLOR = "#888888";
|
|
3210
3257
|
var loadingFrames = [
|
|
@@ -3283,9 +3330,9 @@ function printSummary(results, summary) {
|
|
|
3283
3330
|
var _a;
|
|
3284
3331
|
ruleData.matches.push({
|
|
3285
3332
|
filePath: match.filePath,
|
|
3286
|
-
line: match.range.start.line
|
|
3287
|
-
column: match.range.start.column
|
|
3288
|
-
endLine: match.range.end.line !== match.range.start.line ? match.range.end.line
|
|
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,
|
|
3289
3336
|
text: match.text
|
|
3290
3337
|
});
|
|
3291
3338
|
if ((_a = match.metadata) == null ? void 0 : _a.llmValidation) {
|
|
@@ -3304,9 +3351,9 @@ function printSummary(results, summary) {
|
|
|
3304
3351
|
}
|
|
3305
3352
|
let messageType;
|
|
3306
3353
|
if (ruleData.severity === "violation") {
|
|
3307
|
-
messageType = chalk.hex(VIOLATION_COLOR)(" violation".padStart(
|
|
3354
|
+
messageType = chalk.hex(VIOLATION_COLOR)("\u25A0") + " " + chalk.hex(VIOLATION_COLOR).bold("violation".padStart(8));
|
|
3308
3355
|
} else if (ruleData.severity === "suggestion") {
|
|
3309
|
-
messageType = chalk.hex(SUGGESTION_COLOR)("suggestion".padEnd(
|
|
3356
|
+
messageType = chalk.hex(SUGGESTION_COLOR)("\u25CF") + " " + chalk.hex(SUGGESTION_COLOR).bold("suggestion".padEnd(6));
|
|
3310
3357
|
} else {
|
|
3311
3358
|
messageType = chalk.hex(SKIPPED_COLOR)(" skipped".padEnd(9));
|
|
3312
3359
|
}
|
|
@@ -3322,12 +3369,12 @@ function printSummary(results, summary) {
|
|
|
3322
3369
|
`;
|
|
3323
3370
|
} else {
|
|
3324
3371
|
const sortedMatches = ruleData.matches.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
3325
|
-
const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <=
|
|
3372
|
+
const shouldShowCodeSnippets = sortedMatches.length < 10 && sortedMatches.some((match) => match.text && match.text.split("\n").length <= 30);
|
|
3326
3373
|
if (shouldShowCodeSnippets) {
|
|
3327
3374
|
sortedMatches.forEach((match, index) => {
|
|
3328
|
-
|
|
3375
|
+
if (match.text && match.text.split("\n").length <= 30) {
|
|
3376
|
+
output += ` ${match.filePath}
|
|
3329
3377
|
`;
|
|
3330
|
-
if (match.text && match.text.split("\n").length <= 20) {
|
|
3331
3378
|
const lines = match.text.split("\n");
|
|
3332
3379
|
lines.forEach((line, lineIndex) => {
|
|
3333
3380
|
const lineNumber = match.line + lineIndex;
|
|
@@ -3337,6 +3384,10 @@ function printSummary(results, summary) {
|
|
|
3337
3384
|
output += ` ${lineNumberBox} ${chalk.dim(line)}
|
|
3338
3385
|
`;
|
|
3339
3386
|
});
|
|
3387
|
+
} else {
|
|
3388
|
+
const lineRange = match.endLine ? `(${match.line}:${match.endLine})` : `(${match.line})`;
|
|
3389
|
+
output += ` ${match.filePath} ${chalk.dim(lineRange)}
|
|
3390
|
+
`;
|
|
3340
3391
|
}
|
|
3341
3392
|
if (index < sortedMatches.length - 1) {
|
|
3342
3393
|
output += `
|
|
@@ -3384,21 +3435,29 @@ function printRulesList(rules) {
|
|
|
3384
3435
|
const tableData = [];
|
|
3385
3436
|
tableData.push([
|
|
3386
3437
|
"",
|
|
3387
|
-
`${"id".padEnd(12)} ${chalk.dim("\u2502")} ${"severity".padEnd(
|
|
3438
|
+
`${"id".padEnd(12)} ${chalk.dim("\u2502")} ${"severity".padEnd(16)} ${chalk.dim("\u2502")} ${"execution".padEnd(18)} ${chalk.dim("\u2502")} message`
|
|
3388
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);
|
|
3389
3443
|
tableData.push([
|
|
3390
3444
|
"",
|
|
3391
|
-
`${chalk.dim("\u2500".repeat(12))} ${chalk.dim("\u253C")} ${chalk.dim("\u2500".repeat(
|
|
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))}`
|
|
3392
3446
|
]);
|
|
3393
3447
|
for (const rule of rules) {
|
|
3394
|
-
const
|
|
3395
|
-
const
|
|
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);
|
|
3396
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);
|
|
3397
3455
|
const idPadding = Math.max(0, 12 - rule.id.length);
|
|
3398
|
-
const severityPadding = Math.max(0,
|
|
3456
|
+
const severityPadding = Math.max(0, 16 - (rule.config.severity.length + 2));
|
|
3457
|
+
const executionPadding = Math.max(0, 18 - (rule.config.execution.length + 2));
|
|
3399
3458
|
tableData.push([
|
|
3400
3459
|
"",
|
|
3401
|
-
`${ruleId}${" ".repeat(idPadding)} ${chalk.dim("\u2502")} ${severityText}${" ".repeat(severityPadding)} ${chalk.dim("\u2502")} ${
|
|
3460
|
+
`${ruleId}${" ".repeat(idPadding)} ${chalk.dim("\u2502")} ${severityText}${" ".repeat(severityPadding)} ${chalk.dim("\u2502")} ${executionText}${" ".repeat(executionPadding)} ${chalk.dim("\u2502")} ${truncatedMessage}`
|
|
3402
3461
|
]);
|
|
3403
3462
|
}
|
|
3404
3463
|
const table = textTable(tableData, {
|
|
@@ -3813,8 +3872,7 @@ async function loadApiKey() {
|
|
|
3813
3872
|
return null;
|
|
3814
3873
|
}
|
|
3815
3874
|
}
|
|
3816
|
-
async function ensureConfigured() {
|
|
3817
|
-
const environment = new Environment();
|
|
3875
|
+
async function ensureConfigured(environment) {
|
|
3818
3876
|
let apiKey = process.env.WISPBIT_API_KEY || null;
|
|
3819
3877
|
if (!apiKey) {
|
|
3820
3878
|
apiKey = await loadApiKey();
|
|
@@ -3845,7 +3903,7 @@ async function ensureConfigured() {
|
|
|
3845
3903
|
const repositoryUrl = await environment.getRepositoryUrl();
|
|
3846
3904
|
console.log(
|
|
3847
3905
|
chalk4.red(
|
|
3848
|
-
`No repository in wispbit found for url: ${repositoryUrl}. If your git remote URL was recently modified, use "git remote set-url origin <new-url>" to update the remote URL.`
|
|
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.`
|
|
3849
3907
|
)
|
|
3850
3908
|
);
|
|
3851
3909
|
process.exit(1);
|
|
@@ -3861,7 +3919,7 @@ async function checkForUpdates() {
|
|
|
3861
3919
|
chalk4.bgHex("#b2f5ea").black.bold(` NEW VERSION AVAILABLE: ${latestCliVersion} (current: ${currentVersion}) `)
|
|
3862
3920
|
);
|
|
3863
3921
|
console.log(
|
|
3864
|
-
chalk4.bgHex("#b2f5ea").black.bold(` Run 'pnpm install -g @wispbit/
|
|
3922
|
+
chalk4.bgHex("#b2f5ea").black.bold(` Run 'pnpm install -g @wispbit/local' to update
|
|
3865
3923
|
`)
|
|
3866
3924
|
);
|
|
3867
3925
|
}
|
|
@@ -3909,6 +3967,7 @@ Diff examples:
|
|
|
3909
3967
|
$ wispbit abc123 --include committed Compare against specific commit SHA, show only committed
|
|
3910
3968
|
|
|
3911
3969
|
Global options:
|
|
3970
|
+
--repo-url <url> Override repository URL (default: from git remote)
|
|
3912
3971
|
-v, --version Show version number
|
|
3913
3972
|
-h, --help Show help
|
|
3914
3973
|
`,
|
|
@@ -3926,6 +3985,10 @@ Global options:
|
|
|
3926
3985
|
include: {
|
|
3927
3986
|
type: "string"
|
|
3928
3987
|
},
|
|
3988
|
+
// Repository URL override
|
|
3989
|
+
repoUrl: {
|
|
3990
|
+
type: "string"
|
|
3991
|
+
},
|
|
3929
3992
|
// Debug option
|
|
3930
3993
|
debug: {
|
|
3931
3994
|
type: "boolean",
|
|
@@ -3946,8 +4009,8 @@ Global options:
|
|
|
3946
4009
|
}
|
|
3947
4010
|
);
|
|
3948
4011
|
async function executeCommand(options) {
|
|
3949
|
-
const environment = new Environment();
|
|
3950
|
-
const config = await ensureConfigured();
|
|
4012
|
+
const environment = new Environment({ repositoryUrl: options.repoUrl });
|
|
4013
|
+
const config = await ensureConfigured(environment);
|
|
3951
4014
|
const { ruleId, json: json2, mode, filePath } = options;
|
|
3952
4015
|
let { commitSelector, diffInclude } = options;
|
|
3953
4016
|
if (mode === "diff") {
|
|
@@ -4113,9 +4176,9 @@ async function executeCommand(options) {
|
|
|
4113
4176
|
}
|
|
4114
4177
|
return results;
|
|
4115
4178
|
}
|
|
4116
|
-
async function listRules() {
|
|
4117
|
-
const
|
|
4118
|
-
const
|
|
4179
|
+
async function listRules(repoUrl) {
|
|
4180
|
+
const environment = new Environment({ repositoryUrl: repoUrl });
|
|
4181
|
+
const config = await ensureConfigured(environment);
|
|
4119
4182
|
const ruleProvider = new WispbitRuleProvider(config, environment);
|
|
4120
4183
|
const rules = await ruleProvider.loadAllRules();
|
|
4121
4184
|
printRulesList(rules);
|
|
@@ -4131,7 +4194,8 @@ async function main() {
|
|
|
4131
4194
|
json: cli.flags.json,
|
|
4132
4195
|
debug: cli.flags.debug,
|
|
4133
4196
|
mode: "check",
|
|
4134
|
-
filePath
|
|
4197
|
+
filePath,
|
|
4198
|
+
repoUrl: cli.flags.repoUrl
|
|
4135
4199
|
});
|
|
4136
4200
|
break;
|
|
4137
4201
|
}
|
|
@@ -4159,17 +4223,18 @@ async function main() {
|
|
|
4159
4223
|
debug: cli.flags.debug,
|
|
4160
4224
|
mode: "diff",
|
|
4161
4225
|
diffInclude,
|
|
4162
|
-
commitSelector
|
|
4226
|
+
commitSelector,
|
|
4227
|
+
repoUrl: cli.flags.repoUrl
|
|
4163
4228
|
});
|
|
4164
4229
|
break;
|
|
4165
4230
|
}
|
|
4166
4231
|
case "list": {
|
|
4167
|
-
await listRules();
|
|
4232
|
+
await listRules(cli.flags.repoUrl);
|
|
4168
4233
|
break;
|
|
4169
4234
|
}
|
|
4170
4235
|
case "cache": {
|
|
4171
4236
|
if (subcommand === "purge") {
|
|
4172
|
-
const environment = new Environment();
|
|
4237
|
+
const environment = new Environment({ repositoryUrl: cli.flags.repoUrl });
|
|
4173
4238
|
const storage = new Storage(environment);
|
|
4174
4239
|
const result = await storage.purgeCache();
|
|
4175
4240
|
console.log(
|
|
@@ -4207,7 +4272,8 @@ ${chalk4.green("Cache purged successfully.")} Removed ${result.deletedCount} ite
|
|
|
4207
4272
|
debug: cli.flags.debug,
|
|
4208
4273
|
mode: "diff",
|
|
4209
4274
|
diffInclude,
|
|
4210
|
-
commitSelector
|
|
4275
|
+
commitSelector,
|
|
4276
|
+
repoUrl: cli.flags.repoUrl
|
|
4211
4277
|
});
|
|
4212
4278
|
break;
|
|
4213
4279
|
}
|