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/cli/index.mjs
CHANGED
|
@@ -16,6 +16,7 @@ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
|
16
16
|
"tmp",
|
|
17
17
|
".mcp-tools"
|
|
18
18
|
]);
|
|
19
|
+
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
19
20
|
async function collectFiles(root, options = {}) {
|
|
20
21
|
const entries = [];
|
|
21
22
|
if (!await exists(root)) {
|
|
@@ -30,16 +31,29 @@ async function collectFiles(root, options = {}) {
|
|
|
30
31
|
return entries;
|
|
31
32
|
}
|
|
32
33
|
async function collectFilesByGlobs(root, options) {
|
|
34
|
+
const limit = normalizeLimit(options.limit);
|
|
33
35
|
if (options.globs.length === 0) {
|
|
34
|
-
return [];
|
|
36
|
+
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
35
37
|
}
|
|
36
|
-
|
|
38
|
+
const stream = fg.stream(options.globs, {
|
|
37
39
|
cwd: root,
|
|
38
40
|
ignore: options.ignore ?? [],
|
|
39
41
|
onlyFiles: true,
|
|
40
42
|
absolute: true,
|
|
41
43
|
unique: true
|
|
42
44
|
});
|
|
45
|
+
const files = [];
|
|
46
|
+
let truncated = false;
|
|
47
|
+
for await (const entry of stream) {
|
|
48
|
+
if (files.length >= limit) {
|
|
49
|
+
truncated = true;
|
|
50
|
+
destroyStream(stream);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
files.push(String(entry));
|
|
54
|
+
}
|
|
55
|
+
const matchedFileCount = files.length;
|
|
56
|
+
return { files, truncated, matchedFileCount, limit };
|
|
43
57
|
}
|
|
44
58
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
45
59
|
const items = await readdir(current, { withFileTypes: true });
|
|
@@ -71,6 +85,25 @@ async function exists(target) {
|
|
|
71
85
|
return false;
|
|
72
86
|
}
|
|
73
87
|
}
|
|
88
|
+
function normalizeLimit(value) {
|
|
89
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
90
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
91
|
+
}
|
|
92
|
+
const flooredValue = Math.floor(value);
|
|
93
|
+
if (flooredValue <= 0) {
|
|
94
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
95
|
+
}
|
|
96
|
+
return flooredValue;
|
|
97
|
+
}
|
|
98
|
+
function destroyStream(stream) {
|
|
99
|
+
if (!stream || typeof stream !== "object") {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const record2 = stream;
|
|
103
|
+
if (typeof record2.destroy === "function") {
|
|
104
|
+
record2.destroy();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
74
107
|
|
|
75
108
|
// src/cli/commands/analyze.ts
|
|
76
109
|
async function runAnalyze(options) {
|
|
@@ -594,7 +627,7 @@ async function collectSpecEntries(specsRoot) {
|
|
|
594
627
|
dir,
|
|
595
628
|
specPath: path4.join(dir, "spec.md"),
|
|
596
629
|
deltaPath: path4.join(dir, "delta.md"),
|
|
597
|
-
scenarioPath: path4.join(dir, "scenario.
|
|
630
|
+
scenarioPath: path4.join(dir, "scenario.feature")
|
|
598
631
|
}));
|
|
599
632
|
return entries.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
600
633
|
}
|
|
@@ -890,15 +923,18 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
890
923
|
scan: {
|
|
891
924
|
globs: normalizedGlobs,
|
|
892
925
|
excludeGlobs: mergedExcludeGlobs,
|
|
893
|
-
matchedFileCount: 0
|
|
926
|
+
matchedFileCount: 0,
|
|
927
|
+
truncated: false,
|
|
928
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
894
929
|
}
|
|
895
930
|
};
|
|
896
931
|
}
|
|
897
|
-
let
|
|
932
|
+
let scanResult;
|
|
898
933
|
try {
|
|
899
|
-
|
|
934
|
+
scanResult = await collectFilesByGlobs(root, {
|
|
900
935
|
globs: normalizedGlobs,
|
|
901
|
-
ignore: mergedExcludeGlobs
|
|
936
|
+
ignore: mergedExcludeGlobs,
|
|
937
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
902
938
|
});
|
|
903
939
|
} catch (error2) {
|
|
904
940
|
return {
|
|
@@ -906,13 +942,15 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
906
942
|
scan: {
|
|
907
943
|
globs: normalizedGlobs,
|
|
908
944
|
excludeGlobs: mergedExcludeGlobs,
|
|
909
|
-
matchedFileCount: 0
|
|
945
|
+
matchedFileCount: 0,
|
|
946
|
+
truncated: false,
|
|
947
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
910
948
|
},
|
|
911
949
|
error: formatError3(error2)
|
|
912
950
|
};
|
|
913
951
|
}
|
|
914
952
|
const normalizedFiles = Array.from(
|
|
915
|
-
new Set(files.map((file) => path6.normalize(file)))
|
|
953
|
+
new Set(scanResult.files.map((file) => path6.normalize(file)))
|
|
916
954
|
);
|
|
917
955
|
for (const file of normalizedFiles) {
|
|
918
956
|
const text = await readFile3(file, "utf-8");
|
|
@@ -931,7 +969,9 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
931
969
|
scan: {
|
|
932
970
|
globs: normalizedGlobs,
|
|
933
971
|
excludeGlobs: mergedExcludeGlobs,
|
|
934
|
-
matchedFileCount:
|
|
972
|
+
matchedFileCount: scanResult.matchedFileCount,
|
|
973
|
+
truncated: scanResult.truncated,
|
|
974
|
+
limit: scanResult.limit
|
|
935
975
|
}
|
|
936
976
|
};
|
|
937
977
|
}
|
|
@@ -1101,8 +1141,8 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
1101
1141
|
import path9 from "path";
|
|
1102
1142
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1103
1143
|
async function resolveToolVersion() {
|
|
1104
|
-
if ("1.0.
|
|
1105
|
-
return "1.0.
|
|
1144
|
+
if ("1.0.2".length > 0) {
|
|
1145
|
+
return "1.0.2";
|
|
1106
1146
|
}
|
|
1107
1147
|
try {
|
|
1108
1148
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1313,19 +1353,27 @@ async function createDoctorData(options) {
|
|
|
1313
1353
|
...config.validation.traceability.testFileExcludeGlobs
|
|
1314
1354
|
]);
|
|
1315
1355
|
try {
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
|
|
1356
|
+
const scanResult = globs.length === 0 ? {
|
|
1357
|
+
files: [],
|
|
1358
|
+
truncated: false,
|
|
1359
|
+
matchedFileCount: 0,
|
|
1360
|
+
limit: DEFAULT_GLOB_FILE_LIMIT
|
|
1361
|
+
} : await collectFilesByGlobs(root, { globs, ignore: exclude });
|
|
1362
|
+
const matchedCount = scanResult.matchedFileCount;
|
|
1363
|
+
const truncated = scanResult.truncated;
|
|
1364
|
+
const severity = globs.length === 0 ? "warning" : truncated ? "warning" : scenarioFiles.length > 0 && config.validation.traceability.scMustHaveTest && matchedCount === 0 ? "warning" : "ok";
|
|
1319
1365
|
addCheck(checks, {
|
|
1320
1366
|
id: "traceability.testGlobs",
|
|
1321
1367
|
severity,
|
|
1322
1368
|
title: "Test file globs",
|
|
1323
|
-
message: globs.length === 0 ? "testFileGlobs is empty (SC\u2192Test cannot be verified)" : `
|
|
1369
|
+
message: globs.length === 0 ? "testFileGlobs is empty (SC\u2192Test cannot be verified)" : truncated ? `fileCount=${matchedCount} (truncated, limit=${scanResult.limit})` : `fileCount=${matchedCount}`,
|
|
1324
1370
|
details: {
|
|
1325
1371
|
globs,
|
|
1326
1372
|
excludeGlobs: exclude,
|
|
1327
1373
|
scenarioFiles: scenarioFiles.length,
|
|
1328
|
-
scMustHaveTest: config.validation.traceability.scMustHaveTest
|
|
1374
|
+
scMustHaveTest: config.validation.traceability.scMustHaveTest,
|
|
1375
|
+
truncated,
|
|
1376
|
+
limit: scanResult.limit
|
|
1329
1377
|
}
|
|
1330
1378
|
});
|
|
1331
1379
|
} catch (error2) {
|
|
@@ -1334,7 +1382,12 @@ async function createDoctorData(options) {
|
|
|
1334
1382
|
severity: "error",
|
|
1335
1383
|
title: "Test file globs",
|
|
1336
1384
|
message: "Glob scan failed (invalid pattern or filesystem error)",
|
|
1337
|
-
details: {
|
|
1385
|
+
details: {
|
|
1386
|
+
globs,
|
|
1387
|
+
excludeGlobs: exclude,
|
|
1388
|
+
limit: DEFAULT_GLOB_FILE_LIMIT,
|
|
1389
|
+
error: String(error2)
|
|
1390
|
+
}
|
|
1338
1391
|
});
|
|
1339
1392
|
}
|
|
1340
1393
|
return {
|
|
@@ -1368,8 +1421,10 @@ async function buildOutDirCollisionCheck(root) {
|
|
|
1368
1421
|
(collisionRoot) => toRelativePath(result.monorepoRoot, collisionRoot)
|
|
1369
1422
|
).sort((a, b) => a.localeCompare(b))
|
|
1370
1423
|
})).sort((a, b) => a.outDir.localeCompare(b.outDir));
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
1424
|
+
const truncated = result.scan.truncated;
|
|
1425
|
+
const severity = collisions.length > 0 || truncated ? "warning" : "ok";
|
|
1426
|
+
const messageBase = collisions.length > 0 ? `outDir collision detected (count=${collisions.length})` : `outDir collision not detected (configs=${configRoots.length})`;
|
|
1427
|
+
const message = truncated ? `${messageBase}; scan truncated (collected=${result.scan.matchedFileCount}, limit=${result.scan.limit})` : messageBase;
|
|
1373
1428
|
return {
|
|
1374
1429
|
id: "output.outDirCollision",
|
|
1375
1430
|
severity,
|
|
@@ -1378,7 +1433,8 @@ async function buildOutDirCollisionCheck(root) {
|
|
|
1378
1433
|
details: {
|
|
1379
1434
|
monorepoRoot: relativeRoot,
|
|
1380
1435
|
configRoots,
|
|
1381
|
-
collisions
|
|
1436
|
+
collisions,
|
|
1437
|
+
scan: result.scan
|
|
1382
1438
|
}
|
|
1383
1439
|
};
|
|
1384
1440
|
} catch (error2) {
|
|
@@ -1393,10 +1449,11 @@ async function buildOutDirCollisionCheck(root) {
|
|
|
1393
1449
|
}
|
|
1394
1450
|
async function detectOutDirCollisions(root) {
|
|
1395
1451
|
const monorepoRoot = await findMonorepoRoot(root);
|
|
1396
|
-
const
|
|
1452
|
+
const configScan = await collectFilesByGlobs(monorepoRoot, {
|
|
1397
1453
|
globs: ["**/qfai.config.yaml"],
|
|
1398
1454
|
ignore: DEFAULT_CONFIG_SEARCH_IGNORE_GLOBS
|
|
1399
1455
|
});
|
|
1456
|
+
const configPaths = configScan.files;
|
|
1400
1457
|
const configRoots = Array.from(
|
|
1401
1458
|
new Set(configPaths.map((configPath) => path10.dirname(configPath)))
|
|
1402
1459
|
).sort((a, b) => a.localeCompare(b));
|
|
@@ -1417,7 +1474,16 @@ async function detectOutDirCollisions(root) {
|
|
|
1417
1474
|
});
|
|
1418
1475
|
}
|
|
1419
1476
|
}
|
|
1420
|
-
return {
|
|
1477
|
+
return {
|
|
1478
|
+
monorepoRoot,
|
|
1479
|
+
configRoots,
|
|
1480
|
+
collisions,
|
|
1481
|
+
scan: {
|
|
1482
|
+
truncated: configScan.truncated,
|
|
1483
|
+
matchedFileCount: configScan.matchedFileCount,
|
|
1484
|
+
limit: configScan.limit
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1421
1487
|
}
|
|
1422
1488
|
async function findMonorepoRoot(startDir) {
|
|
1423
1489
|
let current = path10.resolve(startDir);
|
|
@@ -2540,12 +2606,11 @@ async function validateScenarios(root, config) {
|
|
|
2540
2606
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2541
2607
|
const entries = await collectSpecEntries(specsRoot);
|
|
2542
2608
|
if (entries.length === 0) {
|
|
2543
|
-
const expected = "spec-0001/scenario.
|
|
2544
|
-
const legacy = "spec-001/scenario.md";
|
|
2609
|
+
const expected = "spec-0001/scenario.feature";
|
|
2545
2610
|
return [
|
|
2546
2611
|
issue4(
|
|
2547
2612
|
"QFAI-SC-000",
|
|
2548
|
-
`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}
|
|
2613
|
+
`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}`,
|
|
2549
2614
|
"info",
|
|
2550
2615
|
specsRoot,
|
|
2551
2616
|
"scenario.files"
|
|
@@ -2562,7 +2627,7 @@ async function validateScenarios(root, config) {
|
|
|
2562
2627
|
issues.push(
|
|
2563
2628
|
issue4(
|
|
2564
2629
|
"QFAI-SC-001",
|
|
2565
|
-
"scenario.
|
|
2630
|
+
"scenario.feature \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
2566
2631
|
"error",
|
|
2567
2632
|
entry.scenarioPath,
|
|
2568
2633
|
"scenario.exists"
|
|
@@ -3524,13 +3589,14 @@ async function createReportData(root, validation, configResult) {
|
|
|
3524
3589
|
issues: normalizedValidation.issues
|
|
3525
3590
|
};
|
|
3526
3591
|
}
|
|
3527
|
-
function formatReportMarkdown(data) {
|
|
3592
|
+
function formatReportMarkdown(data, options = {}) {
|
|
3528
3593
|
const lines = [];
|
|
3594
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
3529
3595
|
lines.push("# QFAI Report");
|
|
3530
3596
|
lines.push("");
|
|
3531
3597
|
lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
|
|
3532
|
-
lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
|
|
3533
|
-
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
3598
|
+
lines.push(`- \u30EB\u30FC\u30C8: ${formatPathLink(data.root, baseUrl)}`);
|
|
3599
|
+
lines.push(`- \u8A2D\u5B9A: ${formatPathLink(data.configPath, baseUrl)}`);
|
|
3534
3600
|
lines.push(`- \u7248: ${data.version}`);
|
|
3535
3601
|
lines.push("");
|
|
3536
3602
|
const severityOrder = {
|
|
@@ -3669,8 +3735,7 @@ function formatReportMarkdown(data) {
|
|
|
3669
3735
|
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
3670
3736
|
);
|
|
3671
3737
|
if (item.file) {
|
|
3672
|
-
|
|
3673
|
-
out.push(`- file: ${item.file}${loc}`);
|
|
3738
|
+
out.push(`- file: ${formatPathWithLine(item.file, item.loc, baseUrl)}`);
|
|
3674
3739
|
}
|
|
3675
3740
|
if (item.rule) {
|
|
3676
3741
|
out.push(`- rule: ${item.rule}`);
|
|
@@ -3796,6 +3861,11 @@ function formatReportMarkdown(data) {
|
|
|
3796
3861
|
lines.push(
|
|
3797
3862
|
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
3798
3863
|
);
|
|
3864
|
+
if (data.traceability.testFiles.truncated) {
|
|
3865
|
+
lines.push(
|
|
3866
|
+
`- testFileTruncated: true (limit=${data.traceability.testFiles.limit})`
|
|
3867
|
+
);
|
|
3868
|
+
}
|
|
3799
3869
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
3800
3870
|
lines.push("- missingIds: (none)");
|
|
3801
3871
|
} else {
|
|
@@ -3805,7 +3875,8 @@ function formatReportMarkdown(data) {
|
|
|
3805
3875
|
if (files.length === 0) {
|
|
3806
3876
|
return id;
|
|
3807
3877
|
}
|
|
3808
|
-
|
|
3878
|
+
const formattedFiles = files.map((file) => formatPathLink(file, baseUrl));
|
|
3879
|
+
return `${id} (${formattedFiles.join(", ")})`;
|
|
3809
3880
|
});
|
|
3810
3881
|
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
3811
3882
|
}
|
|
@@ -3822,7 +3893,8 @@ function formatReportMarkdown(data) {
|
|
|
3822
3893
|
if (refs.length === 0) {
|
|
3823
3894
|
lines.push(`- ${scId}: (none)`);
|
|
3824
3895
|
} else {
|
|
3825
|
-
|
|
3896
|
+
const formattedRefs = refs.map((ref) => formatPathLink(ref, baseUrl));
|
|
3897
|
+
lines.push(`- ${scId}: ${formattedRefs.join(", ")}`);
|
|
3826
3898
|
}
|
|
3827
3899
|
}
|
|
3828
3900
|
}
|
|
@@ -3837,8 +3909,9 @@ function formatReportMarkdown(data) {
|
|
|
3837
3909
|
} else {
|
|
3838
3910
|
for (const item of specScIssues) {
|
|
3839
3911
|
const location = item.file ?? "(unknown)";
|
|
3912
|
+
const formattedLocation = location === "(unknown)" ? location : formatPathLink(location, baseUrl);
|
|
3840
3913
|
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
3841
|
-
lines.push(`- ${
|
|
3914
|
+
lines.push(`- ${formattedLocation}: ${refs}`);
|
|
3842
3915
|
}
|
|
3843
3916
|
}
|
|
3844
3917
|
lines.push("");
|
|
@@ -3850,7 +3923,7 @@ function formatReportMarkdown(data) {
|
|
|
3850
3923
|
} else {
|
|
3851
3924
|
for (const spot of hotspots) {
|
|
3852
3925
|
lines.push(
|
|
3853
|
-
`- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3926
|
+
`- ${formatPathLink(spot.file, baseUrl)}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
|
|
3854
3927
|
);
|
|
3855
3928
|
}
|
|
3856
3929
|
}
|
|
@@ -4007,6 +4080,41 @@ function formatMarkdownTable(headers, rows) {
|
|
|
4007
4080
|
const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
|
|
4008
4081
|
return [formatRow(headers), separator, ...rows.map(formatRow)];
|
|
4009
4082
|
}
|
|
4083
|
+
function normalizeBaseUrl(value) {
|
|
4084
|
+
if (!value) {
|
|
4085
|
+
return void 0;
|
|
4086
|
+
}
|
|
4087
|
+
const trimmed = value.trim();
|
|
4088
|
+
if (!trimmed) {
|
|
4089
|
+
return void 0;
|
|
4090
|
+
}
|
|
4091
|
+
return trimmed.replace(/\/+$/, "");
|
|
4092
|
+
}
|
|
4093
|
+
function formatPathLink(value, baseUrl) {
|
|
4094
|
+
if (!baseUrl) {
|
|
4095
|
+
return value;
|
|
4096
|
+
}
|
|
4097
|
+
if (value === ".") {
|
|
4098
|
+
return `[${value}](${baseUrl})`;
|
|
4099
|
+
}
|
|
4100
|
+
const encoded = encodePathForUrl(value);
|
|
4101
|
+
if (!encoded) {
|
|
4102
|
+
return value;
|
|
4103
|
+
}
|
|
4104
|
+
return `[${value}](${baseUrl}/${encoded})`;
|
|
4105
|
+
}
|
|
4106
|
+
function formatPathWithLine(value, loc, baseUrl) {
|
|
4107
|
+
const link = formatPathLink(value, baseUrl);
|
|
4108
|
+
const line = loc?.line ? `:${loc.line}` : "";
|
|
4109
|
+
return `${link}${line}`;
|
|
4110
|
+
}
|
|
4111
|
+
function encodePathForUrl(value) {
|
|
4112
|
+
const normalized = value.replace(/\\/g, "/");
|
|
4113
|
+
if (normalized === ".") {
|
|
4114
|
+
return "";
|
|
4115
|
+
}
|
|
4116
|
+
return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
4117
|
+
}
|
|
4010
4118
|
function toSortedArray2(values) {
|
|
4011
4119
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
4012
4120
|
}
|
|
@@ -4060,6 +4168,16 @@ function buildHotspots(issues) {
|
|
|
4060
4168
|
);
|
|
4061
4169
|
}
|
|
4062
4170
|
|
|
4171
|
+
// src/cli/lib/warnings.ts
|
|
4172
|
+
function warnIfTruncated(scan, context) {
|
|
4173
|
+
if (!scan.truncated) {
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
warn(
|
|
4177
|
+
`[warn] ${context}: file scan truncated: collected ${scan.matchedFileCount} files (limit ${scan.limit})`
|
|
4178
|
+
);
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4063
4181
|
// src/cli/commands/report.ts
|
|
4064
4182
|
async function runReport(options) {
|
|
4065
4183
|
const root = path20.resolve(options.root);
|
|
@@ -4103,7 +4221,8 @@ async function runReport(options) {
|
|
|
4103
4221
|
}
|
|
4104
4222
|
}
|
|
4105
4223
|
const data = await createReportData(root, validation, configResult);
|
|
4106
|
-
|
|
4224
|
+
warnIfTruncated(data.traceability.testFiles, "report");
|
|
4225
|
+
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
4107
4226
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4108
4227
|
const defaultOut = options.format === "json" ? path20.join(outRoot, "report.json") : path20.join(outRoot, "report.md");
|
|
4109
4228
|
const out = options.outPath ?? defaultOut;
|
|
@@ -4200,6 +4319,7 @@ async function runValidate(options) {
|
|
|
4200
4319
|
const configResult = await loadConfig(root);
|
|
4201
4320
|
const result = await validateProject(root, configResult);
|
|
4202
4321
|
const normalized = normalizeValidationResult(root, result);
|
|
4322
|
+
warnIfTruncated(normalized.traceability.testFiles, "validate");
|
|
4203
4323
|
const failOn = resolveFailOn(options, configResult.config.validation.failOn);
|
|
4204
4324
|
const willFail = shouldFail(normalized, failOn);
|
|
4205
4325
|
const format = options.format ?? "text";
|
|
@@ -4458,6 +4578,17 @@ function parseArgs(argv, cwd) {
|
|
|
4458
4578
|
case "--run-validate":
|
|
4459
4579
|
options.reportRunValidate = true;
|
|
4460
4580
|
break;
|
|
4581
|
+
case "--base-url": {
|
|
4582
|
+
const next = readOptionValue(args, i);
|
|
4583
|
+
if (next === null) {
|
|
4584
|
+
invalid = true;
|
|
4585
|
+
options.help = true;
|
|
4586
|
+
break;
|
|
4587
|
+
}
|
|
4588
|
+
options.reportBaseUrl = next;
|
|
4589
|
+
i += 1;
|
|
4590
|
+
break;
|
|
4591
|
+
}
|
|
4461
4592
|
case "--help":
|
|
4462
4593
|
case "-h":
|
|
4463
4594
|
options.help = true;
|
|
@@ -4554,6 +4685,7 @@ async function run(argv, cwd) {
|
|
|
4554
4685
|
format: options.reportFormat,
|
|
4555
4686
|
...options.reportOut !== void 0 ? { outPath: options.reportOut } : {},
|
|
4556
4687
|
...options.reportIn !== void 0 ? { inputPath: options.reportIn } : {},
|
|
4688
|
+
...options.reportBaseUrl !== void 0 ? { baseUrl: options.reportBaseUrl } : {},
|
|
4557
4689
|
...options.reportRunValidate ? { runValidate: true } : {}
|
|
4558
4690
|
});
|
|
4559
4691
|
}
|
|
@@ -4603,6 +4735,7 @@ Options:
|
|
|
4603
4735
|
--out <path> report/doctor: \u51FA\u529B\u5148
|
|
4604
4736
|
--in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
|
|
4605
4737
|
--run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
|
|
4738
|
+
--base-url <url> report: \u30D1\u30B9\u3092\u30EA\u30F3\u30AF\u5316\u3059\u308B\u57FA\u6E96URL
|
|
4606
4739
|
-h, --help \u30D8\u30EB\u30D7\u8868\u793A
|
|
4607
4740
|
`;
|
|
4608
4741
|
}
|