qfai 0.9.2 → 1.0.1
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/README.md +8 -7
- package/dist/cli/index.cjs +166 -32
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +166 -32
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +103 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.mjs +103 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -531,6 +531,7 @@ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
|
531
531
|
"tmp",
|
|
532
532
|
".mcp-tools"
|
|
533
533
|
]);
|
|
534
|
+
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
534
535
|
async function collectFiles(root, options = {}) {
|
|
535
536
|
const entries = [];
|
|
536
537
|
if (!await exists2(root)) {
|
|
@@ -545,16 +546,29 @@ async function collectFiles(root, options = {}) {
|
|
|
545
546
|
return entries;
|
|
546
547
|
}
|
|
547
548
|
async function collectFilesByGlobs(root, options) {
|
|
549
|
+
const limit = normalizeLimit(options.limit);
|
|
548
550
|
if (options.globs.length === 0) {
|
|
549
|
-
return [];
|
|
551
|
+
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
550
552
|
}
|
|
551
|
-
|
|
553
|
+
const stream = import_fast_glob.default.stream(options.globs, {
|
|
552
554
|
cwd: root,
|
|
553
555
|
ignore: options.ignore ?? [],
|
|
554
556
|
onlyFiles: true,
|
|
555
557
|
absolute: true,
|
|
556
558
|
unique: true
|
|
557
559
|
});
|
|
560
|
+
const files = [];
|
|
561
|
+
let truncated = false;
|
|
562
|
+
for await (const entry of stream) {
|
|
563
|
+
if (files.length >= limit) {
|
|
564
|
+
truncated = true;
|
|
565
|
+
destroyStream(stream);
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
files.push(String(entry));
|
|
569
|
+
}
|
|
570
|
+
const matchedFileCount = files.length;
|
|
571
|
+
return { files, truncated, matchedFileCount, limit };
|
|
558
572
|
}
|
|
559
573
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
560
574
|
const items = await (0, import_promises2.readdir)(current, { withFileTypes: true });
|
|
@@ -586,6 +600,25 @@ async function exists2(target) {
|
|
|
586
600
|
return false;
|
|
587
601
|
}
|
|
588
602
|
}
|
|
603
|
+
function normalizeLimit(value) {
|
|
604
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
605
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
606
|
+
}
|
|
607
|
+
const flooredValue = Math.floor(value);
|
|
608
|
+
if (flooredValue <= 0) {
|
|
609
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
610
|
+
}
|
|
611
|
+
return flooredValue;
|
|
612
|
+
}
|
|
613
|
+
function destroyStream(stream) {
|
|
614
|
+
if (!stream || typeof stream !== "object") {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const record2 = stream;
|
|
618
|
+
if (typeof record2.destroy === "function") {
|
|
619
|
+
record2.destroy();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
589
622
|
|
|
590
623
|
// src/core/specLayout.ts
|
|
591
624
|
var import_promises3 = require("fs/promises");
|
|
@@ -1147,15 +1180,18 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1147
1180
|
scan: {
|
|
1148
1181
|
globs: normalizedGlobs,
|
|
1149
1182
|
excludeGlobs: mergedExcludeGlobs,
|
|
1150
|
-
matchedFileCount: 0
|
|
1183
|
+
matchedFileCount: 0,
|
|
1184
|
+
truncated: false,
|
|
1185
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1151
1186
|
}
|
|
1152
1187
|
};
|
|
1153
1188
|
}
|
|
1154
|
-
let
|
|
1189
|
+
let scanResult;
|
|
1155
1190
|
try {
|
|
1156
|
-
|
|
1191
|
+
scanResult = await collectFilesByGlobs(root, {
|
|
1157
1192
|
globs: normalizedGlobs,
|
|
1158
|
-
ignore: mergedExcludeGlobs
|
|
1193
|
+
ignore: mergedExcludeGlobs,
|
|
1194
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1159
1195
|
});
|
|
1160
1196
|
} catch (error) {
|
|
1161
1197
|
return {
|
|
@@ -1163,13 +1199,15 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1163
1199
|
scan: {
|
|
1164
1200
|
globs: normalizedGlobs,
|
|
1165
1201
|
excludeGlobs: mergedExcludeGlobs,
|
|
1166
|
-
matchedFileCount: 0
|
|
1202
|
+
matchedFileCount: 0,
|
|
1203
|
+
truncated: false,
|
|
1204
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1167
1205
|
},
|
|
1168
1206
|
error: formatError3(error)
|
|
1169
1207
|
};
|
|
1170
1208
|
}
|
|
1171
1209
|
const normalizedFiles = Array.from(
|
|
1172
|
-
new Set(files.map((file) => import_node_path6.default.normalize(file)))
|
|
1210
|
+
new Set(scanResult.files.map((file) => import_node_path6.default.normalize(file)))
|
|
1173
1211
|
);
|
|
1174
1212
|
for (const file of normalizedFiles) {
|
|
1175
1213
|
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
@@ -1188,7 +1226,9 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1188
1226
|
scan: {
|
|
1189
1227
|
globs: normalizedGlobs,
|
|
1190
1228
|
excludeGlobs: mergedExcludeGlobs,
|
|
1191
|
-
matchedFileCount:
|
|
1229
|
+
matchedFileCount: scanResult.matchedFileCount,
|
|
1230
|
+
truncated: scanResult.truncated,
|
|
1231
|
+
limit: scanResult.limit
|
|
1192
1232
|
}
|
|
1193
1233
|
};
|
|
1194
1234
|
}
|
|
@@ -1233,8 +1273,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1233
1273
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
1234
1274
|
var import_node_url = require("url");
|
|
1235
1275
|
async function resolveToolVersion() {
|
|
1236
|
-
if ("0.
|
|
1237
|
-
return "0.
|
|
1276
|
+
if ("1.0.1".length > 0) {
|
|
1277
|
+
return "1.0.1";
|
|
1238
1278
|
}
|
|
1239
1279
|
try {
|
|
1240
1280
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -2906,13 +2946,14 @@ async function createReportData(root, validation, configResult) {
|
|
|
2906
2946
|
issues: normalizedValidation.issues
|
|
2907
2947
|
};
|
|
2908
2948
|
}
|
|
2909
|
-
function formatReportMarkdown(data) {
|
|
2949
|
+
function formatReportMarkdown(data, options = {}) {
|
|
2910
2950
|
const lines = [];
|
|
2951
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
2911
2952
|
lines.push("# QFAI Report");
|
|
2912
2953
|
lines.push("");
|
|
2913
2954
|
lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
|
|
2914
|
-
lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
|
|
2915
|
-
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
2955
|
+
lines.push(`- \u30EB\u30FC\u30C8: ${formatPathLink(data.root, baseUrl)}`);
|
|
2956
|
+
lines.push(`- \u8A2D\u5B9A: ${formatPathLink(data.configPath, baseUrl)}`);
|
|
2916
2957
|
lines.push(`- \u7248: ${data.version}`);
|
|
2917
2958
|
lines.push("");
|
|
2918
2959
|
const severityOrder = {
|
|
@@ -3051,8 +3092,7 @@ function formatReportMarkdown(data) {
|
|
|
3051
3092
|
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
3052
3093
|
);
|
|
3053
3094
|
if (item.file) {
|
|
3054
|
-
|
|
3055
|
-
out.push(`- file: ${item.file}${loc}`);
|
|
3095
|
+
out.push(`- file: ${formatPathWithLine(item.file, item.loc, baseUrl)}`);
|
|
3056
3096
|
}
|
|
3057
3097
|
if (item.rule) {
|
|
3058
3098
|
out.push(`- rule: ${item.rule}`);
|
|
@@ -3178,6 +3218,11 @@ function formatReportMarkdown(data) {
|
|
|
3178
3218
|
lines.push(
|
|
3179
3219
|
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
3180
3220
|
);
|
|
3221
|
+
if (data.traceability.testFiles.truncated) {
|
|
3222
|
+
lines.push(
|
|
3223
|
+
`- testFileTruncated: true (limit=${data.traceability.testFiles.limit})`
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3181
3226
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
3182
3227
|
lines.push("- missingIds: (none)");
|
|
3183
3228
|
} else {
|
|
@@ -3187,7 +3232,8 @@ function formatReportMarkdown(data) {
|
|
|
3187
3232
|
if (files.length === 0) {
|
|
3188
3233
|
return id;
|
|
3189
3234
|
}
|
|
3190
|
-
|
|
3235
|
+
const formattedFiles = files.map((file) => formatPathLink(file, baseUrl));
|
|
3236
|
+
return `${id} (${formattedFiles.join(", ")})`;
|
|
3191
3237
|
});
|
|
3192
3238
|
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
3193
3239
|
}
|
|
@@ -3204,7 +3250,8 @@ function formatReportMarkdown(data) {
|
|
|
3204
3250
|
if (refs.length === 0) {
|
|
3205
3251
|
lines.push(`- ${scId}: (none)`);
|
|
3206
3252
|
} else {
|
|
3207
|
-
|
|
3253
|
+
const formattedRefs = refs.map((ref) => formatPathLink(ref, baseUrl));
|
|
3254
|
+
lines.push(`- ${scId}: ${formattedRefs.join(", ")}`);
|
|
3208
3255
|
}
|
|
3209
3256
|
}
|
|
3210
3257
|
}
|
|
@@ -3219,8 +3266,9 @@ function formatReportMarkdown(data) {
|
|
|
3219
3266
|
} else {
|
|
3220
3267
|
for (const item of specScIssues) {
|
|
3221
3268
|
const location = item.file ?? "(unknown)";
|
|
3269
|
+
const formattedLocation = location === "(unknown)" ? location : formatPathLink(location, baseUrl);
|
|
3222
3270
|
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
3223
|
-
lines.push(`- ${
|
|
3271
|
+
lines.push(`- ${formattedLocation}: ${refs}`);
|
|
3224
3272
|
}
|
|
3225
3273
|
}
|
|
3226
3274
|
lines.push("");
|
|
@@ -3232,7 +3280,7 @@ function formatReportMarkdown(data) {
|
|
|
3232
3280
|
} else {
|
|
3233
3281
|
for (const spot of hotspots) {
|
|
3234
3282
|
lines.push(
|
|
3235
|
-
`- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3283
|
+
`- ${formatPathLink(spot.file, baseUrl)}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3236
3284
|
);
|
|
3237
3285
|
}
|
|
3238
3286
|
}
|
|
@@ -3389,6 +3437,41 @@ function formatMarkdownTable(headers, rows) {
|
|
|
3389
3437
|
const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
|
|
3390
3438
|
return [formatRow(headers), separator, ...rows.map(formatRow)];
|
|
3391
3439
|
}
|
|
3440
|
+
function normalizeBaseUrl(value) {
|
|
3441
|
+
if (!value) {
|
|
3442
|
+
return void 0;
|
|
3443
|
+
}
|
|
3444
|
+
const trimmed = value.trim();
|
|
3445
|
+
if (!trimmed) {
|
|
3446
|
+
return void 0;
|
|
3447
|
+
}
|
|
3448
|
+
return trimmed.replace(/\/+$/, "");
|
|
3449
|
+
}
|
|
3450
|
+
function formatPathLink(value, baseUrl) {
|
|
3451
|
+
if (!baseUrl) {
|
|
3452
|
+
return value;
|
|
3453
|
+
}
|
|
3454
|
+
if (value === ".") {
|
|
3455
|
+
return `[${value}](${baseUrl})`;
|
|
3456
|
+
}
|
|
3457
|
+
const encoded = encodePathForUrl(value);
|
|
3458
|
+
if (!encoded) {
|
|
3459
|
+
return value;
|
|
3460
|
+
}
|
|
3461
|
+
return `[${value}](${baseUrl}/${encoded})`;
|
|
3462
|
+
}
|
|
3463
|
+
function formatPathWithLine(value, loc, baseUrl) {
|
|
3464
|
+
const link = formatPathLink(value, baseUrl);
|
|
3465
|
+
const line = loc?.line ? `:${loc.line}` : "";
|
|
3466
|
+
return `${link}${line}`;
|
|
3467
|
+
}
|
|
3468
|
+
function encodePathForUrl(value) {
|
|
3469
|
+
const normalized = value.replace(/\\/g, "/");
|
|
3470
|
+
if (normalized === ".") {
|
|
3471
|
+
return "";
|
|
3472
|
+
}
|
|
3473
|
+
return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
3474
|
+
}
|
|
3392
3475
|
function toSortedArray2(values) {
|
|
3393
3476
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
3394
3477
|
}
|