qfai 1.0.2 → 1.0.3
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 +11 -1
- package/assets/init/.qfai/contracts/README.md +20 -0
- package/assets/init/.qfai/contracts/ui/assets/thema-001-facebook-like/assets.yaml +6 -0
- package/assets/init/.qfai/contracts/ui/assets/thema-001-facebook-like/palette.png +0 -0
- package/assets/init/.qfai/contracts/ui/assets/ui-0001-sample/assets.yaml +6 -0
- package/assets/init/.qfai/contracts/ui/assets/ui-0001-sample/snapshots/login__desktop__light__default.png +0 -0
- package/assets/init/.qfai/contracts/ui/thema-001-facebook-like.yml +13 -0
- package/assets/init/.qfai/contracts/ui/ui-0001-sample.yaml +9 -0
- package/assets/init/.qfai/specs/README.md +1 -1
- package/assets/init/.qfai/specs/spec-0001/scenario.feature +1 -1
- package/assets/init/.qfai/specs/spec-0001/spec.md +1 -1
- package/dist/cli/index.cjs +585 -109
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +587 -111
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +538 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.mjs +540 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -456,7 +456,15 @@ function isRecord(value) {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
// src/core/ids.ts
|
|
459
|
-
var ID_PREFIXES = [
|
|
459
|
+
var ID_PREFIXES = [
|
|
460
|
+
"SPEC",
|
|
461
|
+
"BR",
|
|
462
|
+
"SC",
|
|
463
|
+
"UI",
|
|
464
|
+
"API",
|
|
465
|
+
"DB",
|
|
466
|
+
"THEMA"
|
|
467
|
+
];
|
|
460
468
|
var STRICT_ID_PATTERNS = {
|
|
461
469
|
SPEC: /\bSPEC-\d{4}\b/g,
|
|
462
470
|
BR: /\bBR-\d{4}\b/g,
|
|
@@ -464,6 +472,7 @@ var STRICT_ID_PATTERNS = {
|
|
|
464
472
|
UI: /\bUI-\d{4}\b/g,
|
|
465
473
|
API: /\bAPI-\d{4}\b/g,
|
|
466
474
|
DB: /\bDB-\d{4}\b/g,
|
|
475
|
+
THEMA: /\bTHEMA-\d{3}\b/g,
|
|
467
476
|
ADR: /\bADR-\d{4}\b/g
|
|
468
477
|
};
|
|
469
478
|
var LOOSE_ID_PATTERNS = {
|
|
@@ -473,6 +482,7 @@ var LOOSE_ID_PATTERNS = {
|
|
|
473
482
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
474
483
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
475
484
|
DB: /\bDB-[A-Za-z0-9_-]+\b/gi,
|
|
485
|
+
THEMA: /\bTHEMA-[A-Za-z0-9_-]+\b/gi,
|
|
476
486
|
ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
|
|
477
487
|
};
|
|
478
488
|
function extractIds(text, prefix) {
|
|
@@ -510,14 +520,15 @@ function isValidId(value, prefix) {
|
|
|
510
520
|
|
|
511
521
|
// src/core/report.ts
|
|
512
522
|
var import_promises15 = require("fs/promises");
|
|
513
|
-
var
|
|
523
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
514
524
|
|
|
515
525
|
// src/core/contractIndex.ts
|
|
516
526
|
var import_promises5 = require("fs/promises");
|
|
517
|
-
var
|
|
527
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
518
528
|
|
|
519
529
|
// src/core/discovery.ts
|
|
520
530
|
var import_promises4 = require("fs/promises");
|
|
531
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
521
532
|
|
|
522
533
|
// src/core/fs.ts
|
|
523
534
|
var import_promises2 = require("fs/promises");
|
|
@@ -666,7 +677,12 @@ async function collectScenarioFiles(specsRoot) {
|
|
|
666
677
|
return filterExisting(entries.map((entry) => entry.scenarioPath));
|
|
667
678
|
}
|
|
668
679
|
async function collectUiContractFiles(uiRoot) {
|
|
669
|
-
|
|
680
|
+
const files = await collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
|
|
681
|
+
return filterByBasenamePrefix(files, "ui-");
|
|
682
|
+
}
|
|
683
|
+
async function collectThemaContractFiles(uiRoot) {
|
|
684
|
+
const files = await collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
|
|
685
|
+
return filterByBasenamePrefix(files, "thema-");
|
|
670
686
|
}
|
|
671
687
|
async function collectApiContractFiles(apiRoot) {
|
|
672
688
|
return collectFiles(apiRoot, { extensions: [".yaml", ".yml", ".json"] });
|
|
@@ -675,12 +691,13 @@ async function collectDbContractFiles(dbRoot) {
|
|
|
675
691
|
return collectFiles(dbRoot, { extensions: [".sql"] });
|
|
676
692
|
}
|
|
677
693
|
async function collectContractFiles(uiRoot, apiRoot, dbRoot) {
|
|
678
|
-
const [ui, api, db] = await Promise.all([
|
|
694
|
+
const [ui, thema, api, db] = await Promise.all([
|
|
679
695
|
collectUiContractFiles(uiRoot),
|
|
696
|
+
collectThemaContractFiles(uiRoot),
|
|
680
697
|
collectApiContractFiles(apiRoot),
|
|
681
698
|
collectDbContractFiles(dbRoot)
|
|
682
699
|
]);
|
|
683
|
-
return { ui, api, db };
|
|
700
|
+
return { ui, thema, api, db };
|
|
684
701
|
}
|
|
685
702
|
async function filterExisting(files) {
|
|
686
703
|
const existing = [];
|
|
@@ -699,10 +716,16 @@ async function exists3(target) {
|
|
|
699
716
|
return false;
|
|
700
717
|
}
|
|
701
718
|
}
|
|
719
|
+
function filterByBasenamePrefix(files, prefix) {
|
|
720
|
+
const lowerPrefix = prefix.toLowerCase();
|
|
721
|
+
return files.filter(
|
|
722
|
+
(file) => import_node_path4.default.basename(file).toLowerCase().startsWith(lowerPrefix)
|
|
723
|
+
);
|
|
724
|
+
}
|
|
702
725
|
|
|
703
726
|
// src/core/contractsDecl.ts
|
|
704
|
-
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
|
|
705
|
-
var CONTRACT_DECLARATION_LINE_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*(?:API|UI|DB)-\d{4}\s*(?:\*\/)?\s*$/;
|
|
727
|
+
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
728
|
+
var CONTRACT_DECLARATION_LINE_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*(?:(?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/;
|
|
706
729
|
function extractDeclaredContractIds(text) {
|
|
707
730
|
const ids = [];
|
|
708
731
|
for (const match of text.matchAll(CONTRACT_DECLARATION_RE)) {
|
|
@@ -720,20 +743,22 @@ function stripContractDeclarationLines(text) {
|
|
|
720
743
|
// src/core/contractIndex.ts
|
|
721
744
|
async function buildContractIndex(root, config) {
|
|
722
745
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
723
|
-
const uiRoot =
|
|
724
|
-
const apiRoot =
|
|
725
|
-
const dbRoot =
|
|
726
|
-
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
746
|
+
const uiRoot = import_node_path5.default.join(contractsRoot, "ui");
|
|
747
|
+
const apiRoot = import_node_path5.default.join(contractsRoot, "api");
|
|
748
|
+
const dbRoot = import_node_path5.default.join(contractsRoot, "db");
|
|
749
|
+
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
727
750
|
collectUiContractFiles(uiRoot),
|
|
751
|
+
collectThemaContractFiles(uiRoot),
|
|
728
752
|
collectApiContractFiles(apiRoot),
|
|
729
753
|
collectDbContractFiles(dbRoot)
|
|
730
754
|
]);
|
|
731
755
|
const index = {
|
|
732
756
|
ids: /* @__PURE__ */ new Set(),
|
|
733
757
|
idToFiles: /* @__PURE__ */ new Map(),
|
|
734
|
-
files: { ui: uiFiles, api: apiFiles, db: dbFiles }
|
|
758
|
+
files: { ui: uiFiles, thema: themaFiles, api: apiFiles, db: dbFiles }
|
|
735
759
|
};
|
|
736
760
|
await indexContractFiles(uiFiles, index);
|
|
761
|
+
await indexContractFiles(themaFiles, index);
|
|
737
762
|
await indexContractFiles(apiFiles, index);
|
|
738
763
|
await indexContractFiles(dbFiles, index);
|
|
739
764
|
return index;
|
|
@@ -752,15 +777,15 @@ function record(index, id, file) {
|
|
|
752
777
|
}
|
|
753
778
|
|
|
754
779
|
// src/core/paths.ts
|
|
755
|
-
var
|
|
780
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
756
781
|
function toRelativePath(root, target) {
|
|
757
782
|
if (!target) {
|
|
758
783
|
return target;
|
|
759
784
|
}
|
|
760
|
-
if (!
|
|
785
|
+
if (!import_node_path6.default.isAbsolute(target)) {
|
|
761
786
|
return toPosixPath(target);
|
|
762
787
|
}
|
|
763
|
-
const relative =
|
|
788
|
+
const relative = import_node_path6.default.relative(root, target);
|
|
764
789
|
if (!relative) {
|
|
765
790
|
return ".";
|
|
766
791
|
}
|
|
@@ -808,7 +833,7 @@ function normalizeValidationResult(root, result) {
|
|
|
808
833
|
}
|
|
809
834
|
|
|
810
835
|
// src/core/parse/contractRefs.ts
|
|
811
|
-
var CONTRACT_REF_ID_RE = /^(?:API|UI|DB)-\d{4}$/;
|
|
836
|
+
var CONTRACT_REF_ID_RE = /^(?:(?:API|UI|DB)-\d{4}|THEMA-\d{3})$/;
|
|
812
837
|
function parseContractRefs(text, options = {}) {
|
|
813
838
|
const linePattern = buildLinePattern(options);
|
|
814
839
|
const lines = [];
|
|
@@ -979,7 +1004,7 @@ function parseSpec(md, file) {
|
|
|
979
1004
|
|
|
980
1005
|
// src/core/traceability.ts
|
|
981
1006
|
var import_promises6 = require("fs/promises");
|
|
982
|
-
var
|
|
1007
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
983
1008
|
|
|
984
1009
|
// src/core/gherkin/parse.ts
|
|
985
1010
|
var import_gherkin = require("@cucumber/gherkin");
|
|
@@ -1207,7 +1232,7 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1207
1232
|
};
|
|
1208
1233
|
}
|
|
1209
1234
|
const normalizedFiles = Array.from(
|
|
1210
|
-
new Set(scanResult.files.map((file) =>
|
|
1235
|
+
new Set(scanResult.files.map((file) => import_node_path7.default.normalize(file)))
|
|
1211
1236
|
);
|
|
1212
1237
|
for (const file of normalizedFiles) {
|
|
1213
1238
|
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
@@ -1270,11 +1295,11 @@ function formatError3(error) {
|
|
|
1270
1295
|
|
|
1271
1296
|
// src/core/version.ts
|
|
1272
1297
|
var import_promises7 = require("fs/promises");
|
|
1273
|
-
var
|
|
1298
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1274
1299
|
var import_node_url = require("url");
|
|
1275
1300
|
async function resolveToolVersion() {
|
|
1276
|
-
if ("1.0.
|
|
1277
|
-
return "1.0.
|
|
1301
|
+
if ("1.0.3".length > 0) {
|
|
1302
|
+
return "1.0.3";
|
|
1278
1303
|
}
|
|
1279
1304
|
try {
|
|
1280
1305
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1289,18 +1314,18 @@ async function resolveToolVersion() {
|
|
|
1289
1314
|
function resolvePackageJsonPath() {
|
|
1290
1315
|
const base = __filename;
|
|
1291
1316
|
const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
|
|
1292
|
-
return
|
|
1317
|
+
return import_node_path8.default.resolve(import_node_path8.default.dirname(basePath), "../../package.json");
|
|
1293
1318
|
}
|
|
1294
1319
|
|
|
1295
1320
|
// src/core/validators/contracts.ts
|
|
1296
1321
|
var import_promises8 = require("fs/promises");
|
|
1297
|
-
var
|
|
1322
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1298
1323
|
|
|
1299
1324
|
// src/core/contracts.ts
|
|
1300
|
-
var
|
|
1325
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1301
1326
|
var import_yaml2 = require("yaml");
|
|
1302
1327
|
function parseStructuredContract(file, text) {
|
|
1303
|
-
const ext =
|
|
1328
|
+
const ext = import_node_path9.default.extname(file).toLowerCase();
|
|
1304
1329
|
if (ext === ".json") {
|
|
1305
1330
|
return JSON.parse(text);
|
|
1306
1331
|
}
|
|
@@ -1317,17 +1342,23 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1317
1342
|
label: "ALTER TABLE ... DROP"
|
|
1318
1343
|
}
|
|
1319
1344
|
];
|
|
1345
|
+
var THEMA_ID_RE = /^THEMA-\d{3}$/;
|
|
1320
1346
|
async function validateContracts(root, config) {
|
|
1321
1347
|
const issues = [];
|
|
1322
|
-
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1323
|
-
issues.push(...await validateUiContracts(import_node_path9.default.join(contractsRoot, "ui")));
|
|
1324
|
-
issues.push(...await validateApiContracts(import_node_path9.default.join(contractsRoot, "api")));
|
|
1325
|
-
issues.push(...await validateDbContracts(import_node_path9.default.join(contractsRoot, "db")));
|
|
1326
1348
|
const contractIndex = await buildContractIndex(root, config);
|
|
1349
|
+
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1350
|
+
const uiRoot = import_node_path10.default.join(contractsRoot, "ui");
|
|
1351
|
+
const themaIds = new Set(
|
|
1352
|
+
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
1353
|
+
);
|
|
1354
|
+
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
1355
|
+
issues.push(...await validateThemaContracts(uiRoot));
|
|
1356
|
+
issues.push(...await validateApiContracts(import_node_path10.default.join(contractsRoot, "api")));
|
|
1357
|
+
issues.push(...await validateDbContracts(import_node_path10.default.join(contractsRoot, "db")));
|
|
1327
1358
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
1328
1359
|
return issues;
|
|
1329
1360
|
}
|
|
1330
|
-
async function validateUiContracts(uiRoot) {
|
|
1361
|
+
async function validateUiContracts(uiRoot, themaIds) {
|
|
1331
1362
|
const files = await collectUiContractFiles(uiRoot);
|
|
1332
1363
|
if (files.length === 0) {
|
|
1333
1364
|
return [
|
|
@@ -1341,6 +1372,60 @@ async function validateUiContracts(uiRoot) {
|
|
|
1341
1372
|
];
|
|
1342
1373
|
}
|
|
1343
1374
|
const issues = [];
|
|
1375
|
+
for (const file of files) {
|
|
1376
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
1377
|
+
const declaredIds = extractDeclaredContractIds(text);
|
|
1378
|
+
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
1379
|
+
let doc = null;
|
|
1380
|
+
try {
|
|
1381
|
+
doc = parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
issues.push(
|
|
1384
|
+
issue(
|
|
1385
|
+
"QFAI-CONTRACT-001",
|
|
1386
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
1387
|
+
"error",
|
|
1388
|
+
file,
|
|
1389
|
+
"contracts.ui.parse"
|
|
1390
|
+
)
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
const invalidIds = extractInvalidIds(text, [
|
|
1394
|
+
"SPEC",
|
|
1395
|
+
"BR",
|
|
1396
|
+
"SC",
|
|
1397
|
+
"UI",
|
|
1398
|
+
"API",
|
|
1399
|
+
"DB",
|
|
1400
|
+
"THEMA",
|
|
1401
|
+
"ADR"
|
|
1402
|
+
]).filter((id) => !shouldIgnoreInvalidId(id, doc));
|
|
1403
|
+
if (invalidIds.length > 0) {
|
|
1404
|
+
issues.push(
|
|
1405
|
+
issue(
|
|
1406
|
+
"QFAI-ID-002",
|
|
1407
|
+
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
1408
|
+
"error",
|
|
1409
|
+
file,
|
|
1410
|
+
"id.format",
|
|
1411
|
+
invalidIds
|
|
1412
|
+
)
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
if (doc) {
|
|
1416
|
+
issues.push(
|
|
1417
|
+
...await validateUiContractDoc(doc, file, uiRoot, themaIds)
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return issues;
|
|
1422
|
+
}
|
|
1423
|
+
async function validateThemaContracts(uiRoot) {
|
|
1424
|
+
const files = await collectThemaContractFiles(uiRoot);
|
|
1425
|
+
if (files.length === 0) {
|
|
1426
|
+
return [];
|
|
1427
|
+
}
|
|
1428
|
+
const issues = [];
|
|
1344
1429
|
for (const file of files) {
|
|
1345
1430
|
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
1346
1431
|
const invalidIds = extractInvalidIds(text, [
|
|
@@ -1350,6 +1435,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1350
1435
|
"UI",
|
|
1351
1436
|
"API",
|
|
1352
1437
|
"DB",
|
|
1438
|
+
"THEMA",
|
|
1353
1439
|
"ADR"
|
|
1354
1440
|
]);
|
|
1355
1441
|
if (invalidIds.length > 0) {
|
|
@@ -1365,17 +1451,95 @@ async function validateUiContracts(uiRoot) {
|
|
|
1365
1451
|
);
|
|
1366
1452
|
}
|
|
1367
1453
|
const declaredIds = extractDeclaredContractIds(text);
|
|
1368
|
-
|
|
1454
|
+
if (declaredIds.length === 0) {
|
|
1455
|
+
issues.push(
|
|
1456
|
+
issue(
|
|
1457
|
+
"QFAI-THEMA-010",
|
|
1458
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306B QFAI-CONTRACT-ID \u304C\u3042\u308A\u307E\u305B\u3093: ${file}`,
|
|
1459
|
+
"error",
|
|
1460
|
+
file,
|
|
1461
|
+
"contracts.thema.declaration"
|
|
1462
|
+
)
|
|
1463
|
+
);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (declaredIds.length > 1) {
|
|
1467
|
+
issues.push(
|
|
1468
|
+
issue(
|
|
1469
|
+
"QFAI-THEMA-011",
|
|
1470
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306B\u8907\u6570\u306E QFAI-CONTRACT-ID \u304C\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u3059: ${declaredIds.join(
|
|
1471
|
+
", "
|
|
1472
|
+
)}`,
|
|
1473
|
+
"error",
|
|
1474
|
+
file,
|
|
1475
|
+
"contracts.thema.declaration",
|
|
1476
|
+
declaredIds
|
|
1477
|
+
)
|
|
1478
|
+
);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
const declaredId = declaredIds[0] ?? "";
|
|
1482
|
+
if (!THEMA_ID_RE.test(declaredId)) {
|
|
1483
|
+
issues.push(
|
|
1484
|
+
issue(
|
|
1485
|
+
"QFAI-THEMA-012",
|
|
1486
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E ID \u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${declaredId}`,
|
|
1487
|
+
"error",
|
|
1488
|
+
file,
|
|
1489
|
+
"contracts.thema.idFormat",
|
|
1490
|
+
[declaredId]
|
|
1491
|
+
)
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
let doc;
|
|
1369
1495
|
try {
|
|
1370
|
-
parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1496
|
+
doc = parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1371
1497
|
} catch (error) {
|
|
1372
1498
|
issues.push(
|
|
1373
1499
|
issue(
|
|
1374
|
-
"QFAI-
|
|
1375
|
-
`
|
|
1500
|
+
"QFAI-THEMA-001",
|
|
1501
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
1376
1502
|
"error",
|
|
1377
1503
|
file,
|
|
1378
|
-
"contracts.
|
|
1504
|
+
"contracts.thema.parse"
|
|
1505
|
+
)
|
|
1506
|
+
);
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const docId = typeof doc.id === "string" ? doc.id : "";
|
|
1510
|
+
if (!THEMA_ID_RE.test(docId)) {
|
|
1511
|
+
issues.push(
|
|
1512
|
+
issue(
|
|
1513
|
+
"QFAI-THEMA-012",
|
|
1514
|
+
docId.length > 0 ? `thema \u306E id \u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${docId}` : "thema \u306E id \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1515
|
+
"error",
|
|
1516
|
+
file,
|
|
1517
|
+
"contracts.thema.idFormat",
|
|
1518
|
+
docId.length > 0 ? [docId] : void 0
|
|
1519
|
+
)
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
const name = typeof doc.name === "string" ? doc.name : "";
|
|
1523
|
+
if (!name) {
|
|
1524
|
+
issues.push(
|
|
1525
|
+
issue(
|
|
1526
|
+
"QFAI-THEMA-014",
|
|
1527
|
+
"thema \u306E name \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1528
|
+
"error",
|
|
1529
|
+
file,
|
|
1530
|
+
"contracts.thema.name"
|
|
1531
|
+
)
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
if (declaredId && docId && declaredId !== docId) {
|
|
1535
|
+
issues.push(
|
|
1536
|
+
issue(
|
|
1537
|
+
"QFAI-THEMA-013",
|
|
1538
|
+
`thema \u306E\u5BA3\u8A00 ID \u3068 id \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093: ${declaredId} / ${docId}`,
|
|
1539
|
+
"error",
|
|
1540
|
+
file,
|
|
1541
|
+
"contracts.thema.idMismatch",
|
|
1542
|
+
[declaredId, docId]
|
|
1379
1543
|
)
|
|
1380
1544
|
);
|
|
1381
1545
|
}
|
|
@@ -1405,6 +1569,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1405
1569
|
"UI",
|
|
1406
1570
|
"API",
|
|
1407
1571
|
"DB",
|
|
1572
|
+
"THEMA",
|
|
1408
1573
|
"ADR"
|
|
1409
1574
|
]);
|
|
1410
1575
|
if (invalidIds.length > 0) {
|
|
@@ -1473,6 +1638,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
1473
1638
|
"UI",
|
|
1474
1639
|
"API",
|
|
1475
1640
|
"DB",
|
|
1641
|
+
"THEMA",
|
|
1476
1642
|
"ADR"
|
|
1477
1643
|
]);
|
|
1478
1644
|
if (invalidIds.length > 0) {
|
|
@@ -1579,6 +1745,278 @@ function validateDuplicateContractIds(contractIndex) {
|
|
|
1579
1745
|
function hasOpenApi(doc) {
|
|
1580
1746
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1581
1747
|
}
|
|
1748
|
+
async function validateUiContractDoc(doc, file, uiRoot, themaIds) {
|
|
1749
|
+
const issues = [];
|
|
1750
|
+
if (Object.prototype.hasOwnProperty.call(doc, "themaRef")) {
|
|
1751
|
+
const themaRef = doc.themaRef;
|
|
1752
|
+
if (typeof themaRef !== "string" || themaRef.length === 0) {
|
|
1753
|
+
issues.push(
|
|
1754
|
+
issue(
|
|
1755
|
+
"QFAI-UI-020",
|
|
1756
|
+
"themaRef \u306F THEMA-001 \u5F62\u5F0F\u306E\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1757
|
+
"error",
|
|
1758
|
+
file,
|
|
1759
|
+
"contracts.ui.themaRef"
|
|
1760
|
+
)
|
|
1761
|
+
);
|
|
1762
|
+
} else if (!THEMA_ID_RE.test(themaRef)) {
|
|
1763
|
+
issues.push(
|
|
1764
|
+
issue(
|
|
1765
|
+
"QFAI-UI-020",
|
|
1766
|
+
`themaRef \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${themaRef}`,
|
|
1767
|
+
"error",
|
|
1768
|
+
file,
|
|
1769
|
+
"contracts.ui.themaRef",
|
|
1770
|
+
[themaRef]
|
|
1771
|
+
)
|
|
1772
|
+
);
|
|
1773
|
+
} else if (!themaIds.has(themaRef)) {
|
|
1774
|
+
issues.push(
|
|
1775
|
+
issue(
|
|
1776
|
+
"QFAI-UI-020",
|
|
1777
|
+
`themaRef \u304C\u5B58\u5728\u3057\u306A\u3044 THEMA \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${themaRef}`,
|
|
1778
|
+
"error",
|
|
1779
|
+
file,
|
|
1780
|
+
"contracts.ui.themaRef",
|
|
1781
|
+
[themaRef]
|
|
1782
|
+
)
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
const assets = doc.assets;
|
|
1787
|
+
if (assets && typeof assets === "object") {
|
|
1788
|
+
issues.push(
|
|
1789
|
+
...await validateUiAssets(
|
|
1790
|
+
assets,
|
|
1791
|
+
file,
|
|
1792
|
+
uiRoot
|
|
1793
|
+
)
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
return issues;
|
|
1797
|
+
}
|
|
1798
|
+
async function validateUiAssets(assets, file, uiRoot) {
|
|
1799
|
+
const issues = [];
|
|
1800
|
+
const packValue = assets.pack;
|
|
1801
|
+
const useValue = assets.use;
|
|
1802
|
+
if (packValue === void 0 && useValue === void 0) {
|
|
1803
|
+
return issues;
|
|
1804
|
+
}
|
|
1805
|
+
if (typeof packValue !== "string" || packValue.length === 0) {
|
|
1806
|
+
issues.push(
|
|
1807
|
+
issue(
|
|
1808
|
+
"QFAI-ASSET-001",
|
|
1809
|
+
"assets.pack \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1810
|
+
"error",
|
|
1811
|
+
file,
|
|
1812
|
+
"assets.pack"
|
|
1813
|
+
)
|
|
1814
|
+
);
|
|
1815
|
+
return issues;
|
|
1816
|
+
}
|
|
1817
|
+
if (!isSafeRelativePath(packValue)) {
|
|
1818
|
+
issues.push(
|
|
1819
|
+
issue(
|
|
1820
|
+
"QFAI-ASSET-001",
|
|
1821
|
+
`assets.pack \u306F ui/ \u914D\u4E0B\u306E\u76F8\u5BFE\u30D1\u30B9\u306E\u307F\u8A31\u53EF\u3055\u308C\u307E\u3059: ${packValue}`,
|
|
1822
|
+
"error",
|
|
1823
|
+
file,
|
|
1824
|
+
"assets.pack",
|
|
1825
|
+
[packValue]
|
|
1826
|
+
)
|
|
1827
|
+
);
|
|
1828
|
+
return issues;
|
|
1829
|
+
}
|
|
1830
|
+
const packDir = import_node_path10.default.resolve(uiRoot, packValue);
|
|
1831
|
+
const packRelative = import_node_path10.default.relative(uiRoot, packDir);
|
|
1832
|
+
if (packRelative.startsWith("..") || import_node_path10.default.isAbsolute(packRelative)) {
|
|
1833
|
+
issues.push(
|
|
1834
|
+
issue(
|
|
1835
|
+
"QFAI-ASSET-001",
|
|
1836
|
+
`assets.pack \u306F ui/ \u914D\u4E0B\u306B\u9650\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044: ${packValue}`,
|
|
1837
|
+
"error",
|
|
1838
|
+
file,
|
|
1839
|
+
"assets.pack",
|
|
1840
|
+
[packValue]
|
|
1841
|
+
)
|
|
1842
|
+
);
|
|
1843
|
+
return issues;
|
|
1844
|
+
}
|
|
1845
|
+
if (!await exists4(packDir)) {
|
|
1846
|
+
issues.push(
|
|
1847
|
+
issue(
|
|
1848
|
+
"QFAI-ASSET-001",
|
|
1849
|
+
`assets.pack \u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${packValue}`,
|
|
1850
|
+
"error",
|
|
1851
|
+
file,
|
|
1852
|
+
"assets.pack",
|
|
1853
|
+
[packValue]
|
|
1854
|
+
)
|
|
1855
|
+
);
|
|
1856
|
+
return issues;
|
|
1857
|
+
}
|
|
1858
|
+
const assetsYamlPath = import_node_path10.default.join(packDir, "assets.yaml");
|
|
1859
|
+
if (!await exists4(assetsYamlPath)) {
|
|
1860
|
+
issues.push(
|
|
1861
|
+
issue(
|
|
1862
|
+
"QFAI-ASSET-002",
|
|
1863
|
+
`assets.yaml \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${assetsYamlPath}`,
|
|
1864
|
+
"error",
|
|
1865
|
+
assetsYamlPath,
|
|
1866
|
+
"assets.yaml"
|
|
1867
|
+
)
|
|
1868
|
+
);
|
|
1869
|
+
return issues;
|
|
1870
|
+
}
|
|
1871
|
+
let manifest;
|
|
1872
|
+
try {
|
|
1873
|
+
const manifestText = await (0, import_promises8.readFile)(assetsYamlPath, "utf-8");
|
|
1874
|
+
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
issues.push(
|
|
1877
|
+
issue(
|
|
1878
|
+
"QFAI-ASSET-002",
|
|
1879
|
+
`assets.yaml \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${assetsYamlPath} (${formatError4(error)})`,
|
|
1880
|
+
"error",
|
|
1881
|
+
assetsYamlPath,
|
|
1882
|
+
"assets.yaml"
|
|
1883
|
+
)
|
|
1884
|
+
);
|
|
1885
|
+
return issues;
|
|
1886
|
+
}
|
|
1887
|
+
const items = Array.isArray(manifest.items) ? manifest.items : [];
|
|
1888
|
+
const itemIds = /* @__PURE__ */ new Set();
|
|
1889
|
+
const itemPaths = [];
|
|
1890
|
+
for (const item of items) {
|
|
1891
|
+
if (!item || typeof item !== "object") {
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
const record2 = item;
|
|
1895
|
+
const id = typeof record2.id === "string" ? record2.id : void 0;
|
|
1896
|
+
const pathValue = typeof record2.path === "string" ? record2.path : void 0;
|
|
1897
|
+
if (id) {
|
|
1898
|
+
itemIds.add(id);
|
|
1899
|
+
}
|
|
1900
|
+
itemPaths.push({ id, path: pathValue });
|
|
1901
|
+
}
|
|
1902
|
+
if (useValue !== void 0) {
|
|
1903
|
+
if (!Array.isArray(useValue) || useValue.some((entry) => typeof entry !== "string")) {
|
|
1904
|
+
issues.push(
|
|
1905
|
+
issue(
|
|
1906
|
+
"QFAI-ASSET-003",
|
|
1907
|
+
"assets.use \u306F\u6587\u5B57\u5217\u914D\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1908
|
+
"error",
|
|
1909
|
+
file,
|
|
1910
|
+
"assets.use"
|
|
1911
|
+
)
|
|
1912
|
+
);
|
|
1913
|
+
} else {
|
|
1914
|
+
const missing = useValue.filter((entry) => !itemIds.has(entry));
|
|
1915
|
+
if (missing.length > 0) {
|
|
1916
|
+
issues.push(
|
|
1917
|
+
issue(
|
|
1918
|
+
"QFAI-ASSET-003",
|
|
1919
|
+
`assets.use \u304C assets.yaml \u306B\u5B58\u5728\u3057\u307E\u305B\u3093: ${missing.join(", ")}`,
|
|
1920
|
+
"error",
|
|
1921
|
+
file,
|
|
1922
|
+
"assets.use",
|
|
1923
|
+
missing
|
|
1924
|
+
)
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
for (const entry of itemPaths) {
|
|
1930
|
+
if (!entry.path) {
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
if (!isSafeRelativePath(entry.path)) {
|
|
1934
|
+
issues.push(
|
|
1935
|
+
issue(
|
|
1936
|
+
"QFAI-ASSET-004",
|
|
1937
|
+
`assets.yaml \u306E path \u304C\u4E0D\u6B63\u3067\u3059: ${entry.path}`,
|
|
1938
|
+
"error",
|
|
1939
|
+
assetsYamlPath,
|
|
1940
|
+
"assets.path",
|
|
1941
|
+
entry.id ? [entry.id] : [entry.path]
|
|
1942
|
+
)
|
|
1943
|
+
);
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
const assetPath = import_node_path10.default.resolve(packDir, entry.path);
|
|
1947
|
+
const assetRelative = import_node_path10.default.relative(packDir, assetPath);
|
|
1948
|
+
if (assetRelative.startsWith("..") || import_node_path10.default.isAbsolute(assetRelative)) {
|
|
1949
|
+
issues.push(
|
|
1950
|
+
issue(
|
|
1951
|
+
"QFAI-ASSET-004",
|
|
1952
|
+
`assets.yaml \u306E path \u304C packDir \u3092\u9038\u8131\u3057\u3066\u3044\u307E\u3059: ${entry.path}`,
|
|
1953
|
+
"error",
|
|
1954
|
+
assetsYamlPath,
|
|
1955
|
+
"assets.path",
|
|
1956
|
+
entry.id ? [entry.id] : [entry.path]
|
|
1957
|
+
)
|
|
1958
|
+
);
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
if (!await exists4(assetPath)) {
|
|
1962
|
+
issues.push(
|
|
1963
|
+
issue(
|
|
1964
|
+
"QFAI-ASSET-004",
|
|
1965
|
+
`assets.yaml \u306E path \u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${entry.path}`,
|
|
1966
|
+
"error",
|
|
1967
|
+
assetsYamlPath,
|
|
1968
|
+
"assets.path",
|
|
1969
|
+
entry.id ? [entry.id] : [entry.path]
|
|
1970
|
+
)
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return issues;
|
|
1975
|
+
}
|
|
1976
|
+
function shouldIgnoreInvalidId(value, doc) {
|
|
1977
|
+
if (!doc) {
|
|
1978
|
+
return false;
|
|
1979
|
+
}
|
|
1980
|
+
const assets = doc.assets;
|
|
1981
|
+
if (!assets || typeof assets !== "object") {
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
const packValue = assets.pack;
|
|
1985
|
+
if (typeof packValue !== "string" || packValue.length === 0) {
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
const normalized = packValue.replace(/\\/g, "/");
|
|
1989
|
+
const basename = import_node_path10.default.posix.basename(normalized);
|
|
1990
|
+
if (!basename) {
|
|
1991
|
+
return false;
|
|
1992
|
+
}
|
|
1993
|
+
return value.toLowerCase() === basename.toLowerCase();
|
|
1994
|
+
}
|
|
1995
|
+
function isSafeRelativePath(value) {
|
|
1996
|
+
if (!value) {
|
|
1997
|
+
return false;
|
|
1998
|
+
}
|
|
1999
|
+
if (import_node_path10.default.isAbsolute(value)) {
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
const normalized = value.replace(/\\/g, "/");
|
|
2003
|
+
if (/^[A-Za-z]:/.test(normalized)) {
|
|
2004
|
+
return false;
|
|
2005
|
+
}
|
|
2006
|
+
const segments = normalized.split("/");
|
|
2007
|
+
if (segments.some((segment) => segment === "..")) {
|
|
2008
|
+
return false;
|
|
2009
|
+
}
|
|
2010
|
+
return true;
|
|
2011
|
+
}
|
|
2012
|
+
async function exists4(target) {
|
|
2013
|
+
try {
|
|
2014
|
+
await (0, import_promises8.access)(target);
|
|
2015
|
+
return true;
|
|
2016
|
+
} catch {
|
|
2017
|
+
return false;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1582
2020
|
function formatError4(error) {
|
|
1583
2021
|
if (error instanceof Error) {
|
|
1584
2022
|
return error.message;
|
|
@@ -1609,7 +2047,7 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
1609
2047
|
|
|
1610
2048
|
// src/core/validators/delta.ts
|
|
1611
2049
|
var import_promises9 = require("fs/promises");
|
|
1612
|
-
var
|
|
2050
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1613
2051
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1614
2052
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1615
2053
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1623,7 +2061,7 @@ async function validateDeltas(root, config) {
|
|
|
1623
2061
|
}
|
|
1624
2062
|
const issues = [];
|
|
1625
2063
|
for (const pack of packs) {
|
|
1626
|
-
const deltaPath =
|
|
2064
|
+
const deltaPath = import_node_path11.default.join(pack, "delta.md");
|
|
1627
2065
|
let text;
|
|
1628
2066
|
try {
|
|
1629
2067
|
text = await (0, import_promises9.readFile)(deltaPath, "utf-8");
|
|
@@ -1703,7 +2141,7 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
1703
2141
|
|
|
1704
2142
|
// src/core/validators/ids.ts
|
|
1705
2143
|
var import_promises10 = require("fs/promises");
|
|
1706
|
-
var
|
|
2144
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1707
2145
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
1708
2146
|
async function validateDefinedIds(root, config) {
|
|
1709
2147
|
const issues = [];
|
|
@@ -1769,7 +2207,7 @@ function recordId(out, id, file) {
|
|
|
1769
2207
|
}
|
|
1770
2208
|
function formatFileList(files, root) {
|
|
1771
2209
|
return files.map((file) => {
|
|
1772
|
-
const relative =
|
|
2210
|
+
const relative = import_node_path12.default.relative(root, file);
|
|
1773
2211
|
return relative.length > 0 ? relative : file;
|
|
1774
2212
|
}).join(", ");
|
|
1775
2213
|
}
|
|
@@ -1797,19 +2235,19 @@ function issue3(code, message, severity, file, rule, refs, category = "compatibi
|
|
|
1797
2235
|
|
|
1798
2236
|
// src/core/promptsIntegrity.ts
|
|
1799
2237
|
var import_promises11 = require("fs/promises");
|
|
1800
|
-
var
|
|
2238
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1801
2239
|
|
|
1802
2240
|
// src/shared/assets.ts
|
|
1803
2241
|
var import_node_fs = require("fs");
|
|
1804
|
-
var
|
|
2242
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1805
2243
|
var import_node_url2 = require("url");
|
|
1806
2244
|
function getInitAssetsDir() {
|
|
1807
2245
|
const base = __filename;
|
|
1808
2246
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
1809
|
-
const baseDir =
|
|
2247
|
+
const baseDir = import_node_path13.default.dirname(basePath);
|
|
1810
2248
|
const candidates = [
|
|
1811
|
-
|
|
1812
|
-
|
|
2249
|
+
import_node_path13.default.resolve(baseDir, "../../../assets/init"),
|
|
2250
|
+
import_node_path13.default.resolve(baseDir, "../../assets/init")
|
|
1813
2251
|
];
|
|
1814
2252
|
for (const candidate of candidates) {
|
|
1815
2253
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
@@ -1827,10 +2265,10 @@ function getInitAssetsDir() {
|
|
|
1827
2265
|
|
|
1828
2266
|
// src/core/promptsIntegrity.ts
|
|
1829
2267
|
async function diffProjectPromptsAgainstInitAssets(root) {
|
|
1830
|
-
const promptsDir =
|
|
2268
|
+
const promptsDir = import_node_path14.default.resolve(root, ".qfai", "prompts");
|
|
1831
2269
|
let templateDir;
|
|
1832
2270
|
try {
|
|
1833
|
-
templateDir =
|
|
2271
|
+
templateDir = import_node_path14.default.join(getInitAssetsDir(), ".qfai", "prompts");
|
|
1834
2272
|
} catch {
|
|
1835
2273
|
return {
|
|
1836
2274
|
status: "skipped_missing_assets",
|
|
@@ -1907,7 +2345,7 @@ function normalizeNewlines(text) {
|
|
|
1907
2345
|
return text.replace(/\r\n/g, "\n");
|
|
1908
2346
|
}
|
|
1909
2347
|
function toRel(base, abs) {
|
|
1910
|
-
const rel =
|
|
2348
|
+
const rel = import_node_path14.default.relative(base, abs);
|
|
1911
2349
|
return rel.replace(/[\\/]+/g, "/");
|
|
1912
2350
|
}
|
|
1913
2351
|
function intersectKeys(a, b) {
|
|
@@ -1953,6 +2391,7 @@ async function validatePromptsIntegrity(root) {
|
|
|
1953
2391
|
|
|
1954
2392
|
// src/core/validators/scenario.ts
|
|
1955
2393
|
var import_promises12 = require("fs/promises");
|
|
2394
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
1956
2395
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
1957
2396
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
1958
2397
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -1975,6 +2414,18 @@ async function validateScenarios(root, config) {
|
|
|
1975
2414
|
}
|
|
1976
2415
|
const issues = [];
|
|
1977
2416
|
for (const entry of entries) {
|
|
2417
|
+
const legacyScenarioPath = import_node_path15.default.join(entry.dir, "scenario.md");
|
|
2418
|
+
if (await fileExists(legacyScenarioPath)) {
|
|
2419
|
+
issues.push(
|
|
2420
|
+
issue4(
|
|
2421
|
+
"QFAI-SC-004",
|
|
2422
|
+
"scenario.md \u306F\u975E\u5BFE\u5FDC\u3067\u3059\u3002scenario.feature \u3078\u79FB\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
2423
|
+
"error",
|
|
2424
|
+
legacyScenarioPath,
|
|
2425
|
+
"scenario.legacy"
|
|
2426
|
+
)
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
1978
2429
|
let text;
|
|
1979
2430
|
try {
|
|
1980
2431
|
text = await (0, import_promises12.readFile)(entry.scenarioPath, "utf-8");
|
|
@@ -2006,6 +2457,7 @@ function validateScenarioContent(text, file) {
|
|
|
2006
2457
|
"UI",
|
|
2007
2458
|
"API",
|
|
2008
2459
|
"DB",
|
|
2460
|
+
"THEMA",
|
|
2009
2461
|
"ADR"
|
|
2010
2462
|
]);
|
|
2011
2463
|
if (invalidIds.length > 0) {
|
|
@@ -2149,6 +2601,14 @@ function isMissingFileError3(error) {
|
|
|
2149
2601
|
}
|
|
2150
2602
|
return error.code === "ENOENT";
|
|
2151
2603
|
}
|
|
2604
|
+
async function fileExists(target) {
|
|
2605
|
+
try {
|
|
2606
|
+
await (0, import_promises12.access)(target);
|
|
2607
|
+
return true;
|
|
2608
|
+
} catch {
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2152
2612
|
|
|
2153
2613
|
// src/core/validators/spec.ts
|
|
2154
2614
|
var import_promises13 = require("fs/promises");
|
|
@@ -2208,6 +2668,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
2208
2668
|
"UI",
|
|
2209
2669
|
"API",
|
|
2210
2670
|
"DB",
|
|
2671
|
+
"THEMA",
|
|
2211
2672
|
"ADR"
|
|
2212
2673
|
]);
|
|
2213
2674
|
if (invalidIds.length > 0) {
|
|
@@ -2387,7 +2848,7 @@ async function validateTraceability(root, config) {
|
|
|
2387
2848
|
"QFAI-TRACE-021",
|
|
2388
2849
|
`Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
|
|
2389
2850
|
", "
|
|
2390
|
-
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2851
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001 / THEMA-001)`,
|
|
2391
2852
|
"error",
|
|
2392
2853
|
file,
|
|
2393
2854
|
"traceability.specContractRefFormat",
|
|
@@ -2451,7 +2912,7 @@ async function validateTraceability(root, config) {
|
|
|
2451
2912
|
"QFAI-TRACE-032",
|
|
2452
2913
|
`Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
|
|
2453
2914
|
", "
|
|
2454
|
-
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2915
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001 / THEMA-001)`,
|
|
2455
2916
|
"error",
|
|
2456
2917
|
file,
|
|
2457
2918
|
"traceability.scenarioContractRefFormat",
|
|
@@ -2830,17 +3291,25 @@ function countIssues(issues) {
|
|
|
2830
3291
|
}
|
|
2831
3292
|
|
|
2832
3293
|
// src/core/report.ts
|
|
2833
|
-
var ID_PREFIXES2 = [
|
|
3294
|
+
var ID_PREFIXES2 = [
|
|
3295
|
+
"SPEC",
|
|
3296
|
+
"BR",
|
|
3297
|
+
"SC",
|
|
3298
|
+
"UI",
|
|
3299
|
+
"API",
|
|
3300
|
+
"DB",
|
|
3301
|
+
"THEMA"
|
|
3302
|
+
];
|
|
2834
3303
|
async function createReportData(root, validation, configResult) {
|
|
2835
|
-
const resolvedRoot =
|
|
3304
|
+
const resolvedRoot = import_node_path16.default.resolve(root);
|
|
2836
3305
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
2837
3306
|
const config = resolved.config;
|
|
2838
3307
|
const configPath = resolved.configPath;
|
|
2839
3308
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
2840
3309
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
2841
|
-
const apiRoot =
|
|
2842
|
-
const uiRoot =
|
|
2843
|
-
const dbRoot =
|
|
3310
|
+
const apiRoot = import_node_path16.default.join(contractsRoot, "api");
|
|
3311
|
+
const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
|
|
3312
|
+
const dbRoot = import_node_path16.default.join(contractsRoot, "db");
|
|
2844
3313
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
2845
3314
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
2846
3315
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2848,7 +3317,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2848
3317
|
const {
|
|
2849
3318
|
api: apiFiles,
|
|
2850
3319
|
ui: uiFiles,
|
|
2851
|
-
db: dbFiles
|
|
3320
|
+
db: dbFiles,
|
|
3321
|
+
thema: themaFiles
|
|
2852
3322
|
} = await collectContractFiles(uiRoot, apiRoot, dbRoot);
|
|
2853
3323
|
const contractIndex = await buildContractIndex(resolvedRoot, config);
|
|
2854
3324
|
const contractIdList = Array.from(contractIndex.ids);
|
|
@@ -2875,7 +3345,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2875
3345
|
...scenarioFiles,
|
|
2876
3346
|
...apiFiles,
|
|
2877
3347
|
...uiFiles,
|
|
2878
|
-
...dbFiles
|
|
3348
|
+
...dbFiles,
|
|
3349
|
+
...themaFiles
|
|
2879
3350
|
]);
|
|
2880
3351
|
const upstreamIds = await collectUpstreamIds([
|
|
2881
3352
|
...specFiles,
|
|
@@ -2912,7 +3383,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2912
3383
|
contracts: {
|
|
2913
3384
|
api: apiFiles.length,
|
|
2914
3385
|
ui: uiFiles.length,
|
|
2915
|
-
db: dbFiles.length
|
|
3386
|
+
db: dbFiles.length,
|
|
3387
|
+
thema: themaFiles.length
|
|
2916
3388
|
},
|
|
2917
3389
|
counts: normalizedValidation.counts
|
|
2918
3390
|
},
|
|
@@ -2922,7 +3394,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2922
3394
|
sc: idsByPrefix.SC,
|
|
2923
3395
|
ui: idsByPrefix.UI,
|
|
2924
3396
|
api: idsByPrefix.API,
|
|
2925
|
-
db: idsByPrefix.DB
|
|
3397
|
+
db: idsByPrefix.DB,
|
|
3398
|
+
thema: idsByPrefix.THEMA
|
|
2926
3399
|
},
|
|
2927
3400
|
traceability: {
|
|
2928
3401
|
upstreamIdsFound: upstreamIds.size,
|
|
@@ -2992,7 +3465,7 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
2992
3465
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
2993
3466
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
2994
3467
|
lines.push(
|
|
2995
|
-
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
3468
|
+
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db} / thema ${data.summary.contracts.thema}`
|
|
2996
3469
|
);
|
|
2997
3470
|
lines.push(
|
|
2998
3471
|
`- issues(total): info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
@@ -3136,6 +3609,7 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
3136
3609
|
lines.push(formatIdLine("UI", data.ids.ui));
|
|
3137
3610
|
lines.push(formatIdLine("API", data.ids.api));
|
|
3138
3611
|
lines.push(formatIdLine("DB", data.ids.db));
|
|
3612
|
+
lines.push(formatIdLine("THEMA", data.ids.thema));
|
|
3139
3613
|
lines.push("");
|
|
3140
3614
|
lines.push("## Traceability");
|
|
3141
3615
|
lines.push("");
|
|
@@ -3357,7 +3831,8 @@ async function collectIds(files) {
|
|
|
3357
3831
|
SC: /* @__PURE__ */ new Set(),
|
|
3358
3832
|
UI: /* @__PURE__ */ new Set(),
|
|
3359
3833
|
API: /* @__PURE__ */ new Set(),
|
|
3360
|
-
DB: /* @__PURE__ */ new Set()
|
|
3834
|
+
DB: /* @__PURE__ */ new Set(),
|
|
3835
|
+
THEMA: /* @__PURE__ */ new Set()
|
|
3361
3836
|
};
|
|
3362
3837
|
for (const file of files) {
|
|
3363
3838
|
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
@@ -3372,7 +3847,8 @@ async function collectIds(files) {
|
|
|
3372
3847
|
SC: toSortedArray2(result.SC),
|
|
3373
3848
|
UI: toSortedArray2(result.UI),
|
|
3374
3849
|
API: toSortedArray2(result.API),
|
|
3375
|
-
DB: toSortedArray2(result.DB)
|
|
3850
|
+
DB: toSortedArray2(result.DB),
|
|
3851
|
+
THEMA: toSortedArray2(result.THEMA)
|
|
3376
3852
|
};
|
|
3377
3853
|
}
|
|
3378
3854
|
async function collectUpstreamIds(files) {
|