qfai 1.0.2 → 1.0.4
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 +13 -12
- package/assets/init/.qfai/README.md +2 -7
- 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/promptpack/commands/plan.md +1 -1
- package/assets/init/.qfai/promptpack/commands/review.md +1 -2
- package/assets/init/.qfai/promptpack/constitution.md +1 -1
- package/assets/init/.qfai/prompts/README.md +1 -3
- package/assets/init/.qfai/prompts/qfai-maintain-traceability.md +3 -3
- package/assets/init/.qfai/prompts/require-to-spec.md +1 -2
- package/assets/init/.qfai/specs/README.md +3 -4
- package/assets/init/.qfai/specs/spec-0001/delta.md +0 -5
- package/assets/init/.qfai/specs/spec-0001/scenario.feature +1 -1
- package/assets/init/.qfai/specs/spec-0001/spec.md +1 -1
- package/assets/init/root/qfai.config.yaml +0 -1
- package/dist/cli/index.cjs +596 -162
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +598 -164
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +549 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +551 -116
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/assets/init/.qfai/promptpack/modes/change.md +0 -5
- package/assets/init/.qfai/promptpack/modes/compatibility.md +0 -6
- package/assets/init/.qfai/promptpack/steering/compatibility-vs-change.md +0 -42
- package/assets/init/.qfai/prompts/qfai-classify-change.md +0 -33
- package/assets/init/.qfai/rules/conventions.md +0 -27
- package/assets/init/.qfai/rules/pnpm.md +0 -29
- package/assets/init/.qfai/samples/analyze/analysis.md +0 -38
- package/assets/init/.qfai/samples/analyze/input_bundle.md +0 -54
package/dist/index.cjs
CHANGED
|
@@ -63,7 +63,6 @@ var defaultConfig = {
|
|
|
63
63
|
paths: {
|
|
64
64
|
contractsDir: ".qfai/contracts",
|
|
65
65
|
specsDir: ".qfai/specs",
|
|
66
|
-
rulesDir: ".qfai/rules",
|
|
67
66
|
outDir: ".qfai/out",
|
|
68
67
|
promptsDir: ".qfai/prompts",
|
|
69
68
|
srcDir: "src",
|
|
@@ -176,13 +175,6 @@ function normalizePaths(raw, configPath, issues) {
|
|
|
176
175
|
configPath,
|
|
177
176
|
issues
|
|
178
177
|
),
|
|
179
|
-
rulesDir: readString(
|
|
180
|
-
raw.rulesDir,
|
|
181
|
-
base.rulesDir,
|
|
182
|
-
"paths.rulesDir",
|
|
183
|
-
configPath,
|
|
184
|
-
issues
|
|
185
|
-
),
|
|
186
178
|
outDir: readString(
|
|
187
179
|
raw.outDir,
|
|
188
180
|
base.outDir,
|
|
@@ -456,7 +448,15 @@ function isRecord(value) {
|
|
|
456
448
|
}
|
|
457
449
|
|
|
458
450
|
// src/core/ids.ts
|
|
459
|
-
var ID_PREFIXES = [
|
|
451
|
+
var ID_PREFIXES = [
|
|
452
|
+
"SPEC",
|
|
453
|
+
"BR",
|
|
454
|
+
"SC",
|
|
455
|
+
"UI",
|
|
456
|
+
"API",
|
|
457
|
+
"DB",
|
|
458
|
+
"THEMA"
|
|
459
|
+
];
|
|
460
460
|
var STRICT_ID_PATTERNS = {
|
|
461
461
|
SPEC: /\bSPEC-\d{4}\b/g,
|
|
462
462
|
BR: /\bBR-\d{4}\b/g,
|
|
@@ -464,6 +464,7 @@ var STRICT_ID_PATTERNS = {
|
|
|
464
464
|
UI: /\bUI-\d{4}\b/g,
|
|
465
465
|
API: /\bAPI-\d{4}\b/g,
|
|
466
466
|
DB: /\bDB-\d{4}\b/g,
|
|
467
|
+
THEMA: /\bTHEMA-\d{3}\b/g,
|
|
467
468
|
ADR: /\bADR-\d{4}\b/g
|
|
468
469
|
};
|
|
469
470
|
var LOOSE_ID_PATTERNS = {
|
|
@@ -473,6 +474,7 @@ var LOOSE_ID_PATTERNS = {
|
|
|
473
474
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
474
475
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
475
476
|
DB: /\bDB-[A-Za-z0-9_-]+\b/gi,
|
|
477
|
+
THEMA: /\bTHEMA-[A-Za-z0-9_-]+\b/gi,
|
|
476
478
|
ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
|
|
477
479
|
};
|
|
478
480
|
function extractIds(text, prefix) {
|
|
@@ -510,14 +512,15 @@ function isValidId(value, prefix) {
|
|
|
510
512
|
|
|
511
513
|
// src/core/report.ts
|
|
512
514
|
var import_promises15 = require("fs/promises");
|
|
513
|
-
var
|
|
515
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
514
516
|
|
|
515
517
|
// src/core/contractIndex.ts
|
|
516
518
|
var import_promises5 = require("fs/promises");
|
|
517
|
-
var
|
|
519
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
518
520
|
|
|
519
521
|
// src/core/discovery.ts
|
|
520
522
|
var import_promises4 = require("fs/promises");
|
|
523
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
521
524
|
|
|
522
525
|
// src/core/fs.ts
|
|
523
526
|
var import_promises2 = require("fs/promises");
|
|
@@ -666,7 +669,12 @@ async function collectScenarioFiles(specsRoot) {
|
|
|
666
669
|
return filterExisting(entries.map((entry) => entry.scenarioPath));
|
|
667
670
|
}
|
|
668
671
|
async function collectUiContractFiles(uiRoot) {
|
|
669
|
-
|
|
672
|
+
const files = await collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
|
|
673
|
+
return filterByBasenamePrefix(files, "ui-");
|
|
674
|
+
}
|
|
675
|
+
async function collectThemaContractFiles(uiRoot) {
|
|
676
|
+
const files = await collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
|
|
677
|
+
return filterByBasenamePrefix(files, "thema-");
|
|
670
678
|
}
|
|
671
679
|
async function collectApiContractFiles(apiRoot) {
|
|
672
680
|
return collectFiles(apiRoot, { extensions: [".yaml", ".yml", ".json"] });
|
|
@@ -675,12 +683,13 @@ async function collectDbContractFiles(dbRoot) {
|
|
|
675
683
|
return collectFiles(dbRoot, { extensions: [".sql"] });
|
|
676
684
|
}
|
|
677
685
|
async function collectContractFiles(uiRoot, apiRoot, dbRoot) {
|
|
678
|
-
const [ui, api, db] = await Promise.all([
|
|
686
|
+
const [ui, thema, api, db] = await Promise.all([
|
|
679
687
|
collectUiContractFiles(uiRoot),
|
|
688
|
+
collectThemaContractFiles(uiRoot),
|
|
680
689
|
collectApiContractFiles(apiRoot),
|
|
681
690
|
collectDbContractFiles(dbRoot)
|
|
682
691
|
]);
|
|
683
|
-
return { ui, api, db };
|
|
692
|
+
return { ui, thema, api, db };
|
|
684
693
|
}
|
|
685
694
|
async function filterExisting(files) {
|
|
686
695
|
const existing = [];
|
|
@@ -699,10 +708,16 @@ async function exists3(target) {
|
|
|
699
708
|
return false;
|
|
700
709
|
}
|
|
701
710
|
}
|
|
711
|
+
function filterByBasenamePrefix(files, prefix) {
|
|
712
|
+
const lowerPrefix = prefix.toLowerCase();
|
|
713
|
+
return files.filter(
|
|
714
|
+
(file) => import_node_path4.default.basename(file).toLowerCase().startsWith(lowerPrefix)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
702
717
|
|
|
703
718
|
// 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*$/;
|
|
719
|
+
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
720
|
+
var CONTRACT_DECLARATION_LINE_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*(?:(?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/;
|
|
706
721
|
function extractDeclaredContractIds(text) {
|
|
707
722
|
const ids = [];
|
|
708
723
|
for (const match of text.matchAll(CONTRACT_DECLARATION_RE)) {
|
|
@@ -720,20 +735,22 @@ function stripContractDeclarationLines(text) {
|
|
|
720
735
|
// src/core/contractIndex.ts
|
|
721
736
|
async function buildContractIndex(root, config) {
|
|
722
737
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
723
|
-
const uiRoot =
|
|
724
|
-
const apiRoot =
|
|
725
|
-
const dbRoot =
|
|
726
|
-
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
738
|
+
const uiRoot = import_node_path5.default.join(contractsRoot, "ui");
|
|
739
|
+
const apiRoot = import_node_path5.default.join(contractsRoot, "api");
|
|
740
|
+
const dbRoot = import_node_path5.default.join(contractsRoot, "db");
|
|
741
|
+
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
727
742
|
collectUiContractFiles(uiRoot),
|
|
743
|
+
collectThemaContractFiles(uiRoot),
|
|
728
744
|
collectApiContractFiles(apiRoot),
|
|
729
745
|
collectDbContractFiles(dbRoot)
|
|
730
746
|
]);
|
|
731
747
|
const index = {
|
|
732
748
|
ids: /* @__PURE__ */ new Set(),
|
|
733
749
|
idToFiles: /* @__PURE__ */ new Map(),
|
|
734
|
-
files: { ui: uiFiles, api: apiFiles, db: dbFiles }
|
|
750
|
+
files: { ui: uiFiles, thema: themaFiles, api: apiFiles, db: dbFiles }
|
|
735
751
|
};
|
|
736
752
|
await indexContractFiles(uiFiles, index);
|
|
753
|
+
await indexContractFiles(themaFiles, index);
|
|
737
754
|
await indexContractFiles(apiFiles, index);
|
|
738
755
|
await indexContractFiles(dbFiles, index);
|
|
739
756
|
return index;
|
|
@@ -752,15 +769,15 @@ function record(index, id, file) {
|
|
|
752
769
|
}
|
|
753
770
|
|
|
754
771
|
// src/core/paths.ts
|
|
755
|
-
var
|
|
772
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
756
773
|
function toRelativePath(root, target) {
|
|
757
774
|
if (!target) {
|
|
758
775
|
return target;
|
|
759
776
|
}
|
|
760
|
-
if (!
|
|
777
|
+
if (!import_node_path6.default.isAbsolute(target)) {
|
|
761
778
|
return toPosixPath(target);
|
|
762
779
|
}
|
|
763
|
-
const relative =
|
|
780
|
+
const relative = import_node_path6.default.relative(root, target);
|
|
764
781
|
if (!relative) {
|
|
765
782
|
return ".";
|
|
766
783
|
}
|
|
@@ -808,7 +825,7 @@ function normalizeValidationResult(root, result) {
|
|
|
808
825
|
}
|
|
809
826
|
|
|
810
827
|
// src/core/parse/contractRefs.ts
|
|
811
|
-
var CONTRACT_REF_ID_RE = /^(?:API|UI|DB)-\d{4}$/;
|
|
828
|
+
var CONTRACT_REF_ID_RE = /^(?:(?:API|UI|DB)-\d{4}|THEMA-\d{3})$/;
|
|
812
829
|
function parseContractRefs(text, options = {}) {
|
|
813
830
|
const linePattern = buildLinePattern(options);
|
|
814
831
|
const lines = [];
|
|
@@ -979,7 +996,7 @@ function parseSpec(md, file) {
|
|
|
979
996
|
|
|
980
997
|
// src/core/traceability.ts
|
|
981
998
|
var import_promises6 = require("fs/promises");
|
|
982
|
-
var
|
|
999
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
983
1000
|
|
|
984
1001
|
// src/core/gherkin/parse.ts
|
|
985
1002
|
var import_gherkin = require("@cucumber/gherkin");
|
|
@@ -1207,7 +1224,7 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
1207
1224
|
};
|
|
1208
1225
|
}
|
|
1209
1226
|
const normalizedFiles = Array.from(
|
|
1210
|
-
new Set(scanResult.files.map((file) =>
|
|
1227
|
+
new Set(scanResult.files.map((file) => import_node_path7.default.normalize(file)))
|
|
1211
1228
|
);
|
|
1212
1229
|
for (const file of normalizedFiles) {
|
|
1213
1230
|
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
@@ -1270,11 +1287,11 @@ function formatError3(error) {
|
|
|
1270
1287
|
|
|
1271
1288
|
// src/core/version.ts
|
|
1272
1289
|
var import_promises7 = require("fs/promises");
|
|
1273
|
-
var
|
|
1290
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1274
1291
|
var import_node_url = require("url");
|
|
1275
1292
|
async function resolveToolVersion() {
|
|
1276
|
-
if ("1.0.
|
|
1277
|
-
return "1.0.
|
|
1293
|
+
if ("1.0.4".length > 0) {
|
|
1294
|
+
return "1.0.4";
|
|
1278
1295
|
}
|
|
1279
1296
|
try {
|
|
1280
1297
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1289,18 +1306,18 @@ async function resolveToolVersion() {
|
|
|
1289
1306
|
function resolvePackageJsonPath() {
|
|
1290
1307
|
const base = __filename;
|
|
1291
1308
|
const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
|
|
1292
|
-
return
|
|
1309
|
+
return import_node_path8.default.resolve(import_node_path8.default.dirname(basePath), "../../package.json");
|
|
1293
1310
|
}
|
|
1294
1311
|
|
|
1295
1312
|
// src/core/validators/contracts.ts
|
|
1296
1313
|
var import_promises8 = require("fs/promises");
|
|
1297
|
-
var
|
|
1314
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
1298
1315
|
|
|
1299
1316
|
// src/core/contracts.ts
|
|
1300
|
-
var
|
|
1317
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1301
1318
|
var import_yaml2 = require("yaml");
|
|
1302
1319
|
function parseStructuredContract(file, text) {
|
|
1303
|
-
const ext =
|
|
1320
|
+
const ext = import_node_path9.default.extname(file).toLowerCase();
|
|
1304
1321
|
if (ext === ".json") {
|
|
1305
1322
|
return JSON.parse(text);
|
|
1306
1323
|
}
|
|
@@ -1317,17 +1334,23 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1317
1334
|
label: "ALTER TABLE ... DROP"
|
|
1318
1335
|
}
|
|
1319
1336
|
];
|
|
1337
|
+
var THEMA_ID_RE = /^THEMA-\d{3}$/;
|
|
1320
1338
|
async function validateContracts(root, config) {
|
|
1321
1339
|
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
1340
|
const contractIndex = await buildContractIndex(root, config);
|
|
1341
|
+
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1342
|
+
const uiRoot = import_node_path10.default.join(contractsRoot, "ui");
|
|
1343
|
+
const themaIds = new Set(
|
|
1344
|
+
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
1345
|
+
);
|
|
1346
|
+
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
1347
|
+
issues.push(...await validateThemaContracts(uiRoot));
|
|
1348
|
+
issues.push(...await validateApiContracts(import_node_path10.default.join(contractsRoot, "api")));
|
|
1349
|
+
issues.push(...await validateDbContracts(import_node_path10.default.join(contractsRoot, "db")));
|
|
1327
1350
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
1328
1351
|
return issues;
|
|
1329
1352
|
}
|
|
1330
|
-
async function validateUiContracts(uiRoot) {
|
|
1353
|
+
async function validateUiContracts(uiRoot, themaIds) {
|
|
1331
1354
|
const files = await collectUiContractFiles(uiRoot);
|
|
1332
1355
|
if (files.length === 0) {
|
|
1333
1356
|
return [
|
|
@@ -1341,6 +1364,60 @@ async function validateUiContracts(uiRoot) {
|
|
|
1341
1364
|
];
|
|
1342
1365
|
}
|
|
1343
1366
|
const issues = [];
|
|
1367
|
+
for (const file of files) {
|
|
1368
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
1369
|
+
const declaredIds = extractDeclaredContractIds(text);
|
|
1370
|
+
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
1371
|
+
let doc = null;
|
|
1372
|
+
try {
|
|
1373
|
+
doc = parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
issues.push(
|
|
1376
|
+
issue(
|
|
1377
|
+
"QFAI-CONTRACT-001",
|
|
1378
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
1379
|
+
"error",
|
|
1380
|
+
file,
|
|
1381
|
+
"contracts.ui.parse"
|
|
1382
|
+
)
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const invalidIds = extractInvalidIds(text, [
|
|
1386
|
+
"SPEC",
|
|
1387
|
+
"BR",
|
|
1388
|
+
"SC",
|
|
1389
|
+
"UI",
|
|
1390
|
+
"API",
|
|
1391
|
+
"DB",
|
|
1392
|
+
"THEMA",
|
|
1393
|
+
"ADR"
|
|
1394
|
+
]).filter((id) => !shouldIgnoreInvalidId(id, doc));
|
|
1395
|
+
if (invalidIds.length > 0) {
|
|
1396
|
+
issues.push(
|
|
1397
|
+
issue(
|
|
1398
|
+
"QFAI-ID-002",
|
|
1399
|
+
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
1400
|
+
"error",
|
|
1401
|
+
file,
|
|
1402
|
+
"id.format",
|
|
1403
|
+
invalidIds
|
|
1404
|
+
)
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
if (doc) {
|
|
1408
|
+
issues.push(
|
|
1409
|
+
...await validateUiContractDoc(doc, file, uiRoot, themaIds)
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return issues;
|
|
1414
|
+
}
|
|
1415
|
+
async function validateThemaContracts(uiRoot) {
|
|
1416
|
+
const files = await collectThemaContractFiles(uiRoot);
|
|
1417
|
+
if (files.length === 0) {
|
|
1418
|
+
return [];
|
|
1419
|
+
}
|
|
1420
|
+
const issues = [];
|
|
1344
1421
|
for (const file of files) {
|
|
1345
1422
|
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
1346
1423
|
const invalidIds = extractInvalidIds(text, [
|
|
@@ -1350,6 +1427,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1350
1427
|
"UI",
|
|
1351
1428
|
"API",
|
|
1352
1429
|
"DB",
|
|
1430
|
+
"THEMA",
|
|
1353
1431
|
"ADR"
|
|
1354
1432
|
]);
|
|
1355
1433
|
if (invalidIds.length > 0) {
|
|
@@ -1365,17 +1443,95 @@ async function validateUiContracts(uiRoot) {
|
|
|
1365
1443
|
);
|
|
1366
1444
|
}
|
|
1367
1445
|
const declaredIds = extractDeclaredContractIds(text);
|
|
1368
|
-
|
|
1446
|
+
if (declaredIds.length === 0) {
|
|
1447
|
+
issues.push(
|
|
1448
|
+
issue(
|
|
1449
|
+
"QFAI-THEMA-010",
|
|
1450
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306B QFAI-CONTRACT-ID \u304C\u3042\u308A\u307E\u305B\u3093: ${file}`,
|
|
1451
|
+
"error",
|
|
1452
|
+
file,
|
|
1453
|
+
"contracts.thema.declaration"
|
|
1454
|
+
)
|
|
1455
|
+
);
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (declaredIds.length > 1) {
|
|
1459
|
+
issues.push(
|
|
1460
|
+
issue(
|
|
1461
|
+
"QFAI-THEMA-011",
|
|
1462
|
+
`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(
|
|
1463
|
+
", "
|
|
1464
|
+
)}`,
|
|
1465
|
+
"error",
|
|
1466
|
+
file,
|
|
1467
|
+
"contracts.thema.declaration",
|
|
1468
|
+
declaredIds
|
|
1469
|
+
)
|
|
1470
|
+
);
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
const declaredId = declaredIds[0] ?? "";
|
|
1474
|
+
if (!THEMA_ID_RE.test(declaredId)) {
|
|
1475
|
+
issues.push(
|
|
1476
|
+
issue(
|
|
1477
|
+
"QFAI-THEMA-012",
|
|
1478
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E ID \u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${declaredId}`,
|
|
1479
|
+
"error",
|
|
1480
|
+
file,
|
|
1481
|
+
"contracts.thema.idFormat",
|
|
1482
|
+
[declaredId]
|
|
1483
|
+
)
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
let doc;
|
|
1369
1487
|
try {
|
|
1370
|
-
parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1488
|
+
doc = parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1371
1489
|
} catch (error) {
|
|
1372
1490
|
issues.push(
|
|
1373
1491
|
issue(
|
|
1374
|
-
"QFAI-
|
|
1375
|
-
`
|
|
1492
|
+
"QFAI-THEMA-001",
|
|
1493
|
+
`thema \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError4(error)})`,
|
|
1376
1494
|
"error",
|
|
1377
1495
|
file,
|
|
1378
|
-
"contracts.
|
|
1496
|
+
"contracts.thema.parse"
|
|
1497
|
+
)
|
|
1498
|
+
);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
const docId = typeof doc.id === "string" ? doc.id : "";
|
|
1502
|
+
if (!THEMA_ID_RE.test(docId)) {
|
|
1503
|
+
issues.push(
|
|
1504
|
+
issue(
|
|
1505
|
+
"QFAI-THEMA-012",
|
|
1506
|
+
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",
|
|
1507
|
+
"error",
|
|
1508
|
+
file,
|
|
1509
|
+
"contracts.thema.idFormat",
|
|
1510
|
+
docId.length > 0 ? [docId] : void 0
|
|
1511
|
+
)
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
const name = typeof doc.name === "string" ? doc.name : "";
|
|
1515
|
+
if (!name) {
|
|
1516
|
+
issues.push(
|
|
1517
|
+
issue(
|
|
1518
|
+
"QFAI-THEMA-014",
|
|
1519
|
+
"thema \u306E name \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1520
|
+
"error",
|
|
1521
|
+
file,
|
|
1522
|
+
"contracts.thema.name"
|
|
1523
|
+
)
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
if (declaredId && docId && declaredId !== docId) {
|
|
1527
|
+
issues.push(
|
|
1528
|
+
issue(
|
|
1529
|
+
"QFAI-THEMA-013",
|
|
1530
|
+
`thema \u306E\u5BA3\u8A00 ID \u3068 id \u304C\u4E00\u81F4\u3057\u307E\u305B\u3093: ${declaredId} / ${docId}`,
|
|
1531
|
+
"error",
|
|
1532
|
+
file,
|
|
1533
|
+
"contracts.thema.idMismatch",
|
|
1534
|
+
[declaredId, docId]
|
|
1379
1535
|
)
|
|
1380
1536
|
);
|
|
1381
1537
|
}
|
|
@@ -1405,6 +1561,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1405
1561
|
"UI",
|
|
1406
1562
|
"API",
|
|
1407
1563
|
"DB",
|
|
1564
|
+
"THEMA",
|
|
1408
1565
|
"ADR"
|
|
1409
1566
|
]);
|
|
1410
1567
|
if (invalidIds.length > 0) {
|
|
@@ -1473,6 +1630,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
1473
1630
|
"UI",
|
|
1474
1631
|
"API",
|
|
1475
1632
|
"DB",
|
|
1633
|
+
"THEMA",
|
|
1476
1634
|
"ADR"
|
|
1477
1635
|
]);
|
|
1478
1636
|
if (invalidIds.length > 0) {
|
|
@@ -1579,6 +1737,278 @@ function validateDuplicateContractIds(contractIndex) {
|
|
|
1579
1737
|
function hasOpenApi(doc) {
|
|
1580
1738
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1581
1739
|
}
|
|
1740
|
+
async function validateUiContractDoc(doc, file, uiRoot, themaIds) {
|
|
1741
|
+
const issues = [];
|
|
1742
|
+
if (Object.prototype.hasOwnProperty.call(doc, "themaRef")) {
|
|
1743
|
+
const themaRef = doc.themaRef;
|
|
1744
|
+
if (typeof themaRef !== "string" || themaRef.length === 0) {
|
|
1745
|
+
issues.push(
|
|
1746
|
+
issue(
|
|
1747
|
+
"QFAI-UI-020",
|
|
1748
|
+
"themaRef \u306F THEMA-001 \u5F62\u5F0F\u306E\u6587\u5B57\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1749
|
+
"error",
|
|
1750
|
+
file,
|
|
1751
|
+
"contracts.ui.themaRef"
|
|
1752
|
+
)
|
|
1753
|
+
);
|
|
1754
|
+
} else if (!THEMA_ID_RE.test(themaRef)) {
|
|
1755
|
+
issues.push(
|
|
1756
|
+
issue(
|
|
1757
|
+
"QFAI-UI-020",
|
|
1758
|
+
`themaRef \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${themaRef}`,
|
|
1759
|
+
"error",
|
|
1760
|
+
file,
|
|
1761
|
+
"contracts.ui.themaRef",
|
|
1762
|
+
[themaRef]
|
|
1763
|
+
)
|
|
1764
|
+
);
|
|
1765
|
+
} else if (!themaIds.has(themaRef)) {
|
|
1766
|
+
issues.push(
|
|
1767
|
+
issue(
|
|
1768
|
+
"QFAI-UI-020",
|
|
1769
|
+
`themaRef \u304C\u5B58\u5728\u3057\u306A\u3044 THEMA \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${themaRef}`,
|
|
1770
|
+
"error",
|
|
1771
|
+
file,
|
|
1772
|
+
"contracts.ui.themaRef",
|
|
1773
|
+
[themaRef]
|
|
1774
|
+
)
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
const assets = doc.assets;
|
|
1779
|
+
if (assets && typeof assets === "object") {
|
|
1780
|
+
issues.push(
|
|
1781
|
+
...await validateUiAssets(
|
|
1782
|
+
assets,
|
|
1783
|
+
file,
|
|
1784
|
+
uiRoot
|
|
1785
|
+
)
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
return issues;
|
|
1789
|
+
}
|
|
1790
|
+
async function validateUiAssets(assets, file, uiRoot) {
|
|
1791
|
+
const issues = [];
|
|
1792
|
+
const packValue = assets.pack;
|
|
1793
|
+
const useValue = assets.use;
|
|
1794
|
+
if (packValue === void 0 && useValue === void 0) {
|
|
1795
|
+
return issues;
|
|
1796
|
+
}
|
|
1797
|
+
if (typeof packValue !== "string" || packValue.length === 0) {
|
|
1798
|
+
issues.push(
|
|
1799
|
+
issue(
|
|
1800
|
+
"QFAI-ASSET-001",
|
|
1801
|
+
"assets.pack \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1802
|
+
"error",
|
|
1803
|
+
file,
|
|
1804
|
+
"assets.pack"
|
|
1805
|
+
)
|
|
1806
|
+
);
|
|
1807
|
+
return issues;
|
|
1808
|
+
}
|
|
1809
|
+
if (!isSafeRelativePath(packValue)) {
|
|
1810
|
+
issues.push(
|
|
1811
|
+
issue(
|
|
1812
|
+
"QFAI-ASSET-001",
|
|
1813
|
+
`assets.pack \u306F ui/ \u914D\u4E0B\u306E\u76F8\u5BFE\u30D1\u30B9\u306E\u307F\u8A31\u53EF\u3055\u308C\u307E\u3059: ${packValue}`,
|
|
1814
|
+
"error",
|
|
1815
|
+
file,
|
|
1816
|
+
"assets.pack",
|
|
1817
|
+
[packValue]
|
|
1818
|
+
)
|
|
1819
|
+
);
|
|
1820
|
+
return issues;
|
|
1821
|
+
}
|
|
1822
|
+
const packDir = import_node_path10.default.resolve(uiRoot, packValue);
|
|
1823
|
+
const packRelative = import_node_path10.default.relative(uiRoot, packDir);
|
|
1824
|
+
if (packRelative.startsWith("..") || import_node_path10.default.isAbsolute(packRelative)) {
|
|
1825
|
+
issues.push(
|
|
1826
|
+
issue(
|
|
1827
|
+
"QFAI-ASSET-001",
|
|
1828
|
+
`assets.pack \u306F ui/ \u914D\u4E0B\u306B\u9650\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044: ${packValue}`,
|
|
1829
|
+
"error",
|
|
1830
|
+
file,
|
|
1831
|
+
"assets.pack",
|
|
1832
|
+
[packValue]
|
|
1833
|
+
)
|
|
1834
|
+
);
|
|
1835
|
+
return issues;
|
|
1836
|
+
}
|
|
1837
|
+
if (!await exists4(packDir)) {
|
|
1838
|
+
issues.push(
|
|
1839
|
+
issue(
|
|
1840
|
+
"QFAI-ASSET-001",
|
|
1841
|
+
`assets.pack \u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${packValue}`,
|
|
1842
|
+
"error",
|
|
1843
|
+
file,
|
|
1844
|
+
"assets.pack",
|
|
1845
|
+
[packValue]
|
|
1846
|
+
)
|
|
1847
|
+
);
|
|
1848
|
+
return issues;
|
|
1849
|
+
}
|
|
1850
|
+
const assetsYamlPath = import_node_path10.default.join(packDir, "assets.yaml");
|
|
1851
|
+
if (!await exists4(assetsYamlPath)) {
|
|
1852
|
+
issues.push(
|
|
1853
|
+
issue(
|
|
1854
|
+
"QFAI-ASSET-002",
|
|
1855
|
+
`assets.yaml \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${assetsYamlPath}`,
|
|
1856
|
+
"error",
|
|
1857
|
+
assetsYamlPath,
|
|
1858
|
+
"assets.yaml"
|
|
1859
|
+
)
|
|
1860
|
+
);
|
|
1861
|
+
return issues;
|
|
1862
|
+
}
|
|
1863
|
+
let manifest;
|
|
1864
|
+
try {
|
|
1865
|
+
const manifestText = await (0, import_promises8.readFile)(assetsYamlPath, "utf-8");
|
|
1866
|
+
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
1867
|
+
} catch (error) {
|
|
1868
|
+
issues.push(
|
|
1869
|
+
issue(
|
|
1870
|
+
"QFAI-ASSET-002",
|
|
1871
|
+
`assets.yaml \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${assetsYamlPath} (${formatError4(error)})`,
|
|
1872
|
+
"error",
|
|
1873
|
+
assetsYamlPath,
|
|
1874
|
+
"assets.yaml"
|
|
1875
|
+
)
|
|
1876
|
+
);
|
|
1877
|
+
return issues;
|
|
1878
|
+
}
|
|
1879
|
+
const items = Array.isArray(manifest.items) ? manifest.items : [];
|
|
1880
|
+
const itemIds = /* @__PURE__ */ new Set();
|
|
1881
|
+
const itemPaths = [];
|
|
1882
|
+
for (const item of items) {
|
|
1883
|
+
if (!item || typeof item !== "object") {
|
|
1884
|
+
continue;
|
|
1885
|
+
}
|
|
1886
|
+
const record2 = item;
|
|
1887
|
+
const id = typeof record2.id === "string" ? record2.id : void 0;
|
|
1888
|
+
const pathValue = typeof record2.path === "string" ? record2.path : void 0;
|
|
1889
|
+
if (id) {
|
|
1890
|
+
itemIds.add(id);
|
|
1891
|
+
}
|
|
1892
|
+
itemPaths.push({ id, path: pathValue });
|
|
1893
|
+
}
|
|
1894
|
+
if (useValue !== void 0) {
|
|
1895
|
+
if (!Array.isArray(useValue) || useValue.some((entry) => typeof entry !== "string")) {
|
|
1896
|
+
issues.push(
|
|
1897
|
+
issue(
|
|
1898
|
+
"QFAI-ASSET-003",
|
|
1899
|
+
"assets.use \u306F\u6587\u5B57\u5217\u914D\u5217\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1900
|
+
"error",
|
|
1901
|
+
file,
|
|
1902
|
+
"assets.use"
|
|
1903
|
+
)
|
|
1904
|
+
);
|
|
1905
|
+
} else {
|
|
1906
|
+
const missing = useValue.filter((entry) => !itemIds.has(entry));
|
|
1907
|
+
if (missing.length > 0) {
|
|
1908
|
+
issues.push(
|
|
1909
|
+
issue(
|
|
1910
|
+
"QFAI-ASSET-003",
|
|
1911
|
+
`assets.use \u304C assets.yaml \u306B\u5B58\u5728\u3057\u307E\u305B\u3093: ${missing.join(", ")}`,
|
|
1912
|
+
"error",
|
|
1913
|
+
file,
|
|
1914
|
+
"assets.use",
|
|
1915
|
+
missing
|
|
1916
|
+
)
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
for (const entry of itemPaths) {
|
|
1922
|
+
if (!entry.path) {
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if (!isSafeRelativePath(entry.path)) {
|
|
1926
|
+
issues.push(
|
|
1927
|
+
issue(
|
|
1928
|
+
"QFAI-ASSET-004",
|
|
1929
|
+
`assets.yaml \u306E path \u304C\u4E0D\u6B63\u3067\u3059: ${entry.path}`,
|
|
1930
|
+
"error",
|
|
1931
|
+
assetsYamlPath,
|
|
1932
|
+
"assets.path",
|
|
1933
|
+
entry.id ? [entry.id] : [entry.path]
|
|
1934
|
+
)
|
|
1935
|
+
);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
const assetPath = import_node_path10.default.resolve(packDir, entry.path);
|
|
1939
|
+
const assetRelative = import_node_path10.default.relative(packDir, assetPath);
|
|
1940
|
+
if (assetRelative.startsWith("..") || import_node_path10.default.isAbsolute(assetRelative)) {
|
|
1941
|
+
issues.push(
|
|
1942
|
+
issue(
|
|
1943
|
+
"QFAI-ASSET-004",
|
|
1944
|
+
`assets.yaml \u306E path \u304C packDir \u3092\u9038\u8131\u3057\u3066\u3044\u307E\u3059: ${entry.path}`,
|
|
1945
|
+
"error",
|
|
1946
|
+
assetsYamlPath,
|
|
1947
|
+
"assets.path",
|
|
1948
|
+
entry.id ? [entry.id] : [entry.path]
|
|
1949
|
+
)
|
|
1950
|
+
);
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (!await exists4(assetPath)) {
|
|
1954
|
+
issues.push(
|
|
1955
|
+
issue(
|
|
1956
|
+
"QFAI-ASSET-004",
|
|
1957
|
+
`assets.yaml \u306E path \u304C\u5B58\u5728\u3057\u307E\u305B\u3093: ${entry.path}`,
|
|
1958
|
+
"error",
|
|
1959
|
+
assetsYamlPath,
|
|
1960
|
+
"assets.path",
|
|
1961
|
+
entry.id ? [entry.id] : [entry.path]
|
|
1962
|
+
)
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
return issues;
|
|
1967
|
+
}
|
|
1968
|
+
function shouldIgnoreInvalidId(value, doc) {
|
|
1969
|
+
if (!doc) {
|
|
1970
|
+
return false;
|
|
1971
|
+
}
|
|
1972
|
+
const assets = doc.assets;
|
|
1973
|
+
if (!assets || typeof assets !== "object") {
|
|
1974
|
+
return false;
|
|
1975
|
+
}
|
|
1976
|
+
const packValue = assets.pack;
|
|
1977
|
+
if (typeof packValue !== "string" || packValue.length === 0) {
|
|
1978
|
+
return false;
|
|
1979
|
+
}
|
|
1980
|
+
const normalized = packValue.replace(/\\/g, "/");
|
|
1981
|
+
const basename = import_node_path10.default.posix.basename(normalized);
|
|
1982
|
+
if (!basename) {
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
return value.toLowerCase() === basename.toLowerCase();
|
|
1986
|
+
}
|
|
1987
|
+
function isSafeRelativePath(value) {
|
|
1988
|
+
if (!value) {
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
if (import_node_path10.default.isAbsolute(value)) {
|
|
1992
|
+
return false;
|
|
1993
|
+
}
|
|
1994
|
+
const normalized = value.replace(/\\/g, "/");
|
|
1995
|
+
if (/^[A-Za-z]:/.test(normalized)) {
|
|
1996
|
+
return false;
|
|
1997
|
+
}
|
|
1998
|
+
const segments = normalized.split("/");
|
|
1999
|
+
if (segments.some((segment) => segment === "..")) {
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
return true;
|
|
2003
|
+
}
|
|
2004
|
+
async function exists4(target) {
|
|
2005
|
+
try {
|
|
2006
|
+
await (0, import_promises8.access)(target);
|
|
2007
|
+
return true;
|
|
2008
|
+
} catch {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
1582
2012
|
function formatError4(error) {
|
|
1583
2013
|
if (error instanceof Error) {
|
|
1584
2014
|
return error.message;
|
|
@@ -1609,12 +2039,7 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
1609
2039
|
|
|
1610
2040
|
// src/core/validators/delta.ts
|
|
1611
2041
|
var import_promises9 = require("fs/promises");
|
|
1612
|
-
var
|
|
1613
|
-
var SECTION_RE = /^##\s+変更区分/m;
|
|
1614
|
-
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1615
|
-
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
1616
|
-
var COMPAT_CHECKED_RE = /^\s*-\s*\[[xX]\]\s*Compatibility\b/m;
|
|
1617
|
-
var CHANGE_CHECKED_RE = /^\s*-\s*\[[xX]\]\s*Change\/Improvement\b/m;
|
|
2042
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1618
2043
|
async function validateDeltas(root, config) {
|
|
1619
2044
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1620
2045
|
const packs = await collectSpecPackDirs(specsRoot);
|
|
@@ -1623,10 +2048,9 @@ async function validateDeltas(root, config) {
|
|
|
1623
2048
|
}
|
|
1624
2049
|
const issues = [];
|
|
1625
2050
|
for (const pack of packs) {
|
|
1626
|
-
const deltaPath =
|
|
1627
|
-
let text;
|
|
2051
|
+
const deltaPath = import_node_path11.default.join(pack, "delta.md");
|
|
1628
2052
|
try {
|
|
1629
|
-
|
|
2053
|
+
await (0, import_promises9.readFile)(deltaPath, "utf-8");
|
|
1630
2054
|
} catch (error) {
|
|
1631
2055
|
if (isMissingFileError2(error)) {
|
|
1632
2056
|
issues.push(
|
|
@@ -1635,41 +2059,16 @@ async function validateDeltas(root, config) {
|
|
|
1635
2059
|
"delta.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1636
2060
|
"error",
|
|
1637
2061
|
deltaPath,
|
|
1638
|
-
"delta.exists"
|
|
2062
|
+
"delta.exists",
|
|
2063
|
+
void 0,
|
|
2064
|
+
"change",
|
|
2065
|
+
"spec-xxxx/delta.md \u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30C6\u30F3\u30D7\u30EC\u306F init \u751F\u6210\u7269\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF09\u3002"
|
|
1639
2066
|
)
|
|
1640
2067
|
);
|
|
1641
2068
|
continue;
|
|
1642
2069
|
}
|
|
1643
2070
|
throw error;
|
|
1644
2071
|
}
|
|
1645
|
-
const hasSection = SECTION_RE.test(text);
|
|
1646
|
-
const hasCompatibility = COMPAT_LINE_RE.test(text);
|
|
1647
|
-
const hasChange = CHANGE_LINE_RE.test(text);
|
|
1648
|
-
if (!hasSection || !hasCompatibility || !hasChange) {
|
|
1649
|
-
issues.push(
|
|
1650
|
-
issue2(
|
|
1651
|
-
"QFAI-DELTA-002",
|
|
1652
|
-
"delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002`## \u5909\u66F4\u533A\u5206` \u3068\u30C1\u30A7\u30C3\u30AF\u30DC\u30C3\u30AF\u30B9\uFF08Compatibility / Change/Improvement\uFF09\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1653
|
-
"error",
|
|
1654
|
-
deltaPath,
|
|
1655
|
-
"delta.section"
|
|
1656
|
-
)
|
|
1657
|
-
);
|
|
1658
|
-
continue;
|
|
1659
|
-
}
|
|
1660
|
-
const compatibilityChecked = COMPAT_CHECKED_RE.test(text);
|
|
1661
|
-
const changeChecked = CHANGE_CHECKED_RE.test(text);
|
|
1662
|
-
if (compatibilityChecked === changeChecked) {
|
|
1663
|
-
issues.push(
|
|
1664
|
-
issue2(
|
|
1665
|
-
"QFAI-DELTA-003",
|
|
1666
|
-
"delta.md \u306E\u5909\u66F4\u533A\u5206\u306F\u3069\u3061\u3089\u304B1\u3064\u3060\u3051\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4E21\u65B9ON/\u4E21\u65B9OFF\u306F\u7121\u52B9\u3067\u3059\uFF09\u3002",
|
|
1667
|
-
"error",
|
|
1668
|
-
deltaPath,
|
|
1669
|
-
"delta.classification"
|
|
1670
|
-
)
|
|
1671
|
-
);
|
|
1672
|
-
}
|
|
1673
2072
|
}
|
|
1674
2073
|
return issues;
|
|
1675
2074
|
}
|
|
@@ -1703,7 +2102,7 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
1703
2102
|
|
|
1704
2103
|
// src/core/validators/ids.ts
|
|
1705
2104
|
var import_promises10 = require("fs/promises");
|
|
1706
|
-
var
|
|
2105
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1707
2106
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
1708
2107
|
async function validateDefinedIds(root, config) {
|
|
1709
2108
|
const issues = [];
|
|
@@ -1769,7 +2168,7 @@ function recordId(out, id, file) {
|
|
|
1769
2168
|
}
|
|
1770
2169
|
function formatFileList(files, root) {
|
|
1771
2170
|
return files.map((file) => {
|
|
1772
|
-
const relative =
|
|
2171
|
+
const relative = import_node_path12.default.relative(root, file);
|
|
1773
2172
|
return relative.length > 0 ? relative : file;
|
|
1774
2173
|
}).join(", ");
|
|
1775
2174
|
}
|
|
@@ -1797,19 +2196,19 @@ function issue3(code, message, severity, file, rule, refs, category = "compatibi
|
|
|
1797
2196
|
|
|
1798
2197
|
// src/core/promptsIntegrity.ts
|
|
1799
2198
|
var import_promises11 = require("fs/promises");
|
|
1800
|
-
var
|
|
2199
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1801
2200
|
|
|
1802
2201
|
// src/shared/assets.ts
|
|
1803
2202
|
var import_node_fs = require("fs");
|
|
1804
|
-
var
|
|
2203
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1805
2204
|
var import_node_url2 = require("url");
|
|
1806
2205
|
function getInitAssetsDir() {
|
|
1807
2206
|
const base = __filename;
|
|
1808
2207
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
1809
|
-
const baseDir =
|
|
2208
|
+
const baseDir = import_node_path13.default.dirname(basePath);
|
|
1810
2209
|
const candidates = [
|
|
1811
|
-
|
|
1812
|
-
|
|
2210
|
+
import_node_path13.default.resolve(baseDir, "../../../assets/init"),
|
|
2211
|
+
import_node_path13.default.resolve(baseDir, "../../assets/init")
|
|
1813
2212
|
];
|
|
1814
2213
|
for (const candidate of candidates) {
|
|
1815
2214
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
@@ -1826,11 +2225,12 @@ function getInitAssetsDir() {
|
|
|
1826
2225
|
}
|
|
1827
2226
|
|
|
1828
2227
|
// src/core/promptsIntegrity.ts
|
|
2228
|
+
var LEGACY_OK_EXTRA = /* @__PURE__ */ new Set(["qfai-classify-change.md"]);
|
|
1829
2229
|
async function diffProjectPromptsAgainstInitAssets(root) {
|
|
1830
|
-
const promptsDir =
|
|
2230
|
+
const promptsDir = import_node_path14.default.resolve(root, ".qfai", "prompts");
|
|
1831
2231
|
let templateDir;
|
|
1832
2232
|
try {
|
|
1833
|
-
templateDir =
|
|
2233
|
+
templateDir = import_node_path14.default.join(getInitAssetsDir(), ".qfai", "prompts");
|
|
1834
2234
|
} catch {
|
|
1835
2235
|
return {
|
|
1836
2236
|
status: "skipped_missing_assets",
|
|
@@ -1874,6 +2274,7 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1874
2274
|
extra.push(rel);
|
|
1875
2275
|
}
|
|
1876
2276
|
}
|
|
2277
|
+
const filteredExtra = extra.filter((rel) => !LEGACY_OK_EXTRA.has(rel));
|
|
1877
2278
|
const common = intersectKeys(templateByRel, projectByRel);
|
|
1878
2279
|
for (const rel of common) {
|
|
1879
2280
|
const templateAbs = templateByRel.get(rel);
|
|
@@ -1893,13 +2294,13 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1893
2294
|
changed.push(rel);
|
|
1894
2295
|
}
|
|
1895
2296
|
}
|
|
1896
|
-
const status = missing.length > 0 ||
|
|
2297
|
+
const status = missing.length > 0 || filteredExtra.length > 0 || changed.length > 0 ? "modified" : "ok";
|
|
1897
2298
|
return {
|
|
1898
2299
|
status,
|
|
1899
2300
|
promptsDir,
|
|
1900
2301
|
templateDir,
|
|
1901
2302
|
missing: missing.sort(),
|
|
1902
|
-
extra:
|
|
2303
|
+
extra: filteredExtra.sort(),
|
|
1903
2304
|
changed: changed.sort()
|
|
1904
2305
|
};
|
|
1905
2306
|
}
|
|
@@ -1907,7 +2308,7 @@ function normalizeNewlines(text) {
|
|
|
1907
2308
|
return text.replace(/\r\n/g, "\n");
|
|
1908
2309
|
}
|
|
1909
2310
|
function toRel(base, abs) {
|
|
1910
|
-
const rel =
|
|
2311
|
+
const rel = import_node_path14.default.relative(base, abs);
|
|
1911
2312
|
return rel.replace(/[\\/]+/g, "/");
|
|
1912
2313
|
}
|
|
1913
2314
|
function intersectKeys(a, b) {
|
|
@@ -1953,6 +2354,7 @@ async function validatePromptsIntegrity(root) {
|
|
|
1953
2354
|
|
|
1954
2355
|
// src/core/validators/scenario.ts
|
|
1955
2356
|
var import_promises12 = require("fs/promises");
|
|
2357
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
1956
2358
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
1957
2359
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
1958
2360
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -1975,6 +2377,18 @@ async function validateScenarios(root, config) {
|
|
|
1975
2377
|
}
|
|
1976
2378
|
const issues = [];
|
|
1977
2379
|
for (const entry of entries) {
|
|
2380
|
+
const legacyScenarioPath = import_node_path15.default.join(entry.dir, "scenario.md");
|
|
2381
|
+
if (await fileExists(legacyScenarioPath)) {
|
|
2382
|
+
issues.push(
|
|
2383
|
+
issue4(
|
|
2384
|
+
"QFAI-SC-004",
|
|
2385
|
+
"scenario.md \u306F\u975E\u5BFE\u5FDC\u3067\u3059\u3002scenario.feature \u3078\u79FB\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
2386
|
+
"error",
|
|
2387
|
+
legacyScenarioPath,
|
|
2388
|
+
"scenario.legacy"
|
|
2389
|
+
)
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
1978
2392
|
let text;
|
|
1979
2393
|
try {
|
|
1980
2394
|
text = await (0, import_promises12.readFile)(entry.scenarioPath, "utf-8");
|
|
@@ -2006,6 +2420,7 @@ function validateScenarioContent(text, file) {
|
|
|
2006
2420
|
"UI",
|
|
2007
2421
|
"API",
|
|
2008
2422
|
"DB",
|
|
2423
|
+
"THEMA",
|
|
2009
2424
|
"ADR"
|
|
2010
2425
|
]);
|
|
2011
2426
|
if (invalidIds.length > 0) {
|
|
@@ -2149,6 +2564,14 @@ function isMissingFileError3(error) {
|
|
|
2149
2564
|
}
|
|
2150
2565
|
return error.code === "ENOENT";
|
|
2151
2566
|
}
|
|
2567
|
+
async function fileExists(target) {
|
|
2568
|
+
try {
|
|
2569
|
+
await (0, import_promises12.access)(target);
|
|
2570
|
+
return true;
|
|
2571
|
+
} catch {
|
|
2572
|
+
return false;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2152
2575
|
|
|
2153
2576
|
// src/core/validators/spec.ts
|
|
2154
2577
|
var import_promises13 = require("fs/promises");
|
|
@@ -2208,6 +2631,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
2208
2631
|
"UI",
|
|
2209
2632
|
"API",
|
|
2210
2633
|
"DB",
|
|
2634
|
+
"THEMA",
|
|
2211
2635
|
"ADR"
|
|
2212
2636
|
]);
|
|
2213
2637
|
if (invalidIds.length > 0) {
|
|
@@ -2387,7 +2811,7 @@ async function validateTraceability(root, config) {
|
|
|
2387
2811
|
"QFAI-TRACE-021",
|
|
2388
2812
|
`Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
|
|
2389
2813
|
", "
|
|
2390
|
-
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2814
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001 / THEMA-001)`,
|
|
2391
2815
|
"error",
|
|
2392
2816
|
file,
|
|
2393
2817
|
"traceability.specContractRefFormat",
|
|
@@ -2451,7 +2875,7 @@ async function validateTraceability(root, config) {
|
|
|
2451
2875
|
"QFAI-TRACE-032",
|
|
2452
2876
|
`Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
|
|
2453
2877
|
", "
|
|
2454
|
-
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2878
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001 / THEMA-001)`,
|
|
2455
2879
|
"error",
|
|
2456
2880
|
file,
|
|
2457
2881
|
"traceability.scenarioContractRefFormat",
|
|
@@ -2830,17 +3254,25 @@ function countIssues(issues) {
|
|
|
2830
3254
|
}
|
|
2831
3255
|
|
|
2832
3256
|
// src/core/report.ts
|
|
2833
|
-
var ID_PREFIXES2 = [
|
|
3257
|
+
var ID_PREFIXES2 = [
|
|
3258
|
+
"SPEC",
|
|
3259
|
+
"BR",
|
|
3260
|
+
"SC",
|
|
3261
|
+
"UI",
|
|
3262
|
+
"API",
|
|
3263
|
+
"DB",
|
|
3264
|
+
"THEMA"
|
|
3265
|
+
];
|
|
2834
3266
|
async function createReportData(root, validation, configResult) {
|
|
2835
|
-
const resolvedRoot =
|
|
3267
|
+
const resolvedRoot = import_node_path16.default.resolve(root);
|
|
2836
3268
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
2837
3269
|
const config = resolved.config;
|
|
2838
3270
|
const configPath = resolved.configPath;
|
|
2839
3271
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
2840
3272
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
2841
|
-
const apiRoot =
|
|
2842
|
-
const uiRoot =
|
|
2843
|
-
const dbRoot =
|
|
3273
|
+
const apiRoot = import_node_path16.default.join(contractsRoot, "api");
|
|
3274
|
+
const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
|
|
3275
|
+
const dbRoot = import_node_path16.default.join(contractsRoot, "db");
|
|
2844
3276
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
2845
3277
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
2846
3278
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2848,7 +3280,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2848
3280
|
const {
|
|
2849
3281
|
api: apiFiles,
|
|
2850
3282
|
ui: uiFiles,
|
|
2851
|
-
db: dbFiles
|
|
3283
|
+
db: dbFiles,
|
|
3284
|
+
thema: themaFiles
|
|
2852
3285
|
} = await collectContractFiles(uiRoot, apiRoot, dbRoot);
|
|
2853
3286
|
const contractIndex = await buildContractIndex(resolvedRoot, config);
|
|
2854
3287
|
const contractIdList = Array.from(contractIndex.ids);
|
|
@@ -2875,7 +3308,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2875
3308
|
...scenarioFiles,
|
|
2876
3309
|
...apiFiles,
|
|
2877
3310
|
...uiFiles,
|
|
2878
|
-
...dbFiles
|
|
3311
|
+
...dbFiles,
|
|
3312
|
+
...themaFiles
|
|
2879
3313
|
]);
|
|
2880
3314
|
const upstreamIds = await collectUpstreamIds([
|
|
2881
3315
|
...specFiles,
|
|
@@ -2912,7 +3346,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2912
3346
|
contracts: {
|
|
2913
3347
|
api: apiFiles.length,
|
|
2914
3348
|
ui: uiFiles.length,
|
|
2915
|
-
db: dbFiles.length
|
|
3349
|
+
db: dbFiles.length,
|
|
3350
|
+
thema: themaFiles.length
|
|
2916
3351
|
},
|
|
2917
3352
|
counts: normalizedValidation.counts
|
|
2918
3353
|
},
|
|
@@ -2922,7 +3357,8 @@ async function createReportData(root, validation, configResult) {
|
|
|
2922
3357
|
sc: idsByPrefix.SC,
|
|
2923
3358
|
ui: idsByPrefix.UI,
|
|
2924
3359
|
api: idsByPrefix.API,
|
|
2925
|
-
db: idsByPrefix.DB
|
|
3360
|
+
db: idsByPrefix.DB,
|
|
3361
|
+
thema: idsByPrefix.THEMA
|
|
2926
3362
|
},
|
|
2927
3363
|
traceability: {
|
|
2928
3364
|
upstreamIdsFound: upstreamIds.size,
|
|
@@ -2992,7 +3428,7 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
2992
3428
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
2993
3429
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
2994
3430
|
lines.push(
|
|
2995
|
-
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
3431
|
+
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db} / thema ${data.summary.contracts.thema}`
|
|
2996
3432
|
);
|
|
2997
3433
|
lines.push(
|
|
2998
3434
|
`- issues(total): info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
@@ -3136,6 +3572,7 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
3136
3572
|
lines.push(formatIdLine("UI", data.ids.ui));
|
|
3137
3573
|
lines.push(formatIdLine("API", data.ids.api));
|
|
3138
3574
|
lines.push(formatIdLine("DB", data.ids.db));
|
|
3575
|
+
lines.push(formatIdLine("THEMA", data.ids.thema));
|
|
3139
3576
|
lines.push("");
|
|
3140
3577
|
lines.push("## Traceability");
|
|
3141
3578
|
lines.push("");
|
|
@@ -3300,12 +3737,8 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
3300
3737
|
"- issue \u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
3301
3738
|
);
|
|
3302
3739
|
}
|
|
3303
|
-
lines.push(
|
|
3304
|
-
|
|
3305
|
-
);
|
|
3306
|
-
lines.push(
|
|
3307
|
-
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
|
|
3308
|
-
);
|
|
3740
|
+
lines.push("- \u5909\u66F4\u5185\u5BB9\u30FB\u53D7\u5165\u89B3\u70B9\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002");
|
|
3741
|
+
lines.push("- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md`");
|
|
3309
3742
|
return lines.join("\n");
|
|
3310
3743
|
}
|
|
3311
3744
|
function formatReportJson(data) {
|
|
@@ -3357,7 +3790,8 @@ async function collectIds(files) {
|
|
|
3357
3790
|
SC: /* @__PURE__ */ new Set(),
|
|
3358
3791
|
UI: /* @__PURE__ */ new Set(),
|
|
3359
3792
|
API: /* @__PURE__ */ new Set(),
|
|
3360
|
-
DB: /* @__PURE__ */ new Set()
|
|
3793
|
+
DB: /* @__PURE__ */ new Set(),
|
|
3794
|
+
THEMA: /* @__PURE__ */ new Set()
|
|
3361
3795
|
};
|
|
3362
3796
|
for (const file of files) {
|
|
3363
3797
|
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
@@ -3372,7 +3806,8 @@ async function collectIds(files) {
|
|
|
3372
3806
|
SC: toSortedArray2(result.SC),
|
|
3373
3807
|
UI: toSortedArray2(result.UI),
|
|
3374
3808
|
API: toSortedArray2(result.API),
|
|
3375
|
-
DB: toSortedArray2(result.DB)
|
|
3809
|
+
DB: toSortedArray2(result.DB),
|
|
3810
|
+
THEMA: toSortedArray2(result.THEMA)
|
|
3376
3811
|
};
|
|
3377
3812
|
}
|
|
3378
3813
|
async function collectUpstreamIds(files) {
|