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.mjs
CHANGED
|
@@ -4,14 +4,13 @@ import path from "path";
|
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
5
|
var defaultConfig = {
|
|
6
6
|
paths: {
|
|
7
|
-
specDir: "qfai/spec",
|
|
8
|
-
decisionsDir: "qfai/spec/decisions",
|
|
9
|
-
scenariosDir: "qfai/spec",
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
dataContractsDir: "qfai/contracts/db",
|
|
7
|
+
specDir: ".qfai/spec",
|
|
8
|
+
decisionsDir: ".qfai/spec/decisions",
|
|
9
|
+
scenariosDir: ".qfai/spec/scenarios",
|
|
10
|
+
contractsDir: ".qfai/contracts",
|
|
11
|
+
uiContractsDir: ".qfai/contracts/ui",
|
|
12
|
+
apiContractsDir: ".qfai/contracts/api",
|
|
13
|
+
dataContractsDir: ".qfai/contracts/db",
|
|
15
14
|
srcDir: "src",
|
|
16
15
|
testsDir: "tests"
|
|
17
16
|
},
|
|
@@ -31,7 +30,8 @@ var defaultConfig = {
|
|
|
31
30
|
traceability: {
|
|
32
31
|
brMustHaveSc: true,
|
|
33
32
|
scMustTouchContracts: true,
|
|
34
|
-
allowOrphanContracts: false
|
|
33
|
+
allowOrphanContracts: false,
|
|
34
|
+
unknownContractIdSeverity: "error"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
output: {
|
|
@@ -106,13 +106,6 @@ function normalizePaths(raw, configPath, issues) {
|
|
|
106
106
|
configPath,
|
|
107
107
|
issues
|
|
108
108
|
),
|
|
109
|
-
rulesDir: readString(
|
|
110
|
-
raw.rulesDir,
|
|
111
|
-
base.rulesDir,
|
|
112
|
-
"paths.rulesDir",
|
|
113
|
-
configPath,
|
|
114
|
-
issues
|
|
115
|
-
),
|
|
116
109
|
contractsDir: readString(
|
|
117
110
|
raw.contractsDir,
|
|
118
111
|
base.contractsDir,
|
|
@@ -237,6 +230,13 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
237
230
|
"validation.traceability.allowOrphanContracts",
|
|
238
231
|
configPath,
|
|
239
232
|
issues
|
|
233
|
+
),
|
|
234
|
+
unknownContractIdSeverity: readTraceabilitySeverity(
|
|
235
|
+
traceabilityRaw?.unknownContractIdSeverity,
|
|
236
|
+
base.traceability.unknownContractIdSeverity,
|
|
237
|
+
"validation.traceability.unknownContractIdSeverity",
|
|
238
|
+
configPath,
|
|
239
|
+
issues
|
|
240
240
|
)
|
|
241
241
|
}
|
|
242
242
|
};
|
|
@@ -316,6 +316,20 @@ function readFailOn(value, fallback, label, configPath, issues) {
|
|
|
316
316
|
}
|
|
317
317
|
return fallback;
|
|
318
318
|
}
|
|
319
|
+
function readTraceabilitySeverity(value, fallback, label, configPath, issues) {
|
|
320
|
+
if (value === "warning" || value === "error") {
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
if (value !== void 0) {
|
|
324
|
+
issues.push(
|
|
325
|
+
configIssue(
|
|
326
|
+
configPath,
|
|
327
|
+
`${label} \u306F warning|error \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
|
|
328
|
+
)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return fallback;
|
|
332
|
+
}
|
|
319
333
|
function readOutputFormat(value, fallback, label, configPath, issues) {
|
|
320
334
|
if (value === "text" || value === "json" || value === "github") {
|
|
321
335
|
return value;
|
|
@@ -356,13 +370,15 @@ function isRecord(value) {
|
|
|
356
370
|
}
|
|
357
371
|
|
|
358
372
|
// src/core/ids.ts
|
|
359
|
-
var
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
373
|
+
var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
|
|
374
|
+
var STRICT_ID_PATTERNS = {
|
|
375
|
+
SPEC: /\bSPEC-\d{4}\b/g,
|
|
376
|
+
BR: /\bBR-\d{4}\b/g,
|
|
377
|
+
SC: /\bSC-\d{4}\b/g,
|
|
378
|
+
UI: /\bUI-\d{4}\b/g,
|
|
379
|
+
API: /\bAPI-\d{4}\b/g,
|
|
380
|
+
DATA: /\bDATA-\d{4}\b/g,
|
|
381
|
+
ADR: /\bADR-\d{4}\b/g
|
|
366
382
|
};
|
|
367
383
|
var LOOSE_ID_PATTERNS = {
|
|
368
384
|
SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
|
|
@@ -370,16 +386,17 @@ var LOOSE_ID_PATTERNS = {
|
|
|
370
386
|
SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
|
|
371
387
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
372
388
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
373
|
-
DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi
|
|
389
|
+
DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi,
|
|
390
|
+
ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
|
|
374
391
|
};
|
|
375
392
|
function extractIds(text, prefix) {
|
|
376
|
-
const pattern =
|
|
393
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
377
394
|
const matches = text.match(pattern);
|
|
378
395
|
return unique(matches ?? []);
|
|
379
396
|
}
|
|
380
397
|
function extractAllIds(text) {
|
|
381
398
|
const all = [];
|
|
382
|
-
|
|
399
|
+
ID_PREFIXES.forEach((prefix) => {
|
|
383
400
|
all.push(...extractIds(text, prefix));
|
|
384
401
|
});
|
|
385
402
|
return unique(all);
|
|
@@ -400,13 +417,13 @@ function unique(values) {
|
|
|
400
417
|
return Array.from(new Set(values));
|
|
401
418
|
}
|
|
402
419
|
function isValidId(value, prefix) {
|
|
403
|
-
const pattern =
|
|
420
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
404
421
|
const strict = new RegExp(pattern.source);
|
|
405
422
|
return strict.test(value);
|
|
406
423
|
}
|
|
407
424
|
|
|
408
425
|
// src/core/report.ts
|
|
409
|
-
import { readFile as
|
|
426
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
410
427
|
|
|
411
428
|
// src/core/discovery.ts
|
|
412
429
|
import path3 from "path";
|
|
@@ -467,8 +484,7 @@ async function exists(target) {
|
|
|
467
484
|
}
|
|
468
485
|
|
|
469
486
|
// src/core/discovery.ts
|
|
470
|
-
var
|
|
471
|
-
var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/i;
|
|
487
|
+
var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/;
|
|
472
488
|
async function collectSpecFiles(specRoot) {
|
|
473
489
|
const files = await collectFiles(specRoot, { extensions: [".md"] });
|
|
474
490
|
return files.filter((file) => isSpecFile(file));
|
|
@@ -492,7 +508,7 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
|
|
|
492
508
|
}
|
|
493
509
|
function isSpecFile(filePath) {
|
|
494
510
|
const name = path3.basename(filePath).toLowerCase();
|
|
495
|
-
return
|
|
511
|
+
return SPEC_NAMED_PATTERN.test(name);
|
|
496
512
|
}
|
|
497
513
|
|
|
498
514
|
// src/core/types.ts
|
|
@@ -503,8 +519,8 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
503
519
|
import path4 from "path";
|
|
504
520
|
import { fileURLToPath } from "url";
|
|
505
521
|
async function resolveToolVersion() {
|
|
506
|
-
if ("0.2.
|
|
507
|
-
return "0.2.
|
|
522
|
+
if ("0.2.9".length > 0) {
|
|
523
|
+
return "0.2.9";
|
|
508
524
|
}
|
|
509
525
|
try {
|
|
510
526
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -524,8 +540,50 @@ function resolvePackageJsonPath() {
|
|
|
524
540
|
|
|
525
541
|
// src/core/validators/contracts.ts
|
|
526
542
|
import { readFile as readFile3 } from "fs/promises";
|
|
543
|
+
|
|
544
|
+
// src/core/contracts.ts
|
|
527
545
|
import path5 from "path";
|
|
528
546
|
import { parse as parseYaml2 } from "yaml";
|
|
547
|
+
function parseStructuredContract(file, text) {
|
|
548
|
+
const ext = path5.extname(file).toLowerCase();
|
|
549
|
+
if (ext === ".json") {
|
|
550
|
+
return JSON.parse(text);
|
|
551
|
+
}
|
|
552
|
+
return parseYaml2(text);
|
|
553
|
+
}
|
|
554
|
+
function extractUiContractIds(doc) {
|
|
555
|
+
const id = typeof doc.id === "string" ? doc.id : "";
|
|
556
|
+
return extractIds(id, "UI");
|
|
557
|
+
}
|
|
558
|
+
function extractApiContractIds(doc) {
|
|
559
|
+
const operationIds = /* @__PURE__ */ new Set();
|
|
560
|
+
collectOperationIds(doc, operationIds);
|
|
561
|
+
const ids = /* @__PURE__ */ new Set();
|
|
562
|
+
for (const operationId of operationIds) {
|
|
563
|
+
extractIds(operationId, "API").forEach((id) => ids.add(id));
|
|
564
|
+
}
|
|
565
|
+
return Array.from(ids);
|
|
566
|
+
}
|
|
567
|
+
function collectOperationIds(value, out) {
|
|
568
|
+
if (!value || typeof value !== "object") {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (Array.isArray(value)) {
|
|
572
|
+
for (const item of value) {
|
|
573
|
+
collectOperationIds(item, out);
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
578
|
+
if (key === "operationId" && typeof entry === "string") {
|
|
579
|
+
out.add(entry);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
collectOperationIds(entry, out);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/core/validators/contracts.ts
|
|
529
587
|
var SQL_DANGEROUS_PATTERNS = [
|
|
530
588
|
{ pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
|
|
531
589
|
{ pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
|
|
@@ -574,12 +632,13 @@ async function validateUiContracts(uiRoot) {
|
|
|
574
632
|
"SC",
|
|
575
633
|
"UI",
|
|
576
634
|
"API",
|
|
577
|
-
"DATA"
|
|
635
|
+
"DATA",
|
|
636
|
+
"ADR"
|
|
578
637
|
]);
|
|
579
638
|
if (invalidIds.length > 0) {
|
|
580
639
|
issues.push(
|
|
581
640
|
issue(
|
|
582
|
-
"
|
|
641
|
+
"QFAI-ID-002",
|
|
583
642
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
584
643
|
"error",
|
|
585
644
|
file,
|
|
@@ -588,30 +647,32 @@ async function validateUiContracts(uiRoot) {
|
|
|
588
647
|
)
|
|
589
648
|
);
|
|
590
649
|
}
|
|
650
|
+
let doc;
|
|
591
651
|
try {
|
|
592
|
-
|
|
593
|
-
const id = typeof doc.id === "string" ? doc.id : "";
|
|
594
|
-
if (!id.startsWith("UI-")) {
|
|
595
|
-
issues.push(
|
|
596
|
-
issue(
|
|
597
|
-
"QFAI-UI-001",
|
|
598
|
-
"UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
599
|
-
"error",
|
|
600
|
-
file,
|
|
601
|
-
"contracts.ui.id"
|
|
602
|
-
)
|
|
603
|
-
);
|
|
604
|
-
}
|
|
652
|
+
doc = parseStructuredContract(file, text);
|
|
605
653
|
} catch (error) {
|
|
606
654
|
issues.push(
|
|
607
655
|
issue(
|
|
608
|
-
"QFAI-
|
|
609
|
-
`UI
|
|
656
|
+
"QFAI-CONTRACT-001",
|
|
657
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
|
|
610
658
|
"error",
|
|
611
659
|
file,
|
|
612
660
|
"contracts.ui.parse"
|
|
613
661
|
)
|
|
614
662
|
);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
const uiIds = extractUiContractIds(doc);
|
|
666
|
+
if (uiIds.length === 0) {
|
|
667
|
+
issues.push(
|
|
668
|
+
issue(
|
|
669
|
+
"QFAI-CONTRACT-002",
|
|
670
|
+
`UI \u5951\u7D04\u306B ID(UI-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
671
|
+
"error",
|
|
672
|
+
file,
|
|
673
|
+
"contracts.ui.id"
|
|
674
|
+
)
|
|
675
|
+
);
|
|
615
676
|
}
|
|
616
677
|
}
|
|
617
678
|
return issues;
|
|
@@ -638,12 +699,13 @@ async function validateApiContracts(apiRoot) {
|
|
|
638
699
|
"SC",
|
|
639
700
|
"UI",
|
|
640
701
|
"API",
|
|
641
|
-
"DATA"
|
|
702
|
+
"DATA",
|
|
703
|
+
"ADR"
|
|
642
704
|
]);
|
|
643
705
|
if (invalidIds.length > 0) {
|
|
644
706
|
issues.push(
|
|
645
707
|
issue(
|
|
646
|
-
"
|
|
708
|
+
"QFAI-ID-002",
|
|
647
709
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
648
710
|
"error",
|
|
649
711
|
file,
|
|
@@ -652,29 +714,43 @@ async function validateApiContracts(apiRoot) {
|
|
|
652
714
|
)
|
|
653
715
|
);
|
|
654
716
|
}
|
|
717
|
+
let doc;
|
|
655
718
|
try {
|
|
656
|
-
|
|
657
|
-
if (!doc || !hasOpenApi(doc)) {
|
|
658
|
-
issues.push(
|
|
659
|
-
issue(
|
|
660
|
-
"QFAI-API-001",
|
|
661
|
-
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
662
|
-
"error",
|
|
663
|
-
file,
|
|
664
|
-
"contracts.api.openapi"
|
|
665
|
-
)
|
|
666
|
-
);
|
|
667
|
-
}
|
|
719
|
+
doc = parseStructuredContract(file, text);
|
|
668
720
|
} catch (error) {
|
|
669
721
|
issues.push(
|
|
670
722
|
issue(
|
|
671
|
-
"QFAI-
|
|
672
|
-
`API \
|
|
723
|
+
"QFAI-CONTRACT-001",
|
|
724
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error)})`,
|
|
673
725
|
"error",
|
|
674
726
|
file,
|
|
675
727
|
"contracts.api.parse"
|
|
676
728
|
)
|
|
677
729
|
);
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (!hasOpenApi(doc)) {
|
|
733
|
+
issues.push(
|
|
734
|
+
issue(
|
|
735
|
+
"QFAI-API-001",
|
|
736
|
+
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
737
|
+
"error",
|
|
738
|
+
file,
|
|
739
|
+
"contracts.api.openapi"
|
|
740
|
+
)
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
const apiIds = extractApiContractIds(doc);
|
|
744
|
+
if (apiIds.length === 0) {
|
|
745
|
+
issues.push(
|
|
746
|
+
issue(
|
|
747
|
+
"QFAI-CONTRACT-002",
|
|
748
|
+
`API \u5951\u7D04\u306B ID(API-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
749
|
+
"error",
|
|
750
|
+
file,
|
|
751
|
+
"contracts.api.id"
|
|
752
|
+
)
|
|
753
|
+
);
|
|
678
754
|
}
|
|
679
755
|
}
|
|
680
756
|
return issues;
|
|
@@ -701,12 +777,13 @@ async function validateDataContracts(dataRoot) {
|
|
|
701
777
|
"SC",
|
|
702
778
|
"UI",
|
|
703
779
|
"API",
|
|
704
|
-
"DATA"
|
|
780
|
+
"DATA",
|
|
781
|
+
"ADR"
|
|
705
782
|
]);
|
|
706
783
|
if (invalidIds.length > 0) {
|
|
707
784
|
issues.push(
|
|
708
785
|
issue(
|
|
709
|
-
"
|
|
786
|
+
"QFAI-ID-002",
|
|
710
787
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
711
788
|
"error",
|
|
712
789
|
file,
|
|
@@ -736,13 +813,6 @@ function lintSql(text, file) {
|
|
|
736
813
|
}
|
|
737
814
|
return issues;
|
|
738
815
|
}
|
|
739
|
-
function parseStructured(file, text) {
|
|
740
|
-
const ext = path5.extname(file).toLowerCase();
|
|
741
|
-
if (ext === ".json") {
|
|
742
|
-
return JSON.parse(text);
|
|
743
|
-
}
|
|
744
|
-
return parseYaml2(text);
|
|
745
|
-
}
|
|
746
816
|
function hasOpenApi(doc) {
|
|
747
817
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
748
818
|
}
|
|
@@ -753,25 +823,165 @@ function formatError2(error) {
|
|
|
753
823
|
return String(error);
|
|
754
824
|
}
|
|
755
825
|
function issue(code, message, severity, file, rule, refs) {
|
|
756
|
-
const
|
|
826
|
+
const issue6 = {
|
|
757
827
|
code,
|
|
758
828
|
severity,
|
|
759
829
|
message
|
|
760
830
|
};
|
|
761
831
|
if (file) {
|
|
762
|
-
|
|
832
|
+
issue6.file = file;
|
|
763
833
|
}
|
|
764
834
|
if (rule) {
|
|
765
|
-
|
|
835
|
+
issue6.rule = rule;
|
|
766
836
|
}
|
|
767
837
|
if (refs && refs.length > 0) {
|
|
768
|
-
|
|
838
|
+
issue6.refs = refs;
|
|
769
839
|
}
|
|
770
|
-
return
|
|
840
|
+
return issue6;
|
|
771
841
|
}
|
|
772
842
|
|
|
773
|
-
// src/core/validators/
|
|
843
|
+
// src/core/validators/ids.ts
|
|
844
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
845
|
+
import path6 from "path";
|
|
846
|
+
|
|
847
|
+
// src/core/contractIndex.ts
|
|
774
848
|
import { readFile as readFile4 } from "fs/promises";
|
|
849
|
+
async function buildContractIndex(root, config) {
|
|
850
|
+
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
851
|
+
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
852
|
+
const dataRoot = resolvePath(root, config, "dataContractsDir");
|
|
853
|
+
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
854
|
+
collectUiContractFiles(uiRoot),
|
|
855
|
+
collectApiContractFiles(apiRoot),
|
|
856
|
+
collectDataContractFiles(dataRoot)
|
|
857
|
+
]);
|
|
858
|
+
const index = {
|
|
859
|
+
ids: /* @__PURE__ */ new Set(),
|
|
860
|
+
idToFiles: /* @__PURE__ */ new Map(),
|
|
861
|
+
files: { ui: uiFiles, api: apiFiles, data: dataFiles },
|
|
862
|
+
structuredParseFailedFiles: /* @__PURE__ */ new Set()
|
|
863
|
+
};
|
|
864
|
+
await indexUiContracts(uiFiles, index);
|
|
865
|
+
await indexApiContracts(apiFiles, index);
|
|
866
|
+
await indexDataContracts(dataFiles, index);
|
|
867
|
+
return index;
|
|
868
|
+
}
|
|
869
|
+
async function indexUiContracts(files, index) {
|
|
870
|
+
for (const file of files) {
|
|
871
|
+
const text = await readFile4(file, "utf-8");
|
|
872
|
+
try {
|
|
873
|
+
const doc = parseStructuredContract(file, text);
|
|
874
|
+
extractUiContractIds(doc).forEach((id) => record(index, id, file));
|
|
875
|
+
} catch {
|
|
876
|
+
index.structuredParseFailedFiles.add(file);
|
|
877
|
+
extractIds(text, "UI").forEach((id) => record(index, id, file));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function indexApiContracts(files, index) {
|
|
882
|
+
for (const file of files) {
|
|
883
|
+
const text = await readFile4(file, "utf-8");
|
|
884
|
+
try {
|
|
885
|
+
const doc = parseStructuredContract(file, text);
|
|
886
|
+
extractApiContractIds(doc).forEach((id) => record(index, id, file));
|
|
887
|
+
} catch {
|
|
888
|
+
index.structuredParseFailedFiles.add(file);
|
|
889
|
+
extractIds(text, "API").forEach((id) => record(index, id, file));
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
async function indexDataContracts(files, index) {
|
|
894
|
+
for (const file of files) {
|
|
895
|
+
const text = await readFile4(file, "utf-8");
|
|
896
|
+
extractIds(text, "DATA").forEach((id) => record(index, id, file));
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function record(index, id, file) {
|
|
900
|
+
index.ids.add(id);
|
|
901
|
+
const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
|
|
902
|
+
current.add(file);
|
|
903
|
+
index.idToFiles.set(id, current);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// src/core/validators/ids.ts
|
|
907
|
+
async function validateDefinedIds(root, config) {
|
|
908
|
+
const issues = [];
|
|
909
|
+
const specRoot = resolvePath(root, config, "specDir");
|
|
910
|
+
const scenarioRoot = resolvePath(root, config, "scenariosDir");
|
|
911
|
+
const specFiles = await collectSpecFiles(specRoot);
|
|
912
|
+
const scenarioFiles = await collectFiles(scenarioRoot, {
|
|
913
|
+
extensions: [".feature"]
|
|
914
|
+
});
|
|
915
|
+
const defined = /* @__PURE__ */ new Map();
|
|
916
|
+
await collectSpecDefinitionIds(specFiles, defined);
|
|
917
|
+
await collectScenarioDefinitionIds(scenarioFiles, defined);
|
|
918
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
919
|
+
for (const [id, files] of contractIndex.idToFiles.entries()) {
|
|
920
|
+
for (const file of files) {
|
|
921
|
+
recordId(defined, id, file);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
for (const [id, files] of defined.entries()) {
|
|
925
|
+
if (files.size <= 1) {
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
const sorted = Array.from(files).sort();
|
|
929
|
+
issues.push(
|
|
930
|
+
issue2(
|
|
931
|
+
"QFAI-ID-001",
|
|
932
|
+
`ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
|
|
933
|
+
"error",
|
|
934
|
+
sorted[0],
|
|
935
|
+
"id.duplicate"
|
|
936
|
+
)
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
return issues;
|
|
940
|
+
}
|
|
941
|
+
async function collectSpecDefinitionIds(files, out) {
|
|
942
|
+
for (const file of files) {
|
|
943
|
+
const text = await readFile5(file, "utf-8");
|
|
944
|
+
extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
|
|
945
|
+
extractIds(text, "BR").forEach((id) => recordId(out, id, file));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
async function collectScenarioDefinitionIds(files, out) {
|
|
949
|
+
for (const file of files) {
|
|
950
|
+
const text = await readFile5(file, "utf-8");
|
|
951
|
+
extractIds(text, "SC").forEach((id) => recordId(out, id, file));
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function recordId(out, id, file) {
|
|
955
|
+
const current = out.get(id) ?? /* @__PURE__ */ new Set();
|
|
956
|
+
current.add(file);
|
|
957
|
+
out.set(id, current);
|
|
958
|
+
}
|
|
959
|
+
function formatFileList(files, root) {
|
|
960
|
+
return files.map((file) => {
|
|
961
|
+
const relative = path6.relative(root, file);
|
|
962
|
+
return relative.length > 0 ? relative : file;
|
|
963
|
+
}).join(", ");
|
|
964
|
+
}
|
|
965
|
+
function issue2(code, message, severity, file, rule, refs) {
|
|
966
|
+
const issue6 = {
|
|
967
|
+
code,
|
|
968
|
+
severity,
|
|
969
|
+
message
|
|
970
|
+
};
|
|
971
|
+
if (file) {
|
|
972
|
+
issue6.file = file;
|
|
973
|
+
}
|
|
974
|
+
if (rule) {
|
|
975
|
+
issue6.rule = rule;
|
|
976
|
+
}
|
|
977
|
+
if (refs && refs.length > 0) {
|
|
978
|
+
issue6.refs = refs;
|
|
979
|
+
}
|
|
980
|
+
return issue6;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/core/validators/scenario.ts
|
|
984
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
775
985
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
776
986
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
777
987
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -782,7 +992,7 @@ async function validateScenarios(root, config) {
|
|
|
782
992
|
});
|
|
783
993
|
if (files.length === 0) {
|
|
784
994
|
return [
|
|
785
|
-
|
|
995
|
+
issue3(
|
|
786
996
|
"QFAI-SC-000",
|
|
787
997
|
"Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
788
998
|
"info",
|
|
@@ -793,7 +1003,7 @@ async function validateScenarios(root, config) {
|
|
|
793
1003
|
}
|
|
794
1004
|
const issues = [];
|
|
795
1005
|
for (const file of files) {
|
|
796
|
-
const text = await
|
|
1006
|
+
const text = await readFile6(file, "utf-8");
|
|
797
1007
|
issues.push(...validateScenarioContent(text, file));
|
|
798
1008
|
}
|
|
799
1009
|
return issues;
|
|
@@ -806,12 +1016,13 @@ function validateScenarioContent(text, file) {
|
|
|
806
1016
|
"SC",
|
|
807
1017
|
"UI",
|
|
808
1018
|
"API",
|
|
809
|
-
"DATA"
|
|
1019
|
+
"DATA",
|
|
1020
|
+
"ADR"
|
|
810
1021
|
]);
|
|
811
1022
|
if (invalidIds.length > 0) {
|
|
812
1023
|
issues.push(
|
|
813
|
-
|
|
814
|
-
"
|
|
1024
|
+
issue3(
|
|
1025
|
+
"QFAI-ID-002",
|
|
815
1026
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
816
1027
|
"error",
|
|
817
1028
|
file,
|
|
@@ -823,7 +1034,7 @@ function validateScenarioContent(text, file) {
|
|
|
823
1034
|
const scIds = extractIds(text, "SC");
|
|
824
1035
|
if (scIds.length === 0) {
|
|
825
1036
|
issues.push(
|
|
826
|
-
|
|
1037
|
+
issue3(
|
|
827
1038
|
"QFAI-SC-001",
|
|
828
1039
|
"SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
829
1040
|
"error",
|
|
@@ -835,7 +1046,7 @@ function validateScenarioContent(text, file) {
|
|
|
835
1046
|
const specIds = extractIds(text, "SPEC");
|
|
836
1047
|
if (specIds.length === 0) {
|
|
837
1048
|
issues.push(
|
|
838
|
-
|
|
1049
|
+
issue3(
|
|
839
1050
|
"QFAI-SC-002",
|
|
840
1051
|
"SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
841
1052
|
"error",
|
|
@@ -847,7 +1058,7 @@ function validateScenarioContent(text, file) {
|
|
|
847
1058
|
const brIds = extractIds(text, "BR");
|
|
848
1059
|
if (brIds.length === 0) {
|
|
849
1060
|
issues.push(
|
|
850
|
-
|
|
1061
|
+
issue3(
|
|
851
1062
|
"QFAI-SC-003",
|
|
852
1063
|
"SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
853
1064
|
"error",
|
|
@@ -868,7 +1079,7 @@ function validateScenarioContent(text, file) {
|
|
|
868
1079
|
}
|
|
869
1080
|
if (missingSteps.length > 0) {
|
|
870
1081
|
issues.push(
|
|
871
|
-
|
|
1082
|
+
issue3(
|
|
872
1083
|
"QFAI-SC-005",
|
|
873
1084
|
`Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
|
|
874
1085
|
"warning",
|
|
@@ -879,34 +1090,35 @@ function validateScenarioContent(text, file) {
|
|
|
879
1090
|
}
|
|
880
1091
|
return issues;
|
|
881
1092
|
}
|
|
882
|
-
function
|
|
883
|
-
const
|
|
1093
|
+
function issue3(code, message, severity, file, rule, refs) {
|
|
1094
|
+
const issue6 = {
|
|
884
1095
|
code,
|
|
885
1096
|
severity,
|
|
886
1097
|
message
|
|
887
1098
|
};
|
|
888
1099
|
if (file) {
|
|
889
|
-
|
|
1100
|
+
issue6.file = file;
|
|
890
1101
|
}
|
|
891
1102
|
if (rule) {
|
|
892
|
-
|
|
1103
|
+
issue6.rule = rule;
|
|
893
1104
|
}
|
|
894
1105
|
if (refs && refs.length > 0) {
|
|
895
|
-
|
|
1106
|
+
issue6.refs = refs;
|
|
896
1107
|
}
|
|
897
|
-
return
|
|
1108
|
+
return issue6;
|
|
898
1109
|
}
|
|
899
1110
|
|
|
900
1111
|
// src/core/validators/spec.ts
|
|
901
|
-
import { readFile as
|
|
1112
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
902
1113
|
async function validateSpecs(root, config) {
|
|
903
1114
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
904
1115
|
const files = await collectSpecFiles(specsRoot);
|
|
905
1116
|
if (files.length === 0) {
|
|
1117
|
+
const expected = "spec-0001-<slug>.md";
|
|
906
1118
|
return [
|
|
907
|
-
|
|
1119
|
+
issue4(
|
|
908
1120
|
"QFAI-SPEC-000",
|
|
909
|
-
|
|
1121
|
+
`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}`,
|
|
910
1122
|
"info",
|
|
911
1123
|
specsRoot,
|
|
912
1124
|
"spec.files"
|
|
@@ -915,7 +1127,7 @@ async function validateSpecs(root, config) {
|
|
|
915
1127
|
}
|
|
916
1128
|
const issues = [];
|
|
917
1129
|
for (const file of files) {
|
|
918
|
-
const text = await
|
|
1130
|
+
const text = await readFile7(file, "utf-8");
|
|
919
1131
|
issues.push(
|
|
920
1132
|
...validateSpecContent(
|
|
921
1133
|
text,
|
|
@@ -934,12 +1146,13 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
934
1146
|
"SC",
|
|
935
1147
|
"UI",
|
|
936
1148
|
"API",
|
|
937
|
-
"DATA"
|
|
1149
|
+
"DATA",
|
|
1150
|
+
"ADR"
|
|
938
1151
|
]);
|
|
939
1152
|
if (invalidIds.length > 0) {
|
|
940
1153
|
issues.push(
|
|
941
|
-
|
|
942
|
-
"
|
|
1154
|
+
issue4(
|
|
1155
|
+
"QFAI-ID-002",
|
|
943
1156
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
944
1157
|
"error",
|
|
945
1158
|
file,
|
|
@@ -951,7 +1164,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
951
1164
|
const specIds = extractIds(text, "SPEC");
|
|
952
1165
|
if (specIds.length === 0) {
|
|
953
1166
|
issues.push(
|
|
954
|
-
|
|
1167
|
+
issue4(
|
|
955
1168
|
"QFAI-SPEC-001",
|
|
956
1169
|
"SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
957
1170
|
"error",
|
|
@@ -963,7 +1176,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
963
1176
|
const brIds = extractIds(text, "BR");
|
|
964
1177
|
if (brIds.length === 0) {
|
|
965
1178
|
issues.push(
|
|
966
|
-
|
|
1179
|
+
issue4(
|
|
967
1180
|
"QFAI-SPEC-002",
|
|
968
1181
|
"BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
969
1182
|
"error",
|
|
@@ -975,7 +1188,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
975
1188
|
const scIds = extractIds(text, "SC");
|
|
976
1189
|
if (scIds.length > 0) {
|
|
977
1190
|
issues.push(
|
|
978
|
-
|
|
1191
|
+
issue4(
|
|
979
1192
|
"QFAI-SPEC-003",
|
|
980
1193
|
"Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
|
|
981
1194
|
"warning",
|
|
@@ -988,7 +1201,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
988
1201
|
for (const section of requiredSections) {
|
|
989
1202
|
if (!text.includes(section)) {
|
|
990
1203
|
issues.push(
|
|
991
|
-
|
|
1204
|
+
issue4(
|
|
992
1205
|
"QFAI-SPEC-004",
|
|
993
1206
|
`\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
|
|
994
1207
|
"error",
|
|
@@ -1000,26 +1213,26 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1000
1213
|
}
|
|
1001
1214
|
return issues;
|
|
1002
1215
|
}
|
|
1003
|
-
function
|
|
1004
|
-
const
|
|
1216
|
+
function issue4(code, message, severity, file, rule, refs) {
|
|
1217
|
+
const issue6 = {
|
|
1005
1218
|
code,
|
|
1006
1219
|
severity,
|
|
1007
1220
|
message
|
|
1008
1221
|
};
|
|
1009
1222
|
if (file) {
|
|
1010
|
-
|
|
1223
|
+
issue6.file = file;
|
|
1011
1224
|
}
|
|
1012
1225
|
if (rule) {
|
|
1013
|
-
|
|
1226
|
+
issue6.rule = rule;
|
|
1014
1227
|
}
|
|
1015
1228
|
if (refs && refs.length > 0) {
|
|
1016
|
-
|
|
1229
|
+
issue6.refs = refs;
|
|
1017
1230
|
}
|
|
1018
|
-
return
|
|
1231
|
+
return issue6;
|
|
1019
1232
|
}
|
|
1020
1233
|
|
|
1021
1234
|
// src/core/validators/traceability.ts
|
|
1022
|
-
import { readFile as
|
|
1235
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1023
1236
|
async function validateTraceability(root, config) {
|
|
1024
1237
|
const issues = [];
|
|
1025
1238
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
@@ -1035,36 +1248,141 @@ async function validateTraceability(root, config) {
|
|
|
1035
1248
|
extensions: [".feature"]
|
|
1036
1249
|
});
|
|
1037
1250
|
const upstreamIds = /* @__PURE__ */ new Set();
|
|
1251
|
+
const specIds = /* @__PURE__ */ new Set();
|
|
1038
1252
|
const brIdsInSpecs = /* @__PURE__ */ new Set();
|
|
1039
1253
|
const brIdsInScenarios = /* @__PURE__ */ new Set();
|
|
1040
1254
|
const scIdsInScenarios = /* @__PURE__ */ new Set();
|
|
1041
1255
|
const scenarioContractIds = /* @__PURE__ */ new Set();
|
|
1042
1256
|
const scWithContracts = /* @__PURE__ */ new Set();
|
|
1043
|
-
|
|
1044
|
-
|
|
1257
|
+
const specToBrIds = /* @__PURE__ */ new Map();
|
|
1258
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
1259
|
+
const contractIds = contractIndex.ids;
|
|
1260
|
+
for (const file of specFiles) {
|
|
1261
|
+
const text = await readFile8(file, "utf-8");
|
|
1262
|
+
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1263
|
+
const specIdsInFile = extractIds(text, "SPEC");
|
|
1264
|
+
specIdsInFile.forEach((id) => specIds.add(id));
|
|
1265
|
+
const brIds = extractIds(text, "BR");
|
|
1266
|
+
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
1267
|
+
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
1268
|
+
...extractIds(text, "UI"),
|
|
1269
|
+
...extractIds(text, "API"),
|
|
1270
|
+
...extractIds(text, "DATA")
|
|
1271
|
+
]);
|
|
1272
|
+
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
1273
|
+
(id) => !contractIds.has(id)
|
|
1274
|
+
);
|
|
1275
|
+
if (unknownContractIds.length > 0) {
|
|
1276
|
+
issues.push(
|
|
1277
|
+
issue5(
|
|
1278
|
+
"QFAI-TRACE-009",
|
|
1279
|
+
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1280
|
+
", "
|
|
1281
|
+
)}`,
|
|
1282
|
+
"error",
|
|
1283
|
+
file,
|
|
1284
|
+
"traceability.specContractExists",
|
|
1285
|
+
unknownContractIds
|
|
1286
|
+
)
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
for (const specId of specIdsInFile) {
|
|
1290
|
+
const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
|
|
1291
|
+
brIds.forEach((id) => current.add(id));
|
|
1292
|
+
specToBrIds.set(specId, current);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
for (const file of decisionFiles) {
|
|
1296
|
+
const text = await readFile8(file, "utf-8");
|
|
1045
1297
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1046
|
-
extractIds(text, "BR").forEach((id) => brIdsInSpecs.add(id));
|
|
1047
1298
|
}
|
|
1048
1299
|
for (const file of scenarioFiles) {
|
|
1049
|
-
const text = await
|
|
1300
|
+
const text = await readFile8(file, "utf-8");
|
|
1050
1301
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1302
|
+
const specIdsInScenario = extractIds(text, "SPEC");
|
|
1051
1303
|
const brIds = extractIds(text, "BR");
|
|
1052
|
-
brIds.forEach((id) => brIdsInScenarios.add(id));
|
|
1053
1304
|
const scIds = extractIds(text, "SC");
|
|
1054
|
-
|
|
1055
|
-
const contractIds = [
|
|
1305
|
+
const scenarioIds = [
|
|
1056
1306
|
...extractIds(text, "UI"),
|
|
1057
1307
|
...extractIds(text, "API"),
|
|
1058
1308
|
...extractIds(text, "DATA")
|
|
1059
1309
|
];
|
|
1060
|
-
|
|
1061
|
-
|
|
1310
|
+
brIds.forEach((id) => brIdsInScenarios.add(id));
|
|
1311
|
+
scIds.forEach((id) => scIdsInScenarios.add(id));
|
|
1312
|
+
scenarioIds.forEach((id) => scenarioContractIds.add(id));
|
|
1313
|
+
if (scenarioIds.length > 0) {
|
|
1062
1314
|
scIds.forEach((id) => scWithContracts.add(id));
|
|
1063
1315
|
}
|
|
1316
|
+
const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
|
|
1317
|
+
if (unknownSpecIds.length > 0) {
|
|
1318
|
+
issues.push(
|
|
1319
|
+
issue5(
|
|
1320
|
+
"QFAI-TRACE-005",
|
|
1321
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
|
|
1322
|
+
"error",
|
|
1323
|
+
file,
|
|
1324
|
+
"traceability.scenarioSpecExists",
|
|
1325
|
+
unknownSpecIds
|
|
1326
|
+
)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
|
|
1330
|
+
if (unknownBrIds.length > 0) {
|
|
1331
|
+
issues.push(
|
|
1332
|
+
issue5(
|
|
1333
|
+
"QFAI-TRACE-006",
|
|
1334
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
|
|
1335
|
+
"error",
|
|
1336
|
+
file,
|
|
1337
|
+
"traceability.scenarioBrExists",
|
|
1338
|
+
unknownBrIds
|
|
1339
|
+
)
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
|
|
1343
|
+
if (unknownContractIds.length > 0) {
|
|
1344
|
+
issues.push(
|
|
1345
|
+
issue5(
|
|
1346
|
+
"QFAI-TRACE-008",
|
|
1347
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1348
|
+
", "
|
|
1349
|
+
)}`,
|
|
1350
|
+
config.validation.traceability.unknownContractIdSeverity,
|
|
1351
|
+
file,
|
|
1352
|
+
"traceability.scenarioContractExists",
|
|
1353
|
+
unknownContractIds
|
|
1354
|
+
)
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
if (specIdsInScenario.length > 0) {
|
|
1358
|
+
const allowedBrIds = /* @__PURE__ */ new Set();
|
|
1359
|
+
for (const specId of specIdsInScenario) {
|
|
1360
|
+
const brIdsForSpec = specToBrIds.get(specId);
|
|
1361
|
+
if (!brIdsForSpec) {
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
brIdsForSpec.forEach((id) => allowedBrIds.add(id));
|
|
1365
|
+
}
|
|
1366
|
+
const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
|
|
1367
|
+
if (invalidBrIds.length > 0) {
|
|
1368
|
+
issues.push(
|
|
1369
|
+
issue5(
|
|
1370
|
+
"QFAI-TRACE-007",
|
|
1371
|
+
`Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
|
|
1372
|
+
", "
|
|
1373
|
+
)} (SPEC: ${specIdsInScenario.join(", ")})`,
|
|
1374
|
+
"error",
|
|
1375
|
+
file,
|
|
1376
|
+
"traceability.scenarioBrUnderSpec",
|
|
1377
|
+
invalidBrIds
|
|
1378
|
+
)
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1064
1382
|
}
|
|
1065
1383
|
if (upstreamIds.size === 0) {
|
|
1066
1384
|
return [
|
|
1067
|
-
|
|
1385
|
+
issue5(
|
|
1068
1386
|
"QFAI-TRACE-000",
|
|
1069
1387
|
"\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1070
1388
|
"info",
|
|
@@ -1079,7 +1397,7 @@ async function validateTraceability(root, config) {
|
|
|
1079
1397
|
);
|
|
1080
1398
|
if (orphanBrIds.length > 0) {
|
|
1081
1399
|
issues.push(
|
|
1082
|
-
|
|
1400
|
+
issue5(
|
|
1083
1401
|
"QFAI_TRACE_BR_ORPHAN",
|
|
1084
1402
|
`BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
|
|
1085
1403
|
"error",
|
|
@@ -1096,7 +1414,7 @@ async function validateTraceability(root, config) {
|
|
|
1096
1414
|
);
|
|
1097
1415
|
if (scWithoutContracts.length > 0) {
|
|
1098
1416
|
issues.push(
|
|
1099
|
-
|
|
1417
|
+
issue5(
|
|
1100
1418
|
"QFAI_TRACE_SC_NO_CONTRACT",
|
|
1101
1419
|
`SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
|
|
1102
1420
|
", "
|
|
@@ -1110,14 +1428,13 @@ async function validateTraceability(root, config) {
|
|
|
1110
1428
|
}
|
|
1111
1429
|
}
|
|
1112
1430
|
if (!config.validation.traceability.allowOrphanContracts) {
|
|
1113
|
-
const contractIds = await collectContractIds(root, config);
|
|
1114
1431
|
if (contractIds.size > 0) {
|
|
1115
1432
|
const orphanContracts = Array.from(contractIds).filter(
|
|
1116
1433
|
(id) => !scenarioContractIds.has(id)
|
|
1117
1434
|
);
|
|
1118
1435
|
if (orphanContracts.length > 0) {
|
|
1119
1436
|
issues.push(
|
|
1120
|
-
|
|
1437
|
+
issue5(
|
|
1121
1438
|
"QFAI_CONTRACT_ORPHAN",
|
|
1122
1439
|
`\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
|
|
1123
1440
|
"error",
|
|
@@ -1134,27 +1451,6 @@ async function validateTraceability(root, config) {
|
|
|
1134
1451
|
);
|
|
1135
1452
|
return issues;
|
|
1136
1453
|
}
|
|
1137
|
-
async function collectContractIds(root, config) {
|
|
1138
|
-
const contractIds = /* @__PURE__ */ new Set();
|
|
1139
|
-
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1140
|
-
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1141
|
-
const dataRoot = resolvePath(root, config, "dataContractsDir");
|
|
1142
|
-
const uiFiles = await collectUiContractFiles(uiRoot);
|
|
1143
|
-
const apiFiles = await collectApiContractFiles(apiRoot);
|
|
1144
|
-
const dataFiles = await collectDataContractFiles(dataRoot);
|
|
1145
|
-
await collectIdsFromFiles(uiFiles, ["UI"], contractIds);
|
|
1146
|
-
await collectIdsFromFiles(apiFiles, ["API"], contractIds);
|
|
1147
|
-
await collectIdsFromFiles(dataFiles, ["DATA"], contractIds);
|
|
1148
|
-
return contractIds;
|
|
1149
|
-
}
|
|
1150
|
-
async function collectIdsFromFiles(files, prefixes, out) {
|
|
1151
|
-
for (const file of files) {
|
|
1152
|
-
const text = await readFile6(file, "utf-8");
|
|
1153
|
-
for (const prefix of prefixes) {
|
|
1154
|
-
extractIds(text, prefix).forEach((id) => out.add(id));
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
1454
|
async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
1159
1455
|
const issues = [];
|
|
1160
1456
|
const codeFiles = await collectFiles(srcRoot, {
|
|
@@ -1166,7 +1462,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1166
1462
|
const targetFiles = [...codeFiles, ...testFiles];
|
|
1167
1463
|
if (targetFiles.length === 0) {
|
|
1168
1464
|
issues.push(
|
|
1169
|
-
|
|
1465
|
+
issue5(
|
|
1170
1466
|
"QFAI-TRACE-001",
|
|
1171
1467
|
"\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1172
1468
|
"info",
|
|
@@ -1179,7 +1475,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1179
1475
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
1180
1476
|
let found = false;
|
|
1181
1477
|
for (const file of targetFiles) {
|
|
1182
|
-
const text = await
|
|
1478
|
+
const text = await readFile8(file, "utf-8");
|
|
1183
1479
|
if (pattern.test(text)) {
|
|
1184
1480
|
found = true;
|
|
1185
1481
|
break;
|
|
@@ -1187,7 +1483,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1187
1483
|
}
|
|
1188
1484
|
if (!found) {
|
|
1189
1485
|
issues.push(
|
|
1190
|
-
|
|
1486
|
+
issue5(
|
|
1191
1487
|
"QFAI-TRACE-002",
|
|
1192
1488
|
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
1193
1489
|
"warning",
|
|
@@ -1202,22 +1498,22 @@ function buildIdPattern(ids) {
|
|
|
1202
1498
|
const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
1203
1499
|
return new RegExp(`\\b(${escaped.join("|")})\\b`);
|
|
1204
1500
|
}
|
|
1205
|
-
function
|
|
1206
|
-
const
|
|
1501
|
+
function issue5(code, message, severity, file, rule, refs) {
|
|
1502
|
+
const issue6 = {
|
|
1207
1503
|
code,
|
|
1208
1504
|
severity,
|
|
1209
1505
|
message
|
|
1210
1506
|
};
|
|
1211
1507
|
if (file) {
|
|
1212
|
-
|
|
1508
|
+
issue6.file = file;
|
|
1213
1509
|
}
|
|
1214
1510
|
if (rule) {
|
|
1215
|
-
|
|
1511
|
+
issue6.rule = rule;
|
|
1216
1512
|
}
|
|
1217
1513
|
if (refs && refs.length > 0) {
|
|
1218
|
-
|
|
1514
|
+
issue6.refs = refs;
|
|
1219
1515
|
}
|
|
1220
|
-
return
|
|
1516
|
+
return issue6;
|
|
1221
1517
|
}
|
|
1222
1518
|
|
|
1223
1519
|
// src/core/validate.ts
|
|
@@ -1229,6 +1525,7 @@ async function validateProject(root, configResult) {
|
|
|
1229
1525
|
...await validateSpecs(root, config),
|
|
1230
1526
|
...await validateScenarios(root, config),
|
|
1231
1527
|
...await validateContracts(root, config),
|
|
1528
|
+
...await validateDefinedIds(root, config),
|
|
1232
1529
|
...await validateTraceability(root, config)
|
|
1233
1530
|
];
|
|
1234
1531
|
const toolVersion = await resolveToolVersion();
|
|
@@ -1241,8 +1538,8 @@ async function validateProject(root, configResult) {
|
|
|
1241
1538
|
}
|
|
1242
1539
|
function countIssues(issues) {
|
|
1243
1540
|
return issues.reduce(
|
|
1244
|
-
(acc,
|
|
1245
|
-
acc[
|
|
1541
|
+
(acc, issue6) => {
|
|
1542
|
+
acc[issue6.severity] += 1;
|
|
1246
1543
|
return acc;
|
|
1247
1544
|
},
|
|
1248
1545
|
{ info: 0, warning: 0, error: 0 }
|
|
@@ -1250,7 +1547,7 @@ function countIssues(issues) {
|
|
|
1250
1547
|
}
|
|
1251
1548
|
|
|
1252
1549
|
// src/core/report.ts
|
|
1253
|
-
var
|
|
1550
|
+
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
|
|
1254
1551
|
async function createReportData(root, validation, configResult) {
|
|
1255
1552
|
const resolved = configResult ?? await loadConfig(root);
|
|
1256
1553
|
const config = resolved.config;
|
|
@@ -1258,7 +1555,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1258
1555
|
const specRoot = resolvePath(root, config, "specDir");
|
|
1259
1556
|
const decisionsRoot = resolvePath(root, config, "decisionsDir");
|
|
1260
1557
|
const scenariosRoot = resolvePath(root, config, "scenariosDir");
|
|
1261
|
-
const rulesRoot = resolvePath(root, config, "rulesDir");
|
|
1262
1558
|
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1263
1559
|
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1264
1560
|
const dbRoot = resolvePath(root, config, "dataContractsDir");
|
|
@@ -1271,7 +1567,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1271
1567
|
const decisionFiles = await collectFiles(decisionsRoot, {
|
|
1272
1568
|
extensions: [".md"]
|
|
1273
1569
|
});
|
|
1274
|
-
const ruleFiles = await collectFiles(rulesRoot, { extensions: [".md"] });
|
|
1275
1570
|
const {
|
|
1276
1571
|
api: apiFiles,
|
|
1277
1572
|
ui: uiFiles,
|
|
@@ -1281,7 +1576,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1281
1576
|
...specFiles,
|
|
1282
1577
|
...scenarioFiles,
|
|
1283
1578
|
...decisionFiles,
|
|
1284
|
-
...ruleFiles,
|
|
1285
1579
|
...apiFiles,
|
|
1286
1580
|
...uiFiles,
|
|
1287
1581
|
...dbFiles
|
|
@@ -1307,7 +1601,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1307
1601
|
specs: specFiles.length,
|
|
1308
1602
|
scenarios: scenarioFiles.length,
|
|
1309
1603
|
decisions: decisionFiles.length,
|
|
1310
|
-
rules: ruleFiles.length,
|
|
1311
1604
|
contracts: {
|
|
1312
1605
|
api: apiFiles.length,
|
|
1313
1606
|
ui: uiFiles.length,
|
|
@@ -1342,7 +1635,6 @@ function formatReportMarkdown(data) {
|
|
|
1342
1635
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
1343
1636
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
1344
1637
|
lines.push(`- decisions: ${data.summary.decisions}`);
|
|
1345
|
-
lines.push(`- rules: ${data.summary.rules}`);
|
|
1346
1638
|
lines.push(
|
|
1347
1639
|
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
1348
1640
|
);
|
|
@@ -1378,7 +1670,7 @@ function formatReportMarkdown(data) {
|
|
|
1378
1670
|
lines.push("");
|
|
1379
1671
|
lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
|
|
1380
1672
|
const traceIssues = data.issues.filter(
|
|
1381
|
-
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code === "QFAI_CONTRACT_ORPHAN"
|
|
1673
|
+
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-") || item.code === "QFAI_CONTRACT_ORPHAN"
|
|
1382
1674
|
);
|
|
1383
1675
|
if (traceIssues.length === 0) {
|
|
1384
1676
|
lines.push("- (none)");
|
|
@@ -1418,8 +1710,8 @@ async function collectIds(files) {
|
|
|
1418
1710
|
DATA: /* @__PURE__ */ new Set()
|
|
1419
1711
|
};
|
|
1420
1712
|
for (const file of files) {
|
|
1421
|
-
const text = await
|
|
1422
|
-
for (const prefix of
|
|
1713
|
+
const text = await readFile9(file, "utf-8");
|
|
1714
|
+
for (const prefix of ID_PREFIXES2) {
|
|
1423
1715
|
const ids = extractIds(text, prefix);
|
|
1424
1716
|
ids.forEach((id) => result[prefix].add(id));
|
|
1425
1717
|
}
|
|
@@ -1436,7 +1728,7 @@ async function collectIds(files) {
|
|
|
1436
1728
|
async function collectUpstreamIds(files) {
|
|
1437
1729
|
const ids = /* @__PURE__ */ new Set();
|
|
1438
1730
|
for (const file of files) {
|
|
1439
|
-
const text = await
|
|
1731
|
+
const text = await readFile9(file, "utf-8");
|
|
1440
1732
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
1441
1733
|
}
|
|
1442
1734
|
return ids;
|
|
@@ -1457,7 +1749,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
1457
1749
|
}
|
|
1458
1750
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
1459
1751
|
for (const file of targetFiles) {
|
|
1460
|
-
const text = await
|
|
1752
|
+
const text = await readFile9(file, "utf-8");
|
|
1461
1753
|
if (pattern.test(text)) {
|
|
1462
1754
|
return true;
|
|
1463
1755
|
}
|
|
@@ -1479,20 +1771,20 @@ function toSortedArray(values) {
|
|
|
1479
1771
|
}
|
|
1480
1772
|
function buildHotspots(issues) {
|
|
1481
1773
|
const map = /* @__PURE__ */ new Map();
|
|
1482
|
-
for (const
|
|
1483
|
-
if (!
|
|
1774
|
+
for (const issue6 of issues) {
|
|
1775
|
+
if (!issue6.file) {
|
|
1484
1776
|
continue;
|
|
1485
1777
|
}
|
|
1486
|
-
const current = map.get(
|
|
1487
|
-
file:
|
|
1778
|
+
const current = map.get(issue6.file) ?? {
|
|
1779
|
+
file: issue6.file,
|
|
1488
1780
|
total: 0,
|
|
1489
1781
|
error: 0,
|
|
1490
1782
|
warning: 0,
|
|
1491
1783
|
info: 0
|
|
1492
1784
|
};
|
|
1493
1785
|
current.total += 1;
|
|
1494
|
-
current[
|
|
1495
|
-
map.set(
|
|
1786
|
+
current[issue6.severity] += 1;
|
|
1787
|
+
map.set(issue6.file, current);
|
|
1496
1788
|
}
|
|
1497
1789
|
return Array.from(map.values()).sort(
|
|
1498
1790
|
(a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
|
|
@@ -1513,6 +1805,7 @@ export {
|
|
|
1513
1805
|
resolvePath,
|
|
1514
1806
|
resolveToolVersion,
|
|
1515
1807
|
validateContracts,
|
|
1808
|
+
validateDefinedIds,
|
|
1516
1809
|
validateProject,
|
|
1517
1810
|
validateScenarioContent,
|
|
1518
1811
|
validateScenarios,
|