qfai 0.7.2 → 0.8.1
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/LICENSE +21 -0
- package/README.md +1 -1
- package/assets/init/.qfai/README.md +4 -1
- package/assets/init/.qfai/promptpack/steering/compatibility-vs-change.md +34 -0
- package/assets/init/.qfai/prompts.local/README.md +5 -0
- package/dist/cli/index.cjs +598 -196
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +586 -184
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +408 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.mjs +408 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -2
package/dist/index.cjs
CHANGED
|
@@ -425,6 +425,7 @@ function configIssue(file, message) {
|
|
|
425
425
|
return {
|
|
426
426
|
code: "QFAI_CONFIG_INVALID",
|
|
427
427
|
severity: "error",
|
|
428
|
+
category: "compatibility",
|
|
428
429
|
message,
|
|
429
430
|
file,
|
|
430
431
|
rule: "config.invalid"
|
|
@@ -508,8 +509,8 @@ function isValidId(value, prefix) {
|
|
|
508
509
|
}
|
|
509
510
|
|
|
510
511
|
// src/core/report.ts
|
|
511
|
-
var
|
|
512
|
-
var
|
|
512
|
+
var import_promises15 = require("fs/promises");
|
|
513
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
513
514
|
|
|
514
515
|
// src/core/contractIndex.ts
|
|
515
516
|
var import_promises5 = require("fs/promises");
|
|
@@ -1232,8 +1233,8 @@ var import_promises7 = require("fs/promises");
|
|
|
1232
1233
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
1233
1234
|
var import_node_url = require("url");
|
|
1234
1235
|
async function resolveToolVersion() {
|
|
1235
|
-
if ("0.
|
|
1236
|
-
return "0.
|
|
1236
|
+
if ("0.8.1".length > 0) {
|
|
1237
|
+
return "0.8.1";
|
|
1237
1238
|
}
|
|
1238
1239
|
try {
|
|
1239
1240
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1544,12 +1545,16 @@ function formatError4(error) {
|
|
|
1544
1545
|
}
|
|
1545
1546
|
return String(error);
|
|
1546
1547
|
}
|
|
1547
|
-
function issue(code, message, severity, file, rule, refs) {
|
|
1548
|
+
function issue(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
1548
1549
|
const issue7 = {
|
|
1549
1550
|
code,
|
|
1550
1551
|
severity,
|
|
1552
|
+
category,
|
|
1551
1553
|
message
|
|
1552
1554
|
};
|
|
1555
|
+
if (suggested_action) {
|
|
1556
|
+
issue7.suggested_action = suggested_action;
|
|
1557
|
+
}
|
|
1553
1558
|
if (file) {
|
|
1554
1559
|
issue7.file = file;
|
|
1555
1560
|
}
|
|
@@ -1604,7 +1609,7 @@ async function validateDeltas(root, config) {
|
|
|
1604
1609
|
issues.push(
|
|
1605
1610
|
issue2(
|
|
1606
1611
|
"QFAI-DELTA-002",
|
|
1607
|
-
"delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
1612
|
+
"delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002`## \u5909\u66F4\u533A\u5206` \u3068\u30C1\u30A7\u30C3\u30AF\u30DC\u30C3\u30AF\u30B9\uFF08Compatibility / Change/Improvement\uFF09\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1608
1613
|
"error",
|
|
1609
1614
|
deltaPath,
|
|
1610
1615
|
"delta.section"
|
|
@@ -1634,12 +1639,16 @@ function isMissingFileError2(error) {
|
|
|
1634
1639
|
}
|
|
1635
1640
|
return error.code === "ENOENT";
|
|
1636
1641
|
}
|
|
1637
|
-
function issue2(code, message, severity, file, rule, refs) {
|
|
1642
|
+
function issue2(code, message, severity, file, rule, refs, category = "change", suggested_action) {
|
|
1638
1643
|
const issue7 = {
|
|
1639
1644
|
code,
|
|
1640
1645
|
severity,
|
|
1646
|
+
category,
|
|
1641
1647
|
message
|
|
1642
1648
|
};
|
|
1649
|
+
if (suggested_action) {
|
|
1650
|
+
issue7.suggested_action = suggested_action;
|
|
1651
|
+
}
|
|
1643
1652
|
if (file) {
|
|
1644
1653
|
issue7.file = file;
|
|
1645
1654
|
}
|
|
@@ -1724,12 +1733,16 @@ function formatFileList(files, root) {
|
|
|
1724
1733
|
return relative.length > 0 ? relative : file;
|
|
1725
1734
|
}).join(", ");
|
|
1726
1735
|
}
|
|
1727
|
-
function issue3(code, message, severity, file, rule, refs) {
|
|
1736
|
+
function issue3(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
1728
1737
|
const issue7 = {
|
|
1729
1738
|
code,
|
|
1730
1739
|
severity,
|
|
1740
|
+
category,
|
|
1731
1741
|
message
|
|
1732
1742
|
};
|
|
1743
|
+
if (suggested_action) {
|
|
1744
|
+
issue7.suggested_action = suggested_action;
|
|
1745
|
+
}
|
|
1733
1746
|
if (file) {
|
|
1734
1747
|
issue7.file = file;
|
|
1735
1748
|
}
|
|
@@ -1742,8 +1755,164 @@ function issue3(code, message, severity, file, rule, refs) {
|
|
|
1742
1755
|
return issue7;
|
|
1743
1756
|
}
|
|
1744
1757
|
|
|
1745
|
-
// src/core/
|
|
1758
|
+
// src/core/promptsIntegrity.ts
|
|
1746
1759
|
var import_promises11 = require("fs/promises");
|
|
1760
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1761
|
+
|
|
1762
|
+
// src/shared/assets.ts
|
|
1763
|
+
var import_node_fs = require("fs");
|
|
1764
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1765
|
+
var import_node_url2 = require("url");
|
|
1766
|
+
function getInitAssetsDir() {
|
|
1767
|
+
const base = __filename;
|
|
1768
|
+
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
1769
|
+
const baseDir = import_node_path12.default.dirname(basePath);
|
|
1770
|
+
const candidates = [
|
|
1771
|
+
import_node_path12.default.resolve(baseDir, "../../../assets/init"),
|
|
1772
|
+
import_node_path12.default.resolve(baseDir, "../../assets/init")
|
|
1773
|
+
];
|
|
1774
|
+
for (const candidate of candidates) {
|
|
1775
|
+
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
1776
|
+
return candidate;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
throw new Error(
|
|
1780
|
+
[
|
|
1781
|
+
"init \u7528\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Template assets not found.",
|
|
1782
|
+
"\u78BA\u8A8D\u3057\u305F\u30D1\u30B9 / Checked paths:",
|
|
1783
|
+
...candidates.map((candidate) => `- ${candidate}`)
|
|
1784
|
+
].join("\n")
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// src/core/promptsIntegrity.ts
|
|
1789
|
+
async function diffProjectPromptsAgainstInitAssets(root) {
|
|
1790
|
+
const promptsDir = import_node_path13.default.resolve(root, ".qfai", "prompts");
|
|
1791
|
+
let templateDir;
|
|
1792
|
+
try {
|
|
1793
|
+
templateDir = import_node_path13.default.join(getInitAssetsDir(), ".qfai", "prompts");
|
|
1794
|
+
} catch {
|
|
1795
|
+
return {
|
|
1796
|
+
status: "skipped_missing_assets",
|
|
1797
|
+
promptsDir,
|
|
1798
|
+
templateDir: "",
|
|
1799
|
+
missing: [],
|
|
1800
|
+
extra: [],
|
|
1801
|
+
changed: []
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
const projectFiles = await collectFiles(promptsDir);
|
|
1805
|
+
if (projectFiles.length === 0) {
|
|
1806
|
+
return {
|
|
1807
|
+
status: "skipped_missing_prompts",
|
|
1808
|
+
promptsDir,
|
|
1809
|
+
templateDir,
|
|
1810
|
+
missing: [],
|
|
1811
|
+
extra: [],
|
|
1812
|
+
changed: []
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
const templateFiles = await collectFiles(templateDir);
|
|
1816
|
+
const templateByRel = /* @__PURE__ */ new Map();
|
|
1817
|
+
for (const abs of templateFiles) {
|
|
1818
|
+
templateByRel.set(toRel(templateDir, abs), abs);
|
|
1819
|
+
}
|
|
1820
|
+
const projectByRel = /* @__PURE__ */ new Map();
|
|
1821
|
+
for (const abs of projectFiles) {
|
|
1822
|
+
projectByRel.set(toRel(promptsDir, abs), abs);
|
|
1823
|
+
}
|
|
1824
|
+
const missing = [];
|
|
1825
|
+
const extra = [];
|
|
1826
|
+
const changed = [];
|
|
1827
|
+
for (const rel of templateByRel.keys()) {
|
|
1828
|
+
if (!projectByRel.has(rel)) {
|
|
1829
|
+
missing.push(rel);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
for (const rel of projectByRel.keys()) {
|
|
1833
|
+
if (!templateByRel.has(rel)) {
|
|
1834
|
+
extra.push(rel);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const common = intersectKeys(templateByRel, projectByRel);
|
|
1838
|
+
for (const rel of common) {
|
|
1839
|
+
const templateAbs = templateByRel.get(rel);
|
|
1840
|
+
const projectAbs = projectByRel.get(rel);
|
|
1841
|
+
if (!templateAbs || !projectAbs) {
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
try {
|
|
1845
|
+
const [a, b] = await Promise.all([
|
|
1846
|
+
(0, import_promises11.readFile)(templateAbs, "utf-8"),
|
|
1847
|
+
(0, import_promises11.readFile)(projectAbs, "utf-8")
|
|
1848
|
+
]);
|
|
1849
|
+
if (normalizeNewlines(a) !== normalizeNewlines(b)) {
|
|
1850
|
+
changed.push(rel);
|
|
1851
|
+
}
|
|
1852
|
+
} catch {
|
|
1853
|
+
changed.push(rel);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const status = missing.length > 0 || extra.length > 0 || changed.length > 0 ? "modified" : "ok";
|
|
1857
|
+
return {
|
|
1858
|
+
status,
|
|
1859
|
+
promptsDir,
|
|
1860
|
+
templateDir,
|
|
1861
|
+
missing: missing.sort(),
|
|
1862
|
+
extra: extra.sort(),
|
|
1863
|
+
changed: changed.sort()
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
function normalizeNewlines(text) {
|
|
1867
|
+
return text.replace(/\r\n/g, "\n");
|
|
1868
|
+
}
|
|
1869
|
+
function toRel(base, abs) {
|
|
1870
|
+
const rel = import_node_path13.default.relative(base, abs);
|
|
1871
|
+
return rel.replace(/[\\/]+/g, "/");
|
|
1872
|
+
}
|
|
1873
|
+
function intersectKeys(a, b) {
|
|
1874
|
+
const out = [];
|
|
1875
|
+
for (const key of a.keys()) {
|
|
1876
|
+
if (b.has(key)) {
|
|
1877
|
+
out.push(key);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return out;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/core/validators/promptsIntegrity.ts
|
|
1884
|
+
async function validatePromptsIntegrity(root) {
|
|
1885
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
1886
|
+
if (diff.status !== "modified") {
|
|
1887
|
+
return [];
|
|
1888
|
+
}
|
|
1889
|
+
const total = diff.missing.length + diff.extra.length + diff.changed.length;
|
|
1890
|
+
const hints = [
|
|
1891
|
+
diff.changed.length > 0 ? `\u5909\u66F4: ${diff.changed.length}` : null,
|
|
1892
|
+
diff.missing.length > 0 ? `\u524A\u9664: ${diff.missing.length}` : null,
|
|
1893
|
+
diff.extra.length > 0 ? `\u8FFD\u52A0: ${diff.extra.length}` : null
|
|
1894
|
+
].filter(Boolean).join(" / ");
|
|
1895
|
+
const sample = [...diff.changed, ...diff.missing, ...diff.extra].slice(0, 10);
|
|
1896
|
+
const sampleText = sample.length > 0 ? ` \u4F8B: ${sample.join(", ")}` : "";
|
|
1897
|
+
return [
|
|
1898
|
+
{
|
|
1899
|
+
code: "QFAI-PROMPTS-001",
|
|
1900
|
+
severity: "error",
|
|
1901
|
+
category: "change",
|
|
1902
|
+
message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
|
|
1903
|
+
suggested_action: [
|
|
1904
|
+
"prompts \u306E\u76F4\u7DE8\u96C6\u306F\u975E\u63A8\u5968\u3067\u3059\uFF08\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8/\u518D init \u3067\u4E0A\u66F8\u304D\u3055\u308C\u5F97\u307E\u3059\uFF09\u3002",
|
|
1905
|
+
"\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
1906
|
+
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
1907
|
+
"- \u6A19\u6E96\u72B6\u614B\u3078\u623B\u3059\u5834\u5408: 'qfai init --force' \u3092\u5B9F\u884C\uFF08prompts \u306E\u307F\u4E0A\u66F8\u304D\u3001prompts.local \u306F\u4FDD\u8B77\uFF09"
|
|
1908
|
+
].join("\n"),
|
|
1909
|
+
rule: "prompts.integrity"
|
|
1910
|
+
}
|
|
1911
|
+
];
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// src/core/validators/scenario.ts
|
|
1915
|
+
var import_promises12 = require("fs/promises");
|
|
1747
1916
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
1748
1917
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
1749
1918
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -1769,7 +1938,7 @@ async function validateScenarios(root, config) {
|
|
|
1769
1938
|
for (const entry of entries) {
|
|
1770
1939
|
let text;
|
|
1771
1940
|
try {
|
|
1772
|
-
text = await (0,
|
|
1941
|
+
text = await (0, import_promises12.readFile)(entry.scenarioPath, "utf-8");
|
|
1773
1942
|
} catch (error) {
|
|
1774
1943
|
if (isMissingFileError3(error)) {
|
|
1775
1944
|
issues.push(
|
|
@@ -1914,12 +2083,16 @@ function validateScenarioContent(text, file) {
|
|
|
1914
2083
|
}
|
|
1915
2084
|
return issues;
|
|
1916
2085
|
}
|
|
1917
|
-
function issue4(code, message, severity, file, rule, refs) {
|
|
2086
|
+
function issue4(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
1918
2087
|
const issue7 = {
|
|
1919
2088
|
code,
|
|
1920
2089
|
severity,
|
|
2090
|
+
category,
|
|
1921
2091
|
message
|
|
1922
2092
|
};
|
|
2093
|
+
if (suggested_action) {
|
|
2094
|
+
issue7.suggested_action = suggested_action;
|
|
2095
|
+
}
|
|
1923
2096
|
if (file) {
|
|
1924
2097
|
issue7.file = file;
|
|
1925
2098
|
}
|
|
@@ -1939,7 +2112,7 @@ function isMissingFileError3(error) {
|
|
|
1939
2112
|
}
|
|
1940
2113
|
|
|
1941
2114
|
// src/core/validators/spec.ts
|
|
1942
|
-
var
|
|
2115
|
+
var import_promises13 = require("fs/promises");
|
|
1943
2116
|
async function validateSpecs(root, config) {
|
|
1944
2117
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1945
2118
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1960,7 +2133,7 @@ async function validateSpecs(root, config) {
|
|
|
1960
2133
|
for (const entry of entries) {
|
|
1961
2134
|
let text;
|
|
1962
2135
|
try {
|
|
1963
|
-
text = await (0,
|
|
2136
|
+
text = await (0, import_promises13.readFile)(entry.specPath, "utf-8");
|
|
1964
2137
|
} catch (error) {
|
|
1965
2138
|
if (isMissingFileError4(error)) {
|
|
1966
2139
|
issues.push(
|
|
@@ -2084,12 +2257,16 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
2084
2257
|
}
|
|
2085
2258
|
return issues;
|
|
2086
2259
|
}
|
|
2087
|
-
function issue5(code, message, severity, file, rule, refs) {
|
|
2260
|
+
function issue5(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2088
2261
|
const issue7 = {
|
|
2089
2262
|
code,
|
|
2090
2263
|
severity,
|
|
2264
|
+
category,
|
|
2091
2265
|
message
|
|
2092
2266
|
};
|
|
2267
|
+
if (suggested_action) {
|
|
2268
|
+
issue7.suggested_action = suggested_action;
|
|
2269
|
+
}
|
|
2093
2270
|
if (file) {
|
|
2094
2271
|
issue7.file = file;
|
|
2095
2272
|
}
|
|
@@ -2109,7 +2286,7 @@ function isMissingFileError4(error) {
|
|
|
2109
2286
|
}
|
|
2110
2287
|
|
|
2111
2288
|
// src/core/validators/traceability.ts
|
|
2112
|
-
var
|
|
2289
|
+
var import_promises14 = require("fs/promises");
|
|
2113
2290
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2114
2291
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2115
2292
|
async function validateTraceability(root, config) {
|
|
@@ -2129,7 +2306,7 @@ async function validateTraceability(root, config) {
|
|
|
2129
2306
|
const contractIndex = await buildContractIndex(root, config);
|
|
2130
2307
|
const contractIds = contractIndex.ids;
|
|
2131
2308
|
for (const file of specFiles) {
|
|
2132
|
-
const text = await (0,
|
|
2309
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2133
2310
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2134
2311
|
const parsed = parseSpec(text, file);
|
|
2135
2312
|
if (parsed.specId) {
|
|
@@ -2147,7 +2324,7 @@ async function validateTraceability(root, config) {
|
|
|
2147
2324
|
issues.push(
|
|
2148
2325
|
issue6(
|
|
2149
2326
|
"QFAI-TRACE-020",
|
|
2150
|
-
"Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
2327
|
+
"Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002\u4F8B: `QFAI-CONTRACT-REF: none` \u307E\u305F\u306F `QFAI-CONTRACT-REF: UI-0001`",
|
|
2151
2328
|
"error",
|
|
2152
2329
|
file,
|
|
2153
2330
|
"traceability.specContractRefRequired"
|
|
@@ -2158,7 +2335,7 @@ async function validateTraceability(root, config) {
|
|
|
2158
2335
|
issues.push(
|
|
2159
2336
|
issue6(
|
|
2160
2337
|
"QFAI-TRACE-023",
|
|
2161
|
-
"Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
2338
|
+
"Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002none \u304B \u5951\u7D04ID \u306E\u3069\u3061\u3089\u304B\u4E00\u65B9\u3060\u3051\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
2162
2339
|
"error",
|
|
2163
2340
|
file,
|
|
2164
2341
|
"traceability.specContractRefFormat"
|
|
@@ -2171,7 +2348,7 @@ async function validateTraceability(root, config) {
|
|
|
2171
2348
|
"QFAI-TRACE-021",
|
|
2172
2349
|
`Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
|
|
2173
2350
|
", "
|
|
2174
|
-
)}`,
|
|
2351
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2175
2352
|
"error",
|
|
2176
2353
|
file,
|
|
2177
2354
|
"traceability.specContractRefFormat",
|
|
@@ -2202,7 +2379,7 @@ async function validateTraceability(root, config) {
|
|
|
2202
2379
|
}
|
|
2203
2380
|
}
|
|
2204
2381
|
for (const file of scenarioFiles) {
|
|
2205
|
-
const text = await (0,
|
|
2382
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2206
2383
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2207
2384
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
2208
2385
|
allowCommentPrefix: true
|
|
@@ -2211,7 +2388,7 @@ async function validateTraceability(root, config) {
|
|
|
2211
2388
|
issues.push(
|
|
2212
2389
|
issue6(
|
|
2213
2390
|
"QFAI-TRACE-031",
|
|
2214
|
-
"Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
2391
|
+
"Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002\u4F8B: `# QFAI-CONTRACT-REF: none` \u307E\u305F\u306F `# QFAI-CONTRACT-REF: UI-0001`",
|
|
2215
2392
|
"error",
|
|
2216
2393
|
file,
|
|
2217
2394
|
"traceability.scenarioContractRefRequired"
|
|
@@ -2222,7 +2399,7 @@ async function validateTraceability(root, config) {
|
|
|
2222
2399
|
issues.push(
|
|
2223
2400
|
issue6(
|
|
2224
2401
|
"QFAI-TRACE-033",
|
|
2225
|
-
"Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
2402
|
+
"Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002none \u304B \u5951\u7D04ID \u306E\u3069\u3061\u3089\u304B\u4E00\u65B9\u3060\u3051\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
2226
2403
|
"error",
|
|
2227
2404
|
file,
|
|
2228
2405
|
"traceability.scenarioContractRefFormat"
|
|
@@ -2235,7 +2412,7 @@ async function validateTraceability(root, config) {
|
|
|
2235
2412
|
"QFAI-TRACE-032",
|
|
2236
2413
|
`Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
|
|
2237
2414
|
", "
|
|
2238
|
-
)}`,
|
|
2415
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2239
2416
|
"error",
|
|
2240
2417
|
file,
|
|
2241
2418
|
"traceability.scenarioContractRefFormat",
|
|
@@ -2524,7 +2701,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2524
2701
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
2525
2702
|
let found = false;
|
|
2526
2703
|
for (const file of targetFiles) {
|
|
2527
|
-
const text = await (0,
|
|
2704
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2528
2705
|
if (pattern.test(text)) {
|
|
2529
2706
|
found = true;
|
|
2530
2707
|
break;
|
|
@@ -2547,12 +2724,16 @@ function buildIdPattern(ids) {
|
|
|
2547
2724
|
const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
2548
2725
|
return new RegExp(`\\b(${escaped.join("|")})\\b`);
|
|
2549
2726
|
}
|
|
2550
|
-
function issue6(code, message, severity, file, rule, refs) {
|
|
2727
|
+
function issue6(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2551
2728
|
const issue7 = {
|
|
2552
2729
|
code,
|
|
2553
2730
|
severity,
|
|
2731
|
+
category,
|
|
2554
2732
|
message
|
|
2555
2733
|
};
|
|
2734
|
+
if (suggested_action) {
|
|
2735
|
+
issue7.suggested_action = suggested_action;
|
|
2736
|
+
}
|
|
2556
2737
|
if (file) {
|
|
2557
2738
|
issue7.file = file;
|
|
2558
2739
|
}
|
|
@@ -2571,6 +2752,7 @@ async function validateProject(root, configResult) {
|
|
|
2571
2752
|
const { config, issues: configIssues } = resolved;
|
|
2572
2753
|
const issues = [
|
|
2573
2754
|
...configIssues,
|
|
2755
|
+
...await validatePromptsIntegrity(root),
|
|
2574
2756
|
...await validateSpecs(root, config),
|
|
2575
2757
|
...await validateDeltas(root, config),
|
|
2576
2758
|
...await validateScenarios(root, config),
|
|
@@ -2611,15 +2793,15 @@ function countIssues(issues) {
|
|
|
2611
2793
|
// src/core/report.ts
|
|
2612
2794
|
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
2613
2795
|
async function createReportData(root, validation, configResult) {
|
|
2614
|
-
const resolvedRoot =
|
|
2796
|
+
const resolvedRoot = import_node_path14.default.resolve(root);
|
|
2615
2797
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
2616
2798
|
const config = resolved.config;
|
|
2617
2799
|
const configPath = resolved.configPath;
|
|
2618
2800
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
2619
2801
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
2620
|
-
const apiRoot =
|
|
2621
|
-
const uiRoot =
|
|
2622
|
-
const dbRoot =
|
|
2802
|
+
const apiRoot = import_node_path14.default.join(contractsRoot, "api");
|
|
2803
|
+
const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
|
|
2804
|
+
const dbRoot = import_node_path14.default.join(contractsRoot, "db");
|
|
2623
2805
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
2624
2806
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
2625
2807
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2733,7 +2915,39 @@ function formatReportMarkdown(data) {
|
|
|
2733
2915
|
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
2734
2916
|
lines.push(`- \u7248: ${data.version}`);
|
|
2735
2917
|
lines.push("");
|
|
2736
|
-
|
|
2918
|
+
const severityOrder = {
|
|
2919
|
+
error: 0,
|
|
2920
|
+
warning: 1,
|
|
2921
|
+
info: 2
|
|
2922
|
+
};
|
|
2923
|
+
const categoryOrder = {
|
|
2924
|
+
compatibility: 0,
|
|
2925
|
+
change: 1
|
|
2926
|
+
};
|
|
2927
|
+
const issuesByCategory = {
|
|
2928
|
+
compatibility: [],
|
|
2929
|
+
change: []
|
|
2930
|
+
};
|
|
2931
|
+
for (const issue7 of data.issues) {
|
|
2932
|
+
const cat = issue7.category;
|
|
2933
|
+
if (cat === "change") {
|
|
2934
|
+
issuesByCategory.change.push(issue7);
|
|
2935
|
+
} else {
|
|
2936
|
+
issuesByCategory.compatibility.push(issue7);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
const countIssuesBySeverity = (issues) => issues.reduce(
|
|
2940
|
+
(acc, i) => {
|
|
2941
|
+
acc[i.severity] += 1;
|
|
2942
|
+
return acc;
|
|
2943
|
+
},
|
|
2944
|
+
{ info: 0, warning: 0, error: 0 }
|
|
2945
|
+
);
|
|
2946
|
+
const compatCounts = countIssuesBySeverity(issuesByCategory.compatibility);
|
|
2947
|
+
const changeCounts = countIssuesBySeverity(issuesByCategory.change);
|
|
2948
|
+
lines.push("## Dashboard");
|
|
2949
|
+
lines.push("");
|
|
2950
|
+
lines.push("### Summary");
|
|
2737
2951
|
lines.push("");
|
|
2738
2952
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
2739
2953
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
@@ -2741,10 +2955,141 @@ function formatReportMarkdown(data) {
|
|
|
2741
2955
|
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
2742
2956
|
);
|
|
2743
2957
|
lines.push(
|
|
2744
|
-
`- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
2958
|
+
`- issues(total): info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
2959
|
+
);
|
|
2960
|
+
lines.push(
|
|
2961
|
+
`- issues(compatibility): info ${compatCounts.info} / warning ${compatCounts.warning} / error ${compatCounts.error}`
|
|
2962
|
+
);
|
|
2963
|
+
lines.push(
|
|
2964
|
+
`- issues(change): info ${changeCounts.info} / warning ${changeCounts.warning} / error ${changeCounts.error}`
|
|
2965
|
+
);
|
|
2966
|
+
lines.push(
|
|
2967
|
+
`- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
|
|
2968
|
+
);
|
|
2969
|
+
lines.push(
|
|
2970
|
+
`- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
|
|
2745
2971
|
);
|
|
2746
2972
|
lines.push("");
|
|
2747
|
-
lines.push("
|
|
2973
|
+
lines.push("### Next Actions");
|
|
2974
|
+
lines.push("");
|
|
2975
|
+
if (data.summary.counts.error > 0) {
|
|
2976
|
+
lines.push(
|
|
2977
|
+
"- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A `qfai validate --fail-on error` \u3092\u901A\u308B\u307E\u3067\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
2978
|
+
);
|
|
2979
|
+
lines.push(
|
|
2980
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
2981
|
+
);
|
|
2982
|
+
} else if (data.summary.counts.warning > 0) {
|
|
2983
|
+
lines.push(
|
|
2984
|
+
"- warning \u306E\u6271\u3044\u306F\u30C1\u30FC\u30E0\u5224\u65AD\u3067\u3059\u3002`--fail-on warning` \u904B\u7528\u306A\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
2985
|
+
);
|
|
2986
|
+
lines.push(
|
|
2987
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
2988
|
+
);
|
|
2989
|
+
} else {
|
|
2990
|
+
lines.push("- issue \u306F\u3042\u308A\u307E\u305B\u3093\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
2991
|
+
lines.push(
|
|
2992
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor` \u2192 `qfai validate` \u2192 `qfai report`\uFF08\u5B9A\u671F\u7684\u306B\u5B9F\u884C\uFF09"
|
|
2993
|
+
);
|
|
2994
|
+
}
|
|
2995
|
+
lines.push("");
|
|
2996
|
+
lines.push("### Index");
|
|
2997
|
+
lines.push("");
|
|
2998
|
+
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
2999
|
+
lines.push("- [Change Issues](#change-issues)");
|
|
3000
|
+
lines.push("- [IDs](#ids)");
|
|
3001
|
+
lines.push("- [Traceability](#traceability)");
|
|
3002
|
+
lines.push("");
|
|
3003
|
+
const formatIssueSummaryTable = (issues) => {
|
|
3004
|
+
const issueKeyToCount = /* @__PURE__ */ new Map();
|
|
3005
|
+
for (const issue7 of issues) {
|
|
3006
|
+
const key = `${issue7.category}|${issue7.severity}|${issue7.code}`;
|
|
3007
|
+
const current = issueKeyToCount.get(key);
|
|
3008
|
+
if (current) {
|
|
3009
|
+
current.count += 1;
|
|
3010
|
+
continue;
|
|
3011
|
+
}
|
|
3012
|
+
issueKeyToCount.set(key, {
|
|
3013
|
+
category: issue7.category,
|
|
3014
|
+
severity: issue7.severity,
|
|
3015
|
+
code: issue7.code,
|
|
3016
|
+
count: 1
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
const rows = Array.from(issueKeyToCount.values()).sort((a, b) => {
|
|
3020
|
+
const ca = categoryOrder[a.category] ?? 999;
|
|
3021
|
+
const cb = categoryOrder[b.category] ?? 999;
|
|
3022
|
+
if (ca !== cb) return ca - cb;
|
|
3023
|
+
const sa = severityOrder[a.severity] ?? 999;
|
|
3024
|
+
const sb = severityOrder[b.severity] ?? 999;
|
|
3025
|
+
if (sa !== sb) return sa - sb;
|
|
3026
|
+
return a.code.localeCompare(b.code);
|
|
3027
|
+
}).map((x) => [x.severity, x.code, String(x.count)]);
|
|
3028
|
+
return rows.length === 0 ? ["- (none)"] : formatMarkdownTable(["Severity", "Code", "Count"], rows);
|
|
3029
|
+
};
|
|
3030
|
+
const formatIssueCards = (issues) => {
|
|
3031
|
+
const sorted = [...issues].sort((a, b) => {
|
|
3032
|
+
const sa = severityOrder[a.severity] ?? 999;
|
|
3033
|
+
const sb = severityOrder[b.severity] ?? 999;
|
|
3034
|
+
if (sa !== sb) return sa - sb;
|
|
3035
|
+
const code = a.code.localeCompare(b.code);
|
|
3036
|
+
if (code !== 0) return code;
|
|
3037
|
+
const fileA = a.file ?? "";
|
|
3038
|
+
const fileB = b.file ?? "";
|
|
3039
|
+
const file = fileA.localeCompare(fileB);
|
|
3040
|
+
if (file !== 0) return file;
|
|
3041
|
+
const lineA = a.loc?.line ?? 0;
|
|
3042
|
+
const lineB = b.loc?.line ?? 0;
|
|
3043
|
+
return lineA - lineB;
|
|
3044
|
+
});
|
|
3045
|
+
if (sorted.length === 0) {
|
|
3046
|
+
return ["- (none)"];
|
|
3047
|
+
}
|
|
3048
|
+
const out = [];
|
|
3049
|
+
for (const item of sorted) {
|
|
3050
|
+
out.push(
|
|
3051
|
+
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
3052
|
+
);
|
|
3053
|
+
if (item.file) {
|
|
3054
|
+
const loc = item.loc?.line ? `:${item.loc.line}` : "";
|
|
3055
|
+
out.push(`- file: ${item.file}${loc}`);
|
|
3056
|
+
}
|
|
3057
|
+
if (item.rule) {
|
|
3058
|
+
out.push(`- rule: ${item.rule}`);
|
|
3059
|
+
}
|
|
3060
|
+
if (item.refs && item.refs.length > 0) {
|
|
3061
|
+
out.push(`- refs: ${item.refs.join(", ")}`);
|
|
3062
|
+
}
|
|
3063
|
+
if (item.suggested_action) {
|
|
3064
|
+
out.push("- suggested_action:");
|
|
3065
|
+
const actionLines = String(item.suggested_action).split("\n");
|
|
3066
|
+
for (const line of actionLines) {
|
|
3067
|
+
out.push(` ${line}`);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
out.push("");
|
|
3071
|
+
}
|
|
3072
|
+
return out;
|
|
3073
|
+
};
|
|
3074
|
+
lines.push("## Compatibility Issues");
|
|
3075
|
+
lines.push("");
|
|
3076
|
+
lines.push("### Summary");
|
|
3077
|
+
lines.push("");
|
|
3078
|
+
lines.push(...formatIssueSummaryTable(issuesByCategory.compatibility));
|
|
3079
|
+
lines.push("");
|
|
3080
|
+
lines.push("### Issues");
|
|
3081
|
+
lines.push("");
|
|
3082
|
+
lines.push(...formatIssueCards(issuesByCategory.compatibility));
|
|
3083
|
+
lines.push("## Change Issues");
|
|
3084
|
+
lines.push("");
|
|
3085
|
+
lines.push("### Summary");
|
|
3086
|
+
lines.push("");
|
|
3087
|
+
lines.push(...formatIssueSummaryTable(issuesByCategory.change));
|
|
3088
|
+
lines.push("");
|
|
3089
|
+
lines.push("### Issues");
|
|
3090
|
+
lines.push("");
|
|
3091
|
+
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
3092
|
+
lines.push("## IDs");
|
|
2748
3093
|
lines.push("");
|
|
2749
3094
|
lines.push(formatIdLine("SPEC", data.ids.spec));
|
|
2750
3095
|
lines.push(formatIdLine("BR", data.ids.br));
|
|
@@ -2753,14 +3098,14 @@ function formatReportMarkdown(data) {
|
|
|
2753
3098
|
lines.push(formatIdLine("API", data.ids.api));
|
|
2754
3099
|
lines.push(formatIdLine("DB", data.ids.db));
|
|
2755
3100
|
lines.push("");
|
|
2756
|
-
lines.push("##
|
|
3101
|
+
lines.push("## Traceability");
|
|
2757
3102
|
lines.push("");
|
|
2758
3103
|
lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
|
|
2759
3104
|
lines.push(
|
|
2760
3105
|
`- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
|
|
2761
3106
|
);
|
|
2762
3107
|
lines.push("");
|
|
2763
|
-
lines.push("
|
|
3108
|
+
lines.push("### Contract Coverage");
|
|
2764
3109
|
lines.push("");
|
|
2765
3110
|
lines.push(`- total: ${data.traceability.contracts.total}`);
|
|
2766
3111
|
lines.push(`- referenced: ${data.traceability.contracts.referenced}`);
|
|
@@ -2769,7 +3114,7 @@ function formatReportMarkdown(data) {
|
|
|
2769
3114
|
`- specContractRefMissing: ${data.traceability.specs.contractRefMissing}`
|
|
2770
3115
|
);
|
|
2771
3116
|
lines.push("");
|
|
2772
|
-
lines.push("
|
|
3117
|
+
lines.push("### Contract \u2192 Spec");
|
|
2773
3118
|
lines.push("");
|
|
2774
3119
|
const contractToSpecs = data.traceability.contracts.idToSpecs;
|
|
2775
3120
|
const contractIds = Object.keys(contractToSpecs).sort(
|
|
@@ -2788,7 +3133,7 @@ function formatReportMarkdown(data) {
|
|
|
2788
3133
|
}
|
|
2789
3134
|
}
|
|
2790
3135
|
lines.push("");
|
|
2791
|
-
lines.push("
|
|
3136
|
+
lines.push("### Spec \u2192 Contracts");
|
|
2792
3137
|
lines.push("");
|
|
2793
3138
|
const specToContracts = data.traceability.specs.specToContracts;
|
|
2794
3139
|
const specIds = Object.keys(specToContracts).sort(
|
|
@@ -2806,7 +3151,7 @@ function formatReportMarkdown(data) {
|
|
|
2806
3151
|
lines.push(...formatMarkdownTable(["Spec", "Status", "Contracts"], rows));
|
|
2807
3152
|
}
|
|
2808
3153
|
lines.push("");
|
|
2809
|
-
lines.push("
|
|
3154
|
+
lines.push("### Specs missing contract-ref");
|
|
2810
3155
|
lines.push("");
|
|
2811
3156
|
const missingRefSpecs = data.traceability.specs.missingRefSpecs;
|
|
2812
3157
|
if (missingRefSpecs.length === 0) {
|
|
@@ -2817,7 +3162,7 @@ function formatReportMarkdown(data) {
|
|
|
2817
3162
|
}
|
|
2818
3163
|
}
|
|
2819
3164
|
lines.push("");
|
|
2820
|
-
lines.push("
|
|
3165
|
+
lines.push("### SC coverage");
|
|
2821
3166
|
lines.push("");
|
|
2822
3167
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2823
3168
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
@@ -2847,7 +3192,7 @@ function formatReportMarkdown(data) {
|
|
|
2847
3192
|
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2848
3193
|
}
|
|
2849
3194
|
lines.push("");
|
|
2850
|
-
lines.push("
|
|
3195
|
+
lines.push("### SC \u2192 referenced tests");
|
|
2851
3196
|
lines.push("");
|
|
2852
3197
|
const scRefs = data.traceability.sc.refs;
|
|
2853
3198
|
const scIds = Object.keys(scRefs).sort((a, b) => a.localeCompare(b));
|
|
@@ -2864,7 +3209,7 @@ function formatReportMarkdown(data) {
|
|
|
2864
3209
|
}
|
|
2865
3210
|
}
|
|
2866
3211
|
lines.push("");
|
|
2867
|
-
lines.push("
|
|
3212
|
+
lines.push("### Spec:SC=1:1 violations");
|
|
2868
3213
|
lines.push("");
|
|
2869
3214
|
const specScIssues = data.issues.filter(
|
|
2870
3215
|
(item) => item.code === "QFAI-TRACE-012"
|
|
@@ -2879,7 +3224,7 @@ function formatReportMarkdown(data) {
|
|
|
2879
3224
|
}
|
|
2880
3225
|
}
|
|
2881
3226
|
lines.push("");
|
|
2882
|
-
lines.push("
|
|
3227
|
+
lines.push("### Hotspots");
|
|
2883
3228
|
lines.push("");
|
|
2884
3229
|
const hotspots = buildHotspots(data.issues);
|
|
2885
3230
|
if (hotspots.length === 0) {
|
|
@@ -2892,35 +3237,28 @@ function formatReportMarkdown(data) {
|
|
|
2892
3237
|
}
|
|
2893
3238
|
}
|
|
2894
3239
|
lines.push("");
|
|
2895
|
-
lines.push("##
|
|
3240
|
+
lines.push("## Guidance");
|
|
2896
3241
|
lines.push("");
|
|
2897
|
-
|
|
2898
|
-
|
|
3242
|
+
lines.push(
|
|
3243
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
2899
3244
|
);
|
|
2900
|
-
if (
|
|
2901
|
-
lines.push("-
|
|
2902
|
-
} else {
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
`- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}`
|
|
2907
|
-
);
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
lines.push("");
|
|
2911
|
-
lines.push("## \u691C\u8A3C\u7D50\u679C");
|
|
2912
|
-
lines.push("");
|
|
2913
|
-
if (data.issues.length === 0) {
|
|
2914
|
-
lines.push("- (none)");
|
|
3245
|
+
if (data.summary.counts.error > 0) {
|
|
3246
|
+
lines.push("- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A error \u304B\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
3247
|
+
} else if (data.summary.counts.warning > 0) {
|
|
3248
|
+
lines.push(
|
|
3249
|
+
"- warning \u306E\u6271\u3044\uFF08Hard Gate \u306B\u3059\u308B\u304B\uFF09\u306F\u904B\u7528\u3067\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
3250
|
+
);
|
|
2915
3251
|
} else {
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
lines.push(
|
|
2920
|
-
`- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
|
|
2921
|
-
);
|
|
2922
|
-
}
|
|
3252
|
+
lines.push(
|
|
3253
|
+
"- issue \u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
3254
|
+
);
|
|
2923
3255
|
}
|
|
3256
|
+
lines.push(
|
|
3257
|
+
"- \u5909\u66F4\u533A\u5206\uFF08Compatibility / Change/Improvement\uFF09\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002"
|
|
3258
|
+
);
|
|
3259
|
+
lines.push(
|
|
3260
|
+
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
|
|
3261
|
+
);
|
|
2924
3262
|
return lines.join("\n");
|
|
2925
3263
|
}
|
|
2926
3264
|
function formatReportJson(data) {
|
|
@@ -2934,7 +3272,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
2934
3272
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
2935
3273
|
}
|
|
2936
3274
|
for (const file of specFiles) {
|
|
2937
|
-
const text = await (0,
|
|
3275
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2938
3276
|
const parsed = parseSpec(text, file);
|
|
2939
3277
|
const specKey = parsed.specId;
|
|
2940
3278
|
if (!specKey) {
|
|
@@ -2975,7 +3313,7 @@ async function collectIds(files) {
|
|
|
2975
3313
|
DB: /* @__PURE__ */ new Set()
|
|
2976
3314
|
};
|
|
2977
3315
|
for (const file of files) {
|
|
2978
|
-
const text = await (0,
|
|
3316
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2979
3317
|
for (const prefix of ID_PREFIXES2) {
|
|
2980
3318
|
const ids = extractIds(text, prefix);
|
|
2981
3319
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -2993,7 +3331,7 @@ async function collectIds(files) {
|
|
|
2993
3331
|
async function collectUpstreamIds(files) {
|
|
2994
3332
|
const ids = /* @__PURE__ */ new Set();
|
|
2995
3333
|
for (const file of files) {
|
|
2996
|
-
const text = await (0,
|
|
3334
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
2997
3335
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
2998
3336
|
}
|
|
2999
3337
|
return ids;
|
|
@@ -3014,7 +3352,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
3014
3352
|
}
|
|
3015
3353
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
3016
3354
|
for (const file of targetFiles) {
|
|
3017
|
-
const text = await (0,
|
|
3355
|
+
const text = await (0, import_promises15.readFile)(file, "utf-8");
|
|
3018
3356
|
if (pattern.test(text)) {
|
|
3019
3357
|
return true;
|
|
3020
3358
|
}
|