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.mjs
CHANGED
|
@@ -29,6 +29,8 @@ var defaultConfig = {
|
|
|
29
29
|
brMustHaveSc: true,
|
|
30
30
|
scMustTouchContracts: true,
|
|
31
31
|
scMustHaveTest: true,
|
|
32
|
+
testFileGlobs: [],
|
|
33
|
+
testFileExcludeGlobs: [],
|
|
32
34
|
scNoTestSeverity: "error",
|
|
33
35
|
allowOrphanContracts: false,
|
|
34
36
|
unknownContractIdSeverity: "error"
|
|
@@ -216,6 +218,20 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
216
218
|
configPath,
|
|
217
219
|
issues
|
|
218
220
|
),
|
|
221
|
+
testFileGlobs: readStringArray(
|
|
222
|
+
traceabilityRaw?.testFileGlobs,
|
|
223
|
+
base.traceability.testFileGlobs,
|
|
224
|
+
"validation.traceability.testFileGlobs",
|
|
225
|
+
configPath,
|
|
226
|
+
issues
|
|
227
|
+
),
|
|
228
|
+
testFileExcludeGlobs: readStringArray(
|
|
229
|
+
traceabilityRaw?.testFileExcludeGlobs,
|
|
230
|
+
base.traceability.testFileExcludeGlobs,
|
|
231
|
+
"validation.traceability.testFileExcludeGlobs",
|
|
232
|
+
configPath,
|
|
233
|
+
issues
|
|
234
|
+
),
|
|
219
235
|
scNoTestSeverity: readTraceabilitySeverity(
|
|
220
236
|
traceabilityRaw?.scNoTestSeverity,
|
|
221
237
|
base.traceability.scNoTestSeverity,
|
|
@@ -402,7 +418,7 @@ function isValidId(value, prefix) {
|
|
|
402
418
|
|
|
403
419
|
// src/core/report.ts
|
|
404
420
|
import { readFile as readFile11 } from "fs/promises";
|
|
405
|
-
import
|
|
421
|
+
import path11 from "path";
|
|
406
422
|
|
|
407
423
|
// src/core/discovery.ts
|
|
408
424
|
import { access as access2 } from "fs/promises";
|
|
@@ -410,6 +426,7 @@ import { access as access2 } from "fs/promises";
|
|
|
410
426
|
// src/core/fs.ts
|
|
411
427
|
import { access, readdir } from "fs/promises";
|
|
412
428
|
import path2 from "path";
|
|
429
|
+
import fg from "fast-glob";
|
|
413
430
|
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
414
431
|
"node_modules",
|
|
415
432
|
".git",
|
|
@@ -431,6 +448,18 @@ async function collectFiles(root, options = {}) {
|
|
|
431
448
|
await walk(root, root, ignoreDirs, extensions, entries);
|
|
432
449
|
return entries;
|
|
433
450
|
}
|
|
451
|
+
async function collectFilesByGlobs(root, options) {
|
|
452
|
+
if (options.globs.length === 0) {
|
|
453
|
+
return [];
|
|
454
|
+
}
|
|
455
|
+
return fg(options.globs, {
|
|
456
|
+
cwd: root,
|
|
457
|
+
ignore: options.ignore ?? [],
|
|
458
|
+
onlyFiles: true,
|
|
459
|
+
absolute: true,
|
|
460
|
+
unique: true
|
|
461
|
+
});
|
|
462
|
+
}
|
|
434
463
|
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
435
464
|
const items = await readdir(current, { withFileTypes: true });
|
|
436
465
|
for (const item of items) {
|
|
@@ -544,6 +573,7 @@ async function exists2(target) {
|
|
|
544
573
|
|
|
545
574
|
// src/core/traceability.ts
|
|
546
575
|
import { readFile as readFile2 } from "fs/promises";
|
|
576
|
+
import path4 from "path";
|
|
547
577
|
|
|
548
578
|
// src/core/gherkin/parse.ts
|
|
549
579
|
import {
|
|
@@ -705,6 +735,27 @@ function unique2(values) {
|
|
|
705
735
|
|
|
706
736
|
// src/core/traceability.ts
|
|
707
737
|
var SC_TAG_RE2 = /^SC-\d{4}$/;
|
|
738
|
+
var SC_TEST_ANNOTATION_RE = /\bQFAI:SC-(\d{4})\b/g;
|
|
739
|
+
var DEFAULT_TEST_FILE_EXCLUDE_GLOBS = [
|
|
740
|
+
"**/node_modules/**",
|
|
741
|
+
"**/.git/**",
|
|
742
|
+
"**/.qfai/**",
|
|
743
|
+
"**/dist/**",
|
|
744
|
+
"**/build/**",
|
|
745
|
+
"**/coverage/**",
|
|
746
|
+
"**/.next/**",
|
|
747
|
+
"**/out/**"
|
|
748
|
+
];
|
|
749
|
+
function extractAnnotatedScIds(text) {
|
|
750
|
+
const ids = /* @__PURE__ */ new Set();
|
|
751
|
+
for (const match of text.matchAll(SC_TEST_ANNOTATION_RE)) {
|
|
752
|
+
const suffix = match[1];
|
|
753
|
+
if (suffix) {
|
|
754
|
+
ids.add(`SC-${suffix}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return Array.from(ids);
|
|
758
|
+
}
|
|
708
759
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
709
760
|
const scIds = /* @__PURE__ */ new Set();
|
|
710
761
|
for (const file of scenarioFiles) {
|
|
@@ -723,14 +774,67 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
723
774
|
}
|
|
724
775
|
return scIds;
|
|
725
776
|
}
|
|
726
|
-
async function
|
|
777
|
+
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
778
|
+
const sources = /* @__PURE__ */ new Map();
|
|
779
|
+
for (const file of scenarioFiles) {
|
|
780
|
+
const text = await readFile2(file, "utf-8");
|
|
781
|
+
const { document, errors } = parseScenarioDocument(text, file);
|
|
782
|
+
if (!document || errors.length > 0) {
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
for (const scenario of document.scenarios) {
|
|
786
|
+
for (const tag of scenario.tags) {
|
|
787
|
+
if (!SC_TAG_RE2.test(tag)) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
const current = sources.get(tag) ?? /* @__PURE__ */ new Set();
|
|
791
|
+
current.add(file);
|
|
792
|
+
sources.set(tag, current);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return sources;
|
|
797
|
+
}
|
|
798
|
+
async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
727
799
|
const refs = /* @__PURE__ */ new Map();
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
800
|
+
const normalizedGlobs = normalizeGlobs(globs);
|
|
801
|
+
const normalizedExcludeGlobs = normalizeGlobs(excludeGlobs);
|
|
802
|
+
const mergedExcludeGlobs = Array.from(
|
|
803
|
+
/* @__PURE__ */ new Set([...DEFAULT_TEST_FILE_EXCLUDE_GLOBS, ...normalizedExcludeGlobs])
|
|
804
|
+
);
|
|
805
|
+
if (normalizedGlobs.length === 0) {
|
|
806
|
+
return {
|
|
807
|
+
refs,
|
|
808
|
+
scan: {
|
|
809
|
+
globs: normalizedGlobs,
|
|
810
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
811
|
+
matchedFileCount: 0
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
let files = [];
|
|
816
|
+
try {
|
|
817
|
+
files = await collectFilesByGlobs(root, {
|
|
818
|
+
globs: normalizedGlobs,
|
|
819
|
+
ignore: mergedExcludeGlobs
|
|
820
|
+
});
|
|
821
|
+
} catch (error) {
|
|
822
|
+
return {
|
|
823
|
+
refs,
|
|
824
|
+
scan: {
|
|
825
|
+
globs: normalizedGlobs,
|
|
826
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
827
|
+
matchedFileCount: 0
|
|
828
|
+
},
|
|
829
|
+
error: formatError3(error)
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const normalizedFiles = Array.from(
|
|
833
|
+
new Set(files.map((file) => path4.normalize(file)))
|
|
834
|
+
);
|
|
835
|
+
for (const file of normalizedFiles) {
|
|
732
836
|
const text = await readFile2(file, "utf-8");
|
|
733
|
-
const scIds =
|
|
837
|
+
const scIds = extractAnnotatedScIds(text);
|
|
734
838
|
if (scIds.length === 0) {
|
|
735
839
|
continue;
|
|
736
840
|
}
|
|
@@ -740,7 +844,14 @@ async function collectScTestReferences(testsRoot) {
|
|
|
740
844
|
refs.set(scId, current);
|
|
741
845
|
}
|
|
742
846
|
}
|
|
743
|
-
return
|
|
847
|
+
return {
|
|
848
|
+
refs,
|
|
849
|
+
scan: {
|
|
850
|
+
globs: normalizedGlobs,
|
|
851
|
+
excludeGlobs: mergedExcludeGlobs,
|
|
852
|
+
matchedFileCount: normalizedFiles.length
|
|
853
|
+
}
|
|
854
|
+
};
|
|
744
855
|
}
|
|
745
856
|
function buildScCoverage(scIds, refs) {
|
|
746
857
|
const sortedScIds = toSortedArray(scIds);
|
|
@@ -768,14 +879,23 @@ function buildScCoverage(scIds, refs) {
|
|
|
768
879
|
function toSortedArray(values) {
|
|
769
880
|
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
770
881
|
}
|
|
882
|
+
function normalizeGlobs(globs) {
|
|
883
|
+
return globs.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
884
|
+
}
|
|
885
|
+
function formatError3(error) {
|
|
886
|
+
if (error instanceof Error) {
|
|
887
|
+
return error.message;
|
|
888
|
+
}
|
|
889
|
+
return String(error);
|
|
890
|
+
}
|
|
771
891
|
|
|
772
892
|
// src/core/version.ts
|
|
773
893
|
import { readFile as readFile3 } from "fs/promises";
|
|
774
|
-
import
|
|
894
|
+
import path5 from "path";
|
|
775
895
|
import { fileURLToPath } from "url";
|
|
776
896
|
async function resolveToolVersion() {
|
|
777
|
-
if ("0.4.
|
|
778
|
-
return "0.4.
|
|
897
|
+
if ("0.4.2".length > 0) {
|
|
898
|
+
return "0.4.2";
|
|
779
899
|
}
|
|
780
900
|
try {
|
|
781
901
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -790,18 +910,18 @@ async function resolveToolVersion() {
|
|
|
790
910
|
function resolvePackageJsonPath() {
|
|
791
911
|
const base = import.meta.url;
|
|
792
912
|
const basePath = base.startsWith("file:") ? fileURLToPath(base) : base;
|
|
793
|
-
return
|
|
913
|
+
return path5.resolve(path5.dirname(basePath), "../../package.json");
|
|
794
914
|
}
|
|
795
915
|
|
|
796
916
|
// src/core/validators/contracts.ts
|
|
797
917
|
import { readFile as readFile4 } from "fs/promises";
|
|
798
|
-
import
|
|
918
|
+
import path7 from "path";
|
|
799
919
|
|
|
800
920
|
// src/core/contracts.ts
|
|
801
|
-
import
|
|
921
|
+
import path6 from "path";
|
|
802
922
|
import { parse as parseYaml2 } from "yaml";
|
|
803
923
|
function parseStructuredContract(file, text) {
|
|
804
|
-
const ext =
|
|
924
|
+
const ext = path6.extname(file).toLowerCase();
|
|
805
925
|
if (ext === ".json") {
|
|
806
926
|
return JSON.parse(text);
|
|
807
927
|
}
|
|
@@ -852,9 +972,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
852
972
|
async function validateContracts(root, config) {
|
|
853
973
|
const issues = [];
|
|
854
974
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
855
|
-
issues.push(...await validateUiContracts(
|
|
856
|
-
issues.push(...await validateApiContracts(
|
|
857
|
-
issues.push(...await validateDataContracts(
|
|
975
|
+
issues.push(...await validateUiContracts(path7.join(contractsRoot, "ui")));
|
|
976
|
+
issues.push(...await validateApiContracts(path7.join(contractsRoot, "api")));
|
|
977
|
+
issues.push(...await validateDataContracts(path7.join(contractsRoot, "db")));
|
|
858
978
|
return issues;
|
|
859
979
|
}
|
|
860
980
|
async function validateUiContracts(uiRoot) {
|
|
@@ -901,7 +1021,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
901
1021
|
issues.push(
|
|
902
1022
|
issue(
|
|
903
1023
|
"QFAI-CONTRACT-001",
|
|
904
|
-
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1024
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
905
1025
|
"error",
|
|
906
1026
|
file,
|
|
907
1027
|
"contracts.ui.parse"
|
|
@@ -968,7 +1088,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
968
1088
|
issues.push(
|
|
969
1089
|
issue(
|
|
970
1090
|
"QFAI-CONTRACT-001",
|
|
971
|
-
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${
|
|
1091
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
972
1092
|
"error",
|
|
973
1093
|
file,
|
|
974
1094
|
"contracts.api.parse"
|
|
@@ -1063,7 +1183,7 @@ function lintSql(text, file) {
|
|
|
1063
1183
|
function hasOpenApi(doc) {
|
|
1064
1184
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1065
1185
|
}
|
|
1066
|
-
function
|
|
1186
|
+
function formatError4(error) {
|
|
1067
1187
|
if (error instanceof Error) {
|
|
1068
1188
|
return error.message;
|
|
1069
1189
|
}
|
|
@@ -1089,7 +1209,7 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
1089
1209
|
|
|
1090
1210
|
// src/core/validators/delta.ts
|
|
1091
1211
|
import { readFile as readFile5 } from "fs/promises";
|
|
1092
|
-
import
|
|
1212
|
+
import path8 from "path";
|
|
1093
1213
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1094
1214
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1095
1215
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1103,7 +1223,7 @@ async function validateDeltas(root, config) {
|
|
|
1103
1223
|
}
|
|
1104
1224
|
const issues = [];
|
|
1105
1225
|
for (const pack of packs) {
|
|
1106
|
-
const deltaPath =
|
|
1226
|
+
const deltaPath = path8.join(pack, "delta.md");
|
|
1107
1227
|
let text;
|
|
1108
1228
|
try {
|
|
1109
1229
|
text = await readFile5(deltaPath, "utf-8");
|
|
@@ -1179,16 +1299,16 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
1179
1299
|
|
|
1180
1300
|
// src/core/validators/ids.ts
|
|
1181
1301
|
import { readFile as readFile7 } from "fs/promises";
|
|
1182
|
-
import
|
|
1302
|
+
import path10 from "path";
|
|
1183
1303
|
|
|
1184
1304
|
// src/core/contractIndex.ts
|
|
1185
1305
|
import { readFile as readFile6 } from "fs/promises";
|
|
1186
|
-
import
|
|
1306
|
+
import path9 from "path";
|
|
1187
1307
|
async function buildContractIndex(root, config) {
|
|
1188
1308
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1189
|
-
const uiRoot =
|
|
1190
|
-
const apiRoot =
|
|
1191
|
-
const dataRoot =
|
|
1309
|
+
const uiRoot = path9.join(contractsRoot, "ui");
|
|
1310
|
+
const apiRoot = path9.join(contractsRoot, "api");
|
|
1311
|
+
const dataRoot = path9.join(contractsRoot, "db");
|
|
1192
1312
|
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1193
1313
|
collectUiContractFiles(uiRoot),
|
|
1194
1314
|
collectApiContractFiles(apiRoot),
|
|
@@ -1426,7 +1546,7 @@ function recordId(out, id, file) {
|
|
|
1426
1546
|
}
|
|
1427
1547
|
function formatFileList(files, root) {
|
|
1428
1548
|
return files.map((file) => {
|
|
1429
|
-
const relative =
|
|
1549
|
+
const relative = path10.relative(root, file);
|
|
1430
1550
|
return relative.length > 0 ? relative : file;
|
|
1431
1551
|
}).join(", ");
|
|
1432
1552
|
}
|
|
@@ -1455,7 +1575,6 @@ var WHEN_PATTERN = /\bWhen\b/;
|
|
|
1455
1575
|
var THEN_PATTERN = /\bThen\b/;
|
|
1456
1576
|
var SC_TAG_RE4 = /^SC-\d{4}$/;
|
|
1457
1577
|
var SPEC_TAG_RE2 = /^SPEC-\d{4}$/;
|
|
1458
|
-
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1459
1578
|
async function validateScenarios(root, config) {
|
|
1460
1579
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1461
1580
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1535,17 +1654,7 @@ function validateScenarioContent(text, file) {
|
|
|
1535
1654
|
const featureSpecTags = document.featureTags.filter(
|
|
1536
1655
|
(tag) => SPEC_TAG_RE2.test(tag)
|
|
1537
1656
|
);
|
|
1538
|
-
if (featureSpecTags.length
|
|
1539
|
-
issues.push(
|
|
1540
|
-
issue4(
|
|
1541
|
-
"QFAI-SC-009",
|
|
1542
|
-
"Feature \u30BF\u30B0\u306B SPEC \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1543
|
-
"error",
|
|
1544
|
-
file,
|
|
1545
|
-
"scenario.featureSpec"
|
|
1546
|
-
)
|
|
1547
|
-
);
|
|
1548
|
-
} else if (featureSpecTags.length > 1) {
|
|
1657
|
+
if (featureSpecTags.length > 1) {
|
|
1549
1658
|
issues.push(
|
|
1550
1659
|
issue4(
|
|
1551
1660
|
"QFAI-SC-009",
|
|
@@ -1573,17 +1682,6 @@ function validateScenarioContent(text, file) {
|
|
|
1573
1682
|
)
|
|
1574
1683
|
);
|
|
1575
1684
|
}
|
|
1576
|
-
if (document.scenarios.length > 1) {
|
|
1577
|
-
issues.push(
|
|
1578
|
-
issue4(
|
|
1579
|
-
"QFAI-SC-011",
|
|
1580
|
-
`Scenario \u306F1\u3064\u306E\u307F\u8A31\u53EF\u3055\u308C\u3066\u3044\u307E\u3059\uFF08\u691C\u51FA: ${document.scenarios.length}\u4EF6\uFF09`,
|
|
1581
|
-
"error",
|
|
1582
|
-
file,
|
|
1583
|
-
"scenario.single"
|
|
1584
|
-
)
|
|
1585
|
-
);
|
|
1586
|
-
}
|
|
1587
1685
|
for (const scenario of document.scenarios) {
|
|
1588
1686
|
if (scenario.tags.length === 0) {
|
|
1589
1687
|
issues.push(
|
|
@@ -1604,12 +1702,6 @@ function validateScenarioContent(text, file) {
|
|
|
1604
1702
|
} else if (scTags.length > 1) {
|
|
1605
1703
|
missingTags.push(`SC(${scTags.length}\u4EF6/1\u4EF6\u5FC5\u9808)`);
|
|
1606
1704
|
}
|
|
1607
|
-
if (!scenario.tags.some((tag) => SPEC_TAG_RE2.test(tag))) {
|
|
1608
|
-
missingTags.push("SPEC");
|
|
1609
|
-
}
|
|
1610
|
-
if (!scenario.tags.some((tag) => BR_TAG_RE2.test(tag))) {
|
|
1611
|
-
missingTags.push("BR");
|
|
1612
|
-
}
|
|
1613
1705
|
if (missingTags.length > 0) {
|
|
1614
1706
|
issues.push(
|
|
1615
1707
|
issue4(
|
|
@@ -1844,9 +1936,8 @@ function isMissingFileError4(error) {
|
|
|
1844
1936
|
|
|
1845
1937
|
// src/core/validators/traceability.ts
|
|
1846
1938
|
import { readFile as readFile10 } from "fs/promises";
|
|
1847
|
-
var SC_TAG_RE5 = /^SC-\d{4}$/;
|
|
1848
1939
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
1849
|
-
var
|
|
1940
|
+
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
1850
1941
|
async function validateTraceability(root, config) {
|
|
1851
1942
|
const issues = [];
|
|
1852
1943
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
@@ -1873,28 +1964,6 @@ async function validateTraceability(root, config) {
|
|
|
1873
1964
|
}
|
|
1874
1965
|
const brIds = parsed.brs.map((br) => br.id);
|
|
1875
1966
|
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
1876
|
-
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
1877
|
-
...extractIds(text, "UI"),
|
|
1878
|
-
...extractIds(text, "API"),
|
|
1879
|
-
...extractIds(text, "DATA")
|
|
1880
|
-
]);
|
|
1881
|
-
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
1882
|
-
(id) => !contractIds.has(id)
|
|
1883
|
-
);
|
|
1884
|
-
if (unknownContractIds.length > 0) {
|
|
1885
|
-
issues.push(
|
|
1886
|
-
issue6(
|
|
1887
|
-
"QFAI-TRACE-009",
|
|
1888
|
-
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1889
|
-
", "
|
|
1890
|
-
)}`,
|
|
1891
|
-
"error",
|
|
1892
|
-
file,
|
|
1893
|
-
"traceability.specContractExists",
|
|
1894
|
-
unknownContractIds
|
|
1895
|
-
)
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
1967
|
if (parsed.specId) {
|
|
1899
1968
|
const current = specToBrIds.get(parsed.specId) ?? /* @__PURE__ */ new Set();
|
|
1900
1969
|
brIds.forEach((id) => current.add(id));
|
|
@@ -1909,16 +1978,42 @@ async function validateTraceability(root, config) {
|
|
|
1909
1978
|
continue;
|
|
1910
1979
|
}
|
|
1911
1980
|
const atoms = buildScenarioAtoms(document);
|
|
1981
|
+
const scIdsInFile = /* @__PURE__ */ new Set();
|
|
1912
1982
|
for (const [index, scenario] of document.scenarios.entries()) {
|
|
1913
1983
|
const atom = atoms[index];
|
|
1914
1984
|
if (!atom) {
|
|
1915
1985
|
continue;
|
|
1916
1986
|
}
|
|
1917
1987
|
const specTags = scenario.tags.filter((tag) => SPEC_TAG_RE3.test(tag));
|
|
1918
|
-
const brTags = scenario.tags.filter((tag) =>
|
|
1919
|
-
const scTags = scenario.tags.filter((tag) =>
|
|
1988
|
+
const brTags = scenario.tags.filter((tag) => BR_TAG_RE2.test(tag));
|
|
1989
|
+
const scTags = scenario.tags.filter((tag) => SC_TAG_RE2.test(tag));
|
|
1990
|
+
if (specTags.length === 0) {
|
|
1991
|
+
issues.push(
|
|
1992
|
+
issue6(
|
|
1993
|
+
"QFAI-TRACE-014",
|
|
1994
|
+
`Scenario \u304C SPEC \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
1995
|
+
"error",
|
|
1996
|
+
file,
|
|
1997
|
+
"traceability.scenarioSpecRequired"
|
|
1998
|
+
)
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
if (brTags.length === 0) {
|
|
2002
|
+
issues.push(
|
|
2003
|
+
issue6(
|
|
2004
|
+
"QFAI-TRACE-015",
|
|
2005
|
+
`Scenario \u304C BR \u30BF\u30B0\u3092\u6301\u3063\u3066\u3044\u307E\u305B\u3093: ${scenario.name}`,
|
|
2006
|
+
"error",
|
|
2007
|
+
file,
|
|
2008
|
+
"traceability.scenarioBrRequired"
|
|
2009
|
+
)
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
1920
2012
|
brTags.forEach((id) => brIdsInScenarios.add(id));
|
|
1921
|
-
scTags.forEach((id) =>
|
|
2013
|
+
scTags.forEach((id) => {
|
|
2014
|
+
scIdsInScenarios.add(id);
|
|
2015
|
+
scIdsInFile.add(id);
|
|
2016
|
+
});
|
|
1922
2017
|
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
1923
2018
|
if (atom.contractIds.length > 0) {
|
|
1924
2019
|
scTags.forEach((id) => scWithContracts.add(id));
|
|
@@ -1996,6 +2091,22 @@ async function validateTraceability(root, config) {
|
|
|
1996
2091
|
}
|
|
1997
2092
|
}
|
|
1998
2093
|
}
|
|
2094
|
+
if (scIdsInFile.size !== 1) {
|
|
2095
|
+
const invalidScIds = Array.from(scIdsInFile).sort(
|
|
2096
|
+
(a, b) => a.localeCompare(b)
|
|
2097
|
+
);
|
|
2098
|
+
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(", ")}`;
|
|
2099
|
+
issues.push(
|
|
2100
|
+
issue6(
|
|
2101
|
+
"QFAI-TRACE-012",
|
|
2102
|
+
`Spec entry \u304C Spec:SC=1:1 \u3092\u6E80\u305F\u3057\u3066\u3044\u307E\u305B\u3093: ${detail}`,
|
|
2103
|
+
"error",
|
|
2104
|
+
file,
|
|
2105
|
+
"traceability.specScOneToOne",
|
|
2106
|
+
invalidScIds
|
|
2107
|
+
)
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
1999
2110
|
}
|
|
2000
2111
|
if (upstreamIds.size === 0) {
|
|
2001
2112
|
return [
|
|
@@ -2044,21 +2155,62 @@ async function validateTraceability(root, config) {
|
|
|
2044
2155
|
);
|
|
2045
2156
|
}
|
|
2046
2157
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2158
|
+
const scRefsResult = await collectScTestReferences(
|
|
2159
|
+
root,
|
|
2160
|
+
config.validation.traceability.testFileGlobs,
|
|
2161
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2162
|
+
);
|
|
2163
|
+
const scTestRefs = scRefsResult.refs;
|
|
2164
|
+
const testFileScan = scRefsResult.scan;
|
|
2165
|
+
const hasScenarios = scIdsInScenarios.size > 0;
|
|
2166
|
+
const hasGlobConfig = testFileScan.globs.length > 0;
|
|
2167
|
+
const hasMatchedTests = testFileScan.matchedFileCount > 0;
|
|
2168
|
+
if (hasScenarios && (!hasGlobConfig || !hasMatchedTests || scRefsResult.error)) {
|
|
2169
|
+
const detail = scRefsResult.error ? `\uFF08\u8A73\u7D30: ${scRefsResult.error}\uFF09` : "";
|
|
2170
|
+
issues.push(
|
|
2171
|
+
issue6(
|
|
2172
|
+
"QFAI-TRACE-013",
|
|
2173
|
+
`\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}`,
|
|
2174
|
+
"error",
|
|
2175
|
+
testsRoot,
|
|
2176
|
+
"traceability.testFileGlobs"
|
|
2177
|
+
)
|
|
2178
|
+
);
|
|
2179
|
+
} else {
|
|
2180
|
+
if (config.validation.traceability.scMustHaveTest && scIdsInScenarios.size) {
|
|
2181
|
+
const scWithoutTests = Array.from(scIdsInScenarios).filter((id) => {
|
|
2182
|
+
const refs = scTestRefs.get(id);
|
|
2183
|
+
return !refs || refs.size === 0;
|
|
2184
|
+
});
|
|
2185
|
+
if (scWithoutTests.length > 0) {
|
|
2186
|
+
issues.push(
|
|
2187
|
+
issue6(
|
|
2188
|
+
"QFAI-TRACE-010",
|
|
2189
|
+
`SC \u304C\u30C6\u30B9\u30C8\u3067\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${scWithoutTests.join(
|
|
2190
|
+
", "
|
|
2191
|
+
)}\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`,
|
|
2192
|
+
config.validation.traceability.scNoTestSeverity,
|
|
2193
|
+
testsRoot,
|
|
2194
|
+
"traceability.scMustHaveTest",
|
|
2195
|
+
scWithoutTests
|
|
2196
|
+
)
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
const unknownScIds = Array.from(scTestRefs.keys()).filter(
|
|
2201
|
+
(id) => !scIdsInScenarios.has(id)
|
|
2202
|
+
);
|
|
2203
|
+
if (unknownScIds.length > 0) {
|
|
2054
2204
|
issues.push(
|
|
2055
2205
|
issue6(
|
|
2056
|
-
"QFAI-TRACE-
|
|
2057
|
-
|
|
2058
|
-
|
|
2206
|
+
"QFAI-TRACE-011",
|
|
2207
|
+
`\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(
|
|
2208
|
+
", "
|
|
2209
|
+
)}`,
|
|
2210
|
+
"error",
|
|
2059
2211
|
testsRoot,
|
|
2060
|
-
"traceability.
|
|
2061
|
-
|
|
2212
|
+
"traceability.scUnknownInTests",
|
|
2213
|
+
unknownScIds
|
|
2062
2214
|
)
|
|
2063
2215
|
);
|
|
2064
2216
|
}
|
|
@@ -2121,8 +2273,8 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2121
2273
|
issues.push(
|
|
2122
2274
|
issue6(
|
|
2123
2275
|
"QFAI-TRACE-002",
|
|
2124
|
-
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
2125
|
-
"
|
|
2276
|
+
"\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",
|
|
2277
|
+
"info",
|
|
2126
2278
|
srcRoot,
|
|
2127
2279
|
"traceability.codeReferences"
|
|
2128
2280
|
)
|
|
@@ -2165,11 +2317,24 @@ async function validateProject(root, configResult) {
|
|
|
2165
2317
|
...await validateDefinedIds(root, config),
|
|
2166
2318
|
...await validateTraceability(root, config)
|
|
2167
2319
|
];
|
|
2320
|
+
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2321
|
+
const scenarioFiles = await collectScenarioFiles(specsRoot);
|
|
2322
|
+
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2323
|
+
const { refs: scTestRefs, scan: testFiles } = await collectScTestReferences(
|
|
2324
|
+
root,
|
|
2325
|
+
config.validation.traceability.testFileGlobs,
|
|
2326
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2327
|
+
);
|
|
2328
|
+
const scCoverage = buildScCoverage(scIds, scTestRefs);
|
|
2168
2329
|
const toolVersion = await resolveToolVersion();
|
|
2169
2330
|
return {
|
|
2170
2331
|
toolVersion,
|
|
2171
2332
|
issues,
|
|
2172
|
-
counts: countIssues(issues)
|
|
2333
|
+
counts: countIssues(issues),
|
|
2334
|
+
traceability: {
|
|
2335
|
+
sc: scCoverage,
|
|
2336
|
+
testFiles
|
|
2337
|
+
}
|
|
2173
2338
|
};
|
|
2174
2339
|
}
|
|
2175
2340
|
function countIssues(issues) {
|
|
@@ -2190,9 +2355,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2190
2355
|
const configPath = resolved.configPath;
|
|
2191
2356
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2192
2357
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2193
|
-
const apiRoot =
|
|
2194
|
-
const uiRoot =
|
|
2195
|
-
const dbRoot =
|
|
2358
|
+
const apiRoot = path11.join(contractsRoot, "api");
|
|
2359
|
+
const uiRoot = path11.join(contractsRoot, "ui");
|
|
2360
|
+
const dbRoot = path11.join(contractsRoot, "db");
|
|
2196
2361
|
const srcRoot = resolvePath(root, config, "srcDir");
|
|
2197
2362
|
const testsRoot = resolvePath(root, config, "testsDir");
|
|
2198
2363
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2219,8 +2384,15 @@ async function createReportData(root, validation, configResult) {
|
|
|
2219
2384
|
testsRoot
|
|
2220
2385
|
);
|
|
2221
2386
|
const scIds = await collectScIdsFromScenarioFiles(scenarioFiles);
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2387
|
+
const scRefsResult = await collectScTestReferences(
|
|
2388
|
+
root,
|
|
2389
|
+
config.validation.traceability.testFileGlobs,
|
|
2390
|
+
config.validation.traceability.testFileExcludeGlobs
|
|
2391
|
+
);
|
|
2392
|
+
const scCoverage = validation?.traceability?.sc ?? buildScCoverage(scIds, scRefsResult.refs);
|
|
2393
|
+
const testFiles = validation?.traceability?.testFiles ?? scRefsResult.scan;
|
|
2394
|
+
const scSources = await collectScIdSourcesFromScenarioFiles(scenarioFiles);
|
|
2395
|
+
const scSourceRecord = mapToSortedRecord(scSources);
|
|
2224
2396
|
const resolvedValidation = validation ?? await validateProject(root, resolved);
|
|
2225
2397
|
const version = await resolveToolVersion();
|
|
2226
2398
|
return {
|
|
@@ -2250,7 +2422,9 @@ async function createReportData(root, validation, configResult) {
|
|
|
2250
2422
|
traceability: {
|
|
2251
2423
|
upstreamIdsFound: upstreamIds.size,
|
|
2252
2424
|
referencedInCodeOrTests: traceability,
|
|
2253
|
-
sc: scCoverage
|
|
2425
|
+
sc: scCoverage,
|
|
2426
|
+
scSources: scSourceRecord,
|
|
2427
|
+
testFiles
|
|
2254
2428
|
},
|
|
2255
2429
|
issues: resolvedValidation.issues
|
|
2256
2430
|
};
|
|
@@ -2291,10 +2465,29 @@ function formatReportMarkdown(data) {
|
|
|
2291
2465
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2292
2466
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
2293
2467
|
lines.push(`- missing: ${data.traceability.sc.missing}`);
|
|
2468
|
+
lines.push(
|
|
2469
|
+
`- testFileGlobs: ${formatList(data.traceability.testFiles.globs)}`
|
|
2470
|
+
);
|
|
2471
|
+
lines.push(
|
|
2472
|
+
`- testFileExcludeGlobs: ${formatList(
|
|
2473
|
+
data.traceability.testFiles.excludeGlobs
|
|
2474
|
+
)}`
|
|
2475
|
+
);
|
|
2476
|
+
lines.push(
|
|
2477
|
+
`- testFileCount: ${data.traceability.testFiles.matchedFileCount}`
|
|
2478
|
+
);
|
|
2294
2479
|
if (data.traceability.sc.missingIds.length === 0) {
|
|
2295
2480
|
lines.push("- missingIds: (none)");
|
|
2296
2481
|
} else {
|
|
2297
|
-
|
|
2482
|
+
const sources = data.traceability.scSources;
|
|
2483
|
+
const missingWithSources = data.traceability.sc.missingIds.map((id) => {
|
|
2484
|
+
const files = sources[id] ?? [];
|
|
2485
|
+
if (files.length === 0) {
|
|
2486
|
+
return id;
|
|
2487
|
+
}
|
|
2488
|
+
return `${id} (${files.join(", ")})`;
|
|
2489
|
+
});
|
|
2490
|
+
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2298
2491
|
}
|
|
2299
2492
|
lines.push("");
|
|
2300
2493
|
lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
|
|
@@ -2313,6 +2506,20 @@ function formatReportMarkdown(data) {
|
|
|
2313
2506
|
}
|
|
2314
2507
|
}
|
|
2315
2508
|
lines.push("");
|
|
2509
|
+
lines.push("## Spec:SC=1:1 \u9055\u53CD");
|
|
2510
|
+
const specScIssues = data.issues.filter(
|
|
2511
|
+
(item) => item.code === "QFAI-TRACE-012"
|
|
2512
|
+
);
|
|
2513
|
+
if (specScIssues.length === 0) {
|
|
2514
|
+
lines.push("- (none)");
|
|
2515
|
+
} else {
|
|
2516
|
+
for (const item of specScIssues) {
|
|
2517
|
+
const location = item.file ?? "(unknown)";
|
|
2518
|
+
const refs = item.refs && item.refs.length > 0 ? item.refs.join(", ") : item.message;
|
|
2519
|
+
lines.push(`- ${location}: ${refs}`);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
lines.push("");
|
|
2316
2523
|
lines.push("## Hotspots");
|
|
2317
2524
|
const hotspots = buildHotspots(data.issues);
|
|
2318
2525
|
if (hotspots.length === 0) {
|
|
@@ -2423,9 +2630,22 @@ function formatIdLine(label, values) {
|
|
|
2423
2630
|
}
|
|
2424
2631
|
return `- ${label}: ${values.join(", ")}`;
|
|
2425
2632
|
}
|
|
2633
|
+
function formatList(values) {
|
|
2634
|
+
if (values.length === 0) {
|
|
2635
|
+
return "(none)";
|
|
2636
|
+
}
|
|
2637
|
+
return values.join(", ");
|
|
2638
|
+
}
|
|
2426
2639
|
function toSortedArray2(values) {
|
|
2427
2640
|
return Array.from(values).sort((a, b) => a.localeCompare(b));
|
|
2428
2641
|
}
|
|
2642
|
+
function mapToSortedRecord(values) {
|
|
2643
|
+
const record2 = {};
|
|
2644
|
+
for (const [key, files] of values.entries()) {
|
|
2645
|
+
record2[key] = Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
2646
|
+
}
|
|
2647
|
+
return record2;
|
|
2648
|
+
}
|
|
2429
2649
|
function buildHotspots(issues) {
|
|
2430
2650
|
const map = /* @__PURE__ */ new Map();
|
|
2431
2651
|
for (const issue7 of issues) {
|