qfai 0.2.6 → 0.2.9
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 +1 -1
- package/assets/init/.qfai/contracts/README.md +6 -4
- package/assets/init/.qfai/contracts/api/api-0001-sample.yaml +1 -4
- package/assets/init/.qfai/prompts/README.md +1 -1
- package/assets/init/.qfai/spec/README.md +6 -3
- package/assets/init/.qfai/spec/decisions/ADR-0001.md +1 -1
- package/assets/init/.qfai/spec/decisions/README.md +2 -2
- package/assets/init/root/qfai.config.yaml +1 -1
- package/dist/cli/index.cjs +485 -193
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +482 -190
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +460 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.mjs +459 -166
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -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");
|
|
@@ -172,7 +172,6 @@ var defaultConfig = {
|
|
|
172
172
|
specDir: ".qfai/spec",
|
|
173
173
|
decisionsDir: ".qfai/spec/decisions",
|
|
174
174
|
scenariosDir: ".qfai/spec/scenarios",
|
|
175
|
-
rulesDir: ".qfai/rules",
|
|
176
175
|
contractsDir: ".qfai/contracts",
|
|
177
176
|
uiContractsDir: ".qfai/contracts/ui",
|
|
178
177
|
apiContractsDir: ".qfai/contracts/api",
|
|
@@ -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);
|
|
@@ -610,13 +624,15 @@ function isSpecFile(filePath) {
|
|
|
610
624
|
}
|
|
611
625
|
|
|
612
626
|
// src/core/ids.ts
|
|
613
|
-
var
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
|
620
636
|
};
|
|
621
637
|
var LOOSE_ID_PATTERNS = {
|
|
622
638
|
SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
|
|
@@ -624,16 +640,17 @@ var LOOSE_ID_PATTERNS = {
|
|
|
624
640
|
SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
|
|
625
641
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
626
642
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
627
|
-
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
|
|
628
645
|
};
|
|
629
646
|
function extractIds(text, prefix) {
|
|
630
|
-
const pattern =
|
|
647
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
631
648
|
const matches = text.match(pattern);
|
|
632
649
|
return unique(matches ?? []);
|
|
633
650
|
}
|
|
634
651
|
function extractAllIds(text) {
|
|
635
652
|
const all = [];
|
|
636
|
-
|
|
653
|
+
ID_PREFIXES.forEach((prefix) => {
|
|
637
654
|
all.push(...extractIds(text, prefix));
|
|
638
655
|
});
|
|
639
656
|
return unique(all);
|
|
@@ -654,7 +671,7 @@ function unique(values) {
|
|
|
654
671
|
return Array.from(new Set(values));
|
|
655
672
|
}
|
|
656
673
|
function isValidId(value, prefix) {
|
|
657
|
-
const pattern =
|
|
674
|
+
const pattern = STRICT_ID_PATTERNS[prefix];
|
|
658
675
|
const strict = new RegExp(pattern.source);
|
|
659
676
|
return strict.test(value);
|
|
660
677
|
}
|
|
@@ -667,8 +684,8 @@ var import_promises4 = require("fs/promises");
|
|
|
667
684
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
668
685
|
var import_node_url2 = require("url");
|
|
669
686
|
async function resolveToolVersion() {
|
|
670
|
-
if ("0.2.
|
|
671
|
-
return "0.2.
|
|
687
|
+
if ("0.2.9".length > 0) {
|
|
688
|
+
return "0.2.9";
|
|
672
689
|
}
|
|
673
690
|
try {
|
|
674
691
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -688,8 +705,50 @@ function resolvePackageJsonPath() {
|
|
|
688
705
|
|
|
689
706
|
// src/core/validators/contracts.ts
|
|
690
707
|
var import_promises5 = require("fs/promises");
|
|
708
|
+
|
|
709
|
+
// src/core/contracts.ts
|
|
691
710
|
var import_node_path8 = __toESM(require("path"), 1);
|
|
692
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
|
|
693
752
|
var SQL_DANGEROUS_PATTERNS = [
|
|
694
753
|
{ pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
|
|
695
754
|
{ pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
|
|
@@ -738,12 +797,13 @@ async function validateUiContracts(uiRoot) {
|
|
|
738
797
|
"SC",
|
|
739
798
|
"UI",
|
|
740
799
|
"API",
|
|
741
|
-
"DATA"
|
|
800
|
+
"DATA",
|
|
801
|
+
"ADR"
|
|
742
802
|
]);
|
|
743
803
|
if (invalidIds.length > 0) {
|
|
744
804
|
issues.push(
|
|
745
805
|
issue(
|
|
746
|
-
"
|
|
806
|
+
"QFAI-ID-002",
|
|
747
807
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
748
808
|
"error",
|
|
749
809
|
file,
|
|
@@ -752,30 +812,32 @@ async function validateUiContracts(uiRoot) {
|
|
|
752
812
|
)
|
|
753
813
|
);
|
|
754
814
|
}
|
|
815
|
+
let doc;
|
|
755
816
|
try {
|
|
756
|
-
|
|
757
|
-
const id = typeof doc.id === "string" ? doc.id : "";
|
|
758
|
-
if (!id.startsWith("UI-")) {
|
|
759
|
-
issues.push(
|
|
760
|
-
issue(
|
|
761
|
-
"QFAI-UI-001",
|
|
762
|
-
"UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
763
|
-
"error",
|
|
764
|
-
file,
|
|
765
|
-
"contracts.ui.id"
|
|
766
|
-
)
|
|
767
|
-
);
|
|
768
|
-
}
|
|
817
|
+
doc = parseStructuredContract(file, text);
|
|
769
818
|
} catch (error2) {
|
|
770
819
|
issues.push(
|
|
771
820
|
issue(
|
|
772
|
-
"QFAI-
|
|
773
|
-
`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)})`,
|
|
774
823
|
"error",
|
|
775
824
|
file,
|
|
776
825
|
"contracts.ui.parse"
|
|
777
826
|
)
|
|
778
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
|
+
);
|
|
779
841
|
}
|
|
780
842
|
}
|
|
781
843
|
return issues;
|
|
@@ -802,12 +864,13 @@ async function validateApiContracts(apiRoot) {
|
|
|
802
864
|
"SC",
|
|
803
865
|
"UI",
|
|
804
866
|
"API",
|
|
805
|
-
"DATA"
|
|
867
|
+
"DATA",
|
|
868
|
+
"ADR"
|
|
806
869
|
]);
|
|
807
870
|
if (invalidIds.length > 0) {
|
|
808
871
|
issues.push(
|
|
809
872
|
issue(
|
|
810
|
-
"
|
|
873
|
+
"QFAI-ID-002",
|
|
811
874
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
812
875
|
"error",
|
|
813
876
|
file,
|
|
@@ -816,29 +879,43 @@ async function validateApiContracts(apiRoot) {
|
|
|
816
879
|
)
|
|
817
880
|
);
|
|
818
881
|
}
|
|
882
|
+
let doc;
|
|
819
883
|
try {
|
|
820
|
-
|
|
821
|
-
if (!doc || !hasOpenApi(doc)) {
|
|
822
|
-
issues.push(
|
|
823
|
-
issue(
|
|
824
|
-
"QFAI-API-001",
|
|
825
|
-
"OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
826
|
-
"error",
|
|
827
|
-
file,
|
|
828
|
-
"contracts.api.openapi"
|
|
829
|
-
)
|
|
830
|
-
);
|
|
831
|
-
}
|
|
884
|
+
doc = parseStructuredContract(file, text);
|
|
832
885
|
} catch (error2) {
|
|
833
886
|
issues.push(
|
|
834
887
|
issue(
|
|
835
|
-
"QFAI-
|
|
836
|
-
`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)})`,
|
|
837
890
|
"error",
|
|
838
891
|
file,
|
|
839
892
|
"contracts.api.parse"
|
|
840
893
|
)
|
|
841
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
|
+
);
|
|
842
919
|
}
|
|
843
920
|
}
|
|
844
921
|
return issues;
|
|
@@ -865,12 +942,13 @@ async function validateDataContracts(dataRoot) {
|
|
|
865
942
|
"SC",
|
|
866
943
|
"UI",
|
|
867
944
|
"API",
|
|
868
|
-
"DATA"
|
|
945
|
+
"DATA",
|
|
946
|
+
"ADR"
|
|
869
947
|
]);
|
|
870
948
|
if (invalidIds.length > 0) {
|
|
871
949
|
issues.push(
|
|
872
950
|
issue(
|
|
873
|
-
"
|
|
951
|
+
"QFAI-ID-002",
|
|
874
952
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
875
953
|
"error",
|
|
876
954
|
file,
|
|
@@ -900,13 +978,6 @@ function lintSql(text, file) {
|
|
|
900
978
|
}
|
|
901
979
|
return issues;
|
|
902
980
|
}
|
|
903
|
-
function parseStructured(file, text) {
|
|
904
|
-
const ext = import_node_path8.default.extname(file).toLowerCase();
|
|
905
|
-
if (ext === ".json") {
|
|
906
|
-
return JSON.parse(text);
|
|
907
|
-
}
|
|
908
|
-
return (0, import_yaml2.parse)(text);
|
|
909
|
-
}
|
|
910
981
|
function hasOpenApi(doc) {
|
|
911
982
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
912
983
|
}
|
|
@@ -917,25 +988,165 @@ function formatError2(error2) {
|
|
|
917
988
|
return String(error2);
|
|
918
989
|
}
|
|
919
990
|
function issue(code, message, severity, file, rule, refs) {
|
|
920
|
-
const
|
|
991
|
+
const issue6 = {
|
|
921
992
|
code,
|
|
922
993
|
severity,
|
|
923
994
|
message
|
|
924
995
|
};
|
|
925
996
|
if (file) {
|
|
926
|
-
|
|
997
|
+
issue6.file = file;
|
|
927
998
|
}
|
|
928
999
|
if (rule) {
|
|
929
|
-
|
|
1000
|
+
issue6.rule = rule;
|
|
930
1001
|
}
|
|
931
1002
|
if (refs && refs.length > 0) {
|
|
932
|
-
|
|
1003
|
+
issue6.refs = refs;
|
|
933
1004
|
}
|
|
934
|
-
return
|
|
1005
|
+
return issue6;
|
|
935
1006
|
}
|
|
936
1007
|
|
|
937
|
-
// 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
|
|
938
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");
|
|
939
1150
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
940
1151
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
941
1152
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -946,7 +1157,7 @@ async function validateScenarios(root, config) {
|
|
|
946
1157
|
});
|
|
947
1158
|
if (files.length === 0) {
|
|
948
1159
|
return [
|
|
949
|
-
|
|
1160
|
+
issue3(
|
|
950
1161
|
"QFAI-SC-000",
|
|
951
1162
|
"Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
952
1163
|
"info",
|
|
@@ -957,7 +1168,7 @@ async function validateScenarios(root, config) {
|
|
|
957
1168
|
}
|
|
958
1169
|
const issues = [];
|
|
959
1170
|
for (const file of files) {
|
|
960
|
-
const text = await (0,
|
|
1171
|
+
const text = await (0, import_promises8.readFile)(file, "utf-8");
|
|
961
1172
|
issues.push(...validateScenarioContent(text, file));
|
|
962
1173
|
}
|
|
963
1174
|
return issues;
|
|
@@ -970,12 +1181,13 @@ function validateScenarioContent(text, file) {
|
|
|
970
1181
|
"SC",
|
|
971
1182
|
"UI",
|
|
972
1183
|
"API",
|
|
973
|
-
"DATA"
|
|
1184
|
+
"DATA",
|
|
1185
|
+
"ADR"
|
|
974
1186
|
]);
|
|
975
1187
|
if (invalidIds.length > 0) {
|
|
976
1188
|
issues.push(
|
|
977
|
-
|
|
978
|
-
"
|
|
1189
|
+
issue3(
|
|
1190
|
+
"QFAI-ID-002",
|
|
979
1191
|
`ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
|
|
980
1192
|
"error",
|
|
981
1193
|
file,
|
|
@@ -987,7 +1199,7 @@ function validateScenarioContent(text, file) {
|
|
|
987
1199
|
const scIds = extractIds(text, "SC");
|
|
988
1200
|
if (scIds.length === 0) {
|
|
989
1201
|
issues.push(
|
|
990
|
-
|
|
1202
|
+
issue3(
|
|
991
1203
|
"QFAI-SC-001",
|
|
992
1204
|
"SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
993
1205
|
"error",
|
|
@@ -999,7 +1211,7 @@ function validateScenarioContent(text, file) {
|
|
|
999
1211
|
const specIds = extractIds(text, "SPEC");
|
|
1000
1212
|
if (specIds.length === 0) {
|
|
1001
1213
|
issues.push(
|
|
1002
|
-
|
|
1214
|
+
issue3(
|
|
1003
1215
|
"QFAI-SC-002",
|
|
1004
1216
|
"SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
1005
1217
|
"error",
|
|
@@ -1011,7 +1223,7 @@ function validateScenarioContent(text, file) {
|
|
|
1011
1223
|
const brIds = extractIds(text, "BR");
|
|
1012
1224
|
if (brIds.length === 0) {
|
|
1013
1225
|
issues.push(
|
|
1014
|
-
|
|
1226
|
+
issue3(
|
|
1015
1227
|
"QFAI-SC-003",
|
|
1016
1228
|
"SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
|
|
1017
1229
|
"error",
|
|
@@ -1032,7 +1244,7 @@ function validateScenarioContent(text, file) {
|
|
|
1032
1244
|
}
|
|
1033
1245
|
if (missingSteps.length > 0) {
|
|
1034
1246
|
issues.push(
|
|
1035
|
-
|
|
1247
|
+
issue3(
|
|
1036
1248
|
"QFAI-SC-005",
|
|
1037
1249
|
`Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
|
|
1038
1250
|
"warning",
|
|
@@ -1043,33 +1255,33 @@ function validateScenarioContent(text, file) {
|
|
|
1043
1255
|
}
|
|
1044
1256
|
return issues;
|
|
1045
1257
|
}
|
|
1046
|
-
function
|
|
1047
|
-
const
|
|
1258
|
+
function issue3(code, message, severity, file, rule, refs) {
|
|
1259
|
+
const issue6 = {
|
|
1048
1260
|
code,
|
|
1049
1261
|
severity,
|
|
1050
1262
|
message
|
|
1051
1263
|
};
|
|
1052
1264
|
if (file) {
|
|
1053
|
-
|
|
1265
|
+
issue6.file = file;
|
|
1054
1266
|
}
|
|
1055
1267
|
if (rule) {
|
|
1056
|
-
|
|
1268
|
+
issue6.rule = rule;
|
|
1057
1269
|
}
|
|
1058
1270
|
if (refs && refs.length > 0) {
|
|
1059
|
-
|
|
1271
|
+
issue6.refs = refs;
|
|
1060
1272
|
}
|
|
1061
|
-
return
|
|
1273
|
+
return issue6;
|
|
1062
1274
|
}
|
|
1063
1275
|
|
|
1064
1276
|
// src/core/validators/spec.ts
|
|
1065
|
-
var
|
|
1277
|
+
var import_promises9 = require("fs/promises");
|
|
1066
1278
|
async function validateSpecs(root, config) {
|
|
1067
1279
|
const specsRoot = resolvePath(root, config, "specDir");
|
|
1068
1280
|
const files = await collectSpecFiles(specsRoot);
|
|
1069
1281
|
if (files.length === 0) {
|
|
1070
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",
|
|
@@ -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
|
|