qfai 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/assets/init/.qfai/README.md +1 -0
- package/assets/init/.qfai/contracts/README.md +17 -8
- package/assets/init/.qfai/contracts/api/api-0001-sample.yaml +3 -2
- package/assets/init/.qfai/contracts/db/db-0001-sample.sql +2 -1
- package/assets/init/.qfai/contracts/ui/ui-0001-sample.yaml +3 -1
- package/assets/init/.qfai/promptpack/modes/change.md +3 -2
- package/assets/init/.qfai/promptpack/modes/compatibility.md +2 -0
- package/assets/init/.qfai/prompts/require-to-spec.md +4 -2
- package/assets/init/.qfai/specs/README.md +9 -2
- package/assets/init/.qfai/specs/spec-0001/spec.md +2 -0
- package/assets/init/root/qfai.config.yaml +0 -1
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +30 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/report.d.ts +7 -0
- package/dist/cli/commands/report.d.ts.map +1 -0
- package/dist/cli/commands/report.js +80 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +9 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +57 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/index.cjs +504 -328
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +504 -328
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib/args.d.ts +18 -0
- package/dist/cli/lib/args.d.ts.map +1 -0
- package/dist/cli/lib/args.js +98 -0
- package/dist/cli/lib/args.js.map +1 -0
- package/dist/cli/lib/assets.d.ts +2 -0
- package/dist/cli/lib/assets.d.ts.map +1 -0
- package/dist/cli/lib/assets.js +24 -0
- package/dist/cli/lib/assets.js.map +1 -0
- package/dist/cli/lib/failOn.d.ts +5 -0
- package/dist/cli/lib/failOn.d.ts.map +1 -0
- package/dist/cli/lib/failOn.js +10 -0
- package/dist/cli/lib/failOn.js.map +1 -0
- package/dist/cli/lib/fs.d.ts +11 -0
- package/dist/cli/lib/fs.d.ts.map +1 -0
- package/dist/cli/lib/fs.js +91 -0
- package/dist/cli/lib/fs.js.map +1 -0
- package/dist/cli/lib/logger.d.ts +4 -0
- package/dist/cli/lib/logger.d.ts.map +1 -0
- package/dist/cli/lib/logger.js +10 -0
- package/dist/cli/lib/logger.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +66 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/core/config.d.ts +47 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +224 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/contractIndex.d.ts +12 -0
- package/dist/core/contractIndex.d.ts.map +1 -0
- package/dist/core/contractIndex.js +38 -0
- package/dist/core/contractIndex.js.map +1 -0
- package/dist/core/contracts.d.ts +5 -0
- package/dist/core/contracts.d.ts.map +1 -0
- package/dist/core/contracts.js +42 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/contractsDecl.d.ts +3 -0
- package/dist/core/contractsDecl.d.ts.map +1 -0
- package/dist/core/contractsDecl.js +19 -0
- package/dist/core/contractsDecl.js.map +1 -0
- package/dist/core/discovery.d.ts +14 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +55 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/fs.d.ts +11 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +68 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/gherkin/parse.d.ts +7 -0
- package/dist/core/gherkin/parse.d.ts.map +1 -0
- package/dist/core/gherkin/parse.js +25 -0
- package/dist/core/gherkin/parse.js.map +1 -0
- package/dist/core/ids.d.ts +6 -0
- package/dist/core/ids.d.ts.map +1 -0
- package/dist/core/ids.js +52 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/parse/adr.d.ts +13 -0
- package/dist/core/parse/adr.d.ts.map +1 -0
- package/dist/core/parse/adr.js +33 -0
- package/dist/core/parse/adr.js.map +1 -0
- package/dist/core/parse/gherkin.d.ts +12 -0
- package/dist/core/parse/gherkin.d.ts.map +1 -0
- package/dist/core/parse/gherkin.js +22 -0
- package/dist/core/parse/gherkin.js.map +1 -0
- package/dist/core/parse/markdown.d.ts +14 -0
- package/dist/core/parse/markdown.d.ts.map +1 -0
- package/dist/core/parse/markdown.js +45 -0
- package/dist/core/parse/markdown.js.map +1 -0
- package/dist/core/parse/spec.d.ts +36 -0
- package/dist/core/parse/spec.d.ts.map +1 -0
- package/dist/core/parse/spec.js +123 -0
- package/dist/core/parse/spec.js.map +1 -0
- package/dist/core/report.d.ts +55 -0
- package/dist/core/report.d.ts.map +1 -0
- package/dist/core/report.js +393 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/scenarioModel.d.ts +33 -0
- package/dist/core/scenarioModel.d.ts.map +1 -0
- package/dist/core/scenarioModel.js +128 -0
- package/dist/core/scenarioModel.js.map +1 -0
- package/dist/core/specLayout.d.ts +8 -0
- package/dist/core/specLayout.d.ts.map +1 -0
- package/dist/core/specLayout.js +36 -0
- package/dist/core/specLayout.js.map +1 -0
- package/dist/core/traceability.d.ts +26 -0
- package/dist/core/traceability.d.ts.map +1 -0
- package/dist/core/traceability.js +157 -0
- package/dist/core/traceability.js.map +1 -0
- package/dist/core/types.d.ts +31 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validate.d.ts +4 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +45 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/core/validators/contracts.d.ts +5 -0
- package/dist/core/validators/contracts.d.ts.map +1 -0
- package/dist/core/validators/contracts.js +189 -0
- package/dist/core/validators/contracts.js.map +1 -0
- package/dist/core/validators/delta.d.ts +4 -0
- package/dist/core/validators/delta.d.ts.map +1 -0
- package/dist/core/validators/delta.js +68 -0
- package/dist/core/validators/delta.js.map +1 -0
- package/dist/core/validators/ids.d.ts +4 -0
- package/dist/core/validators/ids.d.ts.map +1 -0
- package/dist/core/validators/ids.js +88 -0
- package/dist/core/validators/ids.js.map +1 -0
- package/dist/core/validators/scenario.d.ts +5 -0
- package/dist/core/validators/scenario.d.ts.map +1 -0
- package/dist/core/validators/scenario.js +127 -0
- package/dist/core/validators/scenario.js.map +1 -0
- package/dist/core/validators/spec.d.ts +5 -0
- package/dist/core/validators/spec.d.ts.map +1 -0
- package/dist/core/validators/spec.js +94 -0
- package/dist/core/validators/spec.js.map +1 -0
- package/dist/core/validators/traceability.d.ts +4 -0
- package/dist/core/validators/traceability.d.ts.map +1 -0
- package/dist/core/validators/traceability.js +222 -0
- package/dist/core/validators/traceability.js.map +1 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +25 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.cjs +504 -328
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -4
- package/dist/index.d.ts +2 -156
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +504 -328
- package/dist/index.mjs.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -186,7 +186,6 @@ var defaultConfig = {
|
|
|
186
186
|
},
|
|
187
187
|
traceability: {
|
|
188
188
|
brMustHaveSc: true,
|
|
189
|
-
scMustTouchContracts: true,
|
|
190
189
|
scMustHaveTest: true,
|
|
191
190
|
testFileGlobs: [],
|
|
192
191
|
testFileExcludeGlobs: [],
|
|
@@ -363,13 +362,6 @@ function normalizeValidation(raw, configPath, issues) {
|
|
|
363
362
|
configPath,
|
|
364
363
|
issues
|
|
365
364
|
),
|
|
366
|
-
scMustTouchContracts: readBoolean(
|
|
367
|
-
traceabilityRaw?.scMustTouchContracts,
|
|
368
|
-
base.traceability.scMustTouchContracts,
|
|
369
|
-
"validation.traceability.scMustTouchContracts",
|
|
370
|
-
configPath,
|
|
371
|
-
issues
|
|
372
|
-
),
|
|
373
365
|
scMustHaveTest: readBoolean(
|
|
374
366
|
traceabilityRaw?.scMustHaveTest,
|
|
375
367
|
base.traceability.scMustHaveTest,
|
|
@@ -526,6 +518,10 @@ function isRecord(value) {
|
|
|
526
518
|
import { readFile as readFile11 } from "fs/promises";
|
|
527
519
|
import path14 from "path";
|
|
528
520
|
|
|
521
|
+
// src/core/contractIndex.ts
|
|
522
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
523
|
+
import path7 from "path";
|
|
524
|
+
|
|
529
525
|
// src/core/discovery.ts
|
|
530
526
|
import { access as access3 } from "fs/promises";
|
|
531
527
|
|
|
@@ -677,15 +673,66 @@ async function exists3(target) {
|
|
|
677
673
|
}
|
|
678
674
|
}
|
|
679
675
|
|
|
676
|
+
// src/core/contractsDecl.ts
|
|
677
|
+
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
|
|
678
|
+
var CONTRACT_DECLARATION_LINE_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*(?:API|UI|DB)-\d{4}\s*(?:\*\/)?\s*$/;
|
|
679
|
+
function extractDeclaredContractIds(text) {
|
|
680
|
+
const ids = [];
|
|
681
|
+
for (const match of text.matchAll(CONTRACT_DECLARATION_RE)) {
|
|
682
|
+
const id = match[1];
|
|
683
|
+
if (id) {
|
|
684
|
+
ids.push(id);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return ids;
|
|
688
|
+
}
|
|
689
|
+
function stripContractDeclarationLines(text) {
|
|
690
|
+
return text.split(/\r?\n/).filter((line) => !CONTRACT_DECLARATION_LINE_RE.test(line)).join("\n");
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/core/contractIndex.ts
|
|
694
|
+
async function buildContractIndex(root, config) {
|
|
695
|
+
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
696
|
+
const uiRoot = path7.join(contractsRoot, "ui");
|
|
697
|
+
const apiRoot = path7.join(contractsRoot, "api");
|
|
698
|
+
const dbRoot = path7.join(contractsRoot, "db");
|
|
699
|
+
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
700
|
+
collectUiContractFiles(uiRoot),
|
|
701
|
+
collectApiContractFiles(apiRoot),
|
|
702
|
+
collectDataContractFiles(dbRoot)
|
|
703
|
+
]);
|
|
704
|
+
const index = {
|
|
705
|
+
ids: /* @__PURE__ */ new Set(),
|
|
706
|
+
idToFiles: /* @__PURE__ */ new Map(),
|
|
707
|
+
files: { ui: uiFiles, api: apiFiles, db: dbFiles }
|
|
708
|
+
};
|
|
709
|
+
await indexContractFiles(uiFiles, index);
|
|
710
|
+
await indexContractFiles(apiFiles, index);
|
|
711
|
+
await indexContractFiles(dbFiles, index);
|
|
712
|
+
return index;
|
|
713
|
+
}
|
|
714
|
+
async function indexContractFiles(files, index) {
|
|
715
|
+
for (const file of files) {
|
|
716
|
+
const text = await readFile2(file, "utf-8");
|
|
717
|
+
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function record(index, id, file) {
|
|
721
|
+
index.ids.add(id);
|
|
722
|
+
const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
|
|
723
|
+
current.add(file);
|
|
724
|
+
index.idToFiles.set(id, current);
|
|
725
|
+
}
|
|
726
|
+
|
|
680
727
|
// src/core/ids.ts
|
|
681
|
-
var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "
|
|
728
|
+
var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
682
729
|
var STRICT_ID_PATTERNS = {
|
|
683
730
|
SPEC: /\bSPEC-\d{4}\b/g,
|
|
684
731
|
BR: /\bBR-\d{4}\b/g,
|
|
685
732
|
SC: /\bSC-\d{4}\b/g,
|
|
686
733
|
UI: /\bUI-\d{4}\b/g,
|
|
687
734
|
API: /\bAPI-\d{4}\b/g,
|
|
688
|
-
|
|
735
|
+
DB: /\bDB-\d{4}\b/g,
|
|
689
736
|
ADR: /\bADR-\d{4}\b/g
|
|
690
737
|
};
|
|
691
738
|
var LOOSE_ID_PATTERNS = {
|
|
@@ -694,7 +741,7 @@ var LOOSE_ID_PATTERNS = {
|
|
|
694
741
|
SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
|
|
695
742
|
UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
|
|
696
743
|
API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
|
|
697
|
-
|
|
744
|
+
DB: /\bDB-[A-Za-z0-9_-]+\b/gi,
|
|
698
745
|
ADR: /\bADR-[A-Za-z0-9_-]+\b/gi
|
|
699
746
|
};
|
|
700
747
|
function extractIds(text, prefix) {
|
|
@@ -730,9 +777,170 @@ function isValidId(value, prefix) {
|
|
|
730
777
|
return strict.test(value);
|
|
731
778
|
}
|
|
732
779
|
|
|
780
|
+
// src/core/parse/markdown.ts
|
|
781
|
+
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
782
|
+
function parseHeadings(md) {
|
|
783
|
+
const lines = md.split(/\r?\n/);
|
|
784
|
+
const headings = [];
|
|
785
|
+
for (let i = 0; i < lines.length; i++) {
|
|
786
|
+
const line = lines[i] ?? "";
|
|
787
|
+
const match = line.match(HEADING_RE);
|
|
788
|
+
if (!match) continue;
|
|
789
|
+
const levelToken = match[1];
|
|
790
|
+
const title = match[2];
|
|
791
|
+
if (!levelToken || !title) continue;
|
|
792
|
+
headings.push({
|
|
793
|
+
level: levelToken.length,
|
|
794
|
+
title: title.trim(),
|
|
795
|
+
line: i + 1
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
return headings;
|
|
799
|
+
}
|
|
800
|
+
function extractH2Sections(md) {
|
|
801
|
+
const lines = md.split(/\r?\n/);
|
|
802
|
+
const headings = parseHeadings(md).filter((heading) => heading.level === 2);
|
|
803
|
+
const sections = /* @__PURE__ */ new Map();
|
|
804
|
+
for (let i = 0; i < headings.length; i++) {
|
|
805
|
+
const current = headings[i];
|
|
806
|
+
if (!current) continue;
|
|
807
|
+
const next = headings[i + 1];
|
|
808
|
+
const startLine = current.line + 1;
|
|
809
|
+
const endLine = (next?.line ?? lines.length + 1) - 1;
|
|
810
|
+
const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
|
|
811
|
+
sections.set(current.title.trim(), {
|
|
812
|
+
title: current.title.trim(),
|
|
813
|
+
startLine,
|
|
814
|
+
endLine,
|
|
815
|
+
body
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
return sections;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/core/parse/spec.ts
|
|
822
|
+
var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
|
|
823
|
+
var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
|
|
824
|
+
var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
|
|
825
|
+
var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
|
|
826
|
+
var CONTRACT_REF_LINE_RE = /^[ \t]*QFAI-CONTRACT-REF:[ \t]*([^\r\n]*)[ \t]*$/gm;
|
|
827
|
+
var CONTRACT_REF_ID_RE = /^(?:API|UI|DB)-\d{4}$/;
|
|
828
|
+
var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
|
|
829
|
+
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
830
|
+
function parseSpec(md, file) {
|
|
831
|
+
const headings = parseHeadings(md);
|
|
832
|
+
const h1 = headings.find((heading) => heading.level === 1);
|
|
833
|
+
const specId = h1?.title.match(SPEC_ID_RE)?.[0];
|
|
834
|
+
const sections = extractH2Sections(md);
|
|
835
|
+
const sectionNames = new Set(Array.from(sections.keys()));
|
|
836
|
+
const brSection = sections.get(BR_SECTION_TITLE);
|
|
837
|
+
const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
|
|
838
|
+
const startLine = brSection?.startLine ?? 1;
|
|
839
|
+
const brs = [];
|
|
840
|
+
const brsWithoutPriority = [];
|
|
841
|
+
const brsWithInvalidPriority = [];
|
|
842
|
+
for (let i = 0; i < brLines.length; i++) {
|
|
843
|
+
const lineText = brLines[i] ?? "";
|
|
844
|
+
const lineNumber = startLine + i;
|
|
845
|
+
const validMatch = lineText.match(BR_LINE_RE);
|
|
846
|
+
if (validMatch) {
|
|
847
|
+
const id = validMatch[1];
|
|
848
|
+
const priority = validMatch[2];
|
|
849
|
+
const text = validMatch[3];
|
|
850
|
+
if (!id || !priority || !text) continue;
|
|
851
|
+
brs.push({
|
|
852
|
+
id,
|
|
853
|
+
priority,
|
|
854
|
+
text: text.trim(),
|
|
855
|
+
line: lineNumber
|
|
856
|
+
});
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
|
|
860
|
+
if (anyPriorityMatch) {
|
|
861
|
+
const id = anyPriorityMatch[1];
|
|
862
|
+
const priority = anyPriorityMatch[2];
|
|
863
|
+
const text = anyPriorityMatch[3];
|
|
864
|
+
if (!id || !priority || !text) continue;
|
|
865
|
+
if (!VALID_PRIORITIES.has(priority)) {
|
|
866
|
+
brsWithInvalidPriority.push({
|
|
867
|
+
id,
|
|
868
|
+
priority,
|
|
869
|
+
text: text.trim(),
|
|
870
|
+
line: lineNumber
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
|
|
876
|
+
if (noPriorityMatch) {
|
|
877
|
+
const id = noPriorityMatch[1];
|
|
878
|
+
const text = noPriorityMatch[2];
|
|
879
|
+
if (!id || !text) continue;
|
|
880
|
+
brsWithoutPriority.push({
|
|
881
|
+
id,
|
|
882
|
+
text: text.trim(),
|
|
883
|
+
line: lineNumber
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const parsed = {
|
|
888
|
+
file,
|
|
889
|
+
sections: sectionNames,
|
|
890
|
+
brs,
|
|
891
|
+
brsWithoutPriority,
|
|
892
|
+
brsWithInvalidPriority,
|
|
893
|
+
contractRefs: parseContractRefs(md)
|
|
894
|
+
};
|
|
895
|
+
if (specId) {
|
|
896
|
+
parsed.specId = specId;
|
|
897
|
+
}
|
|
898
|
+
return parsed;
|
|
899
|
+
}
|
|
900
|
+
function parseContractRefs(md) {
|
|
901
|
+
const lines = [];
|
|
902
|
+
for (const match of md.matchAll(CONTRACT_REF_LINE_RE)) {
|
|
903
|
+
lines.push((match[1] ?? "").trim());
|
|
904
|
+
}
|
|
905
|
+
const ids = [];
|
|
906
|
+
const invalidTokens = [];
|
|
907
|
+
let hasNone = false;
|
|
908
|
+
for (const line of lines) {
|
|
909
|
+
if (line.length === 0) {
|
|
910
|
+
invalidTokens.push("(empty)");
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const tokens = line.split(",").map((token) => token.trim());
|
|
914
|
+
for (const token of tokens) {
|
|
915
|
+
if (token.length === 0) {
|
|
916
|
+
invalidTokens.push("(empty)");
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (token === "none") {
|
|
920
|
+
hasNone = true;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (CONTRACT_REF_ID_RE.test(token)) {
|
|
924
|
+
ids.push(token);
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
invalidTokens.push(token);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
lines,
|
|
932
|
+
ids: unique2(ids),
|
|
933
|
+
invalidTokens: unique2(invalidTokens),
|
|
934
|
+
hasNone
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
function unique2(values) {
|
|
938
|
+
return Array.from(new Set(values));
|
|
939
|
+
}
|
|
940
|
+
|
|
733
941
|
// src/core/traceability.ts
|
|
734
|
-
import { readFile as
|
|
735
|
-
import
|
|
942
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
943
|
+
import path8 from "path";
|
|
736
944
|
|
|
737
945
|
// src/core/gherkin/parse.ts
|
|
738
946
|
import {
|
|
@@ -769,7 +977,7 @@ var SC_TAG_RE = /^SC-\d{4}$/;
|
|
|
769
977
|
var BR_TAG_RE = /^BR-\d{4}$/;
|
|
770
978
|
var UI_TAG_RE = /^UI-\d{4}$/;
|
|
771
979
|
var API_TAG_RE = /^API-\d{4}$/;
|
|
772
|
-
var
|
|
980
|
+
var DB_TAG_RE = /^DB-\d{4}$/;
|
|
773
981
|
function parseScenarioDocument(text, uri) {
|
|
774
982
|
const { gherkinDocument, errors } = parseGherkin(text, uri);
|
|
775
983
|
if (!gherkinDocument) {
|
|
@@ -798,10 +1006,10 @@ function buildScenarioAtoms(document) {
|
|
|
798
1006
|
return document.scenarios.map((scenario) => {
|
|
799
1007
|
const specIds = scenario.tags.filter((tag) => SPEC_TAG_RE.test(tag));
|
|
800
1008
|
const scIds = scenario.tags.filter((tag) => SC_TAG_RE.test(tag));
|
|
801
|
-
const brIds =
|
|
1009
|
+
const brIds = unique3(scenario.tags.filter((tag) => BR_TAG_RE.test(tag)));
|
|
802
1010
|
const contractIds = /* @__PURE__ */ new Set();
|
|
803
1011
|
scenario.tags.forEach((tag) => {
|
|
804
|
-
if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) ||
|
|
1012
|
+
if (UI_TAG_RE.test(tag) || API_TAG_RE.test(tag) || DB_TAG_RE.test(tag)) {
|
|
805
1013
|
contractIds.add(tag);
|
|
806
1014
|
}
|
|
807
1015
|
});
|
|
@@ -809,7 +1017,7 @@ function buildScenarioAtoms(document) {
|
|
|
809
1017
|
for (const text of collectStepTexts(step)) {
|
|
810
1018
|
extractIds(text, "UI").forEach((id) => contractIds.add(id));
|
|
811
1019
|
extractIds(text, "API").forEach((id) => contractIds.add(id));
|
|
812
|
-
extractIds(text, "
|
|
1020
|
+
extractIds(text, "DB").forEach((id) => contractIds.add(id));
|
|
813
1021
|
}
|
|
814
1022
|
}
|
|
815
1023
|
const atom = {
|
|
@@ -888,7 +1096,7 @@ function collectStepTexts(step) {
|
|
|
888
1096
|
}
|
|
889
1097
|
return texts;
|
|
890
1098
|
}
|
|
891
|
-
function
|
|
1099
|
+
function unique3(values) {
|
|
892
1100
|
return Array.from(new Set(values));
|
|
893
1101
|
}
|
|
894
1102
|
|
|
@@ -918,7 +1126,7 @@ function extractAnnotatedScIds(text) {
|
|
|
918
1126
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
919
1127
|
const scIds = /* @__PURE__ */ new Set();
|
|
920
1128
|
for (const file of scenarioFiles) {
|
|
921
|
-
const text = await
|
|
1129
|
+
const text = await readFile3(file, "utf-8");
|
|
922
1130
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
923
1131
|
if (!document || errors.length > 0) {
|
|
924
1132
|
continue;
|
|
@@ -936,7 +1144,7 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
936
1144
|
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
937
1145
|
const sources = /* @__PURE__ */ new Map();
|
|
938
1146
|
for (const file of scenarioFiles) {
|
|
939
|
-
const text = await
|
|
1147
|
+
const text = await readFile3(file, "utf-8");
|
|
940
1148
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
941
1149
|
if (!document || errors.length > 0) {
|
|
942
1150
|
continue;
|
|
@@ -989,10 +1197,10 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
989
1197
|
};
|
|
990
1198
|
}
|
|
991
1199
|
const normalizedFiles = Array.from(
|
|
992
|
-
new Set(files.map((file) =>
|
|
1200
|
+
new Set(files.map((file) => path8.normalize(file)))
|
|
993
1201
|
);
|
|
994
1202
|
for (const file of normalizedFiles) {
|
|
995
|
-
const text = await
|
|
1203
|
+
const text = await readFile3(file, "utf-8");
|
|
996
1204
|
const scIds = extractAnnotatedScIds(text);
|
|
997
1205
|
if (scIds.length === 0) {
|
|
998
1206
|
continue;
|
|
@@ -1049,16 +1257,16 @@ function formatError3(error2) {
|
|
|
1049
1257
|
}
|
|
1050
1258
|
|
|
1051
1259
|
// src/core/version.ts
|
|
1052
|
-
import { readFile as
|
|
1053
|
-
import
|
|
1260
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1261
|
+
import path9 from "path";
|
|
1054
1262
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1055
1263
|
async function resolveToolVersion() {
|
|
1056
|
-
if ("0.4.
|
|
1057
|
-
return "0.4.
|
|
1264
|
+
if ("0.4.4".length > 0) {
|
|
1265
|
+
return "0.4.4";
|
|
1058
1266
|
}
|
|
1059
1267
|
try {
|
|
1060
1268
|
const packagePath = resolvePackageJsonPath();
|
|
1061
|
-
const raw = await
|
|
1269
|
+
const raw = await readFile4(packagePath, "utf-8");
|
|
1062
1270
|
const parsed = JSON.parse(raw);
|
|
1063
1271
|
const version = typeof parsed.version === "string" ? parsed.version : "";
|
|
1064
1272
|
return version.length > 0 ? version : "unknown";
|
|
@@ -1069,54 +1277,23 @@ async function resolveToolVersion() {
|
|
|
1069
1277
|
function resolvePackageJsonPath() {
|
|
1070
1278
|
const base = import.meta.url;
|
|
1071
1279
|
const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
|
|
1072
|
-
return
|
|
1280
|
+
return path9.resolve(path9.dirname(basePath), "../../package.json");
|
|
1073
1281
|
}
|
|
1074
1282
|
|
|
1075
1283
|
// src/core/validators/contracts.ts
|
|
1076
|
-
import { readFile as
|
|
1077
|
-
import
|
|
1284
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1285
|
+
import path11 from "path";
|
|
1078
1286
|
|
|
1079
1287
|
// src/core/contracts.ts
|
|
1080
|
-
import
|
|
1288
|
+
import path10 from "path";
|
|
1081
1289
|
import { parse as parseYaml2 } from "yaml";
|
|
1082
1290
|
function parseStructuredContract(file, text) {
|
|
1083
|
-
const ext =
|
|
1291
|
+
const ext = path10.extname(file).toLowerCase();
|
|
1084
1292
|
if (ext === ".json") {
|
|
1085
1293
|
return JSON.parse(text);
|
|
1086
1294
|
}
|
|
1087
1295
|
return parseYaml2(text);
|
|
1088
1296
|
}
|
|
1089
|
-
function extractUiContractIds(doc) {
|
|
1090
|
-
const id = typeof doc.id === "string" ? doc.id : "";
|
|
1091
|
-
return extractIds(id, "UI");
|
|
1092
|
-
}
|
|
1093
|
-
function extractApiContractIds(doc) {
|
|
1094
|
-
const operationIds = /* @__PURE__ */ new Set();
|
|
1095
|
-
collectOperationIds(doc, operationIds);
|
|
1096
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1097
|
-
for (const operationId of operationIds) {
|
|
1098
|
-
extractIds(operationId, "API").forEach((id) => ids.add(id));
|
|
1099
|
-
}
|
|
1100
|
-
return Array.from(ids);
|
|
1101
|
-
}
|
|
1102
|
-
function collectOperationIds(value, out) {
|
|
1103
|
-
if (!value || typeof value !== "object") {
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
if (Array.isArray(value)) {
|
|
1107
|
-
for (const item of value) {
|
|
1108
|
-
collectOperationIds(item, out);
|
|
1109
|
-
}
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1113
|
-
if (key === "operationId" && typeof entry === "string") {
|
|
1114
|
-
out.add(entry);
|
|
1115
|
-
continue;
|
|
1116
|
-
}
|
|
1117
|
-
collectOperationIds(entry, out);
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
1297
|
|
|
1121
1298
|
// src/core/validators/contracts.ts
|
|
1122
1299
|
var SQL_DANGEROUS_PATTERNS = [
|
|
@@ -1131,9 +1308,11 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1131
1308
|
async function validateContracts(root, config) {
|
|
1132
1309
|
const issues = [];
|
|
1133
1310
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1134
|
-
issues.push(...await validateUiContracts(
|
|
1135
|
-
issues.push(...await validateApiContracts(
|
|
1136
|
-
issues.push(...await validateDataContracts(
|
|
1311
|
+
issues.push(...await validateUiContracts(path11.join(contractsRoot, "ui")));
|
|
1312
|
+
issues.push(...await validateApiContracts(path11.join(contractsRoot, "api")));
|
|
1313
|
+
issues.push(...await validateDataContracts(path11.join(contractsRoot, "db")));
|
|
1314
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
1315
|
+
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
1137
1316
|
return issues;
|
|
1138
1317
|
}
|
|
1139
1318
|
async function validateUiContracts(uiRoot) {
|
|
@@ -1151,14 +1330,14 @@ async function validateUiContracts(uiRoot) {
|
|
|
1151
1330
|
}
|
|
1152
1331
|
const issues = [];
|
|
1153
1332
|
for (const file of files) {
|
|
1154
|
-
const text = await
|
|
1333
|
+
const text = await readFile5(file, "utf-8");
|
|
1155
1334
|
const invalidIds = extractInvalidIds(text, [
|
|
1156
1335
|
"SPEC",
|
|
1157
1336
|
"BR",
|
|
1158
1337
|
"SC",
|
|
1159
1338
|
"UI",
|
|
1160
1339
|
"API",
|
|
1161
|
-
"
|
|
1340
|
+
"DB",
|
|
1162
1341
|
"ADR"
|
|
1163
1342
|
]);
|
|
1164
1343
|
if (invalidIds.length > 0) {
|
|
@@ -1173,9 +1352,10 @@ async function validateUiContracts(uiRoot) {
|
|
|
1173
1352
|
)
|
|
1174
1353
|
);
|
|
1175
1354
|
}
|
|
1176
|
-
|
|
1355
|
+
const declaredIds = extractDeclaredContractIds(text);
|
|
1356
|
+
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
1177
1357
|
try {
|
|
1178
|
-
|
|
1358
|
+
parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1179
1359
|
} catch (error2) {
|
|
1180
1360
|
issues.push(
|
|
1181
1361
|
issue(
|
|
@@ -1186,19 +1366,6 @@ async function validateUiContracts(uiRoot) {
|
|
|
1186
1366
|
"contracts.ui.parse"
|
|
1187
1367
|
)
|
|
1188
1368
|
);
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
const uiIds = extractUiContractIds(doc);
|
|
1192
|
-
if (uiIds.length === 0) {
|
|
1193
|
-
issues.push(
|
|
1194
|
-
issue(
|
|
1195
|
-
"QFAI-CONTRACT-002",
|
|
1196
|
-
`UI \u5951\u7D04\u306B ID(UI-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
1197
|
-
"error",
|
|
1198
|
-
file,
|
|
1199
|
-
"contracts.ui.id"
|
|
1200
|
-
)
|
|
1201
|
-
);
|
|
1202
1369
|
}
|
|
1203
1370
|
}
|
|
1204
1371
|
return issues;
|
|
@@ -1218,14 +1385,14 @@ async function validateApiContracts(apiRoot) {
|
|
|
1218
1385
|
}
|
|
1219
1386
|
const issues = [];
|
|
1220
1387
|
for (const file of files) {
|
|
1221
|
-
const text = await
|
|
1388
|
+
const text = await readFile5(file, "utf-8");
|
|
1222
1389
|
const invalidIds = extractInvalidIds(text, [
|
|
1223
1390
|
"SPEC",
|
|
1224
1391
|
"BR",
|
|
1225
1392
|
"SC",
|
|
1226
1393
|
"UI",
|
|
1227
1394
|
"API",
|
|
1228
|
-
"
|
|
1395
|
+
"DB",
|
|
1229
1396
|
"ADR"
|
|
1230
1397
|
]);
|
|
1231
1398
|
if (invalidIds.length > 0) {
|
|
@@ -1240,9 +1407,11 @@ async function validateApiContracts(apiRoot) {
|
|
|
1240
1407
|
)
|
|
1241
1408
|
);
|
|
1242
1409
|
}
|
|
1410
|
+
const declaredIds = extractDeclaredContractIds(text);
|
|
1411
|
+
issues.push(...validateDeclaredContractIds(declaredIds, file, "API"));
|
|
1243
1412
|
let doc;
|
|
1244
1413
|
try {
|
|
1245
|
-
doc = parseStructuredContract(file, text);
|
|
1414
|
+
doc = parseStructuredContract(file, stripContractDeclarationLines(text));
|
|
1246
1415
|
} catch (error2) {
|
|
1247
1416
|
issues.push(
|
|
1248
1417
|
issue(
|
|
@@ -1266,18 +1435,6 @@ async function validateApiContracts(apiRoot) {
|
|
|
1266
1435
|
)
|
|
1267
1436
|
);
|
|
1268
1437
|
}
|
|
1269
|
-
const apiIds = extractApiContractIds(doc);
|
|
1270
|
-
if (apiIds.length === 0) {
|
|
1271
|
-
issues.push(
|
|
1272
|
-
issue(
|
|
1273
|
-
"QFAI-CONTRACT-002",
|
|
1274
|
-
`API \u5951\u7D04\u306B ID(API-xxxx) \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${file}`,
|
|
1275
|
-
"error",
|
|
1276
|
-
file,
|
|
1277
|
-
"contracts.api.id"
|
|
1278
|
-
)
|
|
1279
|
-
);
|
|
1280
|
-
}
|
|
1281
1438
|
}
|
|
1282
1439
|
return issues;
|
|
1283
1440
|
}
|
|
@@ -1286,24 +1443,24 @@ async function validateDataContracts(dataRoot) {
|
|
|
1286
1443
|
if (files.length === 0) {
|
|
1287
1444
|
return [
|
|
1288
1445
|
issue(
|
|
1289
|
-
"QFAI-
|
|
1290
|
-
"
|
|
1446
|
+
"QFAI-DB-000",
|
|
1447
|
+
"DB \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
1291
1448
|
"info",
|
|
1292
1449
|
dataRoot,
|
|
1293
|
-
"contracts.
|
|
1450
|
+
"contracts.db.files"
|
|
1294
1451
|
)
|
|
1295
1452
|
];
|
|
1296
1453
|
}
|
|
1297
1454
|
const issues = [];
|
|
1298
1455
|
for (const file of files) {
|
|
1299
|
-
const text = await
|
|
1456
|
+
const text = await readFile5(file, "utf-8");
|
|
1300
1457
|
const invalidIds = extractInvalidIds(text, [
|
|
1301
1458
|
"SPEC",
|
|
1302
1459
|
"BR",
|
|
1303
1460
|
"SC",
|
|
1304
1461
|
"UI",
|
|
1305
1462
|
"API",
|
|
1306
|
-
"
|
|
1463
|
+
"DB",
|
|
1307
1464
|
"ADR"
|
|
1308
1465
|
]);
|
|
1309
1466
|
if (invalidIds.length > 0) {
|
|
@@ -1318,6 +1475,8 @@ async function validateDataContracts(dataRoot) {
|
|
|
1318
1475
|
)
|
|
1319
1476
|
);
|
|
1320
1477
|
}
|
|
1478
|
+
const declaredIds = extractDeclaredContractIds(text);
|
|
1479
|
+
issues.push(...validateDeclaredContractIds(declaredIds, file, "DB"));
|
|
1321
1480
|
issues.push(...lintSql(text, file));
|
|
1322
1481
|
}
|
|
1323
1482
|
return issues;
|
|
@@ -1328,17 +1487,83 @@ function lintSql(text, file) {
|
|
|
1328
1487
|
if (pattern.test(text)) {
|
|
1329
1488
|
issues.push(
|
|
1330
1489
|
issue(
|
|
1331
|
-
"QFAI-
|
|
1490
|
+
"QFAI-DB-001",
|
|
1332
1491
|
`\u5371\u967A\u306A SQL \u64CD\u4F5C\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059: ${label}`,
|
|
1333
1492
|
"warning",
|
|
1334
1493
|
file,
|
|
1335
|
-
"contracts.
|
|
1494
|
+
"contracts.db.sql"
|
|
1336
1495
|
)
|
|
1337
1496
|
);
|
|
1338
1497
|
}
|
|
1339
1498
|
}
|
|
1340
1499
|
return issues;
|
|
1341
1500
|
}
|
|
1501
|
+
function validateDeclaredContractIds(ids, file, kind) {
|
|
1502
|
+
const issues = [];
|
|
1503
|
+
if (ids.length === 0) {
|
|
1504
|
+
issues.push(
|
|
1505
|
+
issue(
|
|
1506
|
+
"QFAI-CONTRACT-010",
|
|
1507
|
+
`\u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306B QFAI-CONTRACT-ID \u304C\u3042\u308A\u307E\u305B\u3093: ${file}`,
|
|
1508
|
+
"error",
|
|
1509
|
+
file,
|
|
1510
|
+
"contracts.declaration"
|
|
1511
|
+
)
|
|
1512
|
+
);
|
|
1513
|
+
return issues;
|
|
1514
|
+
}
|
|
1515
|
+
if (ids.length > 1) {
|
|
1516
|
+
issues.push(
|
|
1517
|
+
issue(
|
|
1518
|
+
"QFAI-CONTRACT-011",
|
|
1519
|
+
`\u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306B\u8907\u6570\u306E QFAI-CONTRACT-ID \u304C\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u3059: ${ids.join(
|
|
1520
|
+
", "
|
|
1521
|
+
)}`,
|
|
1522
|
+
"error",
|
|
1523
|
+
file,
|
|
1524
|
+
"contracts.declaration",
|
|
1525
|
+
ids
|
|
1526
|
+
)
|
|
1527
|
+
);
|
|
1528
|
+
return issues;
|
|
1529
|
+
}
|
|
1530
|
+
const [id] = ids;
|
|
1531
|
+
if (id && !id.startsWith(`${kind}-`)) {
|
|
1532
|
+
issues.push(
|
|
1533
|
+
issue(
|
|
1534
|
+
"QFAI-CONTRACT-013",
|
|
1535
|
+
`\u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u306E QFAI-CONTRACT-ID \u304C ${kind}- \u3067\u306F\u3042\u308A\u307E\u305B\u3093: ${id}`,
|
|
1536
|
+
"error",
|
|
1537
|
+
file,
|
|
1538
|
+
"contracts.declarationPrefix",
|
|
1539
|
+
[id]
|
|
1540
|
+
)
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
return issues;
|
|
1544
|
+
}
|
|
1545
|
+
function validateDuplicateContractIds(contractIndex) {
|
|
1546
|
+
const issues = [];
|
|
1547
|
+
for (const [id, files] of contractIndex.idToFiles.entries()) {
|
|
1548
|
+
if (files.size <= 1) {
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
const sortedFiles = Array.from(files).sort((a, b) => a.localeCompare(b));
|
|
1552
|
+
issues.push(
|
|
1553
|
+
issue(
|
|
1554
|
+
"QFAI-CONTRACT-012",
|
|
1555
|
+
`\u5951\u7D04 ID \u304C\u8907\u6570\u30D5\u30A1\u30A4\u30EB\u3067\u5BA3\u8A00\u3055\u308C\u3066\u3044\u307E\u3059: ${id} (${sortedFiles.join(
|
|
1556
|
+
", "
|
|
1557
|
+
)})`,
|
|
1558
|
+
"error",
|
|
1559
|
+
sortedFiles[0],
|
|
1560
|
+
"contracts.idDuplicate",
|
|
1561
|
+
[id]
|
|
1562
|
+
)
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
return issues;
|
|
1566
|
+
}
|
|
1342
1567
|
function hasOpenApi(doc) {
|
|
1343
1568
|
return typeof doc.openapi === "string" && doc.openapi.length > 0;
|
|
1344
1569
|
}
|
|
@@ -1367,8 +1592,8 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
1367
1592
|
}
|
|
1368
1593
|
|
|
1369
1594
|
// src/core/validators/delta.ts
|
|
1370
|
-
import { readFile as
|
|
1371
|
-
import
|
|
1595
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1596
|
+
import path12 from "path";
|
|
1372
1597
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
1373
1598
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
1374
1599
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -1382,10 +1607,10 @@ async function validateDeltas(root, config) {
|
|
|
1382
1607
|
}
|
|
1383
1608
|
const issues = [];
|
|
1384
1609
|
for (const pack of packs) {
|
|
1385
|
-
const deltaPath =
|
|
1610
|
+
const deltaPath = path12.join(pack, "delta.md");
|
|
1386
1611
|
let text;
|
|
1387
1612
|
try {
|
|
1388
|
-
text = await
|
|
1613
|
+
text = await readFile6(deltaPath, "utf-8");
|
|
1389
1614
|
} catch (error2) {
|
|
1390
1615
|
if (isMissingFileError2(error2)) {
|
|
1391
1616
|
issues.push(
|
|
@@ -1459,187 +1684,6 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
1459
1684
|
// src/core/validators/ids.ts
|
|
1460
1685
|
import { readFile as readFile7 } from "fs/promises";
|
|
1461
1686
|
import path13 from "path";
|
|
1462
|
-
|
|
1463
|
-
// src/core/contractIndex.ts
|
|
1464
|
-
import { readFile as readFile6 } from "fs/promises";
|
|
1465
|
-
import path12 from "path";
|
|
1466
|
-
async function buildContractIndex(root, config) {
|
|
1467
|
-
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1468
|
-
const uiRoot = path12.join(contractsRoot, "ui");
|
|
1469
|
-
const apiRoot = path12.join(contractsRoot, "api");
|
|
1470
|
-
const dataRoot = path12.join(contractsRoot, "db");
|
|
1471
|
-
const [uiFiles, apiFiles, dataFiles] = await Promise.all([
|
|
1472
|
-
collectUiContractFiles(uiRoot),
|
|
1473
|
-
collectApiContractFiles(apiRoot),
|
|
1474
|
-
collectDataContractFiles(dataRoot)
|
|
1475
|
-
]);
|
|
1476
|
-
const index = {
|
|
1477
|
-
ids: /* @__PURE__ */ new Set(),
|
|
1478
|
-
idToFiles: /* @__PURE__ */ new Map(),
|
|
1479
|
-
files: { ui: uiFiles, api: apiFiles, data: dataFiles },
|
|
1480
|
-
structuredParseFailedFiles: /* @__PURE__ */ new Set()
|
|
1481
|
-
};
|
|
1482
|
-
await indexUiContracts(uiFiles, index);
|
|
1483
|
-
await indexApiContracts(apiFiles, index);
|
|
1484
|
-
await indexDataContracts(dataFiles, index);
|
|
1485
|
-
return index;
|
|
1486
|
-
}
|
|
1487
|
-
async function indexUiContracts(files, index) {
|
|
1488
|
-
for (const file of files) {
|
|
1489
|
-
const text = await readFile6(file, "utf-8");
|
|
1490
|
-
try {
|
|
1491
|
-
const doc = parseStructuredContract(file, text);
|
|
1492
|
-
extractUiContractIds(doc).forEach((id) => record(index, id, file));
|
|
1493
|
-
} catch {
|
|
1494
|
-
index.structuredParseFailedFiles.add(file);
|
|
1495
|
-
extractIds(text, "UI").forEach((id) => record(index, id, file));
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
async function indexApiContracts(files, index) {
|
|
1500
|
-
for (const file of files) {
|
|
1501
|
-
const text = await readFile6(file, "utf-8");
|
|
1502
|
-
try {
|
|
1503
|
-
const doc = parseStructuredContract(file, text);
|
|
1504
|
-
extractApiContractIds(doc).forEach((id) => record(index, id, file));
|
|
1505
|
-
} catch {
|
|
1506
|
-
index.structuredParseFailedFiles.add(file);
|
|
1507
|
-
extractIds(text, "API").forEach((id) => record(index, id, file));
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
async function indexDataContracts(files, index) {
|
|
1512
|
-
for (const file of files) {
|
|
1513
|
-
const text = await readFile6(file, "utf-8");
|
|
1514
|
-
extractIds(text, "DATA").forEach((id) => record(index, id, file));
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
function record(index, id, file) {
|
|
1518
|
-
index.ids.add(id);
|
|
1519
|
-
const current = index.idToFiles.get(id) ?? /* @__PURE__ */ new Set();
|
|
1520
|
-
current.add(file);
|
|
1521
|
-
index.idToFiles.set(id, current);
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
// src/core/parse/markdown.ts
|
|
1525
|
-
var HEADING_RE = /^(#{1,6})\s+(.+?)\s*$/;
|
|
1526
|
-
function parseHeadings(md) {
|
|
1527
|
-
const lines = md.split(/\r?\n/);
|
|
1528
|
-
const headings = [];
|
|
1529
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1530
|
-
const line = lines[i] ?? "";
|
|
1531
|
-
const match = line.match(HEADING_RE);
|
|
1532
|
-
if (!match) continue;
|
|
1533
|
-
const levelToken = match[1];
|
|
1534
|
-
const title = match[2];
|
|
1535
|
-
if (!levelToken || !title) continue;
|
|
1536
|
-
headings.push({
|
|
1537
|
-
level: levelToken.length,
|
|
1538
|
-
title: title.trim(),
|
|
1539
|
-
line: i + 1
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
return headings;
|
|
1543
|
-
}
|
|
1544
|
-
function extractH2Sections(md) {
|
|
1545
|
-
const lines = md.split(/\r?\n/);
|
|
1546
|
-
const headings = parseHeadings(md).filter((heading) => heading.level === 2);
|
|
1547
|
-
const sections = /* @__PURE__ */ new Map();
|
|
1548
|
-
for (let i = 0; i < headings.length; i++) {
|
|
1549
|
-
const current = headings[i];
|
|
1550
|
-
if (!current) continue;
|
|
1551
|
-
const next = headings[i + 1];
|
|
1552
|
-
const startLine = current.line + 1;
|
|
1553
|
-
const endLine = (next?.line ?? lines.length + 1) - 1;
|
|
1554
|
-
const body = startLine <= endLine ? lines.slice(startLine - 1, endLine).join("\n") : "";
|
|
1555
|
-
sections.set(current.title.trim(), {
|
|
1556
|
-
title: current.title.trim(),
|
|
1557
|
-
startLine,
|
|
1558
|
-
endLine,
|
|
1559
|
-
body
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
return sections;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
// src/core/parse/spec.ts
|
|
1566
|
-
var SPEC_ID_RE = /\bSPEC-\d{4}\b/;
|
|
1567
|
-
var BR_LINE_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[0-3])\]\s*(.+)$/;
|
|
1568
|
-
var BR_LINE_ANY_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\]\[(P[^\]]+)\]\s*(.+)$/;
|
|
1569
|
-
var BR_LINE_NO_PRIORITY_RE = /^\s*(?:[-*]\s*)?\[(BR-\d{4})\](?!\s*\[P)\s*(.*\S.*)$/;
|
|
1570
|
-
var BR_SECTION_TITLE = "\u696D\u52D9\u30EB\u30FC\u30EB";
|
|
1571
|
-
var VALID_PRIORITIES = /* @__PURE__ */ new Set(["P0", "P1", "P2", "P3"]);
|
|
1572
|
-
function parseSpec(md, file) {
|
|
1573
|
-
const headings = parseHeadings(md);
|
|
1574
|
-
const h1 = headings.find((heading) => heading.level === 1);
|
|
1575
|
-
const specId = h1?.title.match(SPEC_ID_RE)?.[0];
|
|
1576
|
-
const sections = extractH2Sections(md);
|
|
1577
|
-
const sectionNames = new Set(Array.from(sections.keys()));
|
|
1578
|
-
const brSection = sections.get(BR_SECTION_TITLE);
|
|
1579
|
-
const brLines = brSection ? brSection.body.split(/\r?\n/) : [];
|
|
1580
|
-
const startLine = brSection?.startLine ?? 1;
|
|
1581
|
-
const brs = [];
|
|
1582
|
-
const brsWithoutPriority = [];
|
|
1583
|
-
const brsWithInvalidPriority = [];
|
|
1584
|
-
for (let i = 0; i < brLines.length; i++) {
|
|
1585
|
-
const lineText = brLines[i] ?? "";
|
|
1586
|
-
const lineNumber = startLine + i;
|
|
1587
|
-
const validMatch = lineText.match(BR_LINE_RE);
|
|
1588
|
-
if (validMatch) {
|
|
1589
|
-
const id = validMatch[1];
|
|
1590
|
-
const priority = validMatch[2];
|
|
1591
|
-
const text = validMatch[3];
|
|
1592
|
-
if (!id || !priority || !text) continue;
|
|
1593
|
-
brs.push({
|
|
1594
|
-
id,
|
|
1595
|
-
priority,
|
|
1596
|
-
text: text.trim(),
|
|
1597
|
-
line: lineNumber
|
|
1598
|
-
});
|
|
1599
|
-
continue;
|
|
1600
|
-
}
|
|
1601
|
-
const anyPriorityMatch = lineText.match(BR_LINE_ANY_PRIORITY_RE);
|
|
1602
|
-
if (anyPriorityMatch) {
|
|
1603
|
-
const id = anyPriorityMatch[1];
|
|
1604
|
-
const priority = anyPriorityMatch[2];
|
|
1605
|
-
const text = anyPriorityMatch[3];
|
|
1606
|
-
if (!id || !priority || !text) continue;
|
|
1607
|
-
if (!VALID_PRIORITIES.has(priority)) {
|
|
1608
|
-
brsWithInvalidPriority.push({
|
|
1609
|
-
id,
|
|
1610
|
-
priority,
|
|
1611
|
-
text: text.trim(),
|
|
1612
|
-
line: lineNumber
|
|
1613
|
-
});
|
|
1614
|
-
}
|
|
1615
|
-
continue;
|
|
1616
|
-
}
|
|
1617
|
-
const noPriorityMatch = lineText.match(BR_LINE_NO_PRIORITY_RE);
|
|
1618
|
-
if (noPriorityMatch) {
|
|
1619
|
-
const id = noPriorityMatch[1];
|
|
1620
|
-
const text = noPriorityMatch[2];
|
|
1621
|
-
if (!id || !text) continue;
|
|
1622
|
-
brsWithoutPriority.push({
|
|
1623
|
-
id,
|
|
1624
|
-
text: text.trim(),
|
|
1625
|
-
line: lineNumber
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
const parsed = {
|
|
1630
|
-
file,
|
|
1631
|
-
sections: sectionNames,
|
|
1632
|
-
brs,
|
|
1633
|
-
brsWithoutPriority,
|
|
1634
|
-
brsWithInvalidPriority
|
|
1635
|
-
};
|
|
1636
|
-
if (specId) {
|
|
1637
|
-
parsed.specId = specId;
|
|
1638
|
-
}
|
|
1639
|
-
return parsed;
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
// src/core/validators/ids.ts
|
|
1643
1687
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
1644
1688
|
async function validateDefinedIds(root, config) {
|
|
1645
1689
|
const issues = [];
|
|
@@ -1782,7 +1826,7 @@ function validateScenarioContent(text, file) {
|
|
|
1782
1826
|
"SC",
|
|
1783
1827
|
"UI",
|
|
1784
1828
|
"API",
|
|
1785
|
-
"
|
|
1829
|
+
"DB",
|
|
1786
1830
|
"ADR"
|
|
1787
1831
|
]);
|
|
1788
1832
|
if (invalidIds.length > 0) {
|
|
@@ -1980,7 +2024,7 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
1980
2024
|
"SC",
|
|
1981
2025
|
"UI",
|
|
1982
2026
|
"API",
|
|
1983
|
-
"
|
|
2027
|
+
"DB",
|
|
1984
2028
|
"ADR"
|
|
1985
2029
|
]);
|
|
1986
2030
|
if (invalidIds.length > 0) {
|
|
@@ -2109,8 +2153,7 @@ async function validateTraceability(root, config) {
|
|
|
2109
2153
|
const brIdsInSpecs = /* @__PURE__ */ new Set();
|
|
2110
2154
|
const brIdsInScenarios = /* @__PURE__ */ new Set();
|
|
2111
2155
|
const scIdsInScenarios = /* @__PURE__ */ new Set();
|
|
2112
|
-
const
|
|
2113
|
-
const scWithContracts = /* @__PURE__ */ new Set();
|
|
2156
|
+
const specContractIds = /* @__PURE__ */ new Set();
|
|
2114
2157
|
const specToBrIds = /* @__PURE__ */ new Map();
|
|
2115
2158
|
const contractIndex = await buildContractIndex(root, config);
|
|
2116
2159
|
const contractIds = contractIndex.ids;
|
|
@@ -2128,6 +2171,64 @@ async function validateTraceability(root, config) {
|
|
|
2128
2171
|
brIds.forEach((id) => current.add(id));
|
|
2129
2172
|
specToBrIds.set(parsed.specId, current);
|
|
2130
2173
|
}
|
|
2174
|
+
const contractRefs = parsed.contractRefs;
|
|
2175
|
+
if (contractRefs.lines.length === 0) {
|
|
2176
|
+
issues.push(
|
|
2177
|
+
issue6(
|
|
2178
|
+
"QFAI-TRACE-020",
|
|
2179
|
+
"Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
2180
|
+
"error",
|
|
2181
|
+
file,
|
|
2182
|
+
"traceability.specContractRefRequired"
|
|
2183
|
+
)
|
|
2184
|
+
);
|
|
2185
|
+
} else {
|
|
2186
|
+
if (contractRefs.hasNone && contractRefs.ids.length > 0) {
|
|
2187
|
+
issues.push(
|
|
2188
|
+
issue6(
|
|
2189
|
+
"QFAI-TRACE-021",
|
|
2190
|
+
"Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
2191
|
+
"error",
|
|
2192
|
+
file,
|
|
2193
|
+
"traceability.specContractRefFormat"
|
|
2194
|
+
)
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
if (contractRefs.invalidTokens.length > 0) {
|
|
2198
|
+
issues.push(
|
|
2199
|
+
issue6(
|
|
2200
|
+
"QFAI-TRACE-021",
|
|
2201
|
+
`Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
|
|
2202
|
+
", "
|
|
2203
|
+
)}`,
|
|
2204
|
+
"error",
|
|
2205
|
+
file,
|
|
2206
|
+
"traceability.specContractRefFormat",
|
|
2207
|
+
contractRefs.invalidTokens
|
|
2208
|
+
)
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
contractRefs.ids.forEach((id) => {
|
|
2213
|
+
specContractIds.add(id);
|
|
2214
|
+
});
|
|
2215
|
+
const unknownContractIds = contractRefs.ids.filter(
|
|
2216
|
+
(id) => !contractIds.has(id)
|
|
2217
|
+
);
|
|
2218
|
+
if (unknownContractIds.length > 0) {
|
|
2219
|
+
issues.push(
|
|
2220
|
+
issue6(
|
|
2221
|
+
"QFAI-TRACE-021",
|
|
2222
|
+
`Spec \u304C\u672A\u77E5\u306E\u5951\u7D04 ID \u3092\u53C2\u7167\u3057\u3066\u3044\u307E\u3059: ${unknownContractIds.join(
|
|
2223
|
+
", "
|
|
2224
|
+
)}`,
|
|
2225
|
+
"error",
|
|
2226
|
+
file,
|
|
2227
|
+
"traceability.specContractExists",
|
|
2228
|
+
unknownContractIds
|
|
2229
|
+
)
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2131
2232
|
}
|
|
2132
2233
|
for (const file of scenarioFiles) {
|
|
2133
2234
|
const text = await readFile10(file, "utf-8");
|
|
@@ -2173,10 +2274,6 @@ async function validateTraceability(root, config) {
|
|
|
2173
2274
|
scIdsInScenarios.add(id);
|
|
2174
2275
|
scIdsInFile.add(id);
|
|
2175
2276
|
});
|
|
2176
|
-
atom.contractIds.forEach((id) => scenarioContractIds.add(id));
|
|
2177
|
-
if (atom.contractIds.length > 0) {
|
|
2178
|
-
scTags.forEach((id) => scWithContracts.add(id));
|
|
2179
|
-
}
|
|
2180
2277
|
const unknownSpecIds = specTags.filter((id) => !specIds.has(id));
|
|
2181
2278
|
if (unknownSpecIds.length > 0) {
|
|
2182
2279
|
issues.push(
|
|
@@ -2295,25 +2392,6 @@ async function validateTraceability(root, config) {
|
|
|
2295
2392
|
);
|
|
2296
2393
|
}
|
|
2297
2394
|
}
|
|
2298
|
-
if (config.validation.traceability.scMustTouchContracts && scIdsInScenarios.size > 0) {
|
|
2299
|
-
const scWithoutContracts = Array.from(scIdsInScenarios).filter(
|
|
2300
|
-
(id) => !scWithContracts.has(id)
|
|
2301
|
-
);
|
|
2302
|
-
if (scWithoutContracts.length > 0) {
|
|
2303
|
-
issues.push(
|
|
2304
|
-
issue6(
|
|
2305
|
-
"QFAI_TRACE_SC_NO_CONTRACT",
|
|
2306
|
-
`SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
|
|
2307
|
-
", "
|
|
2308
|
-
)}`,
|
|
2309
|
-
"error",
|
|
2310
|
-
specsRoot,
|
|
2311
|
-
"traceability.scMustTouchContracts",
|
|
2312
|
-
scWithoutContracts
|
|
2313
|
-
)
|
|
2314
|
-
);
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
2395
|
const scRefsResult = await collectScTestReferences(
|
|
2318
2396
|
root,
|
|
2319
2397
|
config.validation.traceability.testFileGlobs,
|
|
@@ -2377,16 +2455,16 @@ async function validateTraceability(root, config) {
|
|
|
2377
2455
|
if (!config.validation.traceability.allowOrphanContracts) {
|
|
2378
2456
|
if (contractIds.size > 0) {
|
|
2379
2457
|
const orphanContracts = Array.from(contractIds).filter(
|
|
2380
|
-
(id) => !
|
|
2458
|
+
(id) => !specContractIds.has(id)
|
|
2381
2459
|
);
|
|
2382
2460
|
if (orphanContracts.length > 0) {
|
|
2383
2461
|
issues.push(
|
|
2384
2462
|
issue6(
|
|
2385
|
-
"
|
|
2386
|
-
`\u5951\u7D04\u304C
|
|
2463
|
+
"QFAI-TRACE-022",
|
|
2464
|
+
`\u5951\u7D04\u304C Spec \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
|
|
2387
2465
|
"error",
|
|
2388
2466
|
specsRoot,
|
|
2389
|
-
"traceability.
|
|
2467
|
+
"traceability.contractCoverage",
|
|
2390
2468
|
orphanContracts
|
|
2391
2469
|
)
|
|
2392
2470
|
);
|
|
@@ -2507,7 +2585,7 @@ function countIssues(issues) {
|
|
|
2507
2585
|
}
|
|
2508
2586
|
|
|
2509
2587
|
// src/core/report.ts
|
|
2510
|
-
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "
|
|
2588
|
+
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
2511
2589
|
async function createReportData(root, validation, configResult) {
|
|
2512
2590
|
const resolved = configResult ?? await loadConfig(root);
|
|
2513
2591
|
const config = resolved.config;
|
|
@@ -2526,6 +2604,23 @@ async function createReportData(root, validation, configResult) {
|
|
|
2526
2604
|
ui: uiFiles,
|
|
2527
2605
|
db: dbFiles
|
|
2528
2606
|
} = await collectContractFiles(uiRoot, apiRoot, dbRoot);
|
|
2607
|
+
const contractIndex = await buildContractIndex(root, config);
|
|
2608
|
+
const specContractRefs = await collectSpecContractRefs(specFiles);
|
|
2609
|
+
const contractIdList = Array.from(contractIndex.ids);
|
|
2610
|
+
const referencedContracts = /* @__PURE__ */ new Set();
|
|
2611
|
+
for (const ids of specContractRefs.specToContractIds.values()) {
|
|
2612
|
+
ids.forEach((id) => referencedContracts.add(id));
|
|
2613
|
+
}
|
|
2614
|
+
const referencedContractCount = contractIdList.filter(
|
|
2615
|
+
(id) => referencedContracts.has(id)
|
|
2616
|
+
).length;
|
|
2617
|
+
const orphanContractCount = contractIdList.filter(
|
|
2618
|
+
(id) => !referencedContracts.has(id)
|
|
2619
|
+
).length;
|
|
2620
|
+
const contractIdToSpecsRecord = mapToSortedRecord(specContractRefs.idToSpecs);
|
|
2621
|
+
const specToContractIdsRecord = mapToSortedRecord(
|
|
2622
|
+
specContractRefs.specToContractIds
|
|
2623
|
+
);
|
|
2529
2624
|
const idsByPrefix = await collectIds([
|
|
2530
2625
|
...specFiles,
|
|
2531
2626
|
...scenarioFiles,
|
|
@@ -2576,14 +2671,24 @@ async function createReportData(root, validation, configResult) {
|
|
|
2576
2671
|
sc: idsByPrefix.SC,
|
|
2577
2672
|
ui: idsByPrefix.UI,
|
|
2578
2673
|
api: idsByPrefix.API,
|
|
2579
|
-
|
|
2674
|
+
db: idsByPrefix.DB
|
|
2580
2675
|
},
|
|
2581
2676
|
traceability: {
|
|
2582
2677
|
upstreamIdsFound: upstreamIds.size,
|
|
2583
2678
|
referencedInCodeOrTests: traceability,
|
|
2584
2679
|
sc: scCoverage,
|
|
2585
2680
|
scSources: scSourceRecord,
|
|
2586
|
-
testFiles
|
|
2681
|
+
testFiles,
|
|
2682
|
+
contracts: {
|
|
2683
|
+
total: contractIdList.length,
|
|
2684
|
+
referenced: referencedContractCount,
|
|
2685
|
+
orphan: orphanContractCount,
|
|
2686
|
+
idToSpecs: contractIdToSpecsRecord
|
|
2687
|
+
},
|
|
2688
|
+
specs: {
|
|
2689
|
+
contractRefMissing: specContractRefs.missingRefSpecs.size,
|
|
2690
|
+
specToContractIds: specToContractIdsRecord
|
|
2691
|
+
}
|
|
2587
2692
|
},
|
|
2588
2693
|
issues: resolvedValidation.issues
|
|
2589
2694
|
};
|
|
@@ -2612,7 +2717,7 @@ function formatReportMarkdown(data) {
|
|
|
2612
2717
|
lines.push(formatIdLine("SC", data.ids.sc));
|
|
2613
2718
|
lines.push(formatIdLine("UI", data.ids.ui));
|
|
2614
2719
|
lines.push(formatIdLine("API", data.ids.api));
|
|
2615
|
-
lines.push(formatIdLine("
|
|
2720
|
+
lines.push(formatIdLine("DB", data.ids.db));
|
|
2616
2721
|
lines.push("");
|
|
2617
2722
|
lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3");
|
|
2618
2723
|
lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
|
|
@@ -2620,6 +2725,50 @@ function formatReportMarkdown(data) {
|
|
|
2620
2725
|
`- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
|
|
2621
2726
|
);
|
|
2622
2727
|
lines.push("");
|
|
2728
|
+
lines.push("## \u5951\u7D04\u30AB\u30D0\u30EC\u30C3\u30B8");
|
|
2729
|
+
lines.push(`- total: ${data.traceability.contracts.total}`);
|
|
2730
|
+
lines.push(`- referenced: ${data.traceability.contracts.referenced}`);
|
|
2731
|
+
lines.push(`- orphan: ${data.traceability.contracts.orphan}`);
|
|
2732
|
+
lines.push(
|
|
2733
|
+
`- specContractRefMissing: ${data.traceability.specs.contractRefMissing}`
|
|
2734
|
+
);
|
|
2735
|
+
lines.push("");
|
|
2736
|
+
lines.push("## \u5951\u7D04\u2192Spec");
|
|
2737
|
+
const contractToSpecs = data.traceability.contracts.idToSpecs;
|
|
2738
|
+
const contractIds = Object.keys(contractToSpecs).sort(
|
|
2739
|
+
(a, b) => a.localeCompare(b)
|
|
2740
|
+
);
|
|
2741
|
+
if (contractIds.length === 0) {
|
|
2742
|
+
lines.push("- (none)");
|
|
2743
|
+
} else {
|
|
2744
|
+
for (const contractId of contractIds) {
|
|
2745
|
+
const specs = contractToSpecs[contractId] ?? [];
|
|
2746
|
+
if (specs.length === 0) {
|
|
2747
|
+
lines.push(`- ${contractId}: (none)`);
|
|
2748
|
+
} else {
|
|
2749
|
+
lines.push(`- ${contractId}: ${specs.join(", ")}`);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
lines.push("");
|
|
2754
|
+
lines.push("## Spec\u2192\u5951\u7D04");
|
|
2755
|
+
const specToContracts = data.traceability.specs.specToContractIds;
|
|
2756
|
+
const specIds = Object.keys(specToContracts).sort(
|
|
2757
|
+
(a, b) => a.localeCompare(b)
|
|
2758
|
+
);
|
|
2759
|
+
if (specIds.length === 0) {
|
|
2760
|
+
lines.push("- (none)");
|
|
2761
|
+
} else {
|
|
2762
|
+
for (const specId of specIds) {
|
|
2763
|
+
const contractIds2 = specToContracts[specId] ?? [];
|
|
2764
|
+
if (contractIds2.length === 0) {
|
|
2765
|
+
lines.push(`- ${specId}: (none)`);
|
|
2766
|
+
} else {
|
|
2767
|
+
lines.push(`- ${specId}: ${contractIds2.join(", ")}`);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
lines.push("");
|
|
2623
2772
|
lines.push("## SC\u30AB\u30D0\u30EC\u30C3\u30B8");
|
|
2624
2773
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2625
2774
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
@@ -2693,7 +2842,7 @@ function formatReportMarkdown(data) {
|
|
|
2693
2842
|
lines.push("");
|
|
2694
2843
|
lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
|
|
2695
2844
|
const traceIssues = data.issues.filter(
|
|
2696
|
-
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-")
|
|
2845
|
+
(item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-")
|
|
2697
2846
|
);
|
|
2698
2847
|
if (traceIssues.length === 0) {
|
|
2699
2848
|
lines.push("- (none)");
|
|
@@ -2723,6 +2872,33 @@ function formatReportMarkdown(data) {
|
|
|
2723
2872
|
function formatReportJson(data) {
|
|
2724
2873
|
return JSON.stringify(data, null, 2);
|
|
2725
2874
|
}
|
|
2875
|
+
async function collectSpecContractRefs(specFiles) {
|
|
2876
|
+
const specToContractIds = /* @__PURE__ */ new Map();
|
|
2877
|
+
const idToSpecs = /* @__PURE__ */ new Map();
|
|
2878
|
+
const missingRefSpecs = /* @__PURE__ */ new Set();
|
|
2879
|
+
for (const file of specFiles) {
|
|
2880
|
+
const text = await readFile11(file, "utf-8");
|
|
2881
|
+
const parsed = parseSpec(text, file);
|
|
2882
|
+
const specKey = parsed.specId ?? file;
|
|
2883
|
+
const refs = parsed.contractRefs;
|
|
2884
|
+
if (refs.lines.length === 0) {
|
|
2885
|
+
missingRefSpecs.add(specKey);
|
|
2886
|
+
}
|
|
2887
|
+
const currentContracts = specToContractIds.get(specKey) ?? /* @__PURE__ */ new Set();
|
|
2888
|
+
for (const id of refs.ids) {
|
|
2889
|
+
currentContracts.add(id);
|
|
2890
|
+
const specs = idToSpecs.get(id) ?? /* @__PURE__ */ new Set();
|
|
2891
|
+
specs.add(specKey);
|
|
2892
|
+
idToSpecs.set(id, specs);
|
|
2893
|
+
}
|
|
2894
|
+
specToContractIds.set(specKey, currentContracts);
|
|
2895
|
+
}
|
|
2896
|
+
return {
|
|
2897
|
+
specToContractIds,
|
|
2898
|
+
idToSpecs,
|
|
2899
|
+
missingRefSpecs
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2726
2902
|
async function collectIds(files) {
|
|
2727
2903
|
const result = {
|
|
2728
2904
|
SPEC: /* @__PURE__ */ new Set(),
|
|
@@ -2730,7 +2906,7 @@ async function collectIds(files) {
|
|
|
2730
2906
|
SC: /* @__PURE__ */ new Set(),
|
|
2731
2907
|
UI: /* @__PURE__ */ new Set(),
|
|
2732
2908
|
API: /* @__PURE__ */ new Set(),
|
|
2733
|
-
|
|
2909
|
+
DB: /* @__PURE__ */ new Set()
|
|
2734
2910
|
};
|
|
2735
2911
|
for (const file of files) {
|
|
2736
2912
|
const text = await readFile11(file, "utf-8");
|
|
@@ -2745,7 +2921,7 @@ async function collectIds(files) {
|
|
|
2745
2921
|
SC: toSortedArray2(result.SC),
|
|
2746
2922
|
UI: toSortedArray2(result.UI),
|
|
2747
2923
|
API: toSortedArray2(result.API),
|
|
2748
|
-
|
|
2924
|
+
DB: toSortedArray2(result.DB)
|
|
2749
2925
|
};
|
|
2750
2926
|
}
|
|
2751
2927
|
async function collectUpstreamIds(files) {
|