qfai 0.2.5 → 0.2.8
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 +1 -1
- package/assets/init/.qfai/README.md +42 -0
- package/assets/init/.qfai/contracts/README.md +61 -0
- package/assets/init/{qfai → .qfai}/contracts/api/api-0001-sample.yaml +3 -0
- package/assets/init/{qfai → .qfai}/contracts/db/db-0001-sample.sql +3 -2
- package/assets/init/.qfai/contracts/ui/ui-0001-sample.yaml +6 -0
- package/assets/init/.qfai/out/README.md +17 -0
- package/assets/init/.qfai/prompts/README.md +32 -0
- package/assets/init/{qfai → .qfai}/prompts/makeBusinessFlow.md +1 -1
- package/assets/init/{qfai → .qfai}/prompts/makeOverview.md +1 -1
- package/assets/init/.qfai/spec/README.md +80 -0
- package/assets/init/.qfai/spec/decisions/ADR-0001.md +9 -0
- package/assets/init/.qfai/spec/decisions/README.md +36 -0
- package/assets/init/.qfai/spec/scenarios/scenarios.feature +6 -0
- package/assets/init/.qfai/spec/spec-0001-sample.md +36 -0
- package/assets/init/root/qfai.config.yaml +8 -8
- package/dist/cli/index.cjs +498 -206
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.mjs +495 -203
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +471 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +135 -2
- package/dist/index.mjs +470 -177
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/assets/init/qfai/README.md +0 -6
- package/assets/init/qfai/contracts/ui/ui-0001-sample.yaml +0 -4
- package/assets/init/qfai/spec/decisions/ADR-0001.md +0 -7
- package/assets/init/qfai/spec/scenarios.feature +0 -6
- package/assets/init/qfai/spec/spec-0001-sample.md +0 -29
- 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 -8
- package/dist/cli/commands/report.d.ts.map +0 -1
- package/dist/cli/commands/report.js +0 -83
- package/dist/cli/commands/report.js.map +0 -1
- package/dist/cli/commands/validate.d.ts +0 -10
- package/dist/cli/commands/validate.d.ts.map +0 -1
- package/dist/cli/commands/validate.js +0 -66
- 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 -19
- package/dist/cli/lib/args.d.ts.map +0 -1
- package/dist/cli/lib/args.js +0 -107
- 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 -8
- 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 -73
- 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 -224
- package/dist/core/config.js.map +0 -1
- package/dist/core/discovery.d.ts +0 -11
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -31
- 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/ids.d.ts +0 -5
- package/dist/core/ids.d.ts.map +0 -1
- package/dist/core/ids.js +0 -49
- package/dist/core/ids.js.map +0 -1
- package/dist/core/index.d.ts +0 -11
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -11
- package/dist/core/index.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 -238
- package/dist/core/report.js.map +0 -1
- package/dist/core/types.d.ts +0 -27
- 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 -32
- 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 -157
- package/dist/core/validators/contracts.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 -82
- 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 -69
- 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 -148
- 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
|
@@ -44,6 +44,7 @@ __export(src_exports, {
|
|
|
44
44
|
resolvePath: () => resolvePath,
|
|
45
45
|
resolveToolVersion: () => resolveToolVersion,
|
|
46
46
|
validateContracts: () => validateContracts,
|
|
47
|
+
validateDefinedIds: () => validateDefinedIds,
|
|
47
48
|
validateProject: () => validateProject,
|
|
48
49
|
validateScenarioContent: () => validateScenarioContent,
|
|
49
50
|
validateScenarios: () => validateScenarios,
|
|
@@ -59,14 +60,13 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
59
60
|
var import_yaml = require("yaml");
|
|
60
61
|
var defaultConfig = {
|
|
61
62
|
paths: {
|
|
62
|
-
specDir: "qfai/spec",
|
|
63
|
-
decisionsDir: "qfai/spec/decisions",
|
|
64
|
-
scenariosDir: "qfai/spec",
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
dataContractsDir: "qfai/contracts/db",
|
|
63
|
+
specDir: ".qfai/spec",
|
|
64
|
+
decisionsDir: ".qfai/spec/decisions",
|
|
65
|
+
scenariosDir: ".qfai/spec/scenarios",
|
|
66
|
+
contractsDir: ".qfai/contracts",
|
|
67
|
+
uiContractsDir: ".qfai/contracts/ui",
|
|
68
|
+
apiContractsDir: ".qfai/contracts/api",
|
|
69
|
+
dataContractsDir: ".qfai/contracts/db",
|
|
70
70
|
srcDir: "src",
|
|
71
71
|
testsDir: "tests"
|
|
72
72
|
},
|
|
@@ -86,7 +86,8 @@ var defaultConfig = {
|
|
|
86
86
|
traceability: {
|
|
87
87
|
brMustHaveSc: true,
|
|
88
88
|
scMustTouchContracts: true,
|
|
89
|
-
allowOrphanContracts: false
|
|
89
|
+
allowOrphanContracts: false,
|
|
90
|
+
unknownContractIdSeverity: "error"
|
|
90
91
|
}
|
|
91
92
|
},
|
|
92
93
|
output: {
|
|
@@ -161,13 +162,6 @@ function normalizePaths(raw, configPath, issues) {
|
|
|
161
162
|
configPath,
|
|
162
163
|
issues
|
|
163
164
|
),
|
|
164
|
-
rulesDir: readString(
|
|
165
|
-
raw.rulesDir,
|
|
166
|
-
base.rulesDir,
|
|
167
|
-
"paths.rulesDir",
|
|
168
|
-
configPath,
|
|
169
|
-
issues
|
|
170
|
-
),
|
|
171
165
|
contractsDir: readString(
|
|
172
166
|
raw.contractsDir,
|
|
173
167
|
base.contractsDir,
|
|
@@ -292,6 +286,13 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
292
286
|
"validation.traceability.allowOrphanContracts",
|
|
293
287
|
configPath,
|
|
294
288
|
issues
|
|
289
|
+
),
|
|
290
|
+
unknownContractIdSeverity: readTraceabilitySeverity(
|
|
291
|
+
traceabilityRaw?.unknownContractIdSeverity,
|
|
292
|
+
base.traceability.unknownContractIdSeverity,
|
|
293
|
+
"validation.traceability.unknownContractIdSeverity",
|
|
294
|
+
configPath,
|
|
295
|
+
issues
|
|
295
296
|
)
|
|
296
297
|
}
|
|
297
298
|
};
|
|
@@ -371,6 +372,20 @@ function readFailOn(value, fallback, label, configPath, issues) {
|
|
|
371
372
|
}
|
|
372
373
|
return fallback;
|
|
373
374
|
}
|
|
375
|
+
function readTraceabilitySeverity(value, fallback, label, configPath, issues) {
|
|
376
|
+
if (value === "warning" || value === "error") {
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
if (value !== void 0) {
|
|
380
|
+
issues.push(
|
|
381
|
+
configIssue(
|
|
382
|
+
configPath,
|
|
383
|
+
`${label} \u306F warning|error \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
|
|
384
|
+
)
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return fallback;
|
|
388
|
+
}
|
|
374
389
|
function readOutputFormat(value, fallback, label, configPath, issues) {
|
|
375
390
|
if (value === "text" || value === "json" || value === "github") {
|
|
376
391
|
return value;
|
|
@@ -411,13 +426,15 @@ function isRecord(value) {
|
|
|
411
426
|
}
|
|
412
427
|
|
|
413
428
|
// src/core/ids.ts
|
|
414
|
-
var
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
429
|
+
var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
|
|
430
|
+
var STRICT_ID_PATTERNS = {
|
|
431
|
+
SPEC: /\bSPEC-\d{4}\b/g,
|
|
432
|
+
BR: /\bBR-\d{4}\b/g,
|
|
433
|
+
SC: /\bSC-\d{4}\b/g,
|
|
434
|
+
UI: /\bUI-\d{4}\b/g,
|
|
435
|
+
API: /\bAPI-\d{4}\b/g,
|
|
436
|
+
DATA: /\bDATA-\d{4}\b/g,
|
|
437
|
+
ADR: /\bADR-\d{4}\b/g
|
|
421
438
|
};
|
|
422
439
|
var LOOSE_ID_PATTERNS = {
|
|
423
440
|
SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
|
|
@@ -425,16 +442,17 @@ var LOOSE_ID_PATTERNS = {
|
|
|
425
442
|
SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
|
|
426
443
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
427
444
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
428
|
-
DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi
|
|
445
|
+
DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi,
|
|
446
|
+
ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
|
|
429
447
|
};
|
|
430
448
|
function extractIds(text, prefix) {
|
|
431
|
-
const pattern =
|
|
449
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
432
450
|
const matches = text.match(pattern);
|
|
433
451
|
return unique(matches ?? []);
|
|
434
452
|
}
|
|
435
453
|
function extractAllIds(text) {
|
|
436
454
|
const all = [];
|
|
437
|
-
|
|
455
|
+
ID_PREFIXES.forEach((prefix) => {
|
|
438
456
|
all.push(...extractIds(text, prefix));
|
|
439
457
|
});
|
|
440
458
|
return unique(all);
|
|
@@ -455,13 +473,13 @@ function unique(values) {
|
|
|
455
473
|
return Array.from(new Set(values));
|
|
456
474
|
}
|
|
457
475
|
function isValidId(value, prefix) {
|
|
458
|
-
const pattern =
|
|
476
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
459
477
|
const strict = new RegExp(pattern.source);
|
|
460
478
|
return strict.test(value);
|
|
461
479
|
}
|
|
462
480
|
|
|
463
481
|
// src/core/report.ts
|
|
464
|
-
var
|
|
482
|
+
var import_promises10 = require("fs/promises");
|
|
465
483
|
|
|
466
484
|
// src/core/discovery.ts
|
|
467
485
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
@@ -522,8 +540,7 @@ async function exists(target) {
|
|
|
522
540
|
}
|
|
523
541
|
|
|
524
542
|
// src/core/discovery.ts
|
|
525
|
-
var
|
|
526
|
-
var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/i;
|
|
543
|
+
var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/;
|
|
527
544
|
async function collectSpecFiles(specRoot) {
|
|
528
545
|
const files = await collectFiles(specRoot, { extensions: [".md"] });
|
|
529
546
|
return files.filter((file) => isSpecFile(file));
|
|
@@ -547,7 +564,7 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
|
|
|
547
564
|
}
|
|
548
565
|
function isSpecFile(filePath) {
|
|
549
566
|
const name = import_node_path3.default.basename(filePath).toLowerCase();
|
|
550
|
-
return
|
|
567
|
+
return SPEC_NAMED_PATTERN.test(name);
|
|
551
568
|
}
|
|
552
569
|
|
|
553
570
|
// src/core/types.ts
|
|
@@ -558,8 +575,8 @@ var import_promises3 = require("fs/promises");
|
|
|
558
575
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
559
576
|
var import_node_url = require("url");
|
|
560
577
|
async function resolveToolVersion() {
|
|
561
|
-
if ("0.2.
|
|
562
|
-
return "0.2.
|
|
578
|
+
if ("0.2.9".length > 0) {
|
|
579
|
+
return "0.2.9";
|
|
563
580
|
}
|
|
564
581
|
try {
|
|
565
582
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -579,8 +596,50 @@ function resolvePackageJsonPath() {
|
|
|
579
596
|
|
|
580
597
|
// src/core/validators/contracts.ts
|
|
581
598
|
var import_promises4 = require("fs/promises");
|
|
599
|
+
|
|
600
|
+
// src/core/contracts.ts
|
|
582
601
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
583
602
|
var import_yaml2 = require("yaml");
|
|
603
|
+
function parseStructuredContract(file, text) {
|
|
604
|
+
const ext = import_node_path5.default.extname(file).toLowerCase();
|
|
605
|
+
if (ext === ".json") {
|
|
606
|
+
return JSON.parse(text);
|
|
607
|
+
}
|
|
608
|
+
return (0, import_yaml2.parse)(text);
|
|
609
|
+
}
|
|
610
|
+
function extractUiContractIds(doc) {
|
|
611
|
+
const id = typeof doc.id === "string" ? doc.id : "";
|
|
612
|
+
return extractIds(id, "UI");
|
|
613
|
+
}
|
|
614
|
+
function extractApiContractIds(doc) {
|
|
615
|
+
const operationIds = /* @__PURE__ */ new Set();
|
|
616
|
+
collectOperationIds(doc, operationIds);
|
|
617
|
+
const ids = /* @__PURE__ */ new Set();
|
|
618
|
+
for (const operationId of operationIds) {
|
|
619
|
+
extractIds(operationId, "API").forEach((id) => ids.add(id));
|
|
620
|
+
}
|
|
621
|
+
return Array.from(ids);
|
|
622
|
+
}
|
|
623
|
+
function collectOperationIds(value, out) {
|
|
624
|
+
if (!value || typeof value !== "object") {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (Array.isArray(value)) {
|
|
628
|
+
for (const item of value) {
|
|
629
|
+
collectOperationIds(item, out);
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
634
|
+
if (key === "operationId" && typeof entry === "string") {
|
|
635
|
+
out.add(entry);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
collectOperationIds(entry, out);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/core/validators/contracts.ts
|
|
584
643
|
var SQL_DANGEROUS_PATTERNS = [
|
|
585
644
|
{ pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
|
|
586
645
|
{ pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
|
|
@@ -629,12 +688,13 @@ async function validateUiContracts(uiRoot) {
|
|
|
629
688
|
"SC",
|
|
630
689
|
"UI",
|
|
631
690
|
"API",
|
|
632
|
-
"DATA"
|
|
691
|
+
"DATA",
|
|
692
|
+
"ADR"
|
|
633
693
|
]);
|
|
634
694
|
if (invalidIds.length > 0) {
|
|
635
695
|
issues.push(
|
|
636
696
|
issue(
|
|
637
|
-
"
|
|
697
|
+
"QFAI-ID-002",
|
|
638
698
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
639
699
|
"error",
|
|
640
700
|
file,
|
|
@@ -643,30 +703,32 @@ async function validateUiContracts(uiRoot) {
|
|
|
643
703
|
)
|
|
644
704
|
);
|
|
645
705
|
}
|
|
706
|
+
let doc;
|
|
646
707
|
try {
|
|
647
|
-
|
|
648
|
-
const id = typeof doc.id === "string" ? doc.id : "";
|
|
649
|
-
if (!id.startsWith("UI-")) {
|
|
650
|
-
issues.push(
|
|
651
|
-
issue(
|
|
652
|
-
"QFAI-UI-001",
|
|
653
|
-
"UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
654
|
-
"error",
|
|
655
|
-
file,
|
|
656
|
-
"contracts.ui.id"
|
|
657
|
-
)
|
|
658
|
-
);
|
|
659
|
-
}
|
|
708
|
+
doc = parseStructuredContract(file, text);
|
|
660
709
|
} catch (error) {
|
|
661
710
|
issues.push(
|
|
662
711
|
issue(
|
|
663
|
-
"QFAI-
|
|
664
|
-
`UI
|
|
712
|
+
"QFAI-CONTRACT-001",
|
|
713
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
|
|
665
714
|
"error",
|
|
666
715
|
file,
|
|
667
716
|
"contracts.ui.parse"
|
|
668
717
|
)
|
|
669
718
|
);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
const uiIds = extractUiContractIds(doc);
|
|
722
|
+
if (uiIds.length === 0) {
|
|
723
|
+
issues.push(
|
|
724
|
+
issue(
|
|
725
|
+
"QFAI-CONTRACT-002",
|
|
726
|
+
`UI \u5951\u7D04\u306B ID(UI-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
727
|
+
"error",
|
|
728
|
+
file,
|
|
729
|
+
"contracts.ui.id"
|
|
730
|
+
)
|
|
731
|
+
);
|
|
670
732
|
}
|
|
671
733
|
}
|
|
672
734
|
return issues;
|
|
@@ -693,12 +755,13 @@ async function validateApiContracts(apiRoot) {
|
|
|
693
755
|
"SC",
|
|
694
756
|
"UI",
|
|
695
757
|
"API",
|
|
696
|
-
"DATA"
|
|
758
|
+
"DATA",
|
|
759
|
+
"ADR"
|
|
697
760
|
]);
|
|
698
761
|
if (invalidIds.length > 0) {
|
|
699
762
|
issues.push(
|
|
700
763
|
issue(
|
|
701
|
-
"
|
|
764
|
+
"QFAI-ID-002",
|
|
702
765
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
703
766
|
"error",
|
|
704
767
|
file,
|
|
@@ -707,29 +770,43 @@ async function validateApiContracts(apiRoot) {
|
|
|
707
770
|
)
|
|
708
771
|
);
|
|
709
772
|
}
|
|
773
|
+
let doc;
|
|
710
774
|
try {
|
|
711
|
-
|
|
712
|
-
if (!doc || !hasOpenApi(doc)) {
|
|
713
|
-
issues.push(
|
|
714
|
-
issue(
|
|
715
|
-
"QFAI-API-001",
|
|
716
|
-
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
717
|
-
"error",
|
|
718
|
-
file,
|
|
719
|
-
"contracts.api.openapi"
|
|
720
|
-
)
|
|
721
|
-
);
|
|
722
|
-
}
|
|
775
|
+
doc = parseStructuredContract(file, text);
|
|
723
776
|
} catch (error) {
|
|
724
777
|
issues.push(
|
|
725
778
|
issue(
|
|
726
|
-
"QFAI-
|
|
727
|
-
`API \
|
|
779
|
+
"QFAI-CONTRACT-001",
|
|
780
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
|
|
728
781
|
"error",
|
|
729
782
|
file,
|
|
730
783
|
"contracts.api.parse"
|
|
731
784
|
)
|
|
732
785
|
);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (!hasOpenApi(doc)) {
|
|
789
|
+
issues.push(
|
|
790
|
+
issue(
|
|
791
|
+
"QFAI-API-001",
|
|
792
|
+
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
793
|
+
"error",
|
|
794
|
+
file,
|
|
795
|
+
"contracts.api.openapi"
|
|
796
|
+
)
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
const apiIds = extractApiContractIds(doc);
|
|
800
|
+
if (apiIds.length === 0) {
|
|
801
|
+
issues.push(
|
|
802
|
+
issue(
|
|
803
|
+
"QFAI-CONTRACT-002",
|
|
804
|
+
`API \u5951\u7D04\u306B ID(API-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
805
|
+
"error",
|
|
806
|
+
file,
|
|
807
|
+
"contracts.api.id"
|
|
808
|
+
)
|
|
809
|
+
);
|
|
733
810
|
}
|
|
734
811
|
}
|
|
735
812
|
return issues;
|
|
@@ -756,12 +833,13 @@ async function validateDataContracts(dataRoot) {
|
|
|
756
833
|
"SC",
|
|
757
834
|
"UI",
|
|
758
835
|
"API",
|
|
759
|
-
"DATA"
|
|
836
|
+
"DATA",
|
|
837
|
+
"ADR"
|
|
760
838
|
]);
|
|
761
839
|
if (invalidIds.length > 0) {
|
|
762
840
|
issues.push(
|
|
763
841
|
issue(
|
|
764
|
-
"
|
|
842
|
+
"QFAI-ID-002",
|
|
765
843
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
766
844
|
"error",
|
|
767
845
|
file,
|
|
@@ -791,13 +869,6 @@ function lintSql(text, file) {
|
|
|
791
869
|
}
|
|
792
870
|
return issues;
|
|
793
871
|
}
|
|
794
|
-
function parseStructured(file, text) {
|
|
795
|
-
const ext = import_node_path5.default.extname(file).toLowerCase();
|
|
796
|
-
if (ext === ".json") {
|
|
797
|
-
return JSON.parse(text);
|
|
798
|
-
}
|
|
799
|
-
return (0, import_yaml2.parse)(text);
|
|
800
|
-
}
|
|
801
872
|
function hasOpenApi(doc) {
|
|
802
873
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
803
874
|
}
|
|
@@ -808,25 +879,165 @@ function formatError2(error) {
|
|
|
808
879
|
return String(error);
|
|
809
880
|
}
|
|
810
881
|
function issue(code, message, severity, file, rule, refs) {
|
|
811
|
-
const
|
|
882
|
+
const issue6 = {
|
|
812
883
|
code,
|
|
813
884
|
severity,
|
|
814
885
|
message
|
|
815
886
|
};
|
|
816
887
|
if (file) {
|
|
817
|
-
|
|
888
|
+
issue6.file = file;
|
|
818
889
|
}
|
|
819
890
|
if (rule) {
|
|
820
|
-
|
|
891
|
+
issue6.rule = rule;
|
|
821
892
|
}
|
|
822
893
|
if (refs && refs.length > 0) {
|
|
823
|
-
|
|
894
|
+
issue6.refs = refs;
|
|
824
895
|
}
|
|
825
|
-
return
|
|
896
|
+
return issue6;
|
|
826
897
|
}
|
|
827
898
|
|
|
828
|
-
// src/core/validators/
|
|
899
|
+
// src/core/validators/ids.ts
|
|
900
|
+
var import_promises6 = require("fs/promises");
|
|
901
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
902
|
+
|
|
903
|
+
// src/core/contractIndex.ts
|
|
829
904
|
var import_promises5 = require("fs/promises");
|
|
905
|
+
async function buildContractIndex(root, config) {
|
|
906
|
+
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
907
|
+
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
908
|
+
const dataRoot = resolvePath(root, config, "dataContractsDir");
|
|
909
|
+
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
910
|
+
collectUiContractFiles(uiRoot),
|
|
911
|
+
collectApiContractFiles(apiRoot),
|
|
912
|
+
collectDataContractFiles(dataRoot)
|
|
913
|
+
]);
|
|
914
|
+
const index = {
|
|
915
|
+
ids: /* @__PURE__ */ new Set(),
|
|
916
|
+
idToFiles: /* @__PURE__ */ new Map(),
|
|
917
|
+
files: { ui: uiFiles, api: apiFiles, data: dataFiles },
|
|
918
|
+
structuredParseFailedFiles: /* @__PURE__ */ new Set()
|
|
919
|
+
};
|
|
920
|
+
await indexUiContracts(uiFiles, index);
|
|
921
|
+
await indexApiContracts(apiFiles, index);
|
|
922
|
+
await indexDataContracts(dataFiles, index);
|
|
923
|
+
return index;
|
|
924
|
+
}
|
|
925
|
+
async function indexUiContracts(files, index) {
|
|
926
|
+
for (const file of files) {
|
|
927
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
928
|
+
try {
|
|
929
|
+
const doc = parseStructuredContract(file, text);
|
|
930
|
+
extractUiContractIds(doc).forEach((id) => record(index, id, file));
|
|
931
|
+
} catch {
|
|
932
|
+
index.structuredParseFailedFiles.add(file);
|
|
933
|
+
extractIds(text, "UI").forEach((id) => record(index, id, file));
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
async function indexApiContracts(files, index) {
|
|
938
|
+
for (const file of files) {
|
|
939
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
940
|
+
try {
|
|
941
|
+
const doc = parseStructuredContract(file, text);
|
|
942
|
+
extractApiContractIds(doc).forEach((id) => record(index, id, file));
|
|
943
|
+
} catch {
|
|
944
|
+
index.structuredParseFailedFiles.add(file);
|
|
945
|
+
extractIds(text, "API").forEach((id) => record(index, id, file));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async function indexDataContracts(files, index) {
|
|
950
|
+
for (const file of files) {
|
|
951
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
952
|
+
extractIds(text, "DATA").forEach((id) => record(index, id, file));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function record(index, id, file) {
|
|
956
|
+
index.ids.add(id);
|
|
957
|
+
const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
|
|
958
|
+
current.add(file);
|
|
959
|
+
index.idToFiles.set(id, current);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/core/validators/ids.ts
|
|
963
|
+
async function validateDefinedIds(root, config) {
|
|
964
|
+
const issues = [];
|
|
965
|
+
const specRoot = resolvePath(root, config, "specDir");
|
|
966
|
+
const scenarioRoot = resolvePath(root, config, "scenariosDir");
|
|
967
|
+
const specFiles = await collectSpecFiles(specRoot);
|
|
968
|
+
const scenarioFiles = await collectFiles(scenarioRoot, {
|
|
969
|
+
extensions: [".feature"]
|
|
970
|
+
});
|
|
971
|
+
const defined = /* @__PURE__ */ new Map();
|
|
972
|
+
await collectSpecDefinitionIds(specFiles, defined);
|
|
973
|
+
await collectScenarioDefinitionIds(scenarioFiles, defined);
|
|
974
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
975
|
+
for (const [id, files] of contractIndex.idToFiles.entries()) {
|
|
976
|
+
for (const file of files) {
|
|
977
|
+
recordId(defined, id, file);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
for (const [id, files] of defined.entries()) {
|
|
981
|
+
if (files.size <= 1) {
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const sorted = Array.from(files).sort();
|
|
985
|
+
issues.push(
|
|
986
|
+
issue2(
|
|
987
|
+
"QFAI-ID-001",
|
|
988
|
+
`ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
|
|
989
|
+
"error",
|
|
990
|
+
sorted[0],
|
|
991
|
+
"id.duplicate"
|
|
992
|
+
)
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
return issues;
|
|
996
|
+
}
|
|
997
|
+
async function collectSpecDefinitionIds(files, out) {
|
|
998
|
+
for (const file of files) {
|
|
999
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
1000
|
+
extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
|
|
1001
|
+
extractIds(text, "BR").forEach((id) => recordId(out, id, file));
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async function collectScenarioDefinitionIds(files, out) {
|
|
1005
|
+
for (const file of files) {
|
|
1006
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
1007
|
+
extractIds(text, "SC").forEach((id) => recordId(out, id, file));
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
function recordId(out, id, file) {
|
|
1011
|
+
const current = out.get(id) ?? /* @__PURE__ */ new Set();
|
|
1012
|
+
current.add(file);
|
|
1013
|
+
out.set(id, current);
|
|
1014
|
+
}
|
|
1015
|
+
function formatFileList(files, root) {
|
|
1016
|
+
return files.map((file) => {
|
|
1017
|
+
const relative = import_node_path6.default.relative(root, file);
|
|
1018
|
+
return relative.length > 0 ? relative : file;
|
|
1019
|
+
}).join(", ");
|
|
1020
|
+
}
|
|
1021
|
+
function issue2(code, message, severity, file, rule, refs) {
|
|
1022
|
+
const issue6 = {
|
|
1023
|
+
code,
|
|
1024
|
+
severity,
|
|
1025
|
+
message
|
|
1026
|
+
};
|
|
1027
|
+
if (file) {
|
|
1028
|
+
issue6.file = file;
|
|
1029
|
+
}
|
|
1030
|
+
if (rule) {
|
|
1031
|
+
issue6.rule = rule;
|
|
1032
|
+
}
|
|
1033
|
+
if (refs && refs.length > 0) {
|
|
1034
|
+
issue6.refs = refs;
|
|
1035
|
+
}
|
|
1036
|
+
return issue6;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/core/validators/scenario.ts
|
|
1040
|
+
var import_promises7 = require("fs/promises");
|
|
830
1041
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
831
1042
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
832
1043
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -837,7 +1048,7 @@ async function validateScenarios(root, config) {
|
|
|
837
1048
|
});
|
|
838
1049
|
if (files.length === 0) {
|
|
839
1050
|
return [
|
|
840
|
-
|
|
1051
|
+
issue3(
|
|
841
1052
|
"QFAI-SC-000",
|
|
842
1053
|
"Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
843
1054
|
"info",
|
|
@@ -848,7 +1059,7 @@ async function validateScenarios(root, config) {
|
|
|
848
1059
|
}
|
|
849
1060
|
const issues = [];
|
|
850
1061
|
for (const file of files) {
|
|
851
|
-
const text = await (0,
|
|
1062
|
+
const text = await (0, import_promises7.readFile)(file, "utf-8");
|
|
852
1063
|
issues.push(...validateScenarioContent(text, file));
|
|
853
1064
|
}
|
|
854
1065
|
return issues;
|
|
@@ -861,12 +1072,13 @@ function validateScenarioContent(text, file) {
|
|
|
861
1072
|
"SC",
|
|
862
1073
|
"UI",
|
|
863
1074
|
"API",
|
|
864
|
-
"DATA"
|
|
1075
|
+
"DATA",
|
|
1076
|
+
"ADR"
|
|
865
1077
|
]);
|
|
866
1078
|
if (invalidIds.length > 0) {
|
|
867
1079
|
issues.push(
|
|
868
|
-
|
|
869
|
-
"
|
|
1080
|
+
issue3(
|
|
1081
|
+
"QFAI-ID-002",
|
|
870
1082
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
871
1083
|
"error",
|
|
872
1084
|
file,
|
|
@@ -878,7 +1090,7 @@ function validateScenarioContent(text, file) {
|
|
|
878
1090
|
const scIds = extractIds(text, "SC");
|
|
879
1091
|
if (scIds.length === 0) {
|
|
880
1092
|
issues.push(
|
|
881
|
-
|
|
1093
|
+
issue3(
|
|
882
1094
|
"QFAI-SC-001",
|
|
883
1095
|
"SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
884
1096
|
"error",
|
|
@@ -890,7 +1102,7 @@ function validateScenarioContent(text, file) {
|
|
|
890
1102
|
const specIds = extractIds(text, "SPEC");
|
|
891
1103
|
if (specIds.length === 0) {
|
|
892
1104
|
issues.push(
|
|
893
|
-
|
|
1105
|
+
issue3(
|
|
894
1106
|
"QFAI-SC-002",
|
|
895
1107
|
"SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
896
1108
|
"error",
|
|
@@ -902,7 +1114,7 @@ function validateScenarioContent(text, file) {
|
|
|
902
1114
|
const brIds = extractIds(text, "BR");
|
|
903
1115
|
if (brIds.length === 0) {
|
|
904
1116
|
issues.push(
|
|
905
|
-
|
|
1117
|
+
issue3(
|
|
906
1118
|
"QFAI-SC-003",
|
|
907
1119
|
"SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
908
1120
|
"error",
|
|
@@ -923,7 +1135,7 @@ function validateScenarioContent(text, file) {
|
|
|
923
1135
|
}
|
|
924
1136
|
if (missingSteps.length > 0) {
|
|
925
1137
|
issues.push(
|
|
926
|
-
|
|
1138
|
+
issue3(
|
|
927
1139
|
"QFAI-SC-005",
|
|
928
1140
|
`Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
|
|
929
1141
|
"warning",
|
|
@@ -934,34 +1146,35 @@ function validateScenarioContent(text, file) {
|
|
|
934
1146
|
}
|
|
935
1147
|
return issues;
|
|
936
1148
|
}
|
|
937
|
-
function
|
|
938
|
-
const
|
|
1149
|
+
function issue3(code, message, severity, file, rule, refs) {
|
|
1150
|
+
const issue6 = {
|
|
939
1151
|
code,
|
|
940
1152
|
severity,
|
|
941
1153
|
message
|
|
942
1154
|
};
|
|
943
1155
|
if (file) {
|
|
944
|
-
|
|
1156
|
+
issue6.file = file;
|
|
945
1157
|
}
|
|
946
1158
|
if (rule) {
|
|
947
|
-
|
|
1159
|
+
issue6.rule = rule;
|
|
948
1160
|
}
|
|
949
1161
|
if (refs && refs.length > 0) {
|
|
950
|
-
|
|
1162
|
+
issue6.refs = refs;
|
|
951
1163
|
}
|
|
952
|
-
return
|
|
1164
|
+
return issue6;
|
|
953
1165
|
}
|
|
954
1166
|
|
|
955
1167
|
// src/core/validators/spec.ts
|
|
956
|
-
var
|
|
1168
|
+
var import_promises8 = require("fs/promises");
|
|
957
1169
|
async function validateSpecs(root, config) {
|
|
958
1170
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
959
1171
|
const files = await collectSpecFiles(specsRoot);
|
|
960
1172
|
if (files.length === 0) {
|
|
1173
|
+
const expected = "spec-0001-<slug>.md";
|
|
961
1174
|
return [
|
|
962
|
-
|
|
1175
|
+
issue4(
|
|
963
1176
|
"QFAI-SPEC-000",
|
|
964
|
-
|
|
1177
|
+
`Spec \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u914D\u7F6E\u5834\u6240: ${config.paths.specDir} / \u671F\u5F85\u30D1\u30BF\u30FC\u30F3: ${expected}`,
|
|
965
1178
|
"info",
|
|
966
1179
|
specsRoot,
|
|
967
1180
|
"spec.files"
|
|
@@ -970,7 +1183,7 @@ async function validateSpecs(root, config) {
|
|
|
970
1183
|
}
|
|
971
1184
|
const issues = [];
|
|
972
1185
|
for (const file of files) {
|
|
973
|
-
const text = await (0,
|
|
1186
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
974
1187
|
issues.push(
|
|
975
1188
|
...validateSpecContent(
|
|
976
1189
|
text,
|
|
@@ -989,12 +1202,13 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
989
1202
|
"SC",
|
|
990
1203
|
"UI",
|
|
991
1204
|
"API",
|
|
992
|
-
"DATA"
|
|
1205
|
+
"DATA",
|
|
1206
|
+
"ADR"
|
|
993
1207
|
]);
|
|
994
1208
|
if (invalidIds.length > 0) {
|
|
995
1209
|
issues.push(
|
|
996
|
-
|
|
997
|
-
"
|
|
1210
|
+
issue4(
|
|
1211
|
+
"QFAI-ID-002",
|
|
998
1212
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
999
1213
|
"error",
|
|
1000
1214
|
file,
|
|
@@ -1006,7 +1220,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1006
1220
|
const specIds = extractIds(text, "SPEC");
|
|
1007
1221
|
if (specIds.length === 0) {
|
|
1008
1222
|
issues.push(
|
|
1009
|
-
|
|
1223
|
+
issue4(
|
|
1010
1224
|
"QFAI-SPEC-001",
|
|
1011
1225
|
"SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1012
1226
|
"error",
|
|
@@ -1018,7 +1232,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1018
1232
|
const brIds = extractIds(text, "BR");
|
|
1019
1233
|
if (brIds.length === 0) {
|
|
1020
1234
|
issues.push(
|
|
1021
|
-
|
|
1235
|
+
issue4(
|
|
1022
1236
|
"QFAI-SPEC-002",
|
|
1023
1237
|
"BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1024
1238
|
"error",
|
|
@@ -1030,7 +1244,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1030
1244
|
const scIds = extractIds(text, "SC");
|
|
1031
1245
|
if (scIds.length > 0) {
|
|
1032
1246
|
issues.push(
|
|
1033
|
-
|
|
1247
|
+
issue4(
|
|
1034
1248
|
"QFAI-SPEC-003",
|
|
1035
1249
|
"Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
|
|
1036
1250
|
"warning",
|
|
@@ -1043,7 +1257,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1043
1257
|
for (const section of requiredSections) {
|
|
1044
1258
|
if (!text.includes(section)) {
|
|
1045
1259
|
issues.push(
|
|
1046
|
-
|
|
1260
|
+
issue4(
|
|
1047
1261
|
"QFAI-SPEC-004",
|
|
1048
1262
|
`\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
|
|
1049
1263
|
"error",
|
|
@@ -1055,26 +1269,26 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1055
1269
|
}
|
|
1056
1270
|
return issues;
|
|
1057
1271
|
}
|
|
1058
|
-
function
|
|
1059
|
-
const
|
|
1272
|
+
function issue4(code, message, severity, file, rule, refs) {
|
|
1273
|
+
const issue6 = {
|
|
1060
1274
|
code,
|
|
1061
1275
|
severity,
|
|
1062
1276
|
message
|
|
1063
1277
|
};
|
|
1064
1278
|
if (file) {
|
|
1065
|
-
|
|
1279
|
+
issue6.file = file;
|
|
1066
1280
|
}
|
|
1067
1281
|
if (rule) {
|
|
1068
|
-
|
|
1282
|
+
issue6.rule = rule;
|
|
1069
1283
|
}
|
|
1070
1284
|
if (refs && refs.length > 0) {
|
|
1071
|
-
|
|
1285
|
+
issue6.refs = refs;
|
|
1072
1286
|
}
|
|
1073
|
-
return
|
|
1287
|
+
return issue6;
|
|
1074
1288
|
}
|
|
1075
1289
|
|
|
1076
1290
|
// src/core/validators/traceability.ts
|
|
1077
|
-
var
|
|
1291
|
+
var import_promises9 = require("fs/promises");
|
|
1078
1292
|
async function validateTraceability(root, config) {
|
|
1079
1293
|
const issues = [];
|
|
1080
1294
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
@@ -1090,36 +1304,141 @@ async function validateTraceability(root, config) {
|
|
|
1090
1304
|
extensions: [".feature"]
|
|
1091
1305
|
});
|
|
1092
1306
|
const upstreamIds = /* @__PURE__ */ new Set();
|
|
1307
|
+
const specIds = /* @__PURE__ */ new Set();
|
|
1093
1308
|
const brIdsInSpecs = /* @__PURE__ */ new Set();
|
|
1094
1309
|
const brIdsInScenarios = /* @__PURE__ */ new Set();
|
|
1095
1310
|
const scIdsInScenarios = /* @__PURE__ */ new Set();
|
|
1096
1311
|
const scenarioContractIds = /* @__PURE__ */ new Set();
|
|
1097
1312
|
const scWithContracts = /* @__PURE__ */ new Set();
|
|
1098
|
-
|
|
1099
|
-
|
|
1313
|
+
const specToBrIds = /* @__PURE__ */ new Map();
|
|
1314
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
1315
|
+
const contractIds = contractIndex.ids;
|
|
1316
|
+
for (const file of specFiles) {
|
|
1317
|
+
const text = await (0, import_promises9.readFile)(file, "utf-8");
|
|
1318
|
+
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1319
|
+
const specIdsInFile = extractIds(text, "SPEC");
|
|
1320
|
+
specIdsInFile.forEach((id) => specIds.add(id));
|
|
1321
|
+
const brIds = extractIds(text, "BR");
|
|
1322
|
+
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
1323
|
+
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
1324
|
+
...extractIds(text, "UI"),
|
|
1325
|
+
...extractIds(text, "API"),
|
|
1326
|
+
...extractIds(text, "DATA")
|
|
1327
|
+
]);
|
|
1328
|
+
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
1329
|
+
(id) => !contractIds.has(id)
|
|
1330
|
+
);
|
|
1331
|
+
if (unknownContractIds.length > 0) {
|
|
1332
|
+
issues.push(
|
|
1333
|
+
issue5(
|
|
1334
|
+
"QFAI-TRACE-009",
|
|
1335
|
+
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1336
|
+
", "
|
|
1337
|
+
)}`,
|
|
1338
|
+
"error",
|
|
1339
|
+
file,
|
|
1340
|
+
"traceability.specContractExists",
|
|
1341
|
+
unknownContractIds
|
|
1342
|
+
)
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
for (const specId of specIdsInFile) {
|
|
1346
|
+
const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
|
|
1347
|
+
brIds.forEach((id) => current.add(id));
|
|
1348
|
+
specToBrIds.set(specId, current);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
for (const file of decisionFiles) {
|
|
1352
|
+
const text = await (0, import_promises9.readFile)(file, "utf-8");
|
|
1100
1353
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1101
|
-
extractIds(text, "BR").forEach((id) => brIdsInSpecs.add(id));
|
|
1102
1354
|
}
|
|
1103
1355
|
for (const file of scenarioFiles) {
|
|
1104
|
-
const text = await (0,
|
|
1356
|
+
const text = await (0, import_promises9.readFile)(file, "utf-8");
|
|
1105
1357
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1358
|
+
const specIdsInScenario = extractIds(text, "SPEC");
|
|
1106
1359
|
const brIds = extractIds(text, "BR");
|
|
1107
|
-
brIds.forEach((id) => brIdsInScenarios.add(id));
|
|
1108
1360
|
const scIds = extractIds(text, "SC");
|
|
1109
|
-
|
|
1110
|
-
const contractIds = [
|
|
1361
|
+
const scenarioIds = [
|
|
1111
1362
|
...extractIds(text, "UI"),
|
|
1112
1363
|
...extractIds(text, "API"),
|
|
1113
1364
|
...extractIds(text, "DATA")
|
|
1114
1365
|
];
|
|
1115
|
-
|
|
1116
|
-
|
|
1366
|
+
brIds.forEach((id) => brIdsInScenarios.add(id));
|
|
1367
|
+
scIds.forEach((id) => scIdsInScenarios.add(id));
|
|
1368
|
+
scenarioIds.forEach((id) => scenarioContractIds.add(id));
|
|
1369
|
+
if (scenarioIds.length > 0) {
|
|
1117
1370
|
scIds.forEach((id) => scWithContracts.add(id));
|
|
1118
1371
|
}
|
|
1372
|
+
const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
|
|
1373
|
+
if (unknownSpecIds.length > 0) {
|
|
1374
|
+
issues.push(
|
|
1375
|
+
issue5(
|
|
1376
|
+
"QFAI-TRACE-005",
|
|
1377
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
|
|
1378
|
+
"error",
|
|
1379
|
+
file,
|
|
1380
|
+
"traceability.scenarioSpecExists",
|
|
1381
|
+
unknownSpecIds
|
|
1382
|
+
)
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
|
|
1386
|
+
if (unknownBrIds.length > 0) {
|
|
1387
|
+
issues.push(
|
|
1388
|
+
issue5(
|
|
1389
|
+
"QFAI-TRACE-006",
|
|
1390
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
|
|
1391
|
+
"error",
|
|
1392
|
+
file,
|
|
1393
|
+
"traceability.scenarioBrExists",
|
|
1394
|
+
unknownBrIds
|
|
1395
|
+
)
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
|
|
1399
|
+
if (unknownContractIds.length > 0) {
|
|
1400
|
+
issues.push(
|
|
1401
|
+
issue5(
|
|
1402
|
+
"QFAI-TRACE-008",
|
|
1403
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1404
|
+
", "
|
|
1405
|
+
)}`,
|
|
1406
|
+
config.validation.traceability.unknownContractIdSeverity,
|
|
1407
|
+
file,
|
|
1408
|
+
"traceability.scenarioContractExists",
|
|
1409
|
+
unknownContractIds
|
|
1410
|
+
)
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
if (specIdsInScenario.length > 0) {
|
|
1414
|
+
const allowedBrIds = /* @__PURE__ */ new Set();
|
|
1415
|
+
for (const specId of specIdsInScenario) {
|
|
1416
|
+
const brIdsForSpec = specToBrIds.get(specId);
|
|
1417
|
+
if (!brIdsForSpec) {
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
brIdsForSpec.forEach((id) => allowedBrIds.add(id));
|
|
1421
|
+
}
|
|
1422
|
+
const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
|
|
1423
|
+
if (invalidBrIds.length > 0) {
|
|
1424
|
+
issues.push(
|
|
1425
|
+
issue5(
|
|
1426
|
+
"QFAI-TRACE-007",
|
|
1427
|
+
`Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
|
|
1428
|
+
", "
|
|
1429
|
+
)} (SPEC: ${specIdsInScenario.join(", ")})`,
|
|
1430
|
+
"error",
|
|
1431
|
+
file,
|
|
1432
|
+
"traceability.scenarioBrUnderSpec",
|
|
1433
|
+
invalidBrIds
|
|
1434
|
+
)
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1119
1438
|
}
|
|
1120
1439
|
if (upstreamIds.size === 0) {
|
|
1121
1440
|
return [
|
|
1122
|
-
|
|
1441
|
+
issue5(
|
|
1123
1442
|
"QFAI-TRACE-000",
|
|
1124
1443
|
"\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1125
1444
|
"info",
|
|
@@ -1134,7 +1453,7 @@ async function validateTraceability(root, config) {
|
|
|
1134
1453
|
);
|
|
1135
1454
|
if (orphanBrIds.length > 0) {
|
|
1136
1455
|
issues.push(
|
|
1137
|
-
|
|
1456
|
+
issue5(
|
|
1138
1457
|
"QFAI_TRACE_BR_ORPHAN",
|
|
1139
1458
|
`BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
|
|
1140
1459
|
"error",
|
|
@@ -1151,7 +1470,7 @@ async function validateTraceability(root, config) {
|
|
|
1151
1470
|
);
|
|
1152
1471
|
if (scWithoutContracts.length > 0) {
|
|
1153
1472
|
issues.push(
|
|
1154
|
-
|
|
1473
|
+
issue5(
|
|
1155
1474
|
"QFAI_TRACE_SC_NO_CONTRACT",
|
|
1156
1475
|
`SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
|
|
1157
1476
|
", "
|
|
@@ -1165,14 +1484,13 @@ async function validateTraceability(root, config) {
|
|
|
1165
1484
|
}
|
|
1166
1485
|
}
|
|
1167
1486
|
if (!config.validation.traceability.allowOrphanContracts) {
|
|
1168
|
-
const contractIds = await collectContractIds(root, config);
|
|
1169
1487
|
if (contractIds.size > 0) {
|
|
1170
1488
|
const orphanContracts = Array.from(contractIds).filter(
|
|
1171
1489
|
(id) => !scenarioContractIds.has(id)
|
|
1172
1490
|
);
|
|
1173
1491
|
if (orphanContracts.length > 0) {
|
|
1174
1492
|
issues.push(
|
|
1175
|
-
|
|
1493
|
+
issue5(
|
|
1176
1494
|
"QFAI_CONTRACT_ORPHAN",
|
|
1177
1495
|
`\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
|
|
1178
1496
|
"error",
|
|
@@ -1189,27 +1507,6 @@ async function validateTraceability(root, config) {
|
|
|
1189
1507
|
);
|
|
1190
1508
|
return issues;
|
|
1191
1509
|
}
|
|
1192
|
-
async function collectContractIds(root, config) {
|
|
1193
|
-
const contractIds = /* @__PURE__ */ new Set();
|
|
1194
|
-
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1195
|
-
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1196
|
-
const dataRoot = resolvePath(root, config, "dataContractsDir");
|
|
1197
|
-
const uiFiles = await collectUiContractFiles(uiRoot);
|
|
1198
|
-
const apiFiles = await collectApiContractFiles(apiRoot);
|
|
1199
|
-
const dataFiles = await collectDataContractFiles(dataRoot);
|
|
1200
|
-
await collectIdsFromFiles(uiFiles, ["UI"], contractIds);
|
|
1201
|
-
await collectIdsFromFiles(apiFiles, ["API"], contractIds);
|
|
1202
|
-
await collectIdsFromFiles(dataFiles, ["DATA"], contractIds);
|
|
1203
|
-
return contractIds;
|
|
1204
|
-
}
|
|
1205
|
-
async function collectIdsFromFiles(files, prefixes, out) {
|
|
1206
|
-
for (const file of files) {
|
|
1207
|
-
const text = await (0, import_promises7.readFile)(file, "utf-8");
|
|
1208
|
-
for (const prefix of prefixes) {
|
|
1209
|
-
extractIds(text, prefix).forEach((id) => out.add(id));
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
1510
|
async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
1214
1511
|
const issues = [];
|
|
1215
1512
|
const codeFiles = await collectFiles(srcRoot, {
|
|
@@ -1221,7 +1518,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1221
1518
|
const targetFiles = [...codeFiles, ...testFiles];
|
|
1222
1519
|
if (targetFiles.length === 0) {
|
|
1223
1520
|
issues.push(
|
|
1224
|
-
|
|
1521
|
+
issue5(
|
|
1225
1522
|
"QFAI-TRACE-001",
|
|
1226
1523
|
"\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1227
1524
|
"info",
|
|
@@ -1234,7 +1531,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1234
1531
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
1235
1532
|
let found = false;
|
|
1236
1533
|
for (const file of targetFiles) {
|
|
1237
|
-
const text = await (0,
|
|
1534
|
+
const text = await (0, import_promises9.readFile)(file, "utf-8");
|
|
1238
1535
|
if (pattern.test(text)) {
|
|
1239
1536
|
found = true;
|
|
1240
1537
|
break;
|
|
@@ -1242,7 +1539,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1242
1539
|
}
|
|
1243
1540
|
if (!found) {
|
|
1244
1541
|
issues.push(
|
|
1245
|
-
|
|
1542
|
+
issue5(
|
|
1246
1543
|
"QFAI-TRACE-002",
|
|
1247
1544
|
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
1248
1545
|
"warning",
|
|
@@ -1257,22 +1554,22 @@ function buildIdPattern(ids) {
|
|
|
1257
1554
|
const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
1258
1555
|
return new RegExp(`\\b(${escaped.join("|")})\\b`);
|
|
1259
1556
|
}
|
|
1260
|
-
function
|
|
1261
|
-
const
|
|
1557
|
+
function issue5(code, message, severity, file, rule, refs) {
|
|
1558
|
+
const issue6 = {
|
|
1262
1559
|
code,
|
|
1263
1560
|
severity,
|
|
1264
1561
|
message
|
|
1265
1562
|
};
|
|
1266
1563
|
if (file) {
|
|
1267
|
-
|
|
1564
|
+
issue6.file = file;
|
|
1268
1565
|
}
|
|
1269
1566
|
if (rule) {
|
|
1270
|
-
|
|
1567
|
+
issue6.rule = rule;
|
|
1271
1568
|
}
|
|
1272
1569
|
if (refs && refs.length > 0) {
|
|
1273
|
-
|
|
1570
|
+
issue6.refs = refs;
|
|
1274
1571
|
}
|
|
1275
|
-
return
|
|
1572
|
+
return issue6;
|
|
1276
1573
|
}
|
|
1277
1574
|
|
|
1278
1575
|
// src/core/validate.ts
|
|
@@ -1284,6 +1581,7 @@ async function validateProject(root, configResult) {
|
|
|
1284
1581
|
...await validateSpecs(root, config),
|
|
1285
1582
|
...await validateScenarios(root, config),
|
|
1286
1583
|
...await validateContracts(root, config),
|
|
1584
|
+
...await validateDefinedIds(root, config),
|
|
1287
1585
|
...await validateTraceability(root, config)
|
|
1288
1586
|
];
|
|
1289
1587
|
const toolVersion = await resolveToolVersion();
|
|
@@ -1296,8 +1594,8 @@ async function validateProject(root, configResult) {
|
|
|
1296
1594
|
}
|
|
1297
1595
|
function countIssues(issues) {
|
|
1298
1596
|
return issues.reduce(
|
|
1299
|
-
(acc,
|
|
1300
|
-
acc[
|
|
1597
|
+
(acc, issue6) => {
|
|
1598
|
+
acc[issue6.severity] += 1;
|
|
1301
1599
|
return acc;
|
|
1302
1600
|
},
|
|
1303
1601
|
{ info: 0, warning: 0, error: 0 }
|
|
@@ -1305,7 +1603,7 @@ function countIssues(issues) {
|
|
|
1305
1603
|
}
|
|
1306
1604
|
|
|
1307
1605
|
// src/core/report.ts
|
|
1308
|
-
var
|
|
1606
|
+
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
|
|
1309
1607
|
async function createReportData(root, validation, configResult) {
|
|
1310
1608
|
const resolved = configResult ?? await loadConfig(root);
|
|
1311
1609
|
const config = resolved.config;
|
|
@@ -1313,7 +1611,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1313
1611
|
const specRoot = resolvePath(root, config, "specDir");
|
|
1314
1612
|
const decisionsRoot = resolvePath(root, config, "decisionsDir");
|
|
1315
1613
|
const scenariosRoot = resolvePath(root, config, "scenariosDir");
|
|
1316
|
-
const rulesRoot = resolvePath(root, config, "rulesDir");
|
|
1317
1614
|
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1318
1615
|
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1319
1616
|
const dbRoot = resolvePath(root, config, "dataContractsDir");
|
|
@@ -1326,7 +1623,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1326
1623
|
const decisionFiles = await collectFiles(decisionsRoot, {
|
|
1327
1624
|
extensions: [".md"]
|
|
1328
1625
|
});
|
|
1329
|
-
const ruleFiles = await collectFiles(rulesRoot, { extensions: [".md"] });
|
|
1330
1626
|
const {
|
|
1331
1627
|
api: apiFiles,
|
|
1332
1628
|
ui: uiFiles,
|
|
@@ -1336,7 +1632,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1336
1632
|
...specFiles,
|
|
1337
1633
|
...scenarioFiles,
|
|
1338
1634
|
...decisionFiles,
|
|
1339
|
-
...ruleFiles,
|
|
1340
1635
|
...apiFiles,
|
|
1341
1636
|
...uiFiles,
|
|
1342
1637
|
...dbFiles
|
|
@@ -1362,7 +1657,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1362
1657
|
specs: specFiles.length,
|
|
1363
1658
|
scenarios: scenarioFiles.length,
|
|
1364
1659
|
decisions: decisionFiles.length,
|
|
1365
|
-
rules: ruleFiles.length,
|
|
1366
1660
|
contracts: {
|
|
1367
1661
|
api: apiFiles.length,
|
|
1368
1662
|
ui: uiFiles.length,
|
|
@@ -1397,7 +1691,6 @@ function formatReportMarkdown(data) {
|
|
|
1397
1691
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
1398
1692
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
1399
1693
|
lines.push(`- decisions: ${data.summary.decisions}`);
|
|
1400
|
-
lines.push(`- rules: ${data.summary.rules}`);
|
|
1401
1694
|
lines.push(
|
|
1402
1695
|
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
1403
1696
|
);
|
|
@@ -1433,7 +1726,7 @@ function formatReportMarkdown(data) {
|
|
|
1433
1726
|
lines.push("");
|
|
1434
1727
|
lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
|
|
1435
1728
|
const traceIssues = data.issues.filter(
|
|
1436
|
-
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code === "QFAI_CONTRACT_ORPHAN"
|
|
1729
|
+
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-") || item.code === "QFAI_CONTRACT_ORPHAN"
|
|
1437
1730
|
);
|
|
1438
1731
|
if (traceIssues.length === 0) {
|
|
1439
1732
|
lines.push("- (none)");
|
|
@@ -1473,8 +1766,8 @@ async function collectIds(files) {
|
|
|
1473
1766
|
DATA: /* @__PURE__ */ new Set()
|
|
1474
1767
|
};
|
|
1475
1768
|
for (const file of files) {
|
|
1476
|
-
const text = await (0,
|
|
1477
|
-
for (const prefix of
|
|
1769
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1770
|
+
for (const prefix of ID_PREFIXES2) {
|
|
1478
1771
|
const ids = extractIds(text, prefix);
|
|
1479
1772
|
ids.forEach((id) => result[prefix].add(id));
|
|
1480
1773
|
}
|
|
@@ -1491,7 +1784,7 @@ async function collectIds(files) {
|
|
|
1491
1784
|
async function collectUpstreamIds(files) {
|
|
1492
1785
|
const ids = /* @__PURE__ */ new Set();
|
|
1493
1786
|
for (const file of files) {
|
|
1494
|
-
const text = await (0,
|
|
1787
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1495
1788
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
1496
1789
|
}
|
|
1497
1790
|
return ids;
|
|
@@ -1512,7 +1805,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
1512
1805
|
}
|
|
1513
1806
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
1514
1807
|
for (const file of targetFiles) {
|
|
1515
|
-
const text = await (0,
|
|
1808
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1516
1809
|
if (pattern.test(text)) {
|
|
1517
1810
|
return true;
|
|
1518
1811
|
}
|
|
@@ -1534,20 +1827,20 @@ function toSortedArray(values) {
|
|
|
1534
1827
|
}
|
|
1535
1828
|
function buildHotspots(issues) {
|
|
1536
1829
|
const map = /* @__PURE__ */ new Map();
|
|
1537
|
-
for (const
|
|
1538
|
-
if (!
|
|
1830
|
+
for (const issue6 of issues) {
|
|
1831
|
+
if (!issue6.file) {
|
|
1539
1832
|
continue;
|
|
1540
1833
|
}
|
|
1541
|
-
const current = map.get(
|
|
1542
|
-
file:
|
|
1834
|
+
const current = map.get(issue6.file) ?? {
|
|
1835
|
+
file: issue6.file,
|
|
1543
1836
|
total: 0,
|
|
1544
1837
|
error: 0,
|
|
1545
1838
|
warning: 0,
|
|
1546
1839
|
info: 0
|
|
1547
1840
|
};
|
|
1548
1841
|
current.total += 1;
|
|
1549
|
-
current[
|
|
1550
|
-
map.set(
|
|
1842
|
+
current[issue6.severity] += 1;
|
|
1843
|
+
map.set(issue6.file, current);
|
|
1551
1844
|
}
|
|
1552
1845
|
return Array.from(map.values()).sort(
|
|
1553
1846
|
(a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
|
|
@@ -1569,6 +1862,7 @@ function buildHotspots(issues) {
|
|
|
1569
1862
|
resolvePath,
|
|
1570
1863
|
resolveToolVersion,
|
|
1571
1864
|
validateContracts,
|
|
1865
|
+
validateDefinedIds,
|
|
1572
1866
|
validateProject,
|
|
1573
1867
|
validateScenarioContent,
|
|
1574
1868
|
validateScenarios,
|