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.mjs
CHANGED
|
@@ -368,6 +368,7 @@ function configIssue(file, message) {
|
|
|
368
368
|
return {
|
|
369
369
|
code: "QFAI_CONFIG_INVALID",
|
|
370
370
|
severity: "error",
|
|
371
|
+
category: "compatibility",
|
|
371
372
|
message,
|
|
372
373
|
file,
|
|
373
374
|
rule: "config.invalid"
|
|
@@ -451,8 +452,8 @@ function isValidId(value, prefix) {
|
|
|
451
452
|
}
|
|
452
453
|
|
|
453
454
|
// src/core/report.ts
|
|
454
|
-
import { readFile as
|
|
455
|
-
import
|
|
455
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
456
|
+
import path14 from "path";
|
|
456
457
|
|
|
457
458
|
// src/core/contractIndex.ts
|
|
458
459
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -1179,8 +1180,8 @@ import { readFile as readFile4 } from "fs/promises";
|
|
|
1179
1180
|
import path7 from "path";
|
|
1180
1181
|
import { fileURLToPath } from "url";
|
|
1181
1182
|
async function resolveToolVersion() {
|
|
1182
|
-
if ("0.
|
|
1183
|
-
return "0.
|
|
1183
|
+
if ("0.8.1".length > 0) {
|
|
1184
|
+
return "0.8.1";
|
|
1184
1185
|
}
|
|
1185
1186
|
try {
|
|
1186
1187
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1491,12 +1492,16 @@ function formatError4(error) {
|
|
|
1491
1492
|
}
|
|
1492
1493
|
return String(error);
|
|
1493
1494
|
}
|
|
1494
|
-
function issue(code, message, severity, file, rule, refs) {
|
|
1495
|
+
function issue(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
1495
1496
|
const issue7 = {
|
|
1496
1497
|
code,
|
|
1497
1498
|
severity,
|
|
1499
|
+
category,
|
|
1498
1500
|
message
|
|
1499
1501
|
};
|
|
1502
|
+
if (suggested_action) {
|
|
1503
|
+
issue7.suggested_action = suggested_action;
|
|
1504
|
+
}
|
|
1500
1505
|
if (file) {
|
|
1501
1506
|
issue7.file = file;
|
|
1502
1507
|
}
|
|
@@ -1551,7 +1556,7 @@ async function validateDeltas(root, config) {
|
|
|
1551
1556
|
issues.push(
|
|
1552
1557
|
issue2(
|
|
1553
1558
|
"QFAI-DELTA-002",
|
|
1554
|
-
"delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
1559
|
+
"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",
|
|
1555
1560
|
"error",
|
|
1556
1561
|
deltaPath,
|
|
1557
1562
|
"delta.section"
|
|
@@ -1581,12 +1586,16 @@ function isMissingFileError2(error) {
|
|
|
1581
1586
|
}
|
|
1582
1587
|
return error.code === "ENOENT";
|
|
1583
1588
|
}
|
|
1584
|
-
function issue2(code, message, severity, file, rule, refs) {
|
|
1589
|
+
function issue2(code, message, severity, file, rule, refs, category = "change", suggested_action) {
|
|
1585
1590
|
const issue7 = {
|
|
1586
1591
|
code,
|
|
1587
1592
|
severity,
|
|
1593
|
+
category,
|
|
1588
1594
|
message
|
|
1589
1595
|
};
|
|
1596
|
+
if (suggested_action) {
|
|
1597
|
+
issue7.suggested_action = suggested_action;
|
|
1598
|
+
}
|
|
1590
1599
|
if (file) {
|
|
1591
1600
|
issue7.file = file;
|
|
1592
1601
|
}
|
|
@@ -1671,12 +1680,16 @@ function formatFileList(files, root) {
|
|
|
1671
1680
|
return relative.length > 0 ? relative : file;
|
|
1672
1681
|
}).join(", ");
|
|
1673
1682
|
}
|
|
1674
|
-
function issue3(code, message, severity, file, rule, refs) {
|
|
1683
|
+
function issue3(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
1675
1684
|
const issue7 = {
|
|
1676
1685
|
code,
|
|
1677
1686
|
severity,
|
|
1687
|
+
category,
|
|
1678
1688
|
message
|
|
1679
1689
|
};
|
|
1690
|
+
if (suggested_action) {
|
|
1691
|
+
issue7.suggested_action = suggested_action;
|
|
1692
|
+
}
|
|
1680
1693
|
if (file) {
|
|
1681
1694
|
issue7.file = file;
|
|
1682
1695
|
}
|
|
@@ -1689,8 +1702,164 @@ function issue3(code, message, severity, file, rule, refs) {
|
|
|
1689
1702
|
return issue7;
|
|
1690
1703
|
}
|
|
1691
1704
|
|
|
1692
|
-
// src/core/
|
|
1705
|
+
// src/core/promptsIntegrity.ts
|
|
1693
1706
|
import { readFile as readFile8 } from "fs/promises";
|
|
1707
|
+
import path13 from "path";
|
|
1708
|
+
|
|
1709
|
+
// src/shared/assets.ts
|
|
1710
|
+
import { existsSync } from "fs";
|
|
1711
|
+
import path12 from "path";
|
|
1712
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1713
|
+
function getInitAssetsDir() {
|
|
1714
|
+
const base = import.meta.url;
|
|
1715
|
+
const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
|
|
1716
|
+
const baseDir = path12.dirname(basePath);
|
|
1717
|
+
const candidates = [
|
|
1718
|
+
path12.resolve(baseDir, "../../../assets/init"),
|
|
1719
|
+
path12.resolve(baseDir, "../../assets/init")
|
|
1720
|
+
];
|
|
1721
|
+
for (const candidate of candidates) {
|
|
1722
|
+
if (existsSync(candidate)) {
|
|
1723
|
+
return candidate;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
throw new Error(
|
|
1727
|
+
[
|
|
1728
|
+
"init \u7528\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Template assets not found.",
|
|
1729
|
+
"\u78BA\u8A8D\u3057\u305F\u30D1\u30B9 / Checked paths:",
|
|
1730
|
+
...candidates.map((candidate) => `- ${candidate}`)
|
|
1731
|
+
].join("\n")
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/core/promptsIntegrity.ts
|
|
1736
|
+
async function diffProjectPromptsAgainstInitAssets(root) {
|
|
1737
|
+
const promptsDir = path13.resolve(root, ".qfai", "prompts");
|
|
1738
|
+
let templateDir;
|
|
1739
|
+
try {
|
|
1740
|
+
templateDir = path13.join(getInitAssetsDir(), ".qfai", "prompts");
|
|
1741
|
+
} catch {
|
|
1742
|
+
return {
|
|
1743
|
+
status: "skipped_missing_assets",
|
|
1744
|
+
promptsDir,
|
|
1745
|
+
templateDir: "",
|
|
1746
|
+
missing: [],
|
|
1747
|
+
extra: [],
|
|
1748
|
+
changed: []
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
const projectFiles = await collectFiles(promptsDir);
|
|
1752
|
+
if (projectFiles.length === 0) {
|
|
1753
|
+
return {
|
|
1754
|
+
status: "skipped_missing_prompts",
|
|
1755
|
+
promptsDir,
|
|
1756
|
+
templateDir,
|
|
1757
|
+
missing: [],
|
|
1758
|
+
extra: [],
|
|
1759
|
+
changed: []
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
const templateFiles = await collectFiles(templateDir);
|
|
1763
|
+
const templateByRel = /* @__PURE__ */ new Map();
|
|
1764
|
+
for (const abs of templateFiles) {
|
|
1765
|
+
templateByRel.set(toRel(templateDir, abs), abs);
|
|
1766
|
+
}
|
|
1767
|
+
const projectByRel = /* @__PURE__ */ new Map();
|
|
1768
|
+
for (const abs of projectFiles) {
|
|
1769
|
+
projectByRel.set(toRel(promptsDir, abs), abs);
|
|
1770
|
+
}
|
|
1771
|
+
const missing = [];
|
|
1772
|
+
const extra = [];
|
|
1773
|
+
const changed = [];
|
|
1774
|
+
for (const rel of templateByRel.keys()) {
|
|
1775
|
+
if (!projectByRel.has(rel)) {
|
|
1776
|
+
missing.push(rel);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
for (const rel of projectByRel.keys()) {
|
|
1780
|
+
if (!templateByRel.has(rel)) {
|
|
1781
|
+
extra.push(rel);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const common = intersectKeys(templateByRel, projectByRel);
|
|
1785
|
+
for (const rel of common) {
|
|
1786
|
+
const templateAbs = templateByRel.get(rel);
|
|
1787
|
+
const projectAbs = projectByRel.get(rel);
|
|
1788
|
+
if (!templateAbs || !projectAbs) {
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
try {
|
|
1792
|
+
const [a, b] = await Promise.all([
|
|
1793
|
+
readFile8(templateAbs, "utf-8"),
|
|
1794
|
+
readFile8(projectAbs, "utf-8")
|
|
1795
|
+
]);
|
|
1796
|
+
if (normalizeNewlines(a) !== normalizeNewlines(b)) {
|
|
1797
|
+
changed.push(rel);
|
|
1798
|
+
}
|
|
1799
|
+
} catch {
|
|
1800
|
+
changed.push(rel);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
const status = missing.length > 0 || extra.length > 0 || changed.length > 0 ? "modified" : "ok";
|
|
1804
|
+
return {
|
|
1805
|
+
status,
|
|
1806
|
+
promptsDir,
|
|
1807
|
+
templateDir,
|
|
1808
|
+
missing: missing.sort(),
|
|
1809
|
+
extra: extra.sort(),
|
|
1810
|
+
changed: changed.sort()
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
function normalizeNewlines(text) {
|
|
1814
|
+
return text.replace(/\r\n/g, "\n");
|
|
1815
|
+
}
|
|
1816
|
+
function toRel(base, abs) {
|
|
1817
|
+
const rel = path13.relative(base, abs);
|
|
1818
|
+
return rel.replace(/[\\/]+/g, "/");
|
|
1819
|
+
}
|
|
1820
|
+
function intersectKeys(a, b) {
|
|
1821
|
+
const out = [];
|
|
1822
|
+
for (const key of a.keys()) {
|
|
1823
|
+
if (b.has(key)) {
|
|
1824
|
+
out.push(key);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
return out;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// src/core/validators/promptsIntegrity.ts
|
|
1831
|
+
async function validatePromptsIntegrity(root) {
|
|
1832
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
1833
|
+
if (diff.status !== "modified") {
|
|
1834
|
+
return [];
|
|
1835
|
+
}
|
|
1836
|
+
const total = diff.missing.length + diff.extra.length + diff.changed.length;
|
|
1837
|
+
const hints = [
|
|
1838
|
+
diff.changed.length > 0 ? `\u5909\u66F4: ${diff.changed.length}` : null,
|
|
1839
|
+
diff.missing.length > 0 ? `\u524A\u9664: ${diff.missing.length}` : null,
|
|
1840
|
+
diff.extra.length > 0 ? `\u8FFD\u52A0: ${diff.extra.length}` : null
|
|
1841
|
+
].filter(Boolean).join(" / ");
|
|
1842
|
+
const sample = [...diff.changed, ...diff.missing, ...diff.extra].slice(0, 10);
|
|
1843
|
+
const sampleText = sample.length > 0 ? ` \u4F8B: ${sample.join(", ")}` : "";
|
|
1844
|
+
return [
|
|
1845
|
+
{
|
|
1846
|
+
code: "QFAI-PROMPTS-001",
|
|
1847
|
+
severity: "error",
|
|
1848
|
+
category: "change",
|
|
1849
|
+
message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
|
|
1850
|
+
suggested_action: [
|
|
1851
|
+
"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",
|
|
1852
|
+
"\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
1853
|
+
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
1854
|
+
"- \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"
|
|
1855
|
+
].join("\n"),
|
|
1856
|
+
rule: "prompts.integrity"
|
|
1857
|
+
}
|
|
1858
|
+
];
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// src/core/validators/scenario.ts
|
|
1862
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
1694
1863
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
1695
1864
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
1696
1865
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -1716,7 +1885,7 @@ async function validateScenarios(root, config) {
|
|
|
1716
1885
|
for (const entry of entries) {
|
|
1717
1886
|
let text;
|
|
1718
1887
|
try {
|
|
1719
|
-
text = await
|
|
1888
|
+
text = await readFile9(entry.scenarioPath, "utf-8");
|
|
1720
1889
|
} catch (error) {
|
|
1721
1890
|
if (isMissingFileError3(error)) {
|
|
1722
1891
|
issues.push(
|
|
@@ -1861,12 +2030,16 @@ function validateScenarioContent(text, file) {
|
|
|
1861
2030
|
}
|
|
1862
2031
|
return issues;
|
|
1863
2032
|
}
|
|
1864
|
-
function issue4(code, message, severity, file, rule, refs) {
|
|
2033
|
+
function issue4(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
1865
2034
|
const issue7 = {
|
|
1866
2035
|
code,
|
|
1867
2036
|
severity,
|
|
2037
|
+
category,
|
|
1868
2038
|
message
|
|
1869
2039
|
};
|
|
2040
|
+
if (suggested_action) {
|
|
2041
|
+
issue7.suggested_action = suggested_action;
|
|
2042
|
+
}
|
|
1870
2043
|
if (file) {
|
|
1871
2044
|
issue7.file = file;
|
|
1872
2045
|
}
|
|
@@ -1886,7 +2059,7 @@ function isMissingFileError3(error) {
|
|
|
1886
2059
|
}
|
|
1887
2060
|
|
|
1888
2061
|
// src/core/validators/spec.ts
|
|
1889
|
-
import { readFile as
|
|
2062
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
1890
2063
|
async function validateSpecs(root, config) {
|
|
1891
2064
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
1892
2065
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1907,7 +2080,7 @@ async function validateSpecs(root, config) {
|
|
|
1907
2080
|
for (const entry of entries) {
|
|
1908
2081
|
let text;
|
|
1909
2082
|
try {
|
|
1910
|
-
text = await
|
|
2083
|
+
text = await readFile10(entry.specPath, "utf-8");
|
|
1911
2084
|
} catch (error) {
|
|
1912
2085
|
if (isMissingFileError4(error)) {
|
|
1913
2086
|
issues.push(
|
|
@@ -2031,12 +2204,16 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
2031
2204
|
}
|
|
2032
2205
|
return issues;
|
|
2033
2206
|
}
|
|
2034
|
-
function issue5(code, message, severity, file, rule, refs) {
|
|
2207
|
+
function issue5(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2035
2208
|
const issue7 = {
|
|
2036
2209
|
code,
|
|
2037
2210
|
severity,
|
|
2211
|
+
category,
|
|
2038
2212
|
message
|
|
2039
2213
|
};
|
|
2214
|
+
if (suggested_action) {
|
|
2215
|
+
issue7.suggested_action = suggested_action;
|
|
2216
|
+
}
|
|
2040
2217
|
if (file) {
|
|
2041
2218
|
issue7.file = file;
|
|
2042
2219
|
}
|
|
@@ -2056,7 +2233,7 @@ function isMissingFileError4(error) {
|
|
|
2056
2233
|
}
|
|
2057
2234
|
|
|
2058
2235
|
// src/core/validators/traceability.ts
|
|
2059
|
-
import { readFile as
|
|
2236
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
2060
2237
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2061
2238
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2062
2239
|
async function validateTraceability(root, config) {
|
|
@@ -2076,7 +2253,7 @@ async function validateTraceability(root, config) {
|
|
|
2076
2253
|
const contractIndex = await buildContractIndex(root, config);
|
|
2077
2254
|
const contractIds = contractIndex.ids;
|
|
2078
2255
|
for (const file of specFiles) {
|
|
2079
|
-
const text = await
|
|
2256
|
+
const text = await readFile11(file, "utf-8");
|
|
2080
2257
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2081
2258
|
const parsed = parseSpec(text, file);
|
|
2082
2259
|
if (parsed.specId) {
|
|
@@ -2094,7 +2271,7 @@ async function validateTraceability(root, config) {
|
|
|
2094
2271
|
issues.push(
|
|
2095
2272
|
issue6(
|
|
2096
2273
|
"QFAI-TRACE-020",
|
|
2097
|
-
"Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
2274
|
+
"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`",
|
|
2098
2275
|
"error",
|
|
2099
2276
|
file,
|
|
2100
2277
|
"traceability.specContractRefRequired"
|
|
@@ -2105,7 +2282,7 @@ async function validateTraceability(root, config) {
|
|
|
2105
2282
|
issues.push(
|
|
2106
2283
|
issue6(
|
|
2107
2284
|
"QFAI-TRACE-023",
|
|
2108
|
-
"Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
2285
|
+
"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",
|
|
2109
2286
|
"error",
|
|
2110
2287
|
file,
|
|
2111
2288
|
"traceability.specContractRefFormat"
|
|
@@ -2118,7 +2295,7 @@ async function validateTraceability(root, config) {
|
|
|
2118
2295
|
"QFAI-TRACE-021",
|
|
2119
2296
|
`Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
|
|
2120
2297
|
", "
|
|
2121
|
-
)}`,
|
|
2298
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2122
2299
|
"error",
|
|
2123
2300
|
file,
|
|
2124
2301
|
"traceability.specContractRefFormat",
|
|
@@ -2149,7 +2326,7 @@ async function validateTraceability(root, config) {
|
|
|
2149
2326
|
}
|
|
2150
2327
|
}
|
|
2151
2328
|
for (const file of scenarioFiles) {
|
|
2152
|
-
const text = await
|
|
2329
|
+
const text = await readFile11(file, "utf-8");
|
|
2153
2330
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2154
2331
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
2155
2332
|
allowCommentPrefix: true
|
|
@@ -2158,7 +2335,7 @@ async function validateTraceability(root, config) {
|
|
|
2158
2335
|
issues.push(
|
|
2159
2336
|
issue6(
|
|
2160
2337
|
"QFAI-TRACE-031",
|
|
2161
|
-
"Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
2338
|
+
"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`",
|
|
2162
2339
|
"error",
|
|
2163
2340
|
file,
|
|
2164
2341
|
"traceability.scenarioContractRefRequired"
|
|
@@ -2169,7 +2346,7 @@ async function validateTraceability(root, config) {
|
|
|
2169
2346
|
issues.push(
|
|
2170
2347
|
issue6(
|
|
2171
2348
|
"QFAI-TRACE-033",
|
|
2172
|
-
"Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
|
|
2349
|
+
"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",
|
|
2173
2350
|
"error",
|
|
2174
2351
|
file,
|
|
2175
2352
|
"traceability.scenarioContractRefFormat"
|
|
@@ -2182,7 +2359,7 @@ async function validateTraceability(root, config) {
|
|
|
2182
2359
|
"QFAI-TRACE-032",
|
|
2183
2360
|
`Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
|
|
2184
2361
|
", "
|
|
2185
|
-
)}`,
|
|
2362
|
+
)} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
|
|
2186
2363
|
"error",
|
|
2187
2364
|
file,
|
|
2188
2365
|
"traceability.scenarioContractRefFormat",
|
|
@@ -2471,7 +2648,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2471
2648
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
2472
2649
|
let found = false;
|
|
2473
2650
|
for (const file of targetFiles) {
|
|
2474
|
-
const text = await
|
|
2651
|
+
const text = await readFile11(file, "utf-8");
|
|
2475
2652
|
if (pattern.test(text)) {
|
|
2476
2653
|
found = true;
|
|
2477
2654
|
break;
|
|
@@ -2494,12 +2671,16 @@ function buildIdPattern(ids) {
|
|
|
2494
2671
|
const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
2495
2672
|
return new RegExp(`\\b(${escaped.join("|")})\\b`);
|
|
2496
2673
|
}
|
|
2497
|
-
function issue6(code, message, severity, file, rule, refs) {
|
|
2674
|
+
function issue6(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2498
2675
|
const issue7 = {
|
|
2499
2676
|
code,
|
|
2500
2677
|
severity,
|
|
2678
|
+
category,
|
|
2501
2679
|
message
|
|
2502
2680
|
};
|
|
2681
|
+
if (suggested_action) {
|
|
2682
|
+
issue7.suggested_action = suggested_action;
|
|
2683
|
+
}
|
|
2503
2684
|
if (file) {
|
|
2504
2685
|
issue7.file = file;
|
|
2505
2686
|
}
|
|
@@ -2518,6 +2699,7 @@ async function validateProject(root, configResult) {
|
|
|
2518
2699
|
const { config, issues: configIssues } = resolved;
|
|
2519
2700
|
const issues = [
|
|
2520
2701
|
...configIssues,
|
|
2702
|
+
...await validatePromptsIntegrity(root),
|
|
2521
2703
|
...await validateSpecs(root, config),
|
|
2522
2704
|
...await validateDeltas(root, config),
|
|
2523
2705
|
...await validateScenarios(root, config),
|
|
@@ -2558,15 +2740,15 @@ function countIssues(issues) {
|
|
|
2558
2740
|
// src/core/report.ts
|
|
2559
2741
|
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
2560
2742
|
async function createReportData(root, validation, configResult) {
|
|
2561
|
-
const resolvedRoot =
|
|
2743
|
+
const resolvedRoot = path14.resolve(root);
|
|
2562
2744
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
2563
2745
|
const config = resolved.config;
|
|
2564
2746
|
const configPath = resolved.configPath;
|
|
2565
2747
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
2566
2748
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
2567
|
-
const apiRoot =
|
|
2568
|
-
const uiRoot =
|
|
2569
|
-
const dbRoot =
|
|
2749
|
+
const apiRoot = path14.join(contractsRoot, "api");
|
|
2750
|
+
const uiRoot = path14.join(contractsRoot, "ui");
|
|
2751
|
+
const dbRoot = path14.join(contractsRoot, "db");
|
|
2570
2752
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
2571
2753
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
2572
2754
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -2680,7 +2862,39 @@ function formatReportMarkdown(data) {
|
|
|
2680
2862
|
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
2681
2863
|
lines.push(`- \u7248: ${data.version}`);
|
|
2682
2864
|
lines.push("");
|
|
2683
|
-
|
|
2865
|
+
const severityOrder = {
|
|
2866
|
+
error: 0,
|
|
2867
|
+
warning: 1,
|
|
2868
|
+
info: 2
|
|
2869
|
+
};
|
|
2870
|
+
const categoryOrder = {
|
|
2871
|
+
compatibility: 0,
|
|
2872
|
+
change: 1
|
|
2873
|
+
};
|
|
2874
|
+
const issuesByCategory = {
|
|
2875
|
+
compatibility: [],
|
|
2876
|
+
change: []
|
|
2877
|
+
};
|
|
2878
|
+
for (const issue7 of data.issues) {
|
|
2879
|
+
const cat = issue7.category;
|
|
2880
|
+
if (cat === "change") {
|
|
2881
|
+
issuesByCategory.change.push(issue7);
|
|
2882
|
+
} else {
|
|
2883
|
+
issuesByCategory.compatibility.push(issue7);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
const countIssuesBySeverity = (issues) => issues.reduce(
|
|
2887
|
+
(acc, i) => {
|
|
2888
|
+
acc[i.severity] += 1;
|
|
2889
|
+
return acc;
|
|
2890
|
+
},
|
|
2891
|
+
{ info: 0, warning: 0, error: 0 }
|
|
2892
|
+
);
|
|
2893
|
+
const compatCounts = countIssuesBySeverity(issuesByCategory.compatibility);
|
|
2894
|
+
const changeCounts = countIssuesBySeverity(issuesByCategory.change);
|
|
2895
|
+
lines.push("## Dashboard");
|
|
2896
|
+
lines.push("");
|
|
2897
|
+
lines.push("### Summary");
|
|
2684
2898
|
lines.push("");
|
|
2685
2899
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
2686
2900
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
@@ -2688,10 +2902,141 @@ function formatReportMarkdown(data) {
|
|
|
2688
2902
|
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
2689
2903
|
);
|
|
2690
2904
|
lines.push(
|
|
2691
|
-
`- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
2905
|
+
`- issues(total): info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
2906
|
+
);
|
|
2907
|
+
lines.push(
|
|
2908
|
+
`- issues(compatibility): info ${compatCounts.info} / warning ${compatCounts.warning} / error ${compatCounts.error}`
|
|
2909
|
+
);
|
|
2910
|
+
lines.push(
|
|
2911
|
+
`- issues(change): info ${changeCounts.info} / warning ${changeCounts.warning} / error ${changeCounts.error}`
|
|
2912
|
+
);
|
|
2913
|
+
lines.push(
|
|
2914
|
+
`- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
|
|
2915
|
+
);
|
|
2916
|
+
lines.push(
|
|
2917
|
+
`- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
|
|
2692
2918
|
);
|
|
2693
2919
|
lines.push("");
|
|
2694
|
-
lines.push("
|
|
2920
|
+
lines.push("### Next Actions");
|
|
2921
|
+
lines.push("");
|
|
2922
|
+
if (data.summary.counts.error > 0) {
|
|
2923
|
+
lines.push(
|
|
2924
|
+
"- 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"
|
|
2925
|
+
);
|
|
2926
|
+
lines.push(
|
|
2927
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
2928
|
+
);
|
|
2929
|
+
} else if (data.summary.counts.warning > 0) {
|
|
2930
|
+
lines.push(
|
|
2931
|
+
"- 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"
|
|
2932
|
+
);
|
|
2933
|
+
lines.push(
|
|
2934
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
2935
|
+
);
|
|
2936
|
+
} else {
|
|
2937
|
+
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");
|
|
2938
|
+
lines.push(
|
|
2939
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor` \u2192 `qfai validate` \u2192 `qfai report`\uFF08\u5B9A\u671F\u7684\u306B\u5B9F\u884C\uFF09"
|
|
2940
|
+
);
|
|
2941
|
+
}
|
|
2942
|
+
lines.push("");
|
|
2943
|
+
lines.push("### Index");
|
|
2944
|
+
lines.push("");
|
|
2945
|
+
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
2946
|
+
lines.push("- [Change Issues](#change-issues)");
|
|
2947
|
+
lines.push("- [IDs](#ids)");
|
|
2948
|
+
lines.push("- [Traceability](#traceability)");
|
|
2949
|
+
lines.push("");
|
|
2950
|
+
const formatIssueSummaryTable = (issues) => {
|
|
2951
|
+
const issueKeyToCount = /* @__PURE__ */ new Map();
|
|
2952
|
+
for (const issue7 of issues) {
|
|
2953
|
+
const key = `${issue7.category}|${issue7.severity}|${issue7.code}`;
|
|
2954
|
+
const current = issueKeyToCount.get(key);
|
|
2955
|
+
if (current) {
|
|
2956
|
+
current.count += 1;
|
|
2957
|
+
continue;
|
|
2958
|
+
}
|
|
2959
|
+
issueKeyToCount.set(key, {
|
|
2960
|
+
category: issue7.category,
|
|
2961
|
+
severity: issue7.severity,
|
|
2962
|
+
code: issue7.code,
|
|
2963
|
+
count: 1
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
const rows = Array.from(issueKeyToCount.values()).sort((a, b) => {
|
|
2967
|
+
const ca = categoryOrder[a.category] ?? 999;
|
|
2968
|
+
const cb = categoryOrder[b.category] ?? 999;
|
|
2969
|
+
if (ca !== cb) return ca - cb;
|
|
2970
|
+
const sa = severityOrder[a.severity] ?? 999;
|
|
2971
|
+
const sb = severityOrder[b.severity] ?? 999;
|
|
2972
|
+
if (sa !== sb) return sa - sb;
|
|
2973
|
+
return a.code.localeCompare(b.code);
|
|
2974
|
+
}).map((x) => [x.severity, x.code, String(x.count)]);
|
|
2975
|
+
return rows.length === 0 ? ["- (none)"] : formatMarkdownTable(["Severity", "Code", "Count"], rows);
|
|
2976
|
+
};
|
|
2977
|
+
const formatIssueCards = (issues) => {
|
|
2978
|
+
const sorted = [...issues].sort((a, b) => {
|
|
2979
|
+
const sa = severityOrder[a.severity] ?? 999;
|
|
2980
|
+
const sb = severityOrder[b.severity] ?? 999;
|
|
2981
|
+
if (sa !== sb) return sa - sb;
|
|
2982
|
+
const code = a.code.localeCompare(b.code);
|
|
2983
|
+
if (code !== 0) return code;
|
|
2984
|
+
const fileA = a.file ?? "";
|
|
2985
|
+
const fileB = b.file ?? "";
|
|
2986
|
+
const file = fileA.localeCompare(fileB);
|
|
2987
|
+
if (file !== 0) return file;
|
|
2988
|
+
const lineA = a.loc?.line ?? 0;
|
|
2989
|
+
const lineB = b.loc?.line ?? 0;
|
|
2990
|
+
return lineA - lineB;
|
|
2991
|
+
});
|
|
2992
|
+
if (sorted.length === 0) {
|
|
2993
|
+
return ["- (none)"];
|
|
2994
|
+
}
|
|
2995
|
+
const out = [];
|
|
2996
|
+
for (const item of sorted) {
|
|
2997
|
+
out.push(
|
|
2998
|
+
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
2999
|
+
);
|
|
3000
|
+
if (item.file) {
|
|
3001
|
+
const loc = item.loc?.line ? `:${item.loc.line}` : "";
|
|
3002
|
+
out.push(`- file: ${item.file}${loc}`);
|
|
3003
|
+
}
|
|
3004
|
+
if (item.rule) {
|
|
3005
|
+
out.push(`- rule: ${item.rule}`);
|
|
3006
|
+
}
|
|
3007
|
+
if (item.refs && item.refs.length > 0) {
|
|
3008
|
+
out.push(`- refs: ${item.refs.join(", ")}`);
|
|
3009
|
+
}
|
|
3010
|
+
if (item.suggested_action) {
|
|
3011
|
+
out.push("- suggested_action:");
|
|
3012
|
+
const actionLines = String(item.suggested_action).split("\n");
|
|
3013
|
+
for (const line of actionLines) {
|
|
3014
|
+
out.push(` ${line}`);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
out.push("");
|
|
3018
|
+
}
|
|
3019
|
+
return out;
|
|
3020
|
+
};
|
|
3021
|
+
lines.push("## Compatibility Issues");
|
|
3022
|
+
lines.push("");
|
|
3023
|
+
lines.push("### Summary");
|
|
3024
|
+
lines.push("");
|
|
3025
|
+
lines.push(...formatIssueSummaryTable(issuesByCategory.compatibility));
|
|
3026
|
+
lines.push("");
|
|
3027
|
+
lines.push("### Issues");
|
|
3028
|
+
lines.push("");
|
|
3029
|
+
lines.push(...formatIssueCards(issuesByCategory.compatibility));
|
|
3030
|
+
lines.push("## Change Issues");
|
|
3031
|
+
lines.push("");
|
|
3032
|
+
lines.push("### Summary");
|
|
3033
|
+
lines.push("");
|
|
3034
|
+
lines.push(...formatIssueSummaryTable(issuesByCategory.change));
|
|
3035
|
+
lines.push("");
|
|
3036
|
+
lines.push("### Issues");
|
|
3037
|
+
lines.push("");
|
|
3038
|
+
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
3039
|
+
lines.push("## IDs");
|
|
2695
3040
|
lines.push("");
|
|
2696
3041
|
lines.push(formatIdLine("SPEC", data.ids.spec));
|
|
2697
3042
|
lines.push(formatIdLine("BR", data.ids.br));
|
|
@@ -2700,14 +3045,14 @@ function formatReportMarkdown(data) {
|
|
|
2700
3045
|
lines.push(formatIdLine("API", data.ids.api));
|
|
2701
3046
|
lines.push(formatIdLine("DB", data.ids.db));
|
|
2702
3047
|
lines.push("");
|
|
2703
|
-
lines.push("##
|
|
3048
|
+
lines.push("## Traceability");
|
|
2704
3049
|
lines.push("");
|
|
2705
3050
|
lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
|
|
2706
3051
|
lines.push(
|
|
2707
3052
|
`- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
|
|
2708
3053
|
);
|
|
2709
3054
|
lines.push("");
|
|
2710
|
-
lines.push("
|
|
3055
|
+
lines.push("### Contract Coverage");
|
|
2711
3056
|
lines.push("");
|
|
2712
3057
|
lines.push(`- total: ${data.traceability.contracts.total}`);
|
|
2713
3058
|
lines.push(`- referenced: ${data.traceability.contracts.referenced}`);
|
|
@@ -2716,7 +3061,7 @@ function formatReportMarkdown(data) {
|
|
|
2716
3061
|
`- specContractRefMissing: ${data.traceability.specs.contractRefMissing}`
|
|
2717
3062
|
);
|
|
2718
3063
|
lines.push("");
|
|
2719
|
-
lines.push("
|
|
3064
|
+
lines.push("### Contract \u2192 Spec");
|
|
2720
3065
|
lines.push("");
|
|
2721
3066
|
const contractToSpecs = data.traceability.contracts.idToSpecs;
|
|
2722
3067
|
const contractIds = Object.keys(contractToSpecs).sort(
|
|
@@ -2735,7 +3080,7 @@ function formatReportMarkdown(data) {
|
|
|
2735
3080
|
}
|
|
2736
3081
|
}
|
|
2737
3082
|
lines.push("");
|
|
2738
|
-
lines.push("
|
|
3083
|
+
lines.push("### Spec \u2192 Contracts");
|
|
2739
3084
|
lines.push("");
|
|
2740
3085
|
const specToContracts = data.traceability.specs.specToContracts;
|
|
2741
3086
|
const specIds = Object.keys(specToContracts).sort(
|
|
@@ -2753,7 +3098,7 @@ function formatReportMarkdown(data) {
|
|
|
2753
3098
|
lines.push(...formatMarkdownTable(["Spec", "Status", "Contracts"], rows));
|
|
2754
3099
|
}
|
|
2755
3100
|
lines.push("");
|
|
2756
|
-
lines.push("
|
|
3101
|
+
lines.push("### Specs missing contract-ref");
|
|
2757
3102
|
lines.push("");
|
|
2758
3103
|
const missingRefSpecs = data.traceability.specs.missingRefSpecs;
|
|
2759
3104
|
if (missingRefSpecs.length === 0) {
|
|
@@ -2764,7 +3109,7 @@ function formatReportMarkdown(data) {
|
|
|
2764
3109
|
}
|
|
2765
3110
|
}
|
|
2766
3111
|
lines.push("");
|
|
2767
|
-
lines.push("
|
|
3112
|
+
lines.push("### SC coverage");
|
|
2768
3113
|
lines.push("");
|
|
2769
3114
|
lines.push(`- total: ${data.traceability.sc.total}`);
|
|
2770
3115
|
lines.push(`- covered: ${data.traceability.sc.covered}`);
|
|
@@ -2794,7 +3139,7 @@ function formatReportMarkdown(data) {
|
|
|
2794
3139
|
lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
|
|
2795
3140
|
}
|
|
2796
3141
|
lines.push("");
|
|
2797
|
-
lines.push("
|
|
3142
|
+
lines.push("### SC \u2192 referenced tests");
|
|
2798
3143
|
lines.push("");
|
|
2799
3144
|
const scRefs = data.traceability.sc.refs;
|
|
2800
3145
|
const scIds = Object.keys(scRefs).sort((a, b) => a.localeCompare(b));
|
|
@@ -2811,7 +3156,7 @@ function formatReportMarkdown(data) {
|
|
|
2811
3156
|
}
|
|
2812
3157
|
}
|
|
2813
3158
|
lines.push("");
|
|
2814
|
-
lines.push("
|
|
3159
|
+
lines.push("### Spec:SC=1:1 violations");
|
|
2815
3160
|
lines.push("");
|
|
2816
3161
|
const specScIssues = data.issues.filter(
|
|
2817
3162
|
(item) => item.code === "QFAI-TRACE-012"
|
|
@@ -2826,7 +3171,7 @@ function formatReportMarkdown(data) {
|
|
|
2826
3171
|
}
|
|
2827
3172
|
}
|
|
2828
3173
|
lines.push("");
|
|
2829
|
-
lines.push("
|
|
3174
|
+
lines.push("### Hotspots");
|
|
2830
3175
|
lines.push("");
|
|
2831
3176
|
const hotspots = buildHotspots(data.issues);
|
|
2832
3177
|
if (hotspots.length === 0) {
|
|
@@ -2839,35 +3184,28 @@ function formatReportMarkdown(data) {
|
|
|
2839
3184
|
}
|
|
2840
3185
|
}
|
|
2841
3186
|
lines.push("");
|
|
2842
|
-
lines.push("##
|
|
3187
|
+
lines.push("## Guidance");
|
|
2843
3188
|
lines.push("");
|
|
2844
|
-
|
|
2845
|
-
|
|
3189
|
+
lines.push(
|
|
3190
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
2846
3191
|
);
|
|
2847
|
-
if (
|
|
2848
|
-
lines.push("-
|
|
2849
|
-
} else {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
`- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}`
|
|
2854
|
-
);
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
lines.push("");
|
|
2858
|
-
lines.push("## \u691C\u8A3C\u7D50\u679C");
|
|
2859
|
-
lines.push("");
|
|
2860
|
-
if (data.issues.length === 0) {
|
|
2861
|
-
lines.push("- (none)");
|
|
3192
|
+
if (data.summary.counts.error > 0) {
|
|
3193
|
+
lines.push("- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A error \u304B\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
|
|
3194
|
+
} else if (data.summary.counts.warning > 0) {
|
|
3195
|
+
lines.push(
|
|
3196
|
+
"- warning \u306E\u6271\u3044\uFF08Hard Gate \u306B\u3059\u308B\u304B\uFF09\u306F\u904B\u7528\u3067\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
3197
|
+
);
|
|
2862
3198
|
} else {
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
lines.push(
|
|
2867
|
-
`- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
|
|
2868
|
-
);
|
|
2869
|
-
}
|
|
3199
|
+
lines.push(
|
|
3200
|
+
"- 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"
|
|
3201
|
+
);
|
|
2870
3202
|
}
|
|
3203
|
+
lines.push(
|
|
3204
|
+
"- \u5909\u66F4\u533A\u5206\uFF08Compatibility / Change/Improvement\uFF09\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002"
|
|
3205
|
+
);
|
|
3206
|
+
lines.push(
|
|
3207
|
+
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
|
|
3208
|
+
);
|
|
2871
3209
|
return lines.join("\n");
|
|
2872
3210
|
}
|
|
2873
3211
|
function formatReportJson(data) {
|
|
@@ -2881,7 +3219,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
2881
3219
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
2882
3220
|
}
|
|
2883
3221
|
for (const file of specFiles) {
|
|
2884
|
-
const text = await
|
|
3222
|
+
const text = await readFile12(file, "utf-8");
|
|
2885
3223
|
const parsed = parseSpec(text, file);
|
|
2886
3224
|
const specKey = parsed.specId;
|
|
2887
3225
|
if (!specKey) {
|
|
@@ -2922,7 +3260,7 @@ async function collectIds(files) {
|
|
|
2922
3260
|
DB: /* @__PURE__ */ new Set()
|
|
2923
3261
|
};
|
|
2924
3262
|
for (const file of files) {
|
|
2925
|
-
const text = await
|
|
3263
|
+
const text = await readFile12(file, "utf-8");
|
|
2926
3264
|
for (const prefix of ID_PREFIXES2) {
|
|
2927
3265
|
const ids = extractIds(text, prefix);
|
|
2928
3266
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -2940,7 +3278,7 @@ async function collectIds(files) {
|
|
|
2940
3278
|
async function collectUpstreamIds(files) {
|
|
2941
3279
|
const ids = /* @__PURE__ */ new Set();
|
|
2942
3280
|
for (const file of files) {
|
|
2943
|
-
const text = await
|
|
3281
|
+
const text = await readFile12(file, "utf-8");
|
|
2944
3282
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
2945
3283
|
}
|
|
2946
3284
|
return ids;
|
|
@@ -2961,7 +3299,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
2961
3299
|
}
|
|
2962
3300
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
2963
3301
|
for (const file of targetFiles) {
|
|
2964
|
-
const text = await
|
|
3302
|
+
const text = await readFile12(file, "utf-8");
|
|
2965
3303
|
if (pattern.test(text)) {
|
|
2966
3304
|
return true;
|
|
2967
3305
|
}
|