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.mjs
CHANGED
|
@@ -155,7 +155,7 @@ function report(copied, skipped, dryRun, label) {
|
|
|
155
155
|
|
|
156
156
|
// src/cli/commands/report.ts
|
|
157
157
|
import { mkdir as mkdir2, readFile as readFile12, writeFile } from "fs/promises";
|
|
158
|
-
import
|
|
158
|
+
import path15 from "path";
|
|
159
159
|
|
|
160
160
|
// src/core/config.ts
|
|
161
161
|
import { readFile } from "fs/promises";
|
|
@@ -188,6 +188,8 @@ var defaultConfig = {
|
|
|
188
188
|
brMustHaveSc: true,
|
|
189
189
|
scMustTouchContracts: true,
|
|
190
190
|
scMustHaveTest: true,
|
|
191
|
+
testFileGlobs: [],
|
|
192
|
+
testFileExcludeGlobs: [],
|
|
191
193
|
scNoTestSeverity: "error",
|
|
192
194
|
allowOrphanContracts: false,
|
|
193
195
|
unknownContractIdSeverity: "error"
|
|
@@ -375,6 +377,20 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
375
377
|
configPath,
|
|
376
378
|
issues
|
|
377
379
|
),
|
|
380
|
+
testFileGlobs: readStringArray(
|
|
381
|
+
traceabilityRaw?.testFileGlobs,
|
|
382
|
+
base.traceability.testFileGlobs,
|
|
383
|
+
"validation.traceability.testFileGlobs",
|
|
384
|
+
configPath,
|
|
385
|
+
issues
|
|
386
|
+
),
|
|
387
|
+
testFileExcludeGlobs: readStringArray(
|
|
388
|
+
traceabilityRaw?.testFileExcludeGlobs,
|
|
389
|
+
base.traceability.testFileExcludeGlobs,
|
|
390
|
+
"validation.traceability.testFileExcludeGlobs",
|
|
391
|
+
configPath,
|
|
392
|
+
issues
|
|
393
|
+
),
|
|
378
394
|
scNoTestSeverity: readTraceabilitySeverity(
|
|
379
395
|
traceabilityRaw?.scNoTestSeverity,
|
|
380
396
|
base.traceability.scNoTestSeverity,
|
|
@@ -508,7 +524,7 @@ function isRecord(value) {
|
|
|
508
524
|
|
|
509
525
|
// src/core/report.ts
|
|
510
526
|
import { readFile as readFile11 } from "fs/promises";
|
|
511
|
-
import
|
|
527
|
+
import path14 from "path";
|
|
512
528
|
|
|
513
529
|
// src/core/discovery.ts
|
|
514
530
|
import { access as access3 } from "fs/promises";
|
|
@@ -516,6 +532,7 @@ import { access as access3 } from "fs/promises";
|
|
|
516
532
|
// src/core/fs.ts
|
|
517
533
|
import { access as access2, readdir as readdir2 } from "fs/promises";
|
|
518
534
|
import path5 from "path";
|
|
535
|
+
import fg from "fast-glob";
|
|
519
536
|
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
520
537
|
"node_modules",
|
|
521
538
|
".git",
|
|
@@ -537,6 +554,18 @@ async function collectFiles(root, options = {}) {
|
|
|
537
554
|
await walk(root, root, ignoreDirs, extensions, entries);
|
|
538
555
|
return entries;
|
|
539
556
|
}
|
|
557
|
+
async function collectFilesByGlobs(root, options) {
|
|
558
|
+
if (options.globs.length === 0) {
|
|
559
|
+
return [];
|
|
560
|
+
}
|
|
561
|
+
return fg(options.globs, {
|
|
562
|
+
cwd: root,
|
|
563
|
+
ignore: options.ignore ?? [],
|
|
564
|
+
onlyFiles: true,
|
|
565
|
+
absolute: true,
|
|
566
|
+
unique: true
|
|
567
|
+
});
|
|
568
|
+
}
|
|
540
569
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
541
570
|
const items = await readdir2(current, { withFileTypes: true });
|
|
542
571
|
for (const item of items) {
|
|
@@ -703,6 +732,7 @@ function isValidId(value, prefix) {
|
|
|
703
732
|
|
|
704
733
|
// src/core/traceability.ts
|
|
705
734
|
import { readFile as readFile2 } from "fs/promises";
|
|
735
|
+
import path7 from "path";
|
|
706
736
|
|
|
707
737
|
// src/core/gherkin/parse.ts
|
|
708
738
|
import {
|
|
@@ -864,6 +894,27 @@ function unique2(values) {
|
|
|
864
894
|
|
|
865
895
|
// src/core/traceability.ts
|
|
866
896
|
var SC_TAG_RE2 = /^SC-\d{4}$/;
|
|
897
|
+
var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
|
|
898
|
+
var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
|
|
899
|
+
"**/node_modules/**",
|
|
900
|
+
"**/.git/**",
|
|
901
|
+
"**/.qfai/**",
|
|
902
|
+
"**/dist/**",
|
|
903
|
+
"**/build/**",
|
|
904
|
+
"**/coverage/**",
|
|
905
|
+
"**/.next/**",
|
|
906
|
+
"**/out/**"
|
|
907
|
+
];
|
|
908
|
+
function extractAnnotatedScIds(text) {
|
|
909
|
+
const ids = /* @__PURE__ */ new Set();
|
|
910
|
+
for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
|
|
911
|
+
const suffix = match[1];
|
|
912
|
+
if (suffix) {
|
|
913
|
+
ids.add(`SC-${suffix}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return Array.from(ids);
|
|
917
|
+
}
|
|
867
918
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
868
919
|
const scIds = /* @__PURE__ */ new Set();
|
|
869
920
|
for (const file of scenarioFiles) {
|
|
@@ -882,14 +933,67 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
882
933
|
}
|
|
883
934
|
return scIds;
|
|
884
935
|
}
|
|
885
|
-
async function
|
|
936
|
+
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
937
|
+
const sources = /* @__PURE__ */ new Map();
|
|
938
|
+
for (const file of scenarioFiles) {
|
|
939
|
+
const text = await readFile2(file, "utf-8");
|
|
940
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
941
|
+
if (!document || errors.length > 0) {
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
for (const scenario of document.scenarios) {
|
|
945
|
+
for (const tag of scenario.tags) {
|
|
946
|
+
if (!SC_TAG_RE2.test(tag)) {
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
|
|
950
|
+
current.add(file);
|
|
951
|
+
sources.set(tag, current);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return sources;
|
|
956
|
+
}
|
|
957
|
+
async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
886
958
|
const refs = /* @__PURE__ */ new Map();
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
959
|
+
const normalizedGlobs = normalizeGlobs(globs);
|
|
960
|
+
const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
|
|
961
|
+
const mergedExcludeGlobs = Array.from(
|
|
962
|
+
/* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
|
|
963
|
+
);
|
|
964
|
+
if (normalizedGlobs.length === 0) {
|
|
965
|
+
return {
|
|
966
|
+
refs,
|
|
967
|
+
scan: {
|
|
968
|
+
globs: normalizedGlobs,
|
|
969
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
970
|
+
matchedFileCount: 0
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
let files = [];
|
|
975
|
+
try {
|
|
976
|
+
files = await collectFilesByGlobs(root, {
|
|
977
|
+
globs: normalizedGlobs,
|
|
978
|
+
ignore: mergedExcludeGlobs
|
|
979
|
+
});
|
|
980
|
+
} catch (error2) {
|
|
981
|
+
return {
|
|
982
|
+
refs,
|
|
983
|
+
scan: {
|
|
984
|
+
globs: normalizedGlobs,
|
|
985
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
986
|
+
matchedFileCount: 0
|
|
987
|
+
},
|
|
988
|
+
error: formatError3(error2)
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
const normalizedFiles = Array.from(
|
|
992
|
+
new Set(files.map((file) => path7.normalize(file)))
|
|
993
|
+
);
|
|
994
|
+
for (const file of normalizedFiles) {
|
|
891
995
|
const text = await readFile2(file, "utf-8");
|
|
892
|
-
const scIds =
|
|
996
|
+
const scIds = extractAnnotatedScIds(text);
|
|
893
997
|
if (scIds.length === 0) {
|
|
894
998
|
continue;
|
|
895
999
|
}
|
|
@@ -899,7 +1003,14 @@ async function collectScTestReferences(testsRoot) {
|
|
|
899
1003
|
refs.set(scId, current);
|
|
900
1004
|
}
|
|
901
1005
|
}
|
|
902
|
-
return
|
|
1006
|
+
return {
|
|
1007
|
+
refs,
|
|
1008
|
+
scan: {
|
|
1009
|
+
globs: normalizedGlobs,
|
|
1010
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
1011
|
+
matchedFileCount: normalizedFiles.length
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
903
1014
|
}
|
|
904
1015
|
function buildScCoverage(scIds, refs) {
|
|
905
1016
|
const sortedScIds = toSortedArray(scIds);
|
|
@@ -927,14 +1038,23 @@ function buildScCoverage(scIds, refs) {
|
|
|
927
1038
|
function toSortedArray(values) {
|
|
928
1039
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
929
1040
|
}
|
|
1041
|
+
function normalizeGlobs(globs) {
|
|
1042
|
+
return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1043
|
+
}
|
|
1044
|
+
function formatError3(error2) {
|
|
1045
|
+
if (error2 instanceof Error) {
|
|
1046
|
+
return error2.message;
|
|
1047
|
+
}
|
|
1048
|
+
return String(error2);
|
|
1049
|
+
}
|
|
930
1050
|
|
|
931
1051
|
// src/core/version.ts
|
|
932
1052
|
import { readFile as readFile3 } from "fs/promises";
|
|
933
|
-
import
|
|
1053
|
+
import path8 from "path";
|
|
934
1054
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
935
1055
|
async function resolveToolVersion() {
|
|
936
|
-
if ("0.4.
|
|
937
|
-
return "0.4.
|
|
1056
|
+
if ("0.4.2".length > 0) {
|
|
1057
|
+
return "0.4.2";
|
|
938
1058
|
}
|
|
939
1059
|
try {
|
|
940
1060
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -949,18 +1069,18 @@ async function resolveToolVersion() {
|
|
|
949
1069
|
function resolvePackageJsonPath() {
|
|
950
1070
|
const base = import.meta.url;
|
|
951
1071
|
const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
|
|
952
|
-
return
|
|
1072
|
+
return path8.resolve(path8.dirname(basePath), "../../package.json");
|
|
953
1073
|
}
|
|
954
1074
|
|
|
955
1075
|
// src/core/validators/contracts.ts
|
|
956
1076
|
import { readFile as readFile4 } from "fs/promises";
|
|
957
|
-
import
|
|
1077
|
+
import path10 from "path";
|
|
958
1078
|
|
|
959
1079
|
// src/core/contracts.ts
|
|
960
|
-
import
|
|
1080
|
+
import path9 from "path";
|
|
961
1081
|
import { parse as parseYaml2 } from "yaml";
|
|
962
1082
|
function parseStructuredContract(file, text) {
|
|
963
|
-
const ext =
|
|
1083
|
+
const ext = path9.extname(file).toLowerCase();
|
|
964
1084
|
if (ext === ".json") {
|
|
965
1085
|
return JSON.parse(text);
|
|
966
1086
|
}
|
|
@@ -1011,9 +1131,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1011
1131
|
async function validateContracts(root, config) {
|
|
1012
1132
|
const issues = [];
|
|
1013
1133
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1014
|
-
issues.push(...await validateUiContracts(
|
|
1015
|
-
issues.push(...await validateApiContracts(
|
|
1016
|
-
issues.push(...await validateDataContracts(
|
|
1134
|
+
issues.push(...await validateUiContracts(path10.join(contractsRoot, "ui")));
|
|
1135
|
+
issues.push(...await validateApiContracts(path10.join(contractsRoot, "api")));
|
|
1136
|
+
issues.push(...await validateDataContracts(path10.join(contractsRoot, "db")));
|
|
1017
1137
|
return issues;
|
|
1018
1138
|
}
|
|
1019
1139
|
async function validateUiContracts(uiRoot) {
|
|
@@ -1060,7 +1180,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1060
1180
|
issues.push(
|
|
1061
1181
|
issue(
|
|
1062
1182
|
"QFAI-CONTRACT-001",
|
|
1063
|
-
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1183
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
|
|
1064
1184
|
"error",
|
|
1065
1185
|
file,
|
|
1066
1186
|
"contracts.ui.parse"
|
|
@@ -1127,7 +1247,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1127
1247
|
issues.push(
|
|
1128
1248
|
issue(
|
|
1129
1249
|
"QFAI-CONTRACT-001",
|
|
1130
|
-
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1250
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error2)})`,
|
|
1131
1251
|
"error",
|
|
1132
1252
|
file,
|
|
1133
1253
|
"contracts.api.parse"
|
|
@@ -1222,7 +1342,7 @@ function lintSql(text, file) {
|
|
|
1222
1342
|
function hasOpenApi(doc) {
|
|
1223
1343
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1224
1344
|
}
|
|
1225
|
-
function
|
|
1345
|
+
function formatError4(error2) {
|
|
1226
1346
|
if (error2 instanceof Error) {
|
|
1227
1347
|
return error2.message;
|
|
1228
1348
|
}
|
|
@@ -1248,7 +1368,7 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
1248
1368
|
|
|
1249
1369
|
// src/core/validators/delta.ts
|
|
1250
1370
|
import { readFile as readFile5 } from "fs/promises";
|
|
1251
|
-
import
|
|
1371
|
+
import path11 from "path";
|
|
1252
1372
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1253
1373
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1254
1374
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1262,7 +1382,7 @@ async function validateDeltas(root, config) {
|
|
|
1262
1382
|
}
|
|
1263
1383
|
const issues = [];
|
|
1264
1384
|
for (const pack of packs) {
|
|
1265
|
-
const deltaPath =
|
|
1385
|
+
const deltaPath = path11.join(pack, "delta.md");
|
|
1266
1386
|
let text;
|
|
1267
1387
|
try {
|
|
1268
1388
|
text = await readFile5(deltaPath, "utf-8");
|
|
@@ -1338,16 +1458,16 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
1338
1458
|
|
|
1339
1459
|
// src/core/validators/ids.ts
|
|
1340
1460
|
import { readFile as readFile7 } from "fs/promises";
|
|
1341
|
-
import
|
|
1461
|
+
import path13 from "path";
|
|
1342
1462
|
|
|
1343
1463
|
// src/core/contractIndex.ts
|
|
1344
1464
|
import { readFile as readFile6 } from "fs/promises";
|
|
1345
|
-
import
|
|
1465
|
+
import path12 from "path";
|
|
1346
1466
|
async function buildContractIndex(root, config) {
|
|
1347
1467
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1348
|
-
const uiRoot =
|
|
1349
|
-
const apiRoot =
|
|
1350
|
-
const dataRoot =
|
|
1468
|
+
const uiRoot = path12.join(contractsRoot, "ui");
|
|
1469
|
+
const apiRoot = path12.join(contractsRoot, "api");
|
|
1470
|
+
const dataRoot = path12.join(contractsRoot, "db");
|
|
1351
1471
|
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1352
1472
|
collectUiContractFiles(uiRoot),
|
|
1353
1473
|
collectApiContractFiles(apiRoot),
|
|
@@ -1585,7 +1705,7 @@ function recordId(out, id, file) {
|
|
|
1585
1705
|
}
|
|
1586
1706
|
function formatFileList(files, root) {
|
|
1587
1707
|
return files.map((file) => {
|
|
1588
|
-
const relative =
|
|
1708
|
+
const relative = path13.relative(root, file);
|
|
1589
1709
|
return relative.length > 0 ? relative : file;
|
|
1590
1710
|
}).join(", ");
|
|
1591
1711
|
}
|
|
@@ -1614,7 +1734,6 @@ var WHEN_PATTERN = /\bWhen\b/;
|
|
|
1614
1734
|
var THEN_PATTERN = /\bThen\b/;
|
|
1615
1735
|
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
1616
1736
|
var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
|
|
1617
|
-
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1618
1737
|
async function validateScenarios(root, config) {
|
|
1619
1738
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1620
1739
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1694,17 +1813,7 @@ function validateScenarioContent(text, file) {
|
|
|
1694
1813
|
const featureSpecTags = document.featureTags.filter(
|
|
1695
1814
|
(tag) => SPEC_TAG_RE2.test(tag)
|
|
1696
1815
|
);
|
|
1697
|
-
if (featureSpecTags.length
|
|
1698
|
-
issues.push(
|
|
1699
|
-
issue4(
|
|
1700
|
-
"QFAI-SC-009",
|
|
1701
|
-
"Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1702
|
-
"error",
|
|
1703
|
-
file,
|
|
1704
|
-
"scenario.featureSpec"
|
|
1705
|
-
)
|
|
1706
|
-
);
|
|
1707
|
-
} else if (featureSpecTags.length > 1) {
|
|
1816
|
+
if (featureSpecTags.length > 1) {
|
|
1708
1817
|
issues.push(
|
|
1709
1818
|
issue4(
|
|
1710
1819
|
"QFAI-SC-009",
|
|
@@ -1732,17 +1841,6 @@ function validateScenarioContent(text, file) {
|
|
|
1732
1841
|
)
|
|
1733
1842
|
);
|
|
1734
1843
|
}
|
|
1735
|
-
if (document.scenarios.length > 1) {
|
|
1736
|
-
issues.push(
|
|
1737
|
-
issue4(
|
|
1738
|
-
"QFAI-SC-011",
|
|
1739
|
-
`Scenario \u306F1\u3064\u306E\u307F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u691C\u51FA: ${document.scenarios.length}\u4EF6\uFF09`,
|
|
1740
|
-
"error",
|
|
1741
|
-
file,
|
|
1742
|
-
"scenario.single"
|
|
1743
|
-
)
|
|
1744
|
-
);
|
|
1745
|
-
}
|
|
1746
1844
|
for (const scenario of document.scenarios) {
|
|
1747
1845
|
if (scenario.tags.length === 0) {
|
|
1748
1846
|
issues.push(
|
|
@@ -1763,12 +1861,6 @@ function validateScenarioContent(text, file) {
|
|
|
1763
1861
|
} else if (scTags.length > 1) {
|
|
1764
1862
|
missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
|
|
1765
1863
|
}
|
|
1766
|
-
if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
|
|
1767
|
-
missingTags.push("SPEC");
|
|
1768
|
-
}
|
|
1769
|
-
if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
|
|
1770
|
-
missingTags.push("BR");
|
|
1771
|
-
}
|
|
1772
1864
|
if (missingTags.length > 0) {
|
|
1773
1865
|
issues.push(
|
|
1774
1866
|
issue4(
|
|
@@ -2003,9 +2095,8 @@ function isMissingFileError4(error2) {
|
|
|
2003
2095
|
|
|
2004
2096
|
// src/core/validators/traceability.ts
|
|
2005
2097
|
import { readFile as readFile10 } from "fs/promises";
|
|
2006
|
-
var SC_TAG_RE5 = /^SC-\d{4}$/;
|
|
2007
2098
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2008
|
-
var
|
|
2099
|
+
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2009
2100
|
async function validateTraceability(root, config) {
|
|
2010
2101
|
const issues = [];
|
|
2011
2102
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -2032,28 +2123,6 @@ async function validateTraceability(root, config) {
|
|
|
2032
2123
|
}
|
|
2033
2124
|
const brIds = parsed.brs.map((br) => br.id);
|
|
2034
2125
|
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
2035
|
-
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
2036
|
-
...extractIds(text, "UI"),
|
|
2037
|
-
...extractIds(text, "API"),
|
|
2038
|
-
...extractIds(text, "DATA")
|
|
2039
|
-
]);
|
|
2040
|
-
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
2041
|
-
(id) => !contractIds.has(id)
|
|
2042
|
-
);
|
|
2043
|
-
if (unknownContractIds.length > 0) {
|
|
2044
|
-
issues.push(
|
|
2045
|
-
issue6(
|
|
2046
|
-
"QFAI-TRACE-009",
|
|
2047
|
-
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
2048
|
-
", "
|
|
2049
|
-
)}`,
|
|
2050
|
-
"error",
|
|
2051
|
-
file,
|
|
2052
|
-
"traceability.specContractExists",
|
|
2053
|
-
unknownContractIds
|
|
2054
|
-
)
|
|
2055
|
-
);
|
|
2056
|
-
}
|
|
2057
2126
|
if (parsed.specId) {
|
|
2058
2127
|
const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
|
|
2059
2128
|
brIds.forEach((id) => current.add(id));
|
|
@@ -2068,16 +2137,42 @@ async function validateTraceability(root, config) {
|
|
|
2068
2137
|
continue;
|
|
2069
2138
|
}
|
|
2070
2139
|
const atoms = buildScenarioAtoms(document);
|
|
2140
|
+
const scIdsInFile = /* @__PURE__ */ new Set();
|
|
2071
2141
|
for (const [index, scenario] of document.scenarios.entries()) {
|
|
2072
2142
|
const atom = atoms[index];
|
|
2073
2143
|
if (!atom) {
|
|
2074
2144
|
continue;
|
|
2075
2145
|
}
|
|
2076
2146
|
const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
|
|
2077
|
-
const brTags = scenario.tags.filter((tag) =>
|
|
2078
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
2147
|
+
const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
|
|
2148
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
|
|
2149
|
+
if (specTags.length === 0) {
|
|
2150
|
+
issues.push(
|
|
2151
|
+
issue6(
|
|
2152
|
+
"QFAI-TRACE-014",
|
|
2153
|
+
`Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2154
|
+
"error",
|
|
2155
|
+
file,
|
|
2156
|
+
"traceability.scenarioSpecRequired"
|
|
2157
|
+
)
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
if (brTags.length === 0) {
|
|
2161
|
+
issues.push(
|
|
2162
|
+
issue6(
|
|
2163
|
+
"QFAI-TRACE-015",
|
|
2164
|
+
`Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2165
|
+
"error",
|
|
2166
|
+
file,
|
|
2167
|
+
"traceability.scenarioBrRequired"
|
|
2168
|
+
)
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2079
2171
|
brTags.forEach((id) => brIdsInScenarios.add(id));
|
|
2080
|
-
scTags.forEach((id) =>
|
|
2172
|
+
scTags.forEach((id) => {
|
|
2173
|
+
scIdsInScenarios.add(id);
|
|
2174
|
+
scIdsInFile.add(id);
|
|
2175
|
+
});
|
|
2081
2176
|
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
2082
2177
|
if (atom.contractIds.length > 0) {
|
|
2083
2178
|
scTags.forEach((id) => scWithContracts.add(id));
|
|
@@ -2155,6 +2250,22 @@ async function validateTraceability(root, config) {
|
|
|
2155
2250
|
}
|
|
2156
2251
|
}
|
|
2157
2252
|
}
|
|
2253
|
+
if (scIdsInFile.size !== 1) {
|
|
2254
|
+
const invalidScIds = Array.from(scIdsInFile).sort(
|
|
2255
|
+
(a, b) => a.localeCompare(b)
|
|
2256
|
+
);
|
|
2257
|
+
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(", ")}`;
|
|
2258
|
+
issues.push(
|
|
2259
|
+
issue6(
|
|
2260
|
+
"QFAI-TRACE-012",
|
|
2261
|
+
`Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
|
|
2262
|
+
"error",
|
|
2263
|
+
file,
|
|
2264
|
+
"traceability.specScOneToOne",
|
|
2265
|
+
invalidScIds
|
|
2266
|
+
)
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2158
2269
|
}
|
|
2159
2270
|
if (upstreamIds.size === 0) {
|
|
2160
2271
|
return [
|
|
@@ -2203,21 +2314,62 @@ async function validateTraceability(root, config) {
|
|
|
2203
2314
|
);
|
|
2204
2315
|
}
|
|
2205
2316
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2317
|
+
const scRefsResult = await collectScTestReferences(
|
|
2318
|
+
root,
|
|
2319
|
+
config.validation.traceability.testFileGlobs,
|
|
2320
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2321
|
+
);
|
|
2322
|
+
const scTestRefs = scRefsResult.refs;
|
|
2323
|
+
const testFileScan = scRefsResult.scan;
|
|
2324
|
+
const hasScenarios = scIdsInScenarios.size > 0;
|
|
2325
|
+
const hasGlobConfig = testFileScan.globs.length > 0;
|
|
2326
|
+
const hasMatchedTests = testFileScan.matchedFileCount > 0;
|
|
2327
|
+
if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
|
|
2328
|
+
const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
|
|
2329
|
+
issues.push(
|
|
2330
|
+
issue6(
|
|
2331
|
+
"QFAI-TRACE-013",
|
|
2332
|
+
`\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}`,
|
|
2333
|
+
"error",
|
|
2334
|
+
testsRoot,
|
|
2335
|
+
"traceability.testFileGlobs"
|
|
2336
|
+
)
|
|
2337
|
+
);
|
|
2338
|
+
} else {
|
|
2339
|
+
if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
|
|
2340
|
+
const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
|
|
2341
|
+
const refs = scTestRefs.get(id);
|
|
2342
|
+
return !refs || refs.size === 0;
|
|
2343
|
+
});
|
|
2344
|
+
if (scWithoutTests.length > 0) {
|
|
2345
|
+
issues.push(
|
|
2346
|
+
issue6(
|
|
2347
|
+
"QFAI-TRACE-010",
|
|
2348
|
+
`SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
|
|
2349
|
+
", "
|
|
2350
|
+
)}\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`,
|
|
2351
|
+
config.validation.traceability.scNoTestSeverity,
|
|
2352
|
+
testsRoot,
|
|
2353
|
+
"traceability.scMustHaveTest",
|
|
2354
|
+
scWithoutTests
|
|
2355
|
+
)
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
const unknownScIds = Array.from(scTestRefs.keys()).filter(
|
|
2360
|
+
(id) => !scIdsInScenarios.has(id)
|
|
2361
|
+
);
|
|
2362
|
+
if (unknownScIds.length > 0) {
|
|
2213
2363
|
issues.push(
|
|
2214
2364
|
issue6(
|
|
2215
|
-
"QFAI-TRACE-
|
|
2216
|
-
|
|
2217
|
-
|
|
2365
|
+
"QFAI-TRACE-011",
|
|
2366
|
+
`\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(
|
|
2367
|
+
", "
|
|
2368
|
+
)}`,
|
|
2369
|
+
"error",
|
|
2218
2370
|
testsRoot,
|
|
2219
|
-
"traceability.
|
|
2220
|
-
|
|
2371
|
+
"traceability.scUnknownInTests",
|
|
2372
|
+
unknownScIds
|
|
2221
2373
|
)
|
|
2222
2374
|
);
|
|
2223
2375
|
}
|
|
@@ -2280,8 +2432,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2280
2432
|
issues.push(
|
|
2281
2433
|
issue6(
|
|
2282
2434
|
"QFAI-TRACE-002",
|
|
2283
|
-
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
2284
|
-
"
|
|
2435
|
+
"\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",
|
|
2436
|
+
"info",
|
|
2285
2437
|
srcRoot,
|
|
2286
2438
|
"traceability.codeReferences"
|
|
2287
2439
|
)
|
|
@@ -2324,11 +2476,24 @@ async function validateProject(root, configResult) {
|
|
|
2324
2476
|
...await validateDefinedIds(root, config),
|
|
2325
2477
|
...await validateTraceability(root, config)
|
|
2326
2478
|
];
|
|
2479
|
+
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2480
|
+
const scenarioFiles = await collectScenarioFiles(specsRoot);
|
|
2481
|
+
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2482
|
+
const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
|
|
2483
|
+
root,
|
|
2484
|
+
config.validation.traceability.testFileGlobs,
|
|
2485
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2486
|
+
);
|
|
2487
|
+
const scCoverage = buildScCoverage(scIds, scTestRefs);
|
|
2327
2488
|
const toolVersion = await resolveToolVersion();
|
|
2328
2489
|
return {
|
|
2329
2490
|
toolVersion,
|
|
2330
2491
|
issues,
|
|
2331
|
-
counts: countIssues(issues)
|
|
2492
|
+
counts: countIssues(issues),
|
|
2493
|
+
traceability: {
|
|
2494
|
+
sc: scCoverage,
|
|
2495
|
+
testFiles
|
|
2496
|
+
}
|
|
2332
2497
|
};
|
|
2333
2498
|
}
|
|
2334
2499
|
function countIssues(issues) {
|
|
@@ -2349,9 +2514,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2349
2514
|
const configPath = resolved.configPath;
|
|
2350
2515
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2351
2516
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2352
|
-
const apiRoot =
|
|
2353
|
-
const uiRoot =
|
|
2354
|
-
const dbRoot =
|
|
2517
|
+
const apiRoot = path14.join(contractsRoot, "api");
|
|
2518
|
+
const uiRoot = path14.join(contractsRoot, "ui");
|
|
2519
|
+
const dbRoot = path14.join(contractsRoot, "db");
|
|
2355
2520
|
const srcRoot = resolvePath(root, config, "srcDir");
|
|
2356
2521
|
const testsRoot = resolvePath(root, config, "testsDir");
|
|
2357
2522
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2378,8 +2543,15 @@ async function createReportData(root, validation, configResult) {
|
|
|
2378
2543
|
testsRoot
|
|
2379
2544
|
);
|
|
2380
2545
|
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2546
|
+
const scRefsResult = await collectScTestReferences(
|
|
2547
|
+
root,
|
|
2548
|
+
config.validation.traceability.testFileGlobs,
|
|
2549
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2550
|
+
);
|
|
2551
|
+
const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
|
|
2552
|
+
const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
|
|
2553
|
+
const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
|
|
2554
|
+
const scSourceRecord = mapToSortedRecord(scSources);
|
|
2383
2555
|
const resolvedValidation = validation ?? await validateProject(root, resolved);
|
|
2384
2556
|
const version = await resolveToolVersion();
|
|
2385
2557
|
return {
|
|
@@ -2409,7 +2581,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2409
2581
|
traceability: {
|
|
2410
2582
|
upstreamIdsFound: upstreamIds.size,
|
|
2411
2583
|
referencedInCodeOrTests: traceability,
|
|
2412
|
-
sc: scCoverage
|
|
2584
|
+
sc: scCoverage,
|
|
2585
|
+
scSources: scSourceRecord,
|
|
2586
|
+
testFiles
|
|
2413
2587
|
},
|
|
2414
2588
|
issues: resolvedValidation.issues
|
|
2415
2589
|
};
|
|
@@ -2450,10 +2624,29 @@ function formatReportMarkdown(data) {
|
|
|
2450
2624
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2451
2625
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
2452
2626
|
lines.push(`- missing: ${data.traceability.sc.missing}`);
|
|
2627
|
+
lines.push(
|
|
2628
|
+
`- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
|
|
2629
|
+
);
|
|
2630
|
+
lines.push(
|
|
2631
|
+
`- testFileExcludeGlobs: ${formatList(
|
|
2632
|
+
data.traceability.testFiles.excludeGlobs
|
|
2633
|
+
)}`
|
|
2634
|
+
);
|
|
2635
|
+
lines.push(
|
|
2636
|
+
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
2637
|
+
);
|
|
2453
2638
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
2454
2639
|
lines.push("- missingIds: (none)");
|
|
2455
2640
|
} else {
|
|
2456
|
-
|
|
2641
|
+
const sources = data.traceability.scSources;
|
|
2642
|
+
const missingWithSources = data.traceability.sc.missingIds.map((id) => {
|
|
2643
|
+
const files = sources[id] ?? [];
|
|
2644
|
+
if (files.length === 0) {
|
|
2645
|
+
return id;
|
|
2646
|
+
}
|
|
2647
|
+
return `${id} (${files.join(", ")})`;
|
|
2648
|
+
});
|
|
2649
|
+
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2457
2650
|
}
|
|
2458
2651
|
lines.push("");
|
|
2459
2652
|
lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
|
|
@@ -2472,6 +2665,20 @@ function formatReportMarkdown(data) {
|
|
|
2472
2665
|
}
|
|
2473
2666
|
}
|
|
2474
2667
|
lines.push("");
|
|
2668
|
+
lines.push("## Spec:SC=1:1 \u9055\u53CD");
|
|
2669
|
+
const specScIssues = data.issues.filter(
|
|
2670
|
+
(item) => item.code === "QFAI-TRACE-012"
|
|
2671
|
+
);
|
|
2672
|
+
if (specScIssues.length === 0) {
|
|
2673
|
+
lines.push("- (none)");
|
|
2674
|
+
} else {
|
|
2675
|
+
for (const item of specScIssues) {
|
|
2676
|
+
const location = item.file ?? "(unknown)";
|
|
2677
|
+
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
2678
|
+
lines.push(`- ${location}: ${refs}`);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
lines.push("");
|
|
2475
2682
|
lines.push("## Hotspots");
|
|
2476
2683
|
const hotspots = buildHotspots(data.issues);
|
|
2477
2684
|
if (hotspots.length === 0) {
|
|
@@ -2582,9 +2789,22 @@ function formatIdLine(label, values) {
|
|
|
2582
2789
|
}
|
|
2583
2790
|
return `- ${label}: ${values.join(", ")}`;
|
|
2584
2791
|
}
|
|
2792
|
+
function formatList(values) {
|
|
2793
|
+
if (values.length === 0) {
|
|
2794
|
+
return "(none)";
|
|
2795
|
+
}
|
|
2796
|
+
return values.join(", ");
|
|
2797
|
+
}
|
|
2585
2798
|
function toSortedArray2(values) {
|
|
2586
2799
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
2587
2800
|
}
|
|
2801
|
+
function mapToSortedRecord(values) {
|
|
2802
|
+
const record2 = {};
|
|
2803
|
+
for (const [key, files] of values.entries()) {
|
|
2804
|
+
record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
2805
|
+
}
|
|
2806
|
+
return record2;
|
|
2807
|
+
}
|
|
2588
2808
|
function buildHotspots(issues) {
|
|
2589
2809
|
const map = /* @__PURE__ */ new Map();
|
|
2590
2810
|
for (const issue7 of issues) {
|
|
@@ -2609,10 +2829,10 @@ function buildHotspots(issues) {
|
|
|
2609
2829
|
|
|
2610
2830
|
// src/cli/commands/report.ts
|
|
2611
2831
|
async function runReport(options) {
|
|
2612
|
-
const root =
|
|
2832
|
+
const root = path15.resolve(options.root);
|
|
2613
2833
|
const configResult = await loadConfig(root);
|
|
2614
2834
|
const input = configResult.config.output.validateJsonPath;
|
|
2615
|
-
const inputPath =
|
|
2835
|
+
const inputPath = path15.isAbsolute(input) ? input : path15.resolve(root, input);
|
|
2616
2836
|
let validation;
|
|
2617
2837
|
try {
|
|
2618
2838
|
validation = await readValidationResult(inputPath);
|
|
@@ -2637,10 +2857,10 @@ async function runReport(options) {
|
|
|
2637
2857
|
const data = await createReportData(root, validation, configResult);
|
|
2638
2858
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
2639
2859
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
2640
|
-
const defaultOut = options.format === "json" ?
|
|
2860
|
+
const defaultOut = options.format === "json" ? path15.join(outRoot, "report.json") : path15.join(outRoot, "report.md");
|
|
2641
2861
|
const out = options.outPath ?? defaultOut;
|
|
2642
|
-
const outPath =
|
|
2643
|
-
await mkdir2(
|
|
2862
|
+
const outPath = path15.isAbsolute(out) ? out : path15.resolve(root, out);
|
|
2863
|
+
await mkdir2(path15.dirname(outPath), { recursive: true });
|
|
2644
2864
|
await writeFile(outPath, `${output}
|
|
2645
2865
|
`, "utf-8");
|
|
2646
2866
|
info(
|
|
@@ -2683,7 +2903,7 @@ function isMissingFileError5(error2) {
|
|
|
2683
2903
|
|
|
2684
2904
|
// src/cli/commands/validate.ts
|
|
2685
2905
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
|
|
2686
|
-
import
|
|
2906
|
+
import path16 from "path";
|
|
2687
2907
|
|
|
2688
2908
|
// src/cli/lib/failOn.ts
|
|
2689
2909
|
function shouldFail(result, failOn) {
|
|
@@ -2698,7 +2918,7 @@ function shouldFail(result, failOn) {
|
|
|
2698
2918
|
|
|
2699
2919
|
// src/cli/commands/validate.ts
|
|
2700
2920
|
async function runValidate(options) {
|
|
2701
|
-
const root =
|
|
2921
|
+
const root = path16.resolve(options.root);
|
|
2702
2922
|
const configResult = await loadConfig(root);
|
|
2703
2923
|
const result = await validateProject(root, configResult);
|
|
2704
2924
|
const format = options.format ?? "text";
|
|
@@ -2747,8 +2967,8 @@ function emitGitHub(issue7) {
|
|
|
2747
2967
|
);
|
|
2748
2968
|
}
|
|
2749
2969
|
async function emitJson(result, root, jsonPath) {
|
|
2750
|
-
const abs =
|
|
2751
|
-
await mkdir3(
|
|
2970
|
+
const abs = path16.isAbsolute(jsonPath) ? jsonPath : path16.resolve(root, jsonPath);
|
|
2971
|
+
await mkdir3(path16.dirname(abs), { recursive: true });
|
|
2752
2972
|
await writeFile2(abs, `${JSON.stringify(result, null, 2)}
|
|
2753
2973
|
`, "utf-8");
|
|
2754
2974
|
}
|