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/cli/index.cjs
CHANGED
|
@@ -131,9 +131,9 @@ function error(message) {
|
|
|
131
131
|
async function runInit(options) {
|
|
132
132
|
const assetsRoot = getInitAssetsDir();
|
|
133
133
|
const rootAssets = import_node_path3.default.join(assetsRoot, "root");
|
|
134
|
-
const qfaiAssets = import_node_path3.default.join(assetsRoot, "qfai");
|
|
134
|
+
const qfaiAssets = import_node_path3.default.join(assetsRoot, ".qfai");
|
|
135
135
|
const destRoot = import_node_path3.default.resolve(options.dir);
|
|
136
|
-
const destQfai = import_node_path3.default.join(destRoot, "qfai");
|
|
136
|
+
const destQfai = import_node_path3.default.join(destRoot, ".qfai");
|
|
137
137
|
const rootResult = await copyTemplateTree(rootAssets, destRoot, {
|
|
138
138
|
force: options.force,
|
|
139
139
|
dryRun: options.dryRun
|
|
@@ -160,8 +160,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// src/cli/commands/report.ts
|
|
163
|
-
var
|
|
164
|
-
var
|
|
163
|
+
var import_promises12 = require("fs/promises");
|
|
164
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
165
165
|
|
|
166
166
|
// src/core/config.ts
|
|
167
167
|
var import_promises2 = require("fs/promises");
|
|
@@ -169,14 +169,13 @@ var import_node_path4 = __toESM(require("path"), 1);
|
|
|
169
169
|
var import_yaml = require("yaml");
|
|
170
170
|
var defaultConfig = {
|
|
171
171
|
paths: {
|
|
172
|
-
specDir: "qfai/spec",
|
|
173
|
-
decisionsDir: "qfai/spec/decisions",
|
|
174
|
-
scenariosDir: "qfai/spec",
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
dataContractsDir: "qfai/contracts/db",
|
|
172
|
+
specDir: ".qfai/spec",
|
|
173
|
+
decisionsDir: ".qfai/spec/decisions",
|
|
174
|
+
scenariosDir: ".qfai/spec/scenarios",
|
|
175
|
+
contractsDir: ".qfai/contracts",
|
|
176
|
+
uiContractsDir: ".qfai/contracts/ui",
|
|
177
|
+
apiContractsDir: ".qfai/contracts/api",
|
|
178
|
+
dataContractsDir: ".qfai/contracts/db",
|
|
180
179
|
srcDir: "src",
|
|
181
180
|
testsDir: "tests"
|
|
182
181
|
},
|
|
@@ -196,7 +195,8 @@ var defaultConfig = {
|
|
|
196
195
|
traceability: {
|
|
197
196
|
brMustHaveSc: true,
|
|
198
197
|
scMustTouchContracts: true,
|
|
199
|
-
allowOrphanContracts: false
|
|
198
|
+
allowOrphanContracts: false,
|
|
199
|
+
unknownContractIdSeverity: "error"
|
|
200
200
|
}
|
|
201
201
|
},
|
|
202
202
|
output: {
|
|
@@ -271,13 +271,6 @@ function normalizePaths(raw, configPath, issues) {
|
|
|
271
271
|
configPath,
|
|
272
272
|
issues
|
|
273
273
|
),
|
|
274
|
-
rulesDir: readString(
|
|
275
|
-
raw.rulesDir,
|
|
276
|
-
base.rulesDir,
|
|
277
|
-
"paths.rulesDir",
|
|
278
|
-
configPath,
|
|
279
|
-
issues
|
|
280
|
-
),
|
|
281
274
|
contractsDir: readString(
|
|
282
275
|
raw.contractsDir,
|
|
283
276
|
base.contractsDir,
|
|
@@ -402,6 +395,13 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
402
395
|
"validation.traceability.allowOrphanContracts",
|
|
403
396
|
configPath,
|
|
404
397
|
issues
|
|
398
|
+
),
|
|
399
|
+
unknownContractIdSeverity: readTraceabilitySeverity(
|
|
400
|
+
traceabilityRaw?.unknownContractIdSeverity,
|
|
401
|
+
base.traceability.unknownContractIdSeverity,
|
|
402
|
+
"validation.traceability.unknownContractIdSeverity",
|
|
403
|
+
configPath,
|
|
404
|
+
issues
|
|
405
405
|
)
|
|
406
406
|
}
|
|
407
407
|
};
|
|
@@ -481,6 +481,20 @@ function readFailOn(value, fallback, label, configPath, issues) {
|
|
|
481
481
|
}
|
|
482
482
|
return fallback;
|
|
483
483
|
}
|
|
484
|
+
function readTraceabilitySeverity(value, fallback, label, configPath, issues) {
|
|
485
|
+
if (value === "warning" || value === "error") {
|
|
486
|
+
return value;
|
|
487
|
+
}
|
|
488
|
+
if (value !== void 0) {
|
|
489
|
+
issues.push(
|
|
490
|
+
configIssue(
|
|
491
|
+
configPath,
|
|
492
|
+
`${label} \u306F warning|error \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
|
|
493
|
+
)
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return fallback;
|
|
497
|
+
}
|
|
484
498
|
function readOutputFormat(value, fallback, label, configPath, issues) {
|
|
485
499
|
if (value === "text" || value === "json" || value === "github") {
|
|
486
500
|
return value;
|
|
@@ -521,7 +535,7 @@ function isRecord(value) {
|
|
|
521
535
|
}
|
|
522
536
|
|
|
523
537
|
// src/core/report.ts
|
|
524
|
-
var
|
|
538
|
+
var import_promises11 = require("fs/promises");
|
|
525
539
|
|
|
526
540
|
// src/core/discovery.ts
|
|
527
541
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
@@ -582,8 +596,7 @@ async function exists2(target) {
|
|
|
582
596
|
}
|
|
583
597
|
|
|
584
598
|
// src/core/discovery.ts
|
|
585
|
-
var
|
|
586
|
-
var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/i;
|
|
599
|
+
var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/;
|
|
587
600
|
async function collectSpecFiles(specRoot) {
|
|
588
601
|
const files = await collectFiles(specRoot, { extensions: [".md"] });
|
|
589
602
|
return files.filter((file) => isSpecFile(file));
|
|
@@ -607,17 +620,19 @@ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
|
|
|
607
620
|
}
|
|
608
621
|
function isSpecFile(filePath) {
|
|
609
622
|
const name = import_node_path6.default.basename(filePath).toLowerCase();
|
|
610
|
-
return
|
|
623
|
+
return SPEC_NAMED_PATTERN.test(name);
|
|
611
624
|
}
|
|
612
625
|
|
|
613
626
|
// src/core/ids.ts
|
|
614
|
-
var
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
627
|
+
var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
|
|
628
|
+
var STRICT_ID_PATTERNS = {
|
|
629
|
+
SPEC: /\bSPEC-\d{4}\b/g,
|
|
630
|
+
BR: /\bBR-\d{4}\b/g,
|
|
631
|
+
SC: /\bSC-\d{4}\b/g,
|
|
632
|
+
UI: /\bUI-\d{4}\b/g,
|
|
633
|
+
API: /\bAPI-\d{4}\b/g,
|
|
634
|
+
DATA: /\bDATA-\d{4}\b/g,
|
|
635
|
+
ADR: /\bADR-\d{4}\b/g
|
|
621
636
|
};
|
|
622
637
|
var LOOSE_ID_PATTERNS = {
|
|
623
638
|
SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
|
|
@@ -625,16 +640,17 @@ var LOOSE_ID_PATTERNS = {
|
|
|
625
640
|
SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
|
|
626
641
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
627
642
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
628
|
-
DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi
|
|
643
|
+
DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi,
|
|
644
|
+
ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
|
|
629
645
|
};
|
|
630
646
|
function extractIds(text, prefix) {
|
|
631
|
-
const pattern =
|
|
647
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
632
648
|
const matches = text.match(pattern);
|
|
633
649
|
return unique(matches ?? []);
|
|
634
650
|
}
|
|
635
651
|
function extractAllIds(text) {
|
|
636
652
|
const all = [];
|
|
637
|
-
|
|
653
|
+
ID_PREFIXES.forEach((prefix) => {
|
|
638
654
|
all.push(...extractIds(text, prefix));
|
|
639
655
|
});
|
|
640
656
|
return unique(all);
|
|
@@ -655,7 +671,7 @@ function unique(values) {
|
|
|
655
671
|
return Array.from(new Set(values));
|
|
656
672
|
}
|
|
657
673
|
function isValidId(value, prefix) {
|
|
658
|
-
const pattern =
|
|
674
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
659
675
|
const strict = new RegExp(pattern.source);
|
|
660
676
|
return strict.test(value);
|
|
661
677
|
}
|
|
@@ -668,8 +684,8 @@ var import_promises4 = require("fs/promises");
|
|
|
668
684
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
669
685
|
var import_node_url2 = require("url");
|
|
670
686
|
async function resolveToolVersion() {
|
|
671
|
-
if ("0.2.
|
|
672
|
-
return "0.2.
|
|
687
|
+
if ("0.2.9".length > 0) {
|
|
688
|
+
return "0.2.9";
|
|
673
689
|
}
|
|
674
690
|
try {
|
|
675
691
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -689,8 +705,50 @@ function resolvePackageJsonPath() {
|
|
|
689
705
|
|
|
690
706
|
// src/core/validators/contracts.ts
|
|
691
707
|
var import_promises5 = require("fs/promises");
|
|
708
|
+
|
|
709
|
+
// src/core/contracts.ts
|
|
692
710
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
693
711
|
var import_yaml2 = require("yaml");
|
|
712
|
+
function parseStructuredContract(file, text) {
|
|
713
|
+
const ext = import_node_path8.default.extname(file).toLowerCase();
|
|
714
|
+
if (ext === ".json") {
|
|
715
|
+
return JSON.parse(text);
|
|
716
|
+
}
|
|
717
|
+
return (0, import_yaml2.parse)(text);
|
|
718
|
+
}
|
|
719
|
+
function extractUiContractIds(doc) {
|
|
720
|
+
const id = typeof doc.id === "string" ? doc.id : "";
|
|
721
|
+
return extractIds(id, "UI");
|
|
722
|
+
}
|
|
723
|
+
function extractApiContractIds(doc) {
|
|
724
|
+
const operationIds = /* @__PURE__ */ new Set();
|
|
725
|
+
collectOperationIds(doc, operationIds);
|
|
726
|
+
const ids = /* @__PURE__ */ new Set();
|
|
727
|
+
for (const operationId of operationIds) {
|
|
728
|
+
extractIds(operationId, "API").forEach((id) => ids.add(id));
|
|
729
|
+
}
|
|
730
|
+
return Array.from(ids);
|
|
731
|
+
}
|
|
732
|
+
function collectOperationIds(value, out) {
|
|
733
|
+
if (!value || typeof value !== "object") {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (Array.isArray(value)) {
|
|
737
|
+
for (const item of value) {
|
|
738
|
+
collectOperationIds(item, out);
|
|
739
|
+
}
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
743
|
+
if (key === "operationId" && typeof entry === "string") {
|
|
744
|
+
out.add(entry);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
collectOperationIds(entry, out);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/core/validators/contracts.ts
|
|
694
752
|
var SQL_DANGEROUS_PATTERNS = [
|
|
695
753
|
{ pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
|
|
696
754
|
{ pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
|
|
@@ -739,12 +797,13 @@ async function validateUiContracts(uiRoot) {
|
|
|
739
797
|
"SC",
|
|
740
798
|
"UI",
|
|
741
799
|
"API",
|
|
742
|
-
"DATA"
|
|
800
|
+
"DATA",
|
|
801
|
+
"ADR"
|
|
743
802
|
]);
|
|
744
803
|
if (invalidIds.length > 0) {
|
|
745
804
|
issues.push(
|
|
746
805
|
issue(
|
|
747
|
-
"
|
|
806
|
+
"QFAI-ID-002",
|
|
748
807
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
749
808
|
"error",
|
|
750
809
|
file,
|
|
@@ -753,30 +812,32 @@ async function validateUiContracts(uiRoot) {
|
|
|
753
812
|
)
|
|
754
813
|
);
|
|
755
814
|
}
|
|
815
|
+
let doc;
|
|
756
816
|
try {
|
|
757
|
-
|
|
758
|
-
const id = typeof doc.id === "string" ? doc.id : "";
|
|
759
|
-
if (!id.startsWith("UI-")) {
|
|
760
|
-
issues.push(
|
|
761
|
-
issue(
|
|
762
|
-
"QFAI-UI-001",
|
|
763
|
-
"UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
764
|
-
"error",
|
|
765
|
-
file,
|
|
766
|
-
"contracts.ui.id"
|
|
767
|
-
)
|
|
768
|
-
);
|
|
769
|
-
}
|
|
817
|
+
doc = parseStructuredContract(file, text);
|
|
770
818
|
} catch (error2) {
|
|
771
819
|
issues.push(
|
|
772
820
|
issue(
|
|
773
|
-
"QFAI-
|
|
774
|
-
`UI
|
|
821
|
+
"QFAI-CONTRACT-001",
|
|
822
|
+
`UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error2)})`,
|
|
775
823
|
"error",
|
|
776
824
|
file,
|
|
777
825
|
"contracts.ui.parse"
|
|
778
826
|
)
|
|
779
827
|
);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
const uiIds = extractUiContractIds(doc);
|
|
831
|
+
if (uiIds.length === 0) {
|
|
832
|
+
issues.push(
|
|
833
|
+
issue(
|
|
834
|
+
"QFAI-CONTRACT-002",
|
|
835
|
+
`UI \u5951\u7D04\u306B ID(UI-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
836
|
+
"error",
|
|
837
|
+
file,
|
|
838
|
+
"contracts.ui.id"
|
|
839
|
+
)
|
|
840
|
+
);
|
|
780
841
|
}
|
|
781
842
|
}
|
|
782
843
|
return issues;
|
|
@@ -803,12 +864,13 @@ async function validateApiContracts(apiRoot) {
|
|
|
803
864
|
"SC",
|
|
804
865
|
"UI",
|
|
805
866
|
"API",
|
|
806
|
-
"DATA"
|
|
867
|
+
"DATA",
|
|
868
|
+
"ADR"
|
|
807
869
|
]);
|
|
808
870
|
if (invalidIds.length > 0) {
|
|
809
871
|
issues.push(
|
|
810
872
|
issue(
|
|
811
|
-
"
|
|
873
|
+
"QFAI-ID-002",
|
|
812
874
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
813
875
|
"error",
|
|
814
876
|
file,
|
|
@@ -817,29 +879,43 @@ async function validateApiContracts(apiRoot) {
|
|
|
817
879
|
)
|
|
818
880
|
);
|
|
819
881
|
}
|
|
882
|
+
let doc;
|
|
820
883
|
try {
|
|
821
|
-
|
|
822
|
-
if (!doc || !hasOpenApi(doc)) {
|
|
823
|
-
issues.push(
|
|
824
|
-
issue(
|
|
825
|
-
"QFAI-API-001",
|
|
826
|
-
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
827
|
-
"error",
|
|
828
|
-
file,
|
|
829
|
-
"contracts.api.openapi"
|
|
830
|
-
)
|
|
831
|
-
);
|
|
832
|
-
}
|
|
884
|
+
doc = parseStructuredContract(file, text);
|
|
833
885
|
} catch (error2) {
|
|
834
886
|
issues.push(
|
|
835
887
|
issue(
|
|
836
|
-
"QFAI-
|
|
837
|
-
`API \
|
|
888
|
+
"QFAI-CONTRACT-001",
|
|
889
|
+
`API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${file} (${formatError2(error2)})`,
|
|
838
890
|
"error",
|
|
839
891
|
file,
|
|
840
892
|
"contracts.api.parse"
|
|
841
893
|
)
|
|
842
894
|
);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (!hasOpenApi(doc)) {
|
|
898
|
+
issues.push(
|
|
899
|
+
issue(
|
|
900
|
+
"QFAI-API-001",
|
|
901
|
+
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
902
|
+
"error",
|
|
903
|
+
file,
|
|
904
|
+
"contracts.api.openapi"
|
|
905
|
+
)
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
const apiIds = extractApiContractIds(doc);
|
|
909
|
+
if (apiIds.length === 0) {
|
|
910
|
+
issues.push(
|
|
911
|
+
issue(
|
|
912
|
+
"QFAI-CONTRACT-002",
|
|
913
|
+
`API \u5951\u7D04\u306B ID(API-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
914
|
+
"error",
|
|
915
|
+
file,
|
|
916
|
+
"contracts.api.id"
|
|
917
|
+
)
|
|
918
|
+
);
|
|
843
919
|
}
|
|
844
920
|
}
|
|
845
921
|
return issues;
|
|
@@ -866,12 +942,13 @@ async function validateDataContracts(dataRoot) {
|
|
|
866
942
|
"SC",
|
|
867
943
|
"UI",
|
|
868
944
|
"API",
|
|
869
|
-
"DATA"
|
|
945
|
+
"DATA",
|
|
946
|
+
"ADR"
|
|
870
947
|
]);
|
|
871
948
|
if (invalidIds.length > 0) {
|
|
872
949
|
issues.push(
|
|
873
950
|
issue(
|
|
874
|
-
"
|
|
951
|
+
"QFAI-ID-002",
|
|
875
952
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
876
953
|
"error",
|
|
877
954
|
file,
|
|
@@ -901,13 +978,6 @@ function lintSql(text, file) {
|
|
|
901
978
|
}
|
|
902
979
|
return issues;
|
|
903
980
|
}
|
|
904
|
-
function parseStructured(file, text) {
|
|
905
|
-
const ext = import_node_path8.default.extname(file).toLowerCase();
|
|
906
|
-
if (ext === ".json") {
|
|
907
|
-
return JSON.parse(text);
|
|
908
|
-
}
|
|
909
|
-
return (0, import_yaml2.parse)(text);
|
|
910
|
-
}
|
|
911
981
|
function hasOpenApi(doc) {
|
|
912
982
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
913
983
|
}
|
|
@@ -918,25 +988,165 @@ function formatError2(error2) {
|
|
|
918
988
|
return String(error2);
|
|
919
989
|
}
|
|
920
990
|
function issue(code, message, severity, file, rule, refs) {
|
|
921
|
-
const
|
|
991
|
+
const issue6 = {
|
|
922
992
|
code,
|
|
923
993
|
severity,
|
|
924
994
|
message
|
|
925
995
|
};
|
|
926
996
|
if (file) {
|
|
927
|
-
|
|
997
|
+
issue6.file = file;
|
|
928
998
|
}
|
|
929
999
|
if (rule) {
|
|
930
|
-
|
|
1000
|
+
issue6.rule = rule;
|
|
931
1001
|
}
|
|
932
1002
|
if (refs && refs.length > 0) {
|
|
933
|
-
|
|
1003
|
+
issue6.refs = refs;
|
|
934
1004
|
}
|
|
935
|
-
return
|
|
1005
|
+
return issue6;
|
|
936
1006
|
}
|
|
937
1007
|
|
|
938
|
-
// src/core/validators/
|
|
1008
|
+
// src/core/validators/ids.ts
|
|
1009
|
+
var import_promises7 = require("fs/promises");
|
|
1010
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1011
|
+
|
|
1012
|
+
// src/core/contractIndex.ts
|
|
939
1013
|
var import_promises6 = require("fs/promises");
|
|
1014
|
+
async function buildContractIndex(root, config) {
|
|
1015
|
+
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1016
|
+
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1017
|
+
const dataRoot = resolvePath(root, config, "dataContractsDir");
|
|
1018
|
+
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1019
|
+
collectUiContractFiles(uiRoot),
|
|
1020
|
+
collectApiContractFiles(apiRoot),
|
|
1021
|
+
collectDataContractFiles(dataRoot)
|
|
1022
|
+
]);
|
|
1023
|
+
const index = {
|
|
1024
|
+
ids: /* @__PURE__ */ new Set(),
|
|
1025
|
+
idToFiles: /* @__PURE__ */ new Map(),
|
|
1026
|
+
files: { ui: uiFiles, api: apiFiles, data: dataFiles },
|
|
1027
|
+
structuredParseFailedFiles: /* @__PURE__ */ new Set()
|
|
1028
|
+
};
|
|
1029
|
+
await indexUiContracts(uiFiles, index);
|
|
1030
|
+
await indexApiContracts(apiFiles, index);
|
|
1031
|
+
await indexDataContracts(dataFiles, index);
|
|
1032
|
+
return index;
|
|
1033
|
+
}
|
|
1034
|
+
async function indexUiContracts(files, index) {
|
|
1035
|
+
for (const file of files) {
|
|
1036
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
1037
|
+
try {
|
|
1038
|
+
const doc = parseStructuredContract(file, text);
|
|
1039
|
+
extractUiContractIds(doc).forEach((id) => record(index, id, file));
|
|
1040
|
+
} catch {
|
|
1041
|
+
index.structuredParseFailedFiles.add(file);
|
|
1042
|
+
extractIds(text, "UI").forEach((id) => record(index, id, file));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
async function indexApiContracts(files, index) {
|
|
1047
|
+
for (const file of files) {
|
|
1048
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
1049
|
+
try {
|
|
1050
|
+
const doc = parseStructuredContract(file, text);
|
|
1051
|
+
extractApiContractIds(doc).forEach((id) => record(index, id, file));
|
|
1052
|
+
} catch {
|
|
1053
|
+
index.structuredParseFailedFiles.add(file);
|
|
1054
|
+
extractIds(text, "API").forEach((id) => record(index, id, file));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
async function indexDataContracts(files, index) {
|
|
1059
|
+
for (const file of files) {
|
|
1060
|
+
const text = await (0, import_promises6.readFile)(file, "utf-8");
|
|
1061
|
+
extractIds(text, "DATA").forEach((id) => record(index, id, file));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function record(index, id, file) {
|
|
1065
|
+
index.ids.add(id);
|
|
1066
|
+
const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
|
|
1067
|
+
current.add(file);
|
|
1068
|
+
index.idToFiles.set(id, current);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/core/validators/ids.ts
|
|
1072
|
+
async function validateDefinedIds(root, config) {
|
|
1073
|
+
const issues = [];
|
|
1074
|
+
const specRoot = resolvePath(root, config, "specDir");
|
|
1075
|
+
const scenarioRoot = resolvePath(root, config, "scenariosDir");
|
|
1076
|
+
const specFiles = await collectSpecFiles(specRoot);
|
|
1077
|
+
const scenarioFiles = await collectFiles(scenarioRoot, {
|
|
1078
|
+
extensions: [".feature"]
|
|
1079
|
+
});
|
|
1080
|
+
const defined = /* @__PURE__ */ new Map();
|
|
1081
|
+
await collectSpecDefinitionIds(specFiles, defined);
|
|
1082
|
+
await collectScenarioDefinitionIds(scenarioFiles, defined);
|
|
1083
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
1084
|
+
for (const [id, files] of contractIndex.idToFiles.entries()) {
|
|
1085
|
+
for (const file of files) {
|
|
1086
|
+
recordId(defined, id, file);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
for (const [id, files] of defined.entries()) {
|
|
1090
|
+
if (files.size <= 1) {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const sorted = Array.from(files).sort();
|
|
1094
|
+
issues.push(
|
|
1095
|
+
issue2(
|
|
1096
|
+
"QFAI-ID-001",
|
|
1097
|
+
`ID \u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059: ${id} (${formatFileList(sorted, root)})`,
|
|
1098
|
+
"error",
|
|
1099
|
+
sorted[0],
|
|
1100
|
+
"id.duplicate"
|
|
1101
|
+
)
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
return issues;
|
|
1105
|
+
}
|
|
1106
|
+
async function collectSpecDefinitionIds(files, out) {
|
|
1107
|
+
for (const file of files) {
|
|
1108
|
+
const text = await (0, import_promises7.readFile)(file, "utf-8");
|
|
1109
|
+
extractIds(text, "SPEC").forEach((id) => recordId(out, id, file));
|
|
1110
|
+
extractIds(text, "BR").forEach((id) => recordId(out, id, file));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async function collectScenarioDefinitionIds(files, out) {
|
|
1114
|
+
for (const file of files) {
|
|
1115
|
+
const text = await (0, import_promises7.readFile)(file, "utf-8");
|
|
1116
|
+
extractIds(text, "SC").forEach((id) => recordId(out, id, file));
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function recordId(out, id, file) {
|
|
1120
|
+
const current = out.get(id) ?? /* @__PURE__ */ new Set();
|
|
1121
|
+
current.add(file);
|
|
1122
|
+
out.set(id, current);
|
|
1123
|
+
}
|
|
1124
|
+
function formatFileList(files, root) {
|
|
1125
|
+
return files.map((file) => {
|
|
1126
|
+
const relative = import_node_path9.default.relative(root, file);
|
|
1127
|
+
return relative.length > 0 ? relative : file;
|
|
1128
|
+
}).join(", ");
|
|
1129
|
+
}
|
|
1130
|
+
function issue2(code, message, severity, file, rule, refs) {
|
|
1131
|
+
const issue6 = {
|
|
1132
|
+
code,
|
|
1133
|
+
severity,
|
|
1134
|
+
message
|
|
1135
|
+
};
|
|
1136
|
+
if (file) {
|
|
1137
|
+
issue6.file = file;
|
|
1138
|
+
}
|
|
1139
|
+
if (rule) {
|
|
1140
|
+
issue6.rule = rule;
|
|
1141
|
+
}
|
|
1142
|
+
if (refs && refs.length > 0) {
|
|
1143
|
+
issue6.refs = refs;
|
|
1144
|
+
}
|
|
1145
|
+
return issue6;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// src/core/validators/scenario.ts
|
|
1149
|
+
var import_promises8 = require("fs/promises");
|
|
940
1150
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
941
1151
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
942
1152
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -947,7 +1157,7 @@ async function validateScenarios(root, config) {
|
|
|
947
1157
|
});
|
|
948
1158
|
if (files.length === 0) {
|
|
949
1159
|
return [
|
|
950
|
-
|
|
1160
|
+
issue3(
|
|
951
1161
|
"QFAI-SC-000",
|
|
952
1162
|
"Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
953
1163
|
"info",
|
|
@@ -958,7 +1168,7 @@ async function validateScenarios(root, config) {
|
|
|
958
1168
|
}
|
|
959
1169
|
const issues = [];
|
|
960
1170
|
for (const file of files) {
|
|
961
|
-
const text = await (0,
|
|
1171
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
962
1172
|
issues.push(...validateScenarioContent(text, file));
|
|
963
1173
|
}
|
|
964
1174
|
return issues;
|
|
@@ -971,12 +1181,13 @@ function validateScenarioContent(text, file) {
|
|
|
971
1181
|
"SC",
|
|
972
1182
|
"UI",
|
|
973
1183
|
"API",
|
|
974
|
-
"DATA"
|
|
1184
|
+
"DATA",
|
|
1185
|
+
"ADR"
|
|
975
1186
|
]);
|
|
976
1187
|
if (invalidIds.length > 0) {
|
|
977
1188
|
issues.push(
|
|
978
|
-
|
|
979
|
-
"
|
|
1189
|
+
issue3(
|
|
1190
|
+
"QFAI-ID-002",
|
|
980
1191
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
981
1192
|
"error",
|
|
982
1193
|
file,
|
|
@@ -988,7 +1199,7 @@ function validateScenarioContent(text, file) {
|
|
|
988
1199
|
const scIds = extractIds(text, "SC");
|
|
989
1200
|
if (scIds.length === 0) {
|
|
990
1201
|
issues.push(
|
|
991
|
-
|
|
1202
|
+
issue3(
|
|
992
1203
|
"QFAI-SC-001",
|
|
993
1204
|
"SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
994
1205
|
"error",
|
|
@@ -1000,7 +1211,7 @@ function validateScenarioContent(text, file) {
|
|
|
1000
1211
|
const specIds = extractIds(text, "SPEC");
|
|
1001
1212
|
if (specIds.length === 0) {
|
|
1002
1213
|
issues.push(
|
|
1003
|
-
|
|
1214
|
+
issue3(
|
|
1004
1215
|
"QFAI-SC-002",
|
|
1005
1216
|
"SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
1006
1217
|
"error",
|
|
@@ -1012,7 +1223,7 @@ function validateScenarioContent(text, file) {
|
|
|
1012
1223
|
const brIds = extractIds(text, "BR");
|
|
1013
1224
|
if (brIds.length === 0) {
|
|
1014
1225
|
issues.push(
|
|
1015
|
-
|
|
1226
|
+
issue3(
|
|
1016
1227
|
"QFAI-SC-003",
|
|
1017
1228
|
"SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
1018
1229
|
"error",
|
|
@@ -1033,7 +1244,7 @@ function validateScenarioContent(text, file) {
|
|
|
1033
1244
|
}
|
|
1034
1245
|
if (missingSteps.length > 0) {
|
|
1035
1246
|
issues.push(
|
|
1036
|
-
|
|
1247
|
+
issue3(
|
|
1037
1248
|
"QFAI-SC-005",
|
|
1038
1249
|
`Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
|
|
1039
1250
|
"warning",
|
|
@@ -1044,34 +1255,35 @@ function validateScenarioContent(text, file) {
|
|
|
1044
1255
|
}
|
|
1045
1256
|
return issues;
|
|
1046
1257
|
}
|
|
1047
|
-
function
|
|
1048
|
-
const
|
|
1258
|
+
function issue3(code, message, severity, file, rule, refs) {
|
|
1259
|
+
const issue6 = {
|
|
1049
1260
|
code,
|
|
1050
1261
|
severity,
|
|
1051
1262
|
message
|
|
1052
1263
|
};
|
|
1053
1264
|
if (file) {
|
|
1054
|
-
|
|
1265
|
+
issue6.file = file;
|
|
1055
1266
|
}
|
|
1056
1267
|
if (rule) {
|
|
1057
|
-
|
|
1268
|
+
issue6.rule = rule;
|
|
1058
1269
|
}
|
|
1059
1270
|
if (refs && refs.length > 0) {
|
|
1060
|
-
|
|
1271
|
+
issue6.refs = refs;
|
|
1061
1272
|
}
|
|
1062
|
-
return
|
|
1273
|
+
return issue6;
|
|
1063
1274
|
}
|
|
1064
1275
|
|
|
1065
1276
|
// src/core/validators/spec.ts
|
|
1066
|
-
var
|
|
1277
|
+
var import_promises9 = require("fs/promises");
|
|
1067
1278
|
async function validateSpecs(root, config) {
|
|
1068
1279
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
1069
1280
|
const files = await collectSpecFiles(specsRoot);
|
|
1070
1281
|
if (files.length === 0) {
|
|
1282
|
+
const expected = "spec-0001-<slug>.md";
|
|
1071
1283
|
return [
|
|
1072
|
-
|
|
1284
|
+
issue4(
|
|
1073
1285
|
"QFAI-SPEC-000",
|
|
1074
|
-
|
|
1286
|
+
`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}`,
|
|
1075
1287
|
"info",
|
|
1076
1288
|
specsRoot,
|
|
1077
1289
|
"spec.files"
|
|
@@ -1080,7 +1292,7 @@ async function validateSpecs(root, config) {
|
|
|
1080
1292
|
}
|
|
1081
1293
|
const issues = [];
|
|
1082
1294
|
for (const file of files) {
|
|
1083
|
-
const text = await (0,
|
|
1295
|
+
const text = await (0, import_promises9.readFile)(file, "utf-8");
|
|
1084
1296
|
issues.push(
|
|
1085
1297
|
...validateSpecContent(
|
|
1086
1298
|
text,
|
|
@@ -1099,12 +1311,13 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1099
1311
|
"SC",
|
|
1100
1312
|
"UI",
|
|
1101
1313
|
"API",
|
|
1102
|
-
"DATA"
|
|
1314
|
+
"DATA",
|
|
1315
|
+
"ADR"
|
|
1103
1316
|
]);
|
|
1104
1317
|
if (invalidIds.length > 0) {
|
|
1105
1318
|
issues.push(
|
|
1106
|
-
|
|
1107
|
-
"
|
|
1319
|
+
issue4(
|
|
1320
|
+
"QFAI-ID-002",
|
|
1108
1321
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
1109
1322
|
"error",
|
|
1110
1323
|
file,
|
|
@@ -1116,7 +1329,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1116
1329
|
const specIds = extractIds(text, "SPEC");
|
|
1117
1330
|
if (specIds.length === 0) {
|
|
1118
1331
|
issues.push(
|
|
1119
|
-
|
|
1332
|
+
issue4(
|
|
1120
1333
|
"QFAI-SPEC-001",
|
|
1121
1334
|
"SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1122
1335
|
"error",
|
|
@@ -1128,7 +1341,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1128
1341
|
const brIds = extractIds(text, "BR");
|
|
1129
1342
|
if (brIds.length === 0) {
|
|
1130
1343
|
issues.push(
|
|
1131
|
-
|
|
1344
|
+
issue4(
|
|
1132
1345
|
"QFAI-SPEC-002",
|
|
1133
1346
|
"BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1134
1347
|
"error",
|
|
@@ -1140,7 +1353,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1140
1353
|
const scIds = extractIds(text, "SC");
|
|
1141
1354
|
if (scIds.length > 0) {
|
|
1142
1355
|
issues.push(
|
|
1143
|
-
|
|
1356
|
+
issue4(
|
|
1144
1357
|
"QFAI-SPEC-003",
|
|
1145
1358
|
"Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
|
|
1146
1359
|
"warning",
|
|
@@ -1153,7 +1366,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1153
1366
|
for (const section of requiredSections) {
|
|
1154
1367
|
if (!text.includes(section)) {
|
|
1155
1368
|
issues.push(
|
|
1156
|
-
|
|
1369
|
+
issue4(
|
|
1157
1370
|
"QFAI-SPEC-004",
|
|
1158
1371
|
`\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
|
|
1159
1372
|
"error",
|
|
@@ -1165,26 +1378,26 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1165
1378
|
}
|
|
1166
1379
|
return issues;
|
|
1167
1380
|
}
|
|
1168
|
-
function
|
|
1169
|
-
const
|
|
1381
|
+
function issue4(code, message, severity, file, rule, refs) {
|
|
1382
|
+
const issue6 = {
|
|
1170
1383
|
code,
|
|
1171
1384
|
severity,
|
|
1172
1385
|
message
|
|
1173
1386
|
};
|
|
1174
1387
|
if (file) {
|
|
1175
|
-
|
|
1388
|
+
issue6.file = file;
|
|
1176
1389
|
}
|
|
1177
1390
|
if (rule) {
|
|
1178
|
-
|
|
1391
|
+
issue6.rule = rule;
|
|
1179
1392
|
}
|
|
1180
1393
|
if (refs && refs.length > 0) {
|
|
1181
|
-
|
|
1394
|
+
issue6.refs = refs;
|
|
1182
1395
|
}
|
|
1183
|
-
return
|
|
1396
|
+
return issue6;
|
|
1184
1397
|
}
|
|
1185
1398
|
|
|
1186
1399
|
// src/core/validators/traceability.ts
|
|
1187
|
-
var
|
|
1400
|
+
var import_promises10 = require("fs/promises");
|
|
1188
1401
|
async function validateTraceability(root, config) {
|
|
1189
1402
|
const issues = [];
|
|
1190
1403
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
@@ -1200,36 +1413,141 @@ async function validateTraceability(root, config) {
|
|
|
1200
1413
|
extensions: [".feature"]
|
|
1201
1414
|
});
|
|
1202
1415
|
const upstreamIds = /* @__PURE__ */ new Set();
|
|
1416
|
+
const specIds = /* @__PURE__ */ new Set();
|
|
1203
1417
|
const brIdsInSpecs = /* @__PURE__ */ new Set();
|
|
1204
1418
|
const brIdsInScenarios = /* @__PURE__ */ new Set();
|
|
1205
1419
|
const scIdsInScenarios = /* @__PURE__ */ new Set();
|
|
1206
1420
|
const scenarioContractIds = /* @__PURE__ */ new Set();
|
|
1207
1421
|
const scWithContracts = /* @__PURE__ */ new Set();
|
|
1208
|
-
|
|
1209
|
-
|
|
1422
|
+
const specToBrIds = /* @__PURE__ */ new Map();
|
|
1423
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
1424
|
+
const contractIds = contractIndex.ids;
|
|
1425
|
+
for (const file of specFiles) {
|
|
1426
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1427
|
+
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1428
|
+
const specIdsInFile = extractIds(text, "SPEC");
|
|
1429
|
+
specIdsInFile.forEach((id) => specIds.add(id));
|
|
1430
|
+
const brIds = extractIds(text, "BR");
|
|
1431
|
+
brIds.forEach((id) => brIdsInSpecs.add(id));
|
|
1432
|
+
const referencedContractIds = /* @__PURE__ */ new Set([
|
|
1433
|
+
...extractIds(text, "UI"),
|
|
1434
|
+
...extractIds(text, "API"),
|
|
1435
|
+
...extractIds(text, "DATA")
|
|
1436
|
+
]);
|
|
1437
|
+
const unknownContractIds = Array.from(referencedContractIds).filter(
|
|
1438
|
+
(id) => !contractIds.has(id)
|
|
1439
|
+
);
|
|
1440
|
+
if (unknownContractIds.length > 0) {
|
|
1441
|
+
issues.push(
|
|
1442
|
+
issue5(
|
|
1443
|
+
"QFAI-TRACE-009",
|
|
1444
|
+
`Spec \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1445
|
+
", "
|
|
1446
|
+
)}`,
|
|
1447
|
+
"error",
|
|
1448
|
+
file,
|
|
1449
|
+
"traceability.specContractExists",
|
|
1450
|
+
unknownContractIds
|
|
1451
|
+
)
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
for (const specId of specIdsInFile) {
|
|
1455
|
+
const current = specToBrIds.get(specId) ?? /* @__PURE__ */ new Set();
|
|
1456
|
+
brIds.forEach((id) => current.add(id));
|
|
1457
|
+
specToBrIds.set(specId, current);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
for (const file of decisionFiles) {
|
|
1461
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1210
1462
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1211
|
-
extractIds(text, "BR").forEach((id) => brIdsInSpecs.add(id));
|
|
1212
1463
|
}
|
|
1213
1464
|
for (const file of scenarioFiles) {
|
|
1214
|
-
const text = await (0,
|
|
1465
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1215
1466
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
1467
|
+
const specIdsInScenario = extractIds(text, "SPEC");
|
|
1216
1468
|
const brIds = extractIds(text, "BR");
|
|
1217
|
-
brIds.forEach((id) => brIdsInScenarios.add(id));
|
|
1218
1469
|
const scIds = extractIds(text, "SC");
|
|
1219
|
-
|
|
1220
|
-
const contractIds = [
|
|
1470
|
+
const scenarioIds = [
|
|
1221
1471
|
...extractIds(text, "UI"),
|
|
1222
1472
|
...extractIds(text, "API"),
|
|
1223
1473
|
...extractIds(text, "DATA")
|
|
1224
1474
|
];
|
|
1225
|
-
|
|
1226
|
-
|
|
1475
|
+
brIds.forEach((id) => brIdsInScenarios.add(id));
|
|
1476
|
+
scIds.forEach((id) => scIdsInScenarios.add(id));
|
|
1477
|
+
scenarioIds.forEach((id) => scenarioContractIds.add(id));
|
|
1478
|
+
if (scenarioIds.length > 0) {
|
|
1227
1479
|
scIds.forEach((id) => scWithContracts.add(id));
|
|
1228
1480
|
}
|
|
1481
|
+
const unknownSpecIds = specIdsInScenario.filter((id) => !specIds.has(id));
|
|
1482
|
+
if (unknownSpecIds.length > 0) {
|
|
1483
|
+
issues.push(
|
|
1484
|
+
issue5(
|
|
1485
|
+
"QFAI-TRACE-005",
|
|
1486
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 SPEC \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownSpecIds.join(", ")}`,
|
|
1487
|
+
"error",
|
|
1488
|
+
file,
|
|
1489
|
+
"traceability.scenarioSpecExists",
|
|
1490
|
+
unknownSpecIds
|
|
1491
|
+
)
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
const unknownBrIds = brIds.filter((id) => !brIdsInSpecs.has(id));
|
|
1495
|
+
if (unknownBrIds.length > 0) {
|
|
1496
|
+
issues.push(
|
|
1497
|
+
issue5(
|
|
1498
|
+
"QFAI-TRACE-006",
|
|
1499
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044 BR \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownBrIds.join(", ")}`,
|
|
1500
|
+
"error",
|
|
1501
|
+
file,
|
|
1502
|
+
"traceability.scenarioBrExists",
|
|
1503
|
+
unknownBrIds
|
|
1504
|
+
)
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
const unknownContractIds = scenarioIds.filter((id) => !contractIds.has(id));
|
|
1508
|
+
if (unknownContractIds.length > 0) {
|
|
1509
|
+
issues.push(
|
|
1510
|
+
issue5(
|
|
1511
|
+
"QFAI-TRACE-008",
|
|
1512
|
+
`Scenario \u304C\u5B58\u5728\u3057\u306A\u3044\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
1513
|
+
", "
|
|
1514
|
+
)}`,
|
|
1515
|
+
config.validation.traceability.unknownContractIdSeverity,
|
|
1516
|
+
file,
|
|
1517
|
+
"traceability.scenarioContractExists",
|
|
1518
|
+
unknownContractIds
|
|
1519
|
+
)
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
if (specIdsInScenario.length > 0) {
|
|
1523
|
+
const allowedBrIds = /* @__PURE__ */ new Set();
|
|
1524
|
+
for (const specId of specIdsInScenario) {
|
|
1525
|
+
const brIdsForSpec = specToBrIds.get(specId);
|
|
1526
|
+
if (!brIdsForSpec) {
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
brIdsForSpec.forEach((id) => allowedBrIds.add(id));
|
|
1530
|
+
}
|
|
1531
|
+
const invalidBrIds = brIds.filter((id) => !allowedBrIds.has(id));
|
|
1532
|
+
if (invalidBrIds.length > 0) {
|
|
1533
|
+
issues.push(
|
|
1534
|
+
issue5(
|
|
1535
|
+
"QFAI-TRACE-007",
|
|
1536
|
+
`Scenario \u306E BR \u304C\u53C2\u7167 SPEC \u306B\u5C5E\u3057\u3066\u3044\u307E\u305B\u3093: ${invalidBrIds.join(
|
|
1537
|
+
", "
|
|
1538
|
+
)} (SPEC: ${specIdsInScenario.join(", ")})`,
|
|
1539
|
+
"error",
|
|
1540
|
+
file,
|
|
1541
|
+
"traceability.scenarioBrUnderSpec",
|
|
1542
|
+
invalidBrIds
|
|
1543
|
+
)
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1229
1547
|
}
|
|
1230
1548
|
if (upstreamIds.size === 0) {
|
|
1231
1549
|
return [
|
|
1232
|
-
|
|
1550
|
+
issue5(
|
|
1233
1551
|
"QFAI-TRACE-000",
|
|
1234
1552
|
"\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1235
1553
|
"info",
|
|
@@ -1244,7 +1562,7 @@ async function validateTraceability(root, config) {
|
|
|
1244
1562
|
);
|
|
1245
1563
|
if (orphanBrIds.length > 0) {
|
|
1246
1564
|
issues.push(
|
|
1247
|
-
|
|
1565
|
+
issue5(
|
|
1248
1566
|
"QFAI_TRACE_BR_ORPHAN",
|
|
1249
1567
|
`BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
|
|
1250
1568
|
"error",
|
|
@@ -1261,7 +1579,7 @@ async function validateTraceability(root, config) {
|
|
|
1261
1579
|
);
|
|
1262
1580
|
if (scWithoutContracts.length > 0) {
|
|
1263
1581
|
issues.push(
|
|
1264
|
-
|
|
1582
|
+
issue5(
|
|
1265
1583
|
"QFAI_TRACE_SC_NO_CONTRACT",
|
|
1266
1584
|
`SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
|
|
1267
1585
|
", "
|
|
@@ -1275,14 +1593,13 @@ async function validateTraceability(root, config) {
|
|
|
1275
1593
|
}
|
|
1276
1594
|
}
|
|
1277
1595
|
if (!config.validation.traceability.allowOrphanContracts) {
|
|
1278
|
-
const contractIds = await collectContractIds(root, config);
|
|
1279
1596
|
if (contractIds.size > 0) {
|
|
1280
1597
|
const orphanContracts = Array.from(contractIds).filter(
|
|
1281
1598
|
(id) => !scenarioContractIds.has(id)
|
|
1282
1599
|
);
|
|
1283
1600
|
if (orphanContracts.length > 0) {
|
|
1284
1601
|
issues.push(
|
|
1285
|
-
|
|
1602
|
+
issue5(
|
|
1286
1603
|
"QFAI_CONTRACT_ORPHAN",
|
|
1287
1604
|
`\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
|
|
1288
1605
|
"error",
|
|
@@ -1299,27 +1616,6 @@ async function validateTraceability(root, config) {
|
|
|
1299
1616
|
);
|
|
1300
1617
|
return issues;
|
|
1301
1618
|
}
|
|
1302
|
-
async function collectContractIds(root, config) {
|
|
1303
|
-
const contractIds = /* @__PURE__ */ new Set();
|
|
1304
|
-
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1305
|
-
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1306
|
-
const dataRoot = resolvePath(root, config, "dataContractsDir");
|
|
1307
|
-
const uiFiles = await collectUiContractFiles(uiRoot);
|
|
1308
|
-
const apiFiles = await collectApiContractFiles(apiRoot);
|
|
1309
|
-
const dataFiles = await collectDataContractFiles(dataRoot);
|
|
1310
|
-
await collectIdsFromFiles(uiFiles, ["UI"], contractIds);
|
|
1311
|
-
await collectIdsFromFiles(apiFiles, ["API"], contractIds);
|
|
1312
|
-
await collectIdsFromFiles(dataFiles, ["DATA"], contractIds);
|
|
1313
|
-
return contractIds;
|
|
1314
|
-
}
|
|
1315
|
-
async function collectIdsFromFiles(files, prefixes, out) {
|
|
1316
|
-
for (const file of files) {
|
|
1317
|
-
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
1318
|
-
for (const prefix of prefixes) {
|
|
1319
|
-
extractIds(text, prefix).forEach((id) => out.add(id));
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
1619
|
async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
1324
1620
|
const issues = [];
|
|
1325
1621
|
const codeFiles = await collectFiles(srcRoot, {
|
|
@@ -1331,7 +1627,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1331
1627
|
const targetFiles = [...codeFiles, ...testFiles];
|
|
1332
1628
|
if (targetFiles.length === 0) {
|
|
1333
1629
|
issues.push(
|
|
1334
|
-
|
|
1630
|
+
issue5(
|
|
1335
1631
|
"QFAI-TRACE-001",
|
|
1336
1632
|
"\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1337
1633
|
"info",
|
|
@@ -1344,7 +1640,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1344
1640
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
1345
1641
|
let found = false;
|
|
1346
1642
|
for (const file of targetFiles) {
|
|
1347
|
-
const text = await (0,
|
|
1643
|
+
const text = await (0, import_promises10.readFile)(file, "utf-8");
|
|
1348
1644
|
if (pattern.test(text)) {
|
|
1349
1645
|
found = true;
|
|
1350
1646
|
break;
|
|
@@ -1352,7 +1648,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
1352
1648
|
}
|
|
1353
1649
|
if (!found) {
|
|
1354
1650
|
issues.push(
|
|
1355
|
-
|
|
1651
|
+
issue5(
|
|
1356
1652
|
"QFAI-TRACE-002",
|
|
1357
1653
|
"\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
|
|
1358
1654
|
"warning",
|
|
@@ -1367,22 +1663,22 @@ function buildIdPattern(ids) {
|
|
|
1367
1663
|
const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
1368
1664
|
return new RegExp(`\\b(${escaped.join("|")})\\b`);
|
|
1369
1665
|
}
|
|
1370
|
-
function
|
|
1371
|
-
const
|
|
1666
|
+
function issue5(code, message, severity, file, rule, refs) {
|
|
1667
|
+
const issue6 = {
|
|
1372
1668
|
code,
|
|
1373
1669
|
severity,
|
|
1374
1670
|
message
|
|
1375
1671
|
};
|
|
1376
1672
|
if (file) {
|
|
1377
|
-
|
|
1673
|
+
issue6.file = file;
|
|
1378
1674
|
}
|
|
1379
1675
|
if (rule) {
|
|
1380
|
-
|
|
1676
|
+
issue6.rule = rule;
|
|
1381
1677
|
}
|
|
1382
1678
|
if (refs && refs.length > 0) {
|
|
1383
|
-
|
|
1679
|
+
issue6.refs = refs;
|
|
1384
1680
|
}
|
|
1385
|
-
return
|
|
1681
|
+
return issue6;
|
|
1386
1682
|
}
|
|
1387
1683
|
|
|
1388
1684
|
// src/core/validate.ts
|
|
@@ -1394,6 +1690,7 @@ async function validateProject(root, configResult) {
|
|
|
1394
1690
|
...await validateSpecs(root, config),
|
|
1395
1691
|
...await validateScenarios(root, config),
|
|
1396
1692
|
...await validateContracts(root, config),
|
|
1693
|
+
...await validateDefinedIds(root, config),
|
|
1397
1694
|
...await validateTraceability(root, config)
|
|
1398
1695
|
];
|
|
1399
1696
|
const toolVersion = await resolveToolVersion();
|
|
@@ -1406,8 +1703,8 @@ async function validateProject(root, configResult) {
|
|
|
1406
1703
|
}
|
|
1407
1704
|
function countIssues(issues) {
|
|
1408
1705
|
return issues.reduce(
|
|
1409
|
-
(acc,
|
|
1410
|
-
acc[
|
|
1706
|
+
(acc, issue6) => {
|
|
1707
|
+
acc[issue6.severity] += 1;
|
|
1411
1708
|
return acc;
|
|
1412
1709
|
},
|
|
1413
1710
|
{ info: 0, warning: 0, error: 0 }
|
|
@@ -1415,7 +1712,7 @@ function countIssues(issues) {
|
|
|
1415
1712
|
}
|
|
1416
1713
|
|
|
1417
1714
|
// src/core/report.ts
|
|
1418
|
-
var
|
|
1715
|
+
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
|
|
1419
1716
|
async function createReportData(root, validation, configResult) {
|
|
1420
1717
|
const resolved = configResult ?? await loadConfig(root);
|
|
1421
1718
|
const config = resolved.config;
|
|
@@ -1423,7 +1720,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1423
1720
|
const specRoot = resolvePath(root, config, "specDir");
|
|
1424
1721
|
const decisionsRoot = resolvePath(root, config, "decisionsDir");
|
|
1425
1722
|
const scenariosRoot = resolvePath(root, config, "scenariosDir");
|
|
1426
|
-
const rulesRoot = resolvePath(root, config, "rulesDir");
|
|
1427
1723
|
const apiRoot = resolvePath(root, config, "apiContractsDir");
|
|
1428
1724
|
const uiRoot = resolvePath(root, config, "uiContractsDir");
|
|
1429
1725
|
const dbRoot = resolvePath(root, config, "dataContractsDir");
|
|
@@ -1436,7 +1732,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1436
1732
|
const decisionFiles = await collectFiles(decisionsRoot, {
|
|
1437
1733
|
extensions: [".md"]
|
|
1438
1734
|
});
|
|
1439
|
-
const ruleFiles = await collectFiles(rulesRoot, { extensions: [".md"] });
|
|
1440
1735
|
const {
|
|
1441
1736
|
api: apiFiles,
|
|
1442
1737
|
ui: uiFiles,
|
|
@@ -1446,7 +1741,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1446
1741
|
...specFiles,
|
|
1447
1742
|
...scenarioFiles,
|
|
1448
1743
|
...decisionFiles,
|
|
1449
|
-
...ruleFiles,
|
|
1450
1744
|
...apiFiles,
|
|
1451
1745
|
...uiFiles,
|
|
1452
1746
|
...dbFiles
|
|
@@ -1472,7 +1766,6 @@ async function createReportData(root, validation, configResult) {
|
|
|
1472
1766
|
specs: specFiles.length,
|
|
1473
1767
|
scenarios: scenarioFiles.length,
|
|
1474
1768
|
decisions: decisionFiles.length,
|
|
1475
|
-
rules: ruleFiles.length,
|
|
1476
1769
|
contracts: {
|
|
1477
1770
|
api: apiFiles.length,
|
|
1478
1771
|
ui: uiFiles.length,
|
|
@@ -1507,7 +1800,6 @@ function formatReportMarkdown(data) {
|
|
|
1507
1800
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
1508
1801
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
1509
1802
|
lines.push(`- decisions: ${data.summary.decisions}`);
|
|
1510
|
-
lines.push(`- rules: ${data.summary.rules}`);
|
|
1511
1803
|
lines.push(
|
|
1512
1804
|
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
1513
1805
|
);
|
|
@@ -1543,7 +1835,7 @@ function formatReportMarkdown(data) {
|
|
|
1543
1835
|
lines.push("");
|
|
1544
1836
|
lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
|
|
1545
1837
|
const traceIssues = data.issues.filter(
|
|
1546
|
-
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code === "QFAI_CONTRACT_ORPHAN"
|
|
1838
|
+
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-") || item.code === "QFAI_CONTRACT_ORPHAN"
|
|
1547
1839
|
);
|
|
1548
1840
|
if (traceIssues.length === 0) {
|
|
1549
1841
|
lines.push("- (none)");
|
|
@@ -1583,8 +1875,8 @@ async function collectIds(files) {
|
|
|
1583
1875
|
DATA: /* @__PURE__ */ new Set()
|
|
1584
1876
|
};
|
|
1585
1877
|
for (const file of files) {
|
|
1586
|
-
const text = await (0,
|
|
1587
|
-
for (const prefix of
|
|
1878
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1879
|
+
for (const prefix of ID_PREFIXES2) {
|
|
1588
1880
|
const ids = extractIds(text, prefix);
|
|
1589
1881
|
ids.forEach((id) => result[prefix].add(id));
|
|
1590
1882
|
}
|
|
@@ -1601,7 +1893,7 @@ async function collectIds(files) {
|
|
|
1601
1893
|
async function collectUpstreamIds(files) {
|
|
1602
1894
|
const ids = /* @__PURE__ */ new Set();
|
|
1603
1895
|
for (const file of files) {
|
|
1604
|
-
const text = await (0,
|
|
1896
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1605
1897
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
1606
1898
|
}
|
|
1607
1899
|
return ids;
|
|
@@ -1622,7 +1914,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
1622
1914
|
}
|
|
1623
1915
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
1624
1916
|
for (const file of targetFiles) {
|
|
1625
|
-
const text = await (0,
|
|
1917
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1626
1918
|
if (pattern.test(text)) {
|
|
1627
1919
|
return true;
|
|
1628
1920
|
}
|
|
@@ -1644,20 +1936,20 @@ function toSortedArray(values) {
|
|
|
1644
1936
|
}
|
|
1645
1937
|
function buildHotspots(issues) {
|
|
1646
1938
|
const map = /* @__PURE__ */ new Map();
|
|
1647
|
-
for (const
|
|
1648
|
-
if (!
|
|
1939
|
+
for (const issue6 of issues) {
|
|
1940
|
+
if (!issue6.file) {
|
|
1649
1941
|
continue;
|
|
1650
1942
|
}
|
|
1651
|
-
const current = map.get(
|
|
1652
|
-
file:
|
|
1943
|
+
const current = map.get(issue6.file) ?? {
|
|
1944
|
+
file: issue6.file,
|
|
1653
1945
|
total: 0,
|
|
1654
1946
|
error: 0,
|
|
1655
1947
|
warning: 0,
|
|
1656
1948
|
info: 0
|
|
1657
1949
|
};
|
|
1658
1950
|
current.total += 1;
|
|
1659
|
-
current[
|
|
1660
|
-
map.set(
|
|
1951
|
+
current[issue6.severity] += 1;
|
|
1952
|
+
map.set(issue6.file, current);
|
|
1661
1953
|
}
|
|
1662
1954
|
return Array.from(map.values()).sort(
|
|
1663
1955
|
(a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
|
|
@@ -1666,10 +1958,10 @@ function buildHotspots(issues) {
|
|
|
1666
1958
|
|
|
1667
1959
|
// src/cli/commands/report.ts
|
|
1668
1960
|
async function runReport(options) {
|
|
1669
|
-
const root =
|
|
1961
|
+
const root = import_node_path10.default.resolve(options.root);
|
|
1670
1962
|
const configResult = await loadConfig(root);
|
|
1671
1963
|
const input = options.jsonPath ?? configResult.config.output.jsonPath;
|
|
1672
|
-
const inputPath =
|
|
1964
|
+
const inputPath = import_node_path10.default.isAbsolute(input) ? input : import_node_path10.default.resolve(root, input);
|
|
1673
1965
|
let validation;
|
|
1674
1966
|
try {
|
|
1675
1967
|
validation = await readValidationResult(inputPath);
|
|
@@ -1694,9 +1986,9 @@ async function runReport(options) {
|
|
|
1694
1986
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
1695
1987
|
const defaultOut = options.format === "json" ? ".qfai/out/report.json" : ".qfai/out/report.md";
|
|
1696
1988
|
const out = options.outPath ?? defaultOut;
|
|
1697
|
-
const outPath =
|
|
1698
|
-
await (0,
|
|
1699
|
-
await (0,
|
|
1989
|
+
const outPath = import_node_path10.default.isAbsolute(out) ? out : import_node_path10.default.resolve(root, out);
|
|
1990
|
+
await (0, import_promises12.mkdir)(import_node_path10.default.dirname(outPath), { recursive: true });
|
|
1991
|
+
await (0, import_promises12.writeFile)(outPath, `${output}
|
|
1700
1992
|
`, "utf-8");
|
|
1701
1993
|
info(
|
|
1702
1994
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -1704,7 +1996,7 @@ async function runReport(options) {
|
|
|
1704
1996
|
info(`wrote report: ${outPath}`);
|
|
1705
1997
|
}
|
|
1706
1998
|
async function readValidationResult(inputPath) {
|
|
1707
|
-
const raw = await (0,
|
|
1999
|
+
const raw = await (0, import_promises12.readFile)(inputPath, "utf-8");
|
|
1708
2000
|
const parsed = JSON.parse(raw);
|
|
1709
2001
|
if (!isValidationResult(parsed)) {
|
|
1710
2002
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -1720,17 +2012,17 @@ function isValidationResult(value) {
|
|
|
1720
2012
|
if (!value || typeof value !== "object") {
|
|
1721
2013
|
return false;
|
|
1722
2014
|
}
|
|
1723
|
-
const
|
|
1724
|
-
if (typeof
|
|
2015
|
+
const record2 = value;
|
|
2016
|
+
if (typeof record2.schemaVersion !== "string") {
|
|
1725
2017
|
return false;
|
|
1726
2018
|
}
|
|
1727
|
-
if (typeof
|
|
2019
|
+
if (typeof record2.toolVersion !== "string") {
|
|
1728
2020
|
return false;
|
|
1729
2021
|
}
|
|
1730
|
-
if (!Array.isArray(
|
|
2022
|
+
if (!Array.isArray(record2.issues)) {
|
|
1731
2023
|
return false;
|
|
1732
2024
|
}
|
|
1733
|
-
const counts =
|
|
2025
|
+
const counts = record2.counts;
|
|
1734
2026
|
if (!counts) {
|
|
1735
2027
|
return false;
|
|
1736
2028
|
}
|
|
@@ -1740,13 +2032,13 @@ function isMissingFileError(error2) {
|
|
|
1740
2032
|
if (!error2 || typeof error2 !== "object") {
|
|
1741
2033
|
return false;
|
|
1742
2034
|
}
|
|
1743
|
-
const
|
|
1744
|
-
return
|
|
2035
|
+
const record2 = error2;
|
|
2036
|
+
return record2.code === "ENOENT";
|
|
1745
2037
|
}
|
|
1746
2038
|
|
|
1747
2039
|
// src/cli/commands/validate.ts
|
|
1748
|
-
var
|
|
1749
|
-
var
|
|
2040
|
+
var import_promises13 = require("fs/promises");
|
|
2041
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
1750
2042
|
|
|
1751
2043
|
// src/cli/lib/failOn.ts
|
|
1752
2044
|
function shouldFail(result, failOn) {
|
|
@@ -1761,7 +2053,7 @@ function shouldFail(result, failOn) {
|
|
|
1761
2053
|
|
|
1762
2054
|
// src/cli/commands/validate.ts
|
|
1763
2055
|
async function runValidate(options) {
|
|
1764
|
-
const root =
|
|
2056
|
+
const root = import_node_path11.default.resolve(options.root);
|
|
1765
2057
|
const configResult = await loadConfig(root);
|
|
1766
2058
|
const result = await validateProject(root, configResult);
|
|
1767
2059
|
const format = options.format ?? configResult.config.output.format;
|
|
@@ -1805,21 +2097,21 @@ function emitText(result) {
|
|
|
1805
2097
|
`
|
|
1806
2098
|
);
|
|
1807
2099
|
}
|
|
1808
|
-
function emitGitHub(
|
|
1809
|
-
const level =
|
|
1810
|
-
const file =
|
|
1811
|
-
const line =
|
|
1812
|
-
const column =
|
|
2100
|
+
function emitGitHub(issue6) {
|
|
2101
|
+
const level = issue6.severity === "error" ? "error" : issue6.severity === "warning" ? "warning" : "notice";
|
|
2102
|
+
const file = issue6.file ? `file=${issue6.file}` : "";
|
|
2103
|
+
const line = issue6.loc?.line ? `,line=${issue6.loc.line}` : "";
|
|
2104
|
+
const column = issue6.loc?.column ? `,col=${issue6.loc.column}` : "";
|
|
1813
2105
|
const location = file ? ` ${file}${line}${column}` : "";
|
|
1814
2106
|
process.stdout.write(
|
|
1815
|
-
`::${level}${location}::${
|
|
2107
|
+
`::${level}${location}::${issue6.code}: ${issue6.message}
|
|
1816
2108
|
`
|
|
1817
2109
|
);
|
|
1818
2110
|
}
|
|
1819
2111
|
async function emitJson(result, root, jsonPath) {
|
|
1820
|
-
const abs =
|
|
1821
|
-
await (0,
|
|
1822
|
-
await (0,
|
|
2112
|
+
const abs = import_node_path11.default.isAbsolute(jsonPath) ? jsonPath : import_node_path11.default.resolve(root, jsonPath);
|
|
2113
|
+
await (0, import_promises13.mkdir)(import_node_path11.default.dirname(abs), { recursive: true });
|
|
2114
|
+
await (0, import_promises13.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
1823
2115
|
`, "utf-8");
|
|
1824
2116
|
}
|
|
1825
2117
|
|