qfai 1.0.0 → 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 +4 -4
- 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.d.cts
CHANGED
|
@@ -9,6 +9,8 @@ type TestFileScan = {
|
|
|
9
9
|
globs: string[];
|
|
10
10
|
excludeGlobs: string[];
|
|
11
11
|
matchedFileCount: number;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
limit: number;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
type IssueSeverity = "info" | "warning" | "error";
|
|
@@ -157,7 +159,10 @@ type ReportData = {
|
|
|
157
159
|
issues: Issue[];
|
|
158
160
|
};
|
|
159
161
|
declare function createReportData(root: string, validation?: ValidationResult, configResult?: ConfigLoadResult): Promise<ReportData>;
|
|
160
|
-
|
|
162
|
+
type ReportMarkdownOptions = {
|
|
163
|
+
baseUrl?: string;
|
|
164
|
+
};
|
|
165
|
+
declare function formatReportMarkdown(data: ReportData, options?: ReportMarkdownOptions): string;
|
|
161
166
|
declare function formatReportJson(data: ReportData): string;
|
|
162
167
|
|
|
163
168
|
declare function validateProject(root: string, configResult?: ConfigLoadResult): Promise<ValidationResult>;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ type TestFileScan = {
|
|
|
9
9
|
globs: string[];
|
|
10
10
|
excludeGlobs: string[];
|
|
11
11
|
matchedFileCount: number;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
limit: number;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
type IssueSeverity = "info" | "warning" | "error";
|
|
@@ -157,7 +159,10 @@ type ReportData = {
|
|
|
157
159
|
issues: Issue[];
|
|
158
160
|
};
|
|
159
161
|
declare function createReportData(root: string, validation?: ValidationResult, configResult?: ConfigLoadResult): Promise<ReportData>;
|
|
160
|
-
|
|
162
|
+
type ReportMarkdownOptions = {
|
|
163
|
+
baseUrl?: string;
|
|
164
|
+
};
|
|
165
|
+
declare function formatReportMarkdown(data: ReportData, options?: ReportMarkdownOptions): string;
|
|
161
166
|
declare function formatReportJson(data: ReportData): string;
|
|
162
167
|
|
|
163
168
|
declare function validateProject(root: string, configResult?: ConfigLoadResult): Promise<ValidationResult>;
|
package/dist/index.mjs
CHANGED
|
@@ -474,6 +474,7 @@ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
|
474
474
|
"tmp",
|
|
475
475
|
".mcp-tools"
|
|
476
476
|
]);
|
|
477
|
+
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
477
478
|
async function collectFiles(root, options = {}) {
|
|
478
479
|
const entries = [];
|
|
479
480
|
if (!await exists2(root)) {
|
|
@@ -488,16 +489,29 @@ async function collectFiles(root, options = {}) {
|
|
|
488
489
|
return entries;
|
|
489
490
|
}
|
|
490
491
|
async function collectFilesByGlobs(root, options) {
|
|
492
|
+
const limit = normalizeLimit(options.limit);
|
|
491
493
|
if (options.globs.length === 0) {
|
|
492
|
-
return [];
|
|
494
|
+
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
493
495
|
}
|
|
494
|
-
|
|
496
|
+
const stream = fg.stream(options.globs, {
|
|
495
497
|
cwd: root,
|
|
496
498
|
ignore: options.ignore ?? [],
|
|
497
499
|
onlyFiles: true,
|
|
498
500
|
absolute: true,
|
|
499
501
|
unique: true
|
|
500
502
|
});
|
|
503
|
+
const files = [];
|
|
504
|
+
let truncated = false;
|
|
505
|
+
for await (const entry of stream) {
|
|
506
|
+
if (files.length >= limit) {
|
|
507
|
+
truncated = true;
|
|
508
|
+
destroyStream(stream);
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
files.push(String(entry));
|
|
512
|
+
}
|
|
513
|
+
const matchedFileCount = files.length;
|
|
514
|
+
return { files, truncated, matchedFileCount, limit };
|
|
501
515
|
}
|
|
502
516
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
503
517
|
const items = await readdir(current, { withFileTypes: true });
|
|
@@ -529,6 +543,25 @@ async function exists2(target) {
|
|
|
529
543
|
return false;
|
|
530
544
|
}
|
|
531
545
|
}
|
|
546
|
+
function normalizeLimit(value) {
|
|
547
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
548
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
549
|
+
}
|
|
550
|
+
const flooredValue = Math.floor(value);
|
|
551
|
+
if (flooredValue <= 0) {
|
|
552
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
553
|
+
}
|
|
554
|
+
return flooredValue;
|
|
555
|
+
}
|
|
556
|
+
function destroyStream(stream) {
|
|
557
|
+
if (!stream || typeof stream !== "object") {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const record2 = stream;
|
|
561
|
+
if (typeof record2.destroy === "function") {
|
|
562
|
+
record2.destroy();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
532
565
|
|
|
533
566
|
// src/core/specLayout.ts
|
|
534
567
|
import { readdir as readdir2 } from "fs/promises";
|
|
@@ -1094,15 +1127,18 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1094
1127
|
scan: {
|
|
1095
1128
|
globs: normalizedGlobs,
|
|
1096
1129
|
excludeGlobs: mergedExcludeGlobs,
|
|
1097
|
-
matchedFileCount: 0
|
|
1130
|
+
matchedFileCount: 0,
|
|
1131
|
+
truncated: false,
|
|
1132
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1098
1133
|
}
|
|
1099
1134
|
};
|
|
1100
1135
|
}
|
|
1101
|
-
let
|
|
1136
|
+
let scanResult;
|
|
1102
1137
|
try {
|
|
1103
|
-
|
|
1138
|
+
scanResult = await collectFilesByGlobs(root, {
|
|
1104
1139
|
globs: normalizedGlobs,
|
|
1105
|
-
ignore: mergedExcludeGlobs
|
|
1140
|
+
ignore: mergedExcludeGlobs,
|
|
1141
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1106
1142
|
});
|
|
1107
1143
|
} catch (error) {
|
|
1108
1144
|
return {
|
|
@@ -1110,13 +1146,15 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1110
1146
|
scan: {
|
|
1111
1147
|
globs: normalizedGlobs,
|
|
1112
1148
|
excludeGlobs: mergedExcludeGlobs,
|
|
1113
|
-
matchedFileCount: 0
|
|
1149
|
+
matchedFileCount: 0,
|
|
1150
|
+
truncated: false,
|
|
1151
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1114
1152
|
},
|
|
1115
1153
|
error: formatError3(error)
|
|
1116
1154
|
};
|
|
1117
1155
|
}
|
|
1118
1156
|
const normalizedFiles = Array.from(
|
|
1119
|
-
new Set(files.map((file) => path6.normalize(file)))
|
|
1157
|
+
new Set(scanResult.files.map((file) => path6.normalize(file)))
|
|
1120
1158
|
);
|
|
1121
1159
|
for (const file of normalizedFiles) {
|
|
1122
1160
|
const text = await readFile3(file, "utf-8");
|
|
@@ -1135,7 +1173,9 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1135
1173
|
scan: {
|
|
1136
1174
|
globs: normalizedGlobs,
|
|
1137
1175
|
excludeGlobs: mergedExcludeGlobs,
|
|
1138
|
-
matchedFileCount:
|
|
1176
|
+
matchedFileCount: scanResult.matchedFileCount,
|
|
1177
|
+
truncated: scanResult.truncated,
|
|
1178
|
+
limit: scanResult.limit
|
|
1139
1179
|
}
|
|
1140
1180
|
};
|
|
1141
1181
|
}
|
|
@@ -1180,8 +1220,8 @@ import { readFile as readFile4 } from "fs/promises";
|
|
|
1180
1220
|
import path7 from "path";
|
|
1181
1221
|
import { fileURLToPath } from "url";
|
|
1182
1222
|
async function resolveToolVersion() {
|
|
1183
|
-
if ("1.0.
|
|
1184
|
-
return "1.0.
|
|
1223
|
+
if ("1.0.1".length > 0) {
|
|
1224
|
+
return "1.0.1";
|
|
1185
1225
|
}
|
|
1186
1226
|
try {
|
|
1187
1227
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -2853,13 +2893,14 @@ async function createReportData(root, validation, configResult) {
|
|
|
2853
2893
|
issues: normalizedValidation.issues
|
|
2854
2894
|
};
|
|
2855
2895
|
}
|
|
2856
|
-
function formatReportMarkdown(data) {
|
|
2896
|
+
function formatReportMarkdown(data, options = {}) {
|
|
2857
2897
|
const lines = [];
|
|
2898
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
2858
2899
|
lines.push("# QFAI Report");
|
|
2859
2900
|
lines.push("");
|
|
2860
2901
|
lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
|
|
2861
|
-
lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
|
|
2862
|
-
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
2902
|
+
lines.push(`- \u30EB\u30FC\u30C8: ${formatPathLink(data.root, baseUrl)}`);
|
|
2903
|
+
lines.push(`- \u8A2D\u5B9A: ${formatPathLink(data.configPath, baseUrl)}`);
|
|
2863
2904
|
lines.push(`- \u7248: ${data.version}`);
|
|
2864
2905
|
lines.push("");
|
|
2865
2906
|
const severityOrder = {
|
|
@@ -2998,8 +3039,7 @@ function formatReportMarkdown(data) {
|
|
|
2998
3039
|
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
2999
3040
|
);
|
|
3000
3041
|
if (item.file) {
|
|
3001
|
-
|
|
3002
|
-
out.push(`- file: ${item.file}${loc}`);
|
|
3042
|
+
out.push(`- file: ${formatPathWithLine(item.file, item.loc, baseUrl)}`);
|
|
3003
3043
|
}
|
|
3004
3044
|
if (item.rule) {
|
|
3005
3045
|
out.push(`- rule: ${item.rule}`);
|
|
@@ -3125,6 +3165,11 @@ function formatReportMarkdown(data) {
|
|
|
3125
3165
|
lines.push(
|
|
3126
3166
|
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
3127
3167
|
);
|
|
3168
|
+
if (data.traceability.testFiles.truncated) {
|
|
3169
|
+
lines.push(
|
|
3170
|
+
`- testFileTruncated: true (limit=${data.traceability.testFiles.limit})`
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3128
3173
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
3129
3174
|
lines.push("- missingIds: (none)");
|
|
3130
3175
|
} else {
|
|
@@ -3134,7 +3179,8 @@ function formatReportMarkdown(data) {
|
|
|
3134
3179
|
if (files.length === 0) {
|
|
3135
3180
|
return id;
|
|
3136
3181
|
}
|
|
3137
|
-
|
|
3182
|
+
const formattedFiles = files.map((file) => formatPathLink(file, baseUrl));
|
|
3183
|
+
return `${id} (${formattedFiles.join(", ")})`;
|
|
3138
3184
|
});
|
|
3139
3185
|
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
3140
3186
|
}
|
|
@@ -3151,7 +3197,8 @@ function formatReportMarkdown(data) {
|
|
|
3151
3197
|
if (refs.length === 0) {
|
|
3152
3198
|
lines.push(`- ${scId}: (none)`);
|
|
3153
3199
|
} else {
|
|
3154
|
-
|
|
3200
|
+
const formattedRefs = refs.map((ref) => formatPathLink(ref, baseUrl));
|
|
3201
|
+
lines.push(`- ${scId}: ${formattedRefs.join(", ")}`);
|
|
3155
3202
|
}
|
|
3156
3203
|
}
|
|
3157
3204
|
}
|
|
@@ -3166,8 +3213,9 @@ function formatReportMarkdown(data) {
|
|
|
3166
3213
|
} else {
|
|
3167
3214
|
for (const item of specScIssues) {
|
|
3168
3215
|
const location = item.file ?? "(unknown)";
|
|
3216
|
+
const formattedLocation = location === "(unknown)" ? location : formatPathLink(location, baseUrl);
|
|
3169
3217
|
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
3170
|
-
lines.push(`- ${
|
|
3218
|
+
lines.push(`- ${formattedLocation}: ${refs}`);
|
|
3171
3219
|
}
|
|
3172
3220
|
}
|
|
3173
3221
|
lines.push("");
|
|
@@ -3179,7 +3227,7 @@ function formatReportMarkdown(data) {
|
|
|
3179
3227
|
} else {
|
|
3180
3228
|
for (const spot of hotspots) {
|
|
3181
3229
|
lines.push(
|
|
3182
|
-
`- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3230
|
+
`- ${formatPathLink(spot.file, baseUrl)}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3183
3231
|
);
|
|
3184
3232
|
}
|
|
3185
3233
|
}
|
|
@@ -3336,6 +3384,41 @@ function formatMarkdownTable(headers, rows) {
|
|
|
3336
3384
|
const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
|
|
3337
3385
|
return [formatRow(headers), separator, ...rows.map(formatRow)];
|
|
3338
3386
|
}
|
|
3387
|
+
function normalizeBaseUrl(value) {
|
|
3388
|
+
if (!value) {
|
|
3389
|
+
return void 0;
|
|
3390
|
+
}
|
|
3391
|
+
const trimmed = value.trim();
|
|
3392
|
+
if (!trimmed) {
|
|
3393
|
+
return void 0;
|
|
3394
|
+
}
|
|
3395
|
+
return trimmed.replace(/\/+$/, "");
|
|
3396
|
+
}
|
|
3397
|
+
function formatPathLink(value, baseUrl) {
|
|
3398
|
+
if (!baseUrl) {
|
|
3399
|
+
return value;
|
|
3400
|
+
}
|
|
3401
|
+
if (value === ".") {
|
|
3402
|
+
return `[${value}](${baseUrl})`;
|
|
3403
|
+
}
|
|
3404
|
+
const encoded = encodePathForUrl(value);
|
|
3405
|
+
if (!encoded) {
|
|
3406
|
+
return value;
|
|
3407
|
+
}
|
|
3408
|
+
return `[${value}](${baseUrl}/${encoded})`;
|
|
3409
|
+
}
|
|
3410
|
+
function formatPathWithLine(value, loc, baseUrl) {
|
|
3411
|
+
const link = formatPathLink(value, baseUrl);
|
|
3412
|
+
const line = loc?.line ? `:${loc.line}` : "";
|
|
3413
|
+
return `${link}${line}`;
|
|
3414
|
+
}
|
|
3415
|
+
function encodePathForUrl(value) {
|
|
3416
|
+
const normalized = value.replace(/\\/g, "/");
|
|
3417
|
+
if (normalized === ".") {
|
|
3418
|
+
return "";
|
|
3419
|
+
}
|
|
3420
|
+
return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
3421
|
+
}
|
|
3339
3422
|
function toSortedArray2(values) {
|
|
3340
3423
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
3341
3424
|
}
|