@ipation/specbridge 1.0.2 → 1.0.4
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/CHANGELOG.md +165 -0
- package/dist/cli.js +53 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.js +47 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1323,9 +1323,7 @@ var ErrorsAnalyzer = class {
|
|
|
1323
1323
|
);
|
|
1324
1324
|
if (errorClasses.length < 2) return null;
|
|
1325
1325
|
const files = scanner.getFiles();
|
|
1326
|
-
|
|
1327
|
-
let extendsCustomBase = 0;
|
|
1328
|
-
let customBaseName = null;
|
|
1326
|
+
const baseCount = /* @__PURE__ */ new Map();
|
|
1329
1327
|
for (const errorClass of errorClasses) {
|
|
1330
1328
|
const file = files.find((f) => f.path === errorClass.file);
|
|
1331
1329
|
if (!file) continue;
|
|
@@ -1334,22 +1332,27 @@ var ErrorsAnalyzer = class {
|
|
|
1334
1332
|
const extendClause = classDecl.getExtends();
|
|
1335
1333
|
if (extendClause) {
|
|
1336
1334
|
const baseName = extendClause.getText();
|
|
1337
|
-
if (baseName
|
|
1338
|
-
|
|
1339
|
-
} else if (baseName.endsWith("Error")) {
|
|
1340
|
-
extendsCustomBase++;
|
|
1341
|
-
customBaseName = customBaseName || baseName;
|
|
1335
|
+
if (baseName !== "Error" && baseName.endsWith("Error")) {
|
|
1336
|
+
baseCount.set(baseName, (baseCount.get(baseName) || 0) + 1);
|
|
1342
1337
|
}
|
|
1343
1338
|
}
|
|
1344
1339
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1340
|
+
let customBaseName = null;
|
|
1341
|
+
let maxCount = 0;
|
|
1342
|
+
for (const [baseName, count] of baseCount.entries()) {
|
|
1343
|
+
if (count > maxCount) {
|
|
1344
|
+
maxCount = count;
|
|
1345
|
+
customBaseName = baseName;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
if (maxCount >= 3 && customBaseName) {
|
|
1349
|
+
const confidence = calculateConfidence(maxCount, errorClasses.length);
|
|
1347
1350
|
return createPattern(this.id, {
|
|
1348
1351
|
id: "errors-custom-base",
|
|
1349
1352
|
name: "Custom Error Base Class",
|
|
1350
1353
|
description: `Custom errors extend a common base class (${customBaseName})`,
|
|
1351
1354
|
confidence,
|
|
1352
|
-
occurrences:
|
|
1355
|
+
occurrences: maxCount,
|
|
1353
1356
|
examples: errorClasses.slice(0, 3).map((c) => ({
|
|
1354
1357
|
file: c.file,
|
|
1355
1358
|
line: c.line,
|
|
@@ -1360,7 +1363,7 @@ var ErrorsAnalyzer = class {
|
|
|
1360
1363
|
rule: `Custom error classes should extend ${customBaseName}`,
|
|
1361
1364
|
severity: "medium",
|
|
1362
1365
|
scope: "src/**/*.ts",
|
|
1363
|
-
verifier: "
|
|
1366
|
+
verifier: "errors"
|
|
1364
1367
|
}
|
|
1365
1368
|
});
|
|
1366
1369
|
}
|
|
@@ -1431,10 +1434,11 @@ var ErrorsAnalyzer = class {
|
|
|
1431
1434
|
} else if (text.startsWith("new ") && text.includes("Error")) {
|
|
1432
1435
|
throwCustom++;
|
|
1433
1436
|
if (examples.length < 3) {
|
|
1437
|
+
const snippet = text.length > 50 ? text.slice(0, 50) + "..." : text;
|
|
1434
1438
|
examples.push({
|
|
1435
1439
|
file: path,
|
|
1436
1440
|
line: node.getStartLineNumber(),
|
|
1437
|
-
snippet: `throw ${
|
|
1441
|
+
snippet: `throw ${snippet}`
|
|
1438
1442
|
});
|
|
1439
1443
|
}
|
|
1440
1444
|
}
|
|
@@ -1456,7 +1460,7 @@ var ErrorsAnalyzer = class {
|
|
|
1456
1460
|
rule: "Throw custom error classes instead of generic Error",
|
|
1457
1461
|
severity: "medium",
|
|
1458
1462
|
scope: "src/**/*.ts",
|
|
1459
|
-
verifier: "
|
|
1463
|
+
verifier: "errors"
|
|
1460
1464
|
}
|
|
1461
1465
|
});
|
|
1462
1466
|
}
|
|
@@ -2030,9 +2034,11 @@ var VerificationEngine = class {
|
|
|
2030
2034
|
let passed = 0;
|
|
2031
2035
|
let failed = 0;
|
|
2032
2036
|
const skipped = 0;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2037
|
+
let timeoutHandle = null;
|
|
2038
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
2039
|
+
timeoutHandle = setTimeout(() => resolve("timeout"), timeout);
|
|
2040
|
+
timeoutHandle.unref();
|
|
2041
|
+
});
|
|
2036
2042
|
const verificationPromise = this.verifyFiles(
|
|
2037
2043
|
filesToVerify,
|
|
2038
2044
|
decisions,
|
|
@@ -2048,17 +2054,25 @@ var VerificationEngine = class {
|
|
|
2048
2054
|
}
|
|
2049
2055
|
}
|
|
2050
2056
|
);
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2057
|
+
let result;
|
|
2058
|
+
try {
|
|
2059
|
+
result = await Promise.race([verificationPromise, timeoutPromise]);
|
|
2060
|
+
if (result === "timeout") {
|
|
2061
|
+
return {
|
|
2062
|
+
success: false,
|
|
2063
|
+
violations: allViolations,
|
|
2064
|
+
checked,
|
|
2065
|
+
passed,
|
|
2066
|
+
failed,
|
|
2067
|
+
skipped: filesToVerify.length - checked,
|
|
2068
|
+
duration: timeout
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
} finally {
|
|
2072
|
+
if (timeoutHandle) {
|
|
2073
|
+
clearTimeout(timeoutHandle);
|
|
2074
|
+
timeoutHandle = null;
|
|
2075
|
+
}
|
|
2062
2076
|
}
|
|
2063
2077
|
const hasBlockingViolations = allViolations.some((v) => {
|
|
2064
2078
|
if (level === "commit") {
|
|
@@ -2753,7 +2767,7 @@ function formatMarkdownReport(report) {
|
|
|
2753
2767
|
lines.push("| Decision | Status | Constraints | Violations | Compliance |");
|
|
2754
2768
|
lines.push("|----------|--------|-------------|------------|------------|");
|
|
2755
2769
|
for (const dec of report.byDecision) {
|
|
2756
|
-
const complianceEmoji = dec.compliance >= 90 ? "" : dec.compliance >= 70 ? "" : "";
|
|
2770
|
+
const complianceEmoji = dec.compliance >= 90 ? "\u2705" : dec.compliance >= 70 ? "\u26A0\uFE0F" : "\u274C";
|
|
2757
2771
|
lines.push(
|
|
2758
2772
|
`| ${dec.title} | ${dec.status} | ${dec.constraints} | ${dec.violations} | ${complianceEmoji} ${dec.compliance}% |`
|
|
2759
2773
|
);
|
|
@@ -2762,15 +2776,15 @@ function formatMarkdownReport(report) {
|
|
|
2762
2776
|
}
|
|
2763
2777
|
lines.push("---");
|
|
2764
2778
|
lines.push("");
|
|
2765
|
-
lines.push("*Generated by [SpecBridge](https://github.com/
|
|
2779
|
+
lines.push("*Generated by [SpecBridge](https://github.com/nouatzi/specbridge)*");
|
|
2766
2780
|
return lines.join("\n");
|
|
2767
2781
|
}
|
|
2768
2782
|
function formatProgressBar(percentage) {
|
|
2769
2783
|
const width = 20;
|
|
2770
2784
|
const filled = Math.round(percentage / 100 * width);
|
|
2771
2785
|
const empty = width - filled;
|
|
2772
|
-
const filledChar = "";
|
|
2773
|
-
const emptyChar = "";
|
|
2786
|
+
const filledChar = "\u2588";
|
|
2787
|
+
const emptyChar = "\u2591";
|
|
2774
2788
|
return `\`${filledChar.repeat(filled)}${emptyChar.repeat(empty)}\` ${percentage}%`;
|
|
2775
2789
|
}
|
|
2776
2790
|
|
|
@@ -2830,7 +2844,7 @@ function formatContextAsMarkdown(context) {
|
|
|
2830
2844
|
lines.push("### Constraints");
|
|
2831
2845
|
lines.push("");
|
|
2832
2846
|
for (const constraint of decision.constraints) {
|
|
2833
|
-
const typeEmoji = constraint.type === "invariant" ? "" : constraint.type === "convention" ? "" : "";
|
|
2847
|
+
const typeEmoji = constraint.type === "invariant" ? "\u{1F512}" : constraint.type === "convention" ? "\u{1F4CB}" : "\u{1F4A1}";
|
|
2834
2848
|
const severityBadge = `[${constraint.severity.toUpperCase()}]`;
|
|
2835
2849
|
lines.push(`- ${typeEmoji} **${severityBadge}** ${constraint.rule}`);
|
|
2836
2850
|
}
|