qfai 1.0.0 → 1.0.2
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 +5 -5
- package/assets/init/.qfai/README.md +2 -2
- package/assets/init/.qfai/contracts/README.md +1 -1
- package/assets/init/.qfai/prompts/makeBusinessFlow.md +1 -1
- package/assets/init/.qfai/prompts/makeOverview.md +1 -1
- package/assets/init/.qfai/prompts/qfai-maintain-traceability.md +1 -1
- package/assets/init/.qfai/prompts/require-to-spec.md +2 -2
- package/assets/init/.qfai/samples/analyze/analysis.md +1 -1
- package/assets/init/.qfai/specs/README.md +2 -2
- package/assets/init/.qfai/specs/spec-0001/delta.md +1 -1
- package/dist/cli/index.cjs +170 -37
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +170 -37
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +107 -25
- 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 +107 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- /package/assets/init/.qfai/specs/spec-0001/{scenario.md → scenario.feature} +0 -0
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");
|
|
@@ -597,7 +630,7 @@ async function collectSpecEntries(specsRoot) {
|
|
|
597
630
|
dir,
|
|
598
631
|
specPath: import_node_path3.default.join(dir, "spec.md"),
|
|
599
632
|
deltaPath: import_node_path3.default.join(dir, "delta.md"),
|
|
600
|
-
scenarioPath: import_node_path3.default.join(dir, "scenario.
|
|
633
|
+
scenarioPath: import_node_path3.default.join(dir, "scenario.feature")
|
|
601
634
|
}));
|
|
602
635
|
return entries.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
603
636
|
}
|
|
@@ -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 ("1.0.
|
|
1237
|
-
return "1.0.
|
|
1276
|
+
if ("1.0.2".length > 0) {
|
|
1277
|
+
return "1.0.2";
|
|
1238
1278
|
}
|
|
1239
1279
|
try {
|
|
1240
1280
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1922,12 +1962,11 @@ async function validateScenarios(root, config) {
|
|
|
1922
1962
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1923
1963
|
const entries = await collectSpecEntries(specsRoot);
|
|
1924
1964
|
if (entries.length === 0) {
|
|
1925
|
-
const expected = "spec-0001/scenario.
|
|
1926
|
-
const legacy = "spec-001/scenario.md";
|
|
1965
|
+
const expected = "spec-0001/scenario.feature";
|
|
1927
1966
|
return [
|
|
1928
1967
|
issue4(
|
|
1929
1968
|
"QFAI-SC-000",
|
|
1930
|
-
`Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected}
|
|
1969
|
+
`Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specsDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected}`,
|
|
1931
1970
|
"info",
|
|
1932
1971
|
specsRoot,
|
|
1933
1972
|
"scenario.files"
|
|
@@ -1944,7 +1983,7 @@ async function validateScenarios(root, config) {
|
|
|
1944
1983
|
issues.push(
|
|
1945
1984
|
issue4(
|
|
1946
1985
|
"QFAI-SC-001",
|
|
1947
|
-
"scenario.
|
|
1986
|
+
"scenario.feature \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1948
1987
|
"error",
|
|
1949
1988
|
entry.scenarioPath,
|
|
1950
1989
|
"scenario.exists"
|
|
@@ -2906,13 +2945,14 @@ async function createReportData(root, validation, configResult) {
|
|
|
2906
2945
|
issues: normalizedValidation.issues
|
|
2907
2946
|
};
|
|
2908
2947
|
}
|
|
2909
|
-
function formatReportMarkdown(data) {
|
|
2948
|
+
function formatReportMarkdown(data, options = {}) {
|
|
2910
2949
|
const lines = [];
|
|
2950
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
2911
2951
|
lines.push("# QFAI Report");
|
|
2912
2952
|
lines.push("");
|
|
2913
2953
|
lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
|
|
2914
|
-
lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
|
|
2915
|
-
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
2954
|
+
lines.push(`- \u30EB\u30FC\u30C8: ${formatPathLink(data.root, baseUrl)}`);
|
|
2955
|
+
lines.push(`- \u8A2D\u5B9A: ${formatPathLink(data.configPath, baseUrl)}`);
|
|
2916
2956
|
lines.push(`- \u7248: ${data.version}`);
|
|
2917
2957
|
lines.push("");
|
|
2918
2958
|
const severityOrder = {
|
|
@@ -3051,8 +3091,7 @@ function formatReportMarkdown(data) {
|
|
|
3051
3091
|
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
3052
3092
|
);
|
|
3053
3093
|
if (item.file) {
|
|
3054
|
-
|
|
3055
|
-
out.push(`- file: ${item.file}${loc}`);
|
|
3094
|
+
out.push(`- file: ${formatPathWithLine(item.file, item.loc, baseUrl)}`);
|
|
3056
3095
|
}
|
|
3057
3096
|
if (item.rule) {
|
|
3058
3097
|
out.push(`- rule: ${item.rule}`);
|
|
@@ -3178,6 +3217,11 @@ function formatReportMarkdown(data) {
|
|
|
3178
3217
|
lines.push(
|
|
3179
3218
|
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
3180
3219
|
);
|
|
3220
|
+
if (data.traceability.testFiles.truncated) {
|
|
3221
|
+
lines.push(
|
|
3222
|
+
`- testFileTruncated: true (limit=${data.traceability.testFiles.limit})`
|
|
3223
|
+
);
|
|
3224
|
+
}
|
|
3181
3225
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
3182
3226
|
lines.push("- missingIds: (none)");
|
|
3183
3227
|
} else {
|
|
@@ -3187,7 +3231,8 @@ function formatReportMarkdown(data) {
|
|
|
3187
3231
|
if (files.length === 0) {
|
|
3188
3232
|
return id;
|
|
3189
3233
|
}
|
|
3190
|
-
|
|
3234
|
+
const formattedFiles = files.map((file) => formatPathLink(file, baseUrl));
|
|
3235
|
+
return `${id} (${formattedFiles.join(", ")})`;
|
|
3191
3236
|
});
|
|
3192
3237
|
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
3193
3238
|
}
|
|
@@ -3204,7 +3249,8 @@ function formatReportMarkdown(data) {
|
|
|
3204
3249
|
if (refs.length === 0) {
|
|
3205
3250
|
lines.push(`- ${scId}: (none)`);
|
|
3206
3251
|
} else {
|
|
3207
|
-
|
|
3252
|
+
const formattedRefs = refs.map((ref) => formatPathLink(ref, baseUrl));
|
|
3253
|
+
lines.push(`- ${scId}: ${formattedRefs.join(", ")}`);
|
|
3208
3254
|
}
|
|
3209
3255
|
}
|
|
3210
3256
|
}
|
|
@@ -3219,8 +3265,9 @@ function formatReportMarkdown(data) {
|
|
|
3219
3265
|
} else {
|
|
3220
3266
|
for (const item of specScIssues) {
|
|
3221
3267
|
const location = item.file ?? "(unknown)";
|
|
3268
|
+
const formattedLocation = location === "(unknown)" ? location : formatPathLink(location, baseUrl);
|
|
3222
3269
|
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
3223
|
-
lines.push(`- ${
|
|
3270
|
+
lines.push(`- ${formattedLocation}: ${refs}`);
|
|
3224
3271
|
}
|
|
3225
3272
|
}
|
|
3226
3273
|
lines.push("");
|
|
@@ -3232,7 +3279,7 @@ function formatReportMarkdown(data) {
|
|
|
3232
3279
|
} else {
|
|
3233
3280
|
for (const spot of hotspots) {
|
|
3234
3281
|
lines.push(
|
|
3235
|
-
`- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3282
|
+
`- ${formatPathLink(spot.file, baseUrl)}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3236
3283
|
);
|
|
3237
3284
|
}
|
|
3238
3285
|
}
|
|
@@ -3389,6 +3436,41 @@ function formatMarkdownTable(headers, rows) {
|
|
|
3389
3436
|
const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
|
|
3390
3437
|
return [formatRow(headers), separator, ...rows.map(formatRow)];
|
|
3391
3438
|
}
|
|
3439
|
+
function normalizeBaseUrl(value) {
|
|
3440
|
+
if (!value) {
|
|
3441
|
+
return void 0;
|
|
3442
|
+
}
|
|
3443
|
+
const trimmed = value.trim();
|
|
3444
|
+
if (!trimmed) {
|
|
3445
|
+
return void 0;
|
|
3446
|
+
}
|
|
3447
|
+
return trimmed.replace(/\/+$/, "");
|
|
3448
|
+
}
|
|
3449
|
+
function formatPathLink(value, baseUrl) {
|
|
3450
|
+
if (!baseUrl) {
|
|
3451
|
+
return value;
|
|
3452
|
+
}
|
|
3453
|
+
if (value === ".") {
|
|
3454
|
+
return `[${value}](${baseUrl})`;
|
|
3455
|
+
}
|
|
3456
|
+
const encoded = encodePathForUrl(value);
|
|
3457
|
+
if (!encoded) {
|
|
3458
|
+
return value;
|
|
3459
|
+
}
|
|
3460
|
+
return `[${value}](${baseUrl}/${encoded})`;
|
|
3461
|
+
}
|
|
3462
|
+
function formatPathWithLine(value, loc, baseUrl) {
|
|
3463
|
+
const link = formatPathLink(value, baseUrl);
|
|
3464
|
+
const line = loc?.line ? `:${loc.line}` : "";
|
|
3465
|
+
return `${link}${line}`;
|
|
3466
|
+
}
|
|
3467
|
+
function encodePathForUrl(value) {
|
|
3468
|
+
const normalized = value.replace(/\\/g, "/");
|
|
3469
|
+
if (normalized === ".") {
|
|
3470
|
+
return "";
|
|
3471
|
+
}
|
|
3472
|
+
return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
3473
|
+
}
|
|
3392
3474
|
function toSortedArray2(values) {
|
|
3393
3475
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
3394
3476
|
}
|