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/index.cjs
CHANGED
|
@@ -85,6 +85,8 @@ var defaultConfig = {
|
|
|
85
85
|
brMustHaveSc: true,
|
|
86
86
|
scMustTouchContracts: true,
|
|
87
87
|
scMustHaveTest: true,
|
|
88
|
+
testFileGlobs: [],
|
|
89
|
+
testFileExcludeGlobs: [],
|
|
88
90
|
scNoTestSeverity: "error",
|
|
89
91
|
allowOrphanContracts: false,
|
|
90
92
|
unknownContractIdSeverity: "error"
|
|
@@ -272,6 +274,20 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
272
274
|
configPath,
|
|
273
275
|
issues
|
|
274
276
|
),
|
|
277
|
+
testFileGlobs: readStringArray(
|
|
278
|
+
traceabilityRaw?.testFileGlobs,
|
|
279
|
+
base.traceability.testFileGlobs,
|
|
280
|
+
"validation.traceability.testFileGlobs",
|
|
281
|
+
configPath,
|
|
282
|
+
issues
|
|
283
|
+
),
|
|
284
|
+
testFileExcludeGlobs: readStringArray(
|
|
285
|
+
traceabilityRaw?.testFileExcludeGlobs,
|
|
286
|
+
base.traceability.testFileExcludeGlobs,
|
|
287
|
+
"validation.traceability.testFileExcludeGlobs",
|
|
288
|
+
configPath,
|
|
289
|
+
issues
|
|
290
|
+
),
|
|
275
291
|
scNoTestSeverity: readTraceabilitySeverity(
|
|
276
292
|
traceabilityRaw?.scNoTestSeverity,
|
|
277
293
|
base.traceability.scNoTestSeverity,
|
|
@@ -458,7 +474,7 @@ function isValidId(value, prefix) {
|
|
|
458
474
|
|
|
459
475
|
// src/core/report.ts
|
|
460
476
|
var import_promises14 = require("fs/promises");
|
|
461
|
-
var
|
|
477
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
462
478
|
|
|
463
479
|
// src/core/discovery.ts
|
|
464
480
|
var import_promises4 = require("fs/promises");
|
|
@@ -466,6 +482,7 @@ var import_promises4 = require("fs/promises");
|
|
|
466
482
|
// src/core/fs.ts
|
|
467
483
|
var import_promises2 = require("fs/promises");
|
|
468
484
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
485
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
469
486
|
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
470
487
|
"node_modules",
|
|
471
488
|
".git",
|
|
@@ -487,6 +504,18 @@ async function collectFiles(root, options = {}) {
|
|
|
487
504
|
await walk(root, root, ignoreDirs, extensions, entries);
|
|
488
505
|
return entries;
|
|
489
506
|
}
|
|
507
|
+
async function collectFilesByGlobs(root, options) {
|
|
508
|
+
if (options.globs.length === 0) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
return (0, import_fast_glob.default)(options.globs, {
|
|
512
|
+
cwd: root,
|
|
513
|
+
ignore: options.ignore ?? [],
|
|
514
|
+
onlyFiles: true,
|
|
515
|
+
absolute: true,
|
|
516
|
+
unique: true
|
|
517
|
+
});
|
|
518
|
+
}
|
|
490
519
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
491
520
|
const items = await (0, import_promises2.readdir)(current, { withFileTypes: true });
|
|
492
521
|
for (const item of items) {
|
|
@@ -600,6 +629,7 @@ async function exists2(target) {
|
|
|
600
629
|
|
|
601
630
|
// src/core/traceability.ts
|
|
602
631
|
var import_promises5 = require("fs/promises");
|
|
632
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
603
633
|
|
|
604
634
|
// src/core/gherkin/parse.ts
|
|
605
635
|
var import_gherkin = require("@cucumber/gherkin");
|
|
@@ -757,6 +787,27 @@ function unique2(values) {
|
|
|
757
787
|
|
|
758
788
|
// src/core/traceability.ts
|
|
759
789
|
var SC_TAG_RE2 = /^SC-\d{4}$/;
|
|
790
|
+
var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
|
|
791
|
+
var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
|
|
792
|
+
"**/node_modules/**",
|
|
793
|
+
"**/.git/**",
|
|
794
|
+
"**/.qfai/**",
|
|
795
|
+
"**/dist/**",
|
|
796
|
+
"**/build/**",
|
|
797
|
+
"**/coverage/**",
|
|
798
|
+
"**/.next/**",
|
|
799
|
+
"**/out/**"
|
|
800
|
+
];
|
|
801
|
+
function extractAnnotatedScIds(text) {
|
|
802
|
+
const ids = /* @__PURE__ */ new Set();
|
|
803
|
+
for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
|
|
804
|
+
const suffix = match[1];
|
|
805
|
+
if (suffix) {
|
|
806
|
+
ids.add(`SC-${suffix}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return Array.from(ids);
|
|
810
|
+
}
|
|
760
811
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
761
812
|
const scIds = /* @__PURE__ */ new Set();
|
|
762
813
|
for (const file of scenarioFiles) {
|
|
@@ -775,14 +826,67 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
775
826
|
}
|
|
776
827
|
return scIds;
|
|
777
828
|
}
|
|
778
|
-
async function
|
|
829
|
+
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
830
|
+
const sources = /* @__PURE__ */ new Map();
|
|
831
|
+
for (const file of scenarioFiles) {
|
|
832
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
833
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
834
|
+
if (!document || errors.length > 0) {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
for (const scenario of document.scenarios) {
|
|
838
|
+
for (const tag of scenario.tags) {
|
|
839
|
+
if (!SC_TAG_RE2.test(tag)) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
|
|
843
|
+
current.add(file);
|
|
844
|
+
sources.set(tag, current);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return sources;
|
|
849
|
+
}
|
|
850
|
+
async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
779
851
|
const refs = /* @__PURE__ */ new Map();
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
852
|
+
const normalizedGlobs = normalizeGlobs(globs);
|
|
853
|
+
const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
|
|
854
|
+
const mergedExcludeGlobs = Array.from(
|
|
855
|
+
/* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
|
|
856
|
+
);
|
|
857
|
+
if (normalizedGlobs.length === 0) {
|
|
858
|
+
return {
|
|
859
|
+
refs,
|
|
860
|
+
scan: {
|
|
861
|
+
globs: normalizedGlobs,
|
|
862
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
863
|
+
matchedFileCount: 0
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
let files = [];
|
|
868
|
+
try {
|
|
869
|
+
files = await collectFilesByGlobs(root, {
|
|
870
|
+
globs: normalizedGlobs,
|
|
871
|
+
ignore: mergedExcludeGlobs
|
|
872
|
+
});
|
|
873
|
+
} catch (error) {
|
|
874
|
+
return {
|
|
875
|
+
refs,
|
|
876
|
+
scan: {
|
|
877
|
+
globs: normalizedGlobs,
|
|
878
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
879
|
+
matchedFileCount: 0
|
|
880
|
+
},
|
|
881
|
+
error: formatError3(error)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const normalizedFiles = Array.from(
|
|
885
|
+
new Set(files.map((file) => import_node_path4.default.normalize(file)))
|
|
886
|
+
);
|
|
887
|
+
for (const file of normalizedFiles) {
|
|
784
888
|
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
785
|
-
const scIds =
|
|
889
|
+
const scIds = extractAnnotatedScIds(text);
|
|
786
890
|
if (scIds.length === 0) {
|
|
787
891
|
continue;
|
|
788
892
|
}
|
|
@@ -792,7 +896,14 @@ async function collectScTestReferences(testsRoot) {
|
|
|
792
896
|
refs.set(scId, current);
|
|
793
897
|
}
|
|
794
898
|
}
|
|
795
|
-
return
|
|
899
|
+
return {
|
|
900
|
+
refs,
|
|
901
|
+
scan: {
|
|
902
|
+
globs: normalizedGlobs,
|
|
903
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
904
|
+
matchedFileCount: normalizedFiles.length
|
|
905
|
+
}
|
|
906
|
+
};
|
|
796
907
|
}
|
|
797
908
|
function buildScCoverage(scIds, refs) {
|
|
798
909
|
const sortedScIds = toSortedArray(scIds);
|
|
@@ -820,14 +931,23 @@ function buildScCoverage(scIds, refs) {
|
|
|
820
931
|
function toSortedArray(values) {
|
|
821
932
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
822
933
|
}
|
|
934
|
+
function normalizeGlobs(globs) {
|
|
935
|
+
return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
936
|
+
}
|
|
937
|
+
function formatError3(error) {
|
|
938
|
+
if (error instanceof Error) {
|
|
939
|
+
return error.message;
|
|
940
|
+
}
|
|
941
|
+
return String(error);
|
|
942
|
+
}
|
|
823
943
|
|
|
824
944
|
// src/core/version.ts
|
|
825
945
|
var import_promises6 = require("fs/promises");
|
|
826
|
-
var
|
|
946
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
827
947
|
var import_node_url = require("url");
|
|
828
948
|
async function resolveToolVersion() {
|
|
829
|
-
if ("0.4.
|
|
830
|
-
return "0.4.
|
|
949
|
+
if ("0.4.2".length > 0) {
|
|
950
|
+
return "0.4.2";
|
|
831
951
|
}
|
|
832
952
|
try {
|
|
833
953
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -842,18 +962,18 @@ async function resolveToolVersion() {
|
|
|
842
962
|
function resolvePackageJsonPath() {
|
|
843
963
|
const base = __filename;
|
|
844
964
|
const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
|
|
845
|
-
return
|
|
965
|
+
return import_node_path5.default.resolve(import_node_path5.default.dirname(basePath), "../../package.json");
|
|
846
966
|
}
|
|
847
967
|
|
|
848
968
|
// src/core/validators/contracts.ts
|
|
849
969
|
var import_promises7 = require("fs/promises");
|
|
850
|
-
var
|
|
970
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
851
971
|
|
|
852
972
|
// src/core/contracts.ts
|
|
853
|
-
var
|
|
973
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
854
974
|
var import_yaml2 = require("yaml");
|
|
855
975
|
function parseStructuredContract(file, text) {
|
|
856
|
-
const ext =
|
|
976
|
+
const ext = import_node_path6.default.extname(file).toLowerCase();
|
|
857
977
|
if (ext === ".json") {
|
|
858
978
|
return JSON.parse(text);
|
|
859
979
|
}
|
|
@@ -904,9 +1024,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
904
1024
|
async function validateContracts(root, config) {
|
|
905
1025
|
const issues = [];
|
|
906
1026
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
907
|
-
issues.push(...await validateUiContracts(
|
|
908
|
-
issues.push(...await validateApiContracts(
|
|
909
|
-
issues.push(...await validateDataContracts(
|
|
1027
|
+
issues.push(...await validateUiContracts(import_node_path7.default.join(contractsRoot, "ui")));
|
|
1028
|
+
issues.push(...await validateApiContracts(import_node_path7.default.join(contractsRoot, "api")));
|
|
1029
|
+
issues.push(...await validateDataContracts(import_node_path7.default.join(contractsRoot, "db")));
|
|
910
1030
|
return issues;
|
|
911
1031
|
}
|
|
912
1032
|
async function validateUiContracts(uiRoot) {
|
|
@@ -953,7 +1073,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
953
1073
|
issues.push(
|
|
954
1074
|
issue(
|
|
955
1075
|
"QFAI-CONTRACT-001",
|
|
956
|
-
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1076
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
957
1077
|
"error",
|
|
958
1078
|
file,
|
|
959
1079
|
"contracts.ui.parse"
|
|
@@ -1020,7 +1140,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1020
1140
|
issues.push(
|
|
1021
1141
|
issue(
|
|
1022
1142
|
"QFAI-CONTRACT-001",
|
|
1023
|
-
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1143
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
1024
1144
|
"error",
|
|
1025
1145
|
file,
|
|
1026
1146
|
"contracts.api.parse"
|
|
@@ -1115,7 +1235,7 @@ function lintSql(text, file) {
|
|
|
1115
1235
|
function hasOpenApi(doc) {
|
|
1116
1236
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1117
1237
|
}
|
|
1118
|
-
function
|
|
1238
|
+
function formatError4(error) {
|
|
1119
1239
|
if (error instanceof Error) {
|
|
1120
1240
|
return error.message;
|
|
1121
1241
|
}
|
|
@@ -1141,7 +1261,7 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
1141
1261
|
|
|
1142
1262
|
// src/core/validators/delta.ts
|
|
1143
1263
|
var import_promises8 = require("fs/promises");
|
|
1144
|
-
var
|
|
1264
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1145
1265
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1146
1266
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1147
1267
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1155,7 +1275,7 @@ async function validateDeltas(root, config) {
|
|
|
1155
1275
|
}
|
|
1156
1276
|
const issues = [];
|
|
1157
1277
|
for (const pack of packs) {
|
|
1158
|
-
const deltaPath =
|
|
1278
|
+
const deltaPath = import_node_path8.default.join(pack, "delta.md");
|
|
1159
1279
|
let text;
|
|
1160
1280
|
try {
|
|
1161
1281
|
text = await (0, import_promises8.readFile)(deltaPath, "utf-8");
|
|
@@ -1231,16 +1351,16 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
1231
1351
|
|
|
1232
1352
|
// src/core/validators/ids.ts
|
|
1233
1353
|
var import_promises10 = require("fs/promises");
|
|
1234
|
-
var
|
|
1354
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1235
1355
|
|
|
1236
1356
|
// src/core/contractIndex.ts
|
|
1237
1357
|
var import_promises9 = require("fs/promises");
|
|
1238
|
-
var
|
|
1358
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1239
1359
|
async function buildContractIndex(root, config) {
|
|
1240
1360
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1241
|
-
const uiRoot =
|
|
1242
|
-
const apiRoot =
|
|
1243
|
-
const dataRoot =
|
|
1361
|
+
const uiRoot = import_node_path9.default.join(contractsRoot, "ui");
|
|
1362
|
+
const apiRoot = import_node_path9.default.join(contractsRoot, "api");
|
|
1363
|
+
const dataRoot = import_node_path9.default.join(contractsRoot, "db");
|
|
1244
1364
|
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1245
1365
|
collectUiContractFiles(uiRoot),
|
|
1246
1366
|
collectApiContractFiles(apiRoot),
|
|
@@ -1478,7 +1598,7 @@ function recordId(out, id, file) {
|
|
|
1478
1598
|
}
|
|
1479
1599
|
function formatFileList(files, root) {
|
|
1480
1600
|
return files.map((file) => {
|
|
1481
|
-
const relative =
|
|
1601
|
+
const relative = import_node_path10.default.relative(root, file);
|
|
1482
1602
|
return relative.length > 0 ? relative : file;
|
|
1483
1603
|
}).join(", ");
|
|
1484
1604
|
}
|
|
@@ -1507,7 +1627,6 @@ var WHEN_PATTERN = /\bWhen\b/;
|
|
|
1507
1627
|
var THEN_PATTERN = /\bThen\b/;
|
|
1508
1628
|
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
1509
1629
|
var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
|
|
1510
|
-
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1511
1630
|
async function validateScenarios(root, config) {
|
|
1512
1631
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1513
1632
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1587,17 +1706,7 @@ function validateScenarioContent(text, file) {
|
|
|
1587
1706
|
const featureSpecTags = document.featureTags.filter(
|
|
1588
1707
|
(tag) => SPEC_TAG_RE2.test(tag)
|
|
1589
1708
|
);
|
|
1590
|
-
if (featureSpecTags.length
|
|
1591
|
-
issues.push(
|
|
1592
|
-
issue4(
|
|
1593
|
-
"QFAI-SC-009",
|
|
1594
|
-
"Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1595
|
-
"error",
|
|
1596
|
-
file,
|
|
1597
|
-
"scenario.featureSpec"
|
|
1598
|
-
)
|
|
1599
|
-
);
|
|
1600
|
-
} else if (featureSpecTags.length > 1) {
|
|
1709
|
+
if (featureSpecTags.length > 1) {
|
|
1601
1710
|
issues.push(
|
|
1602
1711
|
issue4(
|
|
1603
1712
|
"QFAI-SC-009",
|
|
@@ -1625,17 +1734,6 @@ function validateScenarioContent(text, file) {
|
|
|
1625
1734
|
)
|
|
1626
1735
|
);
|
|
1627
1736
|
}
|
|
1628
|
-
if (document.scenarios.length > 1) {
|
|
1629
|
-
issues.push(
|
|
1630
|
-
issue4(
|
|
1631
|
-
"QFAI-SC-011",
|
|
1632
|
-
`Scenario \u306F1\u3064\u306E\u307F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u691C\u51FA: ${document.scenarios.length}\u4EF6\uFF09`,
|
|
1633
|
-
"error",
|
|
1634
|
-
file,
|
|
1635
|
-
"scenario.single"
|
|
1636
|
-
)
|
|
1637
|
-
);
|
|
1638
|
-
}
|
|
1639
1737
|
for (const scenario of document.scenarios) {
|
|
1640
1738
|
if (scenario.tags.length === 0) {
|
|
1641
1739
|
issues.push(
|
|
@@ -1656,12 +1754,6 @@ function validateScenarioContent(text, file) {
|
|
|
1656
1754
|
} else if (scTags.length > 1) {
|
|
1657
1755
|
missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
|
|
1658
1756
|
}
|
|
1659
|
-
if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
|
|
1660
|
-
missingTags.push("SPEC");
|
|
1661
|
-
}
|
|
1662
|
-
if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
|
|
1663
|
-
missingTags.push("BR");
|
|
1664
|
-
}
|
|
1665
1757
|
if (missingTags.length > 0) {
|
|
1666
1758
|
issues.push(
|
|
1667
1759
|
issue4(
|
|
@@ -1896,9 +1988,8 @@ function isMissingFileError4(error) {
|
|
|
1896
1988
|
|
|
1897
1989
|
// src/core/validators/traceability.ts
|
|
1898
1990
|
var import_promises13 = require("fs/promises");
|
|
1899
|
-
var SC_TAG_RE5 = /^SC-\d{4}$/;
|
|
1900
1991
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
1901
|
-
var
|
|
1992
|
+
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1902
1993
|
async function validateTraceability(root, config) {
|
|
1903
1994
|
const issues = [];
|
|
1904
1995
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -1925,28 +2016,6 @@ async function validateTraceability(root, config) {
|
|
|
1925
2016
|
}
|
|
1926
2017
|
const brIds = parsed.brs.map((br) => br.id);
|
|
1927
2018
|
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
1928
|
-
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
1929
|
-
...extractIds(text, "UI"),
|
|
1930
|
-
...extractIds(text, "API"),
|
|
1931
|
-
...extractIds(text, "DATA")
|
|
1932
|
-
]);
|
|
1933
|
-
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
1934
|
-
(id) => !contractIds.has(id)
|
|
1935
|
-
);
|
|
1936
|
-
if (unknownContractIds.length > 0) {
|
|
1937
|
-
issues.push(
|
|
1938
|
-
issue6(
|
|
1939
|
-
"QFAI-TRACE-009",
|
|
1940
|
-
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1941
|
-
", "
|
|
1942
|
-
)}`,
|
|
1943
|
-
"error",
|
|
1944
|
-
file,
|
|
1945
|
-
"traceability.specContractExists",
|
|
1946
|
-
unknownContractIds
|
|
1947
|
-
)
|
|
1948
|
-
);
|
|
1949
|
-
}
|
|
1950
2019
|
if (parsed.specId) {
|
|
1951
2020
|
const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
|
|
1952
2021
|
brIds.forEach((id) => current.add(id));
|
|
@@ -1961,16 +2030,42 @@ async function validateTraceability(root, config) {
|
|
|
1961
2030
|
continue;
|
|
1962
2031
|
}
|
|
1963
2032
|
const atoms = buildScenarioAtoms(document);
|
|
2033
|
+
const scIdsInFile = /* @__PURE__ */ new Set();
|
|
1964
2034
|
for (const [index, scenario] of document.scenarios.entries()) {
|
|
1965
2035
|
const atom = atoms[index];
|
|
1966
2036
|
if (!atom) {
|
|
1967
2037
|
continue;
|
|
1968
2038
|
}
|
|
1969
2039
|
const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
|
|
1970
|
-
const brTags = scenario.tags.filter((tag) =>
|
|
1971
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
2040
|
+
const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
|
|
2041
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
|
|
2042
|
+
if (specTags.length === 0) {
|
|
2043
|
+
issues.push(
|
|
2044
|
+
issue6(
|
|
2045
|
+
"QFAI-TRACE-014",
|
|
2046
|
+
`Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2047
|
+
"error",
|
|
2048
|
+
file,
|
|
2049
|
+
"traceability.scenarioSpecRequired"
|
|
2050
|
+
)
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
if (brTags.length === 0) {
|
|
2054
|
+
issues.push(
|
|
2055
|
+
issue6(
|
|
2056
|
+
"QFAI-TRACE-015",
|
|
2057
|
+
`Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2058
|
+
"error",
|
|
2059
|
+
file,
|
|
2060
|
+
"traceability.scenarioBrRequired"
|
|
2061
|
+
)
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
1972
2064
|
brTags.forEach((id) => brIdsInScenarios.add(id));
|
|
1973
|
-
scTags.forEach((id) =>
|
|
2065
|
+
scTags.forEach((id) => {
|
|
2066
|
+
scIdsInScenarios.add(id);
|
|
2067
|
+
scIdsInFile.add(id);
|
|
2068
|
+
});
|
|
1974
2069
|
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
1975
2070
|
if (atom.contractIds.length > 0) {
|
|
1976
2071
|
scTags.forEach((id) => scWithContracts.add(id));
|
|
@@ -2048,6 +2143,22 @@ async function validateTraceability(root, config) {
|
|
|
2048
2143
|
}
|
|
2049
2144
|
}
|
|
2050
2145
|
}
|
|
2146
|
+
if (scIdsInFile.size !== 1) {
|
|
2147
|
+
const invalidScIds = Array.from(scIdsInFile).sort(
|
|
2148
|
+
(a, b) => a.localeCompare(b)
|
|
2149
|
+
);
|
|
2150
|
+
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(", ")}`;
|
|
2151
|
+
issues.push(
|
|
2152
|
+
issue6(
|
|
2153
|
+
"QFAI-TRACE-012",
|
|
2154
|
+
`Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
|
|
2155
|
+
"error",
|
|
2156
|
+
file,
|
|
2157
|
+
"traceability.specScOneToOne",
|
|
2158
|
+
invalidScIds
|
|
2159
|
+
)
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2051
2162
|
}
|
|
2052
2163
|
if (upstreamIds.size === 0) {
|
|
2053
2164
|
return [
|
|
@@ -2096,21 +2207,62 @@ async function validateTraceability(root, config) {
|
|
|
2096
2207
|
);
|
|
2097
2208
|
}
|
|
2098
2209
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2210
|
+
const scRefsResult = await collectScTestReferences(
|
|
2211
|
+
root,
|
|
2212
|
+
config.validation.traceability.testFileGlobs,
|
|
2213
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2214
|
+
);
|
|
2215
|
+
const scTestRefs = scRefsResult.refs;
|
|
2216
|
+
const testFileScan = scRefsResult.scan;
|
|
2217
|
+
const hasScenarios = scIdsInScenarios.size > 0;
|
|
2218
|
+
const hasGlobConfig = testFileScan.globs.length > 0;
|
|
2219
|
+
const hasMatchedTests = testFileScan.matchedFileCount > 0;
|
|
2220
|
+
if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
|
|
2221
|
+
const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
|
|
2222
|
+
issues.push(
|
|
2223
|
+
issue6(
|
|
2224
|
+
"QFAI-TRACE-013",
|
|
2225
|
+
`\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}`,
|
|
2226
|
+
"error",
|
|
2227
|
+
testsRoot,
|
|
2228
|
+
"traceability.testFileGlobs"
|
|
2229
|
+
)
|
|
2230
|
+
);
|
|
2231
|
+
} else {
|
|
2232
|
+
if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
|
|
2233
|
+
const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
|
|
2234
|
+
const refs = scTestRefs.get(id);
|
|
2235
|
+
return !refs || refs.size === 0;
|
|
2236
|
+
});
|
|
2237
|
+
if (scWithoutTests.length > 0) {
|
|
2238
|
+
issues.push(
|
|
2239
|
+
issue6(
|
|
2240
|
+
"QFAI-TRACE-010",
|
|
2241
|
+
`SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
|
|
2242
|
+
", "
|
|
2243
|
+
)}\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`,
|
|
2244
|
+
config.validation.traceability.scNoTestSeverity,
|
|
2245
|
+
testsRoot,
|
|
2246
|
+
"traceability.scMustHaveTest",
|
|
2247
|
+
scWithoutTests
|
|
2248
|
+
)
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
const unknownScIds = Array.from(scTestRefs.keys()).filter(
|
|
2253
|
+
(id) => !scIdsInScenarios.has(id)
|
|
2254
|
+
);
|
|
2255
|
+
if (unknownScIds.length > 0) {
|
|
2106
2256
|
issues.push(
|
|
2107
2257
|
issue6(
|
|
2108
|
-
"QFAI-TRACE-
|
|
2109
|
-
|
|
2110
|
-
|
|
2258
|
+
"QFAI-TRACE-011",
|
|
2259
|
+
`\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(
|
|
2260
|
+
", "
|
|
2261
|
+
)}`,
|
|
2262
|
+
"error",
|
|
2111
2263
|
testsRoot,
|
|
2112
|
-
"traceability.
|
|
2113
|
-
|
|
2264
|
+
"traceability.scUnknownInTests",
|
|
2265
|
+
unknownScIds
|
|
2114
2266
|
)
|
|
2115
2267
|
);
|
|
2116
2268
|
}
|
|
@@ -2173,8 +2325,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2173
2325
|
issues.push(
|
|
2174
2326
|
issue6(
|
|
2175
2327
|
"QFAI-TRACE-002",
|
|
2176
|
-
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
2177
|
-
"
|
|
2328
|
+
"\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",
|
|
2329
|
+
"info",
|
|
2178
2330
|
srcRoot,
|
|
2179
2331
|
"traceability.codeReferences"
|
|
2180
2332
|
)
|
|
@@ -2217,11 +2369,24 @@ async function validateProject(root, configResult) {
|
|
|
2217
2369
|
...await validateDefinedIds(root, config),
|
|
2218
2370
|
...await validateTraceability(root, config)
|
|
2219
2371
|
];
|
|
2372
|
+
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2373
|
+
const scenarioFiles = await collectScenarioFiles(specsRoot);
|
|
2374
|
+
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2375
|
+
const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
|
|
2376
|
+
root,
|
|
2377
|
+
config.validation.traceability.testFileGlobs,
|
|
2378
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2379
|
+
);
|
|
2380
|
+
const scCoverage = buildScCoverage(scIds, scTestRefs);
|
|
2220
2381
|
const toolVersion = await resolveToolVersion();
|
|
2221
2382
|
return {
|
|
2222
2383
|
toolVersion,
|
|
2223
2384
|
issues,
|
|
2224
|
-
counts: countIssues(issues)
|
|
2385
|
+
counts: countIssues(issues),
|
|
2386
|
+
traceability: {
|
|
2387
|
+
sc: scCoverage,
|
|
2388
|
+
testFiles
|
|
2389
|
+
}
|
|
2225
2390
|
};
|
|
2226
2391
|
}
|
|
2227
2392
|
function countIssues(issues) {
|
|
@@ -2242,9 +2407,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2242
2407
|
const configPath = resolved.configPath;
|
|
2243
2408
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2244
2409
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2245
|
-
const apiRoot =
|
|
2246
|
-
const uiRoot =
|
|
2247
|
-
const dbRoot =
|
|
2410
|
+
const apiRoot = import_node_path11.default.join(contractsRoot, "api");
|
|
2411
|
+
const uiRoot = import_node_path11.default.join(contractsRoot, "ui");
|
|
2412
|
+
const dbRoot = import_node_path11.default.join(contractsRoot, "db");
|
|
2248
2413
|
const srcRoot = resolvePath(root, config, "srcDir");
|
|
2249
2414
|
const testsRoot = resolvePath(root, config, "testsDir");
|
|
2250
2415
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2271,8 +2436,15 @@ async function createReportData(root, validation, configResult) {
|
|
|
2271
2436
|
testsRoot
|
|
2272
2437
|
);
|
|
2273
2438
|
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2274
|
-
const
|
|
2275
|
-
|
|
2439
|
+
const scRefsResult = await collectScTestReferences(
|
|
2440
|
+
root,
|
|
2441
|
+
config.validation.traceability.testFileGlobs,
|
|
2442
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2443
|
+
);
|
|
2444
|
+
const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
|
|
2445
|
+
const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
|
|
2446
|
+
const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
|
|
2447
|
+
const scSourceRecord = mapToSortedRecord(scSources);
|
|
2276
2448
|
const resolvedValidation = validation ?? await validateProject(root, resolved);
|
|
2277
2449
|
const version = await resolveToolVersion();
|
|
2278
2450
|
return {
|
|
@@ -2302,7 +2474,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2302
2474
|
traceability: {
|
|
2303
2475
|
upstreamIdsFound: upstreamIds.size,
|
|
2304
2476
|
referencedInCodeOrTests: traceability,
|
|
2305
|
-
sc: scCoverage
|
|
2477
|
+
sc: scCoverage,
|
|
2478
|
+
scSources: scSourceRecord,
|
|
2479
|
+
testFiles
|
|
2306
2480
|
},
|
|
2307
2481
|
issues: resolvedValidation.issues
|
|
2308
2482
|
};
|
|
@@ -2343,10 +2517,29 @@ function formatReportMarkdown(data) {
|
|
|
2343
2517
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2344
2518
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
2345
2519
|
lines.push(`- missing: ${data.traceability.sc.missing}`);
|
|
2520
|
+
lines.push(
|
|
2521
|
+
`- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
|
|
2522
|
+
);
|
|
2523
|
+
lines.push(
|
|
2524
|
+
`- testFileExcludeGlobs: ${formatList(
|
|
2525
|
+
data.traceability.testFiles.excludeGlobs
|
|
2526
|
+
)}`
|
|
2527
|
+
);
|
|
2528
|
+
lines.push(
|
|
2529
|
+
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
2530
|
+
);
|
|
2346
2531
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
2347
2532
|
lines.push("- missingIds: (none)");
|
|
2348
2533
|
} else {
|
|
2349
|
-
|
|
2534
|
+
const sources = data.traceability.scSources;
|
|
2535
|
+
const missingWithSources = data.traceability.sc.missingIds.map((id) => {
|
|
2536
|
+
const files = sources[id] ?? [];
|
|
2537
|
+
if (files.length === 0) {
|
|
2538
|
+
return id;
|
|
2539
|
+
}
|
|
2540
|
+
return `${id} (${files.join(", ")})`;
|
|
2541
|
+
});
|
|
2542
|
+
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2350
2543
|
}
|
|
2351
2544
|
lines.push("");
|
|
2352
2545
|
lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
|
|
@@ -2365,6 +2558,20 @@ function formatReportMarkdown(data) {
|
|
|
2365
2558
|
}
|
|
2366
2559
|
}
|
|
2367
2560
|
lines.push("");
|
|
2561
|
+
lines.push("## Spec:SC=1:1 \u9055\u53CD");
|
|
2562
|
+
const specScIssues = data.issues.filter(
|
|
2563
|
+
(item) => item.code === "QFAI-TRACE-012"
|
|
2564
|
+
);
|
|
2565
|
+
if (specScIssues.length === 0) {
|
|
2566
|
+
lines.push("- (none)");
|
|
2567
|
+
} else {
|
|
2568
|
+
for (const item of specScIssues) {
|
|
2569
|
+
const location = item.file ?? "(unknown)";
|
|
2570
|
+
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
2571
|
+
lines.push(`- ${location}: ${refs}`);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
lines.push("");
|
|
2368
2575
|
lines.push("## Hotspots");
|
|
2369
2576
|
const hotspots = buildHotspots(data.issues);
|
|
2370
2577
|
if (hotspots.length === 0) {
|
|
@@ -2475,9 +2682,22 @@ function formatIdLine(label, values) {
|
|
|
2475
2682
|
}
|
|
2476
2683
|
return `- ${label}: ${values.join(", ")}`;
|
|
2477
2684
|
}
|
|
2685
|
+
function formatList(values) {
|
|
2686
|
+
if (values.length === 0) {
|
|
2687
|
+
return "(none)";
|
|
2688
|
+
}
|
|
2689
|
+
return values.join(", ");
|
|
2690
|
+
}
|
|
2478
2691
|
function toSortedArray2(values) {
|
|
2479
2692
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
2480
2693
|
}
|
|
2694
|
+
function mapToSortedRecord(values) {
|
|
2695
|
+
const record2 = {};
|
|
2696
|
+
for (const [key, files] of values.entries()) {
|
|
2697
|
+
record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
2698
|
+
}
|
|
2699
|
+
return record2;
|
|
2700
|
+
}
|
|
2481
2701
|
function buildHotspots(issues) {
|
|
2482
2702
|
const map = /* @__PURE__ */ new Map();
|
|
2483
2703
|
for (const issue7 of issues) {
|