qfai 0.4.0 → 0.4.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 +6 -1
- package/assets/init/.qfai/README.md +2 -1
- package/assets/init/.qfai/prompts/README.md +1 -0
- package/assets/init/.qfai/prompts/qfai-generate-test-globs.md +29 -0
- package/assets/init/root/qfai.config.yaml +6 -0
- package/assets/init/root/tests/qfai-traceability.sample.test.ts +2 -2
- package/dist/cli/index.cjs +337 -117
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.mjs +337 -117
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +327 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -9
- package/dist/index.d.ts +156 -2
- package/dist/index.mjs +327 -107
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/cli/commands/init.d.ts +0 -8
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/init.js +0 -30
- package/dist/cli/commands/init.js.map +0 -1
- package/dist/cli/commands/report.d.ts +0 -7
- package/dist/cli/commands/report.d.ts.map +0 -1
- package/dist/cli/commands/report.js +0 -80
- package/dist/cli/commands/report.js.map +0 -1
- package/dist/cli/commands/validate.d.ts +0 -9
- package/dist/cli/commands/validate.d.ts.map +0 -1
- package/dist/cli/commands/validate.js +0 -57
- package/dist/cli/commands/validate.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -7
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/lib/args.d.ts +0 -18
- package/dist/cli/lib/args.d.ts.map +0 -1
- package/dist/cli/lib/args.js +0 -98
- package/dist/cli/lib/args.js.map +0 -1
- package/dist/cli/lib/assets.d.ts +0 -2
- package/dist/cli/lib/assets.d.ts.map +0 -1
- package/dist/cli/lib/assets.js +0 -24
- package/dist/cli/lib/assets.js.map +0 -1
- package/dist/cli/lib/failOn.d.ts +0 -5
- package/dist/cli/lib/failOn.d.ts.map +0 -1
- package/dist/cli/lib/failOn.js +0 -10
- package/dist/cli/lib/failOn.js.map +0 -1
- package/dist/cli/lib/fs.d.ts +0 -11
- package/dist/cli/lib/fs.d.ts.map +0 -1
- package/dist/cli/lib/fs.js +0 -91
- package/dist/cli/lib/fs.js.map +0 -1
- package/dist/cli/lib/logger.d.ts +0 -4
- package/dist/cli/lib/logger.d.ts.map +0 -1
- package/dist/cli/lib/logger.js +0 -10
- package/dist/cli/lib/logger.js.map +0 -1
- package/dist/cli/main.d.ts +0 -2
- package/dist/cli/main.d.ts.map +0 -1
- package/dist/cli/main.js +0 -66
- package/dist/cli/main.js.map +0 -1
- package/dist/core/config.d.ts +0 -46
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -222
- package/dist/core/config.js.map +0 -1
- package/dist/core/contractIndex.d.ts +0 -13
- package/dist/core/contractIndex.d.ts.map +0 -1
- package/dist/core/contractIndex.js +0 -66
- package/dist/core/contractIndex.js.map +0 -1
- package/dist/core/contracts.d.ts +0 -5
- package/dist/core/contracts.d.ts.map +0 -1
- package/dist/core/contracts.js +0 -42
- package/dist/core/contracts.js.map +0 -1
- package/dist/core/discovery.d.ts +0 -14
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -55
- package/dist/core/discovery.js.map +0 -1
- package/dist/core/fs.d.ts +0 -6
- package/dist/core/fs.d.ts.map +0 -1
- package/dist/core/fs.js +0 -55
- package/dist/core/fs.js.map +0 -1
- package/dist/core/gherkin/parse.d.ts +0 -7
- package/dist/core/gherkin/parse.d.ts.map +0 -1
- package/dist/core/gherkin/parse.js +0 -25
- package/dist/core/gherkin/parse.js.map +0 -1
- package/dist/core/ids.d.ts +0 -6
- package/dist/core/ids.d.ts.map +0 -1
- package/dist/core/ids.js +0 -52
- package/dist/core/ids.js.map +0 -1
- package/dist/core/index.d.ts +0 -13
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -13
- package/dist/core/index.js.map +0 -1
- package/dist/core/parse/adr.d.ts +0 -13
- package/dist/core/parse/adr.d.ts.map +0 -1
- package/dist/core/parse/adr.js +0 -33
- package/dist/core/parse/adr.js.map +0 -1
- package/dist/core/parse/gherkin.d.ts +0 -12
- package/dist/core/parse/gherkin.d.ts.map +0 -1
- package/dist/core/parse/gherkin.js +0 -22
- package/dist/core/parse/gherkin.js.map +0 -1
- package/dist/core/parse/markdown.d.ts +0 -14
- package/dist/core/parse/markdown.d.ts.map +0 -1
- package/dist/core/parse/markdown.js +0 -45
- package/dist/core/parse/markdown.js.map +0 -1
- package/dist/core/parse/spec.d.ts +0 -28
- package/dist/core/parse/spec.d.ts.map +0 -1
- package/dist/core/parse/spec.js +0 -80
- package/dist/core/parse/spec.js.map +0 -1
- package/dist/core/report.d.ts +0 -41
- package/dist/core/report.d.ts.map +0 -1
- package/dist/core/report.js +0 -260
- package/dist/core/report.js.map +0 -1
- package/dist/core/scenarioModel.d.ts +0 -33
- package/dist/core/scenarioModel.d.ts.map +0 -1
- package/dist/core/scenarioModel.js +0 -130
- package/dist/core/scenarioModel.js.map +0 -1
- package/dist/core/specLayout.d.ts +0 -8
- package/dist/core/specLayout.d.ts.map +0 -1
- package/dist/core/specLayout.js +0 -36
- package/dist/core/specLayout.js.map +0 -1
- package/dist/core/traceability.d.ts +0 -12
- package/dist/core/traceability.d.ts.map +0 -1
- package/dist/core/traceability.js +0 -70
- package/dist/core/traceability.js.map +0 -1
- package/dist/core/types.d.ts +0 -25
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -2
- package/dist/core/types.js.map +0 -1
- package/dist/core/validate.d.ts +0 -4
- package/dist/core/validate.d.ts.map +0 -1
- package/dist/core/validate.js +0 -34
- package/dist/core/validate.js.map +0 -1
- package/dist/core/validators/contracts.d.ts +0 -5
- package/dist/core/validators/contracts.d.ts.map +0 -1
- package/dist/core/validators/contracts.js +0 -162
- package/dist/core/validators/contracts.js.map +0 -1
- package/dist/core/validators/delta.d.ts +0 -4
- package/dist/core/validators/delta.d.ts.map +0 -1
- package/dist/core/validators/delta.js +0 -68
- package/dist/core/validators/delta.js.map +0 -1
- package/dist/core/validators/ids.d.ts +0 -4
- package/dist/core/validators/ids.d.ts.map +0 -1
- package/dist/core/validators/ids.js +0 -88
- package/dist/core/validators/ids.js.map +0 -1
- package/dist/core/validators/scenario.d.ts +0 -5
- package/dist/core/validators/scenario.d.ts.map +0 -1
- package/dist/core/validators/scenario.js +0 -140
- package/dist/core/validators/scenario.js.map +0 -1
- package/dist/core/validators/spec.d.ts +0 -5
- package/dist/core/validators/spec.d.ts.map +0 -1
- package/dist/core/validators/spec.js +0 -94
- package/dist/core/validators/spec.js.map +0 -1
- package/dist/core/validators/traceability.d.ts +0 -4
- package/dist/core/validators/traceability.d.ts.map +0 -1
- package/dist/core/validators/traceability.js +0 -190
- package/dist/core/validators/traceability.js.map +0 -1
- package/dist/core/version.d.ts +0 -2
- package/dist/core/version.d.ts.map +0 -1
- package/dist/core/version.js +0 -25
- package/dist/core/version.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -178,7 +178,7 @@ function report(copied, skipped, dryRun, label) {
|
|
|
178
178
|
|
|
179
179
|
// src/cli/commands/report.ts
|
|
180
180
|
var import_promises16 = require("fs/promises");
|
|
181
|
-
var
|
|
181
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
182
182
|
|
|
183
183
|
// src/core/config.ts
|
|
184
184
|
var import_promises2 = require("fs/promises");
|
|
@@ -211,6 +211,8 @@ var defaultConfig = {
|
|
|
211
211
|
brMustHaveSc: true,
|
|
212
212
|
scMustTouchContracts: true,
|
|
213
213
|
scMustHaveTest: true,
|
|
214
|
+
testFileGlobs: [],
|
|
215
|
+
testFileExcludeGlobs: [],
|
|
214
216
|
scNoTestSeverity: "error",
|
|
215
217
|
allowOrphanContracts: false,
|
|
216
218
|
unknownContractIdSeverity: "error"
|
|
@@ -398,6 +400,20 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
398
400
|
configPath,
|
|
399
401
|
issues
|
|
400
402
|
),
|
|
403
|
+
testFileGlobs: readStringArray(
|
|
404
|
+
traceabilityRaw?.testFileGlobs,
|
|
405
|
+
base.traceability.testFileGlobs,
|
|
406
|
+
"validation.traceability.testFileGlobs",
|
|
407
|
+
configPath,
|
|
408
|
+
issues
|
|
409
|
+
),
|
|
410
|
+
testFileExcludeGlobs: readStringArray(
|
|
411
|
+
traceabilityRaw?.testFileExcludeGlobs,
|
|
412
|
+
base.traceability.testFileExcludeGlobs,
|
|
413
|
+
"validation.traceability.testFileExcludeGlobs",
|
|
414
|
+
configPath,
|
|
415
|
+
issues
|
|
416
|
+
),
|
|
401
417
|
scNoTestSeverity: readTraceabilitySeverity(
|
|
402
418
|
traceabilityRaw?.scNoTestSeverity,
|
|
403
419
|
base.traceability.scNoTestSeverity,
|
|
@@ -531,7 +547,7 @@ function isRecord(value) {
|
|
|
531
547
|
|
|
532
548
|
// src/core/report.ts
|
|
533
549
|
var import_promises15 = require("fs/promises");
|
|
534
|
-
var
|
|
550
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
535
551
|
|
|
536
552
|
// src/core/discovery.ts
|
|
537
553
|
var import_promises5 = require("fs/promises");
|
|
@@ -539,6 +555,7 @@ var import_promises5 = require("fs/promises");
|
|
|
539
555
|
// src/core/fs.ts
|
|
540
556
|
var import_promises3 = require("fs/promises");
|
|
541
557
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
558
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
542
559
|
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
543
560
|
"node_modules",
|
|
544
561
|
".git",
|
|
@@ -560,6 +577,18 @@ async function collectFiles(root, options = {}) {
|
|
|
560
577
|
await walk(root, root, ignoreDirs, extensions, entries);
|
|
561
578
|
return entries;
|
|
562
579
|
}
|
|
580
|
+
async function collectFilesByGlobs(root, options) {
|
|
581
|
+
if (options.globs.length === 0) {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
return (0, import_fast_glob.default)(options.globs, {
|
|
585
|
+
cwd: root,
|
|
586
|
+
ignore: options.ignore ?? [],
|
|
587
|
+
onlyFiles: true,
|
|
588
|
+
absolute: true,
|
|
589
|
+
unique: true
|
|
590
|
+
});
|
|
591
|
+
}
|
|
563
592
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
564
593
|
const items = await (0, import_promises3.readdir)(current, { withFileTypes: true });
|
|
565
594
|
for (const item of items) {
|
|
@@ -726,6 +755,7 @@ function isValidId(value, prefix) {
|
|
|
726
755
|
|
|
727
756
|
// src/core/traceability.ts
|
|
728
757
|
var import_promises6 = require("fs/promises");
|
|
758
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
729
759
|
|
|
730
760
|
// src/core/gherkin/parse.ts
|
|
731
761
|
var import_gherkin = require("@cucumber/gherkin");
|
|
@@ -883,6 +913,27 @@ function unique2(values) {
|
|
|
883
913
|
|
|
884
914
|
// src/core/traceability.ts
|
|
885
915
|
var SC_TAG_RE2 = /^SC-\d{4}$/;
|
|
916
|
+
var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
|
|
917
|
+
var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
|
|
918
|
+
"**/node_modules/**",
|
|
919
|
+
"**/.git/**",
|
|
920
|
+
"**/.qfai/**",
|
|
921
|
+
"**/dist/**",
|
|
922
|
+
"**/build/**",
|
|
923
|
+
"**/coverage/**",
|
|
924
|
+
"**/.next/**",
|
|
925
|
+
"**/out/**"
|
|
926
|
+
];
|
|
927
|
+
function extractAnnotatedScIds(text) {
|
|
928
|
+
const ids = /* @__PURE__ */ new Set();
|
|
929
|
+
for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
|
|
930
|
+
const suffix = match[1];
|
|
931
|
+
if (suffix) {
|
|
932
|
+
ids.add(`SC-${suffix}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return Array.from(ids);
|
|
936
|
+
}
|
|
886
937
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
887
938
|
const scIds = /* @__PURE__ */ new Set();
|
|
888
939
|
for (const file of scenarioFiles) {
|
|
@@ -901,14 +952,67 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
901
952
|
}
|
|
902
953
|
return scIds;
|
|
903
954
|
}
|
|
904
|
-
async function
|
|
955
|
+
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
956
|
+
const sources = /* @__PURE__ */ new Map();
|
|
957
|
+
for (const file of scenarioFiles) {
|
|
958
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
959
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
960
|
+
if (!document || errors.length > 0) {
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
for (const scenario of document.scenarios) {
|
|
964
|
+
for (const tag of scenario.tags) {
|
|
965
|
+
if (!SC_TAG_RE2.test(tag)) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
|
|
969
|
+
current.add(file);
|
|
970
|
+
sources.set(tag, current);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return sources;
|
|
975
|
+
}
|
|
976
|
+
async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
905
977
|
const refs = /* @__PURE__ */ new Map();
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
978
|
+
const normalizedGlobs = normalizeGlobs(globs);
|
|
979
|
+
const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
|
|
980
|
+
const mergedExcludeGlobs = Array.from(
|
|
981
|
+
/* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
|
|
982
|
+
);
|
|
983
|
+
if (normalizedGlobs.length === 0) {
|
|
984
|
+
return {
|
|
985
|
+
refs,
|
|
986
|
+
scan: {
|
|
987
|
+
globs: normalizedGlobs,
|
|
988
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
989
|
+
matchedFileCount: 0
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
let files = [];
|
|
994
|
+
try {
|
|
995
|
+
files = await collectFilesByGlobs(root, {
|
|
996
|
+
globs: normalizedGlobs,
|
|
997
|
+
ignore: mergedExcludeGlobs
|
|
998
|
+
});
|
|
999
|
+
} catch (error2) {
|
|
1000
|
+
return {
|
|
1001
|
+
refs,
|
|
1002
|
+
scan: {
|
|
1003
|
+
globs: normalizedGlobs,
|
|
1004
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
1005
|
+
matchedFileCount: 0
|
|
1006
|
+
},
|
|
1007
|
+
error: formatError3(error2)
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const normalizedFiles = Array.from(
|
|
1011
|
+
new Set(files.map((file) => import_node_path7.default.normalize(file)))
|
|
1012
|
+
);
|
|
1013
|
+
for (const file of normalizedFiles) {
|
|
910
1014
|
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
911
|
-
const scIds =
|
|
1015
|
+
const scIds = extractAnnotatedScIds(text);
|
|
912
1016
|
if (scIds.length === 0) {
|
|
913
1017
|
continue;
|
|
914
1018
|
}
|
|
@@ -918,7 +1022,14 @@ async function collectScTestReferences(testsRoot) {
|
|
|
918
1022
|
refs.set(scId, current);
|
|
919
1023
|
}
|
|
920
1024
|
}
|
|
921
|
-
return
|
|
1025
|
+
return {
|
|
1026
|
+
refs,
|
|
1027
|
+
scan: {
|
|
1028
|
+
globs: normalizedGlobs,
|
|
1029
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
1030
|
+
matchedFileCount: normalizedFiles.length
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
922
1033
|
}
|
|
923
1034
|
function buildScCoverage(scIds, refs) {
|
|
924
1035
|
const sortedScIds = toSortedArray(scIds);
|
|
@@ -946,14 +1057,23 @@ function buildScCoverage(scIds, refs) {
|
|
|
946
1057
|
function toSortedArray(values) {
|
|
947
1058
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
948
1059
|
}
|
|
1060
|
+
function normalizeGlobs(globs) {
|
|
1061
|
+
return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1062
|
+
}
|
|
1063
|
+
function formatError3(error2) {
|
|
1064
|
+
if (error2 instanceof Error) {
|
|
1065
|
+
return error2.message;
|
|
1066
|
+
}
|
|
1067
|
+
return String(error2);
|
|
1068
|
+
}
|
|
949
1069
|
|
|
950
1070
|
// src/core/version.ts
|
|
951
1071
|
var import_promises7 = require("fs/promises");
|
|
952
|
-
var
|
|
1072
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
953
1073
|
var import_node_url2 = require("url");
|
|
954
1074
|
async function resolveToolVersion() {
|
|
955
|
-
if ("0.4.
|
|
956
|
-
return "0.4.
|
|
1075
|
+
if ("0.4.2".length > 0) {
|
|
1076
|
+
return "0.4.2";
|
|
957
1077
|
}
|
|
958
1078
|
try {
|
|
959
1079
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -968,18 +1088,18 @@ async function resolveToolVersion() {
|
|
|
968
1088
|
function resolvePackageJsonPath() {
|
|
969
1089
|
const base = __filename;
|
|
970
1090
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
971
|
-
return
|
|
1091
|
+
return import_node_path8.default.resolve(import_node_path8.default.dirname(basePath), "../../package.json");
|
|
972
1092
|
}
|
|
973
1093
|
|
|
974
1094
|
// src/core/validators/contracts.ts
|
|
975
1095
|
var import_promises8 = require("fs/promises");
|
|
976
|
-
var
|
|
1096
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
977
1097
|
|
|
978
1098
|
// src/core/contracts.ts
|
|
979
|
-
var
|
|
1099
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
980
1100
|
var import_yaml2 = require("yaml");
|
|
981
1101
|
function parseStructuredContract(file, text) {
|
|
982
|
-
const ext =
|
|
1102
|
+
const ext = import_node_path9.default.extname(file).toLowerCase();
|
|
983
1103
|
if (ext === ".json") {
|
|
984
1104
|
return JSON.parse(text);
|
|
985
1105
|
}
|
|
@@ -1030,9 +1150,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1030
1150
|
async function validateContracts(root, config) {
|
|
1031
1151
|
const issues = [];
|
|
1032
1152
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1033
|
-
issues.push(...await validateUiContracts(
|
|
1034
|
-
issues.push(...await validateApiContracts(
|
|
1035
|
-
issues.push(...await validateDataContracts(
|
|
1153
|
+
issues.push(...await validateUiContracts(import_node_path10.default.join(contractsRoot, "ui")));
|
|
1154
|
+
issues.push(...await validateApiContracts(import_node_path10.default.join(contractsRoot, "api")));
|
|
1155
|
+
issues.push(...await validateDataContracts(import_node_path10.default.join(contractsRoot, "db")));
|
|
1036
1156
|
return issues;
|
|
1037
1157
|
}
|
|
1038
1158
|
async function validateUiContracts(uiRoot) {
|
|
@@ -1079,7 +1199,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1079
1199
|
issues.push(
|
|
1080
1200
|
issue(
|
|
1081
1201
|
"QFAI-CONTRACT-001",
|
|
1082
|
-
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1202
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
|
|
1083
1203
|
"error",
|
|
1084
1204
|
file,
|
|
1085
1205
|
"contracts.ui.parse"
|
|
@@ -1146,7 +1266,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1146
1266
|
issues.push(
|
|
1147
1267
|
issue(
|
|
1148
1268
|
"QFAI-CONTRACT-001",
|
|
1149
|
-
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1269
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
|
|
1150
1270
|
"error",
|
|
1151
1271
|
file,
|
|
1152
1272
|
"contracts.api.parse"
|
|
@@ -1241,7 +1361,7 @@ function lintSql(text, file) {
|
|
|
1241
1361
|
function hasOpenApi(doc) {
|
|
1242
1362
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1243
1363
|
}
|
|
1244
|
-
function
|
|
1364
|
+
function formatError4(error2) {
|
|
1245
1365
|
if (error2 instanceof Error) {
|
|
1246
1366
|
return error2.message;
|
|
1247
1367
|
}
|
|
@@ -1267,7 +1387,7 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
1267
1387
|
|
|
1268
1388
|
// src/core/validators/delta.ts
|
|
1269
1389
|
var import_promises9 = require("fs/promises");
|
|
1270
|
-
var
|
|
1390
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1271
1391
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1272
1392
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1273
1393
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1281,7 +1401,7 @@ async function validateDeltas(root, config) {
|
|
|
1281
1401
|
}
|
|
1282
1402
|
const issues = [];
|
|
1283
1403
|
for (const pack of packs) {
|
|
1284
|
-
const deltaPath =
|
|
1404
|
+
const deltaPath = import_node_path11.default.join(pack, "delta.md");
|
|
1285
1405
|
let text;
|
|
1286
1406
|
try {
|
|
1287
1407
|
text = await (0, import_promises9.readFile)(deltaPath, "utf-8");
|
|
@@ -1357,16 +1477,16 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
1357
1477
|
|
|
1358
1478
|
// src/core/validators/ids.ts
|
|
1359
1479
|
var import_promises11 = require("fs/promises");
|
|
1360
|
-
var
|
|
1480
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1361
1481
|
|
|
1362
1482
|
// src/core/contractIndex.ts
|
|
1363
1483
|
var import_promises10 = require("fs/promises");
|
|
1364
|
-
var
|
|
1484
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1365
1485
|
async function buildContractIndex(root, config) {
|
|
1366
1486
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1367
|
-
const uiRoot =
|
|
1368
|
-
const apiRoot =
|
|
1369
|
-
const dataRoot =
|
|
1487
|
+
const uiRoot = import_node_path12.default.join(contractsRoot, "ui");
|
|
1488
|
+
const apiRoot = import_node_path12.default.join(contractsRoot, "api");
|
|
1489
|
+
const dataRoot = import_node_path12.default.join(contractsRoot, "db");
|
|
1370
1490
|
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1371
1491
|
collectUiContractFiles(uiRoot),
|
|
1372
1492
|
collectApiContractFiles(apiRoot),
|
|
@@ -1604,7 +1724,7 @@ function recordId(out, id, file) {
|
|
|
1604
1724
|
}
|
|
1605
1725
|
function formatFileList(files, root) {
|
|
1606
1726
|
return files.map((file) => {
|
|
1607
|
-
const relative =
|
|
1727
|
+
const relative = import_node_path13.default.relative(root, file);
|
|
1608
1728
|
return relative.length > 0 ? relative : file;
|
|
1609
1729
|
}).join(", ");
|
|
1610
1730
|
}
|
|
@@ -1633,7 +1753,6 @@ var WHEN_PATTERN = /\bWhen\b/;
|
|
|
1633
1753
|
var THEN_PATTERN = /\bThen\b/;
|
|
1634
1754
|
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
1635
1755
|
var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
|
|
1636
|
-
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1637
1756
|
async function validateScenarios(root, config) {
|
|
1638
1757
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1639
1758
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1713,17 +1832,7 @@ function validateScenarioContent(text, file) {
|
|
|
1713
1832
|
const featureSpecTags = document.featureTags.filter(
|
|
1714
1833
|
(tag) => SPEC_TAG_RE2.test(tag)
|
|
1715
1834
|
);
|
|
1716
|
-
if (featureSpecTags.length
|
|
1717
|
-
issues.push(
|
|
1718
|
-
issue4(
|
|
1719
|
-
"QFAI-SC-009",
|
|
1720
|
-
"Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1721
|
-
"error",
|
|
1722
|
-
file,
|
|
1723
|
-
"scenario.featureSpec"
|
|
1724
|
-
)
|
|
1725
|
-
);
|
|
1726
|
-
} else if (featureSpecTags.length > 1) {
|
|
1835
|
+
if (featureSpecTags.length > 1) {
|
|
1727
1836
|
issues.push(
|
|
1728
1837
|
issue4(
|
|
1729
1838
|
"QFAI-SC-009",
|
|
@@ -1751,17 +1860,6 @@ function validateScenarioContent(text, file) {
|
|
|
1751
1860
|
)
|
|
1752
1861
|
);
|
|
1753
1862
|
}
|
|
1754
|
-
if (document.scenarios.length > 1) {
|
|
1755
|
-
issues.push(
|
|
1756
|
-
issue4(
|
|
1757
|
-
"QFAI-SC-011",
|
|
1758
|
-
`Scenario \u306F1\u3064\u306E\u307F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u691C\u51FA: ${document.scenarios.length}\u4EF6\uFF09`,
|
|
1759
|
-
"error",
|
|
1760
|
-
file,
|
|
1761
|
-
"scenario.single"
|
|
1762
|
-
)
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
1863
|
for (const scenario of document.scenarios) {
|
|
1766
1864
|
if (scenario.tags.length === 0) {
|
|
1767
1865
|
issues.push(
|
|
@@ -1782,12 +1880,6 @@ function validateScenarioContent(text, file) {
|
|
|
1782
1880
|
} else if (scTags.length > 1) {
|
|
1783
1881
|
missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
|
|
1784
1882
|
}
|
|
1785
|
-
if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
|
|
1786
|
-
missingTags.push("SPEC");
|
|
1787
|
-
}
|
|
1788
|
-
if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
|
|
1789
|
-
missingTags.push("BR");
|
|
1790
|
-
}
|
|
1791
1883
|
if (missingTags.length > 0) {
|
|
1792
1884
|
issues.push(
|
|
1793
1885
|
issue4(
|
|
@@ -2022,9 +2114,8 @@ function isMissingFileError4(error2) {
|
|
|
2022
2114
|
|
|
2023
2115
|
// src/core/validators/traceability.ts
|
|
2024
2116
|
var import_promises14 = require("fs/promises");
|
|
2025
|
-
var SC_TAG_RE5 = /^SC-\d{4}$/;
|
|
2026
2117
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2027
|
-
var
|
|
2118
|
+
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2028
2119
|
async function validateTraceability(root, config) {
|
|
2029
2120
|
const issues = [];
|
|
2030
2121
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -2051,28 +2142,6 @@ async function validateTraceability(root, config) {
|
|
|
2051
2142
|
}
|
|
2052
2143
|
const brIds = parsed.brs.map((br) => br.id);
|
|
2053
2144
|
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
2054
|
-
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
2055
|
-
...extractIds(text, "UI"),
|
|
2056
|
-
...extractIds(text, "API"),
|
|
2057
|
-
...extractIds(text, "DATA")
|
|
2058
|
-
]);
|
|
2059
|
-
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
2060
|
-
(id) => !contractIds.has(id)
|
|
2061
|
-
);
|
|
2062
|
-
if (unknownContractIds.length > 0) {
|
|
2063
|
-
issues.push(
|
|
2064
|
-
issue6(
|
|
2065
|
-
"QFAI-TRACE-009",
|
|
2066
|
-
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
2067
|
-
", "
|
|
2068
|
-
)}`,
|
|
2069
|
-
"error",
|
|
2070
|
-
file,
|
|
2071
|
-
"traceability.specContractExists",
|
|
2072
|
-
unknownContractIds
|
|
2073
|
-
)
|
|
2074
|
-
);
|
|
2075
|
-
}
|
|
2076
2145
|
if (parsed.specId) {
|
|
2077
2146
|
const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
|
|
2078
2147
|
brIds.forEach((id) => current.add(id));
|
|
@@ -2087,16 +2156,42 @@ async function validateTraceability(root, config) {
|
|
|
2087
2156
|
continue;
|
|
2088
2157
|
}
|
|
2089
2158
|
const atoms = buildScenarioAtoms(document);
|
|
2159
|
+
const scIdsInFile = /* @__PURE__ */ new Set();
|
|
2090
2160
|
for (const [index, scenario] of document.scenarios.entries()) {
|
|
2091
2161
|
const atom = atoms[index];
|
|
2092
2162
|
if (!atom) {
|
|
2093
2163
|
continue;
|
|
2094
2164
|
}
|
|
2095
2165
|
const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
|
|
2096
|
-
const brTags = scenario.tags.filter((tag) =>
|
|
2097
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
2166
|
+
const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
|
|
2167
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
|
|
2168
|
+
if (specTags.length === 0) {
|
|
2169
|
+
issues.push(
|
|
2170
|
+
issue6(
|
|
2171
|
+
"QFAI-TRACE-014",
|
|
2172
|
+
`Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2173
|
+
"error",
|
|
2174
|
+
file,
|
|
2175
|
+
"traceability.scenarioSpecRequired"
|
|
2176
|
+
)
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
if (brTags.length === 0) {
|
|
2180
|
+
issues.push(
|
|
2181
|
+
issue6(
|
|
2182
|
+
"QFAI-TRACE-015",
|
|
2183
|
+
`Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2184
|
+
"error",
|
|
2185
|
+
file,
|
|
2186
|
+
"traceability.scenarioBrRequired"
|
|
2187
|
+
)
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2098
2190
|
brTags.forEach((id) => brIdsInScenarios.add(id));
|
|
2099
|
-
scTags.forEach((id) =>
|
|
2191
|
+
scTags.forEach((id) => {
|
|
2192
|
+
scIdsInScenarios.add(id);
|
|
2193
|
+
scIdsInFile.add(id);
|
|
2194
|
+
});
|
|
2100
2195
|
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
2101
2196
|
if (atom.contractIds.length > 0) {
|
|
2102
2197
|
scTags.forEach((id) => scWithContracts.add(id));
|
|
@@ -2174,6 +2269,22 @@ async function validateTraceability(root, config) {
|
|
|
2174
2269
|
}
|
|
2175
2270
|
}
|
|
2176
2271
|
}
|
|
2272
|
+
if (scIdsInFile.size !== 1) {
|
|
2273
|
+
const invalidScIds = Array.from(scIdsInFile).sort(
|
|
2274
|
+
(a, b) => a.localeCompare(b)
|
|
2275
|
+
);
|
|
2276
|
+
const detail = invalidScIds.length === 0 ? "SC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" : `\u8907\u6570\u306E SC \u304C\u5B58\u5728\u3057\u307E\u3059: ${invalidScIds.join(", ")}`;
|
|
2277
|
+
issues.push(
|
|
2278
|
+
issue6(
|
|
2279
|
+
"QFAI-TRACE-012",
|
|
2280
|
+
`Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
|
|
2281
|
+
"error",
|
|
2282
|
+
file,
|
|
2283
|
+
"traceability.specScOneToOne",
|
|
2284
|
+
invalidScIds
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2177
2288
|
}
|
|
2178
2289
|
if (upstreamIds.size === 0) {
|
|
2179
2290
|
return [
|
|
@@ -2222,21 +2333,62 @@ async function validateTraceability(root, config) {
|
|
|
2222
2333
|
);
|
|
2223
2334
|
}
|
|
2224
2335
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2336
|
+
const scRefsResult = await collectScTestReferences(
|
|
2337
|
+
root,
|
|
2338
|
+
config.validation.traceability.testFileGlobs,
|
|
2339
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2340
|
+
);
|
|
2341
|
+
const scTestRefs = scRefsResult.refs;
|
|
2342
|
+
const testFileScan = scRefsResult.scan;
|
|
2343
|
+
const hasScenarios = scIdsInScenarios.size > 0;
|
|
2344
|
+
const hasGlobConfig = testFileScan.globs.length > 0;
|
|
2345
|
+
const hasMatchedTests = testFileScan.matchedFileCount > 0;
|
|
2346
|
+
if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
|
|
2347
|
+
const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
|
|
2348
|
+
issues.push(
|
|
2349
|
+
issue6(
|
|
2350
|
+
"QFAI-TRACE-013",
|
|
2351
|
+
`\u30C6\u30B9\u30C8\u63A2\u7D22 glob \u304C\u672A\u8A2D\u5B9A/\u4E0D\u6B63/\u4E00\u81F4\u30D5\u30A1\u30A4\u30EB0\u306E\u305F\u3081 SC\u2192Test \u3092\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093\u3002${detail}`,
|
|
2352
|
+
"error",
|
|
2353
|
+
testsRoot,
|
|
2354
|
+
"traceability.testFileGlobs"
|
|
2355
|
+
)
|
|
2356
|
+
);
|
|
2357
|
+
} else {
|
|
2358
|
+
if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
|
|
2359
|
+
const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
|
|
2360
|
+
const refs = scTestRefs.get(id);
|
|
2361
|
+
return !refs || refs.size === 0;
|
|
2362
|
+
});
|
|
2363
|
+
if (scWithoutTests.length > 0) {
|
|
2364
|
+
issues.push(
|
|
2365
|
+
issue6(
|
|
2366
|
+
"QFAI-TRACE-010",
|
|
2367
|
+
`SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
|
|
2368
|
+
", "
|
|
2369
|
+
)}\u3002testFileGlobs \u306B\u4E00\u81F4\u3059\u308B\u30C6\u30B9\u30C8\u30D5\u30A1\u30A4\u30EB\u3078 QFAI:SC-xxxx \u3092\u8A18\u8F09\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
2370
|
+
config.validation.traceability.scNoTestSeverity,
|
|
2371
|
+
testsRoot,
|
|
2372
|
+
"traceability.scMustHaveTest",
|
|
2373
|
+
scWithoutTests
|
|
2374
|
+
)
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
const unknownScIds = Array.from(scTestRefs.keys()).filter(
|
|
2379
|
+
(id) => !scIdsInScenarios.has(id)
|
|
2380
|
+
);
|
|
2381
|
+
if (unknownScIds.length > 0) {
|
|
2232
2382
|
issues.push(
|
|
2233
2383
|
issue6(
|
|
2234
|
-
"QFAI-TRACE-
|
|
2235
|
-
|
|
2236
|
-
|
|
2384
|
+
"QFAI-TRACE-011",
|
|
2385
|
+
`\u30C6\u30B9\u30C8\u304C\u672A\u77E5\u306E SC \u3092\u30A2\u30CE\u30C6\u30FC\u30B7\u30E7\u30F3\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownScIds.join(
|
|
2386
|
+
", "
|
|
2387
|
+
)}`,
|
|
2388
|
+
"error",
|
|
2237
2389
|
testsRoot,
|
|
2238
|
-
"traceability.
|
|
2239
|
-
|
|
2390
|
+
"traceability.scUnknownInTests",
|
|
2391
|
+
unknownScIds
|
|
2240
2392
|
)
|
|
2241
2393
|
);
|
|
2242
2394
|
}
|
|
@@ -2299,8 +2451,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2299
2451
|
issues.push(
|
|
2300
2452
|
issue6(
|
|
2301
2453
|
"QFAI-TRACE-002",
|
|
2302
|
-
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
2303
|
-
"
|
|
2454
|
+
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\uFF08\u53C2\u8003\u60C5\u5831\uFF09\u3002",
|
|
2455
|
+
"info",
|
|
2304
2456
|
srcRoot,
|
|
2305
2457
|
"traceability.codeReferences"
|
|
2306
2458
|
)
|
|
@@ -2343,11 +2495,24 @@ async function validateProject(root, configResult) {
|
|
|
2343
2495
|
...await validateDefinedIds(root, config),
|
|
2344
2496
|
...await validateTraceability(root, config)
|
|
2345
2497
|
];
|
|
2498
|
+
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2499
|
+
const scenarioFiles = await collectScenarioFiles(specsRoot);
|
|
2500
|
+
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2501
|
+
const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
|
|
2502
|
+
root,
|
|
2503
|
+
config.validation.traceability.testFileGlobs,
|
|
2504
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2505
|
+
);
|
|
2506
|
+
const scCoverage = buildScCoverage(scIds, scTestRefs);
|
|
2346
2507
|
const toolVersion = await resolveToolVersion();
|
|
2347
2508
|
return {
|
|
2348
2509
|
toolVersion,
|
|
2349
2510
|
issues,
|
|
2350
|
-
counts: countIssues(issues)
|
|
2511
|
+
counts: countIssues(issues),
|
|
2512
|
+
traceability: {
|
|
2513
|
+
sc: scCoverage,
|
|
2514
|
+
testFiles
|
|
2515
|
+
}
|
|
2351
2516
|
};
|
|
2352
2517
|
}
|
|
2353
2518
|
function countIssues(issues) {
|
|
@@ -2368,9 +2533,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2368
2533
|
const configPath = resolved.configPath;
|
|
2369
2534
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2370
2535
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2371
|
-
const apiRoot =
|
|
2372
|
-
const uiRoot =
|
|
2373
|
-
const dbRoot =
|
|
2536
|
+
const apiRoot = import_node_path14.default.join(contractsRoot, "api");
|
|
2537
|
+
const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
|
|
2538
|
+
const dbRoot = import_node_path14.default.join(contractsRoot, "db");
|
|
2374
2539
|
const srcRoot = resolvePath(root, config, "srcDir");
|
|
2375
2540
|
const testsRoot = resolvePath(root, config, "testsDir");
|
|
2376
2541
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2397,8 +2562,15 @@ async function createReportData(root, validation, configResult) {
|
|
|
2397
2562
|
testsRoot
|
|
2398
2563
|
);
|
|
2399
2564
|
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2400
|
-
const
|
|
2401
|
-
|
|
2565
|
+
const scRefsResult = await collectScTestReferences(
|
|
2566
|
+
root,
|
|
2567
|
+
config.validation.traceability.testFileGlobs,
|
|
2568
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2569
|
+
);
|
|
2570
|
+
const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
|
|
2571
|
+
const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
|
|
2572
|
+
const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
|
|
2573
|
+
const scSourceRecord = mapToSortedRecord(scSources);
|
|
2402
2574
|
const resolvedValidation = validation ?? await validateProject(root, resolved);
|
|
2403
2575
|
const version = await resolveToolVersion();
|
|
2404
2576
|
return {
|
|
@@ -2428,7 +2600,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2428
2600
|
traceability: {
|
|
2429
2601
|
upstreamIdsFound: upstreamIds.size,
|
|
2430
2602
|
referencedInCodeOrTests: traceability,
|
|
2431
|
-
sc: scCoverage
|
|
2603
|
+
sc: scCoverage,
|
|
2604
|
+
scSources: scSourceRecord,
|
|
2605
|
+
testFiles
|
|
2432
2606
|
},
|
|
2433
2607
|
issues: resolvedValidation.issues
|
|
2434
2608
|
};
|
|
@@ -2469,10 +2643,29 @@ function formatReportMarkdown(data) {
|
|
|
2469
2643
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2470
2644
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
2471
2645
|
lines.push(`- missing: ${data.traceability.sc.missing}`);
|
|
2646
|
+
lines.push(
|
|
2647
|
+
`- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
|
|
2648
|
+
);
|
|
2649
|
+
lines.push(
|
|
2650
|
+
`- testFileExcludeGlobs: ${formatList(
|
|
2651
|
+
data.traceability.testFiles.excludeGlobs
|
|
2652
|
+
)}`
|
|
2653
|
+
);
|
|
2654
|
+
lines.push(
|
|
2655
|
+
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
2656
|
+
);
|
|
2472
2657
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
2473
2658
|
lines.push("- missingIds: (none)");
|
|
2474
2659
|
} else {
|
|
2475
|
-
|
|
2660
|
+
const sources = data.traceability.scSources;
|
|
2661
|
+
const missingWithSources = data.traceability.sc.missingIds.map((id) => {
|
|
2662
|
+
const files = sources[id] ?? [];
|
|
2663
|
+
if (files.length === 0) {
|
|
2664
|
+
return id;
|
|
2665
|
+
}
|
|
2666
|
+
return `${id} (${files.join(", ")})`;
|
|
2667
|
+
});
|
|
2668
|
+
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2476
2669
|
}
|
|
2477
2670
|
lines.push("");
|
|
2478
2671
|
lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
|
|
@@ -2491,6 +2684,20 @@ function formatReportMarkdown(data) {
|
|
|
2491
2684
|
}
|
|
2492
2685
|
}
|
|
2493
2686
|
lines.push("");
|
|
2687
|
+
lines.push("## Spec:SC=1:1 \u9055\u53CD");
|
|
2688
|
+
const specScIssues = data.issues.filter(
|
|
2689
|
+
(item) => item.code === "QFAI-TRACE-012"
|
|
2690
|
+
);
|
|
2691
|
+
if (specScIssues.length === 0) {
|
|
2692
|
+
lines.push("- (none)");
|
|
2693
|
+
} else {
|
|
2694
|
+
for (const item of specScIssues) {
|
|
2695
|
+
const location = item.file ?? "(unknown)";
|
|
2696
|
+
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
2697
|
+
lines.push(`- ${location}: ${refs}`);
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
lines.push("");
|
|
2494
2701
|
lines.push("## Hotspots");
|
|
2495
2702
|
const hotspots = buildHotspots(data.issues);
|
|
2496
2703
|
if (hotspots.length === 0) {
|
|
@@ -2601,9 +2808,22 @@ function formatIdLine(label, values) {
|
|
|
2601
2808
|
}
|
|
2602
2809
|
return `- ${label}: ${values.join(", ")}`;
|
|
2603
2810
|
}
|
|
2811
|
+
function formatList(values) {
|
|
2812
|
+
if (values.length === 0) {
|
|
2813
|
+
return "(none)";
|
|
2814
|
+
}
|
|
2815
|
+
return values.join(", ");
|
|
2816
|
+
}
|
|
2604
2817
|
function toSortedArray2(values) {
|
|
2605
2818
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
2606
2819
|
}
|
|
2820
|
+
function mapToSortedRecord(values) {
|
|
2821
|
+
const record2 = {};
|
|
2822
|
+
for (const [key, files] of values.entries()) {
|
|
2823
|
+
record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
2824
|
+
}
|
|
2825
|
+
return record2;
|
|
2826
|
+
}
|
|
2607
2827
|
function buildHotspots(issues) {
|
|
2608
2828
|
const map = /* @__PURE__ */ new Map();
|
|
2609
2829
|
for (const issue7 of issues) {
|
|
@@ -2628,10 +2848,10 @@ function buildHotspots(issues) {
|
|
|
2628
2848
|
|
|
2629
2849
|
// src/cli/commands/report.ts
|
|
2630
2850
|
async function runReport(options) {
|
|
2631
|
-
const root =
|
|
2851
|
+
const root = import_node_path15.default.resolve(options.root);
|
|
2632
2852
|
const configResult = await loadConfig(root);
|
|
2633
2853
|
const input = configResult.config.output.validateJsonPath;
|
|
2634
|
-
const inputPath =
|
|
2854
|
+
const inputPath = import_node_path15.default.isAbsolute(input) ? input : import_node_path15.default.resolve(root, input);
|
|
2635
2855
|
let validation;
|
|
2636
2856
|
try {
|
|
2637
2857
|
validation = await readValidationResult(inputPath);
|
|
@@ -2656,10 +2876,10 @@ async function runReport(options) {
|
|
|
2656
2876
|
const data = await createReportData(root, validation, configResult);
|
|
2657
2877
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
2658
2878
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
2659
|
-
const defaultOut = options.format === "json" ?
|
|
2879
|
+
const defaultOut = options.format === "json" ? import_node_path15.default.join(outRoot, "report.json") : import_node_path15.default.join(outRoot, "report.md");
|
|
2660
2880
|
const out = options.outPath ?? defaultOut;
|
|
2661
|
-
const outPath =
|
|
2662
|
-
await (0, import_promises16.mkdir)(
|
|
2881
|
+
const outPath = import_node_path15.default.isAbsolute(out) ? out : import_node_path15.default.resolve(root, out);
|
|
2882
|
+
await (0, import_promises16.mkdir)(import_node_path15.default.dirname(outPath), { recursive: true });
|
|
2663
2883
|
await (0, import_promises16.writeFile)(outPath, `${output}
|
|
2664
2884
|
`, "utf-8");
|
|
2665
2885
|
info(
|
|
@@ -2702,7 +2922,7 @@ function isMissingFileError5(error2) {
|
|
|
2702
2922
|
|
|
2703
2923
|
// src/cli/commands/validate.ts
|
|
2704
2924
|
var import_promises17 = require("fs/promises");
|
|
2705
|
-
var
|
|
2925
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2706
2926
|
|
|
2707
2927
|
// src/cli/lib/failOn.ts
|
|
2708
2928
|
function shouldFail(result, failOn) {
|
|
@@ -2717,7 +2937,7 @@ function shouldFail(result, failOn) {
|
|
|
2717
2937
|
|
|
2718
2938
|
// src/cli/commands/validate.ts
|
|
2719
2939
|
async function runValidate(options) {
|
|
2720
|
-
const root =
|
|
2940
|
+
const root = import_node_path16.default.resolve(options.root);
|
|
2721
2941
|
const configResult = await loadConfig(root);
|
|
2722
2942
|
const result = await validateProject(root, configResult);
|
|
2723
2943
|
const format = options.format ?? "text";
|
|
@@ -2766,8 +2986,8 @@ function emitGitHub(issue7) {
|
|
|
2766
2986
|
);
|
|
2767
2987
|
}
|
|
2768
2988
|
async function emitJson(result, root, jsonPath) {
|
|
2769
|
-
const abs =
|
|
2770
|
-
await (0, import_promises17.mkdir)(
|
|
2989
|
+
const abs = import_node_path16.default.isAbsolute(jsonPath) ? jsonPath : import_node_path16.default.resolve(root, jsonPath);
|
|
2990
|
+
await (0, import_promises17.mkdir)(import_node_path16.default.dirname(abs), { recursive: true });
|
|
2771
2991
|
await (0, import_promises17.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
2772
2992
|
`, "utf-8");
|
|
2773
2993
|
}
|